mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
Add action-required route
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack, Group } from '@mantine/core';
|
||||||
|
import { RiAlertFill } from 'react-icons/ri';
|
||||||
|
import { Text } from '@/renderer/components';
|
||||||
|
|
||||||
|
interface ActionRequiredContainerProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ActionRequiredContainer = ({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}: ActionRequiredContainerProps) => {
|
||||||
|
return (
|
||||||
|
<Stack sx={{ cursor: 'default' }}>
|
||||||
|
<Group>
|
||||||
|
<RiAlertFill color="var(--warning-color)" size={30} />
|
||||||
|
<Text size="xl" sx={{ textTransform: 'uppercase' }}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Stack>{children}</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import isElectron from 'is-electron';
|
||||||
|
import { FileInput, Text, Button } from '@/renderer/components';
|
||||||
|
import { settings } from '@/renderer/features/settings';
|
||||||
|
|
||||||
|
export const MpvRequired = () => {
|
||||||
|
const [mpvPath, setMpvPath] = useState('');
|
||||||
|
const handleSetMpvPath = (e: File) => {
|
||||||
|
settings.set('mpv_path', e.path);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getMpvPath = async () => {
|
||||||
|
if (!isElectron()) return setMpvPath('');
|
||||||
|
const mpvPath = await settings.get('mpv_path');
|
||||||
|
return setMpvPath(mpvPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
getMpvPath();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text size="lg">
|
||||||
|
MPV is required for local playback. Set your MPV executable location
|
||||||
|
below.
|
||||||
|
</Text>
|
||||||
|
<FileInput placeholder={mpvPath} onChange={handleSetMpvPath} />
|
||||||
|
<Button onClick={() => settings.restart()}>Restart</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Text } from '@/renderer/components';
|
||||||
|
import { useAuthStore } from '@/renderer/store';
|
||||||
|
|
||||||
|
export const ServerCredentialRequired = () => {
|
||||||
|
const currentServer = useAuthStore((state) => state.currentServer);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text size="lg">
|
||||||
|
The selected server '{currentServer?.name}' requires an
|
||||||
|
additional login to access.
|
||||||
|
</Text>
|
||||||
|
<Text size="lg">
|
||||||
|
Add your credentials in the 'manage servers' menu or switch to
|
||||||
|
a different server.
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Text } from '@/renderer/components';
|
||||||
|
|
||||||
|
export const ServerRequired = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text size="xl">No server selected.</Text>
|
||||||
|
<Text>Add or select a server in the file menu.</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './routes/action-required-route';
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Center, Group, Stack } from '@mantine/core';
|
||||||
|
import isElectron from 'is-electron';
|
||||||
|
import { RiCheckFill } from 'react-icons/ri';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Button, Text } from '@/renderer/components';
|
||||||
|
import { ActionRequiredContainer } from '@/renderer/features/action-required/components/action-required-container';
|
||||||
|
import { MpvRequired } from '@/renderer/features/action-required/components/mpv-required';
|
||||||
|
import { ServerCredentialRequired } from '@/renderer/features/action-required/components/server-credential-required';
|
||||||
|
import { ServerRequired } from '@/renderer/features/action-required/components/server-required';
|
||||||
|
import { settings } from '@/renderer/features/settings';
|
||||||
|
import { useServerCredential } from '@/renderer/features/shared';
|
||||||
|
import { AppRoute } from '@/renderer/router/routes';
|
||||||
|
import { useAuthStore } from '@/renderer/store';
|
||||||
|
|
||||||
|
export const ActionRequiredRoute = () => {
|
||||||
|
const { serverToken } = useServerCredential();
|
||||||
|
const currentServer = useAuthStore((state) => state.currentServer);
|
||||||
|
const [isMpvRequired, setIsMpvRequired] = useState(false);
|
||||||
|
const isServerRequired = !currentServer;
|
||||||
|
const isCredentialRequired = currentServer?.noCredential && !serverToken;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getMpvPath = async () => {
|
||||||
|
if (!isElectron()) return setIsMpvRequired(false);
|
||||||
|
const mpvPath = await settings.get('mpv_path');
|
||||||
|
return setIsMpvRequired(!mpvPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
getMpvPath();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const checks = [
|
||||||
|
{
|
||||||
|
component: <MpvRequired />,
|
||||||
|
title: 'MPV required',
|
||||||
|
valid: !isMpvRequired,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: <ServerCredentialRequired />,
|
||||||
|
title: 'Credentials required',
|
||||||
|
valid: !isCredentialRequired,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: <ServerRequired />,
|
||||||
|
title: 'Server required',
|
||||||
|
valid: !isServerRequired,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const canReturnHome = checks.every((c) => c.valid);
|
||||||
|
const displayedCheck = checks.find((c) => !c.valid);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Center sx={{ width: '100vw' }}>
|
||||||
|
<Stack spacing="xl" sx={{ maxWidth: '50%' }}>
|
||||||
|
<Group noWrap>
|
||||||
|
{displayedCheck && (
|
||||||
|
<ActionRequiredContainer title={displayedCheck.title}>
|
||||||
|
{displayedCheck?.component}
|
||||||
|
</ActionRequiredContainer>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
<Stack mt="2rem">
|
||||||
|
{canReturnHome && (
|
||||||
|
<>
|
||||||
|
<Group noWrap position="center">
|
||||||
|
<RiCheckFill color="var(--success-color)" size={30} />
|
||||||
|
<Text size="xl">No issues found.</Text>
|
||||||
|
</Group>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
disabled={!canReturnHome}
|
||||||
|
to={AppRoute.HOME}
|
||||||
|
variant="filled"
|
||||||
|
>
|
||||||
|
Return home
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import isElectron from 'is-electron';
|
||||||
import { Navigate, Outlet, useLocation } from 'react-router-dom';
|
import { Navigate, Outlet, useLocation } from 'react-router-dom';
|
||||||
|
import { settings } from '@/renderer/features/settings';
|
||||||
|
import { useServerCredential } from '@/renderer/features/shared';
|
||||||
|
import { AppRoute } from '@/renderer/router/routes';
|
||||||
import { useAuthStore } from '@/renderer/store';
|
import { useAuthStore } from '@/renderer/store';
|
||||||
|
|
||||||
interface PrivateOutletProps {
|
interface PrivateOutletProps {
|
||||||
@@ -7,8 +12,37 @@ interface PrivateOutletProps {
|
|||||||
|
|
||||||
export const AppOutlet = ({ redirectTo }: PrivateOutletProps) => {
|
export const AppOutlet = ({ redirectTo }: PrivateOutletProps) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isAuthenticated = useAuthStore((state) => !!state.accessToken);
|
|
||||||
const logout = useAuthStore((state) => state.logout);
|
const logout = useAuthStore((state) => state.logout);
|
||||||
|
const isAuthenticated = useAuthStore((state) => !!state.accessToken);
|
||||||
|
const { serverToken } = useServerCredential();
|
||||||
|
const currentServer = useAuthStore((state) => state.currentServer);
|
||||||
|
|
||||||
|
const [isMpvRequired, setIsMpvRequired] = useState(false);
|
||||||
|
const isServerRequired = !currentServer;
|
||||||
|
const isCredentialRequired = currentServer?.noCredential && !serverToken;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getMpvPath = async () => {
|
||||||
|
if (!isElectron()) return setIsMpvRequired(false);
|
||||||
|
const mpvPath = await settings.get('mpv_path');
|
||||||
|
return setIsMpvRequired(!mpvPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
getMpvPath();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const actions = [isServerRequired, isCredentialRequired, isMpvRequired];
|
||||||
|
const actionRequired = actions.some((c) => c);
|
||||||
|
|
||||||
|
if (actionRequired) {
|
||||||
|
return (
|
||||||
|
<Navigate
|
||||||
|
replace
|
||||||
|
state={{ from: location }}
|
||||||
|
to={AppRoute.ACTION_REQUIRED}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return <Outlet />;
|
return <Outlet />;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
||||||
import { Routes, Route, Link } from 'react-router-dom';
|
import { Routes, Route, Link } from 'react-router-dom';
|
||||||
|
import { ActionRequiredRoute } from '@/renderer/features/action-required';
|
||||||
import { AlbumListRoute } from '@/renderer/features/albums';
|
import { AlbumListRoute } from '@/renderer/features/albums';
|
||||||
import { LoginRoute } from '@/renderer/features/auth';
|
import { LoginRoute } from '@/renderer/features/auth';
|
||||||
import { DashboardRoute } from '@/renderer/features/dashboard';
|
import { DashboardRoute } from '@/renderer/features/dashboard';
|
||||||
@@ -28,6 +29,12 @@ export const AppRouter = () => {
|
|||||||
</Route>
|
</Route>
|
||||||
<Route element={<></>} path={AppRoute.PLAYING} />
|
<Route element={<></>} path={AppRoute.PLAYING} />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route element={<DefaultLayout shell />}>
|
||||||
|
<Route
|
||||||
|
element={<ActionRequiredRoute />}
|
||||||
|
path={AppRoute.ACTION_REQUIRED}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Referenced from: https://betterprogramming.pub/the-best-way-to-manage-routes-in-a-react-project-with-typescript-c4e8d4422d64
|
// Referenced from: https://betterprogramming.pub/the-best-way-to-manage-routes-in-a-react-project-with-typescript-c4e8d4422d64
|
||||||
|
|
||||||
export enum AppRoute {
|
export enum AppRoute {
|
||||||
|
ACTION_REQUIRED = '/action-required',
|
||||||
EXPLORE = '/explore',
|
EXPLORE = '/explore',
|
||||||
HOME = '/',
|
HOME = '/',
|
||||||
LIBRARY_ALBUMARTISTS = '/library/album-artists',
|
LIBRARY_ALBUMARTISTS = '/library/album-artists',
|
||||||
@@ -22,6 +23,7 @@ export enum AppRoute {
|
|||||||
|
|
||||||
type TArgs =
|
type TArgs =
|
||||||
| { path: AppRoute.HOME }
|
| { path: AppRoute.HOME }
|
||||||
|
| { path: AppRoute.ACTION_REQUIRED }
|
||||||
| { path: AppRoute.NOW_PLAYING }
|
| { path: AppRoute.NOW_PLAYING }
|
||||||
| { path: AppRoute.EXPLORE }
|
| { path: AppRoute.EXPLORE }
|
||||||
| { path: AppRoute.LOGIN }
|
| { path: AppRoute.LOGIN }
|
||||||
|
|||||||
Reference in New Issue
Block a user