Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions frontend/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
19 changes: 17 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,28 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.13",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"input-otp": "^1.4.2",
"lucide-react": "^0.544.0",
"motion": "^12.23.22",
"next": "15.5.4",
"next-themes": "^0.4.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"tailwind-merge": "^3.3.1",
"tw-animate-css": "^1.4.0"
"react-resizable-panels": "^3.0.6",
"shiki": "^3.13.0",
"tailwind-merge": "^3.3.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
Expand All @@ -34,6 +48,7 @@
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.2.0",
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
"typescript": "^5",
"typescript-eslint": "^8.44.1"
}
Expand Down
Binary file added frontend/public/images/peerprep.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
125 changes: 125 additions & 0 deletions frontend/src/app/account/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"use client";

import { useState } from "react";
import { useParams } from "next/navigation";
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";
import Navbar from "@/components/ui/nav-bar";

export default function AccountPage() {
const { id } = useParams();
console.log("User ID from URL:", id);

// State variables for user details and form inputs
const [username, setUsername] = useState("JohnDoe");
const [email, setEmail] = useState("[email protected]");
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmNewPassword, setConfirmNewPassword] = useState("");
const [editMode, setEditMode] = useState(false);
const handleSaveChanges = () => {
// empty for now
};

return (
<div>
<Navbar />

<Card className="mr-30 mt-10 ml-30 max-h-screen">
<CardHeader>
<CardTitle>Account Settings</CardTitle>
<CardDescription>Manage your account details</CardDescription>
</CardHeader>

<CardContent className="grid gap-6">
{/* Username */}
<div className="grid gap-3">
<Label htmlFor="username">Username</Label>
<div className="flex items-center">
<Input
id="username"
name="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={!editMode}
required
/>
</div>
</div>

{/* Email */}
<div className="grid gap-3">
<Label htmlFor="email">Email</Label>
<div className="flex items-center">
<Input
id="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={!editMode}
required
/>
</div>
</div>

{/* Password */}
<div className="grid gap-3 relative">
<Label htmlFor="current-password">Current Password</Label>
<Input
id="current-password"
name="current-password"
type="text"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
disabled={!editMode}
required
className="pr-10"
/>
</div>

<div className="grid gap-3">
<Label htmlFor="new-password">New Password</Label>
<Input
id="new-password"
name="new-password"
type="text"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
disabled={!editMode}
required
/>
</div>

<div className="grid gap-3">
<Label htmlFor="confirm-new-password">Confirm New Password</Label>
<Input
id="confirm-new-password"
name="confirm-new-password"
type="text"
value={confirmNewPassword}
onChange={(e) => setConfirmNewPassword(e.target.value)}
disabled={!editMode}
required
/>
</div>
</CardContent>
<div className="flex gap-2 mr-6 ml-6">
{editMode ? (
<Button variant="destructive" className="flex-1" onClick={() => setEditMode(!editMode)}>
Cancel
</Button>
) : (
<Button variant="default" className="flex-1" onClick={() => setEditMode(!editMode)}>
Edit
</Button>
)}
<Button className="flex-1" onClick={handleSaveChanges}>
Save Changes
</Button>
</div>
</Card>
</div>
);
}
141 changes: 141 additions & 0 deletions frontend/src/app/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"use client";
import { useState, useCallback, FormEvent } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import Image from "next/image";
import SignInForm from "@/components/auth/sign-in-form";
import SignUpForm from "@/components/auth/sign-up-form";
import { useRouter } from "next/navigation";
import Header from "@/components/ui/header";
import { Home } from "lucide-react";

// Types for errors
type SignUpErrors = {
username?: string;
email?: string;
password?: string;
retypePassword?: string;
};

export default function AuthPage() {
const router = useRouter();
const [signUpErrors, setSignUpErrors] = useState<SignUpErrors>({});

// Dummy submit function
const submitForm = useCallback(
async (payload: Record<string, string> & { type: "sign-in" | "sign-up" }) => {
console.log("Submitting payload:", payload);
await new Promise((res) => setTimeout(res, 700));
return { ok: true, message: "Submitted (dummy)" };
},
[],
);

// Validation
const validateSignUp = (username: string, password: string, retypePassword: string) => {
const errors: SignUpErrors = {};

if (!username || username.length < 6 || username.length > 32)
errors.username = "Username must be 6–32 characters";
else if (/^\d+$/.test(username)) errors.username = "Username cannot be only numbers";

if (!password || password.length < 12 || password.length > 64)
errors.password = "Password must be 12–64 characters";
else {
const hasUpper = /[A-Z]/.test(password);
const hasLower = /[a-z]/.test(password);
const hasDigit = /[0-9]/.test(password);
const hasSpecial = /[^\w\s]/.test(password);
if (!(hasUpper && hasLower && hasDigit && hasSpecial))
errors.password =
"Password must include 1 uppercase, 1 lowercase, 1 number, and 1 special character";
}

if (password !== retypePassword) errors.retypePassword = "Passwords do not match";

return errors;
};

const handleSignInSubmit = async (e: FormEvent, username: string, password: string) => {
e.preventDefault();
const res = await submitForm({
type: "sign-in",
username,
password,
});
if (res.ok) {
alert("Signed in (dummy)");
}
};

const handleSignUpSubmit = async (
e: FormEvent,
username: string,
email: string,
password: string,
retypePassword: string,
) => {
e.preventDefault();
const errors = validateSignUp(username, password, retypePassword);
if (Object.keys(errors).length > 0) {
setSignUpErrors(errors);
return;
}
setSignUpErrors({});
const res = await submitForm({
type: "sign-up",
username,
email,
password,
retypePassword,
});
if (res.ok) {
// Redirect to verification page (dummy)
router.push("/verify/1");
}
};

return (
<div>
<Header />
<div className="min-h-screen flex items-center justify-center gap-30 p-2 sm:p-4 md:p-8 lg:p-12 xl:p-20">
<div className="flex flex-col items-center mb-10 gap-4">
<h1 className="scroll-m-20 text-center text-4xl font-extrabold tracking-tight text-balance">
Welcome to PeerPrep
</h1>
<h3 className="scroll-m-20 text-center text-2xl font-semibold tracking-tight text-balance">
Collaborate, learn, and prepare for your technical interviews with peers
</h3>
<Image src="/images/peerprep.png" alt="PeerPrep" width={400} height={400} />
</div>
<div className="w-full max-w-sm flex-col gap-6 flex">
<Tabs defaultValue="sign-in">
<TabsList className="grid w-full grid-cols-3 items-center text-center">
<div className="flex justify-center">
<Home
className="text-muted-foreground hover:cursor-pointer text-2xl"
onClick={() => router.push("/")}
/>
</div>
<div className="flex justify-center">
<TabsTrigger value="sign-in">Sign In</TabsTrigger>
</div>
<div className="flex justify-center">
<TabsTrigger value="sign-up">Sign Up</TabsTrigger>
</div>
</TabsList>

{/* Sign In */}
<TabsContent value="sign-in">
<SignInForm onSubmit={handleSignInSubmit} />
</TabsContent>

{/* Sign Up */}
<TabsContent value="sign-up">
<SignUpForm onSubmit={handleSignUpSubmit} errors={signUpErrors} />
</TabsContent>
</Tabs>
</div>
</div>
</div>
);
}
66 changes: 66 additions & 0 deletions frontend/src/app/forgot-pw/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use client";
import { useState, useCallback } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useRouter } from "next/navigation";
import Header from "@/components/ui/header";
import MessageDialog from "@/components/ui/message-dialog";

export default function ForgotPasswordPage() {
const router = useRouter();
const [email, setEmail] = useState("");
const [isEmailSent, setIsEmailSent] = useState(false);

const handleSendEmail = useCallback(async () => {
setIsEmailSent(true);
}, []);

const handleBackToLogin = useCallback(() => {
router.push("/auth");
}, [router]);

return (
<div>
<Header />
<h1 className="mt-40 text-center text-3xl sm:text-4xl font-extrabold tracking-tight">
Forgot your password?
</h1>
<div className="mt-20 flex items-center justify-center ">
<div className="w-full max-w-md rounded-2xl bg-white shadow-lg sm:p-8 space-y-6">
<p className="text-center text-sm sm:text-base font-medium text-gray-600">
Enter your email to receive a password reset link.
</p>
<div className="space-y-4">
<form
onSubmit={(e) => {
e.preventDefault();
handleSendEmail();
}}
>
<Input
type="email"
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
required
/>
<Button className="mt-4 w-full" type="submit">
Send Email
</Button>
</form>
<Button variant="outline" className="w-full" onClick={handleBackToLogin}>
Back to login
</Button>
</div>
</div>
</div>

{/* Email sent confirmation */}
<MessageDialog
open={isEmailSent}
setOpen={setIsEmailSent}
title={`Email sent to ${email}`}
description="Please check your email for the password reset link."
/>
</div>
);
}
Loading