diff --git a/app/(main)/(auth)/applications/[id]/page.tsx b/app/(main)/(auth)/applications/[id]/page.tsx index 1d8cd6e9..14dc1df3 100644 --- a/app/(main)/(auth)/applications/[id]/page.tsx +++ b/app/(main)/(auth)/applications/[id]/page.tsx @@ -1,3 +1,4 @@ +import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { getApplicationForReview } from '@/prisma/data/applications'; @@ -21,6 +22,16 @@ interface ApplicationDetailPageProps { params: Promise<{ id: string }>; } +export async function generateMetadata({ + params, +}: ApplicationDetailPageProps): Promise { + const { id } = await params; + const user = await getCurrentUser(); + const application = await getApplicationForReview(id, user); + if (!application) return {}; + return { title: application.user.name ?? application.user.email }; +} + export default async function ApplicationDetailPage({ params, }: ApplicationDetailPageProps) { diff --git a/app/(main)/(auth)/applications/page.tsx b/app/(main)/(auth)/applications/page.tsx index de666e31..829d3efa 100644 --- a/app/(main)/(auth)/applications/page.tsx +++ b/app/(main)/(auth)/applications/page.tsx @@ -1,3 +1,4 @@ +import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; import { @@ -20,6 +21,8 @@ import type { import { ApplicationsTable } from '@/components/features/applications-table'; import { ApplicationsToolbar } from '@/components/features/applications-toolbar'; +export const metadata: Metadata = { title: 'Applications' }; + interface ApplicationsPageProps { searchParams: Promise>; } diff --git a/app/(main)/(auth)/global-questions/page.tsx b/app/(main)/(auth)/global-questions/page.tsx index 5a05d56f..bbc97882 100644 --- a/app/(main)/(auth)/global-questions/page.tsx +++ b/app/(main)/(auth)/global-questions/page.tsx @@ -1,3 +1,4 @@ +import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; import { getGlobalQuestions } from '@/prisma/data/global-questions'; @@ -9,6 +10,8 @@ import { GlobalQuestionsTable } from '@/components/features/global-questions-tab import { PageHeader } from '@/components/layouts/page-header'; import { Button } from '@/components/ui/button'; +export const metadata: Metadata = { title: 'Global Questions' }; + export default async function GlobalQuestionsPage() { const user = await getCurrentUser(); if (!user.isAdmin) redirect('/'); diff --git a/app/(main)/(auth)/my-applications/page.tsx b/app/(main)/(auth)/my-applications/page.tsx index 7703bf89..c1c7da3f 100644 --- a/app/(main)/(auth)/my-applications/page.tsx +++ b/app/(main)/(auth)/my-applications/page.tsx @@ -1,3 +1,5 @@ +import type { Metadata } from 'next'; + import { getMyApplications } from '@/prisma/data/applications'; import { getCurrentUser } from '@/lib/auth/server'; @@ -5,6 +7,8 @@ import { getCurrentUser } from '@/lib/auth/server'; import { MyApplicationsTable } from '@/components/features/my-applications-table'; import { PageHeader } from '@/components/layouts/page-header'; +export const metadata: Metadata = { title: 'My Applications' }; + export default async function MyApplicationsPage() { const user = await getCurrentUser(); const applications = await getMyApplications(user.id); diff --git a/app/(main)/(auth)/positions/[id]/apply/page.tsx b/app/(main)/(auth)/positions/[id]/apply/page.tsx index b487ce95..932d36a5 100644 --- a/app/(main)/(auth)/positions/[id]/apply/page.tsx +++ b/app/(main)/(auth)/positions/[id]/apply/page.tsx @@ -1,3 +1,4 @@ +import type { Metadata } from 'next'; import Link from 'next/link'; import { redirect } from 'next/navigation'; @@ -13,11 +14,20 @@ import { PageHeader } from '@/components/layouts/page-header'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; -export default async function ApplyPage({ - params, -}: { +interface ApplyPageProps { params: Promise<{ id: string }>; -}) { +} + +export async function generateMetadata({ + params, +}: ApplyPageProps): Promise { + const { id } = await params; + const position = await getPositionForApply(id); + if (!position) return {}; + return { title: `Apply: ${position.title}` }; +} + +export default async function ApplyPage({ params }: ApplyPageProps) { const { id } = await params; const user = await getCurrentUser(); if (!user) redirect('/sign-in'); diff --git a/app/(main)/(auth)/positions/[id]/edit/page.tsx b/app/(main)/(auth)/positions/[id]/edit/page.tsx index 38837f01..a255f5b6 100644 --- a/app/(main)/(auth)/positions/[id]/edit/page.tsx +++ b/app/(main)/(auth)/positions/[id]/edit/page.tsx @@ -1,3 +1,4 @@ +import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; import { getPositionForEdit } from '@/prisma/data/positions'; @@ -14,6 +15,15 @@ interface EditPositionPageProps { params: Promise<{ id: string }>; } +export async function generateMetadata({ + params, +}: EditPositionPageProps): Promise { + const { id } = await params; + const position = await getPositionForEdit(id); + if (!position) return {}; + return { title: `Edit: ${position.title}` }; +} + export default async function EditPositionPage({ params, }: EditPositionPageProps) { diff --git a/app/(main)/(auth)/users/page.tsx b/app/(main)/(auth)/users/page.tsx index 06e28638..87c1ef9d 100644 --- a/app/(main)/(auth)/users/page.tsx +++ b/app/(main)/(auth)/users/page.tsx @@ -1,3 +1,4 @@ +import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; import { getUsersForAdmin } from '@/prisma/data/users'; @@ -7,6 +8,8 @@ import { getCurrentUser } from '@/lib/auth/server'; import { UsersTable } from '@/components/features/users-table'; import { PageHeader } from '@/components/layouts/page-header'; +export const metadata: Metadata = { title: 'Users' }; + export default async function UsersPage() { const user = await getCurrentUser(); if (!user.isAdmin) redirect('/'); diff --git a/app/(main)/positions/page.tsx b/app/(main)/positions/page.tsx index e2a59931..ca8612d2 100644 --- a/app/(main)/positions/page.tsx +++ b/app/(main)/positions/page.tsx @@ -1,3 +1,5 @@ +import type { Metadata } from 'next'; + import { Briefcase } from 'lucide-react'; import { getPositionApplicationStats } from '@/prisma/data/applications'; @@ -15,6 +17,8 @@ import { PositionCard } from '@/components/features/position-card'; import { PositionCreateDialog } from '@/components/features/position-create-dialog'; import { EmptyState } from '@/components/ui/empty-state'; +export const metadata: Metadata = { title: 'Positions' }; + export default async function PositionsPage() { const user = await getOptionalUser(); const isAdmin = user?.isAdmin ?? false; diff --git a/app/(main)/profile/page.tsx b/app/(main)/profile/page.tsx index 67d8e4c2..ac0bf3d4 100644 --- a/app/(main)/profile/page.tsx +++ b/app/(main)/profile/page.tsx @@ -1,3 +1,5 @@ +import type { Metadata } from 'next'; + import { getProfileData } from '@/prisma/data/profile'; import { getCurrentUser } from '@/lib/auth/server'; @@ -5,6 +7,8 @@ import { getCurrentUser } from '@/lib/auth/server'; import { ProfileForm } from '@/components/features/profile-form'; import { PageHeader } from '@/components/layouts/page-header'; +export const metadata: Metadata = { title: 'Profile' }; + export default async function ProfilePage() { const user = await getCurrentUser(); const profileData = await getProfileData(user.id); diff --git a/app/layout.tsx b/app/layout.tsx index a162f943..f934149b 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -11,7 +11,7 @@ import './globals.css'; const inter = Inter({ variable: '--font-sans', subsets: ['latin'] }); export const metadata: Metadata = { - title: { default: 'Aplio', template: 'Aplio - %s' }, + title: { default: 'Aplio', template: 'Aplio • %s' }, description: 'A student government application management system.', icons: [ // Light-mode favicon: white/light background logo diff --git a/app/login/bypass/page.tsx b/app/login/bypass/page.tsx index ec46fe60..cf7eda1b 100644 --- a/app/login/bypass/page.tsx +++ b/app/login/bypass/page.tsx @@ -1,9 +1,12 @@ +import type { Metadata } from 'next'; import Link from 'next/link'; import { loginAsBypassUser } from '@/prisma/services/dev-bypass'; import { Button } from '@/components/ui/button'; +export const metadata: Metadata = { title: 'Dev Login' }; + export default function BypassLoginPage() { return (
diff --git a/app/login/page.tsx b/app/login/page.tsx index d8e6b952..4ee83992 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,3 +1,4 @@ +import type { Metadata } from 'next'; import Link from 'next/link'; import { redirect } from 'next/navigation'; @@ -6,6 +7,8 @@ import { AuthView } from '@neondatabase/auth/react/ui'; import { getOptionalUser } from '@/lib/auth/server'; import { PRIVACY_HREF, TERMS_HREF } from '@/lib/constants'; +export const metadata: Metadata = { title: 'Sign In' }; + // Constrain redirectTo to a same-origin relative path — accept only values // starting with a single "/" and not "//" to prevent open-redirect attacks. function safeRedirectTo(value: string | undefined): string {