feat(oxibadge): add onboarding flow with i18n and reader preferences
Check, format and test / build (push) Successful in 9m59s

Adds the first real screen: a setup form for the device's host/port,
tested against the real device (3 retries) and persisted to
preferences.toml before showing a placeholder "done" screen.

- Rust: test_connection/save_preferences/load_preferences commands;
  preferences stored as [[reader]] in TOML so more readers can be
  added later without a format change.
- Svelte: split the monolithic page into reusable components
  (Screen, Column, Field, Button, ErrorBanner, Hint) under
  src/lib/components, sharing design tokens from app.css.
- Automatic it/en i18n via svelte-i18n, locale detected from
  navigator.language.

Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 11:08:02 +02:00
parent 268a917a69
commit 99772b9e4a
19 changed files with 821 additions and 29 deletions
+22 -29
View File
@@ -1,33 +1,26 @@
<main>
<img src="/logo.svg" alt="Oxibadge logo" class="logo" />
<h1>Hello, world!</h1>
</main>
<script lang="ts">
import { invoke } from '@tauri-apps/api/core';
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import Screen from '$lib/components/Screen.svelte';
import Onboarding from '$lib/components/Onboarding.svelte';
<style>
:global(body) {
margin: 0;
color: #f6f6f6;
background-color: #2f2f2f;
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
}
type Step = 'loading' | 'setup' | 'done';
main {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1.5rem;
}
let step = $state<Step>('loading');
.logo {
width: 8rem;
height: 8rem;
}
onMount(async () => {
const preferences = await invoke<{ host: string; port: number } | null>('load_preferences');
step = preferences ? 'done' : 'setup';
});
</script>
h1 {
margin: 0;
font-size: 2rem;
font-weight: 500;
}
</style>
<Screen>
{#if step === 'loading'}
<p>{$_('loading')}</p>
{:else if step === 'setup'}
<Onboarding onconnected={() => (step = 'done')} />
{:else}
<p>{$_('done.message')}</p>
{/if}
</Screen>