diff --git a/frontend/src/app/[locale]/(auth)/login/metadata.ts b/frontend/src/app/[locale]/(auth)/login/metadata.ts new file mode 100644 index 0000000..0f1334b --- /dev/null +++ b/frontend/src/app/[locale]/(auth)/login/metadata.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file - Server-only Next.js metadata generation covered by E2E */ +import type { Metadata } from 'next'; +import { getTranslations } from 'next-intl/server'; +import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'auth.login' }); + + return generatePageMetadata(locale as Locale, t('title'), t('subtitle'), '/login'); +} diff --git a/frontend/src/app/[locale]/(auth)/login/page.tsx b/frontend/src/app/[locale]/(auth)/login/page.tsx index 357349a..e2e4b6b 100644 --- a/frontend/src/app/[locale]/(auth)/login/page.tsx +++ b/frontend/src/app/[locale]/(auth)/login/page.tsx @@ -1,7 +1,4 @@ -import { Metadata } from 'next'; import dynamic from 'next/dynamic'; -import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; -import { getTranslations } from 'next-intl/server'; // Code-split LoginForm - heavy with react-hook-form + validation const LoginForm = dynamic( @@ -18,17 +15,8 @@ const LoginForm = dynamic( } ); -/* istanbul ignore next - Next.js metadata generation covered by e2e tests */ -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string }>; -}): Promise { - const { locale } = await params; - const t = await getTranslations({ locale, namespace: 'auth.login' }); - - return generatePageMetadata(locale as Locale, t('title'), t('subtitle'), '/login'); -} +// Re-export server-only metadata from separate, ignored file +export { generateMetadata } from './metadata'; export default function LoginPage() { return ( diff --git a/frontend/src/app/[locale]/(auth)/password-reset/confirm/metadata.ts b/frontend/src/app/[locale]/(auth)/password-reset/confirm/metadata.ts new file mode 100644 index 0000000..2c258b1 --- /dev/null +++ b/frontend/src/app/[locale]/(auth)/password-reset/confirm/metadata.ts @@ -0,0 +1,20 @@ +/* istanbul ignore file - Server-only Next.js metadata generation covered by E2E */ +import type { Metadata } from 'next'; +import { getTranslations } from 'next-intl/server'; +import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'auth.passwordResetConfirm' }); + + return generatePageMetadata( + locale as Locale, + t('title'), + t('instructions'), + '/password-reset/confirm' + ); +} diff --git a/frontend/src/app/[locale]/(auth)/password-reset/confirm/page.tsx b/frontend/src/app/[locale]/(auth)/password-reset/confirm/page.tsx index e0e3be1..7f2497d 100644 --- a/frontend/src/app/[locale]/(auth)/password-reset/confirm/page.tsx +++ b/frontend/src/app/[locale]/(auth)/password-reset/confirm/page.tsx @@ -3,27 +3,11 @@ * Users set a new password using the token from their email */ -import { Metadata } from 'next'; import { Suspense } from 'react'; -import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; -import { getTranslations } from 'next-intl/server'; import PasswordResetConfirmContent from './PasswordResetConfirmContent'; -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string }>; -}): Promise { - const { locale } = await params; - const t = await getTranslations({ locale, namespace: 'auth.passwordResetConfirm' }); - - return generatePageMetadata( - locale as Locale, - t('title'), - t('instructions'), - '/password-reset/confirm' - ); -} +// Re-export server-only metadata from separate, ignored file +export { generateMetadata } from './metadata'; export default function PasswordResetConfirmPage() { return ( diff --git a/frontend/src/app/[locale]/(auth)/password-reset/metadata.ts b/frontend/src/app/[locale]/(auth)/password-reset/metadata.ts new file mode 100644 index 0000000..daa4e48 --- /dev/null +++ b/frontend/src/app/[locale]/(auth)/password-reset/metadata.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file - Server-only Next.js metadata generation covered by E2E */ +import type { Metadata } from 'next'; +import { getTranslations } from 'next-intl/server'; +import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'auth.passwordReset' }); + + return generatePageMetadata(locale as Locale, t('title'), t('subtitle'), '/password-reset'); +} diff --git a/frontend/src/app/[locale]/(auth)/password-reset/page.tsx b/frontend/src/app/[locale]/(auth)/password-reset/page.tsx index e85b2fd..a65750e 100644 --- a/frontend/src/app/[locale]/(auth)/password-reset/page.tsx +++ b/frontend/src/app/[locale]/(auth)/password-reset/page.tsx @@ -3,10 +3,7 @@ * Users enter their email to receive reset instructions */ -import { Metadata } from 'next'; import dynamic from 'next/dynamic'; -import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; -import { getTranslations } from 'next-intl/server'; // Code-split PasswordResetRequestForm const PasswordResetRequestForm = dynamic( @@ -25,17 +22,8 @@ const PasswordResetRequestForm = dynamic( } ); -/* istanbul ignore next - Next.js metadata generation covered by e2e tests */ -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string }>; -}): Promise { - const { locale } = await params; - const t = await getTranslations({ locale, namespace: 'auth.passwordReset' }); - - return generatePageMetadata(locale as Locale, t('title'), t('subtitle'), '/password-reset'); -} +// Re-export server-only metadata from separate, ignored file +export { generateMetadata } from './metadata'; export default function PasswordResetPage() { return ( diff --git a/frontend/src/app/[locale]/(auth)/register/metadata.ts b/frontend/src/app/[locale]/(auth)/register/metadata.ts new file mode 100644 index 0000000..e80aab8 --- /dev/null +++ b/frontend/src/app/[locale]/(auth)/register/metadata.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file - Server-only Next.js metadata generation covered by E2E */ +import type { Metadata } from 'next'; +import { getTranslations } from 'next-intl/server'; +import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'auth.register' }); + + return generatePageMetadata(locale as Locale, t('title'), t('subtitle'), '/register'); +} diff --git a/frontend/src/app/[locale]/(auth)/register/page.tsx b/frontend/src/app/[locale]/(auth)/register/page.tsx index 244f983..db7329f 100644 --- a/frontend/src/app/[locale]/(auth)/register/page.tsx +++ b/frontend/src/app/[locale]/(auth)/register/page.tsx @@ -1,7 +1,4 @@ -import { Metadata } from 'next'; import dynamic from 'next/dynamic'; -import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; -import { getTranslations } from 'next-intl/server'; // Code-split RegisterForm (313 lines) const RegisterForm = dynamic( @@ -18,17 +15,8 @@ const RegisterForm = dynamic( } ); -/* istanbul ignore next - Next.js metadata generation covered by e2e tests */ -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string }>; -}): Promise { - const { locale } = await params; - const t = await getTranslations({ locale, namespace: 'auth.register' }); - - return generatePageMetadata(locale as Locale, t('title'), t('subtitle'), '/register'); -} +// Re-export server-only metadata from separate, ignored file +export { generateMetadata } from './metadata'; export default function RegisterPage() { return ( diff --git a/frontend/src/app/[locale]/admin/metadata.ts b/frontend/src/app/[locale]/admin/metadata.ts new file mode 100644 index 0000000..ade92d7 --- /dev/null +++ b/frontend/src/app/[locale]/admin/metadata.ts @@ -0,0 +1,6 @@ +/* istanbul ignore file - Server-only Next.js metadata generation covered by E2E */ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Admin Dashboard', +}; diff --git a/frontend/src/app/[locale]/admin/organizations/[id]/members/metadata.ts b/frontend/src/app/[locale]/admin/organizations/[id]/members/metadata.ts new file mode 100644 index 0000000..107ea43 --- /dev/null +++ b/frontend/src/app/[locale]/admin/organizations/[id]/members/metadata.ts @@ -0,0 +1,6 @@ +/* istanbul ignore file - Server-only Next.js metadata generation covered by E2E */ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Organization Members', +}; diff --git a/frontend/src/app/[locale]/admin/organizations/[id]/members/page.tsx b/frontend/src/app/[locale]/admin/organizations/[id]/members/page.tsx index cf46144..697509d 100644 --- a/frontend/src/app/[locale]/admin/organizations/[id]/members/page.tsx +++ b/frontend/src/app/[locale]/admin/organizations/[id]/members/page.tsx @@ -4,17 +4,13 @@ * Protected by AuthGuard in layout with requireAdmin=true */ -/* istanbul ignore next - Next.js type import for metadata */ -import type { Metadata } from 'next'; import { Link } from '@/lib/i18n/routing'; import { ArrowLeft } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { OrganizationMembersContent } from '@/components/admin/organizations/OrganizationMembersContent'; -/* istanbul ignore next - Next.js metadata, not executable code */ -export const metadata: Metadata = { - title: 'Organization Members', -}; +// Re-export server-only metadata from separate, ignored file +export { metadata } from './metadata'; interface PageProps { params: Promise<{ diff --git a/frontend/src/app/[locale]/admin/organizations/metadata.ts b/frontend/src/app/[locale]/admin/organizations/metadata.ts new file mode 100644 index 0000000..f8c7d23 --- /dev/null +++ b/frontend/src/app/[locale]/admin/organizations/metadata.ts @@ -0,0 +1,6 @@ +/* istanbul ignore file - Server-only Next.js metadata generation covered by E2E */ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Organizations', +}; diff --git a/frontend/src/app/[locale]/admin/organizations/page.tsx b/frontend/src/app/[locale]/admin/organizations/page.tsx index 422bf14..816d09c 100644 --- a/frontend/src/app/[locale]/admin/organizations/page.tsx +++ b/frontend/src/app/[locale]/admin/organizations/page.tsx @@ -4,17 +4,13 @@ * Protected by AuthGuard in layout with requireAdmin=true */ -/* istanbul ignore next - Next.js type import for metadata */ -import type { Metadata } from 'next'; import { Link } from '@/lib/i18n/routing'; import { ArrowLeft } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { OrganizationManagementContent } from '@/components/admin/organizations/OrganizationManagementContent'; -/* istanbul ignore next - Next.js metadata, not executable code */ -export const metadata: Metadata = { - title: 'Organizations', -}; +// Re-export server-only metadata from separate, ignored file +export { metadata } from './metadata'; export default function AdminOrganizationsPage() { return ( diff --git a/frontend/src/app/[locale]/admin/page.tsx b/frontend/src/app/[locale]/admin/page.tsx index 95c6952..54b4ec7 100644 --- a/frontend/src/app/[locale]/admin/page.tsx +++ b/frontend/src/app/[locale]/admin/page.tsx @@ -4,8 +4,6 @@ * Protected by AuthGuard in layout with requireAdmin=true */ -/* istanbul ignore next - Next.js type import for metadata */ -import type { Metadata } from 'next'; import { Link } from '@/lib/i18n/routing'; import { DashboardStats } from '@/components/admin'; import { @@ -16,10 +14,8 @@ import { } from '@/components/charts'; import { Users, Building2, Settings } from 'lucide-react'; -/* istanbul ignore next - Next.js metadata, not executable code */ -export const metadata: Metadata = { - title: 'Admin Dashboard', -}; +// Re-export server-only metadata from separate, ignored file +export { metadata } from './metadata'; export default function AdminPage() { return ( diff --git a/frontend/src/app/[locale]/admin/settings/metadata.ts b/frontend/src/app/[locale]/admin/settings/metadata.ts new file mode 100644 index 0000000..7064e65 --- /dev/null +++ b/frontend/src/app/[locale]/admin/settings/metadata.ts @@ -0,0 +1,6 @@ +/* istanbul ignore file - Server-only Next.js metadata generation covered by E2E */ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'System Settings', +}; diff --git a/frontend/src/app/[locale]/admin/settings/page.tsx b/frontend/src/app/[locale]/admin/settings/page.tsx index 74bb903..ccd7465 100644 --- a/frontend/src/app/[locale]/admin/settings/page.tsx +++ b/frontend/src/app/[locale]/admin/settings/page.tsx @@ -4,16 +4,12 @@ * Protected by AuthGuard in layout with requireAdmin=true */ -/* istanbul ignore next - Next.js type import for metadata */ -import type { Metadata } from 'next'; import { Link } from '@/lib/i18n/routing'; import { ArrowLeft } from 'lucide-react'; import { Button } from '@/components/ui/button'; -/* istanbul ignore next - Next.js metadata, not executable code */ -export const metadata: Metadata = { - title: 'System Settings', -}; +// Re-export server-only metadata from separate, ignored file +export { metadata } from './metadata'; export default function AdminSettingsPage() { return ( diff --git a/frontend/src/app/[locale]/admin/users/metadata.ts b/frontend/src/app/[locale]/admin/users/metadata.ts new file mode 100644 index 0000000..5d205a1 --- /dev/null +++ b/frontend/src/app/[locale]/admin/users/metadata.ts @@ -0,0 +1,6 @@ +/* istanbul ignore file - Server-only Next.js metadata generation covered by E2E */ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'User Management', +}; diff --git a/frontend/src/app/[locale]/admin/users/page.tsx b/frontend/src/app/[locale]/admin/users/page.tsx index 968e919..a77237a 100644 --- a/frontend/src/app/[locale]/admin/users/page.tsx +++ b/frontend/src/app/[locale]/admin/users/page.tsx @@ -4,17 +4,13 @@ * Protected by AuthGuard in layout with requireAdmin=true */ -/* istanbul ignore next - Next.js type import for metadata */ -import type { Metadata } from 'next'; import { Link } from '@/lib/i18n/routing'; import { ArrowLeft } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { UserManagementContent } from '@/components/admin/users/UserManagementContent'; -/* istanbul ignore next - Next.js metadata, not executable code */ -export const metadata: Metadata = { - title: 'User Management', -}; +// Re-export server-only metadata from separate, ignored file +export { metadata } from './metadata'; export default function AdminUsersPage() { return ( diff --git a/frontend/src/app/[locale]/forbidden/metadata.ts b/frontend/src/app/[locale]/forbidden/metadata.ts new file mode 100644 index 0000000..9e49421 --- /dev/null +++ b/frontend/src/app/[locale]/forbidden/metadata.ts @@ -0,0 +1,20 @@ +/* istanbul ignore file - Server-only Next.js metadata generation covered by E2E */ +import type { Metadata } from 'next'; +import { getTranslations } from 'next-intl/server'; +import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'errors' }); + + return generatePageMetadata( + locale as Locale, + t('unauthorized'), + t('unauthorizedDescription'), + '/forbidden' + ); +} diff --git a/frontend/src/app/[locale]/forbidden/page.tsx b/frontend/src/app/[locale]/forbidden/page.tsx index f18f536..9c32919 100644 --- a/frontend/src/app/[locale]/forbidden/page.tsx +++ b/frontend/src/app/[locale]/forbidden/page.tsx @@ -3,29 +3,11 @@ * Displayed when users try to access resources they don't have permission for */ -import type { Metadata } from 'next'; import { Link } from '@/lib/i18n/routing'; import { ShieldAlert } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { generatePageMetadata, type Locale } from '@/lib/i18n/metadata'; -import { getTranslations } from 'next-intl/server'; - -/* istanbul ignore next - Next.js metadata generation covered by e2e tests */ -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string }>; -}): Promise { - const { locale } = await params; - const t = await getTranslations({ locale, namespace: 'errors' }); - - return generatePageMetadata( - locale as Locale, - t('unauthorized'), - t('unauthorizedDescription'), - '/forbidden' - ); -} +// Re-export server-only metadata from separate, ignored file +export { generateMetadata } from './metadata'; export default function ForbiddenPage() { return ( diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 6a4bf58..da17abb 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,10 +1,10 @@ +/* istanbul ignore file - Framework-only root redirect covered by E2E */ /** * Root page - redirects to default locale */ import { redirect } from 'next/navigation'; -/* istanbul ignore next - Next.js server-side redirect covered by e2e tests */ export default function RootPage() { // Redirect to default locale (en) redirect('/en'); diff --git a/frontend/src/app/robots.ts b/frontend/src/app/robots.ts index 28fc89b..9e0ee3b 100644 --- a/frontend/src/app/robots.ts +++ b/frontend/src/app/robots.ts @@ -1,10 +1,10 @@ +/* istanbul ignore file - Framework-only metadata route covered by E2E */ import { MetadataRoute } from 'next'; /** * Generate robots.txt * Configures search engine crawler behavior */ -/* istanbul ignore next - Next.js metadata route covered by e2e tests */ export default function robots(): MetadataRoute.Robots { const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'; diff --git a/frontend/src/app/sitemap.ts b/frontend/src/app/sitemap.ts index cf5759f..e66bf10 100644 --- a/frontend/src/app/sitemap.ts +++ b/frontend/src/app/sitemap.ts @@ -1,3 +1,4 @@ +/* istanbul ignore file - Framework-only metadata route covered by E2E */ import { MetadataRoute } from 'next'; import { routing } from '@/lib/i18n/routing'; @@ -5,7 +6,6 @@ import { routing } from '@/lib/i18n/routing'; * Generate multilingual sitemap * Includes all public routes for each supported locale */ -/* istanbul ignore next - Next.js metadata route covered by e2e tests */ export default function sitemap(): MetadataRoute.Sitemap { const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';