feat(frontend): separate profile and chart settings

This commit is contained in:
Jesse_Chen
2026-08-30 14:16:30 +08:00
parent 52477306cc
commit f4fb39e0f3
5 changed files with 40 additions and 46 deletions
+4
View File
@@ -1596,6 +1596,9 @@ button:disabled { cursor: default; opacity: .45; }
.account-modal-overlay { position: fixed; z-index: 40; inset: 0; display: grid; place-items: center; padding: var(--space-4); background: var(--color-scrim); animation: account-overlay-enter 180ms ease-out both; }
.account-modal { width: min(100%, 560px); max-height: min(84dvh, 760px); overflow-y: auto; padding: var(--space-8); border: 1px solid var(--color-border); border-radius: var(--radius-xl); background: var(--color-canvas); box-shadow: var(--shadow-elevated); animation: account-dialog-enter 180ms var(--ease-out) both; }
.profile-modal { width: min(100%, 680px); scroll-padding-block-start: calc(var(--space-8) + 72px); }
.chart-library-modal { width: min(100%, 760px); scroll-padding-block-start: calc(var(--space-8) + 72px); }
.chart-library-modal .account-modal-header { position: sticky; z-index: 1; top: 0; margin: calc(var(--space-8) * -1) calc(var(--space-8) * -1) var(--space-3); padding: var(--space-8) var(--space-8) var(--space-5); border-bottom: 1px solid var(--color-border); background: var(--color-canvas); }
.settings-dialog-copy { margin: 0 0 var(--space-5); color: var(--color-ink-secondary); font-size: var(--type-body-sm); line-height: 1.6; }
.profile-modal .account-modal-header { position: sticky; z-index: 1; top: 0; margin: calc(var(--space-8) * -1) calc(var(--space-8) * -1) var(--space-5); padding: var(--space-8) var(--space-8) var(--space-5); border-bottom: 1px solid var(--color-border); background: var(--color-canvas); }
.profile-modal .birth-section { padding-top: var(--space-5); }
.avatar-section { padding-top: 0; }
@@ -1766,6 +1769,7 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
.select-list { max-height: min(288px, 48dvh) !important; }
.account-modal { max-height: calc(100dvh - var(--space-8)); padding: var(--space-6); }
.profile-modal .account-modal-header { margin: calc(var(--space-6) * -1) calc(var(--space-6) * -1) var(--space-4); padding: var(--space-6) var(--space-6) var(--space-4); }
.chart-library-modal .account-modal-header { margin: calc(var(--space-6) * -1) calc(var(--space-6) * -1) var(--space-4); padding: var(--space-6) var(--space-6) var(--space-4); }
.avatar-editor { grid-template-columns: 72px minmax(0, 1fr); gap: var(--space-4); }
.avatar-editor > .user-avatar { width: 72px !important; height: 72px !important; }
.avatar-palette-list { grid-template-columns: repeat(2, minmax(64px, 1fr)); }
+22 -25
View File
@@ -272,7 +272,7 @@ type Account = {
profile: unknown;
};
type OnboardingStep = "name" | "birth" | "place" | "rectification";
type AccountDialog = "profile" | "logout";
type AccountDialog = "profile" | "chart-library" | "logout";
type DailyStarlanguageCard = { trend: string; action: string; caution: string };
type DailyStarlanguageApiResponse = {
status?: "ok" | "unavailable" | "unauthenticated";
@@ -356,11 +356,13 @@ const themes = defaultGuidedJyotishTopics;
const accountDialogTitles = {
profile: "个人资料",
"chart-library": "星盘资料",
logout: "退出登录?",
} as const satisfies Record<AccountDialog, string>;
const accountDialogClasses = {
profile: "profile-modal",
"chart-library": "chart-library-modal",
logout: "logout-modal",
} as const satisfies Record<AccountDialog, string>;
@@ -1197,7 +1199,6 @@ export default function Home() {
const modelSelectionVersions = useRef(new Map<string, number>());
const activeSessionIdRef = useRef("");
const chartLibraryLoadedAccount = useRef("");
const openChartLibraryOnDialog = useRef(false);
const accountOverlayRef = useRef<AccountOverlayModel | null>(null);
const accountOverlayEpochRef = useRef(0);
const chatActionsRef = useRef<ChatTranscriptActions>({
@@ -3797,14 +3798,9 @@ export default function Home() {
dialogClass: activeAccountDialog ? accountDialogClasses[activeAccountDialog] : "",
signingOut,
close: closeAccountDialog,
takeOpenLibraryRequest() {
if (!openChartLibraryOnDialog.current) return false;
openChartLibraryOnDialog.current = false;
return true;
},
overlayRef: accountDialog,
closeButtonRef: closeButton,
renderProfile(library) {
renderProfile() {
return (
<>
{accountError && <p className="form-error" role="alert">{accountError}</p>}
@@ -3855,9 +3851,22 @@ export default function Home() {
<strong>{profileDraft.name.trim() || "未命名"}</strong>
<small></small>
</div>
<button className="button-secondary" type="button" onClick={library.toggle}></button>
<button className="button-secondary" type="button" onClick={() => openAccountDialog("chart-library")}></button>
</div>
{library.open && (
</section>
{profileNotice && <p className="form-success" role="status">{profileNotice}</p>}
<form className="profile-form" onSubmit={saveProfile}>
<ProfileFields value={profileDraft} onChange={setProfileDraft} nameInputId="profile-name" />
<button className="button-primary save-profile" type="submit" disabled={profileSaving || !account}>{profileSaving ? "保存中" : "保存出生资料"}</button>
</form>
</>
);
},
renderChartLibrary() {
return (
<>
<p className="settings-dialog-copy"></p>
<div className="chart-library-panel" aria-label="星盘库">
<div className="chart-library-group">
<b></b>
@@ -3929,13 +3938,6 @@ export default function Home() {
<button className="button-primary save-profile" type="submit" disabled={!account}></button>
</form>
</div>
)}
</section>
{profileNotice && <p className="form-success" role="status">{profileNotice}</p>}
<form className="profile-form" onSubmit={saveProfile}>
<ProfileFields value={profileDraft} onChange={setProfileDraft} nameInputId="profile-name" />
<button className="button-primary save-profile" type="submit" disabled={profileSaving || !account}>{profileSaving ? "保存中" : "保存出生资料"}</button>
</form>
</>
);
},
@@ -3995,15 +3997,10 @@ export default function Home() {
onNewChat={() => void startNewChat()}
onOpenReports={() => router.push("/reports")}
onSelectSession={selectSession}
onSelectChart={() => {
openChartLibraryOnDialog.current = true;
openAccountDialog("profile");
}}
onAddChart={() => {
openChartLibraryOnDialog.current = true;
openAccountDialog("profile");
}}
onSelectChart={() => openAccountDialog("chart-library")}
onAddChart={() => openAccountDialog("chart-library")}
onOpenProfile={() => openAccountDialog("profile")}
onOpenChartLibrary={() => openAccountDialog("chart-library")}
onOpenRedeem={() => router.push(membershipHref("account-menu"))}
onOpenLogout={() => openAccountDialog("logout")}
/>
@@ -1,22 +1,17 @@
"use client";
import { X } from "lucide-react";
import { memo, useEffect, useState, type MutableRefObject, type ReactNode } from "react";
export type AccountOverlayLibraryControls = Readonly<{
open: boolean;
toggle: () => void;
}>;
import { memo, type MutableRefObject, type ReactNode } from "react";
export type AccountOverlayModel = Readonly<{
title: string;
dialogClass: string;
signingOut: boolean;
close: () => void;
takeOpenLibraryRequest: () => boolean;
overlayRef: MutableRefObject<HTMLElement | null>;
closeButtonRef: MutableRefObject<HTMLButtonElement | null>;
renderProfile: (library: AccountOverlayLibraryControls) => ReactNode;
renderProfile: () => ReactNode;
renderChartLibrary: () => ReactNode;
renderLogout: () => ReactNode;
}>;
@@ -27,17 +22,10 @@ export const AccountDialogOverlay = memo(function AccountDialogOverlay({
modelRef,
}: Readonly<{
open: boolean;
dialog: "profile" | "logout" | null;
dialog: "profile" | "chart-library" | "logout" | null;
openEpoch: number;
modelRef: MutableRefObject<AccountOverlayModel | null>;
}>) {
const [chartLibraryOpen, setChartLibraryOpen] = useState(false);
useEffect(() => {
if (!open) return;
if (modelRef.current?.takeOpenLibraryRequest()) setChartLibraryOpen(true);
}, [dialog, modelRef, open]);
if (!open || dialog === null) return null;
const model = modelRef.current;
if (!model) return null;
@@ -66,10 +54,8 @@ export const AccountDialogOverlay = memo(function AccountDialogOverlay({
<X aria-hidden="true" />
</button>
</header>
{dialog === "profile" && model.renderProfile({
open: chartLibraryOpen,
toggle: () => setChartLibraryOpen((current) => !current),
})}
{dialog === "profile" && model.renderProfile()}
{dialog === "chart-library" && model.renderChartLibrary()}
{dialog === "logout" && model.renderLogout()}
</section>
</div>
+5
View File
@@ -71,6 +71,7 @@ export type AppSidebarProps = {
onSelectChart: (chartId: string) => void;
onAddChart: () => void;
onOpenProfile: () => void;
onOpenChartLibrary: () => void;
onOpenRedeem: () => void;
onOpenLogout: () => void;
};
@@ -92,6 +93,7 @@ export function AppSidebar({
onSelectChart,
onAddChart,
onOpenProfile,
onOpenChartLibrary,
onOpenRedeem,
onOpenLogout,
}: AppSidebarProps) {
@@ -303,6 +305,9 @@ export function AppSidebar({
<Menu.Item className="account-menu-item" onClick={onOpenProfile}>
<UserRound aria-hidden="true" /><span></span><ChevronRight aria-hidden="true" />
</Menu.Item>
<Menu.Item className="account-menu-item" onClick={onOpenChartLibrary}>
<Users aria-hidden="true" /><span></span><ChevronRight aria-hidden="true" />
</Menu.Item>
<Menu.Item className="account-menu-item" onClick={onOpenRedeem}>
<Gift aria-hidden="true" /><span></span><small>{account.credits} </small>
</Menu.Item>
@@ -19,13 +19,15 @@ test("a closed account overlay does not render profile or logout content", () =>
dialogClass: "profile-modal",
signingOut: false,
close() {},
takeOpenLibraryRequest() { return false; },
overlayRef,
closeButtonRef,
renderProfile() {
profileRenders += 1;
return null;
},
renderChartLibrary() {
return null;
},
renderLogout() {
logoutRenders += 1;
return null;