mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-21 19:53:43 +08:00
feat: Add Google Drive sync functionality for app data and history, including new settings UI and a 'ghost' button variant.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { useSync } from '@/lib/hooks/useSync';
|
||||
import { Icons } from '@/components/ui/Icon';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
export function SyncSettings() {
|
||||
const {
|
||||
user,
|
||||
isInitialized,
|
||||
isLoading,
|
||||
error,
|
||||
lastSynced,
|
||||
autoSync,
|
||||
toggleAutoSync,
|
||||
signIn,
|
||||
signOut,
|
||||
syncNow,
|
||||
restoreNow
|
||||
} = useSync();
|
||||
|
||||
if (!isInitialized) {
|
||||
return (
|
||||
<div className="bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-sm)] p-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-2 border-[var(--accent-color)] border-t-transparent"></div>
|
||||
<span className="text-[var(--text-color-secondary)]">正在初始化同步服务...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-sm)] p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-semibold text-[var(--text-color)] flex items-center gap-2">
|
||||
<Icons.Cloud size={24} />
|
||||
<span>云端同步 (Google Drive)</span>
|
||||
</h2>
|
||||
{user && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-[var(--text-color-secondary)]">
|
||||
{user.email}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={signOut}
|
||||
className="text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
>
|
||||
退出
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-[var(--radius-xl)] text-sm text-red-600 dark:text-red-400">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!user ? (
|
||||
<div className="text-center py-6">
|
||||
<p className="text-[var(--text-color-secondary)] mb-4">
|
||||
登录 Google 账号以在设备间同步您的设置和历史记录。
|
||||
数据将存储在您 Google Drive 的专用应用文件夹中。
|
||||
</p>
|
||||
<Button onClick={signIn} variant="primary" className="w-full sm:w-auto">
|
||||
<Icons.Google className="mr-2" size={20} />
|
||||
连接 Google Drive
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between p-4 bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-xl)]">
|
||||
<div>
|
||||
<p className="font-medium text-[var(--text-color)]">自动同步</p>
|
||||
<p className="text-sm text-[var(--text-color-secondary)]">
|
||||
更改设置或观看视频时自动同步
|
||||
</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only peer"
|
||||
checked={autoSync}
|
||||
onChange={(e) => toggleAutoSync(e.target.checked)}
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-[var(--accent-color)]"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
onClick={syncNow}
|
||||
disabled={isLoading}
|
||||
className="flex-1"
|
||||
variant="secondary"
|
||||
>
|
||||
{isLoading ? '同步中...' : '立即同步 (上传)'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={restoreNow}
|
||||
disabled={isLoading}
|
||||
className="flex-1"
|
||||
variant="secondary"
|
||||
>
|
||||
{isLoading ? '同步中...' : '立即恢复 (下载)'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{lastSynced && (
|
||||
<p className="text-xs text-center text-[var(--text-color-secondary)]">
|
||||
上次同步: {lastSynced.toLocaleString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'primary' | 'secondary';
|
||||
variant?: 'primary' | 'secondary' | 'ghost';
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -35,10 +35,15 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(({
|
||||
rounded-[var(--radius-2xl)]
|
||||
text-[var(--text-color)]
|
||||
shadow-[0_2px_8px_color-mix(in_srgb,var(--shadow-color)_50%,transparent)]
|
||||
hover:brightness-110
|
||||
hover:shadow-[0_4px_12px_color-mix(in_srgb,var(--shadow-color)_70%,transparent)]
|
||||
active:scale-[0.98]
|
||||
`,
|
||||
ghost: `
|
||||
bg-transparent
|
||||
text-[var(--text-color)]
|
||||
hover:bg-[var(--glass-border)]
|
||||
active:scale-[0.98]
|
||||
`,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -127,4 +127,16 @@ export const UtilityIcons = {
|
||||
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" />
|
||||
</svg>
|
||||
),
|
||||
|
||||
Cloud: ({ className = "", size = 24 }: IconProps) => (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
|
||||
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z" />
|
||||
</svg>
|
||||
),
|
||||
|
||||
Google: ({ className = "", size = 24 }: IconProps) => (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" className={className}>
|
||||
<path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .533 5.333.533 12S5.867 24 12.48 24c3.44 0 6.013-1.133 8.053-3.24 2.08-2.08 2.72-5.013 2.72-7.427 0-.733-.053-1.44-.16-2.133h-10.613z" />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user