Skip to content

Commit 739b1e4

Browse files
committed
feat(dashboard): enhance App and PhotoSyncActions components with new hooks and functionality
- Integrated useRequireStorageProvider in AppLayer to manage storage provider requirements. - Added auto-run functionality in PhotoSyncActions to trigger sync based on user settings. - Updated useStorageProvidersQuery to accept options for enabling/disabling queries. - Enhanced StorageProvidersManager to prompt users for immediate photo sync after configuration. Signed-off-by: Innei <tukon479@gmail.com>
1 parent 53abfc5 commit 739b1e4

File tree

6 files changed

+128
-7
lines changed

6 files changed

+128
-7
lines changed

be/apps/dashboard/src/App.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import type {FC} from 'react';
1+
import type { FC } from 'react'
22
import { useEffect } from 'react'
33
import { Outlet, useLocation, useNavigate } from 'react-router'
44

55
import { useAccessDeniedValue } from '~/atoms/access-denied'
66
import { ROUTE_PATHS } from '~/constants/routes'
77
import { usePageRedirect } from '~/hooks/usePageRedirect'
8+
import { useRequireStorageProvider } from '~/hooks/useRequireStorageProvider'
89
import { useRoutePermission } from '~/hooks/useRoutePermission'
910

1011
import { RootProviders } from './providers/root-providers'
@@ -23,6 +24,10 @@ function AppLayer() {
2324
session: pageRedirect.sessionQuery.data ?? null,
2425
isLoading: pageRedirect.sessionQuery.isPending,
2526
})
27+
useRequireStorageProvider({
28+
session: pageRedirect.sessionQuery.data ?? null,
29+
isLoading: pageRedirect.sessionQuery.isPending,
30+
})
2631
useAccessDeniedRedirect()
2732

2833
const appIsReady = true
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { atom } from 'jotai'
2+
3+
import { createAtomHooks } from '~/lib/jotai'
4+
5+
export type PhotoSyncAutoRunMode = 'dry-run' | 'apply' | null
6+
7+
const basePhotoSyncAutoRunAtom = atom<PhotoSyncAutoRunMode>(null)
8+
9+
export const [
10+
photoSyncAutoRunAtom,
11+
usePhotoSyncAutoRun,
12+
usePhotoSyncAutoRunValue,
13+
useSetPhotoSyncAutoRun,
14+
getPhotoSyncAutoRun,
15+
setPhotoSyncAutoRun,
16+
] = createAtomHooks(basePhotoSyncAutoRunAtom)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useEffect } from 'react'
2+
import { useLocation, useNavigate } from 'react-router'
3+
4+
import { PUBLIC_ROUTES } from '~/constants/routes'
5+
import type { SessionResponse } from '~/modules/auth/api/session'
6+
import { useStorageProvidersQuery } from '~/modules/storage-providers'
7+
8+
const STORAGE_SETUP_PATH = '/photos/storage'
9+
10+
type UseRequireStorageProviderArgs = {
11+
session: SessionResponse | null
12+
isLoading: boolean
13+
}
14+
15+
export function useRequireStorageProvider({ session, isLoading }: UseRequireStorageProviderArgs) {
16+
const location = useLocation()
17+
const navigate = useNavigate()
18+
19+
const pathname = location.pathname || '/'
20+
const shouldCheck =
21+
!isLoading &&
22+
!!session &&
23+
session.user.role !== 'superadmin' &&
24+
!PUBLIC_ROUTES.has(pathname) &&
25+
!pathname.startsWith('/superadmin')
26+
27+
const storageProvidersQuery = useStorageProvidersQuery({
28+
enabled: shouldCheck,
29+
})
30+
31+
const needsSetup =
32+
shouldCheck &&
33+
storageProvidersQuery.isSuccess &&
34+
(storageProvidersQuery.data?.providers.length ?? 0) === 0 &&
35+
!storageProvidersQuery.isFetching
36+
37+
useEffect(() => {
38+
if (!needsSetup) {
39+
return
40+
}
41+
if (pathname === STORAGE_SETUP_PATH) {
42+
return
43+
}
44+
45+
navigate(STORAGE_SETUP_PATH, { replace: true })
46+
}, [navigate, needsSetup, pathname])
47+
}

be/apps/dashboard/src/modules/photos/components/sync/PhotoSyncActions.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useMutation } from '@tanstack/react-query'
33
import { useEffect, useRef, useState } from 'react'
44
import { toast } from 'sonner'
55

6+
import { usePhotoSyncAutoRunValue, useSetPhotoSyncAutoRun } from '~/atoms/photo-sync'
67
import { useMainPageLayout } from '~/components/layouts/MainPageLayout'
78
import { getRequestErrorMessage } from '~/lib/errors'
89

@@ -15,6 +16,8 @@ export function PhotoSyncActions() {
1516
const { setHeaderActionState } = useMainPageLayout()
1617
const [pendingMode, setPendingMode] = useState<'dry-run' | 'apply' | null>(null)
1718
const abortRef = useRef<AbortController | null>(null)
19+
const autoRunMode = usePhotoSyncAutoRunValue()
20+
const setAutoRunMode = useSetPhotoSyncAutoRun()
1821

1922
const mutation = useMutation({
2023
mutationFn: async (variables: RunPhotoSyncPayload) => {
@@ -60,7 +63,7 @@ export function PhotoSyncActions() {
6063
},
6164
})
6265

63-
const { isPending } = mutation
66+
const { isPending, mutate } = mutation
6467

6568
useEffect(() => {
6669
return () => {
@@ -69,6 +72,17 @@ export function PhotoSyncActions() {
6972
}
7073
}, [setHeaderActionState])
7174

75+
useEffect(() => {
76+
if (!autoRunMode) {
77+
return
78+
}
79+
if (isPending) {
80+
return
81+
}
82+
mutate({ dryRun: autoRunMode === 'dry-run' })
83+
setAutoRunMode(null)
84+
}, [autoRunMode, isPending, mutate, setAutoRunMode])
85+
7286
const handleSync = (dryRun: boolean) => {
7387
mutation.mutate({ dryRun })
7488
}

be/apps/dashboard/src/modules/storage-providers/components/StorageProvidersManager.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { Button, Modal } from '@afilmory/ui'
1+
import { Button, Modal, Prompt } from '@afilmory/ui'
22
import { Spring } from '@afilmory/utils'
33
import { DynamicIcon } from 'lucide-react/dynamic'
44
import { m } from 'motion/react'
5-
import { startTransition, useEffect, useState } from 'react'
5+
import { startTransition, useEffect, useRef, useState } from 'react'
6+
import { useNavigate } from 'react-router'
67

8+
import { useSetPhotoSyncAutoRun } from '~/atoms/photo-sync'
79
import { LinearBorderPanel } from '~/components/common/GlassPanel'
810
import { MainPageLayout, useMainPageLayout } from '~/components/layouts/MainPageLayout'
911
import { useBlock } from '~/hooks/useBlock'
@@ -18,10 +20,14 @@ export function StorageProvidersManager() {
1820
const { data, isLoading, isError, error } = useStorageProvidersQuery()
1921
const updateMutation = useUpdateStorageProvidersMutation()
2022
const { setHeaderActionState } = useMainPageLayout()
23+
const navigate = useNavigate()
24+
const setPhotoSyncAutoRun = useSetPhotoSyncAutoRun()
2125

2226
const [providers, setProviders] = useState<StorageProvider[]>([])
2327
const [activeProviderId, setActiveProviderId] = useState<string | null>(null)
2428
const [isDirty, setIsDirty] = useState(false)
29+
const initialProviderStateRef = useRef<boolean | null>(null)
30+
const hasShownSyncPromptRef = useRef(false)
2531

2632
useBlock({
2733
when: isDirty,
@@ -46,6 +52,15 @@ export function StorageProvidersManager() {
4652
})
4753
}, [data])
4854

55+
useEffect(() => {
56+
if (!data) {
57+
return
58+
}
59+
if (initialProviderStateRef.current === null) {
60+
initialProviderStateRef.current = data.providers.length > 0
61+
}
62+
}, [data])
63+
4964
const orderedProviders = reorderProvidersByActive(providers, activeProviderId)
5065

5166
const markDirty = () => setIsDirty(true)
@@ -104,6 +119,22 @@ export function StorageProvidersManager() {
104119
{
105120
onSuccess: () => {
106121
setIsDirty(false)
122+
const hadProvidersInitially =
123+
initialProviderStateRef.current ?? ((data?.providers.length ?? 0) > 0 ? true : false)
124+
if (!hadProvidersInitially && providers.length > 0 && !hasShownSyncPromptRef.current) {
125+
initialProviderStateRef.current = true
126+
hasShownSyncPromptRef.current = true
127+
Prompt.prompt({
128+
title: '配置完成,立即同步照片?',
129+
description: '存储提供商配置已经保存,是否前往「数据同步」页面立即开始扫描存储中的照片并写入数据库?',
130+
onConfirmText: '开始同步',
131+
onCancelText: '稍后再说',
132+
onConfirm: () => {
133+
setPhotoSyncAutoRun('apply')
134+
navigate('/photos/sync')
135+
},
136+
})
137+
}
107138
},
108139
},
109140
)

be/apps/dashboard/src/modules/storage-providers/hooks.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212

1313
export const STORAGE_PROVIDERS_QUERY_KEY = ['settings', 'storage-providers'] as const
1414

15-
export function useStorageProvidersQuery() {
15+
export function useStorageProvidersQuery(options?: { enabled?: boolean }) {
1616
return useQuery({
1717
queryKey: STORAGE_PROVIDERS_QUERY_KEY,
1818
queryFn: async () => {
@@ -29,6 +29,7 @@ export function useStorageProvidersQuery() {
2929
activeProviderId: ensureActiveProviderId(providers, activeProviderId),
3030
}
3131
},
32+
enabled: options?.enabled ?? true,
3233
})
3334
}
3435

@@ -44,6 +45,7 @@ export function useUpdateStorageProvidersMutation() {
4445
}>(STORAGE_PROVIDERS_QUERY_KEY)?.providers
4546

4647
const resolvedProviders = restoreProviderSecrets(currentProviders, previousProviders ?? [])
48+
const resolvedActiveId = ensureActiveProviderId(resolvedProviders, payload.activeProviderId ?? null)
4749

4850
await updateStorageSettings([
4951
{
@@ -52,11 +54,17 @@ export function useUpdateStorageProvidersMutation() {
5254
},
5355
{
5456
key: STORAGE_SETTING_KEYS.activeProvider,
55-
value: payload.activeProviderId ?? '',
57+
value: resolvedActiveId ?? '',
5658
},
5759
])
60+
61+
return {
62+
providers: resolvedProviders,
63+
activeProviderId: resolvedActiveId,
64+
}
5865
},
59-
onSuccess: () => {
66+
onSuccess: (data) => {
67+
queryClient.setQueryData(STORAGE_PROVIDERS_QUERY_KEY, data)
6068
void queryClient.invalidateQueries({
6169
queryKey: STORAGE_PROVIDERS_QUERY_KEY,
6270
})

0 commit comments

Comments
 (0)