AI slop goes brr
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,7 +11,7 @@ node_modules
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
.cursorignore
|
||||
# Testing
|
||||
coverage
|
||||
|
||||
|
||||
80
apps/admin/app/(main)/jobs/[jobId]/page.tsx
Normal file
80
apps/admin/app/(main)/jobs/[jobId]/page.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { db, jobs, companies, applications, students } from '@workspace/db';
|
||||
import { eq } from '@workspace/db/drizzle';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@workspace/ui/components/card';
|
||||
import { Table, TableBody, TableHead, TableHeader, TableRow, TableCell } from '@workspace/ui/components/table';
|
||||
|
||||
interface JobPageProps {
|
||||
params: { jobId: string };
|
||||
}
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export default async function JobDetailPage({ params }: JobPageProps) {
|
||||
const jobId = Number(params.jobId);
|
||||
if (isNaN(jobId)) notFound();
|
||||
|
||||
const jobRes = await db.select().from(jobs).where(eq(jobs.id, jobId)).limit(1);
|
||||
if (jobRes.length === 0) notFound();
|
||||
const job = jobRes[0];
|
||||
|
||||
const companyRes = await db.select().from(companies).where(eq(companies.id, job.companyId)).limit(1);
|
||||
const company = companyRes[0];
|
||||
|
||||
const applicants = await db
|
||||
.select({
|
||||
applicationId: applications.id,
|
||||
status: applications.status,
|
||||
firstName: students.firstName,
|
||||
lastName: students.lastName,
|
||||
email: students.email,
|
||||
})
|
||||
.from(applications)
|
||||
.leftJoin(students, eq(applications.studentId, students.id))
|
||||
.where(eq(applications.jobId, jobId));
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-10 space-y-10">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{job.title}</CardTitle>
|
||||
<CardDescription>Company: {company?.name ?? 'Unknown'}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<p className="text-sm"><strong>Location:</strong> {job.location}</p>
|
||||
<p className="text-sm"><strong>Salary:</strong> {job.salary}</p>
|
||||
<p className="text-sm"><strong>Deadline:</strong> {job.applicationDeadline.toLocaleDateString()}</p>
|
||||
<p className="whitespace-pre-line mt-4">{job.description}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">Students Applied</h2>
|
||||
{applicants.length === 0 ? (
|
||||
<p className="text-muted-foreground text-sm">No applications yet.</p>
|
||||
) : (
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{applicants.map((a) => (
|
||||
<TableRow key={a.applicationId}>
|
||||
<TableCell>{`${a.firstName ?? ''} ${a.lastName ?? ''}`.trim()}</TableCell>
|
||||
<TableCell>{a.email}</TableCell>
|
||||
<TableCell className="capitalize">{a.status}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
73
apps/admin/app/(main)/jobs/page.tsx
Normal file
73
apps/admin/app/(main)/jobs/page.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { db, jobs, companies } from '@workspace/db';
|
||||
import { eq } from '@workspace/db/drizzle';
|
||||
import Link from 'next/link';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@workspace/ui/components/card';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
async function getAllJobsWithCompany() {
|
||||
const allJobs = await db.select().from(jobs);
|
||||
const allCompanies = await db.select().from(companies);
|
||||
return allJobs.map(job => ({
|
||||
...job,
|
||||
company: allCompanies.find(c => c.id === job.companyId) || null,
|
||||
}));
|
||||
}
|
||||
|
||||
export default async function JobsListPage() {
|
||||
const jobsWithCompany = await getAllJobsWithCompany();
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-10 space-y-8">
|
||||
<h1 className="text-3xl font-bold mb-6">All Jobs</h1>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{jobsWithCompany.length === 0 && (
|
||||
<p className="text-muted-foreground">No jobs found.</p>
|
||||
)}
|
||||
{jobsWithCompany.map((job) => (
|
||||
<Card key={job.id} className="flex flex-col bg-white text-black border border-gray-200">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<span>{job.title}</span>
|
||||
<span className="ml-auto text-xs px-2 py-1 rounded bg-gray-100 border text-gray-600">
|
||||
{job.active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
<span>Company: {job.company?.name ?? 'Unknown'}</span>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<img src={job.company?.imageURL} alt={job.company?.name} className="w-10 h-10 rounded object-cover border" />
|
||||
<div>
|
||||
<div className="font-semibold">{job.company?.name}</div>
|
||||
<div className="text-xs text-gray-500">{job.company?.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<div className="text-sm"><strong>Location:</strong> {job.location}</div>
|
||||
<div className="text-sm"><strong>Salary:</strong> {job.salary}</div>
|
||||
<div className="text-sm"><strong>Deadline:</strong> {job.applicationDeadline.toLocaleDateString()}</div>
|
||||
<div className="text-sm"><strong>Min CGPA:</strong> {job.minCGPA}</div>
|
||||
<div className="text-sm"><strong>Min SSC:</strong> {job.minSSC}</div>
|
||||
<div className="text-sm"><strong>Min HSC:</strong> {job.minHSC}</div>
|
||||
<div className="text-sm"><strong>Allow Dead KT:</strong> {job.allowDeadKT ? 'Yes' : 'No'}</div>
|
||||
<div className="text-sm"><strong>Allow Live KT:</strong> {job.allowLiveKT ? 'Yes' : 'No'}</div>
|
||||
<div className="text-sm"><strong>Job Link:</strong> <a href={job.link} target="_blank" rel="noopener noreferrer" className="underline text-primary">{job.link}</a></div>
|
||||
<div className="text-sm"><strong>Description:</strong> <span className="whitespace-pre-line">{job.description}</span></div>
|
||||
<div className="text-xs text-gray-400 mt-2">Created: {job.createdAt.toLocaleDateString()} | Updated: {job.updatedAt.toLocaleDateString()}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<div className="p-4 pt-0 mt-auto flex gap-2">
|
||||
<Link href={`/jobs/${job.id}`}>
|
||||
<Button variant="outline" className="bg-white text-primary border-primary">View Details</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,28 +9,64 @@ import {
|
||||
} from '@workspace/ui/components/navigation-menu';
|
||||
import Link from 'next/link';
|
||||
|
||||
const navLinks = [
|
||||
{ href: '/', label: 'Home', icon: '🏠' },
|
||||
{ href: '/students', label: 'Students', icon: '🎓' },
|
||||
{ href: '/jobs', label: 'Jobs', icon: '💼' },
|
||||
];
|
||||
|
||||
export default function MainLayout({ children }: { children: React.ReactNode }) {
|
||||
// Helper to check active link (client-side only)
|
||||
const isActive = (href: string) => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
if (href === '/') return window.location.pathname === '/';
|
||||
return window.location.pathname.startsWith(href);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<header className="flex h-16 items-center justify-between border-b bg-background px-4 md:px-6">
|
||||
<nav>
|
||||
<div className="flex min-h-screen bg-background text-foreground">
|
||||
{/* Sidebar */}
|
||||
<aside className="hidden md:flex flex-col w-64 p-6 gap-8">
|
||||
<div className="sticky top-8">
|
||||
<div className="flex flex-col gap-6 rounded-2xl shadow-xl bg-sidebar border border-sidebar-border p-6">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<img src="/favicon.ico" alt="Logo" className="w-10 h-10" />
|
||||
<span className="font-extrabold text-xl tracking-tight text-sidebar-primary">Admin Portal</span>
|
||||
</div>
|
||||
<div className="text-xs uppercase font-semibold text-muted-foreground mb-2 tracking-widest pl-1">Navigation</div>
|
||||
<nav className="flex flex-col gap-2">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="flex items-center gap-3 px-4 py-2 rounded-xl font-medium transition-colors text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 focus-visible:ring-sidebar-accent data-[active=true]:bg-primary data-[active=true]:text-primary-foreground"
|
||||
data-active={typeof window !== 'undefined' && isActive(link.href)}
|
||||
>
|
||||
<span className="text-lg">{link.icon}</span>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
{/* Main content */}
|
||||
<div className="flex-1 flex flex-col min-h-screen">
|
||||
<header className="md:hidden flex items-center justify-between h-16 border-b bg-background px-4">
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="/">Home</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href="/students">Students</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
{navLinks.map((link) => (
|
||||
<NavigationMenuItem key={link.href}>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link href={link.href}>{link.label}</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
))}
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</nav>
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
</header>
|
||||
<main className="flex-1 bg-background text-foreground p-4 md:p-10">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,159 @@
|
||||
import Studs from '@/components/studs';
|
||||
import { db, students } from '@workspace/db';
|
||||
import { auth, signOut } from '@/auth';
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@workspace/ui/components/card';
|
||||
import { Input } from '@workspace/ui/components/input';
|
||||
import { Textarea } from '@workspace/ui/components/textarea';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import Link from 'next/link';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { db, companies, jobs } from '@workspace/db';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogDescription } from '@workspace/ui/components/dialog';
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@workspace/ui/components/accordion';
|
||||
import { Badge } from '@workspace/ui/components/badge';
|
||||
|
||||
async function getStudents() {
|
||||
// -----------------------
|
||||
// Server Actions
|
||||
// -----------------------
|
||||
|
||||
async function createCompany(formData: FormData) {
|
||||
'use server';
|
||||
const s = await db.select().from(students);
|
||||
console.log(s);
|
||||
const name = String(formData.get('name') ?? '').trim();
|
||||
const email = String(formData.get('email') ?? '').trim();
|
||||
const link = String(formData.get('link') ?? '').trim();
|
||||
const description = String(formData.get('description') ?? '').trim() || 'N/A';
|
||||
const imageURL = String(formData.get('imageURL') ?? '').trim() || 'https://via.placeholder.com/200x200?text=Company';
|
||||
|
||||
if (!name) return;
|
||||
|
||||
await db.insert(companies).values({ name, email, link, description, imageURL });
|
||||
revalidatePath('/');
|
||||
}
|
||||
|
||||
async function logOut() {
|
||||
async function createJob(formData: FormData) {
|
||||
'use server';
|
||||
await signOut();
|
||||
const companyId = Number(formData.get('companyId'));
|
||||
const title = String(formData.get('title') ?? '').trim();
|
||||
const link = String(formData.get('jobLink') ?? '').trim();
|
||||
const description = String(formData.get('jobDescription') ?? '').trim() || 'N/A';
|
||||
const location = String(formData.get('location') ?? '').trim() || 'N/A';
|
||||
const imageURL = String(formData.get('jobImageURL') ?? '').trim() || 'https://via.placeholder.com/100x100?text=Job';
|
||||
const salary = String(formData.get('salary') ?? '').trim() || 'N/A';
|
||||
const deadlineRaw = formData.get('deadline');
|
||||
const applicationDeadline = deadlineRaw ? new Date(String(deadlineRaw)) : new Date();
|
||||
|
||||
if (!companyId || !title) return;
|
||||
|
||||
await db.insert(jobs).values({
|
||||
companyId,
|
||||
title,
|
||||
link,
|
||||
description,
|
||||
location,
|
||||
imageURL,
|
||||
salary,
|
||||
applicationDeadline,
|
||||
active: true,
|
||||
});
|
||||
revalidatePath('/');
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
const session = await auth();
|
||||
// -----------------------
|
||||
// Component Helpers
|
||||
// -----------------------
|
||||
|
||||
async function getDashboardData() {
|
||||
const comps = await db.select().from(companies);
|
||||
const allJobs = await db.select().from(jobs);
|
||||
|
||||
return comps.map((comp) => ({
|
||||
...comp,
|
||||
jobs: allJobs.filter((j) => j.companyId === comp.id),
|
||||
}));
|
||||
}
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const data = await getDashboardData();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-svh">
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<h1 className="text-2xl font-bold">Hello admin {session?.user?.name}</h1>
|
||||
<Studs action={getStudents} logOut={logOut} />
|
||||
</div>
|
||||
<div className="container mx-auto py-10 space-y-10">
|
||||
<section className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-primary">Companies Dashboard</h1>
|
||||
<p className="text-muted-foreground mt-1">Manage companies and their job openings.</p>
|
||||
</div>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="bg-primary text-primary-foreground hover:bg-primary/90 transition-colors">Add New Company</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add a new company</DialogTitle>
|
||||
<DialogDescription>
|
||||
Fill in the details below to add a new company to the portal.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form action={createCompany} className="grid grid-cols-1 gap-4 py-4">
|
||||
<Input name="name" placeholder="Company name" required />
|
||||
<Input name="email" placeholder="Contact email" type="email" required />
|
||||
<Input name="link" placeholder="Website / careers link" />
|
||||
<Input name="imageURL" placeholder="Image URL" />
|
||||
<Textarea name="description" placeholder="Short description" />
|
||||
<Button type="submit" className="w-fit">Add Company</Button>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</section>
|
||||
|
||||
<section className="space-y-6">
|
||||
{data.length === 0 && <Card className="p-10 text-muted-foreground text-center shadow-lg border-border bg-card">No companies yet. Add your first company to get started!</Card>}
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{data.map((company) => (
|
||||
<Card key={company.id} className="flex flex-col shadow-xl border border-border bg-card rounded-2xl overflow-hidden">
|
||||
<CardHeader className="p-6">
|
||||
<CardTitle className="flex items-center gap-4">
|
||||
<img src={company.imageURL} alt={company.name} className="w-14 h-14 rounded-xl border-2 border-border object-cover" />
|
||||
<div>
|
||||
<h3 className="font-bold text-lg">{company.name}</h3>
|
||||
<a href={company.link} target="_blank" rel="noopener noreferrer" className="text-sm text-primary hover:underline">{company.link}</a>
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-6 pb-2 space-y-3">
|
||||
<h4 className="font-semibold text-muted-foreground">Open Positions</h4>
|
||||
{company.jobs.length === 0 && <p className="text-sm text-muted-foreground/80">No jobs yet.</p>}
|
||||
{company.jobs.map((job) => (
|
||||
<Link key={job.id} href={`/jobs/${job.id}`} className="flex justify-between items-center p-3 rounded-lg hover:bg-background transition-colors border border-transparent hover:border-border">
|
||||
<span>{job.title}</span>
|
||||
<Badge variant={job.active ? "secondary" : "outline"}>{job.active ? "Active" : "Inactive"}</Badge>
|
||||
</Link>
|
||||
))}
|
||||
</CardContent>
|
||||
<CardFooter className="mt-auto p-0">
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="add-job">
|
||||
<AccordionTrigger className="px-6 text-primary hover:text-primary/90 font-semibold">
|
||||
Add New Job
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="p-6 bg-background border-t">
|
||||
<form action={createJob} className="flex flex-col w-full gap-3">
|
||||
<input type="hidden" name="companyId" value={company.id} />
|
||||
<Input name="title" placeholder="Job title" required />
|
||||
<Input name="jobLink" placeholder="Job link" />
|
||||
<Input name="location" placeholder="Location" />
|
||||
<Input name="salary" placeholder="Salary" />
|
||||
<Input name="deadline" type="date" />
|
||||
<Textarea name="jobDescription" placeholder="Job description" />
|
||||
<Button type="submit" size="sm" className="bg-accent text-accent-foreground hover:bg-accent/90 transition-colors self-start">Add Job</Button>
|
||||
</form>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -1,26 +1,61 @@
|
||||
import { columns, Student } from './columns';
|
||||
import { DataTable } from './data-table';
|
||||
import { db, students } from '@workspace/db';
|
||||
import { Input } from '@workspace/ui/components/input';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { eq } from '@workspace/db/drizzle';
|
||||
import { Card } from '@workspace/ui/components/card';
|
||||
|
||||
async function getData(): Promise<Student[]> {
|
||||
const data = await db.select().from(students);
|
||||
return data;
|
||||
}
|
||||
|
||||
async function addStudent(formData: FormData) {
|
||||
'use server';
|
||||
const email = String(formData.get('email') ?? '').trim();
|
||||
if (!email) return;
|
||||
|
||||
const exists = await db.select().from(students).where(eq(students.email, email)).limit(1);
|
||||
if (exists.length === 0) {
|
||||
await db.insert(students).values({ email });
|
||||
}
|
||||
revalidatePath('/students');
|
||||
}
|
||||
|
||||
async function StudentsTable() {
|
||||
const data = await getData();
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-10">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Students</h1>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{data.length} {data.length === 1 ? 'student' : 'students'} total
|
||||
<div className="space-y-8">
|
||||
{/* Add Student */}
|
||||
<Card className="p-6 shadow-md border border-border bg-card">
|
||||
<h2 className="text-2xl font-bold mb-4 text-primary">Add Student</h2>
|
||||
<form action={addStudent} className="flex gap-2 items-end">
|
||||
<Input name="email" type="email" placeholder="Student email" className="max-w-sm" required />
|
||||
<Button type="submit" className="bg-primary text-primary-foreground hover:bg-primary/90 transition-colors">Add Student</Button>
|
||||
</form>
|
||||
</Card>
|
||||
|
||||
{/* Students Table */}
|
||||
<Card className="p-6 shadow-md border border-border bg-card">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-2xl font-semibold tracking-tight text-accent">Students</h1>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{data.length} {data.length === 1 ? 'student' : 'students'} total
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable columns={columns} data={data} />
|
||||
{data.length === 0 ? (
|
||||
<div className="text-center text-muted-foreground">No students yet. Add your first student above!</div>
|
||||
) : (
|
||||
<DataTable columns={columns} data={data} />
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
{/* Toast placeholder for feedback */}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,19 +8,34 @@ async function logIn() {
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-svh">
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<form action={logIn}>
|
||||
<Button type="submit" variant="outline" className="w-full h-12">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-primary/0 via-primary/10 to-primary/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700 ease-out pointer-events-none" />
|
||||
<div className="relative min-h-svh flex items-center justify-center overflow-hidden bg-gradient-to-br from-blue-100 via-red-100 to-pink-100 transition-colors duration-500">
|
||||
{/* Animated floating shapes */}
|
||||
<div className="pointer-events-none absolute inset-0 z-0">
|
||||
<div className="absolute top-10 left-1/4 w-32 h-32 bg-rose-200/40 rounded-full blur-2xl animate-float-slow" />
|
||||
<div className="absolute bottom-20 right-1/4 w-40 h-40 bg-blue-200/30 rounded-full blur-2xl animate-float-medium" />
|
||||
<div className="absolute top-1/2 left-1/2 w-24 h-24 bg-red-300/30 rounded-full blur-2xl animate-float-fast" />
|
||||
</div>
|
||||
<div className="relative z-10 backdrop-blur-md bg-white/70 rounded-2xl shadow-2xl p-10 flex flex-col items-center gap-8 border border-white/30 max-w-sm w-full transition-all duration-300 hover:shadow-[0_0_32px_4px_rgba(239,68,68,0.25)] hover:border-red-400/60">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
{/* Animated logo */}
|
||||
<img src="/favicon.ico" alt="Logo" className="w-14 h-14 mb-2 drop-shadow-lg animate-bounce-slow" />
|
||||
<h1 className="text-2xl font-bold text-gray-800 tracking-tight">Placement Portal Admin</h1>
|
||||
<p className="text-gray-500 text-sm text-center">Sign in to manage placements and students</p>
|
||||
<p className="text-xs text-red-500 font-semibold italic mt-1 animate-fade-in">Empower your journey. Shape the future.</p>
|
||||
</div>
|
||||
<form action={logIn} className="w-full">
|
||||
<Button type="submit" variant="outline" className="w-full h-12 relative overflow-hidden group rounded-lg shadow-md hover:shadow-lg transition-all focus:ring-2 focus:ring-red-400">
|
||||
<span className="absolute inset-0 bg-gradient-to-r from-red-200/0 via-red-200/20 to-red-200/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700 ease-out pointer-events-none" />
|
||||
<img
|
||||
src="https://static.cdnlogo.com/logos/g/35/google-icon.svg"
|
||||
alt="Google logo"
|
||||
className="w-5 h-5 transition-transform duration-200"
|
||||
className="w-5 h-5 mr-2 inline-block align-middle"
|
||||
/>
|
||||
<span className="relative z-10 font-medium transition-colors duration-200 group-hover:text-foreground">
|
||||
<span className="relative z-10 font-medium transition-colors duration-200 group-hover:text-red-700 group-hover:drop-shadow-md">
|
||||
Sign in with Google
|
||||
</span>
|
||||
{/* Button ripple effect */}
|
||||
<span className="absolute left-1/2 top-1/2 w-0 h-0 bg-red-300/40 rounded-full opacity-0 group-active:opacity-100 group-active:w-32 group-active:h-32 group-active:animate-ripple -translate-x-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.1.1",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.13",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.0",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
|
||||
56
packages/ui/src/components/accordion.tsx
Normal file
56
packages/ui/src/components/accordion.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
|
||||
const Accordion = AccordionPrimitive.Root
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn("border-b-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AccordionItem.displayName = "AccordionItem"
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>span:last-child]:rotate-180",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<span className="h-4 w-4 shrink-0 transition-transform duration-200">▼</span>
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
))
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
))
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||
36
packages/ui/src/components/badge.tsx
Normal file
36
packages/ui/src/components/badge.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
121
packages/ui/src/components/dialog.tsx
Normal file
121
packages/ui/src/components/dialog.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
|
||||
const Dialog = DialogPrimitive.Root
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal
|
||||
|
||||
const DialogClose = DialogPrimitive.Close
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<span className="text-2xl font-thin" aria-hidden="true">×</span>
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogFooter.displayName = "DialogFooter"
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
}
|
||||
@@ -13,74 +13,39 @@
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--background: oklch(0.98 0.003 240);
|
||||
--foreground: oklch(0.12 0.008 240);
|
||||
--card: oklch(0.96 0.004 240);
|
||||
--card-foreground: oklch(0.12 0.008 240);
|
||||
--background: oklch(0.98 0.01 220); /* Soft blue-tinted white */
|
||||
--foreground: oklch(0.16 0.01 240); /* Slightly deeper text */
|
||||
--card: oklch(0.96 0.01 220); /* Slight blue card */
|
||||
--card-foreground: oklch(0.16 0.01 240);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.12 0.008 240);
|
||||
--primary: oklch(0.35 0.18 240);
|
||||
--primary-foreground: oklch(0.98 0.003 240);
|
||||
--secondary: oklch(0.88 0.008 240);
|
||||
--secondary-foreground: oklch(0.12 0.008 240);
|
||||
--muted: oklch(0.92 0.006 240);
|
||||
--muted-foreground: oklch(0.4 0.01 240);
|
||||
--accent: oklch(0.42 0.22 10);
|
||||
--accent-foreground: oklch(0.98 0.003 240);
|
||||
--destructive: oklch(0.45 0.25 15);
|
||||
--destructive-foreground: oklch(0.98 0.003 240);
|
||||
--border: oklch(0.86 0.008 240);
|
||||
--input: oklch(0.94 0.006 240);
|
||||
--ring: oklch(0.35 0.18 240);
|
||||
--chart-1: oklch(0.35 0.18 240);
|
||||
--chart-2: oklch(0.42 0.22 10);
|
||||
--popover-foreground: oklch(0.16 0.01 240);
|
||||
--primary: oklch(0.55 0.18 260); /* Vibrant blue */
|
||||
--primary-foreground: oklch(0.98 0.01 220);
|
||||
--secondary: oklch(0.92 0.01 120); /* Soft green */
|
||||
--secondary-foreground: oklch(0.16 0.01 240);
|
||||
--muted: oklch(0.94 0.01 220);
|
||||
--muted-foreground: oklch(0.45 0.01 240);
|
||||
--accent: oklch(0.72 0.18 40); /* Vibrant orange */
|
||||
--accent-foreground: oklch(0.98 0.01 220);
|
||||
--destructive: oklch(0.65 0.22 25); /* Strong red */
|
||||
--destructive-foreground: oklch(0.98 0.01 220);
|
||||
--border: oklch(0.88 0.01 220);
|
||||
--input: oklch(0.96 0.01 220);
|
||||
--ring: oklch(0.55 0.18 260);
|
||||
--chart-1: oklch(0.55 0.18 260);
|
||||
--chart-2: oklch(0.72 0.18 40);
|
||||
--chart-3: oklch(0.38 0.16 260);
|
||||
--chart-4: oklch(0.4 0.2 350);
|
||||
--chart-5: oklch(0.36 0.15 220);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.94 0.006 240);
|
||||
--sidebar-foreground: oklch(0.12 0.008 240);
|
||||
--sidebar-primary: oklch(0.35 0.18 240);
|
||||
--sidebar-primary-foreground: oklch(0.98 0.003 240);
|
||||
--sidebar-accent: oklch(0.42 0.22 10);
|
||||
--sidebar-accent-foreground: oklch(0.98 0.003 240);
|
||||
--sidebar-border: oklch(0.86 0.008 240);
|
||||
--sidebar-ring: oklch(0.35 0.18 240);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.06 0.008 240);
|
||||
--foreground: oklch(0.94 0.004 240);
|
||||
--card: oklch(0.1 0.01 240);
|
||||
--card-foreground: oklch(0.94 0.004 240);
|
||||
--popover: oklch(0.1 0.01 240);
|
||||
--popover-foreground: oklch(0.94 0.004 240);
|
||||
--primary: oklch(0.55 0.22 240);
|
||||
--primary-foreground: oklch(0.06 0.008 240);
|
||||
--secondary: oklch(0.16 0.01 240);
|
||||
--secondary-foreground: oklch(0.94 0.004 240);
|
||||
--muted: oklch(0.12 0.01 240);
|
||||
--muted-foreground: oklch(0.6 0.01 240);
|
||||
--accent: oklch(0.62 0.28 10);
|
||||
--accent-foreground: oklch(0.06 0.008 240);
|
||||
--destructive: oklch(0.65 0.3 15);
|
||||
--destructive-foreground: oklch(0.06 0.008 240);
|
||||
--border: oklch(0.18 0.01 240);
|
||||
--input: oklch(0.14 0.01 240);
|
||||
--ring: oklch(0.55 0.22 240);
|
||||
--chart-1: oklch(0.55 0.22 240);
|
||||
--chart-2: oklch(0.62 0.28 10);
|
||||
--chart-3: oklch(0.58 0.2 260);
|
||||
--chart-4: oklch(0.6 0.25 350);
|
||||
--chart-5: oklch(0.56 0.18 220);
|
||||
--sidebar: oklch(0.08 0.01 240);
|
||||
--sidebar-foreground: oklch(0.94 0.004 240);
|
||||
--sidebar-primary: oklch(0.55 0.22 240);
|
||||
--sidebar-primary-foreground: oklch(0.06 0.008 240);
|
||||
--sidebar-accent: oklch(0.62 0.28 10);
|
||||
--sidebar-accent-foreground: oklch(0.06 0.008 240);
|
||||
--sidebar-border: oklch(0.18 0.01 240);
|
||||
--sidebar-ring: oklch(0.55 0.22 240);
|
||||
--radius: 0.75rem;
|
||||
--sidebar: oklch(0.97 0.01 220);
|
||||
--sidebar-foreground: oklch(0.16 0.01 240);
|
||||
--sidebar-primary: oklch(0.55 0.18 260);
|
||||
--sidebar-primary-foreground: oklch(0.98 0.01 220);
|
||||
--sidebar-accent: oklch(0.72 0.18 40);
|
||||
--sidebar-accent-foreground: oklch(0.98 0.01 220);
|
||||
--sidebar-border: oklch(0.88 0.01 220);
|
||||
--sidebar-ring: oklch(0.55 0.18 260);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
@@ -130,3 +95,36 @@
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* Login page creative animation keyframes (red/rose accent theme) */
|
||||
@keyframes float-slow {
|
||||
0%, 100% { transform: translateY(0) scale(1); }
|
||||
50% { transform: translateY(-20px) scale(1.05); }
|
||||
}
|
||||
@keyframes float-medium {
|
||||
0%, 100% { transform: translateY(0) scale(1); }
|
||||
50% { transform: translateY(30px) scale(1.1); }
|
||||
}
|
||||
@keyframes float-fast {
|
||||
0%, 100% { transform: translateY(0) scale(1); }
|
||||
50% { transform: translateY(-15px) scale(0.95); }
|
||||
}
|
||||
@keyframes bounce-slow {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes ripple {
|
||||
to { opacity: 0; width: 200px; height: 200px; }
|
||||
}
|
||||
|
||||
/* Utility classes for red/rose accent theme */
|
||||
.animate-float-slow { animation: float-slow 7s ease-in-out infinite; }
|
||||
.animate-float-medium { animation: float-medium 5s ease-in-out infinite; }
|
||||
.animate-float-fast { animation: float-fast 3.5s ease-in-out infinite; }
|
||||
.animate-bounce-slow { animation: bounce-slow 2.5s infinite; }
|
||||
.animate-fade-in { animation: fade-in 1.2s 0.5s both; }
|
||||
.animate-ripple { animation: ripple 0.6s linear; }
|
||||
|
||||
104
pnpm-lock.yaml
generated
104
pnpm-lock.yaml
generated
@@ -226,11 +226,17 @@ importers:
|
||||
'@hookform/resolvers':
|
||||
specifier: ^5.1.1
|
||||
version: 5.1.1(react-hook-form@7.59.0(react@19.1.0))
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-label':
|
||||
specifier: ^2.1.7
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-navigation-menu':
|
||||
specifier: ^1.2.13
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.13(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-progress':
|
||||
specifier: ^1.1.7
|
||||
@@ -928,6 +934,19 @@ packages:
|
||||
'@radix-ui/primitive@1.1.2':
|
||||
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
||||
|
||||
'@radix-ui/react-accordion@1.2.11':
|
||||
resolution: {integrity: sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7':
|
||||
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||
peerDependencies:
|
||||
@@ -941,6 +960,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-collapsible@1.1.11':
|
||||
resolution: {integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-collection@1.1.7':
|
||||
resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
|
||||
peerDependencies:
|
||||
@@ -972,6 +1004,19 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dialog@1.1.14':
|
||||
resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-direction@1.1.1':
|
||||
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
|
||||
peerDependencies:
|
||||
@@ -3912,6 +3957,23 @@ snapshots:
|
||||
|
||||
'@radix-ui/primitive@1.1.2': {}
|
||||
|
||||
'@radix-ui/react-accordion@1.2.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
'@radix-ui/react-collapsible': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -3921,6 +3983,22 @@ snapshots:
|
||||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-collapsible@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
@@ -3945,6 +4023,28 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
|
||||
'@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
|
||||
aria-hidden: 1.2.6
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-direction@1.1.1(@types/react@19.1.8)(react@19.1.0)':
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
|
||||
Reference in New Issue
Block a user