Files
rezervovator/.claude/rules/frontend.md
T
2026-10-12 07:51:29 +02:00

4.4 KiB

paths
paths
src/pages/**
src/components/**
src/locales/**
src/app.rs
src/validator.rs
style/**

Frontend rules

Components

  • Leptos 0.8 API only (use leptos::prelude::*). Components are #[component] functions returning impl IntoView, one page or dialog per file in src/pages/; reusable building blocks go to src/components/. Register new files in the respective mod.rs (pub mod only if used from app.rs).
  • This code runs on the server (SSR) and in the browser (hydration). Browser-only APIs (window(), web_sys) must not run during SSR — keep them in event handlers/effects or behind #[cfg(not(feature = "ssr"))]. Markup must render the same on both sides or hydration breaks.
  • A new admin page needs a <Route path="admin/..."/> in src/app.rs wrapped in <AdminPortal>, and a menu entry in src/components/admin_portal.rs. Paths starting with /admin are login-protected by the middleware; data access is still guarded by perm_check! in the server fn.

Leptos 0.8 pitfalls

  • Views are statically typed: if/else and match arms that return different markup must each end with .into_any().
  • Context is not available in event handlers (on:click etc.). Call use_context in the component body and keep the result — DialogOpener and MenuOpener do this in new(), so create them in a component body, not inside a handler.
  • Markup must be valid HTML, or hydration panics in the browser and the whole page stops reacting: no block elements (<div>, <table>, another <p>) inside <p>, and <tr> only inside <tbody>/<thead>. The compiler doesn't catch this — load the page with a full reload and check the browser console.
  • HTML attributes are typed. Deprecated ones (align, width on <table>) don't compile; use style.
  • A value used twice in one view! (e.g. in an attribute and as a child) must be cloned for the first use.
  • Client-side form validation hooks into <ActionForm> with on:submit:capture and ev.prevent_default(); a plain on:submit runs too late. DataForm already does this.
  • action.value() is not an RwSignal; pass it on as Signal::derive(move || action.value().get()).

Data loading and forms

  • Load data with Resource::new_blocking / Resource::new calling the server fn, rendered inside <Transition fallback=...> with {trl("Loading...")}. Read the resource in a closure — {move || res.get().map(...)} — a plain {res.get()...} block is evaluated once and never updates. Use a dialog's opener.visible() as the resource source so lists reload after the dialog closes (see src/pages/users.rs).
  • Handle all three outcomes of a server call: Err(e), Ok(ApiResponse::Error(msg)), Ok(ApiResponse::Data(d)).
  • Edit/create dialogs: DialogOpener::new() in the parent, ServerAction::<ServerFnStruct>::new() in the dialog, wrapped in <DataForm opener=.. action=.. title="..">. It supplies the modal, Save/Close buttons, client validation and server error display. Confirmations use <QuestionDialog>.
  • Form field names follow the server fn argument: name="company[name]". Pass the id as a hidden input. Fill inputs with prop:value={move || signal.get().field}.
  • DataForm/ModalDialog take title as an untranslated English key (&'static str); they translate it themselves.

Translations

  • Every user-visible string goes through trl("English text"); never hard-code Czech (or any text) in a view. The English text is the key and the fallback.
  • When you add or change a key, add the Czech translation to the cs map in src/locales/catalogues.rs. This includes validation messages and ApiResponse::Error strings from the backend.
  • trl() returns a closure — use it directly as {trl("...")} or attribute value; call it (trl("...")()) when a String is needed.
  • Dates shown to users go through loc_date(), weekday names through show_day().

Styling

  • UI is the Bootstrap-based admin template in assets/vendor/ plus Boxicons (<i class="bx bx-...">). Reuse its classes (card, table, btn btn-primary, form-control, row/col) before writing CSS.
  • Custom styles go to style/main.scss. Never edit assets/vendor/.
  • The public page banner style is assets/data/banner.css, rewritten at runtime by the appearance upload (/*bg-img*/ marker) — keep that marker / the background-image: url('...') form intact.
  • Stylesheets and meta tags are declared in src/components/header.rs.