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:
2026-06-10 14:27:06 +08:00
parent 4e27b4c512
commit 90bb711a45
30 changed files with 967 additions and 335 deletions
+50 -40
View File
@@ -1,24 +1,23 @@
"use client";
/**
* Facebook 面板(默认面板)
* Facebook 面板(默认登录面板)
*
* 原始 Dart: lib/ui/auth/widgets/auth_facebook_panel.dart
*
* 行为
* - 默认显示「Continue with Facebook」+ 「Other sign-in options」入口
* - 社交登录直接调 `nextauth.facebookLogin()` / `nextauth.googleLogin()`NextAuth 流程)
* 视觉规格(与 Dart 对齐)
* - Spacer → logo120h)→ Spacer → Facebook 主按钮 → 链接 → Spacer → 法律
* - Facebook 主按钮:46px 白胶囊 + Facebook "f" 图标(#1877f2+ "Login with Facebook"
* - 社交登录走 NextAuth`facebookLogin()` / `googleLogin()`
* - "Other Sign-in Options" 链接打开底部弹层
* - 弹层 Email 按钮 → 触发 `onSwitchToEmail`(父级派发 `AuthPanelModeChanged`
*/
import { useState } from "react";
import { useAuthState } from "@/stores/auth/auth-context";
import { facebookLogin, googleLogin } from "@/lib/auth/nextauth";
import { AuthSocialButtons } from "./auth-social-buttons";
import { AuthDivider } from "./auth-divider";
import { AuthPrimaryButton } from "./auth-primary-button";
import { AuthErrorMessage } from "./auth-error-message";
import { AuthOtherOptionsDialog } from "./auth-other-options-dialog";
import { AuthLegalText } from "./auth-legal-text";
import { AuthOtherOptionsDialog } from "./auth-other-options-dialog";
import styles from "./auth-facebook-panel.module.css";
export interface AuthFacebookPanelProps {
@@ -26,65 +25,76 @@ export interface AuthFacebookPanelProps {
}
export function AuthFacebookPanel({ onSwitchToEmail }: AuthFacebookPanelProps) {
const state = useAuthState();
const [showOptions, setShowOptions] = useState(false);
const [busy, setBusy] = useState<null | "facebook" | "google">(null);
const [error, setError] = useState<string | null>(null);
const handleFacebook = async () => {
setBusy("facebook");
setError(null);
const r = await facebookLogin();
setBusy(null);
if (!r.success) console.error("[auth-facebook-panel]", r.error);
if (!r.success) setError(r.error?.message ?? "Facebook login failed");
};
const handleGoogle = async () => {
setBusy("google");
setError(null);
const r = await googleLogin();
setBusy(null);
if (!r.success) console.error("[auth-facebook-panel]", r.error);
if (!r.success) setError(r.error?.message ?? "Google login failed");
};
return (
<div className={styles.panel}>
<AuthErrorMessage message={state.errorMessage} />
<AuthSocialButtons
loadingProvider={busy ?? (state.isLoading ? "facebook" : null)}
onFacebook={handleFacebook}
onGoogle={handleGoogle}
<div className={styles.spacer} />
<img
src="/images/auth/ic-logo-login.png"
alt="Cozsweet"
className={styles.logo}
/>
<AuthDivider label="or" />
<AuthPrimaryButton onClick={onSwitchToEmail} variant="primary">
Continue with Email
</AuthPrimaryButton>
<div className={styles.spacer} />
<button
type="button"
className={styles.facebookButton}
onClick={() => void handleFacebook()}
disabled={busy !== null}
>
{busy === "facebook" ? (
<span className={styles.spinner} aria-hidden="true" />
) : (
<span className={styles.facebookIcon} aria-hidden="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="#1877f2">
<path d="M13 22v-8h3l1-4h-4V7.5c0-1.2.4-2 2-2h2V2.2C16.6 2.1 15.5 2 14.3 2 11.5 2 9.5 3.7 9.5 7v3h-3v4h3v8h3.5z" />
</svg>
</span>
)}
<span className={styles.facebookLabel}>
{busy === "facebook" ? "Logging in..." : "Login with Facebook"}
</span>
</button>
<button
type="button"
className={styles.linkButton}
onClick={() => setShowOptions(true)}
>
Other sign-in options
Other Sign In Options
</button>
<div className={styles.spacer} />
<AuthErrorMessage message={error} />
<AuthLegalText />
<AuthOtherOptionsDialog
open={showOptions}
onClose={() => setShowOptions(false)}
onFacebook={() => {
setShowOptions(false);
void handleFacebook();
}}
onGoogle={() => {
setShowOptions(false);
void handleGoogle();
}}
onApple={() => {
setShowOptions(false);
console.warn("Apple login not implemented");
}}
onEmail={() => {
setShowOptions(false);
onSwitchToEmail();
}}
loadingProvider={busy ?? (state.isLoading ? "facebook" : null)}
showApple={false}
mode="facebook"
onFacebook={() => void handleFacebook()}
onGoogle={() => void handleGoogle()}
onEmail={onSwitchToEmail}
/>
</div>
);