// Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; import type { BackupMediaDownloadStatusType, BackupsSubscriptionType, BackupStatusType, } from '../types/backups.node.js'; import type { LocalizerType } from '../types/I18N.std.js'; import { formatTimestamp } from '../util/formatTimestamp.dom.js'; import { SettingsRow } from './PreferencesUtil.dom.js'; import { missingCaseError } from '../util/missingCaseError.std.js'; import { BackupMediaDownloadProgressSettings } from './BackupMediaDownloadProgressSettings.dom.js'; import { BackupLevel } from '../services/backups/types.std.js'; import { SpinnerV2 } from './SpinnerV2.dom.js'; import { MINUTE } from '../util/durations/constants.std.js'; import { isOlderThan } from '../util/timestamp.std.js'; // We'll show a loading spinner if we are fetching fresh data and cached data is older // than this duration const SUBSCRIPTION_STATUS_STALE_TIME_FOR_UI = 5 * MINUTE; export function BackupsDetailsPage({ cloudBackupStatus, backupFreeMediaDays, backupSubscriptionStatus, backupTier, i18n, locale, cancelBackupMediaDownload, pauseBackupMediaDownload, resumeBackupMediaDownload, backupMediaDownloadStatus, }: { cloudBackupStatus?: BackupStatusType; backupFreeMediaDays: number; backupSubscriptionStatus: BackupsSubscriptionType; backupTier: BackupLevel | null; i18n: LocalizerType; locale: string; cancelBackupMediaDownload: () => void; pauseBackupMediaDownload: () => void; resumeBackupMediaDownload: () => void; backupMediaDownloadStatus?: BackupMediaDownloadStatusType; }): JSX.Element { const shouldShowMediaProgress = backupMediaDownloadStatus && backupMediaDownloadStatus.completedBytes < backupMediaDownloadStatus.totalBytes; return ( <>
{backupTier === BackupLevel.Paid ? renderPaidBackupDetailsSummary({ subscriptionStatus: backupSubscriptionStatus, i18n, locale, }) : null} {backupTier === BackupLevel.Free ? renderFreeBackupDetailsSummary({ backupFreeMediaDays, i18n, }) : null}
{cloudBackupStatus || shouldShowMediaProgress ? ( {cloudBackupStatus?.createdTimestamp ? (
{/* TODO (DESKTOP-8509) */} {i18n('icu:Preferences--backup-created-by-phone')} {formatTimestamp(cloudBackupStatus.createdTimestamp, { dateStyle: 'medium', timeStyle: 'short', })}
) : null} {shouldShowMediaProgress && backupMediaDownloadStatus ? (
) : null}
) : null} ); } function renderPaidBackupDetailsSummary({ subscriptionStatus, i18n, locale, }: { locale: string; subscriptionStatus?: BackupsSubscriptionType; i18n: LocalizerType; }): JSX.Element | null { return ( <>
{i18n('icu:Preferences--backup-media-plan__description')}
{subscriptionStatus ? renderSubscriptionDetails({ i18n, locale, subscriptionStatus }) : null}
{getSubscriptionStatusIcon(subscriptionStatus)}
{getSubscriptionNote(i18n, subscriptionStatus)}
); } function getSubscriptionNote( i18n: LocalizerType, subscriptionStatus: BackupsSubscriptionType | undefined ) { const status = subscriptionStatus?.status; switch (status) { case 'active': case 'pending-cancellation': return i18n('icu:Preferences--backup-media-plan__note'); case 'not-found': case 'expired': case undefined: return i18n('icu:Preferences--backup-plan__not-found__note'); default: throw missingCaseError(status); } } function getSubscriptionStatusIcon( subscriptionStatus: BackupsSubscriptionType | undefined ) { const status = subscriptionStatus?.status; switch (status) { case 'active': return (
); case 'pending-cancellation': case 'not-found': case 'expired': case undefined: return (
); default: throw missingCaseError(status); } } function renderFreeBackupDetailsSummary({ backupFreeMediaDays, i18n, }: { backupFreeMediaDays: number; i18n: LocalizerType; }): JSX.Element | null { return ( <>
{i18n('icu:Preferences--backup-messages-plan__description', { mediaDayCount: backupFreeMediaDays, })}
{i18n('icu:Preferences--backup-messages-plan__cost-description')}
{i18n('icu:Preferences--backup-messages-plan__note')}
); } export function renderSubscriptionDetails({ i18n, subscriptionStatus, locale, }: { i18n: LocalizerType; locale: string; subscriptionStatus: BackupsSubscriptionType; }): JSX.Element | null { const { status } = subscriptionStatus; if ( subscriptionStatus.isFetching && isOlderThan( subscriptionStatus.lastFetchedAtMs ?? 0, SUBSCRIPTION_STATUS_STALE_TIME_FOR_UI ) ) { return ( ); } switch (status) { case 'active': return ( <> {subscriptionStatus.cost ? (
{i18n('icu:Preferences--backup-subscription-monthly-cost', { cost: new Intl.NumberFormat(locale, { style: 'currency', currency: subscriptionStatus.cost.currencyCode, currencyDisplay: 'narrowSymbol', }).format(subscriptionStatus.cost.amount), })}
) : null} {subscriptionStatus.renewalTimestamp ? (
{i18n('icu:Preferences--backup-plan__renewal-date', { date: formatTimestamp(subscriptionStatus.renewalTimestamp, { dateStyle: 'medium', }), })}
) : null} ); case 'pending-cancellation': return ( <>
{i18n('icu:Preferences--backup-plan__canceled')}
{subscriptionStatus.expiryTimestamp ? (
{i18n('icu:Preferences--backup-plan__expiry-date', { date: formatTimestamp(subscriptionStatus.expiryTimestamp, { dateStyle: 'medium', }), })}
) : null} ); case 'not-found': case 'expired': return (
{i18n('icu:Preferences--backup-plan-not-found__description')}
); default: throw missingCaseError(status); } }