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",
|
"goBack": "Go back",
|
||||||
"goForward": "Go forward",
|
"goForward": "Go forward",
|
||||||
"manageServers": "Manage servers",
|
"manageServers": "Manage servers",
|
||||||
|
"logout": "Logout",
|
||||||
"privateModeOff": "Turn off private mode",
|
"privateModeOff": "Turn off private mode",
|
||||||
"privateModeOn": "Turn on private mode",
|
"privateModeOn": "Turn on private mode",
|
||||||
"openBrowserDevtools": "Open browser devtools",
|
"openBrowserDevtools": "Open browser devtools",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import qs from 'qs';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
|
import { useAuthStore } from '/@/renderer/store';
|
||||||
import { getServerUrl } from '/@/renderer/utils/normalize-server-url';
|
import { getServerUrl } from '/@/renderer/utils/normalize-server-url';
|
||||||
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
|
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
|
||||||
import { hasFeature } from '/@/shared/api/utils';
|
import { hasFeature } from '/@/shared/api/utils';
|
||||||
@@ -391,10 +392,14 @@ axiosClient.interceptors.response.use(
|
|||||||
if (data['subsonic-response'].status !== 'ok') {
|
if (data['subsonic-response'].status !== 'ok') {
|
||||||
// Suppress code related to non-linked lastfm or spotify from Navidrome
|
// Suppress code related to non-linked lastfm or spotify from Navidrome
|
||||||
if (data['subsonic-response'].error.code !== 0) {
|
if (data['subsonic-response'].error.code !== 0) {
|
||||||
toast.error({
|
const isAuthenticated = Boolean(useAuthStore.getState().currentServer?.credential);
|
||||||
message: data['subsonic-response'].error.message,
|
|
||||||
title: i18n.t('error.genericError') as string,
|
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
|
// Since we do status === 200, override this value with the error code
|
||||||
response.status = data['subsonic-response'].error.code;
|
response.status = data['subsonic-response'].error.code;
|
||||||
@@ -470,6 +475,10 @@ export const ssApiClient = (args: {
|
|||||||
|
|
||||||
return initClient(contract, {
|
return initClient(contract, {
|
||||||
api: async ({ body, headers, method, path, rawQuery }) => {
|
api: async ({ body, headers, method, path, rawQuery }) => {
|
||||||
|
if (server && !server.credential) {
|
||||||
|
throw new Error('Not authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
let baseUrl: string | undefined;
|
let baseUrl: string | undefined;
|
||||||
const authParams: Record<string, any> = {};
|
const authParams: Record<string, any> = {};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { openModal } from '@mantine/modals';
|
import { openModal } from '@mantine/modals';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import isElectron from 'is-electron';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router';
|
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 { ServerListItemWithCredential, ServerType } from '/@/shared/types/domain-types';
|
||||||
import { ServerFeature } from '/@/shared/types/features-types';
|
import { ServerFeature } from '/@/shared/types/features-types';
|
||||||
|
|
||||||
|
const localSettings = isElectron() ? window.api.localSettings : null;
|
||||||
|
|
||||||
export const ServerSelectorItems = () => {
|
export const ServerSelectorItems = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const currentServer = useCurrentServer();
|
const currentServer = useCurrentServer();
|
||||||
const serverList = useServerList();
|
const serverList = useServerList();
|
||||||
const { setCurrentServer, setMusicFolderId } = useAuthStoreActions();
|
const { logout, setCurrentServer, setMusicFolderId } = useAuthStoreActions();
|
||||||
|
|
||||||
const { data: musicFolders } = useQuery(
|
const { data: musicFolders } = useQuery(
|
||||||
currentServer
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenu.Label>{t('page.appMenu.selectServer')}</DropdownMenu.Label>
|
<DropdownMenu.Label>{t('page.appMenu.selectServer')}</DropdownMenu.Label>
|
||||||
@@ -121,13 +139,23 @@ export const ServerSelectorItems = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!isServerLock() && (
|
{!isServerLock() && (
|
||||||
<DropdownMenu.Item
|
<>
|
||||||
leftSection={<Icon icon="edit" />}
|
<DropdownMenu.Divider />
|
||||||
onClick={handleManageServersModal}
|
<DropdownMenu.Item
|
||||||
>
|
leftSection={<Icon icon="edit" />}
|
||||||
{t('page.appMenu.manageServers')}
|
onClick={handleManageServersModal}
|
||||||
</DropdownMenu.Item>
|
>
|
||||||
|
{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 && (
|
{musicFolders && musicFolders.items.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenu.Divider />
|
<DropdownMenu.Divider />
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export interface AuthSlice extends AuthState {
|
|||||||
addServer: (args: ServerListItemWithCredential) => void;
|
addServer: (args: ServerListItemWithCredential) => void;
|
||||||
deleteServer: (id: string) => void;
|
deleteServer: (id: string) => void;
|
||||||
getServer: (id: string) => null | ServerListItemWithCredential;
|
getServer: (id: string) => null | ServerListItemWithCredential;
|
||||||
|
logout: () => void;
|
||||||
setCurrentServer: (server: null | ServerListItemWithCredential) => void;
|
setCurrentServer: (server: null | ServerListItemWithCredential) => void;
|
||||||
setMusicFolderId: (musicFolderId: string[] | undefined) => void;
|
setMusicFolderId: (musicFolderId: string[] | undefined) => void;
|
||||||
updateServer: (id: string, args: Partial<ServerListItemWithCredential>) => void;
|
updateServer: (id: string, args: Partial<ServerListItemWithCredential>) => void;
|
||||||
@@ -48,6 +49,23 @@ export const useAuthStore = createWithEqualityFn<AuthSlice>()(
|
|||||||
if (server) return server;
|
if (server) return server;
|
||||||
return null;
|
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) => {
|
setCurrentServer: (server) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.currentServer = server;
|
state.currentServer = server;
|
||||||
|
|||||||
Reference in New Issue
Block a user