Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# gemini
/images
7 changes: 7 additions & 0 deletions app/auth/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function AuthLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen">
{children}
</div>
)
}
151 changes: 151 additions & 0 deletions app/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"use client";

import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useLogin } from "@/hooks/api/use-login"
import { LoginRequest, loginRequestSchema } from "@/types/kubb/gen";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Field, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { BackgroundGradientAnimation } from "@/components/ui/background-gradient-animation";

export default function AuthPage() {
const { mutate, isPending } = useLogin();

const form = useForm<LoginRequest>({
resolver: zodResolver(loginRequestSchema),
defaultValues: {
username: "",
password: ""
}
});

function onSubmit(values: LoginRequest) {
mutate({
body: values,
});
}

return (
<div className="h-screen grid grid-cols-1 lg:grid-cols-10">

{/* Hero Section */}
<div className="relative overflow-hidden hidden lg:block lg:col-span-7">
<BackgroundGradientAnimation
gradientBackgroundStart="rgb(44, 58, 208)"
gradientBackgroundEnd="rgb(30, 40, 150)"
firstColor="60, 100, 255"
secondColor="80, 130, 255"
thirdColor="40, 80, 220"
fourthColor="0, 0, 0, 0"
fifthColor="0, 0, 0, 0"
pointerColor="60, 100, 255"
size="50%"
containerClassName="h-full w-full"
>
<div className="absolute z-50 inset-0 flex flex-col justify-end p-16 text-white pointer-events-none">
<div className="space-y-6 mb-16">
<h1 className="text-5xl font-playfair font-semibold leading-tight">
Data-driven decisions for better education
</h1>
<p className="text-xl opacity-90 font-medium max-w-lg">
Powered by artificial intelligence to deliver real-time insights and smart recommendations.
</p>
</div>
<div className="text-sm opacity-70">
2026 Faculytics. All rights reserved.
</div>
</div>
</BackgroundGradientAnimation>
</div>

{/* Form Section */}
<div className="bg-brand-neutral flex flex-col col-span-1 lg:col-span-3">
<div className="p-8 text-center lg:text-left">
<h3 className="text-xl font-playfair font-semibold">Faculytics 2.0</h3>
</div>

<div className="flex grow items-center justify-center px-12">
<div className="w-full max-w-md space-y-6">
<div className="text-center">
<h2 className="font-bold font-playfair text-3xl">Welcome</h2>
<p className="text-sm mt-2 text-muted-foreground">
Sign in using your student portal account
</p>
</div>

<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4"
>
<FieldGroup>
<Controller
name="username"
control={form.control}
render={({ field, fieldState }) => (
<Field>
<FieldLabel htmlFor="username">
Username
</FieldLabel>
<Input
{...field}
id="username"
type="text"
placeholder="john@gmail.com"
/>
{fieldState.error && (
<FieldError>
{fieldState.error.message}
</FieldError>
)}
</Field>
)}
/>

<Controller
name="password"
control={form.control}
render={({ field, fieldState }) => (
<Field>
<div className="flex justify-between">
<FieldLabel htmlFor="password">
Password
</FieldLabel>
<FieldLabel className="cursor-pointer">
Forgot Password?
</FieldLabel>
</div>
<Input
{...field}
id="password"
type="password"
/>
{fieldState.error && (
<FieldError>
{fieldState.error.message}
</FieldError>
)}
</Field>
)}
/>
</FieldGroup>

<Button
type="submit"
className="w-full bg-brand-yellow hover:bg-brand-yellow/90 text-black font-semibold cursor-pointer"
disabled={isPending}
>
{isPending ? "Logging in..." : "Login"}
</Button>
</form>
<div className="flex gap-2 text-sm justify-center">
<p className="text-muted-foreground">Need an account?</p>
<a className="cursor-pointer">Contact your administrator</a>
</div>
</div>
</div>
</div>
</div>
)
}
53 changes: 52 additions & 1 deletion app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--font-playfair: var(--font-playfair);
--color-brand-blue: var(--brand-blue);
--color-brand-yellow: var(--brand-yellow);
--color-brand-neutral: var(--brand-neutral);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
Expand Down Expand Up @@ -45,10 +49,57 @@
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);


/* Gradient Animation */
--animate-first: moveVertical 60s ease infinite;
--animate-second: moveInCircle 40s reverse infinite;
--animate-third: moveInCircle 80s linear infinite;
--animate-fourth: moveHorizontal 80s ease infinite;
--animate-fifth: moveInCircle 40s ease infinite;

@keyframes moveHorizontal {
0% {
transform: translateX(-50%) translateY(-10%);
}
50% {
transform: translateX(50%) translateY(10%);
}
100% {
transform: translateX(-50%) translateY(-10%);
}
}
@keyframes moveInCircle {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes moveVertical {
0% {
transform: translateY(-50%);
}
50% {
transform: translateY(50%);
}
100% {
transform: translateY(-50%);
}
}
}
/* End of Gradient Animation */


:root {
--radius: 0.625rem;
--brand-blue: oklch(0.428 0.25 264.53);
--brand-yellow: oklch(0.898 0.196 91.13);
--brand-neutral: oklch(0.982 0.005 84.15);
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
Expand Down Expand Up @@ -123,4 +174,4 @@
body {
@apply bg-background text-foreground;
}
}
}
10 changes: 8 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Geist, Geist_Mono, Playfair_Display } from "next/font/google";
import "./globals.css";
import AppProvider from "@/components/providers/app-provider";

Expand All @@ -13,6 +13,12 @@ const geistMono = Geist_Mono({
subsets: ["latin"],
});

const playfair = Playfair_Display({
variable: "--font-playfair",
subsets: ["latin"],
weight: ["400", "500", "600", "700"],
});

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
Expand All @@ -26,7 +32,7 @@ export default function RootLayout({
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
className={`${geistSans.variable} ${geistMono.variable} ${playfair.variable} antialiased`}
>
<AppProvider>
{children}
Expand Down
7 changes: 7 additions & 0 deletions bun.lock

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

4 changes: 3 additions & 1 deletion components.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
"registries": {
"@aceternity": "https://ui.aceternity.com/registry/{name}.json"
}
}
Loading