feat(auth) : improve Google account handling

This commit is contained in:
2026-10-12 00:51:29 +07:00 Unverified
parent e2af59292c
commit fdbabecf56
7 changed files with 80 additions and 11 deletions
+11
View File
@@ -37,6 +37,8 @@ export function AccountForm({
googleEnabled,
verified,
verificationError,
accountNotLinkedError,
googleSignInError,
}: {
mode: "login" | "register";
siteKey: string;
@@ -44,6 +46,8 @@ export function AccountForm({
googleEnabled: boolean;
verified?: boolean;
verificationError?: boolean;
accountNotLinkedError?: boolean;
googleSignInError?: boolean;
}) {
const router = useRouter();
const register = mode === "register";
@@ -120,6 +124,7 @@ export function AccountForm({
const result = await authClient.signIn.social({
provider: "google",
callbackURL: nextPath,
errorCallbackURL: `/auth/login?googleError=1${nextPath === "/" ? "" : `&next=${encodeURIComponent(nextPath)}`}`,
newUserCallbackURL: `/auth/register?step=profile&next=${encodeURIComponent(nextPath)}`,
fetchOptions: { headers: captcha ? { "x-captcha-response": token } : undefined },
});
@@ -228,6 +233,12 @@ export function AccountForm({
<CardContent className="flex flex-col gap-4">
{verified && <Alert><AlertDescription>ยืนยันอีเมลแล้ว กรุณาเข้าสู่ระบบ</AlertDescription></Alert>}
{verificationError && <Alert variant="destructive"><AlertDescription>ลิงก์ยืนยันไม่ถูกต้องหรือหมดอายุ กรุณาขอลิงก์ใหม่</AlertDescription></Alert>}
{googleSignInError && <Alert variant="destructive" role="alert"><AlertDescription>
เข้าสู่ระบบด้วย Google ไม่สำเร็จ กรุณาลองอีกครั้ง
</AlertDescription></Alert>}
{accountNotLinkedError && <Alert variant="destructive" role="alert"><AlertDescription>
บัญชี Google นี้ยังไม่ได้เชื่อมกับบัญชี Buzz Guide ของคุณ กรุณาเข้าสู่ระบบด้วยอีเมลและรหัสผ่าน แล้วเชื่อม Google ในหน้าโปรไฟล์
</AlertDescription></Alert>}
{notice && <Alert role="status"><AlertDescription>{notice}</AlertDescription></Alert>}
<form ref={formRef} onSubmit={submit}>
<FieldGroup>
@@ -0,0 +1,18 @@
"use client";
import { useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
export function AccountLinkErrorRedirect() {
const router = useRouter();
const searchParams = useSearchParams();
const error = searchParams.get("error");
useEffect(() => {
if (error === "account_not_linked") {
router.replace("/auth/login?error=account_not_linked");
}
}, [error, router]);
return null;
}
+12 -7
View File
@@ -1,7 +1,7 @@
import { redirect } from "next/navigation";
import { eq } from "drizzle-orm";
import { eq, sql } from "drizzle-orm";
import { getDb } from "@/db";
import { users } from "@/db/schema";
import { accounts, users } from "@/db/schema";
import { SiteHeader } from "@/components/public/site-header";
import { isAuthorizedAdmin } from "@/lib/auth/authorization";
import { getCustomerSession, isGoogleAuthEnabled } from "@/lib/auth/server";
@@ -9,19 +9,23 @@ import { safeAuthReturnPath } from "@/lib/auth/return-path";
import { AccountForm } from "./account-form";
import { ProfileForm } from "./profile-form";
export async function AccountPage({ mode, next, verified, verificationError, setup = false }: {
mode: "login" | "register"; next: unknown; verified?: boolean; verificationError?: boolean; setup?: boolean;
export async function AccountPage({ mode, next, verified, verificationError, accountNotLinkedError, googleSignInError, setup = false }: {
mode: "login" | "register"; next: unknown; verified?: boolean; verificationError?: boolean;
accountNotLinkedError?: boolean; googleSignInError?: boolean; setup?: boolean;
}) {
const nextPath = safeAuthReturnPath(next);
const session = await getCustomerSession();
if (session) {
const returnPath = nextPath.startsWith("/admin") && !isAuthorizedAdmin(session.user) ? "/" : nextPath;
if (mode === "register" && setup) {
const [user] = await getDb().select({ name: users.name, email: users.email, image: users.image })
const [user] = await getDb().select({ name: users.name, email: users.email, image: users.image,
hasGoogleAccount: sql<boolean>`exists (select 1 from ${accounts}
where ${accounts.userId} = ${users.id} and ${accounts.providerId} = 'google')` })
.from(users).where(eq(users.id, session.user.id)).limit(1);
if (!user) redirect("/auth/login");
return <div className="min-h-svh"><SiteHeader /><main className="grid min-h-[calc(100svh-4rem)] place-items-center p-4">
<div className="w-full max-w-sm"><ProfileForm {...user} nextPath={returnPath} setup /></div>
<div className="w-full max-w-sm"><ProfileForm {...user} nextPath={returnPath} setup
googlePhotoDefault={Boolean(user.image && user.hasGoogleAccount)} /></div>
</main></div>;
}
if (returnPath !== nextPath) redirect(returnPath);
@@ -31,6 +35,7 @@ export async function AccountPage({ mode, next, verified, verificationError, set
if (process.env.NODE_ENV !== "development" && !siteKey) throw new Error("TURNSTILE_SITE_KEY is required");
return <div className="min-h-svh"><SiteHeader /><main className="grid min-h-[calc(100svh-4rem)] place-items-center p-4">
<AccountForm mode={mode} siteKey={siteKey} nextPath={nextPath} googleEnabled={isGoogleAuthEnabled()}
verified={verified} verificationError={verificationError} />
verified={verified} verificationError={verificationError} accountNotLinkedError={accountNotLinkedError}
googleSignInError={googleSignInError} />
</main></div>;
}
+5 -1
View File
@@ -24,12 +24,14 @@ export function ProfileForm({
image: initialImage,
nextPath,
setup = false,
googlePhotoDefault = false,
}: {
name: string;
email: string;
image: string | null;
nextPath?: string;
setup?: boolean;
googlePhotoDefault?: boolean;
}) {
const [name, setName] = useState(initialName);
const [file, setFile] = useState<File | null>(null);
@@ -83,7 +85,9 @@ export function ProfileForm({
<form id="profile-form" onSubmit={save}>
<FieldGroup>
{setup && <p className="text-sm text-muted-foreground">
สร้างบัญชีเรียบร้อยแล้ว เพิ่มรูปโปรไฟล์ได้ตามต้องการ หรือกดข้ามเพื่อเริ่มใช้งาน คุณสามารถยืนยันอีเมลภายหลังได้ที่หน้าโปรไฟล์
{googlePhotoDefault
? "ใช้รูปโปรไฟล์จาก Google เป็นรูปเริ่มต้นแล้ว หากต้องการเปลี่ยน เลือกรูปใหม่ได้ที่นี่ หรือกดข้ามเพื่อเริ่มใช้งาน"
: "สร้างบัญชีเรียบร้อยแล้ว เพิ่มรูปโปรไฟล์ได้ตามต้องการ หรือกดข้ามเพื่อเริ่มใช้งาน คุณสามารถยืนยันอีเมลภายหลังได้ที่หน้าโปรไฟล์"}
</p>}
<Field>
<FieldLabel htmlFor="profile-image">รูปโปรไฟล์</FieldLabel>