All checks were successful
Build and Push Docker Image / build (push) Successful in 8m5s
The AWARD_MASTER role split sponsor jurors into a parallel UI that hid project files (only showed when the award was anchored to an evaluation round) and duplicated the jury voting path with no real difference in authority — tie-break and finalize were already governed by AwardJuror.isChair regardless of the user's global role. Inviting a juror via the award page defaulted to AWARD_MASTER, randomly fragmenting jury panels. This collapses the role into JURY_MEMBER + isChair: - specialAward.getMyAwardDetail now returns evaluation scores, chair visibility into other jurors' votes, and juror roster - specialAward.submitVote accepts an optional justification per vote - specialAward.confirmWinner moves from awardMasterProcedure to protectedProcedure (juror+chair check inside) - bulkInviteJurors creates JURY_MEMBER accounts and, when the award has a juryGroupId, also adds them to that JuryGroup so they appear on the round-page jury panel - jury award page renders justification, eval-score badges, and a chair tools panel with vote tally + finalize-winner CTA - juryGroup.list includes attached SpecialAwards; the jury-list UI shows a trophy pill alongside round pills - (award-master) route group, awardMasterProcedure, AWARD_MASTER role enum value, and AWARD_MASTER_DECISION decisionMode are deleted - migration demotes any residual AWARD_MASTER users to JURY_MEMBER and recreates the UserRole enum without the value Coup de Coeur on prod: Didier (the sponsor juror added today as AWARD_MASTER by the buggy invite form) was migrated to JURY_MEMBER and attached to the existing "Coup de Coeur" JuryGroup; the SpecialAward itself was linked to that group (juryGroupId was NULL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
177 lines
7.4 KiB
TypeScript
177 lines
7.4 KiB
TypeScript
import type { Metadata } from 'next'
|
|
import Link from 'next/link'
|
|
import Image from 'next/image'
|
|
import { auth } from '@/lib/auth'
|
|
import { redirect } from 'next/navigation'
|
|
import type { Route } from 'next'
|
|
import type { UserRole } from '@prisma/client'
|
|
|
|
export const metadata: Metadata = { title: 'Monaco Ocean Protection Challenge' }
|
|
|
|
export default async function HomePage() {
|
|
const session = await auth()
|
|
|
|
// Redirect authenticated users to their appropriate dashboard.
|
|
// Reads the multi-role array (roles[]) so a user who is e.g. JURY_MEMBER+MENTOR
|
|
// lands on /jury (their highest-priority role) rather than always falling
|
|
// through on the singular `role` field. The context-aware variant —
|
|
// user.getDefaultDashboard tRPC procedure — exists for surfaces that can call
|
|
// tRPC; page.tsx uses static priority for simplicity.
|
|
if (session?.user) {
|
|
const roles = (session.user.roles as UserRole[] | undefined) ?? [session.user.role as UserRole]
|
|
if (roles.includes('SUPER_ADMIN') || roles.includes('PROGRAM_ADMIN')) redirect('/admin')
|
|
if (roles.includes('JURY_MEMBER')) redirect('/jury')
|
|
if (roles.includes('MENTOR')) redirect('/mentor' as Route)
|
|
if (roles.includes('APPLICANT')) redirect('/applicant' as Route)
|
|
if (roles.includes('OBSERVER')) redirect('/observer')
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-h-screen flex-col">
|
|
{/* Header */}
|
|
<header className="border-b border-border bg-white">
|
|
<div className="container-app flex h-16 items-center justify-between">
|
|
<Image
|
|
src="/images/MOPC-blue-long.png"
|
|
alt="MOPC - Monaco Ocean Protection Challenge"
|
|
width={140}
|
|
height={45}
|
|
className="h-10 w-auto"
|
|
priority
|
|
/>
|
|
<Link
|
|
href="/login"
|
|
className="inline-flex h-10 items-center justify-center rounded-md bg-primary px-6 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
|
|
>
|
|
Sign In
|
|
</Link>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Hero Section */}
|
|
<main className="flex flex-1 flex-col">
|
|
<section className="flex flex-1 flex-col items-center justify-center px-4 py-16 text-center">
|
|
<h1 className="text-display-lg text-brand-blue">
|
|
Monaco Ocean Protection Challenge
|
|
</h1>
|
|
<p className="mt-4 max-w-2xl text-lg text-muted-foreground">
|
|
Supporting innovative solutions for ocean conservation through fair
|
|
and transparent project evaluation.
|
|
</p>
|
|
<div className="mt-8 flex gap-4">
|
|
<Link
|
|
href="/login"
|
|
className="inline-flex h-12 items-center justify-center rounded-md bg-primary px-8 text-base font-medium text-primary-foreground transition-colors hover:bg-primary/90"
|
|
>
|
|
Jury Portal
|
|
</Link>
|
|
<a
|
|
href="https://monaco-opc.com"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-flex h-12 items-center justify-center rounded-md border border-border bg-background px-8 text-base font-medium transition-colors hover:bg-muted"
|
|
>
|
|
Learn More
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Features Section */}
|
|
<section className="border-t border-border bg-muted/30 px-4 py-16">
|
|
<div className="container-app">
|
|
<h2 className="text-center text-heading text-brand-blue">
|
|
Platform Features
|
|
</h2>
|
|
<div className="mt-12 grid gap-8 md:grid-cols-3">
|
|
<div className="rounded-lg border border-border bg-white p-6">
|
|
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
|
|
<svg
|
|
className="h-6 w-6 text-primary"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-subheading text-brand-blue">
|
|
Secure Evaluation
|
|
</h3>
|
|
<p className="mt-2 text-small text-muted-foreground">
|
|
Jury members access only their assigned projects with complete
|
|
confidentiality.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="rounded-lg border border-border bg-white p-6">
|
|
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-accent/10">
|
|
<svg
|
|
className="h-6 w-6 text-accent"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-subheading text-brand-blue">
|
|
Real-time Progress
|
|
</h3>
|
|
<p className="mt-2 text-small text-muted-foreground">
|
|
Track evaluation progress and manage voting windows with
|
|
comprehensive dashboards.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="rounded-lg border border-border bg-white p-6">
|
|
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-brand-teal/10">
|
|
<svg
|
|
className="h-6 w-6 text-brand-teal"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-subheading text-brand-blue">
|
|
Mobile First
|
|
</h3>
|
|
<p className="mt-2 text-small text-muted-foreground">
|
|
Evaluate projects anywhere with a fully responsive design
|
|
optimized for all devices.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
{/* Footer */}
|
|
<footer className="border-t border-border bg-brand-blue py-8 text-white">
|
|
<div className="container-app text-center">
|
|
<p className="text-small">
|
|
© {new Date().getFullYear()} Monaco Ocean Protection Challenge. All
|
|
rights reserved.
|
|
</p>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
)
|
|
}
|