Skip to content

Commit c304ff8

Browse files
yashwinclaude
andcommitted
Dashboard: Extract route-agnostic backup components for reuse
Refactor the dashboard's backup UI into shared, route-agnostic pieces so the list/detail/restore/download logic can be reused across variants: * BackupsBrowser - list + detail grid + selection + activity-log fetch * BackupRestoreFlow / BackupDownloadFlow - the restore/download step machines * BackupDetails - restore/download navigation injected via callbacks * BackupCardContent - overview card body parameterized by siteId + backupUrl The dotcom backup pages (sites/backups, sites/backup-restore, sites/backup-download) become thin shells over these components; behavior is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent dc44c6a commit c304ff8

12 files changed

Lines changed: 558 additions & 461 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { siteSettingsQuery } from '@automattic/api-queries';
2+
import { useSuspenseQuery } from '@tanstack/react-query';
3+
import { __experimentalVStack as VStack } from '@wordpress/components';
4+
import { useDispatch } from '@wordpress/data';
5+
import { __ } from '@wordpress/i18n';
6+
import { store as noticesStore } from '@wordpress/notices';
7+
import { useState, useEffect } from 'react';
8+
import { useAnalytics } from '../../app/analytics';
9+
import { Card, CardBody } from '../../components/card';
10+
import { useFormattedTime } from '../../components/formatted-time';
11+
import SiteBackupDownloadError from './error';
12+
import SiteBackupDownloadForm from './form';
13+
import SiteBackupDownloadProgress from './progress';
14+
import SiteBackupDownloadSuccess from './success';
15+
import type { Site } from '@automattic/api-core';
16+
17+
type DownloadStep = 'form' | 'progress' | 'success' | 'error';
18+
19+
export function BackupDownloadFlow( {
20+
site,
21+
rewindId,
22+
initialDownloadId,
23+
onDownloadIdConsumed,
24+
}: {
25+
site: Site;
26+
rewindId: string;
27+
initialDownloadId?: number;
28+
onDownloadIdConsumed?: () => void;
29+
} ) {
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;
38+
39+
const initialStep: DownloadStep = initialDownloadId ? 'progress' : 'form';
40+
41+
const [ currentStep, setCurrentStep ] = useState< DownloadStep >( initialStep );
42+
const [ downloadId, setDownloadId ] = useState< number | null >( initialDownloadId || null );
43+
const [ downloadUrl, setDownloadUrl ] = useState< string | null >( null );
44+
const [ fileSizeBytes, setFileSizeBytes ] = useState< string | undefined >();
45+
const { createSuccessNotice } = useDispatch( noticesStore );
46+
const { recordTracksEvent } = useAnalytics();
47+
48+
// Clean up the downloadId query param once we've captured it.
49+
useEffect( () => {
50+
if ( initialDownloadId ) {
51+
onDownloadIdConsumed?.();
52+
}
53+
}, [ initialDownloadId, onDownloadIdConsumed ] );
54+
55+
const handleDownloadInitiate = ( newDownloadId: number ) => {
56+
recordTracksEvent( 'calypso_dashboard_backups_download_started' );
57+
setCurrentStep( 'progress' );
58+
setDownloadId( newDownloadId );
59+
};
60+
61+
const handleDownloadComplete = ( newDownloadUrl: string, newFileSizeBytes?: string ) => {
62+
recordTracksEvent( 'calypso_dashboard_backups_download_completed' );
63+
setCurrentStep( 'success' );
64+
setDownloadUrl( newDownloadUrl );
65+
setFileSizeBytes( newFileSizeBytes );
66+
createSuccessNotice( __( 'Backup download file is ready.' ), {
67+
type: 'snackbar',
68+
} );
69+
};
70+
71+
const handleDownloadError = () => {
72+
recordTracksEvent( 'calypso_dashboard_backups_download_failed' );
73+
setCurrentStep( 'error' );
74+
};
75+
76+
const handleRetry = () => {
77+
recordTracksEvent( 'calypso_dashboard_backups_download_retry' );
78+
setCurrentStep( 'form' );
79+
setDownloadId( null );
80+
setDownloadUrl( null );
81+
setFileSizeBytes( undefined );
82+
};
83+
84+
const downloadPointDate = useFormattedTime(
85+
new Date( parseFloat( rewindId ) * 1000 ).toISOString(),
86+
{
87+
dateStyle: 'medium',
88+
timeStyle: 'short',
89+
},
90+
timezoneString,
91+
gmtOffset
92+
);
93+
94+
const renderStep = () => {
95+
switch ( currentStep ) {
96+
case 'form':
97+
return (
98+
<SiteBackupDownloadForm
99+
siteId={ site.ID }
100+
rewindId={ rewindId }
101+
downloadPointDate={ downloadPointDate }
102+
onDownloadInitiate={ handleDownloadInitiate }
103+
/>
104+
);
105+
case 'progress':
106+
return downloadId ? (
107+
<SiteBackupDownloadProgress
108+
site={ site }
109+
downloadId={ downloadId }
110+
onDownloadComplete={ handleDownloadComplete }
111+
onDownloadError={ handleDownloadError }
112+
/>
113+
) : null;
114+
case 'success':
115+
return downloadUrl ? (
116+
<SiteBackupDownloadSuccess
117+
downloadPointDate={ downloadPointDate }
118+
downloadUrl={ downloadUrl }
119+
fileSizeBytes={ fileSizeBytes }
120+
/>
121+
) : null;
122+
case 'error':
123+
return <SiteBackupDownloadError onRetry={ handleRetry } />;
124+
}
125+
};
126+
127+
if ( currentStep === 'success' ) {
128+
return <>{ renderStep() }</>;
129+
}
130+
131+
return (
132+
<Card>
133+
<CardBody>
134+
<VStack spacing={ 4 }>{ renderStep() }</VStack>
135+
</CardBody>
136+
</Card>
137+
);
138+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { createInterpolateElement } from '@wordpress/element';
77
import { __ } from '@wordpress/i18n';
88
import { store as noticesStore } from '@wordpress/notices';
99
import { useState } from 'react';
10-
import { siteBackupDownloadRoute } from '../../app/router/sites';
1110
import { ButtonStack } from '../../components/button-stack';
1211
import type { DownloadConfig } from '@automattic/api-core';
1312
import type { Field } from '@wordpress/dataviews';
@@ -51,14 +50,15 @@ const fields: Field< DownloadConfig >[] = [
5150

5251
function SiteBackupDownloadForm( {
5352
siteId,
53+
rewindId,
5454
downloadPointDate,
5555
onDownloadInitiate,
5656
}: {
5757
siteId: number;
58+
rewindId: string;
5859
downloadPointDate: string;
5960
onDownloadInitiate: ( downloadId: number ) => void;
6061
} ) {
61-
const { rewindId } = siteBackupDownloadRoute.useParams();
6262
const { mutate: downloadMutation, isPending: isDownloadMutationPending } = useMutation(
6363
siteBackupDownloadInitiateMutation( siteId )
6464
);

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

Lines changed: 16 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,28 @@
1-
import { siteBySlugQuery, siteSettingsQuery } from '@automattic/api-queries';
1+
import { siteBySlugQuery } from '@automattic/api-queries';
22
import { useSuspenseQuery } from '@tanstack/react-query';
33
import { useRouter } from '@tanstack/react-router';
4-
import { __experimentalVStack as VStack } from '@wordpress/components';
5-
import { useDispatch } from '@wordpress/data';
64
import { __ } from '@wordpress/i18n';
7-
import { store as noticesStore } from '@wordpress/notices';
8-
import { useState, useEffect } from 'react';
9-
import { useAnalytics } from '../../app/analytics';
5+
import { useCallback } from 'react';
106
import Breadcrumbs from '../../app/breadcrumbs';
117
import { siteBackupDownloadRoute } from '../../app/router/sites';
12-
import { Card, CardBody } from '../../components/card';
13-
import { useFormattedTime } from '../../components/formatted-time';
148
import { PageHeader } from '../../components/page-header';
159
import PageLayout from '../../components/page-layout';
16-
import SiteBackupDownloadError from './error';
17-
import SiteBackupDownloadForm from './form';
18-
import SiteBackupDownloadProgress from './progress';
19-
import SiteBackupDownloadSuccess from './success';
20-
21-
type DownloadStep = 'form' | 'progress' | 'success' | 'error';
10+
import { BackupDownloadFlow } from './flow';
2211

2312
function SiteBackupDownload() {
2413
const { siteSlug, rewindId } = siteBackupDownloadRoute.useParams();
2514
const { downloadId: searchDownloadId } = siteBackupDownloadRoute.useSearch();
2615
const { data: site } = useSuspenseQuery( siteBySlugQuery( siteSlug ) );
27-
28-
const { data: siteSettings } = useSuspenseQuery( {
29-
...siteSettingsQuery( site.ID ),
30-
select: ( s ) => ( {
31-
gmtOffset: Number( s?.gmt_offset ) || 0,
32-
timezoneString: s?.timezone_string || undefined,
33-
} ),
34-
} );
35-
36-
const { gmtOffset, timezoneString } = siteSettings;
37-
38-
// Initialize step based on whether downloadId is provided in search params
39-
const initialStep: DownloadStep = searchDownloadId ? 'progress' : 'form';
40-
41-
const [ currentStep, setCurrentStep ] = useState< DownloadStep >( initialStep );
42-
const [ downloadId, setDownloadId ] = useState< number | null >( searchDownloadId || null );
43-
const [ downloadUrl, setDownloadUrl ] = useState< string | null >( null );
44-
const [ fileSizeBytes, setFileSizeBytes ] = useState< string | undefined >();
45-
const { createSuccessNotice } = useDispatch( noticesStore );
46-
const { recordTracksEvent } = useAnalytics();
47-
4816
const router = useRouter();
4917

50-
// Clean up the downloadId query param once we've captured it
51-
useEffect( () => {
52-
if ( searchDownloadId ) {
53-
router.navigate( {
54-
to: siteBackupDownloadRoute.fullPath,
55-
params: { siteSlug, rewindId },
56-
search: {},
57-
replace: true,
58-
} );
59-
}
60-
}, [ router, siteSlug, rewindId, searchDownloadId ] );
61-
62-
const handleDownloadInitiate = ( newDownloadId: number ) => {
63-
recordTracksEvent( 'calypso_dashboard_backups_download_started' );
64-
setCurrentStep( 'progress' );
65-
setDownloadId( newDownloadId );
66-
};
67-
68-
const handleDownloadComplete = ( newDownloadUrl: string, newFileSizeBytes?: string ) => {
69-
recordTracksEvent( 'calypso_dashboard_backups_download_completed' );
70-
setCurrentStep( 'success' );
71-
setDownloadUrl( newDownloadUrl );
72-
setFileSizeBytes( newFileSizeBytes );
73-
createSuccessNotice( __( 'Backup download file is ready.' ), {
74-
type: 'snackbar',
18+
const handleDownloadIdConsumed = useCallback( () => {
19+
router.navigate( {
20+
to: siteBackupDownloadRoute.fullPath,
21+
params: { siteSlug, rewindId },
22+
search: {},
23+
replace: true,
7524
} );
76-
};
77-
78-
const handleDownloadError = () => {
79-
recordTracksEvent( 'calypso_dashboard_backups_download_failed' );
80-
setCurrentStep( 'error' );
81-
};
82-
83-
const handleRetry = () => {
84-
recordTracksEvent( 'calypso_dashboard_backups_download_retry' );
85-
setCurrentStep( 'form' );
86-
setDownloadId( null );
87-
setDownloadUrl( null );
88-
setFileSizeBytes( undefined );
89-
};
90-
91-
const downloadPointDate = useFormattedTime(
92-
new Date( parseFloat( rewindId ) * 1000 ).toISOString(),
93-
{
94-
dateStyle: 'medium',
95-
timeStyle: 'short',
96-
},
97-
timezoneString,
98-
gmtOffset
99-
);
100-
101-
const renderStep = () => {
102-
switch ( currentStep ) {
103-
case 'form':
104-
return (
105-
<SiteBackupDownloadForm
106-
siteId={ site.ID }
107-
downloadPointDate={ downloadPointDate }
108-
onDownloadInitiate={ handleDownloadInitiate }
109-
/>
110-
);
111-
case 'progress':
112-
return downloadId ? (
113-
<SiteBackupDownloadProgress
114-
site={ site }
115-
downloadId={ downloadId }
116-
onDownloadComplete={ handleDownloadComplete }
117-
onDownloadError={ handleDownloadError }
118-
/>
119-
) : null;
120-
case 'success':
121-
return downloadUrl ? (
122-
<SiteBackupDownloadSuccess
123-
downloadPointDate={ downloadPointDate }
124-
downloadUrl={ downloadUrl }
125-
fileSizeBytes={ fileSizeBytes }
126-
/>
127-
) : null;
128-
case 'error':
129-
return <SiteBackupDownloadError onRetry={ handleRetry } />;
130-
}
131-
};
25+
}, [ router, siteSlug, rewindId ] );
13226

13327
return (
13428
<PageLayout
@@ -137,15 +31,12 @@ function SiteBackupDownload() {
13731
<PageHeader prefix={ <Breadcrumbs length={ 2 } /> } title={ __( 'Download backup' ) } />
13832
}
13933
>
140-
{ currentStep !== 'success' ? (
141-
<Card>
142-
<CardBody>
143-
<VStack spacing={ 4 }>{ renderStep() }</VStack>
144-
</CardBody>
145-
</Card>
146-
) : (
147-
renderStep()
148-
) }
34+
<BackupDownloadFlow
35+
site={ site }
36+
rewindId={ rewindId }
37+
initialDownloadId={ searchDownloadId }
38+
onDownloadIdConsumed={ handleDownloadIdConsumed }
39+
/>
14940
</PageLayout>
15041
);
15142
}

0 commit comments

Comments
 (0)