Import Tripoli Cloud frontend prototype

React/TypeScript/Vite/Tailwind prototype from the design session (login,
dashboard, photos, backups, settings views with mocked API layer).
Infra work (Rust services, Ansible, OpenTofu) follows in subsequent commits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-09-16 21:21:43 +02:00
commit 5dbd3b96f6
73 changed files with 4789 additions and 0 deletions

View file

@ -0,0 +1,39 @@
import type { NotificationPrefKey, SettingsRow } from '../../types';
interface SettingsRowViewProps {
row: SettingsRow;
onToggle: (key: NotificationPrefKey) => void;
}
export function SettingsRowView({ row, onToggle }: SettingsRowViewProps) {
return (
<div className="flex flex-wrap items-center gap-4 border-b border-line p-[18px]">
<div className="min-w-0 flex-1 basis-[260px]">
<p className="m-0 mb-1 text-sm font-semibold text-ink">{row.label}</p>
<p className="m-0 text-[12.5px] leading-relaxed text-ink-soft">{row.help}</p>
</div>
{row.kind === 'toggle' ? (
<button
type="button"
onClick={() => onToggle(row.id as NotificationPrefKey)}
role="switch"
aria-checked={row.on}
aria-label={row.label}
className={`flex h-[26px] w-[46px] flex-none items-center rounded-full border border-line p-[3px] transition-colors ${
row.on ? 'bg-brand-gradient' : 'bg-field'
}`}
>
<span
className="h-5 w-5 rounded-full bg-white shadow-[0_2px_6px_rgba(0,0,0,0.3)] transition-transform"
style={{ transform: row.on ? 'translateX(20px)' : 'translateX(0)' }}
/>
</button>
) : (
<span className="flex-none rounded-[10px] border border-line bg-field px-3.5 py-2 text-[13px] font-semibold text-ink">
{row.value}
</span>
)}
</div>
);
}

View file

@ -0,0 +1,55 @@
import { useNavigation } from '../../contexts/NavigationContext';
import { SETTINGS_TABS, useSettings } from '../../hooks/useSettings';
import { SettingsRowView } from './SettingsRowView';
export function SettingsView() {
const { settingsTab, setSettingsTab } = useNavigation();
const { title, rows, togglePref } = useSettings(settingsTab);
return (
<div className="flex max-w-[860px] flex-col gap-6">
<div>
<p className="m-0 mb-[7px] text-[12.5px] font-semibold text-ink-soft">Réglages</p>
<h1 className="m-0 text-[26px] font-extrabold leading-tight tracking-tight sm:text-[36px]">{title}</h1>
</div>
<div className="flex gap-1 overflow-x-auto rounded-[13px] border border-line bg-field p-1">
{SETTINGS_TABS.map((tab) => {
const active = settingsTab === tab.key;
return (
<button
key={tab.key}
type="button"
onClick={() => setSettingsTab(tab.key)}
className={`flex-none whitespace-nowrap rounded-[10px] px-4 py-2.5 text-[13px] font-semibold transition-colors ${
active ? 'bg-panel2 text-ink' : 'bg-transparent text-ink-soft'
}`}
>
{tab.label}
</button>
);
})}
</div>
<div className="overflow-hidden rounded-[18px] border border-line bg-panel">
{rows.map((row) => (
<SettingsRowView key={row.id} row={row} onToggle={togglePref} />
))}
<div className="flex flex-wrap gap-2.5 p-[18px]">
<button
type="button"
className="rounded-xl bg-brand-gradient px-[18px] py-3 text-[13.5px] font-bold text-white transition-[filter] hover:brightness-110"
>
Enregistrer
</button>
<button
type="button"
className="rounded-xl border border-line bg-field px-[18px] py-3 text-[13.5px] font-semibold text-ink"
>
Annuler
</button>
</div>
</div>
</div>
);
}