feat: add PageHeader component and centralize page spacing
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m1s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m1s
- Create reusable PageHeader component (emoji + title + subtitle + actions) - Use PageHeader in sessions, teams, users, objectives pages - Centralize vertical padding in layout (py-6) and remove per-page py-* values Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,7 @@ export default async function GifMoodSessionPage({ params }: GifMoodSessionPageP
|
|||||||
const userTeams = await getUserTeams(authSession.user.id);
|
const userTeams = await getUserTeams(authSession.user.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-4 py-8">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export default function NewGifMoodPage() {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
|
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
|
||||||
const [title, setTitle] = useState(
|
const [title, setTitle] = useState(
|
||||||
() => `GIF Mood - ${new Date().toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' })}`
|
() =>
|
||||||
|
`GIF Mood - ${new Date().toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' })}`
|
||||||
);
|
);
|
||||||
|
|
||||||
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
@@ -48,11 +49,11 @@ export default function NewGifMoodPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-2xl px-4 py-8">
|
<main className="mx-auto max-w-2xl px-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<span>🎭</span>
|
<span>🎞️</span>
|
||||||
Nouveau GIF Mood Board
|
Nouveau GIF Mood Board
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ export default function RootLayout({
|
|||||||
<Providers>
|
<Providers>
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
<Header />
|
<Header />
|
||||||
{children}
|
<div className="py-6">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Providers>
|
</Providers>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default async function MotivatorSessionPage({ params }: MotivatorSessionP
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-4 py-8">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function NewMotivatorSessionPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-2xl px-4 py-8">
|
<main className="mx-auto max-w-2xl px-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { auth } from '@/lib/auth';
|
|||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { getUserOKRs } from '@/services/okrs';
|
import { getUserOKRs } from '@/services/okrs';
|
||||||
import { Card } from '@/components/ui';
|
import { Card, PageHeader } from '@/components/ui';
|
||||||
import { ObjectivesList } from '@/components/okrs/ObjectivesList';
|
import { ObjectivesList } from '@/components/okrs/ObjectivesList';
|
||||||
import { comparePeriods } from '@/lib/okr-utils';
|
import { comparePeriods } from '@/lib/okr-utils';
|
||||||
|
|
||||||
@@ -31,17 +31,12 @@ export default async function ObjectivesPage() {
|
|||||||
const periods = Object.keys(okrsByPeriod).sort(comparePeriods);
|
const periods = Object.keys(okrsByPeriod).sort(comparePeriods);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-4 py-8">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Header */}
|
<PageHeader
|
||||||
<div className="mb-8">
|
emoji="🎯"
|
||||||
<h1 className="text-3xl font-bold text-foreground flex items-center gap-2">
|
title="Mes Objectifs"
|
||||||
<span className="text-3xl">🎯</span>
|
subtitle="Suivez la progression de vos OKRs à travers toutes vos équipes"
|
||||||
Mes Objectifs
|
/>
|
||||||
</h1>
|
|
||||||
<p className="mt-2 text-muted">
|
|
||||||
Suivez la progression de vos OKRs à travers toutes vos équipes
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{okrs.length === 0 ? (
|
{okrs.length === 0 ? (
|
||||||
<Card className="p-12 text-center">
|
<Card className="p-12 text-center">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { WORKSHOPS, getSessionsTabUrl } from '@/lib/workshops';
|
|||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className="mx-auto max-w-7xl px-4 py-12">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="mb-16 text-center">
|
<section className="mb-16 text-center">
|
||||||
<h1 className="mb-4 text-5xl font-bold text-foreground">
|
<h1 className="mb-4 text-5xl font-bold text-foreground">
|
||||||
|
|||||||
@@ -19,18 +19,19 @@ export default async function ProfilePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-2xl px-4 py-8">
|
<main className="mx-auto max-w-2xl px-4">
|
||||||
<div className="mb-8 flex items-center gap-6">
|
<div className="mb-8 flex items-center gap-5">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
src={getGravatarUrl(user.email, 160)}
|
src={getGravatarUrl(user.email, 160)}
|
||||||
alt={user.name || user.email}
|
alt={user.name || user.email}
|
||||||
width={80}
|
width={72}
|
||||||
height={80}
|
height={72}
|
||||||
className="rounded-full border-2 border-border"
|
className="rounded-full border-2 border-border shrink-0"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-foreground">Mon Profil</h1>
|
<h1 className="text-3xl font-bold tracking-tight text-foreground">Mon Profil</h1>
|
||||||
<p className="mt-1 text-muted">Gérez vos informations personnelles</p>
|
<p className="mt-1.5 text-sm text-muted">Gérez vos informations personnelles</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-4 py-8">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default function NewSessionPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-2xl px-4 py-8">
|
<main className="mx-auto max-w-2xl px-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
getGifMoodSessionsByUserId,
|
getGifMoodSessionsByUserId,
|
||||||
getTeamCollaboratorSessionsForAdmin as getTeamGifMoodSessions,
|
getTeamCollaboratorSessionsForAdmin as getTeamGifMoodSessions,
|
||||||
} from '@/services/gif-mood';
|
} from '@/services/gif-mood';
|
||||||
import { Card } from '@/components/ui';
|
import { Card, PageHeader } from '@/components/ui';
|
||||||
import { withWorkshopType } from '@/lib/workshops';
|
import { withWorkshopType } from '@/lib/workshops';
|
||||||
import { WorkshopTabs } from './WorkshopTabs';
|
import { WorkshopTabs } from './WorkshopTabs';
|
||||||
import { NewWorkshopDropdown } from './NewWorkshopDropdown';
|
import { NewWorkshopDropdown } from './NewWorkshopDropdown';
|
||||||
@@ -113,22 +113,17 @@ export default async function SessionsPage() {
|
|||||||
const totalCount = allSessions.length;
|
const totalCount = allSessions.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-4 py-8 sm:py-12">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Header */}
|
<PageHeader
|
||||||
<div className="mb-10 flex flex-col sm:flex-row sm:items-end justify-between gap-6">
|
emoji="🗂️"
|
||||||
<div>
|
title="Mes Ateliers"
|
||||||
<div className="flex items-center gap-3 mb-1.5">
|
subtitle={
|
||||||
<h1 className="text-3xl font-bold tracking-tight text-foreground">Mes Ateliers</h1>
|
totalCount > 0
|
||||||
{totalCount > 0 && (
|
? `${totalCount} atelier${totalCount > 1 ? 's' : ''} · Tous vos ateliers en un seul endroit`
|
||||||
<span className="inline-flex items-center justify-center px-2.5 h-6 rounded-full bg-primary/10 text-primary text-sm font-semibold">
|
: 'Tous vos ateliers en un seul endroit'
|
||||||
{totalCount}
|
}
|
||||||
</span>
|
actions={<NewWorkshopDropdown />}
|
||||||
)}
|
/>
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted">Tous vos ateliers en un seul endroit</p>
|
|
||||||
</div>
|
|
||||||
<NewWorkshopDropdown />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
{hasNoSessions ? (
|
{hasNoSessions ? (
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export default function EditOKRPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
<main className="mx-auto max-w-4xl px-4">
|
||||||
<div className="text-center">Chargement...</div>
|
<div className="text-center">Chargement...</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
@@ -129,7 +129,7 @@ export default function EditOKRPage() {
|
|||||||
|
|
||||||
if (!okr) {
|
if (!okr) {
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
<main className="mx-auto max-w-4xl px-4">
|
||||||
<div className="text-center">OKR non trouvé</div>
|
<div className="text-center">OKR non trouvé</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
@@ -147,7 +147,7 @@ export default function EditOKRPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
<main className="mx-auto max-w-4xl px-4">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Link href={`/teams/${teamId}/okrs/${okrId}`} className="text-muted hover:text-foreground">
|
<Link href={`/teams/${teamId}/okrs/${okrId}`} className="text-muted hover:text-foreground">
|
||||||
← Retour à l'OKR
|
← Retour à l'OKR
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export default function OKRDetailPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
<main className="mx-auto max-w-4xl px-4">
|
||||||
<div className="text-center">Chargement...</div>
|
<div className="text-center">Chargement...</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
@@ -132,7 +132,7 @@ export default function OKRDetailPage() {
|
|||||||
|
|
||||||
if (!okr) {
|
if (!okr) {
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
<main className="mx-auto max-w-4xl px-4">
|
||||||
<div className="text-center">OKR non trouvé</div>
|
<div className="text-center">OKR non trouvé</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
@@ -145,7 +145,7 @@ export default function OKRDetailPage() {
|
|||||||
const canDelete = okr.permissions?.canDelete ?? false;
|
const canDelete = okr.permissions?.canDelete ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
<main className="mx-auto max-w-4xl px-4">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Link href={`/teams/${teamId}`} className="text-muted hover:text-foreground">
|
<Link href={`/teams/${teamId}`} className="text-muted hover:text-foreground">
|
||||||
← Retour à l'équipe
|
← Retour à l'équipe
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ export default function NewOKRPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
<main className="mx-auto max-w-4xl px-4">
|
||||||
<div className="text-center">Chargement...</div>
|
<div className="text-center">Chargement...</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
<main className="mx-auto max-w-4xl px-4">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Link href={`/teams/${teamId}`} className="text-muted hover:text-foreground">
|
<Link href={`/teams/${teamId}`} className="text-muted hover:text-foreground">
|
||||||
← Retour à l'équipe
|
← Retour à l'équipe
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default async function TeamDetailPage({ params }: TeamDetailPageProps) {
|
|||||||
const okrsData = await getTeamOKRs(id);
|
const okrsData = await getTeamOKRs(id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-4 py-8">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default function NewTeamPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-2xl px-4 py-8">
|
<main className="mx-auto max-w-2xl px-4">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Link href="/teams" className="text-muted hover:text-foreground">
|
<Link href="/teams" className="text-muted hover:text-foreground">
|
||||||
← Retour aux équipes
|
← Retour aux équipes
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { auth } from '@/lib/auth';
|
|||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { TeamCard } from '@/components/teams';
|
import { TeamCard } from '@/components/teams';
|
||||||
import { Button } from '@/components/ui';
|
import { Button, PageHeader } from '@/components/ui';
|
||||||
import { getUserTeams } from '@/services/teams';
|
import { getUserTeams } from '@/services/teams';
|
||||||
|
|
||||||
export default async function TeamsPage() {
|
export default async function TeamsPage() {
|
||||||
@@ -15,23 +15,19 @@ export default async function TeamsPage() {
|
|||||||
const teams = await getUserTeams(session.user.id);
|
const teams = await getUserTeams(session.user.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-4 py-8">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Header */}
|
<PageHeader
|
||||||
<div className="mb-8 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
emoji="👥"
|
||||||
<div>
|
title="Équipes"
|
||||||
<h1 className="text-3xl font-bold text-foreground">Équipes</h1>
|
subtitle={`${teams.length} équipe${teams.length !== 1 ? 's' : ''} · Collaborez et définissez vos OKRs`}
|
||||||
<p className="mt-1 text-muted">
|
actions={
|
||||||
{teams.length} équipe{teams.length !== 1 ? 's' : ''}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Link href="/teams/new">
|
<Link href="/teams/new">
|
||||||
<Button className="bg-[var(--purple)] text-white hover:opacity-90 border-transparent">
|
<Button className="bg-[var(--purple)] text-white hover:opacity-90 border-transparent">
|
||||||
Créer une équipe
|
Créer une équipe
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
}
|
||||||
</div>
|
/>
|
||||||
|
|
||||||
{/* Teams Grid */}
|
{/* Teams Grid */}
|
||||||
{teams.length > 0 ? (
|
{teams.length > 0 ? (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { auth } from '@/lib/auth';
|
|||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import { getAllUsersWithStats } from '@/services/auth';
|
import { getAllUsersWithStats } from '@/services/auth';
|
||||||
import { getGravatarUrl } from '@/lib/gravatar';
|
import { getGravatarUrl } from '@/lib/gravatar';
|
||||||
|
import { PageHeader } from '@/components/ui';
|
||||||
|
|
||||||
function formatRelativeTime(date: Date): string {
|
function formatRelativeTime(date: Date): string {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -33,15 +34,12 @@ export default async function UsersPage() {
|
|||||||
const avgSessionsPerUser = users.length > 0 ? totalSessions / users.length : 0;
|
const avgSessionsPerUser = users.length > 0 ? totalSessions / users.length : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-6xl px-4 py-8">
|
<main className="mx-auto max-w-6xl px-4">
|
||||||
{/* Header */}
|
<PageHeader
|
||||||
<div className="mb-8">
|
emoji="🧑💻"
|
||||||
<h1 className="text-3xl font-bold text-foreground">Utilisateurs</h1>
|
title="Utilisateurs"
|
||||||
<p className="mt-1 text-muted">
|
subtitle={`${users.length} utilisateur${users.length > 1 ? 's' : ''} inscrit${users.length > 1 ? 's' : ''} · Vue d'ensemble de la communauté`}
|
||||||
{users.length} utilisateur{users.length > 1 ? 's' : ''} inscrit
|
/>
|
||||||
{users.length > 1 ? 's' : ''}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Global Stats */}
|
{/* Global Stats */}
|
||||||
<div className="mb-8 grid grid-cols-2 gap-4 sm:grid-cols-4">
|
<div className="mb-8 grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default async function WeatherSessionPage({ params }: WeatherSessionPageP
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-4 py-8">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function NewWeatherPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-2xl px-4 py-8">
|
<main className="mx-auto max-w-2xl px-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export default async function WeeklyCheckInSessionPage({ params }: WeeklyCheckIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-4 py-8">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function NewWeeklyCheckInPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-2xl px-4 py-8">
|
<main className="mx-auto max-w-2xl px-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default async function YearReviewSessionPage({ params }: YearReviewSessio
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-4 py-8">
|
<main className="mx-auto max-w-7xl px-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export default function NewYearReviewPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-2xl px-4 py-8">
|
<main className="mx-auto max-w-2xl px-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
|||||||
24
src/components/ui/PageHeader.tsx
Normal file
24
src/components/ui/PageHeader.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface PageHeaderProps {
|
||||||
|
emoji: string;
|
||||||
|
title: string;
|
||||||
|
subtitle?: ReactNode;
|
||||||
|
actions?: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PageHeader({ emoji, title, subtitle, actions, className }: PageHeaderProps) {
|
||||||
|
return (
|
||||||
|
<div className={`mb-8 flex flex-col sm:flex-row sm:items-center justify-between gap-4 ${className ?? ''}`}>
|
||||||
|
<div>
|
||||||
|
<h1 className="flex items-center gap-3 text-3xl font-bold tracking-tight text-foreground">
|
||||||
|
<span className="text-3xl leading-none">{emoji}</span>
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
{subtitle && <p className="mt-1.5 text-sm text-muted">{subtitle}</p>}
|
||||||
|
</div>
|
||||||
|
{actions && <div className="shrink-0">{actions}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ export { EditableMotivatorTitle } from './EditableMotivatorTitle';
|
|||||||
export { EditableYearReviewTitle } from './EditableYearReviewTitle';
|
export { EditableYearReviewTitle } from './EditableYearReviewTitle';
|
||||||
export { EditableWeeklyCheckInTitle } from './EditableWeeklyCheckInTitle';
|
export { EditableWeeklyCheckInTitle } from './EditableWeeklyCheckInTitle';
|
||||||
export { EditableWeatherTitle } from './EditableWeatherTitle';
|
export { EditableWeatherTitle } from './EditableWeatherTitle';
|
||||||
|
export { PageHeader } from './PageHeader';
|
||||||
export { Input } from './Input';
|
export { Input } from './Input';
|
||||||
export { ParticipantInput } from './ParticipantInput';
|
export { ParticipantInput } from './ParticipantInput';
|
||||||
export { Modal, ModalFooter } from './Modal';
|
export { Modal, ModalFooter } from './Modal';
|
||||||
|
|||||||
Reference in New Issue
Block a user