Add action-required route

This commit is contained in:
jeffvli
2022-10-30 01:43:32 -07:00
parent 699ed268e6
commit a60a053b6b
9 changed files with 219 additions and 1 deletions
@@ -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 &apos;{currentServer?.name}&apos; requires an
additional login to access.
</Text>
<Text size="lg">
Add your credentials in the &apos;manage servers&apos; 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>
</>
);
};
+35 -1
View File
@@ -1,4 +1,9 @@
import { useState, useEffect } from 'react';
import isElectron from 'is-electron';
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';
interface PrivateOutletProps {
@@ -7,8 +12,37 @@ interface PrivateOutletProps {
export const AppOutlet = ({ redirectTo }: PrivateOutletProps) => {
const location = useLocation();
const isAuthenticated = useAuthStore((state) => !!state.accessToken);
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) {
return <Outlet />;
+7
View File
@@ -1,5 +1,6 @@
/* eslint-disable sort-keys-fix/sort-keys-fix */
import { Routes, Route, Link } from 'react-router-dom';
import { ActionRequiredRoute } from '@/renderer/features/action-required';
import { AlbumListRoute } from '@/renderer/features/albums';
import { LoginRoute } from '@/renderer/features/auth';
import { DashboardRoute } from '@/renderer/features/dashboard';
@@ -28,6 +29,12 @@ export const AppRouter = () => {
</Route>
<Route element={<></>} path={AppRoute.PLAYING} />
</Route>
<Route element={<DefaultLayout shell />}>
<Route
element={<ActionRequiredRoute />}
path={AppRoute.ACTION_REQUIRED}
/>
</Route>
</Routes>
);
};
+2
View File
@@ -1,6 +1,7 @@
// Referenced from: https://betterprogramming.pub/the-best-way-to-manage-routes-in-a-react-project-with-typescript-c4e8d4422d64
export enum AppRoute {
ACTION_REQUIRED = '/action-required',
EXPLORE = '/explore',
HOME = '/',
LIBRARY_ALBUMARTISTS = '/library/album-artists',
@@ -22,6 +23,7 @@ export enum AppRoute {
type TArgs =
| { path: AppRoute.HOME }
| { path: AppRoute.ACTION_REQUIRED }
| { path: AppRoute.NOW_PLAYING }
| { path: AppRoute.EXPLORE }
| { path: AppRoute.LOGIN }