mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-21 18:06:30 +02:00
add logout button
This commit is contained in:
@@ -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) {
|
||||
toast.error({
|
||||
message: data['subsonic-response'].error.message,
|
||||
title: i18n.t('error.genericError') as string,
|
||||
});
|
||||
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.Item
|
||||
leftSection={<Icon icon="edit" />}
|
||||
onClick={handleManageServersModal}
|
||||
>
|
||||
{t('page.appMenu.manageServers')}
|
||||
</DropdownMenu.Item>
|
||||
<>
|
||||
<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 />
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user