Skip to content

Commit c96cc06

Browse files
committed
refactor(dashboard): streamline loading state handling across components and improve service worker base URL configuration
1 parent bfd7f90 commit c96cc06

File tree

8 files changed

+124
-105
lines changed

8 files changed

+124
-105
lines changed

dashboard/src/components/admins/admins-table.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ export default function AdminsTable({ onEdit, onDelete, onToggleStatus, onResetU
336336
onRemoveAllUsers: handleRemoveAllUsersClick,
337337
})
338338

339-
const showLoadingSpinner = isLoading && isFirstLoadRef.current
339+
const isCurrentlyLoading = isLoading || (isFetching && !adminsResponse)
340340
const isPageLoading = isChangingPage || (isFetching && !isFirstLoadRef.current && !isAutoRefreshingRef.current)
341341

342342
return (
@@ -351,7 +351,7 @@ export default function AdminsTable({ onEdit, onDelete, onToggleStatus, onResetU
351351
onResetUsage={handleResetUsersUsageClick}
352352
onRemoveAllUsers={handleRemoveAllUsersClick}
353353
setStatusToggleDialogOpen={setStatusToggleDialogOpen}
354-
isLoading={showLoadingSpinner}
354+
isLoading={isCurrentlyLoading && isFirstLoadRef.current}
355355
isFetching={isFetching && !isFirstLoadRef.current && !isAutoRefreshingRef.current}
356356
/>
357357
<PaginationControls

dashboard/src/components/groups/groups-list.tsx

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,9 @@ export default function GroupsList({ isDialogOpen, onOpenChange }: GroupsListPro
9393
await refetch()
9494
}
9595

96-
const isEmpty = !isLoading && (!filteredGroups || filteredGroups.length === 0) && !searchQuery.trim()
97-
const isSearchEmpty = !isLoading && (!filteredGroups || filteredGroups.length === 0) && searchQuery.trim() !== ''
96+
const isCurrentlyLoading = isLoading || (isFetching && !groupsData)
97+
const isEmpty = !isCurrentlyLoading && (!filteredGroups || filteredGroups.length === 0) && !searchQuery.trim()
98+
const isSearchEmpty = !isCurrentlyLoading && (!filteredGroups || filteredGroups.length === 0) && searchQuery.trim() !== ''
9899

99100
return (
100101
<div className="w-full flex-1 space-y-4">
@@ -120,7 +121,25 @@ export default function GroupsList({ isDialogOpen, onOpenChange }: GroupsListPro
120121
<RefreshCw className={cn('h-4 w-4', isFetching && 'animate-spin')} />
121122
</Button>
122123
</div>
123-
{isEmpty && (
124+
{isCurrentlyLoading && (
125+
<ScrollArea className="h-[calc(100vh-8rem)]">
126+
<div dir={dir} className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
127+
{[...Array(6)].map((_, i) => (
128+
<Card key={i} className="px-4 py-5">
129+
<div className="flex items-center gap-2 sm:gap-3">
130+
<Skeleton className="h-8 w-8 shrink-0 rounded-full" />
131+
<div className="min-w-0 flex-1 space-y-2">
132+
<Skeleton className="h-5 w-24 sm:w-32" />
133+
<Skeleton className="h-4 w-20 sm:w-24" />
134+
</div>
135+
<Skeleton className="h-8 w-8 shrink-0" />
136+
</div>
137+
</Card>
138+
))}
139+
</div>
140+
</ScrollArea>
141+
)}
142+
{isEmpty && !isCurrentlyLoading && (
124143
<Card className="mb-12">
125144
<CardContent className="p-8 text-center">
126145
<div className="space-y-4">
@@ -130,7 +149,7 @@ export default function GroupsList({ isDialogOpen, onOpenChange }: GroupsListPro
130149
</CardContent>
131150
</Card>
132151
)}
133-
{isSearchEmpty && (
152+
{isSearchEmpty && !isCurrentlyLoading && (
134153
<Card className="mb-12">
135154
<CardContent className="p-8 text-center">
136155
<div className="space-y-4">
@@ -140,23 +159,10 @@ export default function GroupsList({ isDialogOpen, onOpenChange }: GroupsListPro
140159
</CardContent>
141160
</Card>
142161
)}
143-
{!isEmpty && !isSearchEmpty && (
162+
{!isEmpty && !isSearchEmpty && !isCurrentlyLoading && (
144163
<ScrollArea className="h-[calc(100vh-8rem)]">
145164
<div dir={dir} className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
146-
{isLoading
147-
? [...Array(6)].map((_, i) => (
148-
<Card key={i} className="px-4 py-5">
149-
<div className="flex items-center gap-2 sm:gap-3">
150-
<Skeleton className="h-8 w-8 shrink-0 rounded-full" />
151-
<div className="min-w-0 flex-1 space-y-2">
152-
<Skeleton className="h-5 w-24 sm:w-32" />
153-
<Skeleton className="h-4 w-20 sm:w-24" />
154-
</div>
155-
<Skeleton className="h-8 w-8 shrink-0" />
156-
</div>
157-
</Card>
158-
))
159-
: filteredGroups?.map(group => <Group key={group.id} group={group} onEdit={handleEdit} onToggleStatus={handleToggleStatus} />)}
165+
{filteredGroups?.map(group => <Group key={group.id} group={group} onEdit={handleEdit} onToggleStatus={handleToggleStatus} />)}
160166
</div>
161167
</ScrollArea>
162168
)}

dashboard/src/components/hosts/hosts-list.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -831,8 +831,9 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
831831
})
832832
}, [sortedHosts, searchQuery])
833833

834-
const isEmpty = filteredHosts.length === 0 && !searchQuery.trim() && sortedHosts.length === 0
835-
const isSearchEmpty = filteredHosts.length === 0 && searchQuery.trim() !== ''
834+
const isCurrentlyLoading = !data || (isRefreshing && sortedHosts.length === 0)
835+
const isEmpty = !isCurrentlyLoading && filteredHosts.length === 0 && !searchQuery.trim() && sortedHosts.length === 0
836+
const isSearchEmpty = !isCurrentlyLoading && filteredHosts.length === 0 && searchQuery.trim() !== ''
836837

837838
return (
838839
<div>
@@ -858,7 +859,26 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
858859
<RefreshCw className={cn('h-4 w-4', isRefreshing && 'animate-spin')} />
859860
</Button>
860861
</div>
861-
{isEmpty && (
862+
{isCurrentlyLoading && (
863+
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
864+
{Array.from({ length: 6 }).map((_, index) => (
865+
<Card key={index} className="animate-pulse">
866+
<CardContent className="p-4">
867+
<div className="flex flex-col gap-2">
868+
<div className="h-5 w-2/3 rounded-md bg-muted"></div>
869+
<div className="h-3 w-full rounded-md bg-muted"></div>
870+
<div className="h-3 w-4/5 rounded-md bg-muted"></div>
871+
<div className="mt-2 flex justify-between">
872+
<div className="h-6 w-1/4 rounded-md bg-muted"></div>
873+
<div className="h-6 w-1/4 rounded-md bg-muted"></div>
874+
</div>
875+
</div>
876+
</CardContent>
877+
</Card>
878+
))}
879+
</div>
880+
)}
881+
{isEmpty && !isCurrentlyLoading && (
862882
<Card className="mb-12">
863883
<CardContent className="p-8 text-center">
864884
<div className="space-y-4">
@@ -868,7 +888,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
868888
</CardContent>
869889
</Card>
870890
)}
871-
{isSearchEmpty && (
891+
{isSearchEmpty && !isCurrentlyLoading && (
872892
<Card className="mb-12">
873893
<CardContent className="p-8 text-center">
874894
<div className="space-y-4">
@@ -878,7 +898,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
878898
</CardContent>
879899
</Card>
880900
)}
881-
{!isEmpty && !isSearchEmpty && (
901+
{!isEmpty && !isSearchEmpty && !isCurrentlyLoading && (
882902
<div>
883903
<DndContext sensors={isUpdatingPriorities ? [] : sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
884904
<SortableContext items={sortableHosts} strategy={rectSortingStrategy}>

dashboard/src/components/users/users-table.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -487,12 +487,12 @@ const UsersTable = memo(() => {
487487

488488
const totalUsers = usersData?.total || 0
489489
const totalPages = Math.ceil(totalUsers / itemsPerPage)
490-
const showLoadingSpinner = isLoading && isFirstLoadRef.current
491490
const isPageLoading = isChangingPage || (isFetching && !isFirstLoadRef.current && !isAutoRefreshingRef.current)
492491
const hasActiveFilters = !!(filters.search || filters.proxy_id || filters.status || filters.admin?.length || filters.group?.length)
493492
const usersList = usersData?.users || []
494-
const isEmpty = !showLoadingSpinner && usersList.length === 0 && totalUsers === 0 && !hasActiveFilters
495-
const isSearchEmpty = !showLoadingSpinner && usersList.length === 0 && hasActiveFilters
493+
const isCurrentlyLoading = isLoading || (isFetching && !usersData)
494+
const isEmpty = !isCurrentlyLoading && usersList.length === 0 && totalUsers === 0 && !hasActiveFilters
495+
const isSearchEmpty = !isCurrentlyLoading && usersList.length === 0 && hasActiveFilters
496496

497497
return (
498498
<div>
@@ -541,11 +541,20 @@ const UsersTable = memo(() => {
541541
</CardContent>
542542
</Card>
543543
)}
544-
{!isEmpty && !isSearchEmpty && (
544+
{isCurrentlyLoading && !isSearchEmpty && (
545+
<DataTable
546+
columns={columns}
547+
data={[]}
548+
isLoading={true}
549+
isFetching={false}
550+
onEdit={handleEdit}
551+
/>
552+
)}
553+
{!isEmpty && !isSearchEmpty && !isCurrentlyLoading && (
545554
<DataTable
546555
columns={columns}
547556
data={usersList}
548-
isLoading={showLoadingSpinner}
557+
isLoading={false}
549558
isFetching={isFetching && !isFirstLoadRef.current && !isAutoRefreshingRef.current}
550559
onEdit={handleEdit}
551560
/>

dashboard/src/pages/_dashboard.settings.subscriptions.tsx

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -998,27 +998,30 @@ export default function SubscriptionSettings() {
998998

999999
{/* Subscription Rules with Drag & Drop */}
10001000
<div className="space-y-4">
1001-
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
1002-
<div className="space-y-1">
1003-
<h3 className="flex items-center gap-2 text-lg font-semibold tracking-tight">
1004-
{t('settings.subscriptions.rules.title')}
1005-
{ruleFields.length > 0 && (
1006-
<Badge variant="secondary" className="ml-2">
1007-
{ruleFields.length}
1008-
</Badge>
1009-
)}
1010-
</h3>
1011-
<p className="text-sm text-muted-foreground">{t('settings.subscriptions.rules.description')}</p>
1012-
</div>
1013-
<div className="flex flex-col gap-2 sm:flex-row sm:gap-2">
1014-
<Button type="button" variant="outline" size="sm" onClick={handleResetToDefault} className="flex items-center gap-2" disabled={isSaving}>
1015-
<RotateCcw className="h-4 w-4" />
1016-
{t('settings.subscriptions.resetToDefault', { defaultValue: 'Reset to Default' })}
1017-
</Button>
1018-
<Button type="button" variant="outline" size="sm" onClick={addRule} className="flex shrink-0 items-center gap-2">
1019-
<Plus className="h-4 w-4" />
1020-
{t('settings.subscriptions.rules.addRule')}
1021-
</Button>
1001+
<div className="rounded-lg border bg-card p-4 shadow-sm">
1002+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
1003+
<div className="space-y-1">
1004+
<h3 className="flex items-center gap-2 text-lg font-semibold tracking-tight">
1005+
<Sword className="h-5 w-5 text-primary" />
1006+
{t('settings.subscriptions.rules.title')}
1007+
{ruleFields.length > 0 && (
1008+
<Badge variant="secondary" className="ml-2">
1009+
{ruleFields.length}
1010+
</Badge>
1011+
)}
1012+
</h3>
1013+
<p className="text-sm text-muted-foreground">{t('settings.subscriptions.rules.description')}</p>
1014+
</div>
1015+
<div className="flex flex-col gap-2 sm:flex-row sm:gap-2">
1016+
<Button type="button" variant="outline" size="sm" onClick={handleResetToDefault} className="flex items-center gap-2" disabled={isSaving}>
1017+
<RotateCcw className="h-4 w-4" />
1018+
{t('settings.subscriptions.resetToDefault', { defaultValue: 'Reset to Default' })}
1019+
</Button>
1020+
<Button type="button" variant="outline" size="sm" onClick={addRule} className="flex shrink-0 items-center gap-2">
1021+
<Plus className="h-4 w-4" />
1022+
{t('settings.subscriptions.rules.addRule')}
1023+
</Button>
1024+
</div>
10221025
</div>
10231026
</div>
10241027

dashboard/src/pages/_dashboard.templates.tsx

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,9 @@ export default function UserTemplates() {
118118
await refetch()
119119
}
120120

121-
const isEmpty = !isLoading && (!filteredTemplates || filteredTemplates.length === 0) && !searchQuery.trim()
122-
const isSearchEmpty = !isLoading && (!filteredTemplates || filteredTemplates.length === 0) && searchQuery.trim() !== ''
121+
const isCurrentlyLoading = isLoading || (isFetching && !userTemplates)
122+
const isEmpty = !isCurrentlyLoading && (!filteredTemplates || filteredTemplates.length === 0) && !searchQuery.trim()
123+
const isSearchEmpty = !isCurrentlyLoading && (!filteredTemplates || filteredTemplates.length === 0) && searchQuery.trim() !== ''
123124

124125
return (
125126
<div className="flex w-full flex-col items-start gap-2">
@@ -160,7 +161,31 @@ export default function UserTemplates() {
160161
</Button>
161162
</div>
162163

163-
{isEmpty && (
164+
{isCurrentlyLoading && (
165+
<div
166+
className="mb-12 grid transform-gpu animate-slide-up grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"
167+
style={{ animationDuration: '500ms', animationDelay: '100ms', animationFillMode: 'both' }}
168+
>
169+
{[...Array(6)].map((_, i) => (
170+
<Card key={i} className="px-4 py-5 sm:px-5 sm:py-6">
171+
<div className="flex items-start justify-between gap-2 sm:gap-3">
172+
<div className="min-w-0 flex-1">
173+
<div className="flex items-center gap-x-2">
174+
<Skeleton className="h-2 w-2 shrink-0 rounded-full" />
175+
<Skeleton className="h-5 w-24 sm:w-32" />
176+
</div>
177+
<div className="space-y-2">
178+
<Skeleton className="h-4 w-32 sm:w-40 md:w-48" />
179+
<Skeleton className="h-4 w-28 sm:w-36 md:w-40" />
180+
</div>
181+
</div>
182+
<Skeleton className="h-8 w-8 shrink-0" />
183+
</div>
184+
</Card>
185+
))}
186+
</div>
187+
)}
188+
{isEmpty && !isCurrentlyLoading && (
164189
<Card className="mb-12">
165190
<CardContent className="p-8 text-center">
166191
<div className="space-y-4">
@@ -170,7 +195,7 @@ export default function UserTemplates() {
170195
</CardContent>
171196
</Card>
172197
)}
173-
{isSearchEmpty && (
198+
{isSearchEmpty && !isCurrentlyLoading && (
174199
<Card className="mb-12">
175200
<CardContent className="p-8 text-center">
176201
<div className="space-y-4">
@@ -180,30 +205,12 @@ export default function UserTemplates() {
180205
</CardContent>
181206
</Card>
182207
)}
183-
{!isEmpty && !isSearchEmpty && (
208+
{!isEmpty && !isSearchEmpty && !isCurrentlyLoading && (
184209
<div
185210
className="mb-12 grid transform-gpu animate-slide-up grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"
186211
style={{ animationDuration: '500ms', animationDelay: '100ms', animationFillMode: 'both' }}
187212
>
188-
{isLoading
189-
? [...Array(6)].map((_, i) => (
190-
<Card key={i} className="px-4 py-5 sm:px-5 sm:py-6">
191-
<div className="flex items-start justify-between gap-2 sm:gap-3">
192-
<div className="min-w-0 flex-1">
193-
<div className="flex items-center gap-x-2">
194-
<Skeleton className="h-2 w-2 shrink-0 rounded-full" />
195-
<Skeleton className="h-5 w-24 sm:w-32" />
196-
</div>
197-
<div className="space-y-2">
198-
<Skeleton className="h-4 w-32 sm:w-40 md:w-48" />
199-
<Skeleton className="h-4 w-28 sm:w-36 md:w-40" />
200-
</div>
201-
</div>
202-
<Skeleton className="h-8 w-8 shrink-0" />
203-
</div>
204-
</Card>
205-
))
206-
: filteredTemplates?.map((template: UserTemplateResponse) => <UserTemplate onEdit={handleEdit} template={template} key={template.id} onToggleStatus={handleToggleStatus} />)}
213+
{filteredTemplates?.map((template: UserTemplateResponse) => <UserTemplate onEdit={handleEdit} template={template} key={template.id} onToggleStatus={handleToggleStatus} />)}
207214
</div>
208215
)}
209216
</div>

dashboard/src/sw-register.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
1-
const ensureBase = (value?: string) => {
2-
if (!value) {
3-
return '/dashboard/'
4-
}
5-
6-
if (!value.startsWith('/')) {
7-
value = `/${value}`
8-
}
9-
10-
return value.endsWith('/') ? value : `${value}/`
11-
}
12-
131
export function registerSW() {
142
if ('serviceWorker' in navigator) {
15-
const baseUrl = ensureBase(import.meta.env.BASE_URL)
3+
const baseUrl = import.meta.env.BASE_URL || '/'
164

175
navigator.serviceWorker
186
.register(`${baseUrl}sw.js`)

dashboard/vite.config.mts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,8 @@ import tsconfigPaths from 'vite-tsconfig-paths'
77
import path from 'path'
88
import { VitePWA } from 'vite-plugin-pwa'
99

10-
const ensureBase = (value?: string) => {
11-
if (!value) {
12-
return '/dashboard/'
13-
}
14-
15-
if (!value.startsWith('/')) {
16-
value = `/${value}`
17-
}
18-
19-
return value.endsWith('/') ? value : `${value}/`
20-
}
21-
22-
const base = ensureBase(process.env.BASE_URL)
23-
2410
export default defineConfig({
25-
base,
11+
base: process.env.BASE_URL,
2612
clearScreen: false,
2713
server: {
2814
host: true,
@@ -91,7 +77,7 @@ export default defineConfig({
9177
registerType: 'autoUpdate',
9278
injectRegister: false,
9379
workbox: {
94-
navigateFallback: `${base}offline.html`,
80+
navigateFallback: '/index.html',
9581
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
9682
skipWaiting: true,
9783
clientsClaim: true,

0 commit comments

Comments
 (0)