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,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 → logo(120h)→ 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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user