Files
MOPC-Portal/src/app/page.tsx
Matt 7bc2b84d1d
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m5s
refactor(awards): remove AWARD_MASTER role, fold features into jury chair flow
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>
2026-05-07 15:21:09 +02:00

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>
)
}