add logout button

This commit is contained in:
jeffvli
2026-07-18 21:31:47 -07:00
parent d960603268
commit a51299e818
4 changed files with 67 additions and 11 deletions
+1
View File
@@ -502,6 +502,7 @@
"goBack": "Go back",
"goForward": "Go forward",
"manageServers": "Manage servers",
"logout": "Logout",
"privateModeOff": "Turn off private mode",
"privateModeOn": "Turn on private mode",
"openBrowserDevtools": "Open browser devtools",
@@ -4,6 +4,7 @@ import qs from 'qs';
import { z } from 'zod';
import i18n from '/@/i18n/i18n';
import { useAuthStore } from '/@/renderer/store';
import { getServerUrl } from '/@/renderer/utils/normalize-server-url';
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
import { hasFeature } from '/@/shared/api/utils';
@@ -391,10 +392,14 @@ axiosClient.interceptors.response.use(
if (data['subsonic-response'].status !== 'ok') {
// Suppress code related to non-linked lastfm or spotify from Navidrome
if (data['subsonic-response'].error.code !== 0) {
const isAuthenticated = Boolean(useAuthStore.getState().currentServer?.credential);
if (isAuthenticated) {
toast.error({
message: data['subsonic-response'].error.message,
title: i18n.t('error.genericError') as string,
});
}
// Since we do status === 200, override this value with the error code
response.status = data['subsonic-response'].error.code;
@@ -470,6 +475,10 @@ export const ssApiClient = (args: {
return initClient(contract, {
api: async ({ body, headers, method, path, rawQuery }) => {
if (server && !server.credential) {
throw new Error('Not authenticated');
}
let baseUrl: string | undefined;
const authParams: Record<string, any> = {};
@@ -1,5 +1,6 @@
import { openModal } from '@mantine/modals';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import isElectron from 'is-electron';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router';
@@ -17,12 +18,14 @@ import { Icon } from '/@/shared/components/icon/icon';
import { ServerListItemWithCredential, ServerType } from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
const localSettings = isElectron() ? window.api.localSettings : null;
export const ServerSelectorItems = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const currentServer = useCurrentServer();
const serverList = useServerList();
const { setCurrentServer, setMusicFolderId } = useAuthStoreActions();
const { logout, setCurrentServer, setMusicFolderId } = useAuthStoreActions();
const { data: musicFolders } = useQuery(
currentServer
@@ -89,6 +92,21 @@ export const ServerSelectorItems = () => {
});
};
const handleLogout = async () => {
const serverId = currentServer.id;
// Cancel in-flight requests before clearing credentials so they don't
// retry/refetch with an empty token and surface auth error toasts.
await queryClient.cancelQueries();
localSettings?.passwordRemove(serverId);
logout();
// Defer cache clear until after authenticated routes unmount.
setTimeout(() => {
queryClient.clear();
}, 0);
};
return (
<>
<DropdownMenu.Label>{t('page.appMenu.selectServer')}</DropdownMenu.Label>
@@ -121,13 +139,23 @@ export const ServerSelectorItems = () => {
);
})}
{!isServerLock() && (
<>
<DropdownMenu.Divider />
<DropdownMenu.Item
leftSection={<Icon icon="edit" />}
onClick={handleManageServersModal}
>
{t('page.appMenu.manageServers')}
</DropdownMenu.Item>
<DropdownMenu.Item
leftSection={<Icon color="error" icon="signOut" />}
onClick={handleLogout}
>
{t('page.appMenu.logout')}
</DropdownMenu.Item>
</>
)}
{!isServerLock() && <></>}
{musicFolders && musicFolders.items.length > 0 && (
<>
<DropdownMenu.Divider />
+18
View File
@@ -12,6 +12,7 @@ export interface AuthSlice extends AuthState {
addServer: (args: ServerListItemWithCredential) => void;
deleteServer: (id: string) => void;
getServer: (id: string) => null | ServerListItemWithCredential;
logout: () => void;
setCurrentServer: (server: null | ServerListItemWithCredential) => void;
setMusicFolderId: (musicFolderId: string[] | undefined) => void;
updateServer: (id: string, args: Partial<ServerListItemWithCredential>) => void;
@@ -48,6 +49,23 @@ export const useAuthStore = createWithEqualityFn<AuthSlice>()(
if (server) return server;
return null;
},
logout: () => {
set((state) => {
const currentServer = state.currentServer;
if (!currentServer) {
return;
}
const server = state.serverList[currentServer.id];
if (server) {
server.credential = '';
server.ndCredential = undefined;
server.savePassword = false;
}
state.currentServer = null;
});
},
setCurrentServer: (server) => {
set((state) => {
state.currentServer = server;