248 lines
7.5 KiB
TypeScript
248 lines
7.5 KiB
TypeScript
import { Brand } from "@/components/ui/brand"
|
||
import { Input } from "@/components/ui/input"
|
||
import { Label } from "@/components/ui/label"
|
||
import { SubmitButton } from "@/components/ui/submit-button"
|
||
import { createClient } from "@/lib/supabase/server"
|
||
import { Database } from "@/supabase/types"
|
||
import { createServerClient } from "@supabase/ssr"
|
||
import { get } from "@vercel/edge-config"
|
||
import { Metadata } from "next"
|
||
import { cookies, headers } from "next/headers"
|
||
import { redirect } from "next/navigation"
|
||
|
||
import initTranslations from "@/lib/i18n";
|
||
|
||
export const metadata: Metadata = {
|
||
title: "Login"
|
||
}
|
||
|
||
export default async function Login({
|
||
searchParams,
|
||
params: { locale },
|
||
}: {
|
||
searchParams: { message: string };
|
||
params: { locale: string };
|
||
}) {
|
||
const cookieStore = cookies()
|
||
|
||
// 优先从 cookie 获取 locale,否则使用 URL 参数中的 locale
|
||
//const localeString = cookieStore.get('locale')?.value || locale;
|
||
|
||
const localeString = locale;
|
||
|
||
const { t, resources } = await initTranslations(localeString, ['translation']);
|
||
// 打印翻译内容确认是否正常加载
|
||
// console.log("...............localeString: ", {localeString});
|
||
// console.log("Translation for login.email: ", t("login.email"));
|
||
|
||
|
||
const supabase = createServerClient<Database>(
|
||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||
{
|
||
cookies: {
|
||
get(name: string) {
|
||
return cookieStore.get(name)?.value
|
||
}
|
||
}
|
||
}
|
||
)
|
||
const session = (await supabase.auth.getSession()).data.session
|
||
|
||
if (session) {
|
||
const { data: homeWorkspace, error } = await supabase
|
||
.from("workspaces")
|
||
.select("*")
|
||
.eq("user_id", session.user.id)
|
||
.eq("is_home", true)
|
||
.single()
|
||
|
||
if (!homeWorkspace) {
|
||
throw new Error(error.message)
|
||
}
|
||
|
||
console.log("1======>Redirecting to workspace:", homeWorkspace.id)
|
||
return redirect(`/${localeString}/${homeWorkspace.id}/chat`)
|
||
|
||
}
|
||
|
||
const signIn = async (formData: FormData) => {
|
||
"use server"
|
||
|
||
const email = formData.get("email") as string
|
||
const password = formData.get("password") as string
|
||
const cookieStore = cookies()
|
||
const supabase = createClient(cookieStore)
|
||
|
||
const { data, error } = await supabase.auth.signInWithPassword({
|
||
email,
|
||
password
|
||
})
|
||
|
||
if (error) {
|
||
console.log(`==================> ${localeString}/login?message=${error.message}`);
|
||
return redirect(`/${localeString}/login?message=${error.message}`)
|
||
}
|
||
|
||
const { data: homeWorkspace, error: homeWorkspaceError } = await supabase
|
||
.from("workspaces")
|
||
.select("*")
|
||
.eq("user_id", data.user.id)
|
||
.eq("is_home", true)
|
||
.single()
|
||
|
||
if (!homeWorkspace) {
|
||
throw new Error(
|
||
homeWorkspaceError?.message || t("login.unexpectedError")
|
||
)
|
||
}
|
||
|
||
console.log("2======>Redirecting to workspace:", homeWorkspace.id)
|
||
return redirect(`/${localeString}/${homeWorkspace.id}/chat`)
|
||
}
|
||
|
||
const getEnvVarOrEdgeConfigValue = async (name: string) => {
|
||
"use server"
|
||
if (process.env.EDGE_CONFIG) {
|
||
return await get<string>(name)
|
||
}
|
||
|
||
return process.env[name]
|
||
}
|
||
|
||
const signUp = async (formData: FormData) => {
|
||
"use server"
|
||
|
||
const email = formData.get("email") as string
|
||
const password = formData.get("password") as string
|
||
|
||
const emailDomainWhitelistPatternsString = await getEnvVarOrEdgeConfigValue(
|
||
"EMAIL_DOMAIN_WHITELIST"
|
||
)
|
||
const emailDomainWhitelist = emailDomainWhitelistPatternsString?.trim()
|
||
? emailDomainWhitelistPatternsString?.split(",")
|
||
: []
|
||
const emailWhitelistPatternsString =
|
||
await getEnvVarOrEdgeConfigValue("EMAIL_WHITELIST")
|
||
const emailWhitelist = emailWhitelistPatternsString?.trim()
|
||
? emailWhitelistPatternsString?.split(",")
|
||
: []
|
||
|
||
// If there are whitelist patterns, check if the email is allowed to sign up
|
||
if (emailDomainWhitelist.length > 0 || emailWhitelist.length > 0) {
|
||
const domainMatch = emailDomainWhitelist?.includes(email.split("@")[1])
|
||
const emailMatch = emailWhitelist?.includes(email)
|
||
if (!domainMatch && !emailMatch) {
|
||
const errorMessage = t("login.signupNotAllowed", { email }) // ← 这是字符串
|
||
return redirect(
|
||
// `/${localeString}/login?message=Email ${email} is not allowed to sign up.`
|
||
`/${localeString}/login?message=${errorMessage}`
|
||
)
|
||
}
|
||
}
|
||
|
||
const cookieStore = cookies()
|
||
const supabase = createClient(cookieStore)
|
||
|
||
const { error } = await supabase.auth.signUp({
|
||
email,
|
||
password,
|
||
options: {
|
||
// USE IF YOU WANT TO SEND EMAIL VERIFICATION, ALSO CHANGE TOML FILE
|
||
// emailRedirectTo: `${origin}/auth/callback`
|
||
}
|
||
})
|
||
|
||
if (error) {
|
||
console.error(error)
|
||
return redirect(`/${localeString}/login?message=${error.message}`)
|
||
}
|
||
|
||
console.log("2..................Redirecting with locale:", localeString)
|
||
return redirect(`/${localeString}/setup`)
|
||
|
||
// USE IF YOU WANT TO SEND EMAIL VERIFICATION, ALSO CHANGE TOML FILE
|
||
// return redirect("/login?message=Check email to continue sign in process")
|
||
}
|
||
|
||
const handleResetPassword = async (formData: FormData) => {
|
||
"use server"
|
||
|
||
const origin = headers().get("origin")
|
||
const email = formData.get("email") as string
|
||
const cookieStore = cookies()
|
||
const supabase = createClient(cookieStore)
|
||
|
||
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||
redirectTo: `${origin}/auth/callback?next=${localeString}/login/password`
|
||
})
|
||
|
||
if (error) {
|
||
return redirect(`/${localeString}/login?message=${error.message}`)
|
||
}
|
||
|
||
const emailtoResetMessage = t("login.checkEmailToReset") // ← 这是字符串
|
||
return redirect(`/${localeString}/login?message=${emailtoResetMessage}`)
|
||
}
|
||
|
||
|
||
|
||
return (
|
||
<div className="flex w-full flex-1 flex-col justify-center gap-2 px-8 sm:max-w-md">
|
||
<form
|
||
className="animate-in text-foreground flex w-full flex-1 flex-col justify-center gap-2"
|
||
action={signIn}
|
||
>
|
||
<Brand />
|
||
|
||
<Label className="text-md mt-4" htmlFor="email">
|
||
{t("login.email")}
|
||
</Label>
|
||
<Input
|
||
className="mb-3 rounded-md border bg-inherit px-4 py-2"
|
||
name="email"
|
||
placeholder={t("login.emailPlaceholder")}
|
||
required
|
||
/>
|
||
|
||
<Label className="text-md" htmlFor="password">
|
||
{t("login.password")}
|
||
</Label>
|
||
<Input
|
||
className="mb-6 rounded-md border bg-inherit px-4 py-2"
|
||
type="password"
|
||
name="password"
|
||
placeholder={t("login.passwordPlaceholder")}
|
||
/>
|
||
|
||
<SubmitButton className="mb-2 rounded-md bg-blue-700 px-4 py-2 text-white">
|
||
{t("login.loginButton")}
|
||
</SubmitButton>
|
||
|
||
<SubmitButton
|
||
formAction={signUp}
|
||
className="border-foreground/20 mb-2 rounded-md border px-4 py-2"
|
||
>
|
||
{t("login.signUpButton")}
|
||
</SubmitButton>
|
||
|
||
<div className="text-muted-foreground mt-1 flex justify-center text-sm">
|
||
<span className="mr-1">{t("login.forgotPassword")}</span>
|
||
<button
|
||
formAction={handleResetPassword}
|
||
className="text-primary ml-1 underline hover:opacity-80"
|
||
>
|
||
{t("login.reset")}
|
||
</button>
|
||
</div>
|
||
|
||
{searchParams?.message && (
|
||
<p className="bg-foreground/10 text-foreground mt-4 p-4 text-center">
|
||
{searchParams.message}
|
||
</p>
|
||
)}
|
||
</form>
|
||
</div>
|
||
)
|
||
}
|