From 3b63a7b98b947c322284e6298698cc257aabc70a Mon Sep 17 00:00:00 2001 From: Jalil Arfaoui Date: Sun, 8 Mar 2026 00:12:30 +0100 Subject: [PATCH] feat: page profil /me avec dashboard compact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Identité (nom, email) avec bouton Inviter - Carte réputation avec score, rang, barre de progression vers le prochain rang - Lien vers l'historique de réputation - Le nom dans le header devient un lien vers /me - Ajout getNextRankThreshold dans permissions --- app/actions/get-authenticated-contributor.ts | 7 +- app/me/me.module.css | 122 +++++++++++++++++++ app/me/page.tsx | 71 +++++++++++ components/auth/AuthSection/index.tsx | 8 +- domain/reputation/permissions.ts | 8 ++ 5 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 app/me/me.module.css create mode 100644 app/me/page.tsx diff --git a/app/actions/get-authenticated-contributor.ts b/app/actions/get-authenticated-contributor.ts index ec69984..4cf6c3e 100644 --- a/app/actions/get-authenticated-contributor.ts +++ b/app/actions/get-authenticated-contributor.ts @@ -17,5 +17,10 @@ export async function getAuthenticatedContributor() { if (!data) return null - return { id: data.id, reputation: data.reputation ?? 0 } + return { + id: data.id, + reputation: data.reputation ?? 0, + name: (user.user_metadata?.name as string) ?? null, + email: user.email ?? null, + } } diff --git a/app/me/me.module.css b/app/me/me.module.css new file mode 100644 index 0000000..2592ba9 --- /dev/null +++ b/app/me/me.module.css @@ -0,0 +1,122 @@ +.title { + font-family: var(--font-gotham-bold); + font-size: 2em; + color: var(--debats-red); + text-transform: uppercase; + letter-spacing: 2px; + margin: 0 0 32px; +} + +.identity { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 0; + margin-bottom: 24px; + border-bottom: 1px solid var(--border-light); +} + +.identityInfo { + display: flex; + flex-direction: column; + gap: 4px; +} + +.displayName { + font-family: var(--font-gotham-bold); + font-size: 1.2em; + color: var(--text-dark); +} + +.email { + font-family: var(--font-gotham-book); + font-size: 13px; + color: var(--text-light); +} + +.reputationCard { + background: var(--bg-light, #fafafa); + border: 1px solid var(--border-light); + border-radius: 8px; + padding: 24px; +} + +.reputationHeader { + display: flex; + align-items: baseline; + justify-content: space-between; + margin-bottom: 16px; +} + +.reputationScore { + display: flex; + align-items: baseline; + gap: 8px; +} + +.score { + font-family: var(--font-gotham-bold); + font-size: 2em; + color: var(--text-dark); +} + +.scoreLabel { + font-family: var(--font-gotham-book); + font-size: 14px; + color: var(--text-light); +} + +.rank { + font-family: var(--font-gotham-bold); + font-size: 14px; + color: var(--debats-red); + text-transform: uppercase; + letter-spacing: 1px; +} + +.progressBar { + height: 6px; + background: var(--border-light); + border-radius: 3px; + overflow: hidden; + margin-bottom: 8px; +} + +.progressFill { + height: 100%; + background: var(--debats-red); + border-radius: 3px; + transition: width 0.3s ease; +} + +.progressLabel { + font-family: var(--font-gotham-book); + font-size: 12px; + color: var(--text-light); +} + +.historyLink { + display: inline-block; + margin-top: 16px; + font-family: var(--font-gotham-book); + font-size: 13px; + color: var(--debats-red); + text-decoration: none; +} + +.historyLink:hover { + text-decoration: underline; +} + +@media (max-width: 768px) { + .identity { + flex-direction: column; + align-items: flex-start; + gap: 16px; + } + + .reputationHeader { + flex-direction: column; + gap: 8px; + } +} diff --git a/app/me/page.tsx b/app/me/page.tsx new file mode 100644 index 0000000..7607a11 --- /dev/null +++ b/app/me/page.tsx @@ -0,0 +1,71 @@ +import { redirect } from 'next/navigation' +import Link from 'next/link' +import { getAuthenticatedContributor } from '../actions/get-authenticated-contributor' +import { getRank, getNextRankThreshold } from '../../domain/reputation/permissions' +import ContentWithSidebar from '../../components/layout/ContentWithSidebar' +import Button from '../../components/ui/Button' +import styles from './me.module.css' + +export const metadata = { + title: 'Mon compte — Débats.co', +} + +function formatScore(n: number): string { + return n.toLocaleString('fr-FR') +} + +export default async function MePage() { + const contributor = await getAuthenticatedContributor() + + if (!contributor) { + redirect( + '/?notice=' + encodeURIComponent('Vous devez être connecté·e pour accéder à votre profil.'), + ) + } + + const displayName = contributor.name ?? contributor.email ?? 'Contributeur·rice' + const rank = getRank(contributor.reputation) + const nextThreshold = getNextRankThreshold(contributor.reputation) + const progress = nextThreshold + ? Math.min((contributor.reputation / nextThreshold) * 100, 100) + : 100 + + return ( + +

Mon compte

+ +
+
+ {displayName} + {contributor.name && contributor.email && ( + {contributor.email} + )} +
+ +
+ +
+
+
+ {formatScore(contributor.reputation)} + points +
+ {rank} +
+
+
+
+ {nextThreshold && ( + + {formatScore(nextThreshold - contributor.reputation)} pts avant le prochain rang + + )} + + Voir l'historique + +
+ + ) +} diff --git a/components/auth/AuthSection/index.tsx b/components/auth/AuthSection/index.tsx index 35bc83d..fad948f 100644 --- a/components/auth/AuthSection/index.tsx +++ b/components/auth/AuthSection/index.tsx @@ -30,12 +30,8 @@ export default function AuthSection() { const displayName = user.user_metadata?.name || user.email return (
- {displayName} - - Réputation - - - Inviter + + {displayName}