chore(auth): update OAuth login descriptions in multiple languages
This commit is contained in:
@@ -18,3 +18,6 @@ NEXTAUTH_SECRET="your-super-secret-key-change-in-production"
|
||||
# S3_ACCESS_KEY_ID=""
|
||||
# S3_SECRET_ACCESS_KEY=""
|
||||
# S3_ENDPOINT="" # For S3-compatible services like MinIO
|
||||
|
||||
# GITHUB_CLIENT_ID=your_client_id
|
||||
# GITHUB_CLIENT_SECRET=your_client_secret
|
||||
@@ -45,6 +45,7 @@
|
||||
"auth": {
|
||||
"login": "تسجيل الدخول",
|
||||
"loginDescription": "أدخل بياناتك للمتابعة",
|
||||
"loginDescriptionOAuth": "سجل الدخول بحسابك للمتابعة",
|
||||
"register": "إنشاء حساب",
|
||||
"registerDescription": "أنشئ حساباً للبدء",
|
||||
"logout": "تسجيل الخروج",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"loginDescription": "Enter your credentials to continue",
|
||||
"loginDescriptionOAuth": "Sign in with your account to continue",
|
||||
"register": "Register",
|
||||
"registerDescription": "Create an account to get started",
|
||||
"logout": "Logout",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"auth": {
|
||||
"login": "Iniciar sesión",
|
||||
"loginDescription": "Ingresa tus credenciales para continuar",
|
||||
"loginDescriptionOAuth": "Inicia sesión con tu cuenta para continuar",
|
||||
"register": "Registrarse",
|
||||
"registerDescription": "Crea una cuenta para comenzar",
|
||||
"logout": "Cerrar sesión",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"auth": {
|
||||
"login": "ログイン",
|
||||
"loginDescription": "続行するには認証情報を入力してください",
|
||||
"loginDescriptionOAuth": "アカウントでサインインして続行",
|
||||
"register": "登録",
|
||||
"registerDescription": "アカウントを作成して始めましょう",
|
||||
"logout": "ログアウト",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"auth": {
|
||||
"login": "Giriş Yap",
|
||||
"loginDescription": "Devam etmek için bilgilerinizi girin",
|
||||
"loginDescriptionOAuth": "Devam etmek için hesabınızla giriş yapın",
|
||||
"register": "Kayıt Ol",
|
||||
"registerDescription": "Başlamak için bir hesap oluşturun",
|
||||
"logout": "Çıkış Yap",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"auth": {
|
||||
"login": "登录",
|
||||
"loginDescription": "输入您的凭据以继续",
|
||||
"loginDescriptionOAuth": "使用您的账户登录以继续",
|
||||
"register": "注册",
|
||||
"registerDescription": "创建账户以开始使用",
|
||||
"logout": "退出登录",
|
||||
|
||||
+3
-3
@@ -25,10 +25,10 @@ export default defineConfig({
|
||||
|
||||
// Authentication plugin
|
||||
auth: {
|
||||
// Available: "credentials" | "google" | "azure" | custom
|
||||
provider: "credentials",
|
||||
// Available: "credentials" | "google" | "azure" | "github" | custom
|
||||
provider: "github",
|
||||
// Allow public registration
|
||||
allowRegistration: true,
|
||||
allowRegistration: false,
|
||||
},
|
||||
|
||||
// Storage plugin for media uploads
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { LoginForm } from "@/components/auth/login-form";
|
||||
import { getConfig } from "@/lib/config";
|
||||
import { AuthContent } from "@/components/auth/auth-content";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Login",
|
||||
@@ -10,21 +11,27 @@ export const metadata: Metadata = {
|
||||
|
||||
export default async function LoginPage() {
|
||||
const t = await getTranslations("auth");
|
||||
const config = await getConfig();
|
||||
const isCredentials = config.auth.provider === "credentials";
|
||||
|
||||
return (
|
||||
<div className="container flex min-h-[calc(100vh-6rem)] flex-col items-center justify-center py-8">
|
||||
<div className="w-full max-w-sm space-y-4">
|
||||
<div className="text-center space-y-1">
|
||||
<h1 className="text-xl font-semibold">{t("login")}</h1>
|
||||
<p className="text-xs text-muted-foreground">{t("loginDescription")}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{isCredentials ? t("loginDescription") : t("loginDescriptionOAuth")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4">
|
||||
<LoginForm />
|
||||
<AuthContent provider={config.auth.provider} mode="login" />
|
||||
</div>
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
{t("noAccount")}{" "}
|
||||
<Link href="/register" className="text-foreground hover:underline">{t("register")}</Link>
|
||||
</p>
|
||||
{isCredentials && (
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
{t("noAccount")}{" "}
|
||||
<Link href="/register" className="text-foreground hover:underline">{t("register")}</Link>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { RegisterForm } from "@/components/auth/register-form";
|
||||
import { getConfig } from "@/lib/config";
|
||||
import { AuthContent } from "@/components/auth/auth-content";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Register",
|
||||
@@ -10,6 +12,13 @@ export const metadata: Metadata = {
|
||||
|
||||
export default async function RegisterPage() {
|
||||
const t = await getTranslations("auth");
|
||||
const config = await getConfig();
|
||||
const isCredentials = config.auth.provider === "credentials";
|
||||
|
||||
// Block registration if disabled or using OAuth providers
|
||||
if (!isCredentials || !config.auth.allowRegistration) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container flex min-h-[calc(100vh-6rem)] flex-col items-center justify-center py-8">
|
||||
@@ -19,7 +28,7 @@ export default async function RegisterPage() {
|
||||
<p className="text-xs text-muted-foreground">{t("registerDescription")}</p>
|
||||
</div>
|
||||
<div className="border rounded-lg p-4">
|
||||
<RegisterForm />
|
||||
<AuthContent provider={config.auth.provider} mode="register" />
|
||||
</div>
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
{t("hasAccount")}{" "}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/lib/db";
|
||||
import { getConfig } from "@/lib/config";
|
||||
|
||||
const registerSchema = z.object({
|
||||
name: z.string().min(2),
|
||||
@@ -12,6 +13,15 @@ const registerSchema = z.object({
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
// Check if registration is allowed
|
||||
const config = await getConfig();
|
||||
if (!config.auth.allowRegistration) {
|
||||
return NextResponse.json(
|
||||
{ error: "registration_disabled", message: "Registration is disabled" },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const parsed = registerSchema.safeParse(body);
|
||||
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ export default async function RootLayout({
|
||||
<body className={`${fontClasses} antialiased`}>
|
||||
<Providers locale={locale} messages={messages} theme={config.theme} branding={config.branding}>
|
||||
<div className="relative min-h-screen flex flex-col">
|
||||
<Header />
|
||||
<Header showRegister={config.auth.provider === "credentials" && config.auth.allowRegistration} />
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import { LoginForm } from "./login-form";
|
||||
import { RegisterForm } from "./register-form";
|
||||
import { OAuthButton } from "./oauth-button";
|
||||
|
||||
interface AuthContentProps {
|
||||
provider: string;
|
||||
mode: "login" | "register";
|
||||
}
|
||||
|
||||
const providerNames: Record<string, string> = {
|
||||
github: "GitHub",
|
||||
google: "Google",
|
||||
azure: "Microsoft",
|
||||
credentials: "Email",
|
||||
};
|
||||
|
||||
export function AuthContent({ provider, mode }: AuthContentProps) {
|
||||
// For OAuth providers, show the OAuth button
|
||||
if (provider !== "credentials") {
|
||||
return (
|
||||
<OAuthButton
|
||||
provider={provider}
|
||||
providerName={providerNames[provider] || provider}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// For credentials, show the appropriate form
|
||||
return mode === "login" ? <LoginForm /> : <RegisterForm />;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface OAuthButtonProps {
|
||||
provider: string;
|
||||
providerName: string;
|
||||
}
|
||||
|
||||
const providerIcons: Record<string, React.ReactNode> = {
|
||||
github: (
|
||||
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
),
|
||||
google: (
|
||||
<svg className="h-4 w-4" viewBox="0 0 24 24">
|
||||
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
|
||||
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
|
||||
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
||||
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
|
||||
</svg>
|
||||
),
|
||||
azure: (
|
||||
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M5.483 21.3H24L14.025 4.013l-3.038 8.347 5.836 6.938L5.483 21.3zM13.23 2.7L6.105 8.677 0 19.253h5.505l7.725-16.553z"/>
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
export function OAuthButton({ provider, providerName }: OAuthButtonProps) {
|
||||
const t = useTranslations("auth");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleSignIn = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await signIn(provider, { callbackUrl: "/" });
|
||||
} catch (error) {
|
||||
console.error("Sign in error:", error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full h-10"
|
||||
onClick={handleSignIn}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<span className="mr-2">{providerIcons[provider]}</span>
|
||||
)}
|
||||
{t("signInWith", { provider: providerName })}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -42,7 +42,11 @@ const languages = [
|
||||
{ code: "ar", name: "العربية" },
|
||||
];
|
||||
|
||||
export function Header() {
|
||||
interface HeaderProps {
|
||||
showRegister?: boolean;
|
||||
}
|
||||
|
||||
export function Header({ showRegister = true }: HeaderProps) {
|
||||
const { data: session } = useSession();
|
||||
const t = useTranslations();
|
||||
const { theme, setTheme } = useTheme();
|
||||
@@ -230,9 +234,11 @@ export function Header() {
|
||||
<Button variant="ghost" size="sm" className="h-8 text-xs" asChild>
|
||||
<Link href="/login">{t("nav.login")}</Link>
|
||||
</Button>
|
||||
<Button size="sm" className="h-8 text-xs" asChild>
|
||||
<Link href="/register">{t("nav.register")}</Link>
|
||||
</Button>
|
||||
{showRegister && (
|
||||
<Button size="sm" className="h-8 text-xs" asChild>
|
||||
<Link href="/register">{t("nav.register")}</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface ThemeConfig {
|
||||
}
|
||||
|
||||
export interface AuthConfig {
|
||||
provider: "credentials" | "google" | "azure" | string;
|
||||
provider: "credentials" | "google" | "azure" | "github" | string;
|
||||
allowRegistration: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import GitHub from "next-auth/providers/github";
|
||||
import type { AuthPlugin } from "../types";
|
||||
|
||||
export const githubPlugin: AuthPlugin = {
|
||||
id: "github",
|
||||
name: "GitHub",
|
||||
getProvider: () =>
|
||||
GitHub({
|
||||
clientId: process.env.GITHUB_CLIENT_ID!,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
||||
}),
|
||||
};
|
||||
@@ -2,12 +2,14 @@ import { registerAuthPlugin } from "../registry";
|
||||
import { credentialsPlugin } from "./credentials";
|
||||
import { googlePlugin } from "./google";
|
||||
import { azurePlugin } from "./azure";
|
||||
import { githubPlugin } from "./github";
|
||||
|
||||
// Register all built-in auth plugins
|
||||
export function registerBuiltInAuthPlugins(): void {
|
||||
registerAuthPlugin(credentialsPlugin);
|
||||
registerAuthPlugin(googlePlugin);
|
||||
registerAuthPlugin(azurePlugin);
|
||||
registerAuthPlugin(githubPlugin);
|
||||
}
|
||||
|
||||
export { credentialsPlugin, googlePlugin, azurePlugin };
|
||||
export { credentialsPlugin, googlePlugin, azurePlugin, githubPlugin };
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function getConfiguredAuthPlugin() {
|
||||
if (!plugin) {
|
||||
throw new Error(
|
||||
`Auth plugin "${config.auth.provider}" not found. ` +
|
||||
`Available plugins: credentials, google, azure`
|
||||
`Available plugins: credentials, google, azure, github`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user