mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
Update react-query implementation
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
export * from './queries/use-album-detail';
|
export * from './queries/get-album-detail';
|
||||||
export * from './queries/use-album-list';
|
export * from './queries/get-album-list';
|
||||||
export * from './routes/album-list-route';
|
export * from './routes/album-list-route';
|
||||||
|
|||||||
+1
-2
@@ -3,11 +3,10 @@ import { api } from '@/renderer/api';
|
|||||||
import { queryKeys } from '@/renderer/api/query-keys';
|
import { queryKeys } from '@/renderer/api/query-keys';
|
||||||
import { QueryOptions } from '@/renderer/lib/react-query';
|
import { QueryOptions } from '@/renderer/lib/react-query';
|
||||||
import { useAuthStore } from '@/renderer/store';
|
import { useAuthStore } from '@/renderer/store';
|
||||||
import { AlbumDetailResponse } from 'renderer/api/types';
|
|
||||||
|
|
||||||
export const useAlbumDetail = (
|
export const useAlbumDetail = (
|
||||||
query: { albumId: string },
|
query: { albumId: string },
|
||||||
options: QueryOptions<AlbumDetailResponse>
|
options: QueryOptions
|
||||||
) => {
|
) => {
|
||||||
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
||||||
|
|
||||||
+11
-2
@@ -2,20 +2,28 @@ import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
|||||||
import { api } from '@/renderer/api';
|
import { api } from '@/renderer/api';
|
||||||
import { AlbumListParams } from '@/renderer/api/albums.api';
|
import { AlbumListParams } from '@/renderer/api/albums.api';
|
||||||
import { queryKeys } from '@/renderer/api/query-keys';
|
import { queryKeys } from '@/renderer/api/query-keys';
|
||||||
|
import { QueryOptions } from '@/renderer/lib/react-query';
|
||||||
import { useAuthStore } from '@/renderer/store';
|
import { useAuthStore } from '@/renderer/store';
|
||||||
import { AlbumListResponse } from 'renderer/api/types';
|
import { AlbumListResponse } from 'renderer/api/types';
|
||||||
|
|
||||||
export const useAlbumList = (params: AlbumListParams) => {
|
export const useAlbumList = (
|
||||||
|
params: AlbumListParams,
|
||||||
|
options?: QueryOptions
|
||||||
|
) => {
|
||||||
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
enabled: !!serverId,
|
enabled: !!serverId,
|
||||||
queryFn: () => api.albums.getAlbumList({ serverId }, params),
|
queryFn: () => api.albums.getAlbumList({ serverId }, params),
|
||||||
queryKey: queryKeys.albums.list(serverId, params),
|
queryKey: queryKeys.albums.list(serverId, params),
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAlbumListInfinite = (params: AlbumListParams) => {
|
export const useAlbumListInfinite = (
|
||||||
|
params: AlbumListParams,
|
||||||
|
options?: QueryOptions
|
||||||
|
) => {
|
||||||
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
||||||
|
|
||||||
return useInfiniteQuery({
|
return useInfiniteQuery({
|
||||||
@@ -29,5 +37,6 @@ export const useAlbumListInfinite = (params: AlbumListParams) => {
|
|||||||
queryFn: ({ pageParam }) =>
|
queryFn: ({ pageParam }) =>
|
||||||
api.albums.getAlbumList({ serverId }, { ...(pageParam || params) }),
|
api.albums.getAlbumList({ serverId }, { ...(pageParam || params) }),
|
||||||
queryKey: queryKeys.albums.list(serverId, params),
|
queryKey: queryKeys.albums.list(serverId, params),
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
FilterGroupType,
|
FilterGroupType,
|
||||||
encodeAdvancedFiltersQuery,
|
encodeAdvancedFiltersQuery,
|
||||||
} from '@/renderer/features/albums/components/advanced-filters';
|
} from '@/renderer/features/albums/components/advanced-filters';
|
||||||
import { useAlbumList } from '@/renderer/features/albums/queries/use-album-list';
|
import { useAlbumList } from '@/renderer/features/albums/queries/get-album-list';
|
||||||
import { useServerList } from '@/renderer/features/servers';
|
import { useServerList } from '@/renderer/features/servers';
|
||||||
import { AnimatedPage, useServerCredential } from '@/renderer/features/shared';
|
import { AnimatedPage, useServerCredential } from '@/renderer/features/shared';
|
||||||
import { AppRoute } from '@/renderer/router/routes';
|
import { AppRoute } from '@/renderer/router/routes';
|
||||||
@@ -80,7 +80,7 @@ export const AlbumListRoute = () => {
|
|||||||
const page = useAppStore((state) => state.albums);
|
const page = useAppStore((state) => state.albums);
|
||||||
const setPage = useAppStore((state) => state.setPage);
|
const setPage = useAppStore((state) => state.setPage);
|
||||||
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
||||||
const { data: servers } = useServerList({ enabled: true });
|
const serverListQuery = useServerList({ enabled: true });
|
||||||
const [filters, setFilters] = useSetState({
|
const [filters, setFilters] = useSetState({
|
||||||
orderBy: SortOrder.ASC,
|
orderBy: SortOrder.ASC,
|
||||||
serverFolderId: [] as string[],
|
serverFolderId: [] as string[],
|
||||||
@@ -107,11 +107,13 @@ export const AlbumListRoute = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const serverFolders = useMemo(() => {
|
const serverFolders = useMemo(() => {
|
||||||
const server = servers?.data.find((server) => server.id === serverId);
|
const server = serverListQuery?.data?.data.find(
|
||||||
|
(server) => server.id === serverId
|
||||||
|
);
|
||||||
return server?.serverFolders;
|
return server?.serverFolders;
|
||||||
}, [serverId, servers]);
|
}, [serverId, serverListQuery?.data?.data]);
|
||||||
|
|
||||||
const { data: albums } = useAlbumList({
|
const albumListQuery = useAlbumList({
|
||||||
advancedFilters,
|
advancedFilters,
|
||||||
orderBy: filters.orderBy,
|
orderBy: filters.orderBy,
|
||||||
serverFolderId: filters.serverFolderId,
|
serverFolderId: filters.serverFolderId,
|
||||||
@@ -454,7 +456,7 @@ export const AlbumListRoute = () => {
|
|||||||
display={page.list?.display || CardDisplayType.CARD}
|
display={page.list?.display || CardDisplayType.CARD}
|
||||||
fetchFn={fetch}
|
fetchFn={fetch}
|
||||||
height={height}
|
height={height}
|
||||||
itemCount={albums?.pagination.totalEntries || 0}
|
itemCount={albumListQuery?.data?.pagination.totalEntries || 0}
|
||||||
itemGap={20}
|
itemGap={20}
|
||||||
itemSize={150 + page.list?.size}
|
itemSize={150 + page.list?.size}
|
||||||
itemType={LibraryItem.ALBUM}
|
itemType={LibraryItem.ALBUM}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from './queries/genre-list';
|
export * from './queries/get-genre-list';
|
||||||
|
|||||||
+3
-6
@@ -1,20 +1,17 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { api } from '@/renderer/api';
|
import { api } from '@/renderer/api';
|
||||||
import { GenreListResponse } from '@/renderer/api/genres.api';
|
|
||||||
import { queryKeys } from '@/renderer/api/query-keys';
|
import { queryKeys } from '@/renderer/api/query-keys';
|
||||||
import { ApiError } from '@/renderer/api/types';
|
|
||||||
import { QueryOptions } from '@/renderer/lib/react-query';
|
import { QueryOptions } from '@/renderer/lib/react-query';
|
||||||
import { useAuthStore } from '@/renderer/store';
|
import { useAuthStore } from '@/renderer/store';
|
||||||
|
|
||||||
export const useGenreList = (options?: QueryOptions<GenreListResponse>) => {
|
export const useGenreList = (options?: QueryOptions) => {
|
||||||
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
||||||
|
|
||||||
const query = useQuery<GenreListResponse, AxiosError<ApiError>>({
|
const query = useQuery({
|
||||||
enabled: !!serverId,
|
enabled: !!serverId,
|
||||||
queryFn: ({ signal }) => api.genres.getGenreList({ serverId }, signal),
|
queryFn: ({ signal }) => api.genres.getGenreList({ serverId }, signal),
|
||||||
queryKey: queryKeys.genres.list(serverId),
|
queryKey: queryKeys.genres.list(serverId),
|
||||||
staleTime: Infinity,
|
staleTime: 1000 * 60 * 60,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
SegmentedControl,
|
SegmentedControl,
|
||||||
} from '@/renderer/components';
|
} from '@/renderer/components';
|
||||||
import { useCreateServer } from '@/renderer/features/servers/mutations/use-create-server';
|
import { useCreateServer } from '@/renderer/features/servers/mutations/create-server';
|
||||||
|
|
||||||
const SERVER_TYPES = [
|
const SERVER_TYPES = [
|
||||||
{ label: 'Jellyfin', value: ServerType.JELLYFIN },
|
{ label: 'Jellyfin', value: ServerType.JELLYFIN },
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useForm, zodResolver } from '@mantine/form';
|
|||||||
import { useFocusTrap } from '@mantine/hooks';
|
import { useFocusTrap } from '@mantine/hooks';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { Button, TextInput } from '@/renderer/components';
|
import { Button, TextInput } from '@/renderer/components';
|
||||||
import { useCreateServerUrl } from '@/renderer/features/servers/mutations/use-create-server-url';
|
import { useCreateServerUrl } from '@/renderer/features/servers/mutations/create-server-url';
|
||||||
|
|
||||||
interface AddServerUrlFormProps {
|
interface AddServerUrlFormProps {
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useForm } from '@mantine/form';
|
|||||||
import { RiInformationLine } from 'react-icons/ri';
|
import { RiInformationLine } from 'react-icons/ri';
|
||||||
import { Server, ServerType } from '@/renderer/api/types';
|
import { Server, ServerType } from '@/renderer/api/types';
|
||||||
import { Button, PasswordInput, TextInput, toast } from '@/renderer/components';
|
import { Button, PasswordInput, TextInput, toast } from '@/renderer/components';
|
||||||
import { useUpdateServer } from '@/renderer/features/servers/mutations/use-update-server';
|
import { useUpdateServer } from '@/renderer/features/servers/mutations/update-server';
|
||||||
|
|
||||||
interface EditServerFormProps {
|
interface EditServerFormProps {
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
|||||||
@@ -22,18 +22,17 @@ import { AddServerCredentialForm } from '@/renderer/features/servers/components/
|
|||||||
import { AddServerUrlForm } from '@/renderer/features/servers/components/add-server-url-form';
|
import { AddServerUrlForm } from '@/renderer/features/servers/components/add-server-url-form';
|
||||||
import { EditServerForm } from '@/renderer/features/servers/components/edit-server-form';
|
import { EditServerForm } from '@/renderer/features/servers/components/edit-server-form';
|
||||||
import { ServerSection } from '@/renderer/features/servers/components/server-section';
|
import { ServerSection } from '@/renderer/features/servers/components/server-section';
|
||||||
import { useDeleteServerUrl } from '@/renderer/features/servers/mutations/use-delete-server-url';
|
import { useDeleteServerUrl } from '@/renderer/features/servers/mutations/delete-server-url';
|
||||||
import { useDisableServerFolder } from '@/renderer/features/servers/mutations/use-disable-server-folder';
|
import { useDisableServerFolder } from '@/renderer/features/servers/mutations/disable-server-folder';
|
||||||
import { useDisableServerUrl } from '@/renderer/features/servers/mutations/use-disable-server-url';
|
import { useDisableServerUrl } from '@/renderer/features/servers/mutations/disable-server-url';
|
||||||
import { useEnableServerFolder } from '@/renderer/features/servers/mutations/use-enable-server-folder';
|
import { useEnableServerFolder } from '@/renderer/features/servers/mutations/enable-server-folder';
|
||||||
import { useEnableServerUrl } from '@/renderer/features/servers/mutations/use-enable-server-url';
|
import { useEnableServerUrl } from '@/renderer/features/servers/mutations/enable-server-url';
|
||||||
import { useFullScan } from '@/renderer/features/servers/mutations/use-full-scan';
|
import { useFullScan } from '@/renderer/features/servers/mutations/start-full-scan';
|
||||||
import { useQuickScan } from '@/renderer/features/servers/mutations/use-quick-scan';
|
import { useQuickScan } from '@/renderer/features/servers/mutations/start-quick-scan';
|
||||||
import { useUpdateServer } from '@/renderer/features/servers/mutations/use-update-server';
|
import { useUpdateServer } from '@/renderer/features/servers/mutations/update-server';
|
||||||
import { ServerPermission, usePermissions } from '@/renderer/features/shared';
|
import { ServerPermission, usePermissions } from '@/renderer/features/shared';
|
||||||
import { useTaskList } from '@/renderer/features/tasks';
|
import { useTaskList } from '@/renderer/features/tasks';
|
||||||
import { useAuthStore } from '@/renderer/store';
|
import { useAuthStore } from '@/renderer/store';
|
||||||
import { Font } from '@/renderer/styles';
|
|
||||||
|
|
||||||
interface ServerListItemProps {
|
interface ServerListItemProps {
|
||||||
server: Server;
|
server: Server;
|
||||||
@@ -169,100 +168,221 @@ export const ServerListItem = ({ server }: ServerListItemProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Stack mt="1rem" p="1rem" spacing="xl">
|
||||||
<Stack mt="1rem" spacing="xl">
|
<ServerSection
|
||||||
<ServerSection
|
title={
|
||||||
title={
|
<Group position="apart">
|
||||||
<Group position="apart">
|
<Text>Server details</Text>
|
||||||
<Text font={Font.EPILOGUE}>Server details</Text>
|
<Group spacing="md">
|
||||||
<Group spacing="md">
|
|
||||||
{serverPermission >= ServerPermission.ADMIN && (
|
|
||||||
<Button
|
|
||||||
compact
|
|
||||||
disabled={isRunningTask}
|
|
||||||
loading={fullScan.isLoading}
|
|
||||||
variant="subtle"
|
|
||||||
onClick={handleFullScan}
|
|
||||||
>
|
|
||||||
Full scan
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{serverPermission >= ServerPermission.EDITOR && (
|
|
||||||
<Button
|
|
||||||
compact
|
|
||||||
disabled={true || isRunningTask}
|
|
||||||
variant="subtle"
|
|
||||||
onClick={handleQuickScan}
|
|
||||||
>
|
|
||||||
Quick scan
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{edit ? (
|
|
||||||
<EditServerForm
|
|
||||||
server={server}
|
|
||||||
onCancel={() => editHandlers.toggle()}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Group position="apart">
|
|
||||||
<Group>
|
|
||||||
<Stack>
|
|
||||||
<Text>URL</Text>
|
|
||||||
{serverPermission >= ServerPermission.EDITOR && (
|
|
||||||
<Text>Username</Text>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
<Stack>
|
|
||||||
<Text size="sm">{server.url}</Text>
|
|
||||||
{serverPermission >= ServerPermission.EDITOR && (
|
|
||||||
<Text size="sm">{server.username}</Text>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Group>
|
|
||||||
{serverPermission >= ServerPermission.ADMIN && (
|
{serverPermission >= ServerPermission.ADMIN && (
|
||||||
<Group>
|
<Button
|
||||||
<Button
|
compact
|
||||||
tooltip={{ label: 'Edit server details' }}
|
disabled={isRunningTask}
|
||||||
variant="subtle"
|
loading={fullScan.isLoading}
|
||||||
onClick={() => editHandlers.toggle()}
|
variant="subtle"
|
||||||
>
|
onClick={handleFullScan}
|
||||||
<RiEdit2Fill />
|
>
|
||||||
</Button>
|
Full scan
|
||||||
</Group>
|
</Button>
|
||||||
|
)}
|
||||||
|
{serverPermission >= ServerPermission.EDITOR && (
|
||||||
|
<Button
|
||||||
|
compact
|
||||||
|
disabled={true || isRunningTask}
|
||||||
|
variant="subtle"
|
||||||
|
onClick={handleQuickScan}
|
||||||
|
>
|
||||||
|
Quick scan
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
</Group>
|
||||||
</ServerSection>
|
}
|
||||||
|
>
|
||||||
<ServerSection
|
{edit ? (
|
||||||
title={
|
<EditServerForm
|
||||||
|
server={server}
|
||||||
|
onCancel={() => editHandlers.toggle()}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Group position="apart">
|
||||||
<Group>
|
<Group>
|
||||||
<Text>Music Folders</Text>
|
<Stack>
|
||||||
<Tooltip label="Select which music folders you want to enable. Enabled music folders are included in the scan queue and are available to browse.">
|
<Text>URL</Text>
|
||||||
<Group>
|
{serverPermission >= ServerPermission.EDITOR && (
|
||||||
<RiInformationLine />
|
<Text>Username</Text>
|
||||||
</Group>
|
)}
|
||||||
</Tooltip>
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<Text size="sm">{server.url}</Text>
|
||||||
|
{serverPermission >= ServerPermission.EDITOR && (
|
||||||
|
<Text size="sm">{server.username}</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
{serverPermission >= ServerPermission.ADMIN && (
|
||||||
>
|
<Group>
|
||||||
<Stack>
|
<Button
|
||||||
{server.serverFolders?.map((folder) => (
|
tooltip={{ label: 'Edit server details' }}
|
||||||
<Group key={folder.id} position="apart">
|
variant="subtle"
|
||||||
<Text size="sm">{folder.name}</Text>
|
onClick={() => editHandlers.toggle()}
|
||||||
<Group>
|
>
|
||||||
<>
|
<RiEdit2Fill />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</ServerSection>
|
||||||
|
|
||||||
|
<ServerSection
|
||||||
|
title={
|
||||||
|
<Group>
|
||||||
|
<Text>Music Folders</Text>
|
||||||
|
<Tooltip label="Select which music folders you want to enable. Enabled music folders are included in the scan queue and are available to browse.">
|
||||||
|
<Group>
|
||||||
|
<RiInformationLine />
|
||||||
|
</Group>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
{server.serverFolders?.map((folder) => (
|
||||||
|
<Group key={folder.id} position="apart">
|
||||||
|
<Text size="sm">{folder.name}</Text>
|
||||||
|
<Group>
|
||||||
|
<>
|
||||||
|
<Switch
|
||||||
|
checked={folder.enabled}
|
||||||
|
disabled={serverPermission < ServerPermission.ADMIN}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleToggleFolder(folder.id, !e.currentTarget.checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{serverPermission >= ServerPermission.ADMIN && (
|
||||||
|
<DropdownMenu position="bottom-start">
|
||||||
|
<DropdownMenu.Target>
|
||||||
|
<Button compact variant="subtle">
|
||||||
|
<RiMore2Fill size={15} />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenu.Target>
|
||||||
|
<DropdownMenu.Dropdown>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
disabled
|
||||||
|
rightSection={
|
||||||
|
<RiDeleteBin2Line color="var(--danger-color)" />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Dropdown>
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</ServerSection>
|
||||||
|
|
||||||
|
<ServerSection
|
||||||
|
title={
|
||||||
|
<Group>
|
||||||
|
<Text>Credentials</Text>
|
||||||
|
<Tooltip label="If the server admin has required user credentials for this server, you will need to add your own login details here. Any credentials entered here are stored LOCALLY in the browser.">
|
||||||
|
<Group>
|
||||||
|
<RiInformationLine />
|
||||||
|
</Group>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{addCredential ? (
|
||||||
|
<AddServerCredentialForm
|
||||||
|
server={server}
|
||||||
|
onCancel={() => addCredentialHandlers.close()}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Stack>
|
||||||
|
{serverCredentials?.map((credential) => (
|
||||||
|
<Group key={credential.id} position="apart">
|
||||||
|
<Text size="sm">{credential.username}</Text>
|
||||||
|
<Group>
|
||||||
<Switch
|
<Switch
|
||||||
checked={folder.enabled}
|
checked={credential.enabled}
|
||||||
disabled={serverPermission < ServerPermission.ADMIN}
|
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleToggleFolder(folder.id, !e.currentTarget.checked)
|
handleToggleCredential(
|
||||||
|
credential.id,
|
||||||
|
!e.currentTarget.checked
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{serverPermission >= ServerPermission.ADMIN && (
|
<DropdownMenu position="bottom-start">
|
||||||
|
<DropdownMenu.Target>
|
||||||
|
<Button compact variant="subtle">
|
||||||
|
<RiMore2Fill size={15} />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenu.Target>
|
||||||
|
<DropdownMenu.Dropdown>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
rightSection={
|
||||||
|
<RiDeleteBin2Line color="var(--danger-color)" />
|
||||||
|
}
|
||||||
|
onClick={() => handleDeleteCredential(credential.id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Dropdown>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
<Button
|
||||||
|
compact
|
||||||
|
mt={10}
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => addCredentialHandlers.open()}
|
||||||
|
>
|
||||||
|
Add credential
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ServerSection>
|
||||||
|
|
||||||
|
<ServerSection
|
||||||
|
title={
|
||||||
|
<Group>
|
||||||
|
<Text>URLs</Text>
|
||||||
|
<Tooltip label="Enabling a URL will use it to generate image and audio endpoints instead of the server-defined URL. This is useful if you need to be able to access a URL from a local and remote network (e.g. http://localhost:4533 or https://music.domain.net)">
|
||||||
|
<Group>
|
||||||
|
<RiInformationLine />
|
||||||
|
</Group>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{addUrl ? (
|
||||||
|
<AddServerUrlForm
|
||||||
|
serverId={server.id}
|
||||||
|
onCancel={() => addUrlHandlers.close()}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Stack>
|
||||||
|
{server.serverUrls?.map((serverUrl) => (
|
||||||
|
<Group key={serverUrl.id} position="apart">
|
||||||
|
<Text size="sm">{serverUrl.url}</Text>
|
||||||
|
<Group>
|
||||||
|
<Switch
|
||||||
|
checked={serverUrl.enabled}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleToggleUrl(serverUrl.id, !e.currentTarget.checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{serverPermission >= ServerPermission.EDITOR && (
|
||||||
<DropdownMenu position="bottom-start">
|
<DropdownMenu position="bottom-start">
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button compact variant="subtle">
|
<Button compact variant="subtle">
|
||||||
@@ -271,201 +391,73 @@ export const ServerListItem = ({ server }: ServerListItemProps) => {
|
|||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
disabled
|
|
||||||
rightSection={
|
rightSection={
|
||||||
<RiDeleteBin2Line color="var(--danger-color)" />
|
<RiDeleteBin2Line color="var(--danger-color)" />
|
||||||
}
|
}
|
||||||
|
onClick={() => handleDeleteUrl(serverUrl.id)}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
</DropdownMenu.Dropdown>
|
</DropdownMenu.Dropdown>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
</>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
</ServerSection>
|
|
||||||
|
|
||||||
<ServerSection
|
|
||||||
title={
|
|
||||||
<Group>
|
|
||||||
<Text>Credentials</Text>
|
|
||||||
<Tooltip label="If the server admin has required user credentials for this server, you will need to add your own login details here. Any credentials entered here are stored LOCALLY in the browser.">
|
|
||||||
<Group>
|
|
||||||
<RiInformationLine />
|
|
||||||
</Group>
|
|
||||||
</Tooltip>
|
|
||||||
</Group>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{addCredential ? (
|
|
||||||
<AddServerCredentialForm
|
|
||||||
server={server}
|
|
||||||
onCancel={() => addCredentialHandlers.close()}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Stack>
|
|
||||||
{serverCredentials?.map((credential) => (
|
|
||||||
<Group key={credential.id} position="apart">
|
|
||||||
<Text size="sm">{credential.username}</Text>
|
|
||||||
<Group>
|
|
||||||
<Switch
|
|
||||||
checked={credential.enabled}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleToggleCredential(
|
|
||||||
credential.id,
|
|
||||||
!e.currentTarget.checked
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<DropdownMenu position="bottom-start">
|
|
||||||
<DropdownMenu.Target>
|
|
||||||
<Button compact variant="subtle">
|
|
||||||
<RiMore2Fill size={15} />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenu.Target>
|
|
||||||
<DropdownMenu.Dropdown>
|
|
||||||
<DropdownMenu.Item
|
|
||||||
rightSection={
|
|
||||||
<RiDeleteBin2Line color="var(--danger-color)" />
|
|
||||||
}
|
|
||||||
onClick={() =>
|
|
||||||
handleDeleteCredential(credential.id)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
</DropdownMenu.Dropdown>
|
|
||||||
</DropdownMenu>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
</Group>
|
||||||
))}
|
</Group>
|
||||||
</Stack>
|
))}
|
||||||
|
</Stack>
|
||||||
|
{serverPermission >= ServerPermission.EDITOR && (
|
||||||
<Button
|
<Button
|
||||||
compact
|
compact
|
||||||
mt={10}
|
mt={10}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={() => addCredentialHandlers.open()}
|
onClick={() => addUrlHandlers.open()}
|
||||||
>
|
>
|
||||||
Add credential
|
Add URL
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
)}
|
||||||
)}
|
</>
|
||||||
</ServerSection>
|
)}
|
||||||
|
</ServerSection>
|
||||||
|
|
||||||
<ServerSection
|
{serverPermission >= ServerPermission.ADMIN && (
|
||||||
title={
|
<ServerSection title="Danger zone">
|
||||||
|
<Group position="apart">
|
||||||
<Group>
|
<Group>
|
||||||
<Text>URLs</Text>
|
<Text size="sm">Require user credentials</Text>
|
||||||
<Tooltip label="Enabling a URL will use it to generate image and audio endpoints instead of the server-defined URL. This is useful if you need to be able to access a URL from a local and remote network (e.g. http://localhost:4533 or https://music.domain.net)">
|
<Tooltip label="WARNING: Disabling this option will expose your server login credentials to all users. If enabled, all users will be required to enter their own credentials to access this server.">
|
||||||
<Group>
|
<Group>
|
||||||
<RiInformationLine />
|
<RiInformationLine />
|
||||||
</Group>
|
</Group>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Group>
|
</Group>
|
||||||
}
|
<Switch
|
||||||
>
|
checked={server.noCredential}
|
||||||
{addUrl ? (
|
onChange={(e) =>
|
||||||
<AddServerUrlForm
|
toggleRequiredCredential(e.currentTarget.checked)
|
||||||
serverId={server.id}
|
}
|
||||||
onCancel={() => addUrlHandlers.close()}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
</Group>
|
||||||
|
{permissions.isSuperAdmin && (
|
||||||
<>
|
<>
|
||||||
<Stack>
|
<Divider my="xl" />
|
||||||
{server.serverUrls?.map((serverUrl) => (
|
<Button
|
||||||
<Group key={serverUrl.id} position="apart">
|
compact
|
||||||
<Text size="sm">{serverUrl.url}</Text>
|
disabled
|
||||||
<Group>
|
leftIcon={<RiDeleteBin2Line />}
|
||||||
<Switch
|
size="xs"
|
||||||
checked={serverUrl.enabled}
|
sx={{
|
||||||
onChange={(e) =>
|
'&:hover': {
|
||||||
handleToggleUrl(
|
background: 'var(--danger-color)',
|
||||||
serverUrl.id,
|
},
|
||||||
!e.currentTarget.checked
|
background: 'var(--danger-color)',
|
||||||
)
|
}}
|
||||||
}
|
>
|
||||||
/>
|
Delete server
|
||||||
{serverPermission >= ServerPermission.EDITOR && (
|
</Button>
|
||||||
<DropdownMenu position="bottom-start">
|
|
||||||
<DropdownMenu.Target>
|
|
||||||
<Button compact variant="subtle">
|
|
||||||
<RiMore2Fill size={15} />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenu.Target>
|
|
||||||
<DropdownMenu.Dropdown>
|
|
||||||
<DropdownMenu.Item
|
|
||||||
rightSection={
|
|
||||||
<RiDeleteBin2Line color="var(--danger-color)" />
|
|
||||||
}
|
|
||||||
onClick={() => handleDeleteUrl(serverUrl.id)}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
</DropdownMenu.Dropdown>
|
|
||||||
</DropdownMenu>
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
{serverPermission >= ServerPermission.EDITOR && (
|
|
||||||
<Button
|
|
||||||
compact
|
|
||||||
mt={10}
|
|
||||||
variant="subtle"
|
|
||||||
onClick={() => addUrlHandlers.open()}
|
|
||||||
>
|
|
||||||
Add URL
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ServerSection>
|
</ServerSection>
|
||||||
|
)}
|
||||||
{serverPermission >= ServerPermission.ADMIN && (
|
</Stack>
|
||||||
<ServerSection title="Danger zone">
|
|
||||||
<Group position="apart">
|
|
||||||
<Group>
|
|
||||||
<Text size="sm">Require user credentials</Text>
|
|
||||||
<Tooltip label="WARNING: Disabling this option will expose your server login credentials to all users. If enabled, all users will be required to enter their own credentials to access this server.">
|
|
||||||
<Group>
|
|
||||||
<RiInformationLine />
|
|
||||||
</Group>
|
|
||||||
</Tooltip>
|
|
||||||
</Group>
|
|
||||||
<Switch
|
|
||||||
checked={server.noCredential}
|
|
||||||
onChange={(e) =>
|
|
||||||
toggleRequiredCredential(e.currentTarget.checked)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
{permissions.isSuperAdmin && (
|
|
||||||
<>
|
|
||||||
<Divider my="xl" />
|
|
||||||
<Button
|
|
||||||
compact
|
|
||||||
disabled
|
|
||||||
leftIcon={<RiDeleteBin2Line />}
|
|
||||||
size="xs"
|
|
||||||
sx={{
|
|
||||||
'&:hover': {
|
|
||||||
background: 'var(--danger-color)',
|
|
||||||
},
|
|
||||||
background: 'var(--danger-color)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Delete server
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ServerSection>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { openContextModal } from '@mantine/modals';
|
|||||||
import { RiAddFill, RiServerFill } from 'react-icons/ri';
|
import { RiAddFill, RiServerFill } from 'react-icons/ri';
|
||||||
import { Button, ContextModalVars, Accordion } from '@/renderer/components';
|
import { Button, ContextModalVars, Accordion } from '@/renderer/components';
|
||||||
import { ServerListItem } from '@/renderer/features/servers/components/server-list-item';
|
import { ServerListItem } from '@/renderer/features/servers/components/server-list-item';
|
||||||
import { useServerList } from '@/renderer/features/servers/queries/use-server-list';
|
import { useServerList } from '@/renderer/features/servers/queries/get-server-list';
|
||||||
import { usePermissions } from '@/renderer/features/shared';
|
import { usePermissions } from '@/renderer/features/shared';
|
||||||
import { titleCase } from '@/renderer/utils';
|
import { titleCase } from '@/renderer/utils';
|
||||||
import { AddServerForm } from './add-server-form';
|
import { AddServerForm } from './add-server-form';
|
||||||
|
|
||||||
export const ServerList = () => {
|
export const ServerList = () => {
|
||||||
const { data: servers } = useServerList();
|
const serverListQuery = useServerList();
|
||||||
const permissions = usePermissions();
|
const permissions = usePermissions();
|
||||||
|
|
||||||
const handleAddServerModal = () => {
|
const handleAddServerModal = () => {
|
||||||
@@ -49,7 +49,7 @@ export const ServerList = () => {
|
|||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Accordion variant="separated">
|
<Accordion variant="separated">
|
||||||
{servers?.data?.map((s) => (
|
{serverListQuery?.data?.data.map((s) => (
|
||||||
<Accordion.Item key={s.id} value={s.name}>
|
<Accordion.Item key={s.id} value={s.name}>
|
||||||
<Accordion.Control icon={<RiServerFill size={15} />}>
|
<Accordion.Control icon={<RiServerFill size={15} />}>
|
||||||
<Group position="apart">
|
<Group position="apart">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export * from './mutations/use-create-server';
|
export * from './mutations/create-server';
|
||||||
export * from './components/add-server-form';
|
export * from './components/add-server-form';
|
||||||
export * from './components/server-list';
|
export * from './components/server-list';
|
||||||
export * from './queries/use-server-list';
|
export * from './queries/get-server-list';
|
||||||
export * from './queries/get-server-map';
|
export * from './queries/get-server-map';
|
||||||
export * from './mutations/create-server-folder-permission';
|
export * from './mutations/create-server-folder-permission';
|
||||||
export * from './mutations/delete-server-folder-permission';
|
export * from './mutations/delete-server-folder-permission';
|
||||||
|
|||||||
+3
-3
@@ -7,14 +7,14 @@ import { useAuthStore } from '@/renderer/store';
|
|||||||
|
|
||||||
export const useServerList = (
|
export const useServerList = (
|
||||||
params?: { enabled?: boolean },
|
params?: { enabled?: boolean },
|
||||||
options?: QueryOptions<ServerListResponse>
|
options?: QueryOptions
|
||||||
) => {
|
) => {
|
||||||
const currentServer = useAuthStore((state) => state.currentServer);
|
const currentServer = useAuthStore((state) => state.currentServer);
|
||||||
const setCurrentServer = useAuthStore((state) => state.setCurrentServer);
|
const setCurrentServer = useAuthStore((state) => state.setCurrentServer);
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
onSuccess: (data) => {
|
onSettled: (data: ServerListResponse) => {
|
||||||
const currentServerFromList = data.data.find(
|
const currentServerFromList = data?.data.find(
|
||||||
(server) => server.id === currentServer?.id
|
(server) => server.id === currentServer?.id
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { api } from '@/renderer/api';
|
import { api } from '@/renderer/api';
|
||||||
import { queryKeys } from '@/renderer/api/query-keys';
|
import { queryKeys } from '@/renderer/api/query-keys';
|
||||||
import { ServerMapResponse } from '@/renderer/api/servers.api';
|
|
||||||
import { QueryOptions } from '@/renderer/lib/react-query';
|
import { QueryOptions } from '@/renderer/lib/react-query';
|
||||||
|
|
||||||
export const useServerMap = (options?: QueryOptions<ServerMapResponse>) => {
|
export const useServerMap = (options?: QueryOptions) => {
|
||||||
return useQuery<ServerMapResponse>({
|
return useQuery({
|
||||||
cacheTime: Infinity,
|
cacheTime: Infinity,
|
||||||
queryFn: ({ signal }) => api.servers.getServerMap(signal),
|
queryFn: ({ signal }) => api.servers.getServerMap(signal),
|
||||||
queryKey: queryKeys.servers.map(),
|
queryKey: queryKeys.servers.map(),
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from './queries/task-list';
|
export * from './queries/get-task-list';
|
||||||
export * from './mutations/cancel-all-tasks';
|
export * from './mutations/cancel-all-tasks';
|
||||||
export * from './mutations/cancel-task';
|
export * from './mutations/cancel-task';
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import { queryKeys } from '@/renderer/api/query-keys';
|
|||||||
import { TaskListResponse } from '@/renderer/api/tasks.api';
|
import { TaskListResponse } from '@/renderer/api/tasks.api';
|
||||||
import { QueryOptions } from '@/renderer/lib/react-query';
|
import { QueryOptions } from '@/renderer/lib/react-query';
|
||||||
|
|
||||||
export const useTaskList = (options?: QueryOptions<TaskListResponse>) => {
|
export const useTaskList = (options?: QueryOptions) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryFn: ({ signal }) => api.tasks.getActiveTasks(signal),
|
queryFn: ({ signal }) => api.tasks.getActiveTasks(signal),
|
||||||
queryKey: queryKeys.tasks.list(),
|
queryKey: queryKeys.tasks.list(),
|
||||||
@@ -21,9 +21,9 @@ export const ActivityMenu = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
|
||||||
const [isTaskRunning, setIsTaskRunning] = useState(false);
|
const [isTaskRunning, setIsTaskRunning] = useState(false);
|
||||||
const { data: tasks, refetch } = useTaskList({
|
const taskListQuery = useTaskList({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data: ReturnType<typeof useTaskList>['data']) => {
|
||||||
if (data.data.length === 0) {
|
if (data?.data.length === 0) {
|
||||||
queryClient.invalidateQueries(queryKeys.server.root(serverId));
|
queryClient.invalidateQueries(queryKeys.server.root(serverId));
|
||||||
return setIsTaskRunning(false);
|
return setIsTaskRunning(false);
|
||||||
}
|
}
|
||||||
@@ -57,14 +57,14 @@ export const ActivityMenu = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.on('task:started', () => {
|
socket.on('task:started', () => {
|
||||||
setTimeout(() => refetch(), 1000);
|
setTimeout(() => taskListQuery.refetch(), 1000);
|
||||||
setIsTaskRunning(true);
|
setIsTaskRunning(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.off('task:started');
|
socket.off('task:started');
|
||||||
};
|
};
|
||||||
}, [refetch]);
|
}, [taskListQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -80,7 +80,7 @@ export const ActivityMenu = () => {
|
|||||||
</Popover.Target>
|
</Popover.Target>
|
||||||
<Popover.Dropdown>
|
<Popover.Dropdown>
|
||||||
{isTaskRunning ? (
|
{isTaskRunning ? (
|
||||||
tasks?.data?.map((task) => (
|
taskListQuery.data?.data?.map((task) => (
|
||||||
<Group key={task.id} position="apart">
|
<Group key={task.id} position="apart">
|
||||||
<Text>{task.note}</Text>
|
<Text>{task.note}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ export const EditUserPermissionsForm = ({
|
|||||||
onCancel,
|
onCancel,
|
||||||
}: EditUserPermissionsFormProps) => {
|
}: EditUserPermissionsFormProps) => {
|
||||||
const permissions = usePermissions();
|
const permissions = usePermissions();
|
||||||
const { data: servers } = useServerList();
|
const serverListQuery = useServerList();
|
||||||
const { data: user } = useUserDetail({ userId });
|
const userDetailQuery = useUserDetail({ userId });
|
||||||
const createServerPermissionMutation = useCreateServerPermission();
|
const createServerPermissionMutation = useCreateServerPermission();
|
||||||
const deleteServerPermissionMutation = useDeleteServerPermission();
|
const deleteServerPermissionMutation = useDeleteServerPermission();
|
||||||
const updateServerPermissionMutation = useUpdateServerPermission();
|
const updateServerPermissionMutation = useUpdateServerPermission();
|
||||||
@@ -61,17 +61,20 @@ export const EditUserPermissionsForm = ({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const userDetailId = userDetailQuery?.data?.data.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack m={5}>
|
<Stack m={5}>
|
||||||
<Accordion variant="contained">
|
<Accordion variant="contained">
|
||||||
{servers?.data?.map((s) => {
|
{serverListQuery?.data?.data.map((s) => {
|
||||||
const currentServerPermission = user?.data?.serverPermissions?.find(
|
const currentServerPermission =
|
||||||
(p) => p.serverId === s.id
|
userDetailQuery?.data?.data.serverPermissions?.find(
|
||||||
);
|
(p) => p.serverId === s.id
|
||||||
|
);
|
||||||
|
|
||||||
const isServerAdminEditingSelf =
|
const isServerAdminEditingSelf =
|
||||||
permissions[s.id] >= ServerPermission.ADMIN &&
|
permissions[s.id] >= ServerPermission.ADMIN &&
|
||||||
user?.data.id === permissions.userId;
|
userDetailQuery?.data?.data.id === permissions.userId;
|
||||||
|
|
||||||
const isServerAdminEditingOtherAdmin =
|
const isServerAdminEditingOtherAdmin =
|
||||||
!permissions.isAdmin &&
|
!permissions.isAdmin &&
|
||||||
@@ -81,7 +84,7 @@ export const EditUserPermissionsForm = ({
|
|||||||
isServerAdminEditingSelf || isServerAdminEditingOtherAdmin;
|
isServerAdminEditingSelf || isServerAdminEditingOtherAdmin;
|
||||||
|
|
||||||
const handleChangeServerPermission = async (e: string | null) => {
|
const handleChangeServerPermission = async (e: string | null) => {
|
||||||
if (!e || !user) return;
|
if (!e || !userDetailId) return;
|
||||||
|
|
||||||
if (e === 'none' && currentServerPermission) {
|
if (e === 'none' && currentServerPermission) {
|
||||||
deleteServerPermissionMutation.mutate(
|
deleteServerPermissionMutation.mutate(
|
||||||
@@ -90,7 +93,7 @@ export const EditUserPermissionsForm = ({
|
|||||||
permissionId: currentServerPermission.id,
|
permissionId: currentServerPermission.id,
|
||||||
serverId: s.id,
|
serverId: s.id,
|
||||||
},
|
},
|
||||||
userId: user.data.id,
|
userId: userDetailId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onError: (err) =>
|
onError: (err) =>
|
||||||
@@ -110,7 +113,7 @@ export const EditUserPermissionsForm = ({
|
|||||||
permissionId: currentServerPermission.id,
|
permissionId: currentServerPermission.id,
|
||||||
serverId: s.id,
|
serverId: s.id,
|
||||||
},
|
},
|
||||||
userId: user.data.id,
|
userId: userDetailId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onError: (err) =>
|
onError: (err) =>
|
||||||
@@ -125,7 +128,7 @@ export const EditUserPermissionsForm = ({
|
|||||||
{
|
{
|
||||||
body: {
|
body: {
|
||||||
type: e as ServerPermissionType,
|
type: e as ServerPermissionType,
|
||||||
userId: user.data.id,
|
userId: userDetailId,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
serverId: s.id,
|
serverId: s.id,
|
||||||
@@ -191,14 +194,14 @@ export const EditUserPermissionsForm = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
{s.serverFolders?.map((f) => {
|
{s.serverFolders?.map((f) => {
|
||||||
const currentFolderPermission =
|
const currentFolderPermission =
|
||||||
user?.data.serverFolderPermissions?.find(
|
userDetailQuery?.data?.data.serverFolderPermissions?.find(
|
||||||
(p) => p.serverFolderId === f.id
|
(p) => p.serverFolderId === f.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleToggleMusicFolderPermission = async (
|
const handleToggleMusicFolderPermission = async (
|
||||||
e: ChangeEvent<HTMLInputElement>
|
e: ChangeEvent<HTMLInputElement>
|
||||||
) => {
|
) => {
|
||||||
if (!user) return;
|
if (!userDetailId) return;
|
||||||
const { checked } = e.target;
|
const { checked } = e.target;
|
||||||
const serverId = s.id;
|
const serverId = s.id;
|
||||||
|
|
||||||
@@ -206,7 +209,7 @@ export const EditUserPermissionsForm = ({
|
|||||||
createServerFolderPermissionMutation.mutate(
|
createServerFolderPermissionMutation.mutate(
|
||||||
{
|
{
|
||||||
body: {
|
body: {
|
||||||
userId: user.data.id,
|
userId: userDetailId,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
folderId: f.id,
|
folderId: f.id,
|
||||||
@@ -229,7 +232,7 @@ export const EditUserPermissionsForm = ({
|
|||||||
folderPermissionId: currentFolderPermission.id,
|
folderPermissionId: currentFolderPermission.id,
|
||||||
serverId,
|
serverId,
|
||||||
},
|
},
|
||||||
userId: user.data.id,
|
userId: userDetailId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onError: (err) =>
|
onError: (err) =>
|
||||||
@@ -245,7 +248,7 @@ export const EditUserPermissionsForm = ({
|
|||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
key={`server-folder-permission-${f.id}`}
|
key={`server-folder-permission-${f.id}`}
|
||||||
defaultChecked={user?.data.serverFolderPermissions.some(
|
defaultChecked={userDetailQuery?.data?.data.serverFolderPermissions.some(
|
||||||
(p) => p.serverFolderId === f.id
|
(p) => p.serverFolderId === f.id
|
||||||
)}
|
)}
|
||||||
disabled={isServerAdminEditingOtherAdmin}
|
disabled={isServerAdminEditingOtherAdmin}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { api } from '@/renderer/api';
|
import { api } from '@/renderer/api';
|
||||||
import { queryKeys } from '@/renderer/api/query-keys';
|
import { queryKeys } from '@/renderer/api/query-keys';
|
||||||
import { UserDetailResponse } from '@/renderer/api/users.api';
|
|
||||||
import { QueryOptions } from '@/renderer/lib/react-query';
|
import { QueryOptions } from '@/renderer/lib/react-query';
|
||||||
|
|
||||||
export const useUserDetail = (
|
export const useUserDetail = (
|
||||||
q: { userId: string },
|
q: { userId: string },
|
||||||
options?: QueryOptions<UserDetailResponse>
|
options?: QueryOptions
|
||||||
) => {
|
) => {
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryFn: () => api.users.getUserDetail({ userId: q.userId }),
|
queryFn: () => api.users.getUserDetail({ userId: q.userId }),
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { api } from '@/renderer/api';
|
import { api } from '@/renderer/api';
|
||||||
import { queryKeys } from '@/renderer/api/query-keys';
|
import { queryKeys } from '@/renderer/api/query-keys';
|
||||||
import { UserListResponse } from '@/renderer/api/users.api';
|
|
||||||
import { QueryOptions } from '@/renderer/lib/react-query';
|
import { QueryOptions } from '@/renderer/lib/react-query';
|
||||||
import { useAuthStore } from '@/renderer/store';
|
import { useAuthStore } from '@/renderer/store';
|
||||||
import { getFileUrl } from '@/renderer/utils';
|
import { getFileUrl } from '@/renderer/utils';
|
||||||
|
|
||||||
export const useUserList = (options?: QueryOptions<UserListResponse>) => {
|
export const useUserList = (options?: QueryOptions) => {
|
||||||
const serverUrl = useAuthStore((state) => state.serverUrl);
|
const serverUrl = useAuthStore((state) => state.serverUrl);
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import isElectron from 'is-electron';
|
|||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import { Outlet } from 'react-router';
|
import { Outlet } from 'react-router';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { UserDetailResponse } from '@/renderer/api/users.api';
|
||||||
import { SideQueue } from '@/renderer/features/side-queue/components/side-queue';
|
import { SideQueue } from '@/renderer/features/side-queue/components/side-queue';
|
||||||
import { Titlebar } from '@/renderer/features/titlebar/components/titlebar';
|
import { Titlebar } from '@/renderer/features/titlebar/components/titlebar';
|
||||||
import { useUserDetail } from '@/renderer/features/users';
|
import { useUserDetail } from '@/renderer/features/users';
|
||||||
@@ -124,7 +125,7 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => {
|
|||||||
{ userId },
|
{ userId },
|
||||||
{
|
{
|
||||||
cacheTime: Infinity,
|
cacheTime: Infinity,
|
||||||
onSuccess: (res) => {
|
onSuccess: (res: UserDetailResponse) => {
|
||||||
const props = {
|
const props = {
|
||||||
permissions: {
|
permissions: {
|
||||||
id: res.data.id,
|
id: res.data.id,
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import { useAuthStore } from '@/renderer/store';
|
|||||||
import { authApi } from '../api/auth.api';
|
import { authApi } from '../api/auth.api';
|
||||||
|
|
||||||
export const ax = Axios.create({
|
export const ax = Axios.create({
|
||||||
headers: {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -35,23 +33,19 @@ ax.interceptors.response.use(
|
|||||||
async (err) => {
|
async (err) => {
|
||||||
if (err.response && err.response.status === 401) {
|
if (err.response && err.response.status === 401) {
|
||||||
const { config } = err;
|
const { config } = err;
|
||||||
const auth = JSON.parse(
|
|
||||||
localStorage.getItem('store_authentication') || '{}'
|
const auth = useAuthStore.getState();
|
||||||
);
|
|
||||||
|
|
||||||
if (err.response.data.error.message === 'jwt expired' && !config.sent) {
|
if (err.response.data.error.message === 'jwt expired' && !config.sent) {
|
||||||
config.sent = true;
|
config.sent = true;
|
||||||
|
|
||||||
const { accessToken } = (
|
const { accessToken } = (
|
||||||
await authApi.refresh(auth.state.serverUrl, {
|
await authApi.refresh(auth.serverUrl, {
|
||||||
refreshToken: auth.refreshToken,
|
refreshToken: auth.refreshToken,
|
||||||
})
|
})
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
localStorage.setItem(
|
useAuthStore.setState({ ...auth, accessToken });
|
||||||
'store_authentication',
|
|
||||||
JSON.stringify({ ...auth, state: { ...auth.state, accessToken } })
|
|
||||||
);
|
|
||||||
|
|
||||||
config.headers = {
|
config.headers = {
|
||||||
...config.headers,
|
...config.headers,
|
||||||
@@ -61,12 +55,11 @@ ax.interceptors.response.use(
|
|||||||
return Axios(config);
|
return Axios(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { logout } = useAuthStore.getState();
|
// if (err.response.data.error.message === 'No auth token') {
|
||||||
if (err.response.data.error.message === 'No auth token') {
|
// auth.logout();
|
||||||
logout();
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
logout();
|
auth.logout();
|
||||||
}
|
}
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
QueryClient,
|
QueryClient,
|
||||||
UseQueryOptions,
|
UseQueryOptions,
|
||||||
UseMutationOptions,
|
|
||||||
DefaultOptions,
|
DefaultOptions,
|
||||||
QueryCache,
|
QueryCache,
|
||||||
} from '@tanstack/react-query';
|
} from '@tanstack/react-query';
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { PromiseValue } from 'type-fest';
|
|
||||||
import { toast } from '@/renderer/components';
|
import { toast } from '@/renderer/components';
|
||||||
|
|
||||||
const queryCache = new QueryCache({
|
const queryCache = new QueryCache({
|
||||||
@@ -26,7 +23,7 @@ const queryConfig: DefaultOptions = {
|
|||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
},
|
},
|
||||||
refetchOnWindowFocus: process.env.NODE_ENV === 'production',
|
refetchOnWindowFocus: false,
|
||||||
retry: process.env.NODE_ENV === 'production',
|
retry: process.env.NODE_ENV === 'production',
|
||||||
staleTime: 1000 * 5,
|
staleTime: 1000 * 5,
|
||||||
useErrorBoundary: true,
|
useErrorBoundary: true,
|
||||||
@@ -38,31 +35,17 @@ export const queryClient = new QueryClient({
|
|||||||
queryCache,
|
queryCache,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ExtractFnReturnType<FnType extends (...args: any) => any> =
|
export type QueryOptions = {
|
||||||
PromiseValue<ReturnType<FnType>>;
|
|
||||||
|
|
||||||
export type QueryConfig<QueryFnType extends (...args: any) => any> = Omit<
|
|
||||||
UseQueryOptions<ExtractFnReturnType<QueryFnType>>,
|
|
||||||
'queryKey' | 'queryFn'
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type MutationConfig<MutationFnType extends (...args: any) => any> =
|
|
||||||
UseMutationOptions<
|
|
||||||
ExtractFnReturnType<MutationFnType>,
|
|
||||||
AxiosError,
|
|
||||||
Parameters<MutationFnType>[0]
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type QueryOptions<TResponse> = {
|
|
||||||
cacheTime?: UseQueryOptions['cacheTime'];
|
cacheTime?: UseQueryOptions['cacheTime'];
|
||||||
enabled?: UseQueryOptions['enabled'];
|
enabled?: UseQueryOptions['enabled'];
|
||||||
onError?: (err: any) => void;
|
onError?: (err: any) => void;
|
||||||
onSuccess?: (data: any) => void;
|
onSettled?: any;
|
||||||
|
onSuccess?: any;
|
||||||
refetchInterval?: number;
|
refetchInterval?: number;
|
||||||
refetchIntervalInBackground?: UseQueryOptions['refetchIntervalInBackground'];
|
refetchIntervalInBackground?: UseQueryOptions['refetchIntervalInBackground'];
|
||||||
refetchOnWindowFocus?: boolean;
|
refetchOnWindowFocus?: boolean;
|
||||||
retry?: UseQueryOptions['retry'];
|
retry?: UseQueryOptions['retry'];
|
||||||
retryDelay?: UseQueryOptions['retryDelay'];
|
retryDelay?: UseQueryOptions['retryDelay'];
|
||||||
staleTime?: UseQueryOptions['staleTime'];
|
staleTime?: UseQueryOptions['staleTime'];
|
||||||
useErrorBoundary?: any;
|
useErrorBoundary?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user