Skip to content
Merged
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
2 changes: 1 addition & 1 deletion app/[username]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default async function UsernamePage({ params }: UsernamePageProps) {
if (error || !profile) {
notFound();
}
} catch (error) {
} catch {
// If there's any error, show 404
notFound();
}
Expand Down
29 changes: 20 additions & 9 deletions app/admin/forms/core-team/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { apiFetch } from "@/lib/api-fetch";
import { useAuth } from "@/lib/hooks/useAuth";
import {
Dialog,
DialogContent,
Expand Down Expand Up @@ -61,6 +62,7 @@ interface CoreTeamApplication {
}

export default function AdminCoreTeamPage() {
const { loading: authLoading, is_admin } = useAuth();
const [applications, setApplications] = useState<CoreTeamApplication[]>([]);
const [searchTerm, setSearchTerm] = useState("");
const [statusFilter, setStatusFilter] = useState("all");
Expand Down Expand Up @@ -124,6 +126,14 @@ export default function AdminCoreTeamPage() {
return Array.from(new Set(roles)).sort();
}, [applications]);

// Admin access check (after all hooks)
if (authLoading) {
return <div className="flex items-center justify-center min-h-[200px]">Loading...</div>;
}
if (!is_admin) {
return <div className="text-center text-sm text-muted-foreground py-12">Forbidden: admin access required.</div>;
}

// Handle status update
const handleStatusUpdate = async (id: number, newStatus: string) => {
try {
Expand Down Expand Up @@ -161,18 +171,19 @@ export default function AdminCoreTeamPage() {

// Export to CSV
const handleExportCSV = () => {
const sanitize = (v: string) => (/^[-+=@]/.test(v) ? `'${v}` : v);
const csvContent = [
["Name", "Email", "Phone", "Location", "Occupation", "Company", "Preferred Role", "Status", "Applied Date"],
...filteredApplications.map(app => [
`${app.first_name} ${app.last_name}`,
app.email,
app.phone || "",
app.location,
app.occupation,
app.company || "",
app.preferred_role,
app.status,
new Date(app.created_at).toLocaleDateString()
sanitize(`${app.first_name} ${app.last_name}`),
sanitize(app.email),
sanitize(app.phone || ""),
sanitize(app.location),
sanitize(app.occupation),
sanitize(app.company || ""),
sanitize(app.preferred_role),
sanitize(app.status),
sanitize(new Date(app.created_at).toLocaleDateString())
])
].map(row => row.map(cell => `"${cell}"`).join(",")).join("\n");

Expand Down
72 changes: 60 additions & 12 deletions app/api/admin-core-team/route.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
import { NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';

function getSupabaseClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
// Server-side clients
function getServiceClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
}

async function getServerClient() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => {
cookieStore.set(name, value, options);
});
},
},
}
);
}

async function requireAdmin() {
const supa = await getServerClient();
const { data: { user }, error } = await supa.auth.getUser();
if (error || !user) {
return { ok: false, resp: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) };
}
// Check admin flag from profiles (service client to bypass RLS for lookup only)
const svc = getServiceClient();
const { data: profile, error: pErr } = await svc.from('profiles').select('is_admin').eq('id', user.id).single();
if (pErr || !profile?.is_admin) {
return { ok: false, resp: NextResponse.json({ error: 'Forbidden' }, { status: 403 }) };
}
return { ok: true };
}

export async function GET() {
try {
const supabase = getSupabaseClient();
const auth = await requireAdmin();
if (!auth.ok) return auth.resp;

const supabase = getServiceClient();

const { data, error } = await supabase
.from('core_team_applications')
.select('*')
.select('id,first_name,last_name,email,phone,location,occupation,company,experience,skills,portfolio,preferred_role,availability,commitment,motivation,vision,previous_experience,social_media,references_info,additional_info,status,user_id,created_at,updated_at')
.order('created_at', { ascending: false });

if (error) {
Expand All @@ -31,14 +72,18 @@ export async function GET() {

export async function POST(req: Request) {
try {
const auth = await requireAdmin();
if (!auth.ok) return auth.resp;

const body = await req.json();
const { id, status, notes } = body;
const { id, status, notes } = body as { id?: number; status?: string; notes?: string };

if (!id || !status) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
const ALLOWED_STATUSES = new Set(['pending','approved','rejected']);
if (!id || !status || !ALLOWED_STATUSES.has(status)) {
return NextResponse.json({ error: 'Missing required fields or invalid status' }, { status: 400 });
}

const supabase = getSupabaseClient();
const supabase = getServiceClient();

const { data, error } = await supabase
.from('core_team_applications')
Expand All @@ -65,14 +110,17 @@ export async function POST(req: Request) {

export async function PATCH(req: Request) {
try {
const auth = await requireAdmin();
if (!auth.ok) return auth.resp;

const body = await req.json();
const { id, ...updates } = body;

if (!id) {
return NextResponse.json({ error: 'Missing application ID' }, { status: 400 });
}

const supabase = getSupabaseClient();
const supabase = getServiceClient();

const { data, error } = await supabase
.from('core_team_applications')
Expand Down
5 changes: 3 additions & 2 deletions components/forms/collaboration-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Building2, FileText, Loader2 } from "lucide-react";
import { createBrowserClient } from "@supabase/ssr";
import { toast } from "sonner";
import { useAuth } from "@/lib/hooks/useAuth";
import Link from "next/link";

export function CollaborationForm() {
const { user, loading: authLoading } = useAuth();
Expand Down Expand Up @@ -143,9 +144,9 @@ export function CollaborationForm() {
</p>
<Button
className="bg-gradient-to-r from-blue-500 to-cyan-500 hover:from-blue-600 hover:to-cyan-600 text-white"
onClick={() => window.location.href = '/auth/signin'}
asChild
>
Sign In to Continue
<Link href="/auth/signin">Sign In to Continue</Link>
</Button>
</div>
</CardContent>
Expand Down
5 changes: 3 additions & 2 deletions components/forms/core-team-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Crown, Send, Users, Code2, Camera, PenTool, Target, Zap } from "lucide-
import { createBrowserClient } from "@supabase/ssr";
import { toast } from "sonner";
import { useAuth } from "@/lib/hooks/useAuth";
import Link from "next/link";

export function CoreTeamForm() {
const { user, loading: authLoading } = useAuth();
Expand Down Expand Up @@ -200,9 +201,9 @@ export function CoreTeamForm() {
</p>
<Button
className="bg-gradient-to-r from-amber-500 to-yellow-500 hover:from-amber-600 hover:to-yellow-600 text-white"
onClick={() => window.location.href = '/auth/signin'}
asChild
>
Sign In to Continue
<Link href="/auth/signin">Sign In to Continue</Link>
</Button>
</div>
</CardContent>
Expand Down
5 changes: 3 additions & 2 deletions components/forms/judges-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Award, Send, Code2, Users, Globe } from "lucide-react";
import { createBrowserClient } from "@supabase/ssr";
import { toast } from "sonner";
import { useAuth } from "@/lib/hooks/useAuth";
import Link from "next/link";

export function JudgesForm() {
const { user, loading: authLoading } = useAuth();
Expand Down Expand Up @@ -184,9 +185,9 @@ export function JudgesForm() {
</p>
<Button
className="bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-600 hover:to-red-600 text-white"
onClick={() => window.location.href = '/auth/signin'}
asChild
>
Sign In to Continue
<Link href="/auth/signin">Sign In to Continue</Link>
</Button>
</div>
);
Expand Down
5 changes: 3 additions & 2 deletions components/forms/mentor-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Lightbulb, Send, Code2, Users, GraduationCap, MessageSquare } from "luc
import { createBrowserClient } from "@supabase/ssr";
import { toast } from "sonner";
import { useAuth } from "@/lib/hooks/useAuth";
import Link from "next/link";

export function MentorForm() {
const { user, loading: authLoading } = useAuth();
Expand Down Expand Up @@ -229,9 +230,9 @@ export function MentorForm() {
</p>
<Button
className="bg-gradient-to-r from-purple-500 to-indigo-500 hover:from-purple-600 hover:to-indigo-600 text-white"
onClick={() => window.location.href = '/auth/signin'}
asChild
>
Sign In to Continue
<Link href="/auth/signin">Sign In to Continue</Link>
</Button>
</div>
</CardContent>
Expand Down
5 changes: 3 additions & 2 deletions components/forms/sponsorship-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Trophy, Send, Building2 } from "lucide-react";
import { createBrowserClient } from "@supabase/ssr";
import { toast } from "sonner";
import { useAuth } from "@/lib/hooks/useAuth";
import Link from "next/link";

export function SponsorshipForm() {
const { user, loading: authLoading } = useAuth();
Expand Down Expand Up @@ -188,9 +189,9 @@ export function SponsorshipForm() {
</p>
<Button
className="bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white"
onClick={() => window.location.href = '/auth/signin'}
asChild
>
Sign In to Continue
<Link href="/auth/signin">Sign In to Continue</Link>
</Button>
</div>
);
Expand Down
5 changes: 3 additions & 2 deletions components/forms/volunteer-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { HandHeart, Send, Calendar, MapPin, Users, Code2, Heart } from "lucide-r
import { createBrowserClient } from "@supabase/ssr";
import { toast } from "sonner";
import { useAuth } from "@/lib/hooks/useAuth";
import Link from "next/link";

export function VolunteerForm() {
const { user, loading: authLoading } = useAuth();
Expand Down Expand Up @@ -176,9 +177,9 @@ export function VolunteerForm() {
</p>
<Button
className="bg-gradient-to-r from-pink-500 to-rose-500 hover:from-pink-600 hover:to-rose-600 text-white"
onClick={() => window.location.href = '/auth/signin'}
asChild
>
Sign In to Continue
<Link href="/auth/signin">Sign In to Continue</Link>
</Button>
</div>
</CardContent>
Expand Down
Loading