chore: scaffold Next.js 16 admin app with ITA ITO design tokens
Set up the project skeleton for the ITA ITO opening-hours display admin: Next.js 16 (App Router) with TypeScript in strict mode, Tailwind CSS 4, Vitest, ESLint and a blocking CI workflow. The design tokens are copied verbatim from the model_ita_ito project (palette, Inter Variable + Source Serif 4, radii, dark theme) so the two applications look like one family, as required by the spec. ESLint is pinned to v9: eslint-config-next bundles a react plugin that crashes on ESLint 10. The typed `consistent-type-imports` rule is left out because `verbatimModuleSyntax` already enforces the same discipline at compile time, without the cost of typed linting across the repo. PLAN.md records the agreed architecture, including the decisions that depart from the original spec — most importantly the move from BYOD to a self-hosted BYOS server, which removes the TRMNL private plugin, the Liquid template and the webhook entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
export default function AdminHome() {
|
||||
return (
|
||||
<main className="mx-auto max-w-3xl px-4 py-10">
|
||||
<h1 className="text-2xl">ITA ITO — Horaires</h1>
|
||||
<p className="mt-2 text-[var(--ink-muted)]">Tableau de bord à venir.</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
/** Liveness probe used by the Docker HEALTHCHECK. Must stay dependency-free. */
|
||||
export function GET() {
|
||||
return NextResponse.json({ status: 'ok' });
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import './styles/index.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'ITA ITO — Horaires',
|
||||
description: "Administration des horaires d'ouverture affichés en vitrine.",
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
width: 'device-width',
|
||||
initialScale: 1,
|
||||
// The shop-floor use case is a phone held in one hand.
|
||||
viewportFit: 'cover',
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="fr">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default function Home() {
|
||||
redirect('/admin');
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
@import '@fontsource-variable/inter';
|
||||
@import '@fontsource-variable/source-serif-4';
|
||||
@import 'tailwindcss';
|
||||
@import './tokens.css';
|
||||
|
||||
/* Tokens are the single source of truth; Tailwind utilities read them from here. */
|
||||
@theme inline {
|
||||
--color-canvas: var(--canvas);
|
||||
--color-surface: var(--surface);
|
||||
--color-surface-muted: var(--surface-muted);
|
||||
--color-line: var(--line);
|
||||
--color-line-strong: var(--line-strong);
|
||||
--color-ink: var(--ink);
|
||||
--color-ink-muted: var(--ink-muted);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-hover: var(--accent-hover);
|
||||
--color-accent-tint: var(--accent-tint);
|
||||
--color-accent-ink: var(--accent-ink);
|
||||
--color-on-accent: var(--on-accent);
|
||||
--color-danger: var(--danger);
|
||||
--color-danger-tint: var(--danger-tint);
|
||||
--color-success: var(--success);
|
||||
--color-success-tint: var(--success-tint);
|
||||
--radius-sm: var(--radius-sm);
|
||||
--radius-md: var(--radius-md);
|
||||
--radius-lg: var(--radius-lg);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-serif: var(--font-serif);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100dvh;
|
||||
background: var(--canvas);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-sans);
|
||||
font-feature-settings: 'cv11', 'ss01';
|
||||
line-height: 1.55;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-family: var(--font-serif);
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Design tokens. Values inspired by a warm, calm palette; checked for WCAG AA:
|
||||
* - ink on canvas 17.9:1, ink-muted on canvas 6.4:1
|
||||
* - accent (#B5552F) with white text 4.9:1; dark accent (#D97757) on dark canvas 4.9:1
|
||||
*/
|
||||
:root {
|
||||
--canvas: #faf9f5;
|
||||
--surface: #ffffff;
|
||||
--surface-muted: #f3f1ea;
|
||||
--line: #e8e6dc;
|
||||
--line-strong: #d6d3c7;
|
||||
--ink: #141413;
|
||||
--ink-muted: #5e5d59;
|
||||
--accent: #b5552f;
|
||||
--accent-hover: #9e4827;
|
||||
--accent-soft: #c96442;
|
||||
--accent-tint: #f6e7df;
|
||||
/* Accent text on the tinted background: 5.1:1. */
|
||||
--accent-ink: #983f1e;
|
||||
--on-accent: #ffffff;
|
||||
--danger: #b42318;
|
||||
--danger-tint: #fdecea;
|
||||
--success: #3f6b4b;
|
||||
--success-tint: #e7f0e9;
|
||||
--observation: #c8102e;
|
||||
--focus-ring: #c96442;
|
||||
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
|
||||
--font-sans: 'Inter Variable', 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||||
--font-serif: 'Source Serif 4 Variable', 'Source Serif 4', Georgia, 'Times New Roman', serif;
|
||||
|
||||
--safe-top: env(safe-area-inset-top, 0px);
|
||||
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
||||
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
|
||||
:root[data-theme='dark'] {
|
||||
--canvas: #262624;
|
||||
--surface: #30302e;
|
||||
--surface-muted: #2b2b29;
|
||||
--line: #3e3e3a;
|
||||
--line-strong: #52524d;
|
||||
--ink: #f5f4ee;
|
||||
--ink-muted: #b7b5a9;
|
||||
--accent: #d97757;
|
||||
--accent-hover: #e38b6e;
|
||||
--accent-soft: #d97757;
|
||||
--accent-tint: #3d2e27;
|
||||
--accent-ink: #f0b8a0;
|
||||
--on-accent: #1a1a18;
|
||||
--danger: #f08a7e;
|
||||
--danger-tint: #3f2724;
|
||||
--success: #8fbf9a;
|
||||
--success-tint: #26352a;
|
||||
--observation: #ff6b6b;
|
||||
--focus-ring: #d97757;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme='light']) {
|
||||
--canvas: #262624;
|
||||
--surface: #30302e;
|
||||
--surface-muted: #2b2b29;
|
||||
--line: #3e3e3a;
|
||||
--line-strong: #52524d;
|
||||
--ink: #f5f4ee;
|
||||
--ink-muted: #b7b5a9;
|
||||
--accent: #d97757;
|
||||
--accent-hover: #e38b6e;
|
||||
--accent-soft: #d97757;
|
||||
--accent-tint: #3d2e27;
|
||||
--accent-ink: #f0b8a0;
|
||||
--on-accent: #1a1a18;
|
||||
--danger: #f08a7e;
|
||||
--danger-tint: #3f2724;
|
||||
--success: #8fbf9a;
|
||||
--success-tint: #26352a;
|
||||
--observation: #ff6b6b;
|
||||
--focus-ring: #d97757;
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
/* Status / badge tones: soft backgrounds, text ≥ 4.5:1 on its background in both themes. */
|
||||
:root {
|
||||
--tone-neutral-bg: #efede6;
|
||||
--tone-neutral-fg: #4a4944;
|
||||
--tone-accent-bg: #f6e7df;
|
||||
--tone-accent-fg: #8f3f20;
|
||||
--tone-sand-bg: #f1eadb;
|
||||
--tone-sand-fg: #6b5a36;
|
||||
--tone-ochre-bg: #f3e6c8;
|
||||
--tone-ochre-fg: #6f5310;
|
||||
--tone-amber-bg: #f6e2c7;
|
||||
--tone-amber-fg: #7a4a0e;
|
||||
--tone-terracotta-bg: #f4ddd3;
|
||||
--tone-terracotta-fg: #8a3a1d;
|
||||
--tone-plum-bg: #ece0ea;
|
||||
--tone-plum-fg: #6a3b61;
|
||||
--tone-slate-bg: #e1e7ee;
|
||||
--tone-slate-fg: #3b5068;
|
||||
--tone-sage-bg: #e2ebdf;
|
||||
--tone-sage-fg: #3d5a37;
|
||||
--tone-olive-bg: #e8e8d3;
|
||||
--tone-olive-fg: #555522;
|
||||
--tone-gray-bg: #e8e7e3;
|
||||
--tone-gray-fg: #54534e;
|
||||
|
||||
/* Grid ("hoja de pedidos") in the application look. */
|
||||
--grid-header-bg: #efece2;
|
||||
--grid-line: #ddd8ca;
|
||||
--grid-block-line: #b9b3a2;
|
||||
--grid-no-bg: #f3f1ea;
|
||||
--grid-active: #c96442;
|
||||
--grid-selection-bg: #f6e7df;
|
||||
--tabbar-height: 0px;
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] {
|
||||
--tone-neutral-bg: #3a3a37;
|
||||
--tone-neutral-fg: #dedcd2;
|
||||
--tone-accent-bg: #4a3128;
|
||||
--tone-accent-fg: #f0b8a0;
|
||||
--tone-sand-bg: #413a2c;
|
||||
--tone-sand-fg: #e6d6b0;
|
||||
--tone-ochre-bg: #443a22;
|
||||
--tone-ochre-fg: #ecd18f;
|
||||
--tone-amber-bg: #45351f;
|
||||
--tone-amber-fg: #f0c48c;
|
||||
--tone-terracotta-bg: #4a3027;
|
||||
--tone-terracotta-fg: #f1b59c;
|
||||
--tone-plum-bg: #40303e;
|
||||
--tone-plum-fg: #e5bfdd;
|
||||
--tone-slate-bg: #2f3945;
|
||||
--tone-slate-fg: #bdd0e6;
|
||||
--tone-sage-bg: #2f3b2d;
|
||||
--tone-sage-fg: #c1dcb9;
|
||||
--tone-olive-bg: #393a26;
|
||||
--tone-olive-fg: #dcdca6;
|
||||
--tone-gray-bg: #3a3a38;
|
||||
--tone-gray-fg: #d6d4cc;
|
||||
--grid-header-bg: #34342f;
|
||||
--grid-line: #45443f;
|
||||
--grid-block-line: #67655d;
|
||||
--grid-no-bg: #2d2d2b;
|
||||
--grid-active: #d97757;
|
||||
--grid-selection-bg: #3d2e27;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme='light']) {
|
||||
--tone-neutral-bg: #3a3a37;
|
||||
--tone-neutral-fg: #dedcd2;
|
||||
--tone-accent-bg: #4a3128;
|
||||
--tone-accent-fg: #f0b8a0;
|
||||
--tone-sand-bg: #413a2c;
|
||||
--tone-sand-fg: #e6d6b0;
|
||||
--tone-ochre-bg: #443a22;
|
||||
--tone-ochre-fg: #ecd18f;
|
||||
--tone-amber-bg: #45351f;
|
||||
--tone-amber-fg: #f0c48c;
|
||||
--tone-terracotta-bg: #4a3027;
|
||||
--tone-terracotta-fg: #f1b59c;
|
||||
--tone-plum-bg: #40303e;
|
||||
--tone-plum-fg: #e5bfdd;
|
||||
--tone-slate-bg: #2f3945;
|
||||
--tone-slate-fg: #bdd0e6;
|
||||
--tone-sage-bg: #2f3b2d;
|
||||
--tone-sage-fg: #c1dcb9;
|
||||
--tone-olive-bg: #393a26;
|
||||
--tone-olive-fg: #dcdca6;
|
||||
--tone-gray-bg: #3a3a38;
|
||||
--tone-gray-fg: #d6d4cc;
|
||||
--grid-header-bg: #34342f;
|
||||
--grid-line: #45443f;
|
||||
--grid-block-line: #67655d;
|
||||
--grid-no-bg: #2d2d2b;
|
||||
--grid-active: #d97757;
|
||||
--grid-selection-bg: #3d2e27;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user