diff --git a/client/components/jetpack/backup-schedule-setting/index.tsx b/client/components/jetpack/backup-schedule-setting/index.tsx index a6f1c895da67a..9515f198124ad 100644 --- a/client/components/jetpack/backup-schedule-setting/index.tsx +++ b/client/components/jetpack/backup-schedule-setting/index.tsx @@ -1,31 +1,111 @@ import { Card } from '@automattic/components'; +import { useQueryClient } from '@tanstack/react-query'; import { SelectControl } from '@wordpress/components'; -import { useTranslate } from 'i18n-calypso'; +import { TranslateResult, useTranslate } from 'i18n-calypso'; +import { useLocalizedMoment } from 'calypso/components/localized-moment'; +import useScheduledTimeMutation from 'calypso/data/jetpack-backup/use-scheduled-time-mutation'; +import useScheduledTimeQuery from 'calypso/data/jetpack-backup/use-scheduled-time-query'; +import { applySiteOffset } from 'calypso/lib/site/timezone'; +import { useDispatch, useSelector } from 'calypso/state'; +import { errorNotice, successNotice } from 'calypso/state/notices/actions'; +import getSiteGmtOffset from 'calypso/state/selectors/get-site-gmt-offset'; +import getSiteTimezoneValue from 'calypso/state/selectors/get-site-timezone-value'; +import { getSelectedSiteId } from 'calypso/state/ui/selectors'; import type { FunctionComponent } from 'react'; import './style.scss'; -// Helper function to generate all time slots -const generateTimeSlots = (): { label: string; value: string }[] => { - const options = []; - for ( let hour = 0; hour < 24; hour++ ) { - const startTime = hour.toString().padStart( 2, '0' ) + ':00'; - const endTime = hour.toString().padStart( 2, '0' ) + ':59'; - options.push( { - label: `${ startTime } - ${ endTime }`, - value: hour.toString(), - } ); - } - return options; -}; - const BackupScheduleSetting: FunctionComponent = () => { + const dispatch = useDispatch(); const translate = useTranslate(); - const options = generateTimeSlots(); + const queryClient = useQueryClient(); + const moment = useLocalizedMoment(); + const siteId = useSelector( getSelectedSiteId ) as number; + const timezone = useSelector( ( state ) => getSiteTimezoneValue( state, siteId ) ); + const gmtOffset = useSelector( ( state ) => getSiteGmtOffset( state, siteId ) ); + + const convertHourToRange = ( hour: number, isUtc: boolean = false ): string => { + const time = isUtc + ? moment.utc().startOf( 'day' ).hour( hour ) + : moment().startOf( 'day' ).hour( hour ); + + const formatString = isUtc ? 'HH:mm' : 'LT'; // 24-hour format for UTC, 12-hour for local + + const startTime = time.format( formatString ); + const endTime = time.add( 59, 'minutes' ).format( formatString ); + + return `${ startTime } - ${ endTime }`; + }; + + const generateTimeSlots = (): { label: string; value: string }[] => { + const options = []; + for ( let hour = 0; hour < 24; hour++ ) { + const utcTime = moment.utc().startOf( 'day' ).hour( hour ); + const localTime = + timezone && gmtOffset + ? applySiteOffset( utcTime, { timezone, gmtOffset } ) + : utcTime.local(); + const localHour = localTime.hour(); + const timeRange = convertHourToRange( localHour ); + + options.push( { + label: timeRange, + value: hour.toString(), + localHour, // for sorting + } ); + } + + // Sort options by local hour before returning + options.sort( ( a, b ) => a.localHour - b.localHour ); + + // Remove the localHour from the final result as it's not needed anymore + return options.map( ( { label, value } ) => ( { label, value } ) ); + }; + + const timeSlotOptions = generateTimeSlots(); + const { isFetching: isScheduledTimeQueryFetching, data } = useScheduledTimeQuery( siteId ); + const { isPending: isScheduledTimeMutationLoading, mutate: scheduledTimeMutate } = + useScheduledTimeMutation( { + onSuccess: () => { + queryClient.invalidateQueries( { queryKey: [ 'jetpack-backup-scheduled-time', siteId ] } ); + dispatch( + successNotice( translate( 'Daily backup time successfully changed.' ), { + duration: 5000, + isPersistent: true, + } ) + ); + }, + onError: () => { + dispatch( + errorNotice( translate( 'Update daily backup time failed. Please, try again.' ), { + duration: 5000, + isPersistent: true, + } ) + ); + }, + } ); + + const isLoading = isScheduledTimeQueryFetching || isScheduledTimeMutationLoading; + + const updateScheduledTime = ( selectedTime: string ) => { + scheduledTimeMutate( { scheduledHour: Number( selectedTime ) } ); + }; + + const getScheduleInfoMessage = (): TranslateResult => { + const hour = data?.scheduledHour || 0; + const range = convertHourToRange( hour, true ); + + if ( ! data || ! data.scheduledBy ) { + return `${ translate( 'Default time' ) }. UTC: ${ range }`; + } + return `${ translate( 'Time set by %(scheduledBy)s', { + args: { scheduledBy: data.scheduledBy }, + } ) }. UTC: ${ range }`; + }; return (
@@ -33,7 +113,14 @@ const BackupScheduleSetting: FunctionComponent = () => { 'Pick a timeframe for your backup to run. Some site owners prefer scheduling backups at specific times for better control.' ) }
-