Retire Le Calendrier des Dieux du menu, affiche la date du Tribunal sur L'Agora
Build and deploy / deploy (push) Successful in 35s
Build and deploy / deploy (push) Successful in 35s
/calendrier disparaît de la navigation (route toujours fonctionnelle
pour les Archontes qui y accèdent directement) : la date du Tribunal,
seule info largement utile au quotidien, est reprise en en-tête de
/leaderboard. Extrait la logique de décompte dans un helper partagé
src/lib/tribunal-date.ts plutôt que de la dupliquer.
Corrige au passage un espace JSX manquant sur /urne oublié lors du
dernier commit ("Tu as voté pour Xaujourd'hui").
This commit is contained in:
@@ -136,13 +136,13 @@ Ce pattern (RLS pour l'accès à la ligne + trigger `BEFORE UPDATE` pour l'accè
|
|||||||
| `/` | Redirige vers `/leaderboard` si connecté, sinon `/login` |
|
| `/` | Redirige vers `/leaderboard` si connecté, sinon `/login` |
|
||||||
| `/login` | Email + mot de passe |
|
| `/login` | Email + mot de passe |
|
||||||
| `/signup` | Email + mot de passe + pseudo + photo (recadrée) ; gère l'attente de confirmation email |
|
| `/signup` | Email + mot de passe + pseudo + photo (recadrée) ; gère l'attente de confirmation email |
|
||||||
| `/leaderboard` | « L'Agora » — podium top 3 + liste, Realtime, contrôles de points pour les juges |
|
| `/leaderboard` | « L'Agora » — podium top 3 + liste, Realtime, contrôles de points pour les juges, date/décompte du Tribunal en en-tête (`src/lib/tribunal-date.ts`, partagé avec `/calendrier`) |
|
||||||
| `/icare` | « Le Vol d'Icare » — Flappy Bird grec, record personnel all-time, scores figés au Tribunal, points attribués automatiquement |
|
| `/icare` | « Le Vol d'Icare » — Flappy Bird grec, record personnel all-time, scores figés au Tribunal, points attribués automatiquement |
|
||||||
| `/char` | « Le Jeu de l'Agora » — format paysage (projeté) : Le Char (2 colonnes de 3, boutons de duel), Le Gardien du Silence (tirage aléatoire, minuteur discret) et La Roulette (tirage au sort animé) |
|
| `/char` | « Le Jeu de l'Agora » — format paysage (projeté) : Le Char (2 colonnes de 3, boutons de duel), Le Gardien du Silence (tirage aléatoire, minuteur discret) et La Roulette (tirage au sort animé) |
|
||||||
| `/char/questions` | Banque de questions du Char (ajout/édition/réordonnancement/afficher-masquer) + modération des suggestions des Citoyens (ajouter à la banque/rejeter) — juges uniquement, pilotée depuis un téléphone pendant que `/char` est projetée |
|
| `/char/questions` | Banque de questions du Char (ajout/édition/réordonnancement/afficher-masquer) + modération des suggestions des Citoyens (ajouter à la banque/rejeter) — juges uniquement, pilotée depuis un téléphone pendant que `/char` est projetée |
|
||||||
| `/mur` | « Le Mur de la Honte » — une note anonyme par Citoyen par jour (dix places, remis à zéro chaque jour) ; les Archontes y voient en plus l'historique complet avec l'auteur de chaque note |
|
| `/mur` | « Le Mur de la Honte » — une note anonyme par Citoyen par jour (dix places, remis à zéro chaque jour) ; les Archontes y voient en plus l'historique complet avec l'auteur de chaque note |
|
||||||
| `/urne` | « L'Urne de l'Agora » — un jeton par Citoyen par jour sur une autre personne, classement cumulatif public identique pour tous, Archontes compris (aucune vision privilégiée ici) |
|
| `/urne` | « L'Urne de l'Agora » — un jeton par Citoyen par jour sur une autre personne, classement cumulatif public identique pour tous, Archontes compris (aucune vision privilégiée ici) |
|
||||||
| `/calendrier` | « Le Calendrier des Dieux » — agenda de la semaine, panthéon, date du Tribunal, édition réservée aux juges |
|
| `/calendrier` | « Le Calendrier des Dieux » — agenda de la semaine, panthéon, date du Tribunal, édition réservée aux juges. Retirée du menu de navigation (la date du Tribunal, seule info largement utile au quotidien, est reprise sur `/leaderboard`) mais la route reste fonctionnelle — les juges y accèdent directement par l'URL pour gérer le calendrier et changer la date |
|
||||||
| `/journal` | « Le Crieur » — fil live des décrets (attributions de points), lecture pour tous |
|
| `/journal` | « Le Crieur » — fil live des décrets (attributions de points), lecture pour tous |
|
||||||
| `/profile` | Pseudo (verrouillable), photo, déconnexion, et pour les Citoyens : proposition d'une question pour Le Char (modifiable jusqu'au Tribunal) |
|
| `/profile` | Pseudo (verrouillable), photo, déconnexion, et pour les Citoyens : proposition d'une question pour Le Char (modifiable jusqu'au Tribunal) |
|
||||||
| `/admin` | « Admin » — juges uniquement, **vérifié côté serveur** (Server Component), habillée d'une police monospace (rupture volontaire avec le thème grec) — gestion des membres, reset du repère de classement |
|
| `/admin` | « Admin » — juges uniquement, **vérifié côté serveur** (Server Component), habillée d'une police monospace (rupture volontaire avec le thème grec) — gestion des membres, reset du repère de classement |
|
||||||
|
|||||||
@@ -5,20 +5,7 @@ import { createClient, waitForRealtimeAuth } from "@/lib/supabase/client";
|
|||||||
import { DayCard, type DayRow } from "./day-card";
|
import { DayCard, type DayRow } from "./day-card";
|
||||||
import { PANTHEON_PRESETS, getGodPreset } from "@/lib/pantheon";
|
import { PANTHEON_PRESETS, getGodPreset } from "@/lib/pantheon";
|
||||||
import { IconPlus } from "@/components/icons";
|
import { IconPlus } from "@/components/icons";
|
||||||
|
import { formatCountdown } from "@/lib/tribunal-date";
|
||||||
function formatCountdown(tribunalDate: string | null): string {
|
|
||||||
if (!tribunalDate) return "Aucune date n'a encore été fixée par les Archontes.";
|
|
||||||
|
|
||||||
const target = new Date(tribunalDate);
|
|
||||||
const now = new Date();
|
|
||||||
const diffMs = target.getTime() - now.getTime();
|
|
||||||
const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
|
|
||||||
|
|
||||||
if (diffMs < 0) return "Le Tribunal a rendu son verdict.";
|
|
||||||
if (diffDays === 0) return "Le Tribunal siège aujourd'hui !";
|
|
||||||
if (diffDays === 1) return "Le Tribunal siège demain.";
|
|
||||||
return `Le Tribunal siège dans ${diffDays} jours.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CalendrierView({
|
export function CalendrierView({
|
||||||
isJudge,
|
isJudge,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { redirect } from "next/navigation";
|
|||||||
import { createClient } from "@/lib/supabase/server";
|
import { createClient } from "@/lib/supabase/server";
|
||||||
import { LeaderboardView } from "./leaderboard-view";
|
import { LeaderboardView } from "./leaderboard-view";
|
||||||
import { AgoraLogo } from "@/components/agora-logo";
|
import { AgoraLogo } from "@/components/agora-logo";
|
||||||
|
import { formatCountdown, formatTribunalDateTime } from "@/lib/tribunal-date";
|
||||||
|
|
||||||
export default async function LeaderboardPage() {
|
export default async function LeaderboardPage() {
|
||||||
const supabase = await createClient();
|
const supabase = await createClient();
|
||||||
@@ -13,18 +14,26 @@ export default async function LeaderboardPage() {
|
|||||||
redirect("/login");
|
redirect("/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: profiles, error } = await supabase
|
const [{ data: profiles, error }, { data: settings }] = await Promise.all([
|
||||||
.from("profiles")
|
supabase
|
||||||
.select("id, pseudo, avatar_url, points, previous_rank, role")
|
.from("profiles")
|
||||||
.order("points", { ascending: false });
|
.select("id, pseudo, avatar_url, points, previous_rank, role")
|
||||||
|
.order("points", { ascending: false }),
|
||||||
|
supabase.from("settings").select("tribunal_date").eq("id", true).single(),
|
||||||
|
]);
|
||||||
|
|
||||||
const isJudge = profiles?.find((p) => p.id === user.id)?.role === "judge";
|
const isJudge = profiles?.find((p) => p.id === user.id)?.role === "judge";
|
||||||
|
const tribunalDate = settings?.tribunal_date ?? null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-2xl px-4 py-8">
|
<div className="mx-auto w-full max-w-2xl px-4 py-8">
|
||||||
<div className="mb-6 flex flex-col items-center text-center">
|
<div className="mb-6 flex flex-col items-center text-center">
|
||||||
<AgoraLogo className="h-32 w-32 sm:h-40 sm:w-40" />
|
<AgoraLogo className="h-32 w-32 sm:h-40 sm:w-40" />
|
||||||
<p className="mt-2 font-serif text-sm text-marble/60 italic">Le classement des Citoyens</p>
|
<p className="mt-2 font-serif text-sm text-marble/60 italic">Le classement des Citoyens</p>
|
||||||
|
<p className="mt-3 font-heading text-sm tracking-wide text-gold-bright uppercase">
|
||||||
|
{formatCountdown(tribunalDate)}
|
||||||
|
</p>
|
||||||
|
{tribunalDate && <p className="text-xs text-marble/50">{formatTribunalDateTime(tribunalDate)}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ export function UrneView({
|
|||||||
{myVoteToday ? (
|
{myVoteToday ? (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-olive">
|
<p className="text-sm text-olive">
|
||||||
Tu as voté pour {myVoteTarget?.pseudo ?? "quelqu'un"} aujourd'hui.
|
Tu as voté pour {myVoteTarget?.pseudo ?? "quelqu'un"}{" "}
|
||||||
|
aujourd'hui.
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-2 text-xs text-text-mut">Reviens demain pour déposer un nouveau jeton.</p>
|
<p className="mt-2 text-xs text-text-mut">Reviens demain pour déposer un nouveau jeton.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
IconTerminal,
|
IconTerminal,
|
||||||
IconLogout,
|
IconLogout,
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconCalendar,
|
|
||||||
IconWings,
|
IconWings,
|
||||||
IconSword,
|
IconSword,
|
||||||
IconPin,
|
IconPin,
|
||||||
@@ -99,7 +98,6 @@ export function Header({
|
|||||||
{ href: "/char", label: "Le Jeu de l'Agora", icon: <IconSword /> },
|
{ href: "/char", label: "Le Jeu de l'Agora", icon: <IconSword /> },
|
||||||
{ href: "/mur", label: "Le Mur de la Honte", icon: <IconPin /> },
|
{ href: "/mur", label: "Le Mur de la Honte", icon: <IconPin /> },
|
||||||
{ href: "/urne", label: "L'Urne de l'Agora", icon: <IconVote /> },
|
{ href: "/urne", label: "L'Urne de l'Agora", icon: <IconVote /> },
|
||||||
{ href: "/calendrier", label: "Le Calendrier des Dieux", icon: <IconCalendar /> },
|
|
||||||
{ href: "/journal", label: "Le Crieur", icon: <IconScroll /> },
|
{ href: "/journal", label: "Le Crieur", icon: <IconScroll /> },
|
||||||
{ href: "/profile", label: "Mon profil", icon: <IconPerson /> },
|
{ href: "/profile", label: "Mon profil", icon: <IconPerson /> },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Partagé entre /calendrier (édition de la date par les Archontes) et
|
||||||
|
// /leaderboard (affichage seul) — évite de dupliquer la même logique de
|
||||||
|
// décompte à deux endroits.
|
||||||
|
export function formatCountdown(tribunalDate: string | null): string {
|
||||||
|
if (!tribunalDate) return "Aucune date n'a encore été fixée par les Archontes.";
|
||||||
|
|
||||||
|
const target = new Date(tribunalDate);
|
||||||
|
const now = new Date();
|
||||||
|
const diffMs = target.getTime() - now.getTime();
|
||||||
|
const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (diffMs < 0) return "Le Tribunal a rendu son verdict.";
|
||||||
|
if (diffDays === 0) return "Le Tribunal siège aujourd'hui !";
|
||||||
|
if (diffDays === 1) return "Le Tribunal siège demain.";
|
||||||
|
return `Le Tribunal siège dans ${diffDays} jours.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTribunalDateTime(tribunalDate: string): string {
|
||||||
|
const label = new Date(tribunalDate).toLocaleString("fr-FR", {
|
||||||
|
weekday: "long",
|
||||||
|
day: "numeric",
|
||||||
|
month: "long",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
timeZone: "Europe/Paris",
|
||||||
|
});
|
||||||
|
return label.charAt(0).toUpperCase() + label.slice(1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user