feat(settings): add security center, 2FA/TOTP, avatar upload, system settings

- Add TOTP implementation and two-factor data-access for 2FA enrollment

- Add security center card with password policy and session management

- Add avatar upload action and component

- Add system settings actions and data-access (actions-system-settings, data-access-system-settings)

- Add notification preferences and service actions

- Add security-utils and student-overview-data with tests

- Update existing settings views, data-access, and types for new features
This commit is contained in:
SpecialX
2026-06-23 17:37:06 +08:00
parent 242a770cc9
commit 1fcef5c3aa
22 changed files with 3091 additions and 52 deletions

View File

@@ -1,8 +1,10 @@
"use client"
import { Monitor, Moon, Sun } from "lucide-react"
import { Monitor, Moon, Sun, Globe } from "lucide-react"
import { useTheme } from "next-themes"
import { useTranslations } from "next-intl"
import { LocaleSwitcher } from "@/shared/components/locale-switcher"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Label } from "@/shared/components/ui/label"
import {
@@ -15,8 +17,15 @@ import {
type ThemeChoice = "system" | "light" | "dark"
export function ThemePreferencesCard() {
/**
* 外观偏好卡片
*
* 包含主题切换system/light/dark和语言切换。
* 语言切换复用 shared/components/locale-switcher 组件。
*/
export function ThemePreferencesCard(): React.ReactElement {
const t = useTranslations("settings.appearance.theme")
const tLang = useTranslations("settings.appearance.language")
const { theme, setTheme } = useTheme()
const value: ThemeChoice = theme === "light" || theme === "dark" || theme === "system" ? theme : "system"
@@ -27,7 +36,7 @@ export function ThemePreferencesCard() {
<CardTitle>{t("title")}</CardTitle>
<CardDescription>{t("description")}</CardDescription>
</CardHeader>
<CardContent className="grid gap-3 sm:max-w-md">
<CardContent className="grid gap-4 sm:max-w-md">
<div className="space-y-2">
<Label htmlFor="theme">{t("label")}</Label>
<Select value={value} onValueChange={(v) => setTheme(v)}>
@@ -56,6 +65,17 @@ export function ThemePreferencesCard() {
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="language" className="flex items-center gap-1.5">
<Globe className="h-3.5 w-3.5 text-muted-foreground" />
{tLang("label")}
</Label>
<div id="language">
<LocaleSwitcher />
</div>
<p className="text-xs text-muted-foreground">{tLang("description")}</p>
</div>
</CardContent>
</Card>
)