diff --git a/src/app/(site)/staff/page.tsx b/src/app/(site)/staff/page.tsx index eba60771..80a5b19a 100644 --- a/src/app/(site)/staff/page.tsx +++ b/src/app/(site)/staff/page.tsx @@ -2,9 +2,12 @@ import { asc, desc, eq, gte } from "drizzle-orm"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import Link from "@/components/link"; -import { ContentCard, EmptyState } from "@/components/public/ui"; +import { ProfileImage } from "@/components/shared/profile-image"; +import { SurfaceCard } from "@/components/surface-card"; import { db, User, WebsiteTeams } from "@/lib/db"; +import { avatarImageUrl } from "@/lib/format"; import { siteSettings } from "@/lib/services/site-settings"; +import styles from "./staff.module.css"; export async function generateMetadata(): Promise { const t = await getTranslations("pages.staff"); @@ -52,7 +55,7 @@ export default async function StaffPage() { .where(gte(User.rank, minStaffRank)) .orderBy(desc(User.rank), asc(User.username)) .limit(100) - .catch((): StaffMember[] => []), + .catch((): StaffMember[] | null => null), db .select({ id: WebsiteTeams.id, @@ -69,71 +72,92 @@ export default async function StaffPage() { ]); return ( -
- - - {teams.length > 0 ? ( - -
- {teams.map((team) => ( -
-

- {team.rankName} -

- {team.jobDescription ? ( -

- {team.jobDescription} -

- ) : null} -
- ))} -
-
- ) : null} - - - {staff.length === 0 ? ( - {t("emptyState")} - ) : ( -
- - - - - - - - - - {staff.map((u) => ( - - - - - - ))} - -
{t("colUsername")}{t("colRank")}{t("colMotto")}
- {u.username} - {u.rank} - {u.motto || "—"} -
-
+
+ +
+

{t("title")}

+

{t("subtitle")}

+
+ {staff && staff.length > 0 && ( + + {t("membersSubtitle", { count: staff.length })} + )} - +
+
+

+ {t("membersTitle")} +

+ {staff === null ? ( + +

{t("loadError")}

+ + {t("retry")} + +
+ ) : staff.length === 0 ? ( + +

{t("emptyState")}

+
+ ) : ( +
    + {staff.map((member) => ( +
  • + + +
    +

    {member.username}

    +

    {member.motto || t("noMotto")}

    + + {t("viewProfile")} + +
    + +
  • + ))} +
+ )} +
+ {teams.length > 0 && ( +
+

+ {t("ranksTitle")} +

+

{t("ranksSubtitle")}

+
    + {teams.map((team) => ( +
  • + + +
  • + ))} +
+
+ )}
); } diff --git a/src/app/(site)/staff/staff.module.css b/src/app/(site)/staff/staff.module.css new file mode 100644 index 00000000..06c3af8f --- /dev/null +++ b/src/app/(site)/staff/staff.module.css @@ -0,0 +1,148 @@ +.page { + display: grid; + gap: 28px; + min-width: 0; + padding-bottom: 24px; +} +.hero { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 20px; + padding: 28px; + background: linear-gradient( + 130deg, + color-mix(in srgb, var(--color-primary) 10%, var(--color-surface)), + var(--color-surface) + ); +} +.hero h1 { + margin: 0 0 6px; + font-size: clamp(1.6rem, 3vw, 2rem); +} +.hero p, +.subtitle { + color: var(--color-text-muted); + margin: 0; +} +.count { + font-size: 0.85rem; + padding: 10px 14px; + border-radius: 12px; + background: var(--color-surface); + color: var(--color-text-readable); +} +.heading { + font-size: 1.15rem; + margin: 0 0 14px; +} +.subtitle { + margin: -6px 0 18px; + font-size: 0.9rem; +} +.grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + list-style: none; + margin: 0; + padding: 0; +} +.grid li { + min-width: 0; +} +.member { + display: flex; + align-items: center; + gap: 18px; + height: 100%; + padding: 20px; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 16px; + color: var(--color-text-readable); + text-decoration: none; + transition: + border-color 0.15s, + box-shadow 0.15s; +} +.member:hover { + border-color: var(--color-primary); + box-shadow: 0 4px 16px + color-mix(in srgb, var(--color-primary) 10%, transparent); +} +.member:focus-visible { + outline: 3px solid var(--color-primary); + outline-offset: 4px; +} +.avatar { + flex-shrink: 0; + image-rendering: pixelated; + object-fit: contain; + border-radius: 12px; + background: color-mix(in srgb, var(--color-primary) 8%, transparent); +} +.identity { + min-width: 0; + overflow-wrap: anywhere; +} +.identity h3, +.team h3 { + margin: 0 0 8px; + font-size: 1.05rem; +} +.identity p, +.team p { + margin: 0; + font-size: 0.9rem; + color: var(--color-text-muted); + white-space: pre-line; + overflow-wrap: anywhere; +} +.profile { + display: inline-block; + margin-top: 16px; + font-size: 0.8rem; + font-weight: 600; +} +.team { + position: relative; + height: 100%; + padding: 22px; + overflow-wrap: anywhere; +} +.accent { + display: block; + width: 32px; + height: 4px; + border-radius: 4px; + margin-bottom: 16px; +} +.notice { + padding: 24px; +} +.notice p { + margin: 0 0 12px; +} +@media (max-width: 1000px) { + .grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} +@media (max-width: 600px) { + .grid { + grid-template-columns: minmax(0, 1fr); + } + .hero { + padding: 22px; + } + .member { + padding: 18px; + } +} +@media (prefers-reduced-motion: reduce) { + .member { + transition: none; + } +} diff --git a/src/messages/en.json b/src/messages/en.json index 8ad5935d..ef5ac5d6 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -67,7 +67,7 @@ "staff": { "title": "Staff", "subtitle": "The team behind the hotel", - "ranksTitle": "Ranks", + "ranksTitle": "Our teams", "ranksSubtitle": "What each part of the team takes care of", "membersTitle": "Team members", "membersEmptySubtitle": "Nobody to show right now", @@ -75,7 +75,11 @@ "emptyState": "No staff members to show.", "colUsername": "Username", "colRank": "Rank", - "colMotto": "Motto" + "colMotto": "Motto", + "viewProfile": "View profile", + "noMotto": "No motto set", + "loadError": "Unable to load staff members. Please try again shortly.", + "retry": "Try again" }, "leaderboard": { "title": "Leaderboard", diff --git a/src/messages/it.json b/src/messages/it.json index 15413e62..7478507b 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -67,7 +67,7 @@ "staff": { "title": "Staff", "subtitle": "Il team dietro l'hotel", - "ranksTitle": "Ruoli", + "ranksTitle": "I nostri team", "ranksSubtitle": "Di cosa si occupa ogni parte del team", "membersTitle": "Membri del team", "membersEmptySubtitle": "Nessuno da mostrare al momento", @@ -75,7 +75,11 @@ "emptyState": "Nessun membro dello staff da mostrare.", "colUsername": "Nome utente", "colRank": "Ruolo", - "colMotto": "Motto" + "colMotto": "Motto", + "viewProfile": "Visita il profilo", + "noMotto": "Nessun motto impostato", + "loadError": "Impossibile caricare i membri dello staff. Riprova tra poco.", + "retry": "Riprova" }, "leaderboard": { "title": "Classifica",