feat: implement auth flow and app initialization

This commit is contained in:
2026-06-15 18:41:27 +05:30
parent 9de65d0ce6
commit 8d4ff49633
29 changed files with 551 additions and 535 deletions

View File

@@ -3,6 +3,7 @@
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { GuestGuard } from '@/guards';
import { useLoginForm } from '@/hooks/useLoginForm';
import { Loader2 } from 'lucide-react';
import Image from 'next/image';
@@ -11,93 +12,95 @@ export default function LoginPage() {
const { register, handleSubmit, errors, isLoading } = useLoginForm();
return (
<main className="grid min-h-screen bg-background text-foreground lg:grid-cols-[1.05fr_0.95fr]">
<section className="relative hidden overflow-hidden border-r border-border/60 lg:block">
<Image
src="/background.png"
alt=""
fill
priority
className="object-cover"
sizes="55vw"
/>
<div className="absolute inset-0 bg-background/35" />
<div className="absolute inset-x-0 bottom-0 p-10">
<div className="max-w-lg space-y-3">
<p className="text-sm font-medium uppercase tracking-[0.18em] text-primary">
VisionRoad
</p>
<h1 className="text-4xl font-semibold tracking-normal text-foreground">
Road intelligence for faster inspection decisions
</h1>
<p className="text-sm leading-6 text-muted-foreground">
Review projects, uploads, detections, and chainage insights from one secure
workspace.
</p>
<GuestGuard>
<main className="grid min-h-screen bg-background text-foreground lg:grid-cols-[1.05fr_0.95fr]">
<section className="relative hidden overflow-hidden border-r border-border/60 lg:block">
<Image
src="/background.png"
alt=""
fill
priority
className="object-cover"
sizes="55vw"
/>
<div className="absolute inset-0 bg-background/35" />
<div className="absolute inset-x-0 bottom-0 p-10">
<div className="max-w-lg space-y-3">
<p className="text-sm font-medium uppercase tracking-[0.18em] text-primary">
VisionRoad
</p>
<h1 className="text-4xl font-semibold tracking-normal text-foreground">
Road intelligence for faster inspection decisions
</h1>
<p className="text-sm leading-6 text-muted-foreground">
Review projects, uploads, detections, and chainage insights from one secure
workspace.
</p>
</div>
</div>
</div>
</section>
</section>
<section className="flex min-h-screen items-center justify-center px-6 py-10">
<div className="w-full max-w-sm space-y-7">
<header className="space-y-2">
<p className="text-sm font-medium text-muted-foreground">VisionRoad</p>
<h2 className="text-2xl font-semibold tracking-normal">Sign in</h2>
<p className="text-sm text-muted-foreground">
Use your email and password to continue.
</p>
</header>
<section className="flex min-h-screen items-center justify-center px-6 py-10">
<div className="w-full max-w-sm space-y-7">
<header className="space-y-2">
<p className="text-sm font-medium text-muted-foreground">VisionRoad</p>
<h2 className="text-2xl font-semibold tracking-normal">Sign in</h2>
<p className="text-sm text-muted-foreground">
Use your email and password to continue.
</p>
</header>
<form className="space-y-5" onSubmit={handleSubmit}>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="admin@visionroad.ai"
autoComplete="email"
disabled={isLoading}
aria-invalid={!!errors.email}
{...register('email', {
required: 'Email is required',
})}
/>
{errors.email && (
<p className="text-sm text-destructive">{errors.email.message}</p>
)}
</div>
<form className="space-y-5" onSubmit={handleSubmit}>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="admin@visionroad.ai"
autoComplete="email"
disabled={isLoading}
aria-invalid={!!errors.email}
{...register('email', {
required: 'Email is required',
})}
/>
{errors.email && (
<p className="text-sm text-destructive">{errors.email.message}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="Enter password"
autoComplete="current-password"
disabled={isLoading}
aria-invalid={!!errors.password}
{...register('password', {
required: 'Password is required',
})}
/>
{errors.password && (
<p className="text-sm text-destructive">{errors.password.message}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="Enter password"
autoComplete="current-password"
disabled={isLoading}
aria-invalid={!!errors.password}
{...register('password', {
required: 'Password is required',
})}
/>
{errors.password && (
<p className="text-sm text-destructive">{errors.password.message}</p>
)}
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="size-4 animate-spin" />
Signing in
</>
) : (
'Sign in'
)}
</Button>
</form>
</div>
</section>
</main>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="size-4 animate-spin" />
Signing in
</>
) : (
'Sign in'
)}
</Button>
</form>
</div>
</section>
</main>
</GuestGuard>
);
}

View File

@@ -3,6 +3,7 @@ import { AppSidebar } from '@/components/app-sidebar';
import { ModeToggle } from '@/components/mode-toogle';
import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';
import { Separator } from '@/components/ui/separator';
import { AuthGuard } from '@/guards';
import React from 'react';
const ModulesLayout = ({
@@ -11,23 +12,25 @@ const ModulesLayout = ({
children: React.ReactNode;
}>) => {
return (
<SidebarProvider className="bg-transparent overflow-hidden">
<AppSidebar className="bg-transparent" />
<main className="flex flex-1 flex-col w-full h-screen overflow-hidden bg-transparent">
<div className="flex-1 overflow-auto">
{/* Centering container wrapper */}
<div className="max-w-380 mx-auto w-full flex flex-col min-h-full">
<div className="flex gap-3 items-center sticky top-0 bg-background/30 backdrop-blur-xl z-30 py-3 px-6 border-b border-border/10">
<SidebarTrigger />
<ModeToggle />
<Separator orientation="vertical" className="mx-2 h-4" />
<BreadcrumbBasic />
<AuthGuard>
<SidebarProvider className="bg-transparent overflow-hidden">
<AppSidebar className="bg-transparent" />
<main className="flex flex-1 flex-col w-full h-screen overflow-hidden bg-transparent">
<div className="flex-1 overflow-auto">
{/* Centering container wrapper */}
<div className="max-w-380 mx-auto w-full flex flex-col min-h-full">
<div className="flex gap-3 items-center sticky top-0 bg-background/30 backdrop-blur-xl z-30 py-3 px-6 border-b border-border/10">
<SidebarTrigger />
<ModeToggle />
<Separator orientation="vertical" className="mx-2 h-4" />
<BreadcrumbBasic />
</div>
<div className="p-6 flex-1">{children}</div>
</div>
<div className="p-6 flex-1">{children}</div>
</div>
</div>
</main>
</SidebarProvider>
</main>
</SidebarProvider>
</AuthGuard>
);
};

View File

@@ -1,6 +0,0 @@
import { authOptions } from '@/lib/auth/authOptions';
import NextAuth from 'next-auth';
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

View File

@@ -2,7 +2,7 @@ import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import './globals.css';
import { ThemeProvider } from '@/providers/ThemeProvider';
import AuthProvider from '@/providers/AuthProvider';
import AppInitializer from '@/providers/AppInitializer';
import QueryProvider from '@/providers/QueryProvider';
import { Toaster } from '@/components/ui/sonner';
import { BackgroundGradient } from '@/components/background-gradient';
@@ -37,13 +37,13 @@ export default function RootLayout({
disableTransitionOnChange
>
<QueryProvider>
<AuthProvider>
<AppInitializer>
<div className="relative min-h-screen isolate">
<BackgroundGradient />
{children}
</div>
<Toaster position="top-center" />
</AuthProvider>
</AppInitializer>
</QueryProvider>
</ThemeProvider>
</body>