e2a57386e7
Move new AuthPlatform(provider).signIn() out of UI components and into the auth state machine. UI now dispatches AuthGoogle/FacebookLoginSubmitted events; a new oauthSignIn actor inside the machine calls next-auth/react's signIn(provider). Errors and the OAuth redirect window are exposed via the existing state.isLoading / state.errorMessage surface, removing the local busy / error state in auth-facebook-panel. Co-Authored-By: Claude <noreply@anthropic.com>
106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
"use client";
|
||
/**
|
||
* Facebook 面板(默认登录面板)
|
||
*
|
||
* 原始 Dart: lib/ui/auth/widgets/auth_facebook_panel.dart
|
||
*
|
||
* 视觉规格(与 Dart 对齐):
|
||
* - Spacer → logo(120h)→ Spacer → Facebook 主按钮 → 链接 → Spacer → 法律
|
||
* - Facebook 主按钮:46px 白胶囊 + Facebook "f" 图标(#1877f2)+ "Login with Facebook"
|
||
* - 社交登录统一收口到 Auth 状态机:派发 `AuthFacebookLoginSubmitted` /
|
||
* `AuthGoogleLoginSubmitted` 事件,状态机内部调 next-auth/react 的 signIn(provider)
|
||
* - busy / error 状态来自 `useAuthState()`(与邮箱登录同源)
|
||
* - "Other Sign-in Options" 链接打开底部弹层
|
||
* - 弹层 Email 按钮 → 触发 `onSwitchToEmail`(父级派发 `AuthPanelModeChanged`)
|
||
*/
|
||
import Image from "next/image";
|
||
import { useState } from "react";
|
||
|
||
import { useAuthDispatch, 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 styles from "./auth-facebook-panel.module.css";
|
||
|
||
export interface AuthFacebookPanelProps {
|
||
onSwitchToEmail: () => void;
|
||
}
|
||
|
||
export function AuthFacebookPanel({ onSwitchToEmail }: AuthFacebookPanelProps) {
|
||
const [showOptions, setShowOptions] = useState(false);
|
||
const { isLoading, errorMessage } = useAuthState();
|
||
const authDispatch = useAuthDispatch();
|
||
|
||
const handleFacebook = () => {
|
||
authDispatch({
|
||
type: "AuthFacebookLoginSubmitted",
|
||
provider: "facebook",
|
||
});
|
||
};
|
||
|
||
const handleGoogle = () => {
|
||
authDispatch({
|
||
type: "AuthGoogleLoginSubmitted",
|
||
provider: "google",
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className={styles.panel}>
|
||
<div className={styles.spacer} />
|
||
<Image
|
||
src="/images/auth/ic-logo-login.png"
|
||
alt="Cozsweet"
|
||
width={120}
|
||
height={120}
|
||
className={styles.logo}
|
||
priority
|
||
/>
|
||
<div className={styles.spacer} />
|
||
|
||
<button
|
||
type="button"
|
||
className={styles.facebookButton}
|
||
onClick={() => handleFacebook()}
|
||
disabled={isLoading}
|
||
>
|
||
{isLoading ? (
|
||
<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}>
|
||
{isLoading ? "Logging in..." : "Login with Facebook"}
|
||
</span>
|
||
</button>
|
||
|
||
<button
|
||
type="button"
|
||
className={styles.linkButton}
|
||
onClick={() => setShowOptions(true)}
|
||
>
|
||
Other Sign In Options
|
||
</button>
|
||
|
||
<div className={styles.spacer} />
|
||
|
||
<AuthErrorMessage message={errorMessage} />
|
||
<AuthLegalText />
|
||
|
||
<AuthOtherOptionsDialog
|
||
open={showOptions}
|
||
onClose={() => setShowOptions(false)}
|
||
mode="facebook"
|
||
onFacebook={() => handleFacebook()}
|
||
onGoogle={() => handleGoogle()}
|
||
onEmail={onSwitchToEmail}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|