feat(auth): Appwrite-backed sign-in / sign-up / forgot-password + middleware guard
- Server actions in lib/appwrite/auth-actions.ts: signInAction, signUpAction, forgotPasswordAction, signOutAction All use node-appwrite admin client; session secret stored as httpOnly cookie (isletmem-session). Errors localized to Turkish. - Redesigned /sign-in and /sign-up using sign-in-3 split-card layout, branded as 'İşletmem' with gradient brand panel (no external image). Removed social login buttons (email/password only for now). - /forgot-password localized; success state shows email-sent confirmation. - Auth pages redirect to /dashboard if user already has a session. - middleware.ts: * Protects /dashboard, /onboarding, /settings — redirects to /sign-in?redirect=... * Auth pages redirect logged-in users to /dashboard * Keeps legacy /login and /register redirects
This commit is contained in:
@@ -1,57 +1,87 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import Link from "next/link";
|
||||||
import { Button } from "@/components/ui/button"
|
import { useActionState } from "react";
|
||||||
import {
|
import { ArrowLeft, Loader2, MailCheck } from "lucide-react";
|
||||||
Card,
|
|
||||||
CardContent,
|
import { Button } from "@/components/ui/button";
|
||||||
CardDescription,
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
CardHeader,
|
import { Input } from "@/components/ui/input";
|
||||||
CardTitle,
|
import { Label } from "@/components/ui/label";
|
||||||
} from "@/components/ui/card"
|
import { cn } from "@/lib/utils";
|
||||||
import { Input } from "@/components/ui/input"
|
import { forgotPasswordAction, initialAuthState } from "@/lib/appwrite/auth-actions";
|
||||||
import { Label } from "@/components/ui/label"
|
|
||||||
|
export function ForgotPasswordForm1({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
const [state, formAction, isPending] = useActionState(forgotPasswordAction, initialAuthState);
|
||||||
|
|
||||||
export function ForgotPasswordForm1({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"div">) {
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="text-center">
|
<CardHeader className="text-center">
|
||||||
<CardTitle className="text-xl">Forgot your password?</CardTitle>
|
<CardTitle className="text-xl">Şifremi unuttum</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Enter your email address and we'll send you a link to reset your password
|
Email adresinizi girin, sıfırlama bağlantısı gönderelim.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form>
|
{state.ok ? (
|
||||||
<div className="grid gap-6">
|
<div className="flex flex-col items-center gap-3 py-4 text-center">
|
||||||
<div className="grid gap-6">
|
<div className="bg-primary/10 text-primary flex size-12 items-center justify-center rounded-full">
|
||||||
<div className="grid gap-3">
|
<MailCheck className="size-6" />
|
||||||
<Label htmlFor="email">Email</Label>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
placeholder="m@example.com"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button type="submit" className="w-full cursor-pointer">
|
|
||||||
Send Reset Link
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="text-center text-sm">
|
|
||||||
Remember your password?{" "}
|
|
||||||
<a href="/auth/sign-in" className="underline underline-offset-4">
|
|
||||||
Back to sign in
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm">
|
||||||
|
Bağlantı emailinize gönderildi. Gelen kutusunu kontrol edin.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/sign-in"
|
||||||
|
className="text-muted-foreground hover:text-foreground mt-2 flex items-center gap-1 text-sm underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="size-3.5" />
|
||||||
|
Giriş sayfasına dön
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
) : (
|
||||||
|
<form action={formAction} className="flex flex-col gap-4">
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="ornek@firma.com"
|
||||||
|
autoComplete="email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{state.error && (
|
||||||
|
<p className="text-destructive text-sm text-center" role="alert">
|
||||||
|
{state.error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full" disabled={isPending}>
|
||||||
|
{isPending ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="size-4 animate-spin" />
|
||||||
|
Gönderiliyor...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Sıfırlama bağlantısı gönder"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/sign-in"
|
||||||
|
className="text-muted-foreground hover:text-foreground flex items-center justify-center gap-1 text-sm underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="size-3.5" />
|
||||||
|
Giriş sayfasına dön
|
||||||
|
</Link>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ForgotPasswordForm1 } from "./components/forgot-password-form-1"
|
import Link from "next/link";
|
||||||
import { Logo } from "@/components/logo"
|
|
||||||
import Link from "next/link"
|
import { ForgotPasswordForm1 } from "./components/forgot-password-form-1";
|
||||||
|
import { Logo } from "@/components/logo";
|
||||||
|
|
||||||
export default function ForgotPasswordPage() {
|
export default function ForgotPasswordPage() {
|
||||||
return (
|
return (
|
||||||
@@ -10,10 +11,10 @@ export default function ForgotPasswordPage() {
|
|||||||
<div className="bg-primary text-primary-foreground flex size-9 items-center justify-center rounded-md">
|
<div className="bg-primary text-primary-foreground flex size-9 items-center justify-center rounded-md">
|
||||||
<Logo size={24} />
|
<Logo size={24} />
|
||||||
</div>
|
</div>
|
||||||
ShadcnStore
|
<span className="text-lg font-semibold">İşletmem</span>
|
||||||
</Link>
|
</Link>
|
||||||
<ForgotPasswordForm1 />
|
<ForgotPasswordForm1 />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Authentication - ShadcnStore",
|
title: "İşletmem — Giriş",
|
||||||
description: "Sign in to your account or create a new one",
|
description: "İşletmem KovakCRM hesabınıza giriş yapın veya yeni hesap oluşturun.",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AuthLayout({
|
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||||
children,
|
return <div className="min-h-screen bg-background">{children}</div>;
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-background">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,127 +1,157 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import Link from "next/link";
|
||||||
import { useForm } from "react-hook-form"
|
import { useActionState } from "react";
|
||||||
import { z } from "zod"
|
import { Loader2 } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card"
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form"
|
|
||||||
|
|
||||||
const loginFormSchema = z.object({
|
import { Button } from "@/components/ui/button";
|
||||||
email: z.string().email("Invalid email address"),
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
password: z.string().min(6, "Password must be at least 6 characters"),
|
import { Input } from "@/components/ui/input";
|
||||||
})
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Logo } from "@/components/logo";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { initialAuthState, signInAction } from "@/lib/appwrite/auth-actions";
|
||||||
|
|
||||||
type LoginFormValues = z.infer<typeof loginFormSchema>
|
export function LoginForm1({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
const [state, formAction, isPending] = useActionState(signInAction, initialAuthState);
|
||||||
export function LoginForm1({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"div">) {
|
|
||||||
const form = useForm<LoginFormValues>({
|
|
||||||
resolver: zodResolver(loginFormSchema),
|
|
||||||
defaultValues: {
|
|
||||||
email: "test@example.com",
|
|
||||||
password: "password",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||||
<Card>
|
<Card className="overflow-hidden p-0">
|
||||||
<CardHeader className="text-center">
|
<CardContent className="grid p-0 md:grid-cols-2">
|
||||||
<CardTitle className="text-xl">Welcome back</CardTitle>
|
<form action={formAction} className="p-6 md:p-10">
|
||||||
<CardDescription>
|
<div className="flex flex-col gap-6">
|
||||||
Enter your email below to login to your account
|
<div className="flex justify-center">
|
||||||
</CardDescription>
|
<Link href="/" className="flex items-center gap-2 font-medium">
|
||||||
</CardHeader>
|
<div className="bg-primary text-primary-foreground flex size-9 items-center justify-center rounded-md">
|
||||||
<CardContent>
|
<Logo size={22} />
|
||||||
<Form {...form}>
|
</div>
|
||||||
<form action="/">
|
<span className="text-xl font-semibold">İşletmem</span>
|
||||||
<div className="grid gap-6">
|
</Link>
|
||||||
<div className="grid gap-4">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="email"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Email</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
type="email"
|
|
||||||
placeholder="test@example.com"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="password"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<FormLabel>Password</FormLabel>
|
|
||||||
<a
|
|
||||||
href="/auth/forgot-password"
|
|
||||||
className="ml-auto text-sm underline-offset-4 hover:underline"
|
|
||||||
>
|
|
||||||
Forgot your password?
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Input type="password" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button type="submit" className="w-full cursor-pointer">
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button variant="outline" className="w-full cursor-pointer" type="button">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Login with Google
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="text-center text-sm">
|
|
||||||
Don't have an account?{" "}
|
|
||||||
<a href="/auth/sign-up" className="underline underline-offset-4">
|
|
||||||
Sign up
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</Form>
|
<div className="flex flex-col items-center text-center">
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight">Tekrar hoş geldiniz</h1>
|
||||||
|
<p className="text-muted-foreground text-sm text-balance mt-1">
|
||||||
|
Hesabınıza giriş yaparak işletmenizi yönetmeye devam edin
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="ornek@firma.com"
|
||||||
|
autoComplete="email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Label htmlFor="password">Şifre</Label>
|
||||||
|
<Link
|
||||||
|
href="/forgot-password"
|
||||||
|
className="ml-auto text-xs text-muted-foreground hover:text-foreground underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
Şifremi unuttum
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autoComplete="current-password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{state.error && (
|
||||||
|
<p className="text-destructive text-sm text-center" role="alert">
|
||||||
|
{state.error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full" disabled={isPending}>
|
||||||
|
{isPending ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="size-4 animate-spin" />
|
||||||
|
Giriş yapılıyor...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Giriş yap"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="text-center text-sm text-muted-foreground">
|
||||||
|
Hesabınız yok mu?{" "}
|
||||||
|
<Link
|
||||||
|
href="/sign-up"
|
||||||
|
className="text-foreground font-medium underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
Hesap oluştur
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<BrandPanel />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="text-muted-foreground *:[a]:hover:text-primary text-center text-xs text-balance *:[a]:underline *:[a]:underline-offset-4">
|
|
||||||
By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
|
<p className="text-muted-foreground text-center text-xs text-balance">
|
||||||
and <a href="#">Privacy Policy</a>.
|
Giriş yaparak{" "}
|
||||||
|
<Link href="#" className="underline-offset-4 hover:underline">
|
||||||
|
Kullanım Şartları
|
||||||
|
</Link>{" "}
|
||||||
|
ve{" "}
|
||||||
|
<Link href="#" className="underline-offset-4 hover:underline">
|
||||||
|
Gizlilik Politikası
|
||||||
|
</Link>
|
||||||
|
'nı kabul etmiş olursunuz.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BrandPanel() {
|
||||||
|
return (
|
||||||
|
<div className="bg-primary text-primary-foreground relative hidden md:flex md:flex-col md:justify-between overflow-hidden p-10">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 opacity-30"
|
||||||
|
style={{
|
||||||
|
backgroundImage:
|
||||||
|
"radial-gradient(circle at 20% 20%, rgba(255,255,255,0.4) 0%, transparent 40%), radial-gradient(circle at 80% 80%, rgba(255,255,255,0.25) 0%, transparent 45%)",
|
||||||
|
}}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute -top-24 -right-24 size-72 rounded-full bg-white/10 blur-3xl"
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute -bottom-32 -left-20 size-80 rounded-full bg-black/10 blur-3xl"
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="relative z-10 flex items-center gap-2">
|
||||||
|
<div className="bg-primary-foreground/15 ring-1 ring-primary-foreground/20 backdrop-blur flex size-10 items-center justify-center rounded-md">
|
||||||
|
<Logo size={22} />
|
||||||
|
</div>
|
||||||
|
<span className="text-lg font-medium">İşletmem</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10 flex flex-col gap-3">
|
||||||
|
<h2 className="text-3xl font-semibold leading-tight">
|
||||||
|
Müşteriden faturaya, tek panelden işletmenizi yönetin.
|
||||||
|
</h2>
|
||||||
|
<p className="text-primary-foreground/80 text-sm">
|
||||||
|
Müşteriler, hizmetler, takvim, görevler ve finans — hepsi tek yerde, multi-tenant ve ekibinize özel.
|
||||||
|
</p>
|
||||||
|
<div className="text-primary-foreground/70 mt-4 text-xs">KovakSoft tarafından</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { LoginForm1 } from "./components/login-form-1"
|
import { redirect } from "next/navigation";
|
||||||
import { Logo } from "@/components/logo"
|
|
||||||
import Link from "next/link"
|
import { LoginForm1 } from "./components/login-form-1";
|
||||||
|
import { getCurrentUser } from "@/lib/appwrite/server";
|
||||||
|
|
||||||
|
export default async function Page() {
|
||||||
|
const user = await getCurrentUser();
|
||||||
|
if (user) redirect("/dashboard");
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
<div className="bg-muted flex min-h-svh flex-col items-center justify-center p-6 md:p-10">
|
||||||
<div className="flex w-full max-w-sm flex-col gap-6">
|
<div className="w-full max-w-sm md:max-w-4xl">
|
||||||
<Link href="/" className="flex items-center gap-2 self-center font-medium">
|
|
||||||
<div className="bg-primary text-primary-foreground flex size-9 items-center justify-center rounded-md">
|
|
||||||
<Logo size={24} />
|
|
||||||
</div>
|
|
||||||
ShadcnStore
|
|
||||||
</Link>
|
|
||||||
<LoginForm1 />
|
<LoginForm1 />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,195 +1,168 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import Link from "next/link";
|
||||||
import { useForm } from "react-hook-form"
|
import { useActionState } from "react";
|
||||||
import { z } from "zod"
|
import { Loader2 } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card"
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form"
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
|
||||||
|
|
||||||
const signupFormSchema = z.object({
|
import { Button } from "@/components/ui/button";
|
||||||
firstName: z.string().min(1, "First name is required"),
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
lastName: z.string().min(1, "Last name is required"),
|
import { Input } from "@/components/ui/input";
|
||||||
email: z.string().email("Invalid email address"),
|
import { Label } from "@/components/ui/label";
|
||||||
password: z.string().min(6, "Password must be at least 6 characters"),
|
import { Logo } from "@/components/logo";
|
||||||
confirmPassword: z.string().min(6, "Please confirm your password"),
|
import { cn } from "@/lib/utils";
|
||||||
terms: z.boolean().refine(val => val === true, "You must agree to the terms"),
|
import { initialAuthState, signUpAction } from "@/lib/appwrite/auth-actions";
|
||||||
}).refine((data) => data.password === data.confirmPassword, {
|
|
||||||
message: "Passwords don't match",
|
|
||||||
path: ["confirmPassword"],
|
|
||||||
})
|
|
||||||
|
|
||||||
type SignupFormValues = z.infer<typeof signupFormSchema>
|
export function SignupForm1({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
const [state, formAction, isPending] = useActionState(signUpAction, initialAuthState);
|
||||||
export function SignupForm1({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"div">) {
|
|
||||||
const form = useForm<SignupFormValues>({
|
|
||||||
resolver: zodResolver(signupFormSchema),
|
|
||||||
defaultValues: {
|
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
email: "",
|
|
||||||
password: "",
|
|
||||||
confirmPassword: "",
|
|
||||||
terms: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function onSubmit(data: SignupFormValues) {
|
|
||||||
console.log("Signup attempt:", data)
|
|
||||||
// Here you would typically handle the signup
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
<div className={cn("flex flex-col gap-6", className)} {...props}>
|
||||||
<Card>
|
<Card className="overflow-hidden p-0">
|
||||||
<CardHeader className="text-center">
|
<CardContent className="grid p-0 md:grid-cols-2">
|
||||||
<CardTitle className="text-xl">Create Account</CardTitle>
|
<BrandPanel />
|
||||||
<CardDescription>
|
|
||||||
Enter your information to create a new account
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
||||||
<div className="grid gap-6">
|
|
||||||
<div className="grid gap-4">
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="firstName"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>First Name</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="John" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="lastName"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Last Name</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="Doe" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="email"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Email</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
type="email"
|
|
||||||
placeholder="m@example.com"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="password"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Password</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input type="password" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="confirmPassword"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Confirm Password</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input type="password" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="terms"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex items-start space-x-2">
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
className="mt-0.5"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel className="text-sm">
|
|
||||||
I agree to the terms of service and privacy policy
|
|
||||||
</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button type="submit" className="w-full cursor-pointer">
|
|
||||||
Create Account
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button variant="outline" className="w-full cursor-pointer" type="button">
|
<form action={formAction} className="p-6 md:p-10">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<div className="flex flex-col gap-6">
|
||||||
<path
|
<div className="flex justify-center">
|
||||||
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
|
<Link href="/" className="flex items-center gap-2 font-medium">
|
||||||
fill="currentColor"
|
<div className="bg-primary text-primary-foreground flex size-9 items-center justify-center rounded-md">
|
||||||
/>
|
<Logo size={22} />
|
||||||
</svg>
|
</div>
|
||||||
Sign up with Google
|
<span className="text-xl font-semibold">İşletmem</span>
|
||||||
</Button>
|
</Link>
|
||||||
</div>
|
|
||||||
<div className="text-center text-sm">
|
|
||||||
Already have an account?{" "}
|
|
||||||
<a href="/auth/sign-in" className="underline underline-offset-4">
|
|
||||||
Sign in
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</Form>
|
<div className="flex flex-col items-center text-center">
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight">Hesap oluşturun</h1>
|
||||||
|
<p className="text-muted-foreground text-sm text-balance mt-1">
|
||||||
|
Birkaç saniye içinde hesabınız hazır
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="name">Adınız Soyadınız</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Ahmet Yılmaz"
|
||||||
|
autoComplete="name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="ornek@firma.com"
|
||||||
|
autoComplete="email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor="password">Şifre</Label>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
|
minLength={8}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p className="text-muted-foreground text-xs">En az 8 karakter</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{state.error && (
|
||||||
|
<p className="text-destructive text-sm text-center" role="alert">
|
||||||
|
{state.error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full" disabled={isPending}>
|
||||||
|
{isPending ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="size-4 animate-spin" />
|
||||||
|
Hesap oluşturuluyor...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Hesap oluştur"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="text-center text-sm text-muted-foreground">
|
||||||
|
Zaten hesabınız var mı?{" "}
|
||||||
|
<Link
|
||||||
|
href="/sign-in"
|
||||||
|
className="text-foreground font-medium underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
Giriş yap
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="text-muted-foreground *:[a]:hover:text-primary text-center text-xs text-balance *:[a]:underline *:[a]:underline-offset-4">
|
|
||||||
By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
|
<p className="text-muted-foreground text-center text-xs text-balance">
|
||||||
and <a href="#">Privacy Policy</a>.
|
Hesap oluşturarak{" "}
|
||||||
|
<Link href="#" className="underline-offset-4 hover:underline">
|
||||||
|
Kullanım Şartları
|
||||||
|
</Link>{" "}
|
||||||
|
ve{" "}
|
||||||
|
<Link href="#" className="underline-offset-4 hover:underline">
|
||||||
|
Gizlilik Politikası
|
||||||
|
</Link>
|
||||||
|
'nı kabul etmiş olursunuz.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BrandPanel() {
|
||||||
|
return (
|
||||||
|
<div className="bg-primary text-primary-foreground relative hidden md:flex md:flex-col md:justify-between overflow-hidden p-10">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 opacity-30"
|
||||||
|
style={{
|
||||||
|
backgroundImage:
|
||||||
|
"radial-gradient(circle at 20% 20%, rgba(255,255,255,0.4) 0%, transparent 40%), radial-gradient(circle at 80% 80%, rgba(255,255,255,0.25) 0%, transparent 45%)",
|
||||||
|
}}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute -top-24 -left-24 size-72 rounded-full bg-white/10 blur-3xl"
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute -bottom-32 -right-20 size-80 rounded-full bg-black/10 blur-3xl"
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="relative z-10 flex items-center gap-2">
|
||||||
|
<div className="bg-primary-foreground/15 ring-1 ring-primary-foreground/20 backdrop-blur flex size-10 items-center justify-center rounded-md">
|
||||||
|
<Logo size={22} />
|
||||||
|
</div>
|
||||||
|
<span className="text-lg font-medium">İşletmem</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10 flex flex-col gap-3">
|
||||||
|
<h2 className="text-3xl font-semibold leading-tight">
|
||||||
|
İşletmenizi büyütecek tek araç.
|
||||||
|
</h2>
|
||||||
|
<p className="text-primary-foreground/80 text-sm">
|
||||||
|
Hesap oluşturduktan sonra çalışma alanınızı kuruyor, ekibinizi davet ediyor ve hemen kullanmaya başlıyorsunuz.
|
||||||
|
</p>
|
||||||
|
<ul className="text-primary-foreground/85 mt-2 space-y-1 text-sm">
|
||||||
|
<li>• Müşteri & hizmet yönetimi</li>
|
||||||
|
<li>• Görev ve takvim</li>
|
||||||
|
<li>• Finans ve fatura</li>
|
||||||
|
</ul>
|
||||||
|
<div className="text-primary-foreground/70 mt-4 text-xs">KovakSoft tarafından</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { SignupForm1 } from "./components/signup-form-1"
|
import { redirect } from "next/navigation";
|
||||||
import { Logo } from "@/components/logo"
|
|
||||||
import Link from "next/link"
|
import { SignupForm1 } from "./components/signup-form-1";
|
||||||
|
import { getCurrentUser } from "@/lib/appwrite/server";
|
||||||
|
|
||||||
|
export default async function SignUpPage() {
|
||||||
|
const user = await getCurrentUser();
|
||||||
|
if (user) redirect("/dashboard");
|
||||||
|
|
||||||
export default function SignUpPage() {
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
<div className="bg-muted flex min-h-svh flex-col items-center justify-center p-6 md:p-10">
|
||||||
<div className="flex w-full max-w-sm flex-col gap-6">
|
<div className="w-full max-w-sm md:max-w-4xl">
|
||||||
<Link href="/" className="flex items-center gap-2 self-center font-medium">
|
|
||||||
<div className="bg-primary text-primary-foreground flex size-9 items-center justify-center rounded-md">
|
|
||||||
<Logo size={24} />
|
|
||||||
</div>
|
|
||||||
ShadcnStore
|
|
||||||
</Link>
|
|
||||||
<SignupForm1 />
|
<SignupForm1 />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { cookies } from "next/headers";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { AppwriteException, ID } from "node-appwrite";
|
||||||
|
|
||||||
|
import { APPWRITE_SESSION_COOKIE, createAdminClient, createSessionClient } from "./server";
|
||||||
|
|
||||||
|
export type AuthState = {
|
||||||
|
ok: boolean;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initial: AuthState = { ok: false };
|
||||||
|
|
||||||
|
function appwriteError(e: unknown): string {
|
||||||
|
if (e instanceof AppwriteException) {
|
||||||
|
switch (e.type) {
|
||||||
|
case "user_invalid_credentials":
|
||||||
|
return "Email veya şifre hatalı.";
|
||||||
|
case "user_blocked":
|
||||||
|
return "Hesabınız engellenmiş.";
|
||||||
|
case "user_already_exists":
|
||||||
|
case "user_email_already_exists":
|
||||||
|
return "Bu email ile zaten bir hesap var.";
|
||||||
|
case "user_password_mismatch":
|
||||||
|
return "Şifreler eşleşmiyor.";
|
||||||
|
case "general_rate_limit_exceeded":
|
||||||
|
return "Çok fazla deneme. Birkaç dakika sonra tekrar deneyin.";
|
||||||
|
default:
|
||||||
|
return e.message || "Beklenmeyen bir hata oluştu.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Bağlantı hatası. Tekrar deneyin.";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setSessionCookie(secret: string, expire: string) {
|
||||||
|
(await cookies()).set(APPWRITE_SESSION_COOKIE, secret, {
|
||||||
|
path: "/",
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
expires: new Date(expire),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signInAction(_prev: AuthState, formData: FormData): Promise<AuthState> {
|
||||||
|
const email = String(formData.get("email") ?? "").trim();
|
||||||
|
const password = String(formData.get("password") ?? "");
|
||||||
|
|
||||||
|
if (!email || !password) {
|
||||||
|
return { ok: false, error: "Email ve şifre zorunlu." };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { account } = createAdminClient();
|
||||||
|
const session = await account.createEmailPasswordSession(email, password);
|
||||||
|
await setSessionCookie(session.secret, session.expire);
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: appwriteError(e) };
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect("/dashboard");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signUpAction(_prev: AuthState, formData: FormData): Promise<AuthState> {
|
||||||
|
const name = String(formData.get("name") ?? "").trim();
|
||||||
|
const email = String(formData.get("email") ?? "").trim();
|
||||||
|
const password = String(formData.get("password") ?? "");
|
||||||
|
|
||||||
|
if (!name || !email || !password) {
|
||||||
|
return { ok: false, error: "Tüm alanlar zorunlu." };
|
||||||
|
}
|
||||||
|
if (password.length < 8) {
|
||||||
|
return { ok: false, error: "Şifre en az 8 karakter olmalı." };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { account } = createAdminClient();
|
||||||
|
await account.create(ID.unique(), email, password, name);
|
||||||
|
const session = await account.createEmailPasswordSession(email, password);
|
||||||
|
await setSessionCookie(session.secret, session.expire);
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: appwriteError(e) };
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect("/onboarding");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function forgotPasswordAction(
|
||||||
|
_prev: AuthState,
|
||||||
|
formData: FormData,
|
||||||
|
): Promise<AuthState> {
|
||||||
|
const email = String(formData.get("email") ?? "").trim();
|
||||||
|
if (!email) return { ok: false, error: "Email zorunlu." };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { account } = createAdminClient();
|
||||||
|
const recoveryUrl = `${process.env.APP_URL ?? "http://localhost:3000"}/reset-password`;
|
||||||
|
await account.createRecovery(email, recoveryUrl);
|
||||||
|
return { ok: true };
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: appwriteError(e) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signOutAction() {
|
||||||
|
try {
|
||||||
|
const { account } = await createSessionClient();
|
||||||
|
await account.deleteSession("current");
|
||||||
|
} catch {
|
||||||
|
// ignore — cookie will be cleared anyway
|
||||||
|
}
|
||||||
|
(await cookies()).delete(APPWRITE_SESSION_COOKIE);
|
||||||
|
redirect("/sign-in");
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialAuthState = initial;
|
||||||
+48
-25
@@ -1,32 +1,55 @@
|
|||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from "next/server";
|
||||||
import type { NextRequest } from 'next/server'
|
import type { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
const AUTH_COOKIE = "isletmem-session";
|
||||||
|
|
||||||
|
const PUBLIC_AUTH_PATHS = [
|
||||||
|
"/sign-in",
|
||||||
|
"/sign-in-2",
|
||||||
|
"/sign-in-3",
|
||||||
|
"/sign-up",
|
||||||
|
"/sign-up-2",
|
||||||
|
"/sign-up-3",
|
||||||
|
"/forgot-password",
|
||||||
|
"/forgot-password-2",
|
||||||
|
"/forgot-password-3",
|
||||||
|
"/reset-password",
|
||||||
|
];
|
||||||
|
|
||||||
|
const PROTECTED_PREFIXES = ["/dashboard", "/onboarding", "/settings"];
|
||||||
|
|
||||||
// This function can be marked `async` if using `await` inside
|
|
||||||
export function middleware(request: NextRequest) {
|
export function middleware(request: NextRequest) {
|
||||||
// Add custom middleware logic here
|
const { pathname } = request.nextUrl;
|
||||||
// For example: authentication, redirects, etc.
|
const session = request.cookies.get(AUTH_COOKIE)?.value;
|
||||||
|
|
||||||
// Example: Redirect /login to /auth/sign-in
|
// Legacy redirects
|
||||||
if (request.nextUrl.pathname === '/login') {
|
if (pathname === "/login") {
|
||||||
return NextResponse.redirect(new URL('/auth/sign-in', request.url))
|
return NextResponse.redirect(new URL("/sign-in", request.url));
|
||||||
}
|
}
|
||||||
|
if (pathname === "/register") {
|
||||||
// Example: Redirect /register to /auth/sign-up
|
return NextResponse.redirect(new URL("/sign-up", request.url));
|
||||||
if (request.nextUrl.pathname === '/register') {
|
|
||||||
return NextResponse.redirect(new URL('/auth/sign-up', request.url))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.next()
|
const isAuthPath = PUBLIC_AUTH_PATHS.some(
|
||||||
|
(p) => pathname === p || pathname.startsWith(`${p}/`),
|
||||||
|
);
|
||||||
|
const isProtected = PROTECTED_PREFIXES.some(
|
||||||
|
(p) => pathname === p || pathname.startsWith(`${p}/`),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isProtected && !session) {
|
||||||
|
const url = new URL("/sign-in", request.url);
|
||||||
|
url.searchParams.set("redirect", pathname);
|
||||||
|
return NextResponse.redirect(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAuthPath && session) {
|
||||||
|
return NextResponse.redirect(new URL("/dashboard", request.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// See "Matching Paths" below to learn more
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
||||||
// Match all request paths except for the ones starting with:
|
};
|
||||||
// - api (API routes)
|
|
||||||
// - _next/static (static files)
|
|
||||||
// - _next/image (image optimization files)
|
|
||||||
// - favicon.ico (favicon file)
|
|
||||||
'/((?!api|_next/static|_next/image|favicon.ico).*)',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user