From f6a3fb9c4d8dd86f78c9f75a23c1ac35bf35d4eb Mon Sep 17 00:00:00 2001
From: Benedikt Peetz
Date: Thu, 19 Mar 2026 07:45:14 +0100
Subject: feat(treewide): Commit MVP
---
.gitignore | 1 +
Cargo.lock | 86 +++++++-
Cargo.toml | 2 +-
Trunk.toml | 7 +-
flake.lock | 34 ++-
flake.nix | 24 ++-
index.html | 10 +
nix/package.nix | 69 ++++++
public/manifest.json | 13 ++
rocie-macros/src/form/generate.rs | 136 ++++++++++--
rocie-macros/src/form/mod.rs | 11 +-
rocie-macros/src/form/parse.rs | 18 ++
scripts/setup.sh | 6 +
setup/Cargo.lock | 7 +
setup/Cargo.toml | 6 +
setup/src/main.rs | 55 +++++
src/api/mod.rs | 221 +++++++++++++++++--
src/components/async_fetch.rs | 12 +-
src/components/buy.rs | 1 +
src/components/catch_errors.rs | 40 ++++
src/components/checkbox_placeholder.rs | 54 +++++
src/components/container.rs | 52 ++---
src/components/inventory.rs | 4 +-
src/components/login_wall.rs | 42 ++++
src/components/mod.rs | 7 +
src/components/product_overview.rs | 19 +-
src/components/product_parent_overview.rs | 37 ++++
src/components/recipies.rs | 20 +-
src/components/textarea_placeholder.rs | 60 ++++++
src/components/unit_overview.rs | 37 +++-
src/lib.rs | 60 +++++-
src/pages/associate_barcode.rs | 96 +++++----
src/pages/buy.rs | 63 ++++--
src/pages/create_product.rs | 109 +++++++---
src/pages/create_product_parent.rs | 126 +++++++++++
src/pages/create_recipe.rs | 108 ++++++++++
src/pages/home.rs | 46 ++--
src/pages/inventory.rs | 68 +++---
src/pages/login.rs | 81 +++++++
src/pages/mod.rs | 83 ++++++-
src/pages/not_found.rs | 7 +-
src/pages/product.rs | 52 +++++
src/pages/products.rs | 68 ++++++
src/pages/provision.rs | 93 ++++++++
src/pages/recipe.rs | 344 ++++++++++++++++++++++++++++++
src/pages/recipies.rs | 74 ++++++-
src/pages/units.rs | 78 +++++++
47 files changed, 2367 insertions(+), 280 deletions(-)
create mode 100644 nix/package.nix
create mode 100644 public/manifest.json
create mode 100755 scripts/setup.sh
create mode 100644 setup/Cargo.lock
create mode 100644 setup/Cargo.toml
create mode 100644 setup/src/main.rs
create mode 100644 src/components/catch_errors.rs
create mode 100644 src/components/checkbox_placeholder.rs
create mode 100644 src/components/login_wall.rs
create mode 100644 src/components/product_parent_overview.rs
create mode 100644 src/components/textarea_placeholder.rs
create mode 100644 src/pages/create_product_parent.rs
create mode 100644 src/pages/create_recipe.rs
create mode 100644 src/pages/login.rs
create mode 100644 src/pages/product.rs
create mode 100644 src/pages/products.rs
create mode 100644 src/pages/provision.rs
create mode 100644 src/pages/recipe.rs
create mode 100644 src/pages/units.rs
diff --git a/.gitignore b/.gitignore
index f608a06..8914bee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/target
+/setup/target
/dist
/public/tailwindcss-output.css
diff --git a/Cargo.lock b/Cargo.lock
index 645f23d..70883c1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -289,6 +289,35 @@ dependencies = [
"unicode-segmentation",
]
+[[package]]
+name = "cookie"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
+dependencies = [
+ "percent-encoding",
+ "time",
+ "version_check",
+]
+
+[[package]]
+name = "cookie_store"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f"
+dependencies = [
+ "cookie",
+ "document-features",
+ "idna",
+ "log",
+ "publicsuffix",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "time",
+ "url",
+]
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -411,6 +440,15 @@ dependencies = [
"syn",
]
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
[[package]]
name = "drain_filter_polyfill"
version = "0.1.3"
@@ -1215,6 +1253,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -1508,6 +1552,22 @@ dependencies = [
"yansi",
]
+[[package]]
+name = "psl-types"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
+
+[[package]]
+name = "publicsuffix"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
+dependencies = [
+ "idna",
+ "psl-types",
+]
+
[[package]]
name = "quote"
version = "1.0.41"
@@ -1658,12 +1718,14 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
-version = "0.12.24"
+version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
"base64",
"bytes",
+ "cookie",
+ "cookie_store",
"futures-core",
"futures-util",
"http",
@@ -1693,6 +1755,8 @@ dependencies = [
[[package]]
name = "rocie-client"
version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3bb4f4858511444da651f93844df66405447bc778545b096cc7214353e34929"
dependencies = [
"reqwest",
"serde",
@@ -1919,9 +1983,9 @@ dependencies = [
[[package]]
name = "serde_with"
-version = "3.15.1"
+version = "3.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04"
+checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
dependencies = [
"base64",
"chrono",
@@ -1938,9 +2002,9 @@ dependencies = [
[[package]]
name = "serde_with_macros"
-version = "3.15.1"
+version = "3.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955"
+checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
dependencies = [
"darling",
"proc-macro2",
@@ -2294,9 +2358,9 @@ dependencies = [
[[package]]
name = "tower-http"
-version = "0.6.6"
+version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
+checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
"bitflags",
"bytes",
@@ -2423,13 +2487,13 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
-version = "1.18.1"
+version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
+checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [
"getrandom",
"js-sys",
- "serde",
+ "serde_core",
"wasm-bindgen",
]
diff --git a/Cargo.toml b/Cargo.toml
index eb9e795..9749bee 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,7 @@ web-sys = { version = "0.3", features = ["Document", "Window"] }
[dependencies]
rocie-macros = { path = "./rocie-macros/" }
-rocie-client = { path = "../rocie-server/crates/rocie-client" }
+rocie-client = { version = "0.1" }
leptos = { version = "0.8", features = ["csr"] }
reactive_stores = { version = "0.2" }
leptos_meta = { version = "0.8" }
diff --git a/Trunk.toml b/Trunk.toml
index 68c3bf0..c16525c 100644
--- a/Trunk.toml
+++ b/Trunk.toml
@@ -1,10 +1,7 @@
[[hooks]]
stage = "pre_build"
-command = "sh"
-command_arguments = [
- "-c",
- "tailwindcss -i input.css -o public/tailwindcss-output.css",
-]
+command = "tailwindcss"
+command_arguments = ["-i", "input.css", "-o", "public/tailwindcss-output.css"]
[serve]
addresses = ["127.0.0.1"]
diff --git a/flake.lock b/flake.lock
index 6f9f237..a0a10e6 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,12 +1,27 @@
{
"nodes": {
+ "crane": {
+ "locked": {
+ "lastModified": 1773857772,
+ "narHash": "sha256-5xsK26KRHf0WytBtsBnQYC/lTWDhQuT57HJ7SzuqZcM=",
+ "owner": "ipetkov",
+ "repo": "crane",
+ "rev": "b556d7bbae5ff86e378451511873dfd07e4504cd",
+ "type": "github"
+ },
+ "original": {
+ "owner": "ipetkov",
+ "repo": "crane",
+ "type": "github"
+ }
+ },
"nixpkgs": {
"locked": {
- "lastModified": 1761164809,
- "narHash": "sha256-3uM91Lx9WZomE6MMEBorJyEyBNiHWRIxza/GganDxew=",
+ "lastModified": 1773826178,
+ "narHash": "sha256-RwA0KkNaCDBMDGYef/OjG3Z5B5oRTuV6Zy1iPk3F8Ro=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "3d2db9755e7815937fb7b8f089fad9b44bc416d8",
+ "rev": "af90506ab0acf18cfd6449225c32c096138cba52",
"type": "github"
},
"original": {
@@ -18,6 +33,7 @@
},
"root": {
"inputs": {
+ "crane": "crane",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"treefmt-nix": "treefmt-nix"
@@ -30,11 +46,11 @@
]
},
"locked": {
- "lastModified": 1761100675,
- "narHash": "sha256-LX3TCDBeNpCWTDXtGyRASVcLmRPChSli34bgHnZ1DCw=",
+ "lastModified": 1773889863,
+ "narHash": "sha256-tSsmZOHBgq4qfu5MNCAEsKZL1cI4avNLw2oUTXWeb74=",
"owner": "oxalica",
"repo": "rust-overlay",
- "rev": "72161c6c53f6e3f8dadaf54b2204a5094c6a16ae",
+ "rev": "dbfd51be2692cb7022e301d14c139accb4ee63f0",
"type": "github"
},
"original": {
@@ -50,11 +66,11 @@
]
},
"locked": {
- "lastModified": 1760945191,
- "narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
+ "lastModified": 1773297127,
+ "narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=",
"owner": "numtide",
"repo": "treefmt-nix",
- "rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
+ "rev": "71b125cd05fbfd78cab3e070b73544abe24c5016",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 776e8bd..425a1a9 100644
--- a/flake.nix
+++ b/flake.nix
@@ -26,6 +26,7 @@
nixpkgs.follows = "nixpkgs";
};
};
+ crane.url = "github:ipetkov/crane";
};
outputs = {
@@ -33,6 +34,7 @@
nixpkgs,
treefmt-nix,
rust-overlay,
+ crane,
}: let
system = "x86_64-linux";
pkgs = import nixpkgs {
@@ -48,11 +50,23 @@
pkgs.pkg-config
];
- rocie = pkgs.callPackage ./nix/package.nix {};
+ rust =
+ pkgs.rust-bin.stable.latest.default.override {
+ targets = ["wasm32-unknown-unknown"];
+ };
- rust = pkgs.rust-bin.stable.latest.default.override {
- targets = ["wasm32-unknown-unknown"];
- };
+ # NB: we don't need to overlay our custom toolchain for the *entire*
+ # pkgs (which would require rebuidling anything else which uses rust).
+ # Instead, we just want to update the scope that crane will use by appending
+ # our specific toolchain there.
+ craneLib = (crane.mkLib pkgs).overrideToolchain (
+ p:
+ p.rust-bin.stable.latest.default.override {
+ targets = ["wasm32-unknown-unknown"];
+ }
+ );
+
+ rocie = pkgs.callPackage ./nix/package.nix {inherit craneLib;};
treefmtEval = import ./treefmt.nix {inherit treefmt-nix pkgs;};
in {
@@ -74,7 +88,7 @@
packages = [
# rust stuff
rust
- pkgs.mold-wrapped
+ pkgs.mold
pkgs.cargo-edit
pkgs.cargo-expand
diff --git a/index.html b/index.html
index 6fe87e8..7085f94 100644
--- a/index.html
+++ b/index.html
@@ -27,6 +27,16 @@
rel="css"
href="./public/tailwindcss-output.css"
/>
+
+
+
diff --git a/nix/package.nix b/nix/package.nix
new file mode 100644
index 0000000..1bcc0ae
--- /dev/null
+++ b/nix/package.nix
@@ -0,0 +1,69 @@
+# rocie - An enterprise grocery management system
+#
+# Copyright (C) 2024 Benedikt Peetz
+# Copyright (C) 2025 Benedikt Peetz
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of Rocie.
+#
+# You should have received a copy of the License along with this program.
+# If not, see .
+{
+ lib,
+ craneLib,
+ # nativeBuildInputs
+ trunk,
+ tailwindcss,
+ wasm-bindgen-cli_0_2_104,
+ binaryen,
+}:
+craneLib.buildPackage {
+ pname = "rocie-mobile";
+ inherit
+ ((builtins.fromTOML (builtins.readFile
+ ../Cargo.toml)).package)
+ version
+ ;
+
+ src = lib.cleanSourceWith {
+ src = lib.cleanSource ./..;
+ filter = name: type:
+ (type == "directory")
+ || (builtins.elem (builtins.baseNameOf name) [
+ "Cargo.toml"
+ "Cargo.lock"
+ "tailwind.config.js"
+ "index.html"
+ "input.css"
+ "Trunk.toml"
+ "manifest.json"
+ "favicon.ico"
+ ])
+ || (lib.strings.hasSuffix ".rs" (builtins.baseNameOf name));
+ };
+ strictDeps = true;
+
+ cargoExtraArgs = "--target wasm32-unknown-unknown";
+
+ # Tests currently need to be run via `cargo wasi` which
+ # isn't packaged in nixpkgs yet...
+ doCheck = false;
+
+ nativeBuildInputs = [
+ trunk
+ tailwindcss
+ wasm-bindgen-cli_0_2_104
+ binaryen # for wasm-opt
+ ];
+
+ buildInputs = [
+ ];
+
+ postInstall = ''
+ trunk --offline --verbose build --release --locked --frozen --dist "./dist"
+
+ rm --recursive $out/bin
+
+ cp --recursive ./dist/. $out/
+ '';
+}
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 0000000..5ea8b9d
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,13 @@
+{
+ "name": "Rocie",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": "/",
+ "display": "standalone",
+ "prefer_related_applications": false
+}
diff --git a/rocie-macros/src/form/generate.rs b/rocie-macros/src/form/generate.rs
index 5642e6a..89acea8 100644
--- a/rocie-macros/src/form/generate.rs
+++ b/rocie-macros/src/form/generate.rs
@@ -1,7 +1,7 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
-use syn::{Ident, Type, parse_macro_input};
+use syn::{Ident, Path, Type, TypePath, parse_macro_input, punctuated::Punctuated};
use crate::form::{ParsedChild, ParsedInput};
@@ -33,7 +33,9 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
let output = quote!({
use crate::components::{
input_placeholder::InputPlaceholder,
- select_placeholder::SelectPlaceholder
+ select_placeholder::SelectPlaceholder,
+ textarea_placeholder::TextareaPlaceholder,
+ checkbox_placeholder::CheckboxPlaceholder,
};
use leptos::{
@@ -48,7 +50,7 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
Set,
Show,
},
- html::{Input, Select},
+ html::{Input, Select, Textarea},
web_sys::SubmitEvent
};
@@ -126,6 +128,8 @@ fn node_ref(child: &ParsedChild) -> TokenStream2 {
let (name, ty) = match child {
ParsedChild::Input { name, .. } => (name, quote! {Input}),
ParsedChild::Select { name, .. } => (name, quote! {Select}),
+ ParsedChild::Textarea { name, .. } => (name, quote! {Textarea}),
+ ParsedChild::Checkbox { name, .. } => (name, quote! {Input}),
ParsedChild::Show { .. } => unreachable!("Filtered out before"),
};
@@ -149,7 +153,9 @@ fn signal(child: &ParsedChild) -> TokenStream2 {
let (#signal_name_get, #signal_name_set) = signal(None);
}
}
- ParsedChild::Select { .. } => quote! {},
+ ParsedChild::Textarea { .. }
+ | ParsedChild::Checkbox { .. }
+ | ParsedChild::Select { .. } => quote! {},
ParsedChild::Show { children, .. } => children.iter().map(signal).collect(),
}
}
@@ -185,7 +191,9 @@ fn auto_complete_signal(child: &ParsedChild) -> TokenStream2 {
quote! {}
}
}
- ParsedChild::Select { .. } => quote! {},
+ ParsedChild::Select { .. }
+ | ParsedChild::Textarea { .. }
+ | ParsedChild::Checkbox { .. } => quote! {},
ParsedChild::Show { children, .. } => children.iter().map(auto_complete_signal).collect(),
}
}
@@ -238,14 +246,16 @@ fn reactive_signal(child: &ParsedChild) -> TokenStream2 {
quote! {}
}
}
- ParsedChild::Select { .. } => quote! {},
+ ParsedChild::Select { .. }
+ | ParsedChild::Textarea { .. }
+ | ParsedChild::Checkbox { .. } => quote! {},
ParsedChild::Show { children, .. } => children.iter().map(reactive_signal).collect(),
}
}
fn type_verification(child: &ParsedChild) -> TokenStream2 {
match child {
- ParsedChild::Input { .. } => {
+ ParsedChild::Input { .. } | ParsedChild::Textarea { .. } | ParsedChild::Checkbox { .. } => {
quote! {}
}
ParsedChild::Select { name, options, .. } => {
@@ -278,24 +288,65 @@ fn type_verification(child: &ParsedChild) -> TokenStream2 {
fn get_names(input: &ParsedChild) -> Vec<&Ident> {
match input {
- ParsedChild::Input { name, .. } => vec![name],
- ParsedChild::Select { name, .. } => vec![name],
+ ParsedChild::Input { name, .. }
+ | ParsedChild::Textarea { name, .. }
+ | ParsedChild::Checkbox { name, .. }
+ | ParsedChild::Select { name, .. } => vec![name],
ParsedChild::Show { children, .. } => children.iter().flat_map(get_names).collect(),
}
}
fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 {
- fn get_rust_types(input: &ParsedChild) -> Vec<&Type> {
+ macro_rules! mk_type {
+ ($name:ident) => {{
+ let segments = {
+ let mut p = Punctuated::new();
+ p.push(syn::PathSegment {
+ ident: format_ident!(stringify!($name)),
+ arguments: syn::PathArguments::None,
+ });
+ p
+ };
+
+ Type::Path(TypePath {
+ qself: None,
+ path: Path {
+ leading_colon: None,
+ segments,
+ },
+ })
+ }};
+ }
+
+ let string: Type = mk_type!(String);
+ let boolean: Type = mk_type!(bool);
+
+ fn get_rust_types<'a>(
+ input: &'a ParsedChild,
+ string: &'a Type,
+ bool: &'a Type,
+ ) -> Vec<&'a Type> {
match input {
- ParsedChild::Input { rust_type, .. } => vec![rust_type],
- ParsedChild::Select { rust_type, .. } => vec![rust_type],
- ParsedChild::Show { children, .. } => {
- children.iter().flat_map(get_rust_types).collect()
+ ParsedChild::Textarea { .. } => {
+ vec![string]
}
+ ParsedChild::Checkbox { .. } => {
+ vec![bool]
+ }
+ ParsedChild::Input { rust_type, .. } | ParsedChild::Select { rust_type, .. } => {
+ vec![rust_type]
+ }
+ ParsedChild::Show { children, .. } => children
+ .iter()
+ .flat_map(|i| get_rust_types(i, string, bool))
+ .collect(),
}
}
+
let names = children.iter().flat_map(get_names);
- let rust_types = children.iter().flat_map(get_rust_types);
+ let rust_types = children
+ .iter()
+ .flat_map(|i| get_rust_types(i, &string, &boolean));
quote! {
struct Inputs {
@@ -358,6 +409,42 @@ fn fetch_value(child: &ParsedChild) -> TokenStream2 {
};
}
}
+ ParsedChild::Textarea { name, .. } => {
+ let node_ref_name = node_ref_name(name);
+
+ quote! {
+ let #name: String = {
+ let output = #node_ref_name
+ .get()
+ // event handlers can only fire after the view
+ // is mounted to the DOM, so the `NodeRef` will be `Some`
+ .expect("
}
}>
- {move || Suspend::new(async move {
- $resource
- .await
- .map($producer)
- })}
+ {
+ Suspend::new(async move {
+ $resource
+ .await
+ .map($producer)
+ })
+ }
}
}};
diff --git a/src/components/buy.rs b/src/components/buy.rs
index e69de29..8b13789 100644
--- a/src/components/buy.rs
+++ b/src/components/buy.rs
@@ -0,0 +1 @@
+
diff --git a/src/components/catch_errors.rs b/src/components/catch_errors.rs
new file mode 100644
index 0000000..d5a452d
--- /dev/null
+++ b/src/components/catch_errors.rs
@@ -0,0 +1,40 @@
+use leptos::{
+ IntoView, component,
+ error::ErrorBoundary,
+ prelude::{Children, ClassAttribute, CollectView, ElementChild, Get},
+ view,
+};
+
+use crate::components::site_header::SiteHeader;
+
+#[component]
+pub(crate) fn CatchErrors(children: Children) -> impl IntoView {
+ view! {
+
+
+ "Uh oh! Something went wrong!"
+
+ "Errors: "
+
+ {move || {
+ errors
+ .get()
+ .into_iter()
+ .map(|(_, e)| {
+ view! {
+ - {e.to_string()}
+ }
+ })
+ .collect_view()
+ }}
+
+ }
+ }>{children()}
+ }
+}
diff --git a/src/components/checkbox_placeholder.rs b/src/components/checkbox_placeholder.rs
new file mode 100644
index 0000000..a1aaa0c
--- /dev/null
+++ b/src/components/checkbox_placeholder.rs
@@ -0,0 +1,54 @@
+use leptos::{
+ IntoView, component,
+ html::Input,
+ prelude::{ClassAttribute, ElementChild, GlobalAttributes, NodeRef, NodeRefAttribute},
+ view,
+};
+
+use crate::components::get_id;
+
+#[component]
+pub fn CheckboxPlaceholder(
+ label: &'static str,
+ node_ref: NodeRef,
+) -> impl IntoView {
+ let id = get_id();
+
+ view! {
+
+
+
+
+
+ }
+}
diff --git a/src/components/container.rs b/src/components/container.rs
index d6d2f03..3b56713 100644
--- a/src/components/container.rs
+++ b/src/components/container.rs
@@ -1,46 +1,38 @@
use leptos::{
IntoView, component,
- prelude::{Children, ClassAttribute, ElementChild, OnAttribute},
+ prelude::{Children, ClassAttribute, ElementChild},
view,
};
-use leptos_router::{NavigateOptions, hooks::use_navigate};
+use leptos_router::components::A;
#[component]
pub fn Container(
- header: impl IntoView,
- buttons: Vec<(impl IntoView, &'static str)>,
+ header: impl IntoView + 'static,
+ buttons: Vec<(impl IntoView + 'static, &'static str)>,
children: Children,
) -> impl IntoView {
assert!(!buttons.is_empty());
- let first_button_path = buttons.first().expect("Should have at least on button").1;
+ // TODO: Add the direct link to the first button back. <2026-02-15>
+ // let first_button_path = buttons.first().expect("Should have at least on button").1;
view! {
-