Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] created auth check for admin page #9

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions api/check-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { createClient } from '@/utils/supabase/client';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const supabase = createClient();

try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return res.status(401).json({ isAuthorized: false });
}

const { data: adminUser } = await supabase
.from('users')
.select('can_add_and_delete_packages')
.eq('email', user.email)
.single();

const isAuthorized = adminUser?.can_add_and_delete_packages === true;
return res.status(200).json({ isAuthorized });
} catch (error) {
console.error('Authorization check failed:', error);
return res.status(500).json({ isAuthorized: false });
}
}
220 changes: 125 additions & 95 deletions app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ import { Card } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { createClient } from '@supabase/supabase-js'
import { createClient } from '@/utils/supabase/client';
import { useEffect, useState } from 'react'
import { ToastAction } from "@/components/ui/toast"
import { useToast } from "../hooks/use-toast"
import { redirect } from "next/navigation";

// Assuming these functions are defined in the specified path
import { fetchStudentsGivenCollege } from "../../api/admin"

const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL ?? "", process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "")
const supabase = createClient();

const collegeContacts = [
{ collegeName: "Lovett", name: "Sharon O'Leary", email: "[email protected]" },
Expand Down Expand Up @@ -58,6 +59,26 @@ interface Student {
const currentCollegeCoordEmail = "[email protected]"

export default function Component() {
const [isAuthorized, setIsAuthorized] = useState(false);

const checkAuthorization = async () => {
console.log('Checking authorization...');
try {
const response = await fetch('/api/check-auth');
const data = await response.json();
setIsAuthorized(data.isAuthorized);
} catch (error) {
console.error('Authorization check failed:', error);
setIsAuthorized(false);
}
};


useEffect(() => {
checkAuthorization();
}, []);


const [coord, setCoord] = useState<CollegeContact | null>(null)
const [students, setStudents] = useState<Student[] | null>(null)
const [loading, setLoading] = useState(false)
Expand Down Expand Up @@ -109,104 +130,113 @@ export default function Component() {
return matchesFilter && matchesSearch
})


return (
<div className="flex h-screen bg-white">
<div className="hidden w-64 bg-gray-100 lg:block">
<div className="flex h-16 items-center justify-center border-b border-gray-200">
<Package className="mr-2 h-6 w-6 text-[#00205B]" />
<span className="text-lg font-semibold text-[#00205B]">Rice Package Admin</span>
</div>
<div className="mt-4 px-4 space-y-2">
<div className="text-sm font-medium text-gray-600">College Coordinator</div>
<div className="text-[#00205B] font-semibold">{coord?.name}</div>
<div className="text-sm text-gray-600">{coord?.email}</div>
<div className="text-sm text-gray-600">Net ID: {coord?.email.split("@")[0]}</div>
<div className="text-sm font-medium text-[#00205B] mt-4">Assigned College</div>
<div className="text-lg font-bold text-[#00205B]">{coord?.collegeName}</div>
</div>
</div>

<div className="flex flex-1 flex-col overflow-hidden">
<header className="flex h-16 items-center justify-between border-b border-gray-200 bg-white px-6">
<div className="flex items-center gap-4">
<h1 className="text-2xl font-semibold text-[#00205B]">{coord?.collegeName} College</h1>
</div>
<div className="flex items-center gap-4 bg-white">
<Button className="hover:bg-white" variant="ghost" size="icon">
<User className="h-5 w-5 text-[#00205B]" />
</Button>
</div>
</header>

<main className="flex-1 overflow-auto p-6">
<div className="mb-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="flex w-full flex-col gap-4 md:w-auto md:flex-row">
<Select value={filter} onValueChange={setFilter}>
<SelectTrigger className="w-full md:w-[180px] bg-white text-black">
<SelectValue placeholder="Filter by status" />
</SelectTrigger>
<SelectContent className="bg-white text-black">
<SelectItem value="all">All Packages</SelectItem>
<SelectItem value="unclaimed">Unclaimed</SelectItem>
<SelectItem value="claimed">Claimed</SelectItem>
</SelectContent>
</Select>
<div className="relative">
<Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 transform text-gray-400" />
<Input
placeholder="Search students..."
className="pl-8 bg-white text-black"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
{isAuthorized ? (
<>
<div className="hidden w-64 bg-gray-100 lg:block">
<div className="flex h-16 items-center justify-center border-b border-gray-200">
<Package className="mr-2 h-6 w-6 text-[#00205B]" />
<span className="text-lg font-semibold text-[#00205B]">Rice Package Admin</span>
</div>
<div className="mt-4 px-4 space-y-2">
<div className="text-sm font-medium text-gray-600">College Coordinator</div>
<div className="text-[#00205B] font-semibold">{coord?.name}</div>
<div className="text-sm text-gray-600">{coord?.email}</div>
<div className="text-sm text-gray-600">Net ID: {coord?.email.split("@")[0]}</div>
<div className="text-sm font-medium text-[#00205B] mt-4">Assigned College</div>
<div className="text-lg font-bold text-[#00205B]">{coord?.collegeName}</div>
</div>
</div>

<Card>
<Table className="bg-white">
<TableHeader>
<TableRow>
<TableHead className="text-black">Name</TableHead>
<TableHead className="text-black">Rice Email</TableHead>
<TableHead className="text-black">Rice NetID</TableHead>
<TableHead className="text-black"># of Packages To Pick Up</TableHead>
<TableHead className="text-black">Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={5} className="text-center">Loading...</TableCell>
</TableRow>
) : filteredStudents?.map((student) => (
<TableRow key={student.id} className="text-black">
<TableCell className="font-medium">{student.name}</TableCell>
<TableCell>{student.email}</TableCell>
<TableCell>{student.email.split("@")[0]}</TableCell>
<TableCell>
<Badge variant={student.packages.length > 0 ? "default" : "secondary"} className="bg-[#00205B] text-white hover:bg-black">
{student.packages.filter((x) => !x.claimed).length}
</Badge>
</TableCell>
<TableCell>
<Button
variant="outline"
size="sm"
disabled={student.packages.length === 0}
className="bg-white border-[#00205B] text-[#00205B] hover:bg-[#00205B] hover:text-white"
onClick={() => handleClick(student.email.split("@")[0], "Your package has arrived!")}
>
Remind
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</main>
</div>
<div className="flex flex-1 flex-col overflow-hidden">
<header className="flex h-16 items-center justify-between border-b border-gray-200 bg-white px-6">
<div className="flex items-center gap-4">
<h1 className="text-2xl font-semibold text-[#00205B]">{coord?.collegeName} College</h1>
</div>
<div className="flex items-center gap-4 bg-white">
<Button className="hover:bg-white" variant="ghost" size="icon">
<User className="h-5 w-5 text-[#00205B]" />
</Button>
</div>
</header>

<main className="flex-1 overflow-auto p-6">
<div className="mb-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="flex w-full flex-col gap-4 md:w-auto md:flex-row">
<Select value={filter} onValueChange={setFilter}>
<SelectTrigger className="w-full md:w-[180px] bg-white text-black">
<SelectValue placeholder="Filter by status" />
</SelectTrigger>
<SelectContent className="bg-white text-black">
<SelectItem value="all">All Packages</SelectItem>
<SelectItem value="unclaimed">Unclaimed</SelectItem>
<SelectItem value="claimed">Claimed</SelectItem>
</SelectContent>
</Select>
<div className="relative">
<Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 transform text-gray-400" />
<Input
placeholder="Search students..."
className="pl-8 bg-white text-black"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
</div>

<Card>
<Table className="bg-white">
<TableHeader>
<TableRow>
<TableHead className="text-black">Name</TableHead>
<TableHead className="text-black">Rice Email</TableHead>
<TableHead className="text-black">Rice NetID</TableHead>
<TableHead className="text-black"># of Packages To Pick Up</TableHead>
<TableHead className="text-black">Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={5} className="text-center">Loading...</TableCell>
</TableRow>
) : filteredStudents?.map((student) => (
<TableRow key={student.id} className="text-black">
<TableCell className="font-medium">{student.name}</TableCell>
<TableCell>{student.email}</TableCell>
<TableCell>{student.email.split("@")[0]}</TableCell>
<TableCell>
<Badge variant={student.packages.length > 0 ? "default" : "secondary"} className="bg-[#00205B] text-white hover:bg-black">
{student.packages.filter((x) => !x.claimed).length}
</Badge>
</TableCell>
<TableCell>
<Button
variant="outline"
size="sm"
disabled={student.packages.length === 0}
className="bg-white border-[#00205B] text-[#00205B] hover:bg-[#00205B] hover:text-white"
onClick={() => handleClick(student.email.split("@")[0], "Your package has arrived!")}
>
Remind
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</main>
</div>
</>
) : (
<div className="flex flex-1 items-center justify-center">
<h1 className="text-2xl font-semibold text-black">401 - Unauthorized</h1>
</div>
)}
</div>
)
}
30 changes: 29 additions & 1 deletion app/kiosk/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,33 @@

import { useRouter } from "next/navigation";
import { ArrowRight, TruckIcon, PackageOpen } from "lucide-react";
import { createClient } from '@/utils/supabase/client';
import { useEffect, useState } from 'react';


export default function PackageOptions() {
const router = useRouter();
const supabase = createClient();
const [isAuthorized, setIsAuthorized] = useState(false);

const checkAuthorization = async () => {
console.log('Checking authorization...');
try {
const response = await fetch('/api/check-auth');
const data = await response.json();
setIsAuthorized(data.isAuthorized);
} catch (error) {
console.error('Authorization check failed:', error);
setIsAuthorized(false);
}
};

const handleNavigate = (path: string) => {
useEffect(() => {
checkAuthorization();
}, []);


const handleNavigate = async (path: string) => {
router.push(path);
};

Expand All @@ -32,6 +54,8 @@ export default function PackageOptions() {
<ArrowRight className="absolute bottom-4 right-4 w-6 h-6 text-gray-500 dark:text-gray-400 transition-all duration-300 ease-in-out transform translate-x-4 opacity-0 group-hover:translate-x-0 group-hover:opacity-100" />
</div>
</button>

{isAuthorized ? (
<button
onClick={() => handleNavigate("/admin")}
className="relative bg-gray-100 dark:bg-gray-800 rounded-lg shadow-md overflow-hidden transition-all duration-300 ease-in-out hover:shadow-lg"
Expand All @@ -47,6 +71,10 @@ export default function PackageOptions() {
<ArrowRight className="absolute bottom-4 right-4 w-6 h-6 text-gray-500 dark:text-gray-400 transition-all duration-300 ease-in-out transform translate-x-4 opacity-0 group-hover:translate-x-0 group-hover:opacity-100" />
</div>
</button>
) :
(
<></>
)}
</div>
</div>
</div>
Expand Down
Loading