Skip to content

Commit

Permalink
Add login and registration page to the frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
m-danya committed Nov 2, 2024
1 parent e12e548 commit 10f002b
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 21 deletions.
9 changes: 9 additions & 0 deletions frontend/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { LoginForm } from "@/components/login-form"

export default function Page() {
return (
<div className="flex h-screen w-full items-center justify-center px-4">
<LoginForm />
</div>
)
}
23 changes: 15 additions & 8 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
"use client";
import { AppSidebar } from "@/components/app-sidebar";
import { NavActions } from "@/components/nav-actions";
import { TaskList } from "@/components/tasks/task-list";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbList,
BreadcrumbPage,
} from "@/components/ui/breadcrumb";
import { Separator } from "@/components/ui/separator";
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
import useUser from "@/hooks/use-user";
import { useRouter } from "next/navigation";
import { useEffect } from "react";

export default function Page() {
const router = useRouter();
const { user, loading, loggedIn, mutate } = useUser();

useEffect(() => {
console.log(user, loading, loggedIn, mutate);
if (!loggedIn) {
router.push("/login");
}
}, [loggedIn]);
if (!loggedIn) return "";

return (
<SidebarProvider>
<AppSidebar />
Expand Down
139 changes: 139 additions & 0 deletions frontend/components/login-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import useUser from "@/hooks/use-user";

import Link from "next/link";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

export function LoginForm() {
const router = useRouter();
const { mutate } = useUser();

const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

const handleLogin = async () => {
try {
const body = new URLSearchParams();
body.append("username", email || "");
body.append("password", password || "");

const response = await fetch("/api/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
credentials: "include",
body: body.toString(),
});

if (!response.ok) {
const errorData = await response.json();
alert("Login failed: " + errorData.detail);
return;
}

await mutate();

if (typeof window !== "undefined" && window.history.length > 1) {
router.back();
} else {
router.push("/");
}
} catch (error) {
console.error("An unexpected error occurred:", error);
alert("An unexpected error occurred");
}
};

const handleRegister = async () => {
try {
const response = await fetch("/api/auth/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify({
email,
password,
}),
});

if (!response.ok) {
const errorData = await response.json();
alert("Registration failed: " + errorData.detail);
return;
}

await mutate();

handleLogin();
} catch (error) {
console.error("An unexpected error occurred:", error);
alert("An unexpected error occurred");
}
};

return (
<Card className="mx-auto w-96">
<CardHeader>
<CardTitle className="text-2xl">Login or Sign Up</CardTitle>
</CardHeader>
<CardContent>
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="[email protected]"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
{/* <Link href="#" className="ml-auto inline-block text-sm underline">
Forgot your password?
</Link> */}
</div>
<Input
id="password"
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<Button type="button" className="w-full" onClick={handleLogin}>
Login
</Button>
<Button variant="outline" className="w-full" onClick={handleRegister}>
Sign up
</Button>
</div>
{/* <div className="mt-4 text-center text-sm">
Don&apos;t have an account?{" "}
<Link href="#" className="underline">
Sign up
</Link>
</div> */}
</CardContent>
</Card>
);
}
5 changes: 3 additions & 2 deletions frontend/components/nav-sections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import React from "react";

import { useSections } from "@/hooks/use-sections";
import Link from "next/link";

export function NavSections() {
const { sections, rootSectionId, isLoading, isError } = useSections();
Expand Down Expand Up @@ -123,15 +124,15 @@ const TreeElement = React.forwardRef<
return mainContent;
} else {
return (
<a
<Link
ref={ref}
href={`/section/${item.id}`}
title={item.title}
{...props}
className=""
>
{mainContent}
</a>
</Link>
);
}
}
Expand Down
76 changes: 76 additions & 0 deletions frontend/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from "react"

import { cn } from "@/lib/utils"

const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"

const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"

const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"

const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"

const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"

const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
26 changes: 26 additions & 0 deletions frontend/components/ui/label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client"

import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)

const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName

export { Label }
4 changes: 3 additions & 1 deletion frontend/hooks/fetcher.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export const fetcher = async (url: string) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Error fetching ${url}: ${response.statusText}`);
const error = new Error(`Error fetching ${url}: ${response.statusText}`);
(error as any).status = response.status;
throw error;
}
return response.json();
};
17 changes: 17 additions & 0 deletions frontend/hooks/use-user.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import useSWR from "swr";
import { fetcher } from "@/hooks/fetcher";

export default function useUser() {
const { data, mutate, error } = useSWR("/api/auth/me", fetcher);

const loading = !data && !error;
console.log(error);
const loggedIn = !(error && error.status === 401);

return {
loading,
loggedIn,
user: data,
mutate,
};
}
23 changes: 23 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
Expand Down
Loading

0 comments on commit 10f002b

Please sign in to comment.