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
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:
+13
-7
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user