bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
125 lines (124 loc) • 8.88 kB
JavaScript
"use client";
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { CheckCircledIcon, ExclamationTriangleIcon, InfoCircledIcon, UpdateIcon, UploadIcon, } from "@radix-ui/react-icons";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useBitcoinAuth } from "../../hooks/useBitcoinAuth.js";
import { cn } from "../../lib/utils.js";
import { LoadingButton } from "../ui-components/LoadingButton.js";
import { Alert, AlertDescription } from "../ui/alert.js";
import { Badge } from "../ui/badge.js";
import { Button } from "../ui/button.js";
import { Card, CardContent } from "../ui/card.js";
/**
* CloudBackupManager - Manage encrypted backups stored with OAuth providers
*
* Features:
* - Check cloud backup status
* - Update outdated backups
* - Show last backup time
* - OAuth provider indication
* - Automatic status refresh
*/
export function CloudBackupManager({ checkBackupStatus: checkBackupStatusProp, updateCloudBackup: updateCloudBackupProp, onStatusChange, className = "", }) {
useBitcoinAuth();
const [status, setStatus] = useState({
hasCloudBackup: false,
});
const [isChecking, setIsChecking] = useState(true);
const [isUpdating, setIsUpdating] = useState(false);
const [error, setError] = useState(null);
// Default implementation if not provided
const checkBackupStatus = useMemo(() => {
return (checkBackupStatusProp ||
(async () => {
// Check if user has any OAuth providers linked
// For now, simulate based on localStorage
const linkedProvider = localStorage.getItem("oauthProvider");
if (!linkedProvider) {
return { hasCloudBackup: false };
}
// In a real implementation, this would check the backend
// For now, we'll simulate based on localStorage
const lastBackupTime = localStorage.getItem("cloudBackupLastUpdated");
const localBackupTime = localStorage.getItem("localBackupLastUpdated");
return {
hasCloudBackup: !!lastBackupTime,
isOutdated: localBackupTime
? Number.parseInt(localBackupTime) >
Number.parseInt(lastBackupTime || "0")
: false,
lastUpdated: lastBackupTime
? Number.parseInt(lastBackupTime)
: undefined,
provider: linkedProvider || "Unknown",
};
}));
}, [checkBackupStatusProp]);
// Check status function (declared first to be used in useEffect)
const checkStatus = useCallback(async () => {
setIsChecking(true);
setError(null);
try {
const newStatus = await checkBackupStatus();
setStatus(newStatus);
onStatusChange?.(newStatus);
}
catch (err) {
setError(err instanceof Error ? err.message : "Failed to check backup status");
}
finally {
setIsChecking(false);
}
}, [checkBackupStatus, onStatusChange]);
const updateCloudBackup = updateCloudBackupProp ||
(async () => {
// In a real implementation, this would update the backup on the server
// For now, we'll simulate with a delay
await new Promise((resolve) => setTimeout(resolve, 1500));
localStorage.setItem("cloudBackupLastUpdated", Date.now().toString());
});
useEffect(() => {
checkStatus();
}, [checkStatus]);
const handleUpdate = async () => {
setIsUpdating(true);
setError(null);
try {
await updateCloudBackup();
await checkStatus();
}
catch (err) {
setError(err instanceof Error ? err.message : "Failed to update cloud backup");
}
finally {
setIsUpdating(false);
}
};
const formatLastUpdated = (timestamp) => {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1)
return "Just now";
if (diffMins < 60)
return `${diffMins} minute${diffMins > 1 ? "s" : ""} ago`;
if (diffHours < 24)
return `${diffHours} hour${diffHours > 1 ? "s" : ""} ago`;
if (diffDays < 7)
return `${diffDays} day${diffDays > 1 ? "s" : ""} ago`;
return date.toLocaleDateString();
};
if (isChecking) {
return (_jsx(Card, { className: className, children: _jsxs(CardContent, { className: "flex items-center gap-3 p-6", children: [_jsx(UpdateIcon, { className: "animate-spin" }), _jsx("p", { className: "text-base", children: "Checking backup status..." })] }) }));
}
return (_jsx(Card, { className: className, children: _jsxs(CardContent, { className: "flex flex-col gap-4 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(UploadIcon, { className: "w-5 h-5" }), _jsx("h3", { className: "text-lg font-bold", children: "Cloud Backup" })] }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: checkStatus, disabled: isChecking, children: [_jsx(UpdateIcon, { className: "w-4 h-4 mr-1" }), "Refresh"] })] }), error && (_jsxs(Alert, { variant: "destructive", children: [_jsx(ExclamationTriangleIcon, { className: "h-4 w-4" }), _jsx(AlertDescription, { children: error })] })), !status.hasCloudBackup ? (_jsxs(Alert, { children: [_jsx(InfoCircledIcon, { className: "h-4 w-4" }), _jsx(AlertDescription, { children: "No cloud backup found. Link an OAuth provider (Google, GitHub, or X) in Settings to enable cloud backup restoration from other devices." })] })) : (_jsxs(_Fragment, { children: [_jsxs("dl", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("dt", { className: "text-sm text-muted-foreground min-w-[100px]", children: "Status" }), _jsx("dd", { children: _jsx(Badge, { variant: "secondary", className: cn(status.isOutdated
? "bg-orange-100 text-orange-700"
: "bg-green-100 text-green-700"), children: status.isOutdated ? "Outdated" : "Up to date" }) })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("dt", { className: "text-sm text-muted-foreground min-w-[100px]", children: "Provider" }), _jsx("dd", { className: "text-sm", children: status.provider || "Unknown" })] }), status.lastUpdated && (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("dt", { className: "text-sm text-muted-foreground min-w-[100px]", children: "Last Updated" }), _jsx("dd", { className: "text-sm text-muted-foreground", children: formatLastUpdated(status.lastUpdated) })] }))] }), status.isOutdated && (_jsxs(Alert, { className: "bg-purple-50 border-purple-200", children: [_jsx(ExclamationTriangleIcon, { className: "h-4 w-4 text-purple-600" }), _jsx(AlertDescription, { className: "text-purple-700", children: "Your local backup is newer than the cloud backup. Update it to ensure you can access your latest identity from other devices." })] })), _jsx("div", { className: "flex gap-3 justify-center", children: _jsxs(LoadingButton, { loading: isUpdating, disabled: !status.isOutdated, onClick: handleUpdate, className: "w-full", children: [_jsx(UploadIcon, { className: "w-4 h-4 mr-1" }), isUpdating
? "Updating..."
: status.isOutdated
? "Update Cloud Backup"
: "Already Up to Date"] }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(CheckCircledIcon, { className: "text-green-600" }), _jsx("p", { className: "text-sm font-medium", children: "Backup Features" })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "\u2022 Encrypted with your password" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "\u2022 Stored securely with OAuth provider" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "\u2022 Accessible from any device" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "\u2022 Automatic version tracking" })] })] })] }))] }) }));
}