Update homepage routes to include language and mode
Build & Deploy / Build & Push Docker Image (push) Successful in 2m51s
Test / Type Check (all packages) (push) Successful in 52s
Build & Deploy / Deploy to VPS (push) Successful in 4s
Test / API Unit Tests (push) Successful in 49s
Test / Homepage Unit Tests (push) Successful in 44s
Test / Storefront Unit Tests (push) Successful in 39s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Successful in 40s
Test / API Integration Tests (push) Successful in 1m0s

This commit is contained in:
root
2026-07-02 14:10:17 -04:00
parent 56c3bcf666
commit 781512399b
51 changed files with 792 additions and 344 deletions
@@ -4,18 +4,20 @@ import { ForgotPasswordForm } from '@/components/auth/ForgotPasswordForm';
import { ResetPasswordForm } from '@/components/auth/ResetPasswordForm';
import {
isLocale,
localizedPath,
isMode,
localizedModePath,
routeIdFromSlug,
type Locale,
type RouteId,
} from '@/lib/localization/config';
import { getMessages, type ShellMessages } from '@/lib/localization/messages';
import { buildLocalizedMetadata } from '@/lib/metadata/build-metadata';
import type { ThemePreference } from '@/lib/theme/config';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
interface PendingRouteProps {
params: Promise<{ locale: string; slug: string }>;
params: Promise<{ locale: string; mode: string; slug: string }>;
}
function routeLabel(messages: ShellMessages, routeId: RouteId): string {
@@ -39,19 +41,21 @@ function routeLabel(messages: ShellMessages, routeId: RouteId): string {
}
async function resolveParams(params: PendingRouteProps['params']) {
const { locale: localeValue, slug } = await params;
const { locale: localeValue, mode: modeValue, slug } = await params;
if (!isLocale(localeValue)) notFound();
if (!isMode(modeValue)) notFound();
const routeId = routeIdFromSlug(localeValue, slug);
if (!routeId || routeId === 'home') notFound();
return { locale: localeValue as Locale, routeId };
return { locale: localeValue as Locale, mode: modeValue as ThemePreference, routeId };
}
export async function generateMetadata({ params }: PendingRouteProps): Promise<Metadata> {
const { locale, routeId } = await resolveParams(params);
const { locale, mode, routeId } = await resolveParams(params);
const messages = getMessages(locale).shell;
const label = routeLabel(messages, routeId);
return buildLocalizedMetadata({
locale,
mode,
routeId,
title: `${label} | ${messages.metadata.pendingTitle}`,
description: messages.metadata.pendingDescription,
@@ -60,7 +64,7 @@ export async function generateMetadata({ params }: PendingRouteProps): Promise<M
}
export default async function PendingRoute({ params }: PendingRouteProps) {
const { locale, routeId } = await resolveParams(params);
const { locale, mode, routeId } = await resolveParams(params);
const messages = getMessages(locale).shell;
const label = routeLabel(messages, routeId);
@@ -82,7 +86,9 @@ export default async function PendingRoute({ params }: PendingRouteProps) {
body={messages.states.pendingBody}
>
<StateActions>
<StateAction href={localizedPath('home', locale)}>{messages.states.backHome}</StateAction>
<StateAction href={localizedModePath('home', locale, mode)}>
{messages.states.backHome}
</StateAction>
</StateActions>
</StateShell>
);
@@ -0,0 +1,96 @@
'use client';
import { Button } from '@/components/actions/Button';
import { Accordion } from '@/components/controls/Accordion';
import { SegmentedControl } from '@/components/controls/SegmentedControl';
import { Tabs } from '@/components/controls/Tabs';
import { Alert } from '@/components/feedback/Alert';
import { Checkbox, Switch } from '@/components/forms/ChoiceControls';
import { FormField } from '@/components/forms/FormField';
import { TextInput } from '@/components/forms/TextInput';
import { DropdownMenu } from '@/components/overlays/DropdownMenu';
import { Dialog } from '@/components/overlays/Dialog';
import type { ComponentLabCopy } from '@/content/development/component-lab';
import { useRef, useState } from 'react';
import styles from './page.module.css';
export function ComponentLabInteractive({ copy }: { copy: ComponentLabCopy }) {
const [dialogOpen, setDialogOpen] = useState(false);
const [segment, setSegment] = useState('comfortable');
const triggerRef = useRef<HTMLButtonElement>(null);
return (
<div className={styles.interactiveGrid}>
<section className={styles.group} aria-labelledby="lab-actions">
<h2 id="lab-actions">Actions</h2>
<div className={styles.cluster}>
<Button>{copy.actions.primary}</Button>
<Button intent="conversion">{copy.actions.conversion}</Button>
<Button loading>{copy.actions.loading}</Button>
<Button disabled>{copy.actions.disabled}</Button>
<Button ref={triggerRef} intent="secondary" onClick={() => setDialogOpen(true)}>
{copy.dialog.open}
</Button>
<DropdownMenu
label={copy.menu.label}
items={[
{ id: 'review', label: copy.menu.first, onSelect: () => undefined },
{
id: 'archive',
label: copy.menu.second,
onSelect: () => undefined,
destructive: true,
},
]}
/>
</div>
</section>
<section className={styles.group} aria-labelledby="lab-form">
<h2 id="lab-form">Form controls</h2>
<FormField
id="fixture-email"
label={copy.form.label}
supportingText={copy.form.supporting}
error={copy.form.error}
required
>
<TextInput
id="fixture-email"
type="email"
placeholder={copy.form.placeholder}
invalid
describedBy="fixture-email-help fixture-email-error"
/>
</FormField>
<Checkbox label={copy.form.checkbox} />
<Switch label={copy.form.switch} />
<SegmentedControl
label="Density"
value={segment}
onChange={setSegment}
options={[
{ value: 'comfortable', label: 'Comfortable' },
{ value: 'compact', label: 'Compact' },
]}
/>
</section>
<section className={styles.group} aria-labelledby="lab-controls">
<h2 id="lab-controls">Selection and disclosure</h2>
<Tabs label={copy.tabs.label} items={copy.tabs.items} />
<Accordion items={copy.accordion.items} />
</section>
<Alert tone="warning" title={copy.alert.title}>
{copy.alert.body}
</Alert>
<Dialog
open={dialogOpen}
onOpenChange={setDialogOpen}
title={copy.dialog.title}
description={copy.dialog.description}
closeLabel={copy.dialog.close}
returnFocusRef={triggerRef}
>
<p>{copy.dialog.content}</p>
</Dialog>
</div>
);
}
@@ -0,0 +1,32 @@
.main {
min-block-size: 100dvh;
}
.interactiveGrid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-6);
}
.group {
display: grid;
align-content: start;
gap: var(--space-4);
min-inline-size: 0;
padding: var(--space-6);
border: 1px solid var(--border-standard);
border-radius: var(--radius-md);
background: var(--surface-elevated);
}
.group h2 {
margin: 0;
font-size: var(--type-heading-3-size);
}
.cluster {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
}
@media (max-width: 767px) {
.interactiveGrid {
grid-template-columns: 1fr;
}
}
@@ -0,0 +1,82 @@
import { Container, Section, Stack } from '@/components/layout/LayoutPrimitives';
import { Comparison } from '@/components/marketing/Comparison';
import { CTA } from '@/components/marketing/CTA';
import { Metric } from '@/components/marketing/Evidence';
import { ProductPreview } from '@/components/marketing/ProductPreview';
import { SectionHeader } from '@/components/marketing/SectionHeader';
import { Workflow } from '@/components/marketing/Workflow';
import { getComponentLabCopy } from '@/content/development/component-lab';
import { isLocale, isMode, type Locale } from '@/lib/localization/config';
import { notFound } from 'next/navigation';
import { ComponentLabInteractive } from './ComponentLabInteractive';
import styles from './page.module.css';
export const metadata = { robots: { index: false, follow: false } };
export default async function ComponentLabPage({
params,
}: {
params: Promise<{ locale: string; mode: string }>;
}) {
if (process.env.COMPONENT_FIXTURES_ENABLED !== 'true') notFound();
const { locale, mode } = await params;
if (!isLocale(locale)) notFound();
if (!isMode(mode)) notFound();
const resolvedLocale: Locale = locale;
const copy = getComponentLabCopy(resolvedLocale);
return (
<main id="main-content" className={styles.main}>
<Section>
<Container>
<Stack gap="large">
<SectionHeader
eyebrow={copy.developmentLabel}
title={copy.title}
body={copy.body}
headingLevel={1}
/>
<ComponentLabInteractive copy={copy} />
<Workflow label={copy.title} items={copy.workflow} />
<Comparison
columns={[
{
title: copy.comparison.beforeTitle,
items: copy.comparison.before,
tone: 'problem',
},
{
title: copy.comparison.afterTitle,
items: copy.comparison.after,
tone: 'solution',
},
]}
/>
<ProductPreview
title={copy.preview.title}
caption={copy.preview.caption}
illustrativeLabel={copy.preview.illustrative}
rows={copy.preview.rows.map((row, index) => ({
...row,
statusTone: index === 0 ? 'success' : 'warning',
}))}
/>
<Metric
content={{
value: copy.metric.value,
label: copy.metric.label,
qualification: copy.metric.qualification,
status: 'research-only',
}}
statusLabel={copy.metric.status}
/>
<CTA
title={copy.cta.title}
body={copy.cta.body}
primary={{ label: copy.cta.primary, disabledReason: copy.cta.unavailable }}
/>
</Stack>
</Container>
</Section>
</main>
);
}
@@ -0,0 +1,21 @@
import { ForgotPasswordForm } from '@/components/auth/ForgotPasswordForm'
import { isLocale, isMode, type Locale } from '@/lib/localization/config'
import { notFound } from 'next/navigation'
import { Suspense } from 'react'
export default async function ForgotPasswordPage({
params,
}: {
params: Promise<{ locale: string; mode: string }>
}) {
const { locale: localeValue, mode } = await params
if (!isLocale(localeValue)) notFound()
if (!isMode(mode)) notFound()
const locale = localeValue as Locale
return (
<Suspense fallback={null}>
<ForgotPasswordForm locale={locale} />
</Suspense>
)
}
@@ -0,0 +1,82 @@
import styles from '@/components/homepage/Homepage.module.css';
import {
ComparisonSection,
FaqSection,
FinalCtaSection,
HeroSection,
IntegrationsSection,
ModulesSection,
PricingSection,
ResultsSection,
RolesSection,
SecuritySection,
TrustSection,
WorkflowSection,
} from '@/components/homepage/sections';
import { buildHomepageContent } from '@/content/homepage-model';
import { getPublicIntegrationConfig } from '@/lib/integrations/environment';
import { isLocale, isMode, localizedModePath, type Locale } from '@/lib/localization/config';
import { getMessages } from '@/lib/localization/messages';
import { buildLocalizedMetadata } from '@/lib/metadata/build-metadata';
import type { ThemePreference } from '@/lib/theme/config';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
interface LocalePageProps {
params: Promise<{ locale: string; mode: string }>;
}
export async function generateMetadata({ params }: LocalePageProps): Promise<Metadata> {
const { locale: localeValue, mode: modeValue } = await params;
if (!isLocale(localeValue)) notFound();
if (!isMode(modeValue)) notFound();
const messages = getMessages(localeValue);
return buildLocalizedMetadata({
locale: localeValue,
mode: modeValue,
routeId: 'home',
title: messages.homepage.meta.title,
description: messages.homepage.meta.description,
indexable: true,
});
}
export default async function LocaleHomePage({ params }: LocalePageProps) {
const { locale: localeValue, mode: modeValue } = await params;
if (!isLocale(localeValue)) notFound();
if (!isMode(modeValue)) notFound();
const locale: Locale = localeValue;
const mode: ThemePreference = modeValue;
const messages = getMessages(locale);
const content = buildHomepageContent(messages.homepage, messages.shell, locale);
const integrationConfig = getPublicIntegrationConfig();
const homePath = localizedModePath('home', locale, mode);
return (
<main id="main-content" className={styles.main} tabIndex={-1}>
<HeroSection
content={content.hero}
homePath={homePath}
/>
<TrustSection content={content.trust} />
<ComparisonSection content={content.comparison} />
<WorkflowSection content={content.workflow} />
<RolesSection content={content.roles} />
<ModulesSection content={content.modules} />
<ResultsSection content={content.results} />
<IntegrationsSection content={content.integrations} />
<SecuritySection content={content.security} />
<PricingSection
content={content.pricing}
locale={locale}
homePath={homePath}
demoSubmissionEnabled={integrationConfig.demoSubmissionEnabled}
/>
<FaqSection content={content.faq} />
<FinalCtaSection
content={content.final}
/>
</main>
);
}
@@ -0,0 +1,21 @@
import { SignInForm } from '@/components/auth/SignInForm'
import { isLocale, isMode, type Locale } from '@/lib/localization/config'
import { notFound } from 'next/navigation'
import { Suspense } from 'react'
export default async function SignInPage({
params,
}: {
params: Promise<{ locale: string; mode: string }>
}) {
const { locale: localeValue, mode } = await params
if (!isLocale(localeValue)) notFound()
if (!isMode(mode)) notFound()
const locale = localeValue as Locale
return (
<Suspense fallback={null}>
<SignInForm locale={locale} />
</Suspense>
)
}
@@ -1,81 +1,19 @@
import { Container, Section, Stack } from '@/components/layout/LayoutPrimitives';
import { Comparison } from '@/components/marketing/Comparison';
import { CTA } from '@/components/marketing/CTA';
import { Metric } from '@/components/marketing/Evidence';
import { ProductPreview } from '@/components/marketing/ProductPreview';
import { SectionHeader } from '@/components/marketing/SectionHeader';
import { Workflow } from '@/components/marketing/Workflow';
import { getComponentLabCopy } from '@/content/development/component-lab';
import { isLocale, type Locale } from '@/lib/localization/config';
import { notFound } from 'next/navigation';
import { ComponentLabInteractive } from './ComponentLabInteractive';
import styles from './page.module.css';
import { defaultMode, isLocale } from '@/lib/localization/config'
import { isThemePreference, themeCookie } from '@/lib/theme/config'
import { cookies } from 'next/headers'
import { notFound, redirect } from 'next/navigation'
export const metadata = { robots: { index: false, follow: false } };
export const metadata = { robots: { index: false, follow: false } }
export default async function ComponentLabPage({
export default async function LegacyComponentLabPage({
params,
}: {
params: Promise<{ locale: string }>;
params: Promise<{ locale: string }>
}) {
if (process.env.COMPONENT_FIXTURES_ENABLED !== 'true') notFound();
const { locale } = await params;
if (!isLocale(locale)) notFound();
const resolvedLocale: Locale = locale;
const copy = getComponentLabCopy(resolvedLocale);
return (
<main id="main-content" className={styles.main}>
<Section>
<Container>
<Stack gap="large">
<SectionHeader
eyebrow={copy.developmentLabel}
title={copy.title}
body={copy.body}
headingLevel={1}
/>
<ComponentLabInteractive copy={copy} />
<Workflow label={copy.title} items={copy.workflow} />
<Comparison
columns={[
{
title: copy.comparison.beforeTitle,
items: copy.comparison.before,
tone: 'problem',
},
{
title: copy.comparison.afterTitle,
items: copy.comparison.after,
tone: 'solution',
},
]}
/>
<ProductPreview
title={copy.preview.title}
caption={copy.preview.caption}
illustrativeLabel={copy.preview.illustrative}
rows={copy.preview.rows.map((row, index) => ({
...row,
statusTone: index === 0 ? 'success' : 'warning',
}))}
/>
<Metric
content={{
value: copy.metric.value,
label: copy.metric.label,
qualification: copy.metric.qualification,
status: 'research-only',
}}
statusLabel={copy.metric.status}
/>
<CTA
title={copy.cta.title}
body={copy.cta.body}
primary={{ label: copy.cta.primary, disabledReason: copy.cta.unavailable }}
/>
</Stack>
</Container>
</Section>
</main>
);
const { locale } = await params
if (!isLocale(locale)) notFound()
const cookieStore = await cookies()
const cookieMode = cookieStore.get(themeCookie)?.value
const mode = isThemePreference(cookieMode) ? cookieMode : defaultMode
redirect(`/${locale}/${mode}/component-lab`)
}
@@ -1,20 +1,22 @@
import { ForgotPasswordForm } from '@/components/auth/ForgotPasswordForm'
import { isLocale, type Locale } from '@/lib/localization/config'
import { notFound } from 'next/navigation'
import { Suspense } from 'react'
import {
defaultMode,
isLocale,
localizedModePath,
type Locale,
} from '@/lib/localization/config'
import { isThemePreference, themeCookie } from '@/lib/theme/config'
import { cookies } from 'next/headers'
import { notFound, redirect } from 'next/navigation'
export default async function ForgotPasswordPage({
export default async function LegacyForgotPasswordPage({
params,
}: {
params: Promise<{ locale: string }>
}) {
const { locale: localeValue } = await params
if (!isLocale(localeValue)) notFound()
const locale = localeValue as Locale
return (
<Suspense fallback={null}>
<ForgotPasswordForm locale={locale} />
</Suspense>
)
const cookieStore = await cookies()
const cookieMode = cookieStore.get(themeCookie)?.value
const mode = isThemePreference(cookieMode) ? cookieMode : defaultMode
redirect(localizedModePath('forgot-password', localeValue as Locale, mode))
}
+5 -1
View File
@@ -48,8 +48,11 @@ export default async function LocaleLayout({ children, params }: LocaleLayoutPro
const requestHeaders = await headers();
const nonce = requestHeaders.get('x-nonce') ?? undefined;
const cookieStore = await cookies();
const headerPreference = requestHeaders.get('x-theme-preference');
const cookiePreference = cookieStore.get(themeCookie)?.value;
const preference: ThemePreference = isThemePreference(cookiePreference)
const preference: ThemePreference = isThemePreference(headerPreference)
? headerPreference
: isThemePreference(cookiePreference)
? cookiePreference
: 'system';
const resolvedTheme = serverResolvedTheme(preference);
@@ -89,6 +92,7 @@ export default async function LocaleLayout({ children, params }: LocaleLayoutPro
/>
<SiteFooter
locale={locale}
mode={preference}
messages={messages.shell}
/>
</body>
+15 -67
View File
@@ -1,77 +1,25 @@
import styles from '@/components/homepage/Homepage.module.css';
import {
ComparisonSection,
FaqSection,
FinalCtaSection,
HeroSection,
IntegrationsSection,
ModulesSection,
PricingSection,
ResultsSection,
RolesSection,
SecuritySection,
TrustSection,
WorkflowSection,
} from '@/components/homepage/sections';
import { buildHomepageContent } from '@/content/homepage-model';
import { getPublicIntegrationConfig } from '@/lib/integrations/environment';
import { isLocale, localizedPath, type Locale } from '@/lib/localization/config';
import { getMessages } from '@/lib/localization/messages';
import { buildLocalizedMetadata } from '@/lib/metadata/build-metadata';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
defaultMode,
isLocale,
localizedModePath,
type Locale,
} from '@/lib/localization/config';
import { isThemePreference, themeCookie } from '@/lib/theme/config';
import { cookies } from 'next/headers';
import { notFound, redirect } from 'next/navigation';
interface LocalePageProps {
interface LocaleRedirectPageProps {
params: Promise<{ locale: string }>;
}
export async function generateMetadata({ params }: LocalePageProps): Promise<Metadata> {
const { locale: localeValue } = await params;
if (!isLocale(localeValue)) notFound();
const messages = getMessages(localeValue);
return buildLocalizedMetadata({
locale: localeValue,
routeId: 'home',
title: messages.homepage.meta.title,
description: messages.homepage.meta.description,
indexable: true,
});
async function preferredMode() {
const cookieStore = await cookies();
const cookieMode = cookieStore.get(themeCookie)?.value;
return isThemePreference(cookieMode) ? cookieMode : defaultMode;
}
export default async function LocaleHomePage({ params }: LocalePageProps) {
export default async function LocaleRedirectPage({ params }: LocaleRedirectPageProps) {
const { locale: localeValue } = await params;
if (!isLocale(localeValue)) notFound();
const locale: Locale = localeValue;
const messages = getMessages(locale);
const content = buildHomepageContent(messages.homepage, messages.shell, locale);
const integrationConfig = getPublicIntegrationConfig();
const homePath = localizedPath('home', locale);
return (
<main id="main-content" className={styles.main} tabIndex={-1}>
<HeroSection
content={content.hero}
homePath={homePath}
/>
<TrustSection content={content.trust} />
<ComparisonSection content={content.comparison} />
<WorkflowSection content={content.workflow} />
<RolesSection content={content.roles} />
<ModulesSection content={content.modules} />
<ResultsSection content={content.results} />
<IntegrationsSection content={content.integrations} />
<SecuritySection content={content.security} />
<PricingSection
content={content.pricing}
locale={locale}
homePath={homePath}
demoSubmissionEnabled={integrationConfig.demoSubmissionEnabled}
/>
<FaqSection content={content.faq} />
<FinalCtaSection
content={content.final}
/>
</main>
);
redirect(localizedModePath('home', localeValue as Locale, await preferredMode()));
}
+14 -12
View File
@@ -1,20 +1,22 @@
import { SignInForm } from '@/components/auth/SignInForm'
import { isLocale, type Locale } from '@/lib/localization/config'
import { notFound } from 'next/navigation'
import { Suspense } from 'react'
import {
defaultMode,
isLocale,
localizedModePath,
type Locale,
} from '@/lib/localization/config'
import { isThemePreference, themeCookie } from '@/lib/theme/config'
import { cookies } from 'next/headers'
import { notFound, redirect } from 'next/navigation'
export default async function SignInPage({
export default async function LegacySignInPage({
params,
}: {
params: Promise<{ locale: string }>
}) {
const { locale: localeValue } = await params
if (!isLocale(localeValue)) notFound()
const locale = localeValue as Locale
return (
<Suspense fallback={null}>
<SignInForm locale={locale} />
</Suspense>
)
const cookieStore = await cookies()
const cookieMode = cookieStore.get(themeCookie)?.value
const mode = isThemePreference(cookieMode) ? cookieMode : defaultMode
redirect(localizedModePath('sign-in', localeValue as Locale, mode))
}