Skip to content

Commit 65c28db

Browse files
Merge pull request #24 from Nyasa-Roy/nyasa/statistics
Feat: Added the login page with integration
2 parents f1cb691 + a246f34 commit 65c28db

File tree

14 files changed

+793
-5
lines changed

14 files changed

+793
-5
lines changed

app/dashboard/page.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Dashboard from "@/components/Dashboard/Dashboard";
2+
export default function dashboard() {
3+
return <Dashboard />;
4+
}

app/globals.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,8 @@ button:active {
212212
}
213213
.font-Nunito{
214214
font-family: "Nunito";
215+
}
216+
217+
.stroke {
218+
-webkit-text-stroke: 3.95px black;
215219
}

app/schemas/api.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export interface ApiResponse {
1+
export interface ApiResponse<T=unknown> {
2+
status: string;
23
message: string;
3-
data: unknown;
4+
data?: unknown;
5+
error?:string;
46
}

app/schemas/forms/login.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as z from "zod";
2+
3+
export const loginFormSchema = z.object({
4+
email: z
5+
.string()
6+
.email({
7+
message: "Invalid email address",
8+
})
9+
.trim(),
10+
password: z
11+
.string()
12+
.min(6, "Password must not be lesser than 6 characters")
13+
.max(20, "Password must not be greater than 20 characters")
14+
.trim(),
15+
});

app/services/login.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
import { type loginFormSchema } from "@/schemas/forms/login";
1+
import { type loginFormSchema } from "../schemas/forms/login";
22
import { type z } from "zod";
33
import api from ".";
44
import { ApiResponse } from "../schemas/api";
55
import { handleAPIError } from "@/lib/error";
6+
interface LoginData {
7+
username: string;
8+
round: number;
9+
score: number;
10+
}
611

712
export async function login(body: z.infer<typeof loginFormSchema>) {
813
try {
9-
const { data } = await api.post<ApiResponse>(`login`, body);
10-
return data.message;
14+
const { data } = await api.post<ApiResponse<LoginData>>(`login`, body);
15+
return data;
1116
} catch (e) {
1217
throw handleAPIError(e);
1318
}

components/Login.tsx

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
"use client";
2+
3+
import React from "react";
4+
import Image from "next/image";
5+
import { useForm } from "react-hook-form";
6+
import { zodResolver } from "@hookform/resolvers/zod";
7+
import * as z from "zod";import { login } from "@/app/services/login";
8+
import { useRouter } from "next/navigation";
9+
import toast,{Toaster} from "react-hot-toast";
10+
import Button from "@/components/ui/Button";
11+
import {
12+
Form,
13+
FormControl,
14+
FormField,
15+
FormItem,
16+
FormLabel,
17+
FormMessage,
18+
} from "@/components/ui/form";
19+
import { Input } from "@/components/ui/input";
20+
import { ApiError } from "next/dist/server/api-utils";
21+
22+
// validation schema
23+
const formSchema = z.object({
24+
email: z.string().email("*Please enter valid email address"),
25+
password: z.string(),
26+
});
27+
28+
29+
export default function Login() {
30+
const form = useForm<z.infer<typeof formSchema>>({
31+
resolver: zodResolver(formSchema),
32+
defaultValues: {
33+
email: "",
34+
password: "",
35+
},
36+
});
37+
const router = useRouter();
38+
async function onSubmit(values: z.infer<typeof formSchema>) {
39+
try{
40+
const res = await login(values);
41+
if(res.status==="success"){
42+
const { status, message, data } = res;
43+
toast.success("Login successful. Welcome, Chef!");
44+
router.push("/dashboard");
45+
}
46+
else{
47+
const { status, error }=res;
48+
toast.error("An error occurred. Login failed.")
49+
console.error("Login failed:", error);
50+
}
51+
}
52+
// catch(error:unknown){
53+
// // console.log("Request failed with status code: ",error.response.status);
54+
// // toast.error("An error occurred. Login failed.")
55+
// // console.error("Login failed:", error);
56+
// if (error instanceof ApiError) {
57+
// const statusCode = error.status;
58+
59+
// if (error.status === 404) {
60+
// // Email not found
61+
// form.setError("email", { type: "manual", message: "*Please enter valid email address" });
62+
// } else if (statusCode === 409) {
63+
// // Wrong password
64+
// form.setError("password", { type: "manual", message: "*Please enter correct password" });
65+
// } else {
66+
// // Other errors
67+
// toast.error(error.response?.data?.message || "An error occurred");}
68+
// }
69+
// else {
70+
// // Not an axios error
71+
// toast.error("Unexpected error occurred");
72+
// console.error(error);
73+
// }
74+
// }
75+
catch (error: unknown) {
76+
const err = error as { status?: number; message: string };
77+
78+
if (err.message === "User not found") {
79+
form.setError("email", { type: "manual", message: "*Please enter valid email address" });
80+
} else if (err.message === "Invalid password") {
81+
form.setError("password", { type: "manual", message: "*Please enter correct password" });
82+
} else {
83+
toast.error(err.message || "An error occurred");
84+
}
85+
}
86+
}
87+
88+
return (
89+
<div className="relative flex h-screen w-full items-center justify-center text-white overflow-hidden">
90+
{/* Background */}
91+
<div className="absolute inset-0 -z-10 bg-black">
92+
<Image
93+
src="/loginBG.svg"
94+
alt="Background"
95+
width={1920}
96+
height={1080}
97+
className="absolute w-full h-auto"
98+
/>
99+
<Image
100+
src="/loginEllipse.svg"
101+
alt="Ellipse"
102+
fill
103+
className="absolute top-0 left-0 z-0 width-[1692.04px] height-[1447.47px]"
104+
/>
105+
</div>
106+
107+
<div className="absolute top-6 left-1/2 -translate-x-1/2 text-center z-20">
108+
<h2 className="text-[30px] font-[Nulshock] tracking-widest">
109+
CODECHEF PRESENTS
110+
</h2>
111+
</div>
112+
113+
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 text-center z-20">
114+
<p className="text-[38px] font-[Nulshock] tracking-widest text-[#D9D9D9]">10 YEARS. ONE LEGACY.</p>
115+
</div>
116+
117+
{/* Left Section */}
118+
<div className="flex flex-col items-center justify-center w-1/2 p-8 text-center z-10">
119+
<div className="flex flex-col items-center gap-2 ml-[50px]">
120+
<div className="relative inline-block">
121+
<h1 className="absolute inset-0 text-[96.75px] font-[Nulshock] text-[#125128] z-0 translate-x-[-6px] translate-y-[6px] stroke">
122+
COOK OFF
123+
</h1>
124+
125+
<div className="absolute top-[45] left-[0] w-[332.7px] h-[69.11px] opacity-32 z-[20] pointer-events-none bg-gradient-to-r from-[#D9D9D9] to-[#737373] blur-[54.2px]" />
126+
<div className="absolute top-[45] left-[350] w-[332.7px] h-[69.11px] opacity-32 z-[20] pointer-events-none bg-gradient-to-r from-[#D9D9D9] to-[#737373] blur-[54.2px]" />
127+
<h1 className="relative text-[96.75px] font-[Nulshock] text-[#b6ab98] z-10 stroke">
128+
COOK OFF
129+
</h1>
130+
</div>
131+
132+
<div className="flex items-center">
133+
<div className="relative inline-block">
134+
<div className="absolute top-[45] left-[0] w-[267.54px] h-[77px] opacity-32 z-[20] pointer-events-none bg-[#137735] blur-[54.2px]" />
135+
<h1 className="absolute inset-0 text-[96.75px] font-[Nulshock] text-[#125128] z-0 translate-x-[-6px] translate-y-[6px] stroke">
136+
10.0
137+
</h1>
138+
139+
<h1 className="relative text-[96.75px] font-[Nulshock] text-[#137735] z-10 stroke">
140+
<span className="relative inline-block">
141+
10.0
142+
<Image
143+
src="/chef-hat.svg"
144+
alt="Chef Hat"
145+
width={112.52}
146+
height={108.42}
147+
className="absolute -top-9 -right-12 -z-10"
148+
/>
149+
</span>
150+
</h1>
151+
</div>
152+
</div>
153+
154+
</div>
155+
156+
{/* <div className="absolute bottom-6 left-6 flex flex-col items-start z-20">
157+
<p className="text-xs text-gray-400">Co-Hosted by</p>
158+
<Image
159+
src="/musclemind.svg"
160+
alt="Musclemind Logo"
161+
width={120}
162+
height={40}
163+
/>
164+
</div> */}
165+
</div>
166+
167+
<Toaster position="top-right" reverseOrder={false} />
168+
169+
{/* Right Section - Login Form */}
170+
<div className="w-1/2 flex items-center justify-center z-10">
171+
<div className="w-[460px] h-[536px] p-8 shadow-lg rounded-[32px] bg-[#19231E] border border-[#6B6262]">
172+
<h2 className="text-center text-[26.51px] font-[Nulshock] text-white mt-[75.46px]">
173+
START COOKING
174+
</h2>
175+
176+
<Form {...form}>
177+
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col items-center gap-4">
178+
{/* Email */}
179+
<FormField
180+
control={form.control}
181+
name="email"
182+
render={({ field }) => (
183+
<FormItem>
184+
<FormControl>
185+
<Input
186+
placeholder="Enter Email"
187+
{...field}
188+
className="bg-[#B7AB98] w-[351.66px] h-[60.08px] radius-[8.84px] mt-[28.28px] mr-[42.41px] ml-[42.42px] text-black placeholder:text-black font-[Inter] placeholder:font-[Inter] font-medium placeholder:font-medium"
189+
/>
190+
</FormControl>
191+
<FormMessage className="text-[#FF8989] ml-[42.42px] font-[Inter] text-[13px]"/>
192+
</FormItem>
193+
)}
194+
/>
195+
196+
{/* Password */}
197+
<FormField
198+
control={form.control}
199+
name="password"
200+
render={({ field }) => (
201+
<FormItem>
202+
<FormControl>
203+
<Input
204+
type="password"
205+
placeholder="Enter Password"
206+
{...field}
207+
className="bg-[#B7AB98] w-[351.66px] h-[60.08px] radius-[8.84px] mt-[28.28px] mr-[42.41px] ml-[42.42px] text-black placeholder:text-black font-[Inter] placeholder:font-[Inter] font-medium placeholder:font-medium"
208+
/>
209+
</FormControl>
210+
<FormMessage className="text-[#FF8989] ml-[42.42px] font-[Inter] text-[13px]"/>
211+
</FormItem>
212+
)}
213+
/>
214+
215+
<Button
216+
type="submit"
217+
className="w-[148px] h-[53px] rounded-[9px] !mt-[68.92px] !bg-gradient-to-r from-[#32CA67] via-[#26AD55] to-[#26AD55] text-white font-[Ballega] flex items-center justify-center"
218+
>
219+
Log In
220+
</Button>
221+
</form>
222+
</Form>
223+
</div>
224+
</div>
225+
</div>
226+
);
227+
}

0 commit comments

Comments
 (0)