diff --git a/src/renderer/features/action-required/components/action-required-container.tsx b/src/renderer/features/action-required/components/action-required-container.tsx
new file mode 100644
index 000000000..52656bfe4
--- /dev/null
+++ b/src/renderer/features/action-required/components/action-required-container.tsx
@@ -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 (
+
+
+
+
+ {title}
+
+
+ {children}
+
+ );
+};
diff --git a/src/renderer/features/action-required/components/mpv-required.tsx b/src/renderer/features/action-required/components/mpv-required.tsx
new file mode 100644
index 000000000..59b7d8b9b
--- /dev/null
+++ b/src/renderer/features/action-required/components/mpv-required.tsx
@@ -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 (
+ <>
+
+ MPV is required for local playback. Set your MPV executable location
+ below.
+
+
+
+ >
+ );
+};
diff --git a/src/renderer/features/action-required/components/server-credential-required.tsx b/src/renderer/features/action-required/components/server-credential-required.tsx
new file mode 100644
index 000000000..f3a4280ca
--- /dev/null
+++ b/src/renderer/features/action-required/components/server-credential-required.tsx
@@ -0,0 +1,19 @@
+import { Text } from '@/renderer/components';
+import { useAuthStore } from '@/renderer/store';
+
+export const ServerCredentialRequired = () => {
+ const currentServer = useAuthStore((state) => state.currentServer);
+
+ return (
+ <>
+
+ The selected server '{currentServer?.name}' requires an
+ additional login to access.
+
+
+ Add your credentials in the 'manage servers' menu or switch to
+ a different server.
+
+ >
+ );
+};
diff --git a/src/renderer/features/action-required/components/server-required.tsx b/src/renderer/features/action-required/components/server-required.tsx
new file mode 100644
index 000000000..e235cd353
--- /dev/null
+++ b/src/renderer/features/action-required/components/server-required.tsx
@@ -0,0 +1,10 @@
+import { Text } from '@/renderer/components';
+
+export const ServerRequired = () => {
+ return (
+ <>
+ No server selected.
+ Add or select a server in the file menu.
+ >
+ );
+};
diff --git a/src/renderer/features/action-required/index.ts b/src/renderer/features/action-required/index.ts
new file mode 100644
index 000000000..99c1a7eaf
--- /dev/null
+++ b/src/renderer/features/action-required/index.ts
@@ -0,0 +1 @@
+export * from './routes/action-required-route';
diff --git a/src/renderer/features/action-required/routes/action-required-route.tsx b/src/renderer/features/action-required/routes/action-required-route.tsx
new file mode 100644
index 000000000..9e66bca26
--- /dev/null
+++ b/src/renderer/features/action-required/routes/action-required-route.tsx
@@ -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: ,
+ title: 'MPV required',
+ valid: !isMpvRequired,
+ },
+ {
+ component: ,
+ title: 'Credentials required',
+ valid: !isCredentialRequired,
+ },
+ {
+ component: ,
+ title: 'Server required',
+ valid: !isServerRequired,
+ },
+ ];
+
+ const canReturnHome = checks.every((c) => c.valid);
+ const displayedCheck = checks.find((c) => !c.valid);
+
+ return (
+ <>
+
+
+
+ {displayedCheck && (
+
+ {displayedCheck?.component}
+
+ )}
+
+
+ {canReturnHome && (
+ <>
+
+
+ No issues found.
+
+
+ >
+ )}
+
+
+
+ >
+ );
+};
diff --git a/src/renderer/router/app-outlet.tsx b/src/renderer/router/app-outlet.tsx
index 1f438f6f2..621f32522 100644
--- a/src/renderer/router/app-outlet.tsx
+++ b/src/renderer/router/app-outlet.tsx
@@ -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 (
+
+ );
+ }
if (isAuthenticated) {
return ;
diff --git a/src/renderer/router/app-router.tsx b/src/renderer/router/app-router.tsx
index 0f16118b7..b0352b862 100644
--- a/src/renderer/router/app-router.tsx
+++ b/src/renderer/router/app-router.tsx
@@ -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 = () => {
>} path={AppRoute.PLAYING} />
+ }>
+ }
+ path={AppRoute.ACTION_REQUIRED}
+ />
+
);
};
diff --git a/src/renderer/router/routes.ts b/src/renderer/router/routes.ts
index 82ef096e3..7a9f377b2 100644
--- a/src/renderer/router/routes.ts
+++ b/src/renderer/router/routes.ts
@@ -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 }