Initial commit

This commit is contained in:
2026-06-17 13:51:22 +02:00
commit 4898bf7142
81 changed files with 12522 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
"use client"
import { useState } from "react"
import { PageHeader } from "@/components/shared/page-header"
import { StatCard } from "@/components/dashboard/stat-card"
import { RecentLeadsTable } from "@/components/dashboard/recent-leads-table"
import { LeadStatusChart } from "@/components/dashboard/lead-status-chart"
import { LeadsPerMonthChart } from "@/components/dashboard/leads-per-month-chart"
import { dashboardStats } from "@/data/dashboard"
import {
Users,
UserPlus,
PhoneCall,
Clock,
CheckCircle2,
TrendingUp,
ListFilter,
} from "lucide-react"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export default function DashboardPage() {
const stats = dashboardStats
const [period, setPeriod] = useState("6months")
const statCards = [
{ title: "Total Leads", value: stats.totalLeads, icon: Users, variant: "blue" as const, description: "All time" },
{ title: "Open Leads", value: stats.openLeads, icon: UserPlus, variant: "blue" as const, description: "Needs attention" },
{ title: "Contacted", value: stats.contactedLeads, icon: PhoneCall, variant: "amber" as const, description: "In conversation" },
{ title: "Pending", value: stats.pendingLeads, icon: Clock, variant: "purple" as const, description: "Awaiting decision" },
{ title: "Closed", value: stats.closedLeads, icon: CheckCircle2, variant: "emerald" as const, description: "Won" },
{ title: "Conversion Rate", value: `${stats.conversionRate}%`, icon: TrendingUp, variant: "emerald" as const, description: `${stats.closedLeads} of ${stats.totalLeads} leads` },
]
return (
<div className="space-y-6">
<PageHeader title="Dashboard" description="Overview of your sales pipeline">
<Select value={period} onValueChange={setPeriod}>
<SelectTrigger className="h-9 w-[160px]">
<ListFilter className="mr-2 h-4 w-4" />
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="7days">Last 7 days</SelectItem>
<SelectItem value="30days">Last 30 days</SelectItem>
<SelectItem value="6months">Last 6 months</SelectItem>
<SelectItem value="12months">Last 12 months</SelectItem>
</SelectContent>
</Select>
</PageHeader>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
{statCards.map((card, i) => (
<StatCard key={card.title} {...card} index={i} />
))}
</div>
<div className="grid gap-6 lg:grid-cols-2">
<LeadStatusChart data={stats.statusDistribution} />
<LeadsPerMonthChart data={stats.leadsPerMonth} />
</div>
<RecentLeadsTable leads={stats.recentLeads} />
</div>
)
}
+11
View File
@@ -0,0 +1,11 @@
"use client"
import { AppShell } from "@/components/layout/app-shell"
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return <AppShell>{children}</AppShell>
}
+154
View File
@@ -0,0 +1,154 @@
"use client"
import Link from "next/link"
import { motion } from "framer-motion"
import { PageHeader } from "@/components/shared/page-header"
import { LeadDetailsCard } from "@/components/leads/lead-details-card"
import { LeadStatusBadge } from "@/components/leads/lead-status-badge"
import { NoteTimeline } from "@/components/notes/note-timeline"
import { NoteForm } from "@/components/notes/note-form"
import { Button } from "@/components/ui/button"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { getLeadById } from "@/data/leads"
import { getNotesByLeadId } from "@/data/notes"
import { ArrowLeft, Edit, ExternalLink } from "lucide-react"
export default function LeadDetailsPage({ params }: { params: { id: string } }) {
const lead = getLeadById(params.id)
const leadNotes = lead ? getNotesByLeadId(lead.id) : []
if (!lead) {
return (
<div className="flex items-center justify-center h-[60vh]">
<div className="text-center">
<h2 className="text-2xl font-bold">Lead not found</h2>
<p className="mt-2 text-muted-foreground">The lead you are looking for does not exist.</p>
<Link href="/leads" className="mt-4 inline-block">
<Button variant="outline">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Leads
</Button>
</Link>
</div>
</div>
)
}
return (
<div className="space-y-6">
<Link
href="/leads"
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
<ArrowLeft className="h-4 w-4" />
Back to leads
</Link>
<PageHeader
title={
<div className="flex items-center gap-3">
<span>{lead.companyName}</span>
<LeadStatusBadge status={lead.status} />
</div>
}
description={lead.contactName}
>
<div className="flex items-center gap-3">
<Select defaultValue={lead.status}>
<SelectTrigger className="h-9 w-[140px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="open">Open</SelectItem>
<SelectItem value="contacted">Contacted</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
<SelectItem value="closed">Closed</SelectItem>
<SelectItem value="ignored">Ignored</SelectItem>
</SelectContent>
</Select>
<Button className="gap-2">
<Edit className="h-4 w-4" />
Edit
</Button>
</div>
</PageHeader>
<div className="grid gap-6 lg:grid-cols-3">
<div className="space-y-6 lg:col-span-2">
<LeadDetailsCard lead={lead} />
<div className="space-y-6">
<NoteForm leadId={lead.id} />
<NoteTimeline notes={leadNotes} />
</div>
</div>
<div className="space-y-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.1 }}
>
<div className="rounded-lg border bg-card">
<div className="border-b px-6 py-4">
<h3 className="font-semibold">Activity</h3>
</div>
<div className="divide-y">
{[
{ action: "Lead created", date: lead.createdAt, user: lead.assignedUser?.name || "System" },
...leadNotes.slice(0, 3).map((n) => ({
action: "Note added",
date: n.createdAt,
user: n.authorName,
})),
].map((activity, i) => (
<div key={i} className="px-6 py-3">
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-primary/60 shrink-0" />
<p className="text-sm">{activity.action}</p>
</div>
<p className="mt-0.5 text-xs text-muted-foreground pl-4">
by {activity.user} &middot; {new Date(activity.date).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</p>
</div>
))}
</div>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<div className="rounded-lg border bg-card">
<div className="border-b px-6 py-4">
<h3 className="font-semibold">Quick Actions</h3>
</div>
<div className="p-4 space-y-2">
<Button variant="outline" className="w-full justify-start" size="sm">
<ExternalLink className="mr-2 h-4 w-4" />
Send Email
</Button>
<Button variant="outline" className="w-full justify-start" size="sm">
<ExternalLink className="mr-2 h-4 w-4" />
Call Lead
</Button>
</div>
</div>
</motion.div>
</div>
</div>
</div>
)
}
+59
View File
@@ -0,0 +1,59 @@
"use client"
import { useState, useMemo } from "react"
import { PageHeader } from "@/components/shared/page-header"
import { LeadsTable } from "@/components/leads/leads-table"
import { LeadsTableToolbar } from "@/components/leads/leads-table-toolbar"
import { LeadFormDialog } from "@/components/leads/lead-form-dialog"
import { leads } from "@/data/leads"
export default function LeadsPage() {
const [search, setSearch] = useState("")
const [statusFilter, setStatusFilter] = useState("all")
const [createOpen, setCreateOpen] = useState(false)
const filteredLeads = useMemo(() => {
let result = leads
if (search) {
const q = search.toLowerCase()
result = result.filter(
(l) =>
l.companyName.toLowerCase().includes(q) ||
l.contactName.toLowerCase().includes(q) ||
l.email.toLowerCase().includes(q) ||
l.phone.includes(q)
)
}
if (statusFilter && statusFilter !== "all") {
result = result.filter((l) => l.status === statusFilter)
}
return result
}, [search, statusFilter])
return (
<div className="space-y-4">
<PageHeader
title="Leads"
description="Manage and track your sales leads"
/>
<div className="rounded-lg border bg-card">
<div className="p-4">
<LeadsTableToolbar
search={search}
onSearchChange={setSearch}
statusFilter={statusFilter}
onStatusFilterChange={setStatusFilter}
onCreateClick={() => setCreateOpen(true)}
/>
</div>
<LeadsTable data={filteredLeads} />
</div>
<LeadFormDialog open={createOpen} onOpenChange={setCreateOpen} />
</div>
)
}
+43
View File
@@ -0,0 +1,43 @@
"use client"
import { PageHeader } from "@/components/shared/page-header"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { CompanySettingsForm } from "@/components/settings/company-settings-form"
import { UserPreferencesForm } from "@/components/settings/user-preferences-form"
import { ThemeSettings } from "@/components/settings/theme-settings"
import { NotificationSettings } from "@/components/settings/notification-settings"
import { Building2, User, Palette, Bell } from "lucide-react"
export default function SettingsPage() {
const tabs = [
{ value: "company", label: "Company", icon: Building2, component: CompanySettingsForm },
{ value: "preferences", label: "Preferences", icon: User, component: UserPreferencesForm },
{ value: "theme", label: "Theme", icon: Palette, component: ThemeSettings },
{ value: "notifications", label: "Notifications", icon: Bell, component: NotificationSettings },
]
return (
<div className="space-y-4">
<PageHeader
title="Settings"
description="Manage your CRM configuration and preferences"
/>
<Tabs defaultValue="company" className="space-y-6">
<TabsList>
{tabs.map((tab) => (
<TabsTrigger key={tab.value} value={tab.value} className="gap-2">
<tab.icon className="h-4 w-4" />
{tab.label}
</TabsTrigger>
))}
</TabsList>
{tabs.map((tab) => (
<TabsContent key={tab.value} value={tab.value}>
<tab.component />
</TabsContent>
))}
</Tabs>
</div>
)
}
+80
View File
@@ -0,0 +1,80 @@
"use client"
import { useState } from "react"
import { PageHeader } from "@/components/shared/page-header"
import { UsersTable } from "@/components/users/users-table"
import { UserFormDialog } from "@/components/users/user-form-dialog"
import { Button } from "@/components/ui/button"
import { users } from "@/data/users"
import { Plus, Users as UsersIcon } from "lucide-react"
export default function UsersPage() {
const [createOpen, setCreateOpen] = useState(false)
return (
<div className="space-y-4">
<PageHeader
title="Users"
description="Manage team members and their roles"
>
<Button className="gap-2" onClick={() => setCreateOpen(true)}>
<Plus className="h-4 w-4" />
Add User
</Button>
</PageHeader>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4 mb-6">
<div className="rounded-lg border bg-card p-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-500/10">
<UsersIcon className="h-5 w-5 text-blue-600 dark:text-blue-400" />
</div>
<div>
<p className="text-2xl font-bold">{users.length}</p>
<p className="text-xs text-muted-foreground">Total Users</p>
</div>
</div>
</div>
<div className="rounded-lg border bg-card p-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-emerald-500/10">
<UsersIcon className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
</div>
<div>
<p className="text-2xl font-bold">{users.filter((u) => u.active).length}</p>
<p className="text-xs text-muted-foreground">Active</p>
</div>
</div>
</div>
<div className="rounded-lg border bg-card p-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-purple-500/10">
<UsersIcon className="h-5 w-5 text-purple-600 dark:text-purple-400" />
</div>
<div>
<p className="text-2xl font-bold">{users.filter((u) => u.role === "admin").length}</p>
<p className="text-xs text-muted-foreground">Admins</p>
</div>
</div>
</div>
<div className="rounded-lg border bg-card p-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-amber-500/10">
<UsersIcon className="h-5 w-5 text-amber-600 dark:text-amber-400" />
</div>
<div>
<p className="text-2xl font-bold">{users.filter((u) => u.role === "sales").length}</p>
<p className="text-xs text-muted-foreground">Sales</p>
</div>
</div>
</div>
</div>
<div className="rounded-lg border bg-card">
<UsersTable data={users} />
</div>
<UserFormDialog open={createOpen} onOpenChange={setCreateOpen} />
</div>
)
}
+75
View File
@@ -0,0 +1,75 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 210 40% 98%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--sidebar: 222.2 84% 4.9%;
--sidebar-foreground: 210 40% 98%;
--sidebar-primary: 217.2 91.2% 59.8%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 217.2 32.6% 17.5%;
--sidebar-accent-foreground: 210 40% 98%;
--sidebar-border: 217.2 32.6% 17.5%;
--sidebar-ring: 224.3 76.3% 48%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
--sidebar: 222.2 84% 4.9%;
--sidebar-foreground: 210 40% 98%;
--sidebar-primary: 217.2 91.2% 59.8%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 217.2 32.6% 17.5%;
--sidebar-accent-foreground: 210 40% 98%;
--sidebar-border: 217.2 32.6% 17.5%;
--sidebar-ring: 224.3 76.3% 48%;
}
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
font-family: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
}
}
+32
View File
@@ -0,0 +1,32 @@
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import { ThemeProvider } from "@/providers/theme-provider"
import { Toaster } from "@/components/ui/sonner"
import "./globals.css"
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
})
export const metadata: Metadata = {
title: "Coastal IT - CRM",
description: "Customer Relationship Management System",
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`${inter.variable} min-h-screen antialiased`}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
<Toaster />
</ThemeProvider>
</body>
</html>
)
}
+187
View File
@@ -0,0 +1,187 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { motion } from "framer-motion"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Checkbox } from "@/components/ui/checkbox"
import { COMPANY_NAME } from "@/lib/constants"
import { Eye, EyeOff, Loader2 } from "lucide-react"
export default function LoginPage() {
const router = useRouter()
const [email, setEmail] = useState("admin@coastalit.com")
const [password, setPassword] = useState("")
const [showPassword, setShowPassword] = useState(false)
const [loading, setLoading] = useState(false)
const [remember, setRemember] = useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
await new Promise((resolve) => setTimeout(resolve, 1200))
router.push("/dashboard")
}
return (
<div className="flex min-h-screen">
{/* Left - Brand panel */}
<div className="relative hidden flex-1 flex-col justify-between bg-gradient-to-br from-[#1e3a8a] via-[#2563eb] to-[#3b82f6] p-12 text-white lg:flex">
<div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iMC4wNSI+PHBhdGggZD0iTTM2IDM0djItSDI0di0yaDEyek0zNiAyNHYySDI0di0yaDEyeiIvPjwvZz48L2c+PC9zdmc+')] opacity-30" />
<div className="relative z-10">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-white/20 backdrop-blur-sm">
<span className="text-lg font-bold">C</span>
</div>
<span className="text-xl font-semibold">{COMPANY_NAME}</span>
</div>
</div>
<div className="relative z-10 max-w-lg">
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-4xl font-bold leading-tight"
>
Your Agency&apos;s Growth Starts Here
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="mt-4 text-lg text-white/80"
>
Manage leads, track conversions, and grow your web development business with our CRM.
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="mt-8 rounded-xl border border-white/10 bg-white/5 p-5 backdrop-blur-sm"
>
<p className="text-sm italic text-white/90">
&ldquo;This CRM transformed how we manage our sales pipeline. We&apos;ve seen a 40% increase in lead conversion.&rdquo;
</p>
<div className="mt-3 flex items-center gap-3">
<div className="h-10 w-10 rounded-full bg-white/20" />
<div>
<p className="text-sm font-medium">Marcus Johnson</p>
<p className="text-xs text-white/60">Sales Lead, Coastal IT</p>
</div>
</div>
</motion.div>
</div>
<div className="relative z-10 text-sm text-white/60">
&copy; 2026 {COMPANY_NAME}. All rights reserved.
</div>
</div>
{/* Right - Login form */}
<div className="flex flex-1 items-center justify-center p-8">
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4 }}
className="w-full max-w-sm space-y-8"
>
{/* Mobile logo */}
<div className="flex flex-col items-center gap-3 lg:hidden">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary">
<span className="text-xl font-bold text-primary-foreground">C</span>
</div>
<span className="text-xl font-semibold">{COMPANY_NAME}</span>
</div>
<div className="space-y-2 text-center lg:text-left">
<h1 className="text-2xl font-bold tracking-tight">Welcome back</h1>
<p className="text-sm text-muted-foreground">
Sign in to your account to continue
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="name@company.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
autoComplete="email"
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="password">Password</Label>
<button
type="button"
className="text-xs text-primary hover:underline"
>
Forgot password?
</button>
</div>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
autoComplete="current-password"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="remember"
checked={remember}
onCheckedChange={(checked) => setRemember(checked as boolean)}
/>
<label
htmlFor="remember"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Remember me
</label>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{loading ? "Signing in..." : "Sign in"}
</Button>
</form>
<p className="text-center text-xs text-muted-foreground">
By signing in, you agree to our{" "}
<button type="button" className="text-primary hover:underline">
Terms of Service
</button>{" "}
and{" "}
<button type="button" className="text-primary hover:underline">
Privacy Policy
</button>
</p>
</motion.div>
</div>
</div>
)
}
+30
View File
@@ -0,0 +1,30 @@
"use client"
import Link from "next/link"
import { motion } from "framer-motion"
import { Button } from "@/components/ui/button"
import { FileQuestion } from "lucide-react"
export default function NotFound() {
return (
<div className="flex min-h-screen items-center justify-center p-4">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="flex flex-col items-center text-center"
>
<div className="flex h-24 w-24 items-center justify-center rounded-full bg-muted">
<FileQuestion className="h-12 w-12 text-muted-foreground" />
</div>
<h1 className="mt-6 text-4xl font-bold">404</h1>
<p className="mt-2 text-lg text-muted-foreground">Page not found</p>
<p className="mt-1 text-sm text-muted-foreground">
The page you are looking for does not exist.
</p>
<Link href="/dashboard" className="mt-6">
<Button>Go Home</Button>
</Link>
</motion.div>
</div>
)
}
+5
View File
@@ -0,0 +1,5 @@
import { redirect } from "next/navigation"
export default function RootPage() {
redirect("/login")
}