refactor(sidebar): split state derivation

This commit is contained in:
2026-07-10 10:38:21 +08:00
parent 270e84385b
commit a1a93a51fb
13 changed files with 360 additions and 331 deletions
+25 -74
View File
@@ -1,6 +1,5 @@
"use client";
import { useEffect, useState } from "react";
import { Download, LogOut } from "lucide-react";
import { signOut } from "next-auth/react";
@@ -10,61 +9,23 @@ import { ROUTES } from "@/router/routes";
import { useAppNavigator } from "@/router/use-app-navigator";
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
import { useUserDispatch, useUserState } from "@/stores/user/user-context";
import { pwaUtil } from "@/utils";
import {
SidebarWalletCard,
UserHeader,
type SidebarUserState,
} from "./components";
import { SidebarWalletCard, UserHeader } from "./components";
import { getSidebarViewModel } from "./sidebar-view-model";
import { usePwaInstallEntry } from "./use-pwa-install-entry";
import { useSidebarUserBootstrap } from "./use-sidebar-user-bootstrap";
import styles from "./components/sidebar-screen.module.css";
const FALLBACK_USERNAME = "User name";
export function SidebarScreen() {
const navigator = useAppNavigator();
const user = useUserState();
const userDispatch = useUserDispatch();
const auth = useAuthState();
const authDispatch = useAuthDispatch();
const [canShowInstallApp, setCanShowInstallApp] = useState(false);
const pwaInstallEntry = usePwaInstallEntry();
useEffect(() => {
if (!auth.hasInitialized || auth.isLoading) return;
if (auth.loginStatus === "notLoggedIn") return;
userDispatch({ type: "UserFetch" });
}, [
auth.hasInitialized,
auth.isLoading,
auth.loginStatus,
userDispatch,
]);
useEffect(() => {
const updateInstallEntryVisibility = () => {
setCanShowInstallApp(pwaUtil.canShowInstallEntry());
};
pwaUtil.prepareInstallPrompt();
updateInstallEntryVisibility();
const unsubscribe = pwaUtil.subscribe(updateInstallEntryVisibility);
return () => {
unsubscribe();
};
}, []);
const handleInstallAppClick = async () => {
pwaUtil.prepareInstallPrompt();
const result = await pwaUtil.install();
setCanShowInstallApp(
result !== "accepted" &&
!pwaUtil.isInstalled() &&
pwaUtil.canShowInstallEntry(),
);
};
useSidebarUserBootstrap({ auth, userDispatch });
const handleLogoutClick = () => {
userDispatch({ type: "UserClearLocal" });
@@ -73,19 +34,11 @@ export function SidebarScreen() {
navigator.replace(ROUTES.splash, { scroll: false });
};
// 状态派生:未登录 / 已登录未 VIP / 已登录 VIP
const sidebarState: SidebarUserState =
auth.loginStatus === "notLoggedIn"
? "guest"
: user.currentUser?.isVip === true
? "vip"
: "member";
const view = getSidebarViewModel({
loginStatus: auth.loginStatus,
user,
});
const isInitializingUser =
sidebarState !== "guest" && user.currentUser == null;
const name = user.currentUser?.username ?? FALLBACK_USERNAME;
const avatarUrl = user.avatarUrl;
return (
<MobileShell background="#fcf3f4">
<div className={styles.shell}>
@@ -98,42 +51,40 @@ export function SidebarScreen() {
<section className={`${styles.userSlot} ${styles.revealOne}`}>
<UserHeader
state={sidebarState}
name={name}
avatarUrl={avatarUrl}
isLoading={isInitializingUser}
state={view.state}
name={view.name}
avatarUrl={view.avatarUrl}
isLoading={view.isInitializingUser}
onLoginClick={() => navigator.openAuth(ROUTES.sidebar)}
/>
</section>
<section className={`${styles.cardSlot} ${styles.revealTwo}`}>
<SidebarWalletCard
creditBalance={user.creditBalance}
dailyFreeChatLimit={user.currentUser?.dailyFreeChatLimit}
dailyFreeChatRemaining={user.currentUser?.dailyFreeChatRemaining}
dailyFreePrivateLimit={user.currentUser?.dailyFreePrivateLimit}
dailyFreePrivateRemaining={
user.currentUser?.dailyFreePrivateRemaining
}
creditBalance={view.wallet.creditBalance}
dailyFreeChatLimit={view.wallet.dailyFreeChatLimit}
dailyFreeChatRemaining={view.wallet.dailyFreeChatRemaining}
dailyFreePrivateLimit={view.wallet.dailyFreePrivateLimit}
dailyFreePrivateRemaining={view.wallet.dailyFreePrivateRemaining}
onActivateVip={
sidebarState === "vip"
? undefined
: () => navigator.openSubscription({ type: "vip" })
view.canActivateVip
? () => navigator.openSubscription({ type: "vip" })
: undefined
}
onTopUp={() => navigator.openSubscription({ type: "topup" })}
onRulesClick={() => navigator.push(ROUTES.coinsRules)}
/>
</section>
{sidebarState !== "guest" ? (
{view.canShowSettings ? (
<section className={`${styles.settingsSlot} ${styles.revealThree}`}>
<p className={styles.settingsLabel}>Other</p>
<div className={styles.settingsActions}>
{canShowInstallApp ? (
{pwaInstallEntry.canInstall ? (
<button
type="button"
className={`${styles.logoutCard} ${styles.installCard}`}
onClick={() => void handleInstallAppClick()}
onClick={() => void pwaInstallEntry.installApp()}
>
<span
className={`${styles.logoutIcon} ${styles.installIcon}`}