-
+
{t('customers.actions.importCsv')}
-
+
{t('common.backToOrders')}
diff --git a/app/entry/page.tsx b/app/entry/page.tsx
index f463310..38ff781 100644
--- a/app/entry/page.tsx
+++ b/app/entry/page.tsx
@@ -1,6 +1,7 @@
import Link from 'next/link';
import { redirect } from 'next/navigation';
import { LanguageSwitcher } from '@/components/language-switcher';
+import { skossBootstrapRoutes, skossinaRoutes } from '@/lib/application-planes';
import {
launchDemoModeAction,
recoverLocalAdminAccessAction,
@@ -81,7 +82,7 @@ export default async function EntryGatewayPage({
const state = await detectInstanceGatewayState(data);
if (currentUser && state.hasAdminUser && state.hasInstance && !state.onboardingIncomplete) {
- redirect('/');
+ redirect(skossinaRoutes.home);
}
const copy = getEntryCopy(locale);
const runtimeMode = getRuntimeMode();
@@ -126,7 +127,7 @@ export default async function EntryGatewayPage({
{copy.newTitle}
{copy.newBody}
{state.canRunBootstrap ? (
-
+
{copy.newAction}
) : (
diff --git a/app/login/page.tsx b/app/login/page.tsx
index 1fe33db..3bfaeb9 100644
--- a/app/login/page.tsx
+++ b/app/login/page.tsx
@@ -1,8 +1,9 @@
+import Link from 'next/link';
+import { skossBootstrapRoutes } from '@/lib/application-planes';
import { loginAction } from '@/lib/server/actions';
import { getServerTranslator } from '@/lib/i18n/server';
import { readAppData } from '@/lib/server/persistence';
import { detectInstanceGatewayState } from '@/lib/server/instance-entry';
-import Link from 'next/link';
export default async function LoginPage({
searchParams,
@@ -52,11 +53,11 @@ export default async function LoginPage({
- Need a different path? Back to entry gateway for new setup, restore, or demo mode.
+ Need a different path? Back to entry gateway for new setup, restore, or demo mode.
{gatewayState.canRunBootstrap ? (
- Need to initialize this instance first? Run first-use wizard
+ Need to initialize this instance first? Run first-use wizard
) : null}
diff --git a/app/page.tsx b/app/page.tsx
index 4b69c3d..11e50e0 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -3,27 +3,28 @@ import { redirect } from 'next/navigation';
import { ActivityFeed } from '@/components/activity-feed';
import { formatDateLabel } from '@/lib/domain/formatters';
import { getPresetExperience, type WorkspaceLinkKey } from '@/lib/business-presets';
-import { getWorkspaceSummary } from '@/lib/server/demo-data';
+import { buildWorkspaceSummary } from '@/lib/server/demo-data';
import { getCurrentUserContext } from '@/lib/server/auth';
import { detectInstanceGatewayState, shouldRouteToEntryGateway } from '@/lib/server/instance-entry';
import { getServerTranslator } from '@/lib/i18n/server';
import { readAppData } from '@/lib/server/persistence';
+import { skossBootstrapRoutes, skossCoreRoutes, skossinaRoutes, type AppPlaneRoute } from '@/lib/application-planes';
import { ArrowRightIcon, CustomersIcon, HandoffIcon, OrdersIcon, ProductionIcon, SetupIcon, SparklesIcon, TimelineIcon } from '@/components/ui-icons';
type QuickLink = {
- href: Parameters
[0]['href'];
+ href: AppPlaneRoute;
title: string;
description: string;
icon: typeof OrdersIcon;
};
export default async function HomePage() {
- const [summary, { t, locale, preset }, data, userContext] = await Promise.all([
- getWorkspaceSummary(),
+ const [{ t, locale, preset }, data, userContext] = await Promise.all([
getServerTranslator(),
readAppData(),
getCurrentUserContext(),
]);
+ const summary = buildWorkspaceSummary(data);
const { currentUser, visibleWorkspaces, homeWorkspace } = userContext;
const gatewayState = await detectInstanceGatewayState(data);
@@ -39,37 +40,37 @@ export default async function HomePage() {
const quickLinkMap: Record = {
timeline: {
- href: '/timeline',
+ href: skossinaRoutes.timeline,
title: t('home.quickLinks.timeline.title'),
description: t('home.quickLinks.timeline.description'),
icon: TimelineIcon,
},
orders: {
- href: '/orders',
+ href: skossinaRoutes.orders,
title: t('home.quickLinks.orders.title'),
description: t('home.quickLinks.orders.description'),
icon: OrdersIcon,
},
customers: {
- href: '/customers',
+ href: skossinaRoutes.customers,
title: t('home.quickLinks.customers.title'),
description: t('home.quickLinks.customers.description'),
icon: CustomersIcon,
},
production: {
- href: '/production',
+ href: skossinaRoutes.production,
title: t('home.quickLinks.production.title'),
description: t('home.quickLinks.production.description'),
icon: ProductionIcon,
},
handoff: {
- href: '/handoff',
+ href: skossinaRoutes.handoff,
title: t('home.quickLinks.handoff.title'),
description: t('home.quickLinks.handoff.description'),
icon: HandoffIcon,
},
admin: {
- href: '/admin/setup',
+ href: skossCoreRoutes.adminSetup,
title: t('home.quickLinks.setup.title'),
description: t('home.quickLinks.setup.description'),
icon: SetupIcon,
@@ -84,7 +85,7 @@ export default async function HomePage() {
const orderedQuickLinks = orderedQuickLinkKeys.map((key) => quickLinkMap[key]);
if (!summary.preferences.onboardingCompleted) {
- redirect('/bootstrap?step=1');
+ redirect(skossBootstrapRoutes.bootstrapStart);
}
return (
diff --git a/app/setup/page.tsx b/app/setup/page.tsx
index 13aaf87..6899c38 100644
--- a/app/setup/page.tsx
+++ b/app/setup/page.tsx
@@ -1,5 +1,6 @@
import { redirect } from 'next/navigation';
+import { skossCoreRoutes } from '@/lib/application-planes';
export default function LegacySetupRedirectPage() {
- redirect('/admin/setup');
+ redirect(skossCoreRoutes.adminSetup);
}
diff --git a/components/app-shell.tsx b/components/app-shell.tsx
index 478d1d6..3ea757c 100644
--- a/components/app-shell.tsx
+++ b/components/app-shell.tsx
@@ -3,13 +3,16 @@ import type { ReactNode } from 'react';
import { headers } from 'next/headers';
import { PrimaryNav } from '@/components/primary-nav';
import { UserMenu } from '@/components/user-menu';
+import { getApplicationPlane, skossinaRoutes } from '@/lib/application-planes';
import { getServerTranslator } from '@/lib/i18n/server';
-import { readAppData } from '@/lib/server/persistence';
+import { readPersistence } from '@/lib/server/persistence';
import { getCurrentUserContext } from '@/lib/server/auth';
import { getRuntimeModeLabel, isNonProductionMode } from '@/lib/server/runtime-mode';
export async function AppShell({ children }: { children?: ReactNode }) {
- const [{ t }, data] = await Promise.all([getServerTranslator(), readAppData()]);
+ const [{ t }, persistence] = await Promise.all([getServerTranslator(), readPersistence()]);
+ const data = persistence.raw;
+ const workspace = persistence.instance.getWorkspace();
const { currentUser, visibleWorkspaces } = await getCurrentUserContext(data);
const showNonProductionBanner = isNonProductionMode();
const runtimeModeLabel = getRuntimeModeLabel();
@@ -19,9 +22,9 @@ export async function AppShell({ children }: { children?: ReactNode }) {
?? headerStore.get('x-invoke-path')
?? headerStore.get('x-matched-path')
?? '';
- const isBootstrapRoute = requestPath.startsWith('/bootstrap');
+ const plane = getApplicationPlane(requestPath);
- if (isBootstrapRoute) {
+ if (plane === 'bootstrap') {
return (
{showNonProductionBanner ? (
@@ -42,9 +45,9 @@ export async function AppShell({ children }: { children?: ReactNode }) {
) : null}
-
+
SKOSS
- {data.workspace.name}
+ {workspace.name}
{t('shell.brandTitle')}
diff --git a/components/primary-nav.tsx b/components/primary-nav.tsx
index 407375f..591f8c8 100644
--- a/components/primary-nav.tsx
+++ b/components/primary-nav.tsx
@@ -4,6 +4,7 @@ import Link from 'next/link';
import type { ReactNode } from 'react';
import { usePathname } from 'next/navigation';
import { useI18n } from '@/components/i18n-provider';
+import { adminPlaneNavItems, operatorWorkspaceRoutes, type AppPlaneRoute, type OperatorWorkspaceSurface } from '@/lib/application-planes';
import {
CustomersIcon,
HandoffIcon,
@@ -14,28 +15,14 @@ import {
} from '@/components/ui-icons';
import type { WorkspaceSurface } from '@/lib/domain/types';
-type NavHref =
- | '/'
- | '/timeline'
- | '/orders'
- | '/customers'
- | '/production'
- | '/handoff';
-
type NavItem = {
- key: Exclude
;
- href: NavHref;
+ key: OperatorWorkspaceSurface;
+ href: AppPlaneRoute;
labelKey: string;
icon: typeof HomeIcon;
active: (pathname: string) => boolean;
};
-type DesktopAdminItem = {
- href: '/admin' | '/admin/setup' | '/admin/modules';
- labelKey: string;
- active: (pathname: string) => boolean;
-};
-
function NavLink({
href,
label,
@@ -43,7 +30,7 @@ function NavLink({
children,
compact = false,
}: {
- href: NavHref | '/admin' | '/admin/setup' | '/admin/modules';
+ href: AppPlaneRoute;
label: string;
active: boolean;
children?: ReactNode;
@@ -64,54 +51,48 @@ function NavLink({
const navItems: NavItem[] = [
{
key: 'home',
- href: '/',
+ href: operatorWorkspaceRoutes.home,
labelKey: 'nav.home',
icon: HomeIcon,
active: (pathname) => pathname === '/',
},
{
key: 'timeline',
- href: '/timeline',
+ href: operatorWorkspaceRoutes.timeline,
labelKey: 'nav.timeline',
icon: TimelineIcon,
active: (pathname) => pathname.startsWith('/timeline'),
},
{
key: 'orders',
- href: '/orders',
+ href: operatorWorkspaceRoutes.orders,
labelKey: 'nav.orders',
icon: OrdersIcon,
active: (pathname) => pathname.startsWith('/orders'),
},
{
key: 'customers',
- href: '/customers',
+ href: operatorWorkspaceRoutes.customers,
labelKey: 'nav.customers',
icon: CustomersIcon,
active: (pathname) => pathname.startsWith('/customers'),
},
{
key: 'production',
- href: '/production',
+ href: operatorWorkspaceRoutes.production,
labelKey: 'nav.production',
icon: ProductionIcon,
active: (pathname) => pathname.startsWith('/production'),
},
{
key: 'handoff',
- href: '/handoff',
+ href: operatorWorkspaceRoutes.handoff,
labelKey: 'nav.handoff',
icon: HandoffIcon,
active: (pathname) => pathname.startsWith('/handoff'),
},
];
-const desktopAdminItems: DesktopAdminItem[] = [
- { href: '/admin', labelKey: 'nav.setup', active: (pathname) => pathname === '/admin' },
- { href: '/admin/setup', labelKey: 'setup.sections.businessSetup', active: (pathname) => pathname.startsWith('/admin/setup') },
- { href: '/admin/modules', labelKey: 'setup.sections.preferencesSystem', active: (pathname) => pathname.startsWith('/admin/modules') },
-];
-
export function PrimaryNav({
visibleWorkspaces,
mode = 'mobile',
@@ -157,7 +138,7 @@ export function PrimaryNav({
{showAdminSection ? (