diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 06138f17aa57a..91dd7d170e112 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1725,12 +1725,15 @@ importers:
'@automattic/jetpack-connection':
specifier: workspace:*
version: link:../../js-packages/connection
+ '@tanstack/react-query':
+ specifier: 5.20.5
+ version: 5.20.5(react@18.3.1)
'@wordpress/api-fetch':
specifier: 7.11.0
version: 7.11.0
'@wordpress/components':
specifier: 28.11.0
- version: 28.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 28.11.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/data':
specifier: 10.11.0
version: 10.11.0(react@18.3.1)
@@ -1743,6 +1746,9 @@ importers:
'@wordpress/i18n':
specifier: 5.11.0
version: 5.11.0
+ moment:
+ specifier: 2.29.4
+ version: 2.29.4
prop-types:
specifier: ^15.8.1
version: 15.8.1
@@ -1773,10 +1779,13 @@ importers:
version: 10.4.0
'@testing-library/react':
specifier: 16.0.1
- version: 16.0.1(@testing-library/dom@10.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 16.0.1(@testing-library/dom@10.4.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@testing-library/user-event':
specifier: 14.5.2
version: 14.5.2(@testing-library/dom@10.4.0)
+ '@types/react':
+ specifier: 18.3.3
+ version: 18.3.3
'@wordpress/browserslist-config':
specifier: 6.11.0
version: 6.11.0
@@ -1795,6 +1804,9 @@ importers:
sass-loader:
specifier: 12.4.0
version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1))
+ typescript:
+ specifier: 5.0.4
+ version: 5.0.4
webpack:
specifier: 5.94.0
version: 5.94.0(webpack-cli@4.9.1)
@@ -7455,6 +7467,9 @@ packages:
'@types/react@18.3.12':
resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==}
+ '@types/react@18.3.3':
+ resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==}
+
'@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
@@ -15285,6 +15300,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.26.0
+ '@emotion/babel-plugin': 11.12.0
+ '@emotion/cache': 11.13.1
+ '@emotion/serialize': 1.3.2
+ '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
+ '@emotion/utils': 1.4.1
+ '@emotion/weak-memoize': 0.4.0
+ hoist-non-react-statics: 3.3.2
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.3
+ transitivePeerDependencies:
+ - supports-color
+
'@emotion/react@11.13.3(react@18.3.1)':
dependencies:
'@babel/runtime': 7.26.0
@@ -15324,6 +15355,21 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.26.0
+ '@emotion/babel-plugin': 11.12.0
+ '@emotion/is-prop-valid': 1.3.1
+ '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
+ '@emotion/serialize': 1.3.2
+ '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
+ '@emotion/utils': 1.4.1
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.3
+ transitivePeerDependencies:
+ - supports-color
+
'@emotion/styled@11.13.0(@emotion/react@11.13.3(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.26.0
@@ -17148,6 +17194,15 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.12
+ '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.26.0
+ '@testing-library/dom': 10.4.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.3
+
'@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.26.0
@@ -17378,6 +17433,11 @@ snapshots:
'@types/prop-types': 15.7.13
csstype: 3.1.3
+ '@types/react@18.3.3':
+ dependencies:
+ '@types/prop-types': 15.7.13
+ csstype: 3.1.3
+
'@types/resolve@1.20.2': {}
'@types/resolve@1.20.6': {}
@@ -18206,6 +18266,60 @@ snapshots:
- '@types/react'
- supports-color
+ '@wordpress/components@28.11.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@ariakit/react': 0.4.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@babel/runtime': 7.25.7
+ '@emotion/cache': 11.13.1
+ '@emotion/css': 11.13.4
+ '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
+ '@emotion/serialize': 1.3.2
+ '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)
+ '@emotion/utils': 1.4.1
+ '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@types/gradient-parser': 0.1.3
+ '@types/highlight-words-core': 1.2.1
+ '@use-gesture/react': 10.3.1(react@18.3.1)
+ '@wordpress/a11y': 4.11.0
+ '@wordpress/compose': 7.11.0(react@18.3.1)
+ '@wordpress/date': 5.11.0
+ '@wordpress/deprecated': 4.11.0
+ '@wordpress/dom': 4.11.0
+ '@wordpress/element': 6.11.0
+ '@wordpress/escape-html': 3.11.0
+ '@wordpress/hooks': 4.11.0
+ '@wordpress/html-entities': 4.11.0
+ '@wordpress/i18n': 5.11.0
+ '@wordpress/icons': 10.11.0(react@18.3.1)
+ '@wordpress/is-shallow-equal': 5.11.0
+ '@wordpress/keycodes': 4.11.0
+ '@wordpress/primitives': 4.11.0(react@18.3.1)
+ '@wordpress/private-apis': 1.11.0
+ '@wordpress/rich-text': 7.11.0(react@18.3.1)
+ '@wordpress/warning': 3.11.0
+ change-case: 4.1.2
+ clsx: 2.1.1
+ colord: 2.9.3
+ date-fns: 3.6.0
+ deepmerge: 4.3.1
+ fast-deep-equal: 3.1.3
+ framer-motion: 11.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ gradient-parser: 0.1.5
+ highlight-words-core: 1.2.3
+ is-plain-object: 5.0.0
+ memize: 2.1.0
+ path-to-regexp: 6.3.0
+ re-resizable: 6.10.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-colorful: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
+ remove-accents: 0.5.0
+ uuid: 9.0.1
+ transitivePeerDependencies:
+ - '@emotion/is-prop-valid'
+ - '@types/react'
+ - supports-color
+
'@wordpress/components@28.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@ariakit/react': 0.4.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
diff --git a/projects/packages/backup-helper-script-manager/changelog/add-jetpack-backup-schedule-time b/projects/packages/backup-helper-script-manager/changelog/add-jetpack-backup-schedule-time
new file mode 100644
index 0000000000000..798d9040ba827
--- /dev/null
+++ b/projects/packages/backup-helper-script-manager/changelog/add-jetpack-backup-schedule-time
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Backup: added next daily backup schedule time on admin page
diff --git a/projects/packages/backup-helper-script-manager/src/class-helper-script-manager-impl.php b/projects/packages/backup-helper-script-manager/src/class-helper-script-manager-impl.php
index adab13b8167bb..9cd06aa577197 100644
--- a/projects/packages/backup-helper-script-manager/src/class-helper-script-manager-impl.php
+++ b/projects/packages/backup-helper-script-manager/src/class-helper-script-manager-impl.php
@@ -9,7 +9,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
use Exception;
use WP_Error;
diff --git a/projects/packages/backup-helper-script-manager/src/class-helper-script-manager.php b/projects/packages/backup-helper-script-manager/src/class-helper-script-manager.php
index 5833abaffb662..650f2e3c368d1 100644
--- a/projects/packages/backup-helper-script-manager/src/class-helper-script-manager.php
+++ b/projects/packages/backup-helper-script-manager/src/class-helper-script-manager.php
@@ -9,7 +9,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
/**
* Manage installation, deletion and cleanup of Helper Scripts to assist with backing up Jetpack Sites.
diff --git a/projects/packages/backup-helper-script-manager/src/class-throw-on-errors.php b/projects/packages/backup-helper-script-manager/src/class-throw-on-errors.php
index 1f59694f4c487..2b14f255b0669 100644
--- a/projects/packages/backup-helper-script-manager/src/class-throw-on-errors.php
+++ b/projects/packages/backup-helper-script-manager/src/class-throw-on-errors.php
@@ -8,7 +8,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
use Exception;
use Throwable;
diff --git a/projects/packages/backup-helper-script-manager/tests/php/test-class-helper-script-manager-impl.php b/projects/packages/backup-helper-script-manager/tests/php/test-class-helper-script-manager-impl.php
index 7165a52ec8cb5..da7e02fe30473 100644
--- a/projects/packages/backup-helper-script-manager/tests/php/test-class-helper-script-manager-impl.php
+++ b/projects/packages/backup-helper-script-manager/tests/php/test-class-helper-script-manager-impl.php
@@ -4,7 +4,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
use Exception;
use WorDBless\BaseTestCase;
diff --git a/projects/packages/backup-helper-script-manager/tests/php/test-class-throw-on-errors.php b/projects/packages/backup-helper-script-manager/tests/php/test-class-throw-on-errors.php
index fa0482c4de1dc..080556272a798 100644
--- a/projects/packages/backup-helper-script-manager/tests/php/test-class-throw-on-errors.php
+++ b/projects/packages/backup-helper-script-manager/tests/php/test-class-throw-on-errors.php
@@ -8,7 +8,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
use Exception;
use PHPUnit\Framework\TestCase;
diff --git a/projects/packages/backup/actions.php b/projects/packages/backup/actions.php
index 9c5f041de4da0..7608526a2af53 100644
--- a/projects/packages/backup/actions.php
+++ b/projects/packages/backup/actions.php
@@ -23,10 +23,10 @@
}
// Clean up expired Helper Scripts from a scheduled event.
-$add_action( 'jetpack_backup_cleanup_helper_scripts', array( 'Automattic\\Jetpack\\Backup\\V0004\\Helper_Script_Manager', 'cleanup_expired_helper_scripts' ) );
+$add_action( 'jetpack_backup_cleanup_helper_scripts', array( 'Automattic\\Jetpack\\Backup\\V0005\\Helper_Script_Manager', 'cleanup_expired_helper_scripts' ) );
// Register REST routes.
-$add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Backup\\V0004\\REST_Controller', 'register_rest_routes' ) );
+$add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Backup\\V0005\\REST_Controller', 'register_rest_routes' ) );
// Set up package version hook.
$add_filter( 'jetpack_package_versions', 'Automattic\\Jetpack\\Backup\\Package_Version::send_package_version_to_tracker' );
diff --git a/projects/packages/backup/changelog/add-jetpack-backup-schedule-time b/projects/packages/backup/changelog/add-jetpack-backup-schedule-time
new file mode 100644
index 0000000000000..0fc3f5bafb036
--- /dev/null
+++ b/projects/packages/backup/changelog/add-jetpack-backup-schedule-time
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Backup: added next daily backup schedule time on admin page
diff --git a/projects/packages/backup/package.json b/projects/packages/backup/package.json
index 9ca9d21581b5c..c8d2abb35fac6 100644
--- a/projects/packages/backup/package.json
+++ b/projects/packages/backup/package.json
@@ -32,12 +32,14 @@
"@automattic/jetpack-api": "workspace:*",
"@automattic/jetpack-components": "workspace:*",
"@automattic/jetpack-connection": "workspace:*",
+ "@tanstack/react-query": "5.20.5",
"@wordpress/api-fetch": "7.11.0",
"@wordpress/components": "28.11.0",
"@wordpress/data": "10.11.0",
"@wordpress/date": "5.11.0",
"@wordpress/element": "6.11.0",
"@wordpress/i18n": "5.11.0",
+ "moment": "2.29.4",
"prop-types": "^15.8.1",
"react": "18.3.1",
"react-dom": "18.3.1"
@@ -51,12 +53,14 @@
"@testing-library/dom": "10.4.0",
"@testing-library/react": "16.0.1",
"@testing-library/user-event": "14.5.2",
+ "@types/react": "18.3.3",
"@wordpress/browserslist-config": "6.11.0",
"concurrently": "7.6.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"sass": "1.64.1",
"sass-loader": "12.4.0",
+ "typescript": "5.0.4",
"webpack": "5.94.0",
"webpack-cli": "4.9.1"
}
diff --git a/projects/packages/backup/src/class-initial-state.php b/projects/packages/backup/src/class-initial-state.php
index 164c291f6a1da..98a508472166f 100644
--- a/projects/packages/backup/src/class-initial-state.php
+++ b/projects/packages/backup/src/class-initial-state.php
@@ -9,7 +9,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
use Automattic\Jetpack\Connection\Plugin_Storage as Connection_Plugin_Storage;
use Automattic\Jetpack\Status;
diff --git a/projects/packages/backup/src/class-jetpack-backup-upgrades.php b/projects/packages/backup/src/class-jetpack-backup-upgrades.php
index eddf0ba54ec24..be18aede8284a 100644
--- a/projects/packages/backup/src/class-jetpack-backup-upgrades.php
+++ b/projects/packages/backup/src/class-jetpack-backup-upgrades.php
@@ -9,7 +9,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
use function get_option;
use function update_option;
diff --git a/projects/packages/backup/src/class-jetpack-backup.php b/projects/packages/backup/src/class-jetpack-backup.php
index b6e0fd3bafad7..3468d85ea7310 100644
--- a/projects/packages/backup/src/class-jetpack-backup.php
+++ b/projects/packages/backup/src/class-jetpack-backup.php
@@ -9,7 +9,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
if ( ! defined( 'ABSPATH' ) ) {
exit;
@@ -17,7 +17,7 @@
use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Assets;
-use Automattic\Jetpack\Backup\V0004\Initial_State as Backup_Initial_State;
+use Automattic\Jetpack\Backup\V0005\Initial_State as Backup_Initial_State;
use Automattic\Jetpack\Config;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
@@ -341,6 +341,17 @@ public static function register_rest_routes() {
)
);
+ // Get backup schedule time
+ register_rest_route(
+ 'jetpack/v4',
+ '/site/backup/schedule',
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::get_site_backup_schedule_time',
+ 'permission_callback' => __CLASS__ . '::backups_permissions_callback',
+ )
+ );
+
// Get site policies
register_rest_route(
'jetpack/v4',
@@ -785,6 +796,31 @@ public static function enqueue_backup() {
);
}
+ /**
+ * Get site backup schedule time
+ *
+ * @return string|WP_Error A JSON object with the backup schedule time if the request was successful, or a WP_Error otherwise.
+ */
+ public static function get_site_backup_schedule_time() {
+ $blog_id = Jetpack_Options::get_option( 'id' );
+
+ $response = Client::wpcom_json_api_request_as_user(
+ '/sites/' . $blog_id . '/rewind/scheduled',
+ 'v2',
+ array(),
+ null,
+ 'wpcom'
+ );
+
+ if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
+ return null;
+ }
+
+ return rest_ensure_response(
+ json_decode( $response['body'], true )
+ );
+ }
+
/**
* Removes plugin from the connection manager
* If it's the last plugin using the connection, the site will be disconnected.
diff --git a/projects/packages/backup/src/class-rest-controller.php b/projects/packages/backup/src/class-rest-controller.php
index f0b4f30d855e2..02494c943de7f 100644
--- a/projects/packages/backup/src/class-rest-controller.php
+++ b/projects/packages/backup/src/class-rest-controller.php
@@ -10,7 +10,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Rest_Authentication;
diff --git a/projects/packages/backup/src/js/components/Backups.js b/projects/packages/backup/src/js/components/Backups.js
index bfe641cdb4f18..37d091508c355 100644
--- a/projects/packages/backup/src/js/components/Backups.js
+++ b/projects/packages/backup/src/js/components/Backups.js
@@ -25,6 +25,7 @@ import PostsIcon from './icons/posts.svg';
import ThemesIcon from './icons/themes.svg';
import UploadsIcon from './icons/uploads.svg';
import WarningIcon from './icons/warning.svg';
+import NextScheduledBackup from './next-scheduled-backup';
/* eslint react/react-in-jsx-scope: 0 */
export const Backups = () => {
@@ -157,7 +158,8 @@ const CompleteBackup = ( { latestTime, stats } ) => {
/>
{ __( 'Latest Backup', 'jetpack-backup-pkg' ) }
- { formatDateString( latestTime ) }
+ { formatDateString( latestTime ) }
+
>
) }
diff --git a/projects/packages/backup/src/js/components/backups-style.scss b/projects/packages/backup/src/js/components/backups-style.scss
index a3cd4464e77c0..11c11e2fae561 100644
--- a/projects/packages/backup/src/js/components/backups-style.scss
+++ b/projects/packages/backup/src/js/components/backups-style.scss
@@ -122,6 +122,21 @@
}
}
+.backup__latest-time {
+ color: #1d2327;
+ font-size: var(--font-title-large);
+ font-weight: 700;
+ line-height: 40px;
+ margin-bottom: 4px;
+ margin-top: 0;
+ white-space: nowrap;
+}
+
+.backup__next-scheduled-time {
+ font-size: 13px;
+ margin-bottom: 24px;
+}
+
.backup__restore-point-link {
font-size: 14px;
line-height: 22px;
diff --git a/projects/packages/backup/src/js/components/next-scheduled-backup.tsx b/projects/packages/backup/src/js/components/next-scheduled-backup.tsx
new file mode 100644
index 0000000000000..8eb0ecb8c3f79
--- /dev/null
+++ b/projects/packages/backup/src/js/components/next-scheduled-backup.tsx
@@ -0,0 +1,66 @@
+import { getRedirectUrl, LoadingPlaceholder } from '@automattic/jetpack-components';
+import { ExternalLink } from '@wordpress/components';
+import { useSelect } from '@wordpress/data';
+import { useCallback } from '@wordpress/element';
+import { __, sprintf } from '@wordpress/i18n';
+import { FunctionComponent } from 'react';
+import { useNextBackupSchedule } from '../hooks/scheduled-backups/use-next-backup-schedule';
+import useAnalytics from '../hooks/useAnalytics';
+import { STORE_ID } from '../store';
+
+type Props = {
+ siteId: number;
+};
+
+interface StoreSelectors {
+ getCalypsoSlug: () => string;
+}
+
+const NextScheduledBackup: FunctionComponent< Props > = () => {
+ const { tracks } = useAnalytics();
+
+ const domain = useSelect( select => {
+ const selectors: StoreSelectors = select( STORE_ID );
+ return selectors.getCalypsoSlug();
+ }, [] );
+
+ const { hasLoaded, nextBackupDate, timeRange } = useNextBackupSchedule();
+
+ const onModifyClick = useCallback( () => {
+ tracks.recordEvent( 'jetpack_backup_schedule_modify_click' );
+ }, [ tracks ] );
+
+ if ( ! hasLoaded ) {
+ return (
+
+
+
+ );
+ }
+
+ if ( ! nextBackupDate || ! timeRange ) {
+ return null;
+ }
+
+ return (
+
+
+ { sprintf(
+ /* translators: %1$s is the formatted date (e.g., Oct 22); %2$s is a time range, such as 10:00-10:59 AM. */
+ __( 'Next full backup: %1$s, %2$s.', 'jetpack-backup-pkg' ),
+ nextBackupDate.format( 'MMM D' ),
+ timeRange
+ ) }
+ { ' ' }
+
+ { __( 'Modify', 'jetpack-backup-pkg' ) }
+
+
+ );
+};
+
+export default NextScheduledBackup;
diff --git a/projects/packages/backup/src/js/hooks/scheduled-backups/use-next-backup-schedule.ts b/projects/packages/backup/src/js/hooks/scheduled-backups/use-next-backup-schedule.ts
new file mode 100644
index 0000000000000..04784d45ec2b1
--- /dev/null
+++ b/projects/packages/backup/src/js/hooks/scheduled-backups/use-next-backup-schedule.ts
@@ -0,0 +1,79 @@
+import { getSettings } from '@wordpress/date';
+import moment from 'moment';
+import useScheduledTimeQuery from './use-scheduled-time-query';
+
+/**
+ * Converts a given hour into a time range string, either in UTC or local time.
+ * The resulting time range shows a start time and an end time 59 minutes later.
+ *
+ * - For local times, the start time is displayed in 12-hour format without AM/PM,
+ * while the end time includes AM/PM.
+ * - For UTC, the time range is displayed in 24-hour format for both start and end times.
+ *
+ * @param {number} hour - The hour of the day (0-23) for which to generate the time range.
+ * @param {boolean} isUtc - Whether to generate the time range in UTC (true) or local time (false).
+ *
+ * @return {string} - A formatted string representing the time range.
+ */
+const convertHourToRange = ( hour: number, isUtc: boolean = false ): string => {
+ const time = isUtc
+ ? moment.utc().startOf( 'day' ).hour( hour )
+ : moment().startOf( 'day' ).hour( hour );
+
+ const startTimeFormat = isUtc ? 'HH:mm' : 'h:mm';
+ const endTimeFormat = isUtc ? 'HH:mm' : 'h:mm A';
+
+ const startTime = time.format( startTimeFormat );
+ const endTime = time.add( 59, 'minutes' ).format( endTimeFormat );
+
+ return `${ startTime }-${ endTime }`;
+};
+
+export const useNextBackupSchedule = () => {
+ const { data, isSuccess } = useScheduledTimeQuery();
+
+ const getNextBackupDate = () => {
+ if ( ! data || data.scheduledHour === null ) {
+ return null;
+ }
+
+ const currentTime = moment();
+ const backupTimeUtc = moment.utc().startOf( 'day' ).hour( data.scheduledHour );
+
+ let nextBackupDate = backupTimeUtc;
+ const localDateSettings = getSettings();
+
+ if ( localDateSettings.timezone && localDateSettings.timezone.offset ) {
+ nextBackupDate = nextBackupDate.utcOffset( localDateSettings.timezone.offset );
+ } else {
+ nextBackupDate = backupTimeUtc.local();
+ }
+
+ const nextBackupDateEnd = nextBackupDate.clone().add( 59, 'minutes' ).add( 59, 'seconds' );
+
+ // Only move to the next day if the current time is after the backup window
+ if ( currentTime.isAfter( nextBackupDateEnd ) ) {
+ nextBackupDate.add( 1, 'day' ); // Move to next day
+ }
+
+ return nextBackupDate;
+ };
+
+ const nextBackupDate = getNextBackupDate();
+
+ if ( ! nextBackupDate ) {
+ return {
+ hasLoaded: isSuccess,
+ date: null,
+ timeRange: null,
+ };
+ }
+
+ const timeRange = convertHourToRange( nextBackupDate.hour() );
+
+ return {
+ hasLoaded: isSuccess,
+ nextBackupDate,
+ timeRange,
+ };
+};
diff --git a/projects/packages/backup/src/js/hooks/scheduled-backups/use-scheduled-time-query.ts b/projects/packages/backup/src/js/hooks/scheduled-backups/use-scheduled-time-query.ts
new file mode 100644
index 0000000000000..f565b5b9facac
--- /dev/null
+++ b/projects/packages/backup/src/js/hooks/scheduled-backups/use-scheduled-time-query.ts
@@ -0,0 +1,31 @@
+import { useQuery, UseQueryResult } from '@tanstack/react-query';
+import apiFetch from '@wordpress/api-fetch';
+
+export interface ScheduledTimeApi {
+ ok: boolean;
+ scheduled_hour: number;
+ scheduled_by: string | null;
+}
+
+export interface ScheduledTime {
+ scheduledHour: number;
+ scheduledBy: string | null;
+}
+
+const useScheduledTimeQuery = (): UseQueryResult< ScheduledTime, Error > => {
+ const queryKey = [ 'jetpack-backup-scheduled-time' ];
+
+ return useQuery< ScheduledTimeApi, Error, ScheduledTime >( {
+ queryKey,
+ queryFn: async () =>
+ apiFetch( { path: `/jetpack/v4/site/backup/schedule` } ) as Promise< ScheduledTimeApi >,
+ refetchIntervalInBackground: false,
+ refetchOnWindowFocus: false,
+ select: data => ( {
+ scheduledHour: data.scheduled_hour,
+ scheduledBy: data.scheduled_by,
+ } ),
+ } );
+};
+
+export default useScheduledTimeQuery;
diff --git a/projects/packages/backup/src/js/index.js b/projects/packages/backup/src/js/index.js
index a0b1e27644e48..6dd375feee33a 100644
--- a/projects/packages/backup/src/js/index.js
+++ b/projects/packages/backup/src/js/index.js
@@ -1,10 +1,19 @@
import { ThemeProvider } from '@automattic/jetpack-components';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createReduxStore, register } from '@wordpress/data';
import * as WPElement from '@wordpress/element';
import React from 'react';
import Admin from './components/Admin';
import { STORE_ID, storeConfig } from './store';
+const queryClient = new QueryClient( {
+ defaultOptions: {
+ queries: {
+ staleTime: Infinity,
+ },
+ },
+} );
+
const store = createReduxStore( STORE_ID, storeConfig );
register( store );
@@ -19,9 +28,11 @@ function render() {
}
const component = (
-
-
-
+
+
+
+
+
);
WPElement.createRoot( container ).render( component );
}
diff --git a/projects/packages/backup/tests/php/test-rest-controller.php b/projects/packages/backup/tests/php/test-rest-controller.php
index 91e60c6659218..bcc3fc7b61fe1 100644
--- a/projects/packages/backup/tests/php/test-rest-controller.php
+++ b/projects/packages/backup/tests/php/test-rest-controller.php
@@ -4,7 +4,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
use PHPUnit\Framework\TestCase;
@@ -65,7 +65,7 @@ public function set_up() {
wp_set_current_user( 0 );
// Register REST routes.
- add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Backup\\V0004\\REST_Controller', 'register_rest_routes' ) );
+ add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Backup\\V0005\\REST_Controller', 'register_rest_routes' ) );
do_action( 'rest_api_init' );
}
diff --git a/projects/packages/backup/tests/php/test-storage-addon-upsell.php b/projects/packages/backup/tests/php/test-storage-addon-upsell.php
index 890f6faf94245..e4d7fa1a2af1d 100644
--- a/projects/packages/backup/tests/php/test-storage-addon-upsell.php
+++ b/projects/packages/backup/tests/php/test-storage-addon-upsell.php
@@ -4,7 +4,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Backup\V0004;
+namespace Automattic\Jetpack\Backup\V0005;
use PHPUnit\Framework\TestCase;
diff --git a/projects/packages/backup/tsconfig.json b/projects/packages/backup/tsconfig.json
new file mode 100644
index 0000000000000..a2ecf0d6e8fd0
--- /dev/null
+++ b/projects/packages/backup/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "jetpack-js-tools/tsconfig.base.json",
+ "include": [ "./src/js" ]
+}
diff --git a/projects/packages/transport-helper/actions.php b/projects/packages/transport-helper/actions.php
index 9621437e18fa8..c3d7e01ba266e 100644
--- a/projects/packages/transport-helper/actions.php
+++ b/projects/packages/transport-helper/actions.php
@@ -23,10 +23,10 @@
}
// Clean up expired Jetpack Helper Scripts from a scheduled event.
-$add_action( 'jetpack_cleanup_helper_scripts', array( 'Automattic\\Jetpack\\Backup\\V0004\\Helper_Script_Manager', 'cleanup_expired_helper_scripts' ) );
+$add_action( 'jetpack_cleanup_helper_scripts', array( 'Automattic\\Jetpack\\Backup\\V0005\\Helper_Script_Manager', 'cleanup_expired_helper_scripts' ) );
// Register REST routes.
-$add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Transport_Helper\\V0004\\REST_Controller', 'register_rest_routes' ) );
+$add_action( 'rest_api_init', array( 'Automattic\Jetpack\Transport_Helper\V0005\\REST_Controller', 'register_rest_routes' ) );
// Set up package version hook.
$add_filter( 'jetpack_package_versions', 'Automattic\\Jetpack\\Transport_Helper\\Package_Version::send_package_version_to_tracker' );
diff --git a/projects/packages/transport-helper/changelog/add-jetpack-backup-schedule-time b/projects/packages/transport-helper/changelog/add-jetpack-backup-schedule-time
new file mode 100644
index 0000000000000..798d9040ba827
--- /dev/null
+++ b/projects/packages/transport-helper/changelog/add-jetpack-backup-schedule-time
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Backup: added next daily backup schedule time on admin page
diff --git a/projects/packages/transport-helper/src/class-rest-controller.php b/projects/packages/transport-helper/src/class-rest-controller.php
index 9bdf49ebb4775..f15962eb80163 100644
--- a/projects/packages/transport-helper/src/class-rest-controller.php
+++ b/projects/packages/transport-helper/src/class-rest-controller.php
@@ -10,9 +10,9 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Transport_Helper\V0004;
+namespace Automattic\Jetpack\Transport_Helper\V0005;
-use Automattic\Jetpack\Backup\V0004\Helper_Script_Manager;
+use Automattic\Jetpack\Backup\V0005\Helper_Script_Manager;
use Automattic\Jetpack\Connection\Rest_Authentication;
use WP_Error;
use WP_REST_Request;
diff --git a/projects/packages/transport-helper/tests/php/test-rest-controller.php b/projects/packages/transport-helper/tests/php/test-rest-controller.php
index 0653706dd0e18..f496db19f8614 100644
--- a/projects/packages/transport-helper/tests/php/test-rest-controller.php
+++ b/projects/packages/transport-helper/tests/php/test-rest-controller.php
@@ -4,7 +4,7 @@
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
// are installed, or in some other cases).
-namespace Automattic\Jetpack\Transport_Helper\V0004;
+namespace Automattic\Jetpack\Transport_Helper\V0005;
use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
use PHPUnit\Framework\TestCase;
@@ -63,7 +63,7 @@ public function set_up() {
wp_set_current_user( 0 );
// Register REST routes.
- add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Transport_Helper\\V0004\\REST_Controller', 'register_rest_routes' ) );
+ add_action( 'rest_api_init', array( 'Automattic\Jetpack\Transport_Helper\V0005\\REST_Controller', 'register_rest_routes' ) );
do_action( 'rest_api_init' );
}
diff --git a/projects/plugins/backup/changelog/add-jetpack-backup-schedule-time b/projects/plugins/backup/changelog/add-jetpack-backup-schedule-time
new file mode 100644
index 0000000000000..0fc3f5bafb036
--- /dev/null
+++ b/projects/plugins/backup/changelog/add-jetpack-backup-schedule-time
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Backup: added next daily backup schedule time on admin page
diff --git a/projects/plugins/backup/jetpack-backup.php b/projects/plugins/backup/jetpack-backup.php
index b0932ee566591..1de96ecfafacc 100644
--- a/projects/plugins/backup/jetpack-backup.php
+++ b/projects/plugins/backup/jetpack-backup.php
@@ -29,7 +29,7 @@
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-use Automattic\Jetpack\Backup\V0004\Jetpack_Backup as My_Jetpack_Backup;
+use Automattic\Jetpack\Backup\V0005\Jetpack_Backup as My_Jetpack_Backup;
use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
if ( ! defined( 'ABSPATH' ) ) {
@@ -179,7 +179,7 @@ function ( $actions ) {
}
);
-register_deactivation_hook( __FILE__, array( 'Automattic\\Jetpack\\Backup\\V0004\\Jetpack_Backup', 'plugin_deactivation' ) );
+register_deactivation_hook( __FILE__, array( 'Automattic\\Jetpack\\Backup\\V0005\\Jetpack_Backup', 'plugin_deactivation' ) );
// Main plugin class.
My_Jetpack_Backup::initialize();
diff --git a/projects/plugins/jetpack/changelog/add-jetpack-backup-schedule-time b/projects/plugins/jetpack/changelog/add-jetpack-backup-schedule-time
new file mode 100644
index 0000000000000..33fd28e9c13cc
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/add-jetpack-backup-schedule-time
@@ -0,0 +1,4 @@
+Significance: patch
+Type: other
+
+Backup: added next daily backup schedule time on admin page
diff --git a/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-delete-backup-helper-script-endpoint.php b/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-delete-backup-helper-script-endpoint.php
index 420f0b4e4bced..738907df9bc76 100644
--- a/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-delete-backup-helper-script-endpoint.php
+++ b/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-delete-backup-helper-script-endpoint.php
@@ -6,7 +6,7 @@
* @package automattic/jetpack
*/
-use Automattic\Jetpack\Backup\V0004\Helper_Script_Manager;
+use Automattic\Jetpack\Backup\V0005\Helper_Script_Manager;
/**
* API endpoint /sites/%s/delete-backup-helper-script
diff --git a/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-install-backup-helper-script-endpoint.php b/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-install-backup-helper-script-endpoint.php
index 2ec111fb63621..f59748b19ac42 100644
--- a/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-install-backup-helper-script-endpoint.php
+++ b/projects/plugins/jetpack/json-endpoints/jetpack/class-jetpack-json-api-install-backup-helper-script-endpoint.php
@@ -6,7 +6,7 @@
* @package automattic/jetpack
*/
-use Automattic\Jetpack\Backup\V0004\Helper_Script_Manager;
+use Automattic\Jetpack\Backup\V0005\Helper_Script_Manager;
/**
* API endpoint /sites/%s/install-backup-helper-script
diff --git a/projects/plugins/jetpack/tests/php/general/test_jetpack-admin-menu.php b/projects/plugins/jetpack/tests/php/general/test_jetpack-admin-menu.php
index c4d0a6983443a..b350e54652e0d 100644
--- a/projects/plugins/jetpack/tests/php/general/test_jetpack-admin-menu.php
+++ b/projects/plugins/jetpack/tests/php/general/test_jetpack-admin-menu.php
@@ -3,7 +3,7 @@
* @package automattic/jetpack
*/
-use Automattic\Jetpack\Backup\V0004\Jetpack_Backup;
+use Automattic\Jetpack\Backup\V0005\Jetpack_Backup;
use Automattic\Jetpack\Stats_Admin\Dashboard;
use Automattic\Jetpack\VideoPress\Admin_UI;
use Automattic\Jetpack\WordAds\Dashboard as WordAdsDashboard;
diff --git a/projects/plugins/jetpack/uninstall.php b/projects/plugins/jetpack/uninstall.php
index 848bf6266f44d..d5b09a4090b17 100644
--- a/projects/plugins/jetpack/uninstall.php
+++ b/projects/plugins/jetpack/uninstall.php
@@ -5,7 +5,7 @@
* @package automattic/jetpack
*/
-use Automattic\Jetpack\Backup\V0004\Helper_Script_Manager;
+use Automattic\Jetpack\Backup\V0005\Helper_Script_Manager;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Sync\Sender;
diff --git a/projects/plugins/migration/changelog/add-jetpack-backup-schedule-time b/projects/plugins/migration/changelog/add-jetpack-backup-schedule-time
new file mode 100644
index 0000000000000..798d9040ba827
--- /dev/null
+++ b/projects/plugins/migration/changelog/add-jetpack-backup-schedule-time
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Backup: added next daily backup schedule time on admin page
diff --git a/projects/plugins/migration/src/class-wpcom-migration.php b/projects/plugins/migration/src/class-wpcom-migration.php
index 035aec5f06e89..956b41282ddcf 100644
--- a/projects/plugins/migration/src/class-wpcom-migration.php
+++ b/projects/plugins/migration/src/class-wpcom-migration.php
@@ -12,7 +12,7 @@
}
use Automattic\Jetpack\Assets;
-use Automattic\Jetpack\Backup\V0004\Jetpack_Backup;
+use Automattic\Jetpack\Backup\V0005\Jetpack_Backup;
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;