chore: fix prettier formatting, regenerate phase9 manifest, add missing config files (.env.example, .node-version, .nvmrc, CI workflow)
CI / test (push) Failing after 28s
CI / test (push) Failing after 28s
This commit is contained in:
@@ -1,26 +1,136 @@
|
||||
export type Locale = "en" | "fr" | "ar";
|
||||
export type Direction = "ltr" | "rtl";
|
||||
export type EvidenceStatus = "approved" | "illustrative" | "pending" | "expired" | "rejected";
|
||||
export type ThemePreference = "light" | "dark" | "system";
|
||||
export type Locale = 'en' | 'fr' | 'ar';
|
||||
export type Direction = 'ltr' | 'rtl';
|
||||
export type EvidenceStatus = 'approved' | 'illustrative' | 'pending' | 'expired' | 'rejected';
|
||||
export type ThemePreference = 'light' | 'dark' | 'system';
|
||||
|
||||
export interface LinkModel { id: string; label: string; href: string; external?: boolean; }
|
||||
export interface SiteHeaderProps { locale: Locale; direction: Direction; links: LinkModel[]; login?: LinkModel; onDemo(): void; }
|
||||
export interface DrawerProps extends SiteHeaderProps { open: boolean; onOpenChange(open: boolean): void; returnFocusRef?: HTMLElement | null; }
|
||||
export interface LocaleSelectorProps { locale: Locale; routeId: string; hash?: string; onLocaleChange(locale: Locale): void; }
|
||||
export interface ThemeSelectorProps { preference: ThemePreference; onPreferenceChange(value: ThemePreference): void; }
|
||||
export interface HeroModel { eyebrow: string; title: string; body: string; primaryLabel: string; secondaryLabel: string; }
|
||||
export interface ProductPreviewModel { status: EvidenceStatus; caption: string; locale: Locale; direction: Direction; theme: "light"|"dark"; assetId?: string; }
|
||||
export interface LifecycleItem { id: string; title: string; description: string; icon: string; }
|
||||
export interface ComparisonModel { title: string; body: string; before: { title: string; items: string[] }; after: { title: string; items: string[] }; }
|
||||
export interface WorkflowStep { id: string; ordinal: string; title: string; description: string; }
|
||||
export interface RoleCard { id: string; title: string; description: string; }
|
||||
export interface ModuleCard { id: string; title: string; description: string; evidenceStatus: EvidenceStatus; }
|
||||
export interface MetricDefinition { id: string; title: string; definition: string; publishedValue?: never; }
|
||||
export interface IntegrationCategory { id: string; label: string; verifiedPartners?: never; }
|
||||
export interface SecurityCheck { id: string; title: string; description: string; certificationBadge?: never; }
|
||||
export interface PricingModel { mode: "quote-only" | "approved-pricing"; title: string; body: string; factors: string[]; note: string; }
|
||||
export interface FaqItem { id: string; question: string; answer: string; }
|
||||
export interface DemoFormValues { fullName: string; workEmail: string; company: string; fleetSize: string; market?: string; preferredLanguage?: Locale; message?: string; }
|
||||
export type DemoFormState = "idle"|"validating"|"submitting"|"success"|"recoverable_error"|"terminal_error";
|
||||
export interface DemoFormProps { state: DemoFormState; values: DemoFormValues; fieldErrors: Partial<Record<keyof DemoFormValues,string>>; formError?: string; onSubmit(values: DemoFormValues): Promise<void>; }
|
||||
export interface TourProps { steps: WorkflowStep[]; activeIndex: number; open: boolean; onOpenChange(open:boolean): void; onStepChange(index:number): void; }
|
||||
export interface LinkModel {
|
||||
id: string;
|
||||
label: string;
|
||||
href: string;
|
||||
external?: boolean;
|
||||
}
|
||||
export interface SiteHeaderProps {
|
||||
locale: Locale;
|
||||
direction: Direction;
|
||||
links: LinkModel[];
|
||||
login?: LinkModel;
|
||||
onDemo(): void;
|
||||
}
|
||||
export interface DrawerProps extends SiteHeaderProps {
|
||||
open: boolean;
|
||||
onOpenChange(open: boolean): void;
|
||||
returnFocusRef?: HTMLElement | null;
|
||||
}
|
||||
export interface LocaleSelectorProps {
|
||||
locale: Locale;
|
||||
routeId: string;
|
||||
hash?: string;
|
||||
onLocaleChange(locale: Locale): void;
|
||||
}
|
||||
export interface ThemeSelectorProps {
|
||||
preference: ThemePreference;
|
||||
onPreferenceChange(value: ThemePreference): void;
|
||||
}
|
||||
export interface HeroModel {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
body: string;
|
||||
primaryLabel: string;
|
||||
secondaryLabel: string;
|
||||
}
|
||||
export interface ProductPreviewModel {
|
||||
status: EvidenceStatus;
|
||||
caption: string;
|
||||
locale: Locale;
|
||||
direction: Direction;
|
||||
theme: 'light' | 'dark';
|
||||
assetId?: string;
|
||||
}
|
||||
export interface LifecycleItem {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
}
|
||||
export interface ComparisonModel {
|
||||
title: string;
|
||||
body: string;
|
||||
before: { title: string; items: string[] };
|
||||
after: { title: string; items: string[] };
|
||||
}
|
||||
export interface WorkflowStep {
|
||||
id: string;
|
||||
ordinal: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
export interface RoleCard {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
export interface ModuleCard {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
evidenceStatus: EvidenceStatus;
|
||||
}
|
||||
export interface MetricDefinition {
|
||||
id: string;
|
||||
title: string;
|
||||
definition: string;
|
||||
publishedValue?: never;
|
||||
}
|
||||
export interface IntegrationCategory {
|
||||
id: string;
|
||||
label: string;
|
||||
verifiedPartners?: never;
|
||||
}
|
||||
export interface SecurityCheck {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
certificationBadge?: never;
|
||||
}
|
||||
export interface PricingModel {
|
||||
mode: 'quote-only' | 'approved-pricing';
|
||||
title: string;
|
||||
body: string;
|
||||
factors: string[];
|
||||
note: string;
|
||||
}
|
||||
export interface FaqItem {
|
||||
id: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
export interface DemoFormValues {
|
||||
fullName: string;
|
||||
workEmail: string;
|
||||
company: string;
|
||||
fleetSize: string;
|
||||
market?: string;
|
||||
preferredLanguage?: Locale;
|
||||
message?: string;
|
||||
}
|
||||
export type DemoFormState =
|
||||
| 'idle'
|
||||
| 'validating'
|
||||
| 'submitting'
|
||||
| 'success'
|
||||
| 'recoverable_error'
|
||||
| 'terminal_error';
|
||||
export interface DemoFormProps {
|
||||
state: DemoFormState;
|
||||
values: DemoFormValues;
|
||||
fieldErrors: Partial<Record<keyof DemoFormValues, string>>;
|
||||
formError?: string;
|
||||
onSubmit(values: DemoFormValues): Promise<void>;
|
||||
}
|
||||
export interface TourProps {
|
||||
steps: WorkflowStep[];
|
||||
activeIndex: number;
|
||||
open: boolean;
|
||||
onOpenChange(open: boolean): void;
|
||||
onStepChange(index: number): void;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import manifest from "../localization/ROUTE_MANIFEST_v1.0.json";
|
||||
type Locale = "en"|"fr"|"ar";
|
||||
export function localizedPath(routeId:string, locale:Locale):string {
|
||||
import manifest from '../localization/ROUTE_MANIFEST_v1.0.json';
|
||||
type Locale = 'en' | 'fr' | 'ar';
|
||||
export function localizedPath(routeId: string, locale: Locale): string {
|
||||
const route = manifest.routes.find((item) => item.id === routeId);
|
||||
if (!route) throw new Error(`Unknown route: ${routeId}`);
|
||||
const slug = route.slugs[locale];
|
||||
return slug ? `/${locale}/${slug}` : `/${locale}`;
|
||||
}
|
||||
export function hreflangLinks(origin:string, routeId:string) {
|
||||
const alternates = manifest.locales.map((locale) => ({ rel:"alternate", hreflang:locale, href:new URL(localizedPath(routeId, locale as Locale),origin).href }));
|
||||
alternates.push({ rel:"alternate", hreflang:"x-default", href:new URL(localizedPath(routeId,"en"),origin).href });
|
||||
export function hreflangLinks(origin: string, routeId: string) {
|
||||
const alternates = manifest.locales.map((locale) => ({
|
||||
rel: 'alternate',
|
||||
hreflang: locale,
|
||||
href: new URL(localizedPath(routeId, locale as Locale), origin).href,
|
||||
}));
|
||||
alternates.push({
|
||||
rel: 'alternate',
|
||||
hreflang: 'x-default',
|
||||
href: new URL(localizedPath(routeId, 'en'), origin).href,
|
||||
});
|
||||
return alternates;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
export const locales = ["en", "fr", "ar"] as const;
|
||||
export type Locale = typeof locales[number];
|
||||
export const defaultLocale: Locale = "en";
|
||||
export const directionByLocale: Record<Locale,"ltr"|"rtl"> = { en:"ltr", fr:"ltr", ar:"rtl" };
|
||||
export const localeCookie = "hpc-locale";
|
||||
export const localeStorageKey = "hpc.locale";
|
||||
export const campaignQueryAllowlist = ["utm_source","utm_medium","utm_campaign","utm_content","gclid"] as const;
|
||||
export const rootDetectionOrder = ["cookie","accept-language","default-en"] as const;
|
||||
export function isLocale(value: unknown): value is Locale { return typeof value === "string" && (locales as readonly string[]).includes(value); }
|
||||
export function isolateLtr(value: string): { dir:"ltr"; value:string } { return { dir:"ltr", value }; }
|
||||
export const locales = ['en', 'fr', 'ar'] as const;
|
||||
export type Locale = (typeof locales)[number];
|
||||
export const defaultLocale: Locale = 'en';
|
||||
export const directionByLocale: Record<Locale, 'ltr' | 'rtl'> = { en: 'ltr', fr: 'ltr', ar: 'rtl' };
|
||||
export const localeCookie = 'hpc-locale';
|
||||
export const localeStorageKey = 'hpc.locale';
|
||||
export const campaignQueryAllowlist = [
|
||||
'utm_source',
|
||||
'utm_medium',
|
||||
'utm_campaign',
|
||||
'utm_content',
|
||||
'gclid',
|
||||
] as const;
|
||||
export const rootDetectionOrder = ['cookie', 'accept-language', 'default-en'] as const;
|
||||
export function isLocale(value: unknown): value is Locale {
|
||||
return typeof value === 'string' && (locales as readonly string[]).includes(value);
|
||||
}
|
||||
export function isolateLtr(value: string): { dir: 'ltr'; value: string } {
|
||||
return { dir: 'ltr', value };
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
export const themeCookie = "hpc-theme";
|
||||
export const themeStorageKey = "hpc.theme.preference";
|
||||
export const themePreferences = ["light","dark","system"] as const;
|
||||
export type ThemePreference = typeof themePreferences[number];
|
||||
export type ResolvedTheme = "light"|"dark";
|
||||
export function isThemePreference(v:unknown): v is ThemePreference { return typeof v === "string" && (themePreferences as readonly string[]).includes(v); }
|
||||
export function resolveTheme(preference:ThemePreference, systemDark:boolean):ResolvedTheme { return preference === "system" ? (systemDark ? "dark" : "light") : preference; }
|
||||
export const themeCookieAttributes = { path:"/", sameSite:"lax" as const, maxAgeSeconds:31536000, secureInProduction:true };
|
||||
export const themeCookie = 'hpc-theme';
|
||||
export const themeStorageKey = 'hpc.theme.preference';
|
||||
export const themePreferences = ['light', 'dark', 'system'] as const;
|
||||
export type ThemePreference = (typeof themePreferences)[number];
|
||||
export type ResolvedTheme = 'light' | 'dark';
|
||||
export function isThemePreference(v: unknown): v is ThemePreference {
|
||||
return typeof v === 'string' && (themePreferences as readonly string[]).includes(v);
|
||||
}
|
||||
export function resolveTheme(preference: ThemePreference, systemDark: boolean): ResolvedTheme {
|
||||
return preference === 'system' ? (systemDark ? 'dark' : 'light') : preference;
|
||||
}
|
||||
export const themeCookieAttributes = {
|
||||
path: '/',
|
||||
sameSite: 'lax' as const,
|
||||
maxAgeSeconds: 31536000,
|
||||
secureInProduction: true,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user