Skip to content

Commit add48e7

Browse files
yashwinclaude
andcommitted
Dashboard: Address review feedback on backup components
* Derive the backup selection and viewport once in useBackupsBrowserState, and key the mobile-detail chrome off the resolved backup so the page header and the browser body can't disagree. * Move the download-id query-param cleanup into the route-aware shell and drop the callback from the shared BackupDownloadFlow. * Normalize gmt_offset to a number in api-core's fetchSiteSettings and read the site settings directly, removing the per-call-site coercion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 71eb13c commit add48e7

8 files changed

Lines changed: 92 additions & 84 deletions

File tree

client/dashboard/sites/backup-download/flow.tsx

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { __experimentalVStack as VStack } from '@wordpress/components';
44
import { useDispatch } from '@wordpress/data';
55
import { __ } from '@wordpress/i18n';
66
import { store as noticesStore } from '@wordpress/notices';
7-
import { useState, useEffect } from 'react';
7+
import { useState } from 'react';
88
import { useAnalytics } from '../../app/analytics';
99
import { Card, CardBody } from '../../components/card';
1010
import { useFormattedTime } from '../../components/formatted-time';
@@ -20,21 +20,14 @@ export function BackupDownloadFlow( {
2020
site,
2121
rewindId,
2222
initialDownloadId,
23-
onDownloadIdConsumed,
2423
}: {
2524
site: Site;
2625
rewindId: string;
2726
initialDownloadId?: number;
28-
onDownloadIdConsumed?: () => void;
2927
} ) {
30-
const { data: siteSettings } = useSuspenseQuery( {
31-
...siteSettingsQuery( site.ID ),
32-
select: ( s ) => ( {
33-
gmtOffset: Number( s?.gmt_offset ) || 0,
34-
timezoneString: s?.timezone_string || undefined,
35-
} ),
36-
} );
37-
const { gmtOffset, timezoneString } = siteSettings;
28+
const { data: siteSettings } = useSuspenseQuery( siteSettingsQuery( site.ID ) );
29+
const gmtOffset = siteSettings.gmt_offset;
30+
const timezoneString = siteSettings.timezone_string;
3831

3932
const initialStep: DownloadStep = initialDownloadId ? 'progress' : 'form';
4033

@@ -45,13 +38,6 @@ export function BackupDownloadFlow( {
4538
const { createSuccessNotice } = useDispatch( noticesStore );
4639
const { recordTracksEvent } = useAnalytics();
4740

48-
// Clean up the downloadId query param once we've captured it.
49-
useEffect( () => {
50-
if ( initialDownloadId ) {
51-
onDownloadIdConsumed?.();
52-
}
53-
}, [ initialDownloadId, onDownloadIdConsumed ] );
54-
5541
const handleDownloadInitiate = ( newDownloadId: number ) => {
5642
recordTracksEvent( 'calypso_dashboard_backups_download_started' );
5743
setCurrentStep( 'progress' );

client/dashboard/sites/backup-download/index.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { siteBySlugQuery } from '@automattic/api-queries';
22
import { useSuspenseQuery } from '@tanstack/react-query';
33
import { useRouter } from '@tanstack/react-router';
44
import { __ } from '@wordpress/i18n';
5-
import { useCallback } from 'react';
5+
import { useEffect } from 'react';
66
import Breadcrumbs from '../../app/breadcrumbs';
77
import { siteBackupDownloadRoute } from '../../app/router/sites';
88
import { PageHeader } from '../../components/page-header';
@@ -15,14 +15,16 @@ function SiteBackupDownload() {
1515
const { data: site } = useSuspenseQuery( siteBySlugQuery( siteSlug ) );
1616
const router = useRouter();
1717

18-
const handleDownloadIdConsumed = useCallback( () => {
19-
router.navigate( {
20-
to: siteBackupDownloadRoute.fullPath,
21-
params: { siteSlug, rewindId },
22-
search: {},
23-
replace: true,
24-
} );
25-
}, [ router, siteSlug, rewindId ] );
18+
useEffect( () => {
19+
if ( searchDownloadId ) {
20+
router.navigate( {
21+
to: siteBackupDownloadRoute.fullPath,
22+
params: { siteSlug, rewindId },
23+
search: {},
24+
replace: true,
25+
} );
26+
}
27+
}, [ router, siteSlug, rewindId, searchDownloadId ] );
2628

2729
return (
2830
<PageLayout
@@ -35,7 +37,6 @@ function SiteBackupDownload() {
3537
site={ site }
3638
rewindId={ rewindId }
3739
initialDownloadId={ searchDownloadId }
38-
onDownloadIdConsumed={ handleDownloadIdConsumed }
3940
/>
4041
</PageLayout>
4142
);

client/dashboard/sites/backup-restore/flow.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,9 @@ import type { Site } from '@automattic/api-core';
1919
type RestoreStep = 'form' | 'progress' | 'success' | 'error';
2020

2121
export function BackupRestoreFlow( { site, rewindId }: { site: Site; rewindId: string } ) {
22-
const { data: siteSettings } = useSuspenseQuery( {
23-
...siteSettingsQuery( site.ID ),
24-
select: ( s ) => ( {
25-
gmtOffset: Number( s?.gmt_offset ) || 0,
26-
timezoneString: s?.timezone_string || undefined,
27-
} ),
28-
} );
29-
const { gmtOffset, timezoneString } = siteSettings;
22+
const { data: siteSettings } = useSuspenseQuery( siteSettingsQuery( site.ID ) );
23+
const gmtOffset = siteSettings.gmt_offset;
24+
const timezoneString = siteSettings.timezone_string;
3025

3126
const [ currentStep, setCurrentStep ] = useState< RestoreStep >( 'form' );
3227
const [ restoreId, setRestoreId ] = useState< number | null >( null );

client/dashboard/sites/backups/backups-browser.tsx

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { __experimentalGrid as Grid } from '@wordpress/components';
22
import { useViewportMatch } from '@wordpress/compose';
3-
import { useState, useEffect } from 'react';
3+
import { useMemo } from 'react';
44
import { PerformanceTrackerStop } from '../../app/performance-tracking';
55
import { Card, CardBody } from '../../components/card';
66
import { BackupDetails } from './backup-details';
@@ -13,12 +13,54 @@ export function useIsBackupsSmallViewport() {
1313
return useViewportMatch( 'xlarge', '<' );
1414
}
1515

16-
interface BackupsBrowserProps {
16+
// Single owner of the resolved selection so the page header and browser body can't disagree.
17+
export function useBackupsBrowserState( {
18+
site,
19+
rewindId,
20+
dateRange,
21+
timezoneString,
22+
gmtOffset,
23+
enabled,
24+
}: {
1725
site: Site;
1826
rewindId?: string;
1927
dateRange?: { start: Date; end: Date };
2028
timezoneString?: string;
2129
gmtOffset?: number;
30+
enabled?: boolean;
31+
} ) {
32+
const isSmallViewport = useIsBackupsSmallViewport();
33+
34+
const { activityLog, isLoadingActivityLog } = useActivityLog( {
35+
siteId: site.ID,
36+
dateRange,
37+
gmtOffset,
38+
timezoneString,
39+
enabled,
40+
} );
41+
42+
const selectedBackup = useMemo< ActivityLogEntry | null >( () => {
43+
if ( rewindId ) {
44+
return activityLog?.find( ( item ) => item.rewind_id === rewindId ) ?? null;
45+
}
46+
if ( ! isSmallViewport ) {
47+
return activityLog?.[ 0 ] ?? null;
48+
}
49+
return null;
50+
}, [ rewindId, activityLog, isSmallViewport ] );
51+
52+
return { activityLog, isLoadingActivityLog, selectedBackup, isSmallViewport };
53+
}
54+
55+
interface BackupsBrowserProps {
56+
site: Site;
57+
activityLog: ActivityLogEntry[];
58+
isLoadingActivityLog: boolean;
59+
selectedBackup: ActivityLogEntry | null;
60+
isSmallViewport: boolean;
61+
dateRange?: { start: Date; end: Date };
62+
timezoneString?: string;
63+
gmtOffset?: number;
2264
searchParams?: Record< string, unknown >;
2365
onSelectBackup: ( backup: ActivityLogEntry | null ) => void;
2466
onRequestRestore?: ( backup: ActivityLogEntry ) => void;
@@ -28,7 +70,10 @@ interface BackupsBrowserProps {
2870

2971
export function BackupsBrowser( {
3072
site,
31-
rewindId,
73+
activityLog,
74+
isLoadingActivityLog,
75+
selectedBackup,
76+
isSmallViewport,
3277
dateRange,
3378
timezoneString,
3479
gmtOffset,
@@ -38,37 +83,6 @@ export function BackupsBrowser( {
3883
onRequestDownload,
3984
onGranularDownloadReady,
4085
}: BackupsBrowserProps ) {
41-
const isSmallViewport = useIsBackupsSmallViewport();
42-
const [ selectedBackup, setSelectedBackupInState ] = useState< ActivityLogEntry | null >( null );
43-
44-
const { activityLog, isLoadingActivityLog } = useActivityLog( {
45-
siteId: site.ID,
46-
dateRange,
47-
gmtOffset,
48-
timezoneString,
49-
} );
50-
51-
// Auto-select backup based on rewindId parameter.
52-
useEffect( () => {
53-
if ( rewindId && activityLog ) {
54-
const targetBackup = activityLog.find( ( item ) => item.rewind_id === rewindId );
55-
if ( targetBackup ) {
56-
setSelectedBackupInState( targetBackup );
57-
}
58-
return;
59-
}
60-
61-
// Select the first backup on the index route (desktop only) to keep the panel populated.
62-
const backup = activityLog?.[ 0 ];
63-
if ( ! rewindId && backup && ! isSmallViewport ) {
64-
setSelectedBackupInState( backup );
65-
}
66-
67-
if ( ! rewindId && ! backup ) {
68-
setSelectedBackupInState( null );
69-
}
70-
}, [ rewindId, activityLog, isSmallViewport ] );
71-
7286
const renderDetails = ( backup: ActivityLogEntry ) => (
7387
<BackupDetails
7488
backup={ backup }

client/dashboard/sites/backups/index.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import HostingFeatureGatedWithCallout from '../hosting-feature-gated-with-callou
2828
import { SitesNoticeArbiter } from '../notice-arbiter';
2929
import { BackupNotices } from './backup-notices';
3030
import { BackupNowButton } from './backup-now-button';
31-
import { BackupsBrowser, useIsBackupsSmallViewport } from './backups-browser';
31+
import { BackupsBrowser, useBackupsBrowserState } from './backups-browser';
3232
import illustrationUrl from './backups-callout-illustration.svg';
3333
import { useBackupState } from './use-backup-state';
3434
import './style.scss';
@@ -48,14 +48,9 @@ export function BackupsListPage() {
4848
const searchParams = siteBackupsRoute.useSearch();
4949
const backupState = useBackupState( site.ID );
5050

51-
const { data: siteSettings } = useSuspenseQuery( {
52-
...siteSettingsQuery( site.ID ),
53-
select: ( s ) => ( {
54-
gmtOffset: Number( s?.gmt_offset ) || 0,
55-
timezoneString: s?.timezone_string || undefined,
56-
} ),
57-
} );
58-
const { gmtOffset, timezoneString } = siteSettings;
51+
const { data: siteSettings } = useSuspenseQuery( siteSettingsQuery( site.ID ) );
52+
const gmtOffset = siteSettings.gmt_offset;
53+
const timezoneString = siteSettings.timezone_string;
5954

6055
const { dateRange, handleDateRangeChange } = useDateRange( {
6156
timezoneString,
@@ -109,9 +104,17 @@ export function BackupsListPage() {
109104
navigateToBackup( null );
110105
};
111106

112-
const isSmallViewport = useIsBackupsSmallViewport();
113107
const hasBackups = hasHostingFeature( site, HostingFeatures.BACKUPS_SELF_SERVE );
114-
const isMobileDetailsView = isSmallViewport && !! rewindId;
108+
const { activityLog, isLoadingActivityLog, selectedBackup, isSmallViewport } =
109+
useBackupsBrowserState( {
110+
site,
111+
rewindId,
112+
dateRange,
113+
timezoneString,
114+
gmtOffset,
115+
enabled: hasBackups,
116+
} );
117+
const isMobileDetailsView = isSmallViewport && !! selectedBackup;
115118
const shouldShowActions = hasBackups && ! isMobileDetailsView;
116119

117120
const actions = (
@@ -162,7 +165,10 @@ export function BackupsListPage() {
162165
{ hasBackups ? (
163166
<BackupsBrowser
164167
site={ site }
165-
rewindId={ rewindId }
168+
activityLog={ activityLog }
169+
isLoadingActivityLog={ isLoadingActivityLog }
170+
selectedBackup={ selectedBackup }
171+
isSmallViewport={ isSmallViewport }
166172
dateRange={ dateRange }
167173
timezoneString={ timezoneString }
168174
gmtOffset={ gmtOffset }

client/dashboard/sites/backups/use-activity-log.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface UseActivityLogOptions {
1010
dateRange?: { start: Date; end: Date };
1111
timezoneString?: string;
1212
gmtOffset?: number;
13+
enabled?: boolean;
1314
}
1415

1516
export interface UseActivityLogResult {
@@ -25,6 +26,7 @@ export function useActivityLog( {
2526
timezoneString,
2627
gmtOffset,
2728
dateRange,
29+
enabled = true,
2830
}: UseActivityLogOptions ): UseActivityLogResult {
2931
const { backup, hasRecentlyCompleted } = useBackupState( siteId );
3032

@@ -43,6 +45,7 @@ export function useActivityLog( {
4345

4446
const queryResult = useQuery( {
4547
...siteBackupActivityLogEntriesQuery( siteId, undefined, true, after, before ),
48+
enabled,
4649
refetchInterval: ( query ) => {
4750
if ( ! backup || ! hasRecentlyCompleted ) {
4851
return false;

packages/api-core/src/site-settings/fetchers.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@ export async function fetchSiteSettings( siteId: number ): Promise< SiteSettings
66
path: `/sites/${ siteId }/settings`,
77
apiVersion: '1.4',
88
} );
9-
return settings;
9+
return {
10+
...settings,
11+
gmt_offset: Number( settings.gmt_offset ) || 0,
12+
};
1013
}

packages/api-core/src/site-settings/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export interface SiteSettings {
22
blog_public?: number;
33
is_fully_managed_agency_site?: boolean;
4-
gmt_offset?: number | string;
4+
gmt_offset?: number;
55
timezone_string?: string;
66
/**
77
* @deprecated Use `wpcom_public_coming_soon` instead.

0 commit comments

Comments
 (0)