--- paths: - "src/pages/**" - "src/components/**" - "src/locales/**" - "src/app.rs" - "src/validator.rs" - "style/**" - "assets/css/**" --- # 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 `` in `src/app.rs` wrapped in ``, 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 (`
`, ``, another `

`) inside `

`, and `

` only inside ``/``. 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 `
`) 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 `` 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 `` 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::::new()` in the dialog, wrapped in ``. It supplies the modal, Save/Close buttons, client validation and server error display. Confirmations use ``. - 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 `message`s 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 (``). 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 `` in `src/components/header.rs` (order matters: after the vendor files to override them). `style/main.scss` is not linked anywhere and has no effect. Never edit `assets/vendor/` (the one app-owned file there is `assets/vendor/css/control.css`). - 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`.