5.5 KiB
5.5 KiB
paths
| paths | |||||||
|---|---|---|---|---|---|---|---|
|
Frontend rules
Components
- Leptos 0.8 API only (
use leptos::prelude::*). Components are#[component]functions returningimpl IntoView, one page or dialog per file insrc/pages/; reusable building blocks go tosrc/components/. Register new files in the respectivemod.rs(pub modonly if used fromapp.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/..."/>insrc/app.rswrapped in<AdminPortal>, and a menu entry insrc/components/admin_portal.rs. Paths starting with/adminare login-protected by the middleware; data access is still guarded byperm_check!in the server fn.
Leptos 0.8 pitfalls
- Views are statically typed:
if/elseandmatcharms that return different markup must each end with.into_any(). - Context is not available in event handlers (
on:clicketc.). Calluse_contextin the component body and keep the result —DialogOpenerandMenuOpenerdo this innew(), 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,widthon<table>) don't compile; usestyle. - 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>withon:submit:captureandev.prevent_default(); a plainon:submitruns too late.DataFormalready does this. action.value()is not anRwSignal; pass it on asSignal::derive(move || action.value().get()).
Data loading and forms
- Load data with
Resource::new_blocking/Resource::newcalling 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'sopener.visible()as the resource source so lists reload after the dialog closes (seesrc/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 withprop:value={move || signal.get().field}. DataForm/ModalDialogtaketitleas 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
csmap insrc/locales/catalogues.rs. This includes validationmessages andApiResponse::Errorstrings from the backend. trl()returns a closure — use it directly as{trl("...")}or attribute value; call it (trl("...")()) when aStringis needed.- Dates shown to users go through
loc_date(), weekday names throughshow_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 a CSS file in
assets/css/and must be linked with<Link rel="stylesheet">insrc/components/header.rs(order matters: after the vendor files to override them).style/main.scssis not linked anywhere and has no effect. Never editassets/vendor/(the one app-owned file there isassets/vendor/css/control.css). - The admin portal has a light and a dark theme (
src/components/theme.rs, switch in the navbar: auto / light / dark, stored in thethemecookie). Dark colours are overrides inassets/css/dark.css, whichAdminPortallinks with amediaattribute — the public page and the login page never load it. When you add or restyle something in the admin portal, check it in both themes and add the dark override there; don't hard-code light colours (bg-light,bg-white, inlinecolor) in admin views. - Charts are drawn on the server, so the chart server fns take a
darkflag fromTheme::is_dark(). - Styles of the public page are in
assets/css/public.css, scoped by its own classes (.public-pagewraps the public route only,.public-banner*), so they don't leak into the admin portal. The customer's appearance settings reach it as CSS variables in astyleattribute (Appearance::banner_style()insrc/backend/data.rs) — never generate CSS files. Every value put into astyleattribute must be validated first. - Stylesheets and meta tags are declared in
src/components/header.rs.