feat(auth): redesign auth UI to match Dart original
- Full-bleed bg_login.png background outside 500px MobileShell - Facebook landing: logo + Facebook pill button + "Other Sign In Options" link - Replace centered Dialog with true bottom sheet (drag handle, top radius 28px) - Email panel: internal login/register toggle, no separate route - Pink gradient primary button (#f96ADE → #f657A0) - Pill input fields (46px, white, radius 24, pink 10% shadow) - Pink circular checkbox + bold Privacy/Terms rich text (visual only, non-blocking) - Floating 40x40 white back button (top:20/left:20) - Wire AuthPanelModeChanged dispatch for panel switching - New design tokens (--color-auth-*, --auth-*, --radius-bottom-sheet) - New generic BottomSheet component - Copy ic-logo-login.png (+@2x, +@3x) from Dart assets 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,47 +1,117 @@
|
||||
"use client";
|
||||
/**
|
||||
* Email 面板
|
||||
* Email 面板(login/register 内部切换)
|
||||
*
|
||||
* 原始 Dart: lib/ui/auth/widgets/auth_email_panel.dart
|
||||
*
|
||||
* 行为:
|
||||
* - 内部 `authMode`(login/register)切换显示 Login / Register 表单
|
||||
* - 提交派发 `AuthEmailLoginSubmitted` 或 `AuthEmailRegisterSubmitted`
|
||||
* 视觉规格(与 Dart 对齐):
|
||||
* - login 模式:顶部 24px spacer + logo(120h) + 24px gap
|
||||
* - register 模式:48px spacer(两个 24px,无 logo)
|
||||
* - 表单(EmailLoginForm / EmailRegisterForm)
|
||||
* - 12px gap + 切换链接("Don't have an account? Sign up" / "Already have an account? Log in")
|
||||
* - 16px gap + "Other Sign In Options" 链接(打开底部弹层)
|
||||
* - Spacer + AuthLegalText
|
||||
* - 顶部 "← Back" 按钮由父级 AuthPanel 渲染(悬浮 40×40 圆形)
|
||||
*/
|
||||
import { useState } from "react";
|
||||
|
||||
import { useAuthState } from "@/stores/auth/auth-context";
|
||||
|
||||
import { AuthErrorMessage } from "./auth-error-message";
|
||||
import { AuthLegalText } from "./auth-legal-text";
|
||||
import { AuthOtherOptionsDialog } from "./auth-other-options-dialog";
|
||||
import { EmailLoginForm } from "./email-login-form";
|
||||
import { EmailRegisterForm } from "./email-register-form";
|
||||
import styles from "./auth-email-panel.module.css";
|
||||
|
||||
export interface AuthEmailPanelProps {
|
||||
onBack: () => void;
|
||||
/** "Other Sign-in Options" 弹层中 Facebook 按钮回调(派发回 facebook 模式) */
|
||||
onSwitchToFacebook: () => void;
|
||||
/** "Other Sign-in Options" 弹层中 Google 按钮回调(可空;空时不渲染 Google 行) */
|
||||
onGoogle?: () => void;
|
||||
/** 弹层 Google 自定义子节点 */
|
||||
googleSlot?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function AuthEmailPanel({ onBack }: AuthEmailPanelProps) {
|
||||
export function AuthEmailPanel({
|
||||
onSwitchToFacebook,
|
||||
onGoogle,
|
||||
googleSlot,
|
||||
}: AuthEmailPanelProps) {
|
||||
const state = useAuthState();
|
||||
const [mode, setMode] = useState<"login" | "register">("login");
|
||||
const [showOptions, setShowOptions] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={styles.panel}>
|
||||
<button type="button" className={styles.backButton} onClick={onBack}>
|
||||
← Back
|
||||
</button>
|
||||
<div className={styles.topSection}>
|
||||
{mode === "login" ? (
|
||||
<>
|
||||
<div className={styles.spacer24} />
|
||||
<img
|
||||
src="/images/auth/ic-logo-login.png"
|
||||
alt="Cozsweet"
|
||||
className={styles.logo}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.spacer24} />
|
||||
<div className={styles.spacer24} />
|
||||
</>
|
||||
)}
|
||||
<div className={styles.spacer24} />
|
||||
</div>
|
||||
|
||||
{mode === "login" ? (
|
||||
<EmailLoginForm
|
||||
onSwitchToRegister={() => setMode("register")}
|
||||
globalError={state.errorMessage}
|
||||
isLoading={state.isLoading}
|
||||
/>
|
||||
) : (
|
||||
<EmailRegisterForm
|
||||
onSwitchToLogin={() => setMode("login")}
|
||||
globalError={state.errorMessage}
|
||||
isLoading={state.isLoading}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AuthErrorMessage message={state.errorMessage} />
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={styles.toggleButton}
|
||||
onClick={() => setMode((m) => (m === "login" ? "register" : "login"))}
|
||||
>
|
||||
{mode === "login"
|
||||
? "Don't have an account? Sign up"
|
||||
: "Already have an account? Log in"}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={styles.linkButton}
|
||||
onClick={() => setShowOptions(true)}
|
||||
>
|
||||
Other Sign In Options
|
||||
</button>
|
||||
|
||||
<div className={styles.spacer} />
|
||||
<AuthLegalText />
|
||||
|
||||
<AuthOtherOptionsDialog
|
||||
open={showOptions}
|
||||
onClose={() => setShowOptions(false)}
|
||||
mode="email"
|
||||
onFacebook={onSwitchToFacebook}
|
||||
onGoogle={() => {
|
||||
setShowOptions(false);
|
||||
onGoogle?.();
|
||||
}}
|
||||
onEmail={() => {
|
||||
// 在 email 模式下不再显示 Email 入口
|
||||
}}
|
||||
googleSlot={googleSlot}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user