add user authentication

This commit is contained in:
2025-12-16 23:08:09 +03:00
parent cb0b14799e
commit b72974ef62
19 changed files with 543 additions and 10 deletions

View File

@@ -1,5 +1,7 @@
import type { Metadata } from "next";
import "./globals.css";
import { Providers } from "@/app/providers";
import { ReactNode } from "react";
export const metadata: Metadata = {
title: "archive.local",
@@ -9,11 +11,13 @@ export const metadata: Metadata = {
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
children: ReactNode;
}>) {
return (
<html lang="en" className="h-full">
<body className="h-full flex flex-col">{children}</body>
</html>
<Providers>
<html lang="en" className="h-full">
<body className="h-full flex flex-col">{children}</body>
</html>
</Providers>
);
}

View File

@@ -0,0 +1,63 @@
"use client";
import { LoginData, useLoginMutation } from "@/api/auth/useLoginMutation";
import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import dayjs from "dayjs";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
export const LoginForm = () => {
const router = useRouter();
const mutation = useLoginMutation();
const form = useForm<LoginData>({
defaultValues: {
email: "",
password: "",
},
});
const onSubmit = form.handleSubmit((data) => {
mutation.mutate(data, {
onSuccess(resp) {
const expDate = dayjs().add(7, "days").toString();
document.cookie = `access_token=${resp.access_token}; SameSite=None; Secure; Expires=${expDate}; Path=/`;
router.push("/");
},
});
});
return (
<form
className="max-w-[500px] w-full bg-white rounded-lg p-4 shadow-2xl"
onSubmit={onSubmit}
>
<div className="flex flex-col gap-2">
<Input
type="email"
placeholder="johndoe@gmail.com"
label="Email"
{...form.register("email", { required: true })}
/>
<Input
type="password"
placeholder="******"
label="Password"
{...form.register("password", { required: true })}
/>
</div>
<Button type="submit" className="w-full mt-6">
Sign in
</Button>
<p className="mt-2 text-sm">
Don&apos;t have an account? Create one here{" "}
<Link href="/register" className="underline hover:no-underline">
here
</Link>
.
</p>
</form>
);
};

View File

@@ -0,0 +1,18 @@
import { LoginForm } from "@/app/login/LoginForm";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export default async function Login() {
const c = await cookies();
const token = c.get("access_token");
if (token) {
throw redirect("/");
}
return (
<div className="h-full flex flex-col justify-center items-center bg-neutral-100">
<h2 className="mb-10 text-5xl font-medium">archive.local</h2>
<LoginForm />
</div>
);
}

View File

@@ -1,4 +1,14 @@
export default function Home() {
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export default async function Home() {
const c = await cookies();
const token = c.get("access_token");
if (!token) {
throw redirect("/login");
}
return (
<div>
<h1>archive.local</h1>

21
web/src/app/providers.tsx Normal file
View File

@@ -0,0 +1,21 @@
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactNode, useState } from "react";
export const Providers = ({ children }: { children: ReactNode }) => {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
}),
);
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};

View File

@@ -0,0 +1,66 @@
"use client";
import {
RegisterData,
useRegisterMutation,
} from "@/api/auth/useRegisterMutation";
import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import dayjs from "dayjs";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
export const RegisterForm = () => {
const router = useRouter();
const mutation = useRegisterMutation();
const form = useForm<RegisterData>({
defaultValues: {
email: "",
password: "",
},
});
const onSubmit = form.handleSubmit((data) => {
mutation.mutate(data, {
onSuccess(resp) {
const expDate = dayjs().add(7, "days").toString();
document.cookie = `access_token=${resp.access_token}; SameSite=None; Secure; Expires=${expDate}; Path=/`;
router.push("/");
},
});
});
return (
<form
className="max-w-[500px] w-full bg-white rounded-lg p-4 shadow-2xl"
onSubmit={onSubmit}
>
<div className="flex flex-col gap-2">
<Input
type="email"
placeholder="johndoe@gmail.com"
label="Email"
{...form.register("email", { required: true })}
/>
<Input
type="password"
placeholder="******"
label="Password"
{...form.register("password", { required: true })}
/>
</div>
<Button type="submit" className="w-full mt-6">
Sign up
</Button>
<p className="mt-2 text-sm">
Already have an account? Login{" "}
<Link href="/login" className="underline hover:no-underline">
here
</Link>
.
</p>
</form>
);
};

View File

@@ -0,0 +1,18 @@
import { RegisterForm } from "@/app/register/RegisterForm";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export default async function Register() {
const c = await cookies();
const token = c.get("access_token");
if (token) {
throw redirect("/");
}
return (
<div className="h-full flex flex-col justify-center items-center bg-neutral-100">
<h2 className="mb-10 text-5xl font-medium">archive.local</h2>
<RegisterForm />
</div>
);
}