Skip to content

Commit a3ad4b5

Browse files
committed
maint & seat-allocation fix
1 parent ad25b50 commit a3ad4b5

File tree

96 files changed

+12621
-213
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+12621
-213
lines changed

app/admin/layout.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ import { requireRole } from "@/lib/auth";
22
import type { ReactNode } from "react";
33
import { AdminSidebar } from "@/components/admin/admin-sidebar";
44
import { BespokeLayout } from "@/components/ui/bespoke-layout";
5-
import Link from "next/link";
6-
import { createClient } from "@/lib/supabase/server";
7-
import Image from "next/image";
8-
import { UserProfileMenu } from "@/components/student/user-profile-menu";
9-
import { Bell } from "lucide-react";
105
import { DashboardHeader } from "@/components/layouts/DashboardHeader";
116

127
export default async function AdminLayout({
@@ -15,27 +10,35 @@ export default async function AdminLayout({
1510
children: ReactNode;
1611
}) {
1712
const { user, role } = await requireRole("admin");
18-
// const supabase = await createClient(); // Not needed if we don't query more
1913

2014
if (!user) return null; // Should not happen due to redirect
2115

2216
// In a real app we might have an 'admins' table, but for now we'll use user metadata or email
2317
const adminName = user.user_metadata?.name || "Administrator";
18+
const navItems = [
19+
{ href: "/admin", label: "Dashboard" },
20+
{ href: "/admin/students", label: "Students" },
21+
{ href: "/admin/companies", label: "Companies" },
22+
{ href: "/admin/seat-allocation", label: "Seat Allocation" },
23+
{ href: "/admin/messages", label: "Broadcasts" },
24+
{ href: "/admin/settings", label: "Settings" }
25+
];
2426

2527
return (
2628
<BespokeLayout>
2729
<DashboardHeader
28-
role={role as any}
30+
role={role}
2931
userName={adminName}
3032
userEmail={user.email}
3133
avatarUrl={user.user_metadata?.avatar_url}
3234
homeUrl="/admin"
3335
messagesUrl="/admin/messages"
36+
mobileNavItems={navItems}
3437
/>
3538
<div className="flex flex-1 overflow-hidden">
3639
<AdminSidebar />
37-
<main className="flex-1 overflow-y-auto bg-neutral-50/30 p-8">
38-
<div className="max-w-7xl mx-auto space-y-8">{children}</div>
40+
<main className="flex-1 overflow-y-auto bg-neutral-50/30 py-4 px-0 sm:py-6 sm:px-6 lg:py-8 lg:px-8">
41+
<div className="max-w-none sm:max-w-7xl mx-0 sm:mx-auto space-y-8">{children}</div>
3942
</main>
4043
</div>
4144
</BespokeLayout>

app/admin/seat-allocation/page.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Metadata } from "next";
2+
import { SeatAllocationPage } from "@/components/admin/seat-allocation-page";
3+
4+
export const metadata: Metadata = {
5+
title: "Seat Allocation | PlacePro Admin",
6+
description: "Run and publish seat allocation sessions for labs and students."
7+
};
8+
9+
export default function AdminSeatAllocationRoute() {
10+
return <SeatAllocationPage />;
11+
}

app/admin/settings/page.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ export default function AdminSettingsPage() {
174174
const isSuperAdmin = role === "super_admin";
175175

176176
return (
177-
<div className="max-w-6xl mx-auto pb-20">
177+
<div className="max-w-none sm:max-w-6xl mx-0 sm:mx-auto pb-20 px-0 sm:px-0">
178178
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 mb-10">
179179
<div>
180180
<h1 className="text-3xl font-bold tracking-tight text-neutral-900">System Control</h1>
@@ -188,26 +188,29 @@ export default function AdminSettingsPage() {
188188
</div>
189189

190190
<Tabs defaultValue="profile" className="w-full space-y-8">
191-
<TabsList className="bg-neutral-100/50 p-1 border-2 border-black rounded-lg w-fit">
191+
<TabsList className="bg-neutral-100/50 p-1 border-2 border-black rounded-lg w-full flex flex-wrap gap-2 sm:w-fit">
192192
<TabsTrigger
193193
value="profile"
194-
className="data-[state=active]:bg-black data-[state=active]:text-white font-bold px-6 py-2 rounded-md transition-all"
194+
activeIndicatorClassName="bg-black shadow-none"
195+
className="data-[state=active]:text-white font-bold px-4 py-2 rounded-md transition-all w-full sm:w-auto"
195196
>
196197
<UserIcon className="w-4 h-4 mr-2" />
197198
Admin Profile
198199
</TabsTrigger>
199200
{isSuperAdmin && (
200201
<TabsTrigger
201202
value="admins"
202-
className="data-[state=active]:bg-black data-[state=active]:text-white font-bold px-6 py-2 rounded-md transition-all"
203+
activeIndicatorClassName="bg-black shadow-none"
204+
className="data-[state=active]:text-white font-bold px-4 py-2 rounded-md transition-all w-full sm:w-auto"
203205
>
204206
<ShieldCheck className="w-4 h-4 mr-2" />
205207
User Management
206208
</TabsTrigger>
207209
)}
208210
<TabsTrigger
209211
value="security"
210-
className="data-[state=active]:bg-black data-[state=active]:text-white font-bold px-6 py-2 rounded-md transition-all"
212+
activeIndicatorClassName="bg-black shadow-none"
213+
className="data-[state=active]:text-white font-bold px-4 py-2 rounded-md transition-all w-full sm:w-auto"
211214
>
212215
<Lock className="w-4 h-4 mr-2" />
213216
Security
@@ -306,22 +309,22 @@ export default function AdminSettingsPage() {
306309
<CardDescription>Manage password and authentication tokens.</CardDescription>
307310
</CardHeader>
308311
<CardContent className="pt-6 space-y-6">
309-
<div className="flex items-center justify-between p-5 border rounded-2xl bg-neutral-50/50 border-dashed border-neutral-300">
312+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between p-5 border rounded-2xl bg-neutral-50/50 border-dashed border-neutral-300">
310313
<div className="space-y-1">
311314
<p className="text-sm font-bold text-neutral-900">Admin Password Reset</p>
312315
<p className="text-xs text-neutral-500 font-medium">Sends a secure 1-time link to {user?.email}</p>
313316
</div>
314317
<Button
315318
variant="outline"
316-
className="border-black font-bold h-10 px-6 active:translate-y-0.5 transition-all hover:bg-black hover:text-white"
319+
className="border-black font-bold h-10 px-6 active:translate-y-0.5 transition-all hover:bg-black hover:text-white w-full sm:w-auto"
317320
onClick={handleResetPassword}
318321
disabled={isResetting}
319322
>
320323
{isResetting ? "Processing..." : "Initiate Reset"}
321324
</Button>
322325
</div>
323326

324-
<div className="flex items-center justify-between p-5 border rounded-2xl bg-neutral-50/50 border-dashed border-neutral-300 opacity-60 grayscale cursor-not-allowed">
327+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between p-5 border rounded-2xl bg-neutral-50/50 border-dashed border-neutral-300 opacity-60 grayscale cursor-not-allowed">
325328
<div className="space-y-1">
326329
<p className="text-sm font-bold text-neutral-900">Multi-Factor Auth (MFA)</p>
327330
<p className="text-xs text-neutral-500 font-medium">Add an extra layer of security via Authenticator app.</p>
@@ -337,12 +340,12 @@ export default function AdminSettingsPage() {
337340
<h4 className="font-bold text-sm uppercase tracking-wider">Danger Zone</h4>
338341
</div>
339342
<Separator className="bg-red-200" />
340-
<div className="flex items-center justify-between">
343+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
341344
<div className="space-y-1">
342345
<p className="text-sm font-bold text-red-900">Sign out from all devices</p>
343346
<p className="text-xs text-red-600 font-medium">This will terminate all active administrative sessions.</p>
344347
</div>
345-
<Button variant="destructive" className="font-bold h-9 bg-red-600 hover:bg-red-700">
348+
<Button variant="destructive" className="font-bold h-9 bg-red-600 hover:bg-red-700 w-full sm:w-auto">
346349
Logout All
347350
</Button>
348351
</div>

app/api/auth/login/route.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { NextResponse } from "next/server";
2+
import { z } from "zod";
3+
import { createClient as createServerClient } from "@/lib/supabase/server";
4+
import type { UserRole } from "@/types/database.types";
5+
6+
export const runtime = "nodejs";
7+
export const dynamic = "force-dynamic";
8+
9+
const loginSchema = z.object({
10+
email: z.string().email("Please enter a valid email address."),
11+
password: z.string().min(1, "Password is required.")
12+
});
13+
14+
const isAdminRole = (role: UserRole | null) => role === "admin" || role === "super_admin";
15+
16+
export async function POST(request: Request) {
17+
const body = (await request.json().catch(() => null)) as unknown;
18+
const parsed = loginSchema.safeParse(body);
19+
20+
if (!parsed.success) {
21+
return NextResponse.json(
22+
{
23+
success: false,
24+
error: parsed.error.issues[0]?.message ?? "Invalid login payload."
25+
},
26+
{ status: 400 }
27+
);
28+
}
29+
30+
try {
31+
const supabase = await createServerClient();
32+
const { email, password } = parsed.data;
33+
34+
const { data, error } = await supabase.auth.signInWithPassword({
35+
email,
36+
password
37+
});
38+
39+
if (error || !data.user) {
40+
return NextResponse.json(
41+
{
42+
success: false,
43+
error: error?.message ?? "Invalid login credentials."
44+
},
45+
{ status: 401 }
46+
);
47+
}
48+
49+
const { data: roleRow, error: roleError } = await supabase
50+
.from("user_roles")
51+
.select("role, is_active")
52+
.eq("user_id", data.user.id)
53+
.maybeSingle();
54+
55+
if (roleError || !roleRow || !roleRow.is_active) {
56+
await supabase.auth.signOut();
57+
58+
return NextResponse.json(
59+
{
60+
success: false,
61+
error: "Your account is inactive or role is missing."
62+
},
63+
{ status: 403 }
64+
);
65+
}
66+
67+
const role = roleRow.role as UserRole;
68+
69+
return NextResponse.json(
70+
{
71+
success: true,
72+
role,
73+
redirectTo: isAdminRole(role) ? "/admin/students" : "/student/dashboard"
74+
},
75+
{ status: 200 }
76+
);
77+
} catch (error) {
78+
const message = error instanceof Error ? error.message : "Unable to sign in.";
79+
80+
return NextResponse.json(
81+
{
82+
success: false,
83+
error: message
84+
},
85+
{ status: 500 }
86+
);
87+
}
88+
}

app/globals.css

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@
107107
flex-direction: column;
108108
}
109109

110+
@media (max-width: 640px) {
111+
.bespoke-frame {
112+
border-left: 0;
113+
border-right: 0;
114+
max-width: 100%;
115+
margin: 0;
116+
height: auto;
117+
overflow: visible;
118+
}
119+
}
120+
110121
.nav-line {
111122
border-bottom: 2px solid #000;
112123
}
@@ -241,4 +252,4 @@
241252

242253
.hover-logo-3d {
243254
perspective: 1000px;
244-
}
255+
}

app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,15 @@ export default function LandingPage() {
7171

7272
return (
7373
<div className="min-h-screen w-full bg-white flex justify-center selection:bg-black selection:text-white">
74-
<div className="flex w-full max-w-7xl border-x-2 border-black relative text-black bg-white font-sans antialiased overflow-x-hidden">
74+
<div className="flex w-full max-w-none sm:max-w-7xl border-x-0 sm:border-x-2 border-black relative text-black bg-white font-sans antialiased overflow-x-hidden">
7575

7676
{/* Decorative Sidebars */}
7777
<div className="hidden lg:block w-[64px] border-r-2 border-black dot-matrix-pattern relative shrink-0" />
7878

7979
<div className="flex-1 flex flex-col relative technical-grid bg-white">
8080
<LandingNavbar />
8181

82-
<main className="flex-1 flex flex-col items-center relative z-10 px-4">
82+
<main className="flex-1 flex flex-col items-center relative z-10 px-0 sm:px-4">
8383
<LandingHero />
8484
<InstitutionalArchitecture />
8585
</main>

app/student/companies/[id]/page.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export default function CompanyDetailPage() {
162162
<Skeleton className="h-10 w-32" />
163163
<div className="space-y-4">
164164
<Skeleton className="h-40 w-full rounded-xl" />
165-
<div className="grid grid-cols-3 gap-6">
165+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
166166
<Skeleton className="h-96 col-span-2 rounded-xl" />
167167
<Skeleton className="h-96 col-span-1 rounded-xl" />
168168
</div>
@@ -177,7 +177,7 @@ export default function CompanyDetailPage() {
177177
<div className="min-h-screen bg-neutral-50/50 pb-20 font-sans">
178178
{/* Clean Header */}
179179
<div className="bg-white border-b border-neutral-200">
180-
<div className="max-w-6xl mx-auto px-6 py-8">
180+
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-8">
181181
<div className="mb-6">
182182
<Link href="/student/companies">
183183
<Button variant="ghost" size="sm" className="pl-0 text-neutral-500 hover:text-black gap-2">
@@ -265,7 +265,7 @@ export default function CompanyDetailPage() {
265265
</div>
266266
</div>
267267

268-
<div className="max-w-6xl mx-auto px-6 py-8">
268+
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-8">
269269
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
270270

271271
{/* Left Column: Information */}
@@ -303,17 +303,17 @@ export default function CompanyDetailPage() {
303303

304304
{/* Eligibility Breakdown */}
305305
<div className="card-border bg-white rounded-lg overflow-hidden">
306-
<div className="px-6 py-4 border-b border-neutral-100">
306+
<div className="px-4 sm:px-6 py-4 border-b border-neutral-100">
307307
<h2 className="text-lg font-semibold text-neutral-900">Eligibility Criteria</h2>
308308
</div>
309309
<div className="overflow-x-auto">
310-
<table className="w-full text-left text-sm">
310+
<table className="min-w-[720px] w-full text-left text-sm">
311311
<thead className="bg-neutral-50 text-neutral-500">
312312
<tr>
313-
<th className="px-6 py-3 font-medium text-xs uppercase tracking-wide">Criteria</th>
314-
<th className="px-6 py-3 font-medium text-xs uppercase tracking-wide">Required</th>
315-
<th className="px-6 py-3 font-medium text-xs uppercase tracking-wide">Your Profile</th>
316-
<th className="px-6 py-3 font-medium text-xs uppercase tracking-wide text-right">Status</th>
313+
<th className="px-4 sm:px-6 py-3 font-medium text-xs uppercase tracking-wide">Criteria</th>
314+
<th className="px-4 sm:px-6 py-3 font-medium text-xs uppercase tracking-wide">Required</th>
315+
<th className="px-4 sm:px-6 py-3 font-medium text-xs uppercase tracking-wide">Your Profile</th>
316+
<th className="px-4 sm:px-6 py-3 font-medium text-xs uppercase tracking-wide text-right">Status</th>
317317
</tr>
318318
</thead>
319319
<tbody className="divide-y divide-neutral-100">
@@ -326,10 +326,10 @@ export default function CompanyDetailPage() {
326326
{ label: "Profile Status", req: "Complete", val: student.profile_complete ? "Complete" : "Incomplete", pass: !!student.profile_complete }
327327
].map((row, i) => (
328328
<tr key={i} className="hover:bg-neutral-50/50">
329-
<td className="px-6 py-4 font-medium text-neutral-900">{row.label}</td>
330-
<td className="px-6 py-4 text-neutral-600">{row.req}</td>
331-
<td className="px-6 py-4 text-neutral-600">{row.val}</td>
332-
<td className="px-6 py-4 text-right">
329+
<td className="px-4 sm:px-6 py-4 font-medium text-neutral-900">{row.label}</td>
330+
<td className="px-4 sm:px-6 py-4 text-neutral-600">{row.req}</td>
331+
<td className="px-4 sm:px-6 py-4 text-neutral-600">{row.val}</td>
332+
<td className="px-4 sm:px-6 py-4 text-right">
333333
{row.pass ?
334334
<Badge variant="outline" className="bg-emerald-50 text-emerald-700 border-emerald-100 text-[10px] font-medium px-2 py-0.5 rounded-md">
335335
Qualified

app/student/companies/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export default function StudentCompaniesPage() {
175175
</div>
176176
</div>
177177

178-
<div className="flex items-center justify-between md:justify-end gap-6 pl-[4.5rem] md:pl-0">
178+
<div className="flex items-center justify-between md:justify-end gap-6 pl-0 md:pl-[4.5rem]">
179179
<div className="flex flex-col items-start md:items-end gap-1.5">
180180
<div className="flex items-center gap-1.5 text-xs font-medium text-neutral-600">
181181
<span className="bg-neutral-50 px-2.5 py-1 rounded-md text-[11px] border border-neutral-100 font-semibold text-neutral-700">

0 commit comments

Comments
 (0)