fse-theme-default 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/package.json +3 -3
- package/src/components/Head.astro +5 -1
- package/src/components/Logo.astro +8 -3
- package/src/components/NavIcon.astro +48 -0
- package/src/components/SettingsSections.astro +32 -0
- package/src/components/Sidebar.astro +21 -10
- package/src/components/SidebarLinks.astro +10 -3
- package/src/layouts/Layout.astro +1 -1
- package/src/pages/forgot-password.astro +1 -1
- package/src/pages/fse/form.astro +33 -1
- package/src/pages/index.astro +8 -3
- package/src/pages/login.astro +1 -1
- package/src/pages/register.astro +1 -1
- package/src/pages/settings.astro +4 -16
- package/src/pages/user.astro +1 -1
- package/src/types.ts +36 -0
- package/theme.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,8 @@ A child theme overrides any file under `src/` by creating the same path in
|
|
|
33
33
|
its own `src/` — the parent's pages then use the child's version:
|
|
34
34
|
|
|
35
35
|
- `src/layouts/Layout.astro` — the whole shell,
|
|
36
|
-
- `src/components/SidebarLinks.astro` — app-specific sidebar entries (
|
|
36
|
+
- `src/components/SidebarLinks.astro` — app-specific sidebar entries (renders
|
|
37
|
+
the `sidebar-links` slot; overriding it replaces every contribution),
|
|
37
38
|
- `src/components/Logo.astro` — the logo in the sidebar, mobile header and
|
|
38
39
|
auth pages (or replace just `src/assets/logo.svg`),
|
|
39
40
|
- `src/components/Head.astro` — extra `<head>` tags on every page (favicon,
|
|
@@ -41,7 +42,23 @@ its own `src/` — the parent's pages then use the child's version:
|
|
|
41
42
|
- `src/styles/global.css` — the Tailwind root (import `src/styles/theme.css`
|
|
42
43
|
and override tokens after it).
|
|
43
44
|
|
|
44
|
-
`@parent/...` imports the parent's original file (e.g. to wrap it)
|
|
45
|
+
`@parent/...` imports the parent's original file (e.g. to wrap it);
|
|
46
|
+
`@theme/...` the most specific theme's version of a file.
|
|
47
|
+
|
|
48
|
+
### Slots
|
|
49
|
+
|
|
50
|
+
Where several child themes and modules add to one place, the theme has a
|
|
51
|
+
slot: every `src/slots/<slot>/<Name>.astro` of the child theme and of the
|
|
52
|
+
app's modules is rendered there (this theme's first, modules next, the
|
|
53
|
+
child last; a same-named file of a more specific layer replaces the other):
|
|
54
|
+
|
|
55
|
+
- `sidebar-links` — sidebar entries between "Home" and the model links,
|
|
56
|
+
- `settings-sections` — sections of the settings page (data through
|
|
57
|
+
`page_context("settings", ...)`),
|
|
58
|
+
- `head` — extra `<head>` tags,
|
|
59
|
+
- `dashboard` — widgets below the home page's model cards,
|
|
60
|
+
- `record-panels` — panels below a generated record form (check
|
|
61
|
+
`meta.table`).
|
|
45
62
|
|
|
46
63
|
## Development
|
|
47
64
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fse-theme-default",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Default theme for full_stack_engine apps
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Default theme for full_stack_engine apps \u2014 a complete Astro project: app shell, design system, auth pages, emails and the generic metadata-driven CRUD pages. Extend it with a child theme.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT OR Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"astro": ">=7.0.0",
|
|
28
|
-
"fse-ssr": ">=0.
|
|
28
|
+
"fse-ssr": ">=0.3.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@astrojs/check": "^0.9.9",
|
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Extension point: extra <head> tags for every page (favicon, fonts, meta
|
|
4
4
|
* tags). A child theme overrides this file; its version replaces this one,
|
|
5
|
-
* so it should link its own favicon too.
|
|
5
|
+
* so it should link its own favicon too. Tags that only add (a module's
|
|
6
|
+
* stylesheet, a meta tag) go into the `head` slot instead:
|
|
7
|
+
* `src/slots/head/*.astro`.
|
|
6
8
|
*/
|
|
9
|
+
import Slot from "fse-ssr/Slot.astro";
|
|
7
10
|
---
|
|
8
11
|
|
|
9
12
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
13
|
+
<Slot name="head" />
|
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Extension point: the logo shown in the sidebar, the mobile header and the
|
|
4
4
|
* auth pages. A child theme either replaces src/assets/logo.svg or
|
|
5
|
-
* overrides this file (e.g. separate light and dark images
|
|
5
|
+
* overrides this file (e.g. separate light and dark images, other sizes per
|
|
6
|
+
* `variant`).
|
|
6
7
|
*/
|
|
7
8
|
import { Image } from "astro:assets";
|
|
8
9
|
import logo from "../assets/logo.svg";
|
|
9
10
|
|
|
10
11
|
interface Props {
|
|
12
|
+
/** Where it is drawn; picks the default size. */
|
|
13
|
+
variant?: "sidebar" | "mobile" | "auth";
|
|
14
|
+
/** Replaces the variant's size classes entirely. */
|
|
11
15
|
class?: string;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
const {
|
|
18
|
+
const sizes = { sidebar: "h-9 w-auto", mobile: "h-8 w-auto", auth: "h-12 w-auto" };
|
|
19
|
+
const { variant = "sidebar", class: className } = Astro.props;
|
|
15
20
|
---
|
|
16
21
|
|
|
17
|
-
<Image src={logo} alt="Logo" class={className} />
|
|
22
|
+
<Image src={logo} alt="Logo" class={className ?? sizes[variant]} />
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The named 18px icons sidebar entries use. A model picks one with
|
|
4
|
+
* `#[model(nav(icon = "heart"))]` (or `public_nav(...)`); the framework sends
|
|
5
|
+
* `table` when it names none. An unknown name draws nothing — add it here
|
|
6
|
+
* (or in a child theme's override of this file).
|
|
7
|
+
*
|
|
8
|
+
* home, grid, calendar, heart, run, user, users, settings, table, list,
|
|
9
|
+
* box, cart, file, chart, tag, mail, map-pin, star
|
|
10
|
+
*/
|
|
11
|
+
import type { SsrValue } from "fse-ssr/ssr";
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
name: string | SsrValue<string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { name } = Astro.props;
|
|
18
|
+
const a = {
|
|
19
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
20
|
+
width: "18",
|
|
21
|
+
height: "18",
|
|
22
|
+
viewBox: "0 0 24 24",
|
|
23
|
+
fill: "none",
|
|
24
|
+
stroke: "currentColor",
|
|
25
|
+
"stroke-width": "1.5",
|
|
26
|
+
"stroke-linecap": "round",
|
|
27
|
+
"stroke-linejoin": "round",
|
|
28
|
+
} as const;
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
{name === "home" && (<svg {...a}><path d="M3 10.5 12 3l9 7.5"></path><path d="M5 9.5V21h14V9.5"></path><path d="M10 21v-6h4v6"></path></svg>)}
|
|
32
|
+
{name === "grid" && (<svg {...a}><rect width="7.5" height="7.5" x="2.75" y="2.75" rx="1.9"></rect><rect width="7.5" height="7.5" x="13.75" y="2.75" rx="1.9"></rect><rect width="7.5" height="7.5" x="13.75" y="13.75" rx="1.9"></rect><rect width="7.5" height="7.5" x="2.75" y="13.75" rx="1.9"></rect></svg>)}
|
|
33
|
+
{name === "calendar" && (<svg {...a}><rect width="18" height="18" x="3" y="4" rx="2" ry="2"></rect><path d="M16 2v4M8 2v4m-5 4h18"></path></svg>)}
|
|
34
|
+
{name === "heart" && (<svg {...a}><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>)}
|
|
35
|
+
{name === "run" && (<svg {...a}><path d="M13 4a1 1 0 1 0 2 0 1 1 0 0 0-2 0"></path><path d="m7 21 3-6 2 2 3-4 3 8"></path><path d="m5 14 2-5 5 1 2-4"></path></svg>)}
|
|
36
|
+
{name === "user" && (<svg {...a}><circle cx="12" cy="7" r="4"></circle><path d="M5 21v-2a7 7 0 0 1 14 0v2"></path></svg>)}
|
|
37
|
+
{name === "users" && (<svg {...a}><circle cx="9" cy="7" r="4"></circle><path d="M3 21v-2a6 6 0 0 1 12 0v2"></path><path d="M16 3.13a4 4 0 0 1 0 7.75M21 21v-2a6 6 0 0 0-4-5.66"></path></svg>)}
|
|
38
|
+
{name === "settings" && (<svg {...a}><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c.26.6.85 1 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>)}
|
|
39
|
+
{name === "table" && (<svg {...a}><rect width="18" height="18" x="3" y="3" rx="2"></rect><path d="M3 9h18M9 21V9"></path></svg>)}
|
|
40
|
+
{name === "list" && (<svg {...a}><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"></path></svg>)}
|
|
41
|
+
{name === "box" && (<svg {...a}><path d="M21 8 12 3 3 8v8l9 5 9-5z"></path><path d="m3 8 9 5 9-5M12 13v8"></path></svg>)}
|
|
42
|
+
{name === "cart" && (<svg {...a}><circle cx="9" cy="20" r="1"></circle><circle cx="18" cy="20" r="1"></circle><path d="M2 3h3l2.7 12.4a2 2 0 0 0 2 1.6h7.6a2 2 0 0 0 2-1.6L21 7H6"></path></svg>)}
|
|
43
|
+
{name === "file" && (<svg {...a}><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><path d="M14 3v6h6M8 13h8M8 17h5"></path></svg>)}
|
|
44
|
+
{name === "chart" && (<svg {...a}><path d="M3 3v18h18"></path><path d="M7 15v2M12 10v7M17 6v11"></path></svg>)}
|
|
45
|
+
{name === "tag" && (<svg {...a}><path d="M20.6 13.4 13.4 20.6a2 2 0 0 1-2.8 0L3 13V3h10l7.6 7.6a2 2 0 0 1 0 2.8z"></path><circle cx="7.5" cy="7.5" r="1.5"></circle></svg>)}
|
|
46
|
+
{name === "mail" && (<svg {...a}><rect width="18" height="14" x="3" y="5" rx="2"></rect><path d="m3 7 9 6 9-6"></path></svg>)}
|
|
47
|
+
{name === "map-pin" && (<svg {...a}><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>)}
|
|
48
|
+
{name === "star" && (<svg {...a}><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1z"></path></svg>)}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Extension point: the app's own sections of the settings page, rendered
|
|
4
|
+
* between "account security" and "delete account". The default shows the
|
|
5
|
+
* account's name, followed by the `settings-sections` slot: every
|
|
6
|
+
* `src/slots/settings-sections/*.astro` of a child theme or a module (a
|
|
7
|
+
* profile, preferences, ...). Their data comes through
|
|
8
|
+
* `page_context("settings", ...)` (app or module), and POST handlers
|
|
9
|
+
* re-render the page with `auth_module::settings_context`. Overriding this
|
|
10
|
+
* file replaces the default and every contribution.
|
|
11
|
+
*/
|
|
12
|
+
import Slot from "fse-ssr/Slot.astro";
|
|
13
|
+
import { ssr } from "fse-ssr/ssr";
|
|
14
|
+
import type { SettingsPage } from "../types/pages";
|
|
15
|
+
|
|
16
|
+
const { t, first_name, last_name } = ssr<SettingsPage>();
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
{(first_name || last_name) && (
|
|
20
|
+
<div class="py-6">
|
|
21
|
+
<h3 class="text-base font-semibold text-secondary-900">
|
|
22
|
+
{t.settings.name}
|
|
23
|
+
</h3>
|
|
24
|
+
<div class="mt-3 px-4 py-3 rounded-lg bg-secondary-50 border border-secondary-100">
|
|
25
|
+
<span class="text-sm font-medium text-secondary-900">
|
|
26
|
+
{first_name} {last_name}
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
)}
|
|
31
|
+
|
|
32
|
+
<Slot name="settings-sections" />
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
3
|
* The app shell's navigation. Everything is data-driven: the framework
|
|
4
|
-
* injects `
|
|
5
|
-
*
|
|
4
|
+
* injects `nav_groups` (the models' sidebar entries — label, icon and order
|
|
5
|
+
* come from `#[model(nav(...), public_nav(...))]` and the app's
|
|
6
|
+
* translations — grouped by module, each module's under its title) and
|
|
7
|
+
* `user`; links that aren't a model come from the SidebarLinks extension
|
|
8
|
+
* point.
|
|
6
9
|
*/
|
|
7
10
|
import { ssr } from "fse-ssr/ssr";
|
|
8
11
|
import Logo from "./Logo.astro";
|
|
12
|
+
import NavIcon from "./NavIcon.astro";
|
|
9
13
|
import NavLink from "./NavLink.astro";
|
|
10
14
|
import SidebarLinks from "./SidebarLinks.astro";
|
|
11
15
|
import type { ShellContext } from "../types";
|
|
12
16
|
|
|
13
|
-
const { t,
|
|
17
|
+
const { t, nav_groups, user, lang_prefix } = ssr<ShellContext>();
|
|
14
18
|
---
|
|
15
19
|
|
|
16
20
|
<div
|
|
@@ -33,7 +37,7 @@ const { t, nav, user, lang_prefix } = ssr<ShellContext>();
|
|
|
33
37
|
class="sidebar-full-logo flex items-center gap-2 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary-500 rounded-md"
|
|
34
38
|
href={`${lang_prefix}/`}
|
|
35
39
|
>
|
|
36
|
-
<Logo
|
|
40
|
+
<Logo variant="sidebar" />
|
|
37
41
|
</a>
|
|
38
42
|
|
|
39
43
|
<button
|
|
@@ -69,12 +73,19 @@ const { t, nav, user, lang_prefix } = ssr<ShellContext>();
|
|
|
69
73
|
|
|
70
74
|
<SidebarLinks />
|
|
71
75
|
|
|
72
|
-
{
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
{nav_groups.map((group) => (
|
|
77
|
+
<div class="space-y-0.5">
|
|
78
|
+
{group.label && (
|
|
79
|
+
<p class="sidebar-label px-3 pt-4 pb-1 text-xs font-semibold uppercase tracking-wide text-secondary-400 truncate">
|
|
80
|
+
{group.label}
|
|
81
|
+
</p>
|
|
82
|
+
)}
|
|
83
|
+
{group.entries.map((item) => (
|
|
84
|
+
<NavLink href={`${lang_prefix}${item.href}`} label={item.label}>
|
|
85
|
+
<NavIcon name={item.icon} />
|
|
86
|
+
</NavLink>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
78
89
|
))}
|
|
79
90
|
</nav>
|
|
80
91
|
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
3
|
* Extension point: app-specific sidebar entries, rendered between "Home"
|
|
4
|
-
* and the generated model links.
|
|
5
|
-
*
|
|
4
|
+
* and the generated model links. Filled through the `sidebar-links` slot:
|
|
5
|
+
* every `src/slots/sidebar-links/*.astro` of a child theme or a module is
|
|
6
|
+
* rendered here, so several can add links side by side:
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
+
* // src/slots/sidebar-links/Products.astro
|
|
9
|
+
* import NavLink from "@theme/components/NavLink.astro";
|
|
8
10
|
* <NavLink href="/products" label={t.nav.products}>…icon…</NavLink>
|
|
11
|
+
*
|
|
12
|
+
* Overriding this file instead replaces every contribution.
|
|
9
13
|
*/
|
|
14
|
+
import Slot from "fse-ssr/Slot.astro";
|
|
10
15
|
---
|
|
16
|
+
|
|
17
|
+
<Slot name="sidebar-links" />
|
package/src/layouts/Layout.astro
CHANGED
|
@@ -73,7 +73,7 @@ const { lang } = ssr();
|
|
|
73
73
|
<line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line>
|
|
74
74
|
</svg>
|
|
75
75
|
</button>
|
|
76
|
-
<a href="/"><Logo
|
|
76
|
+
<a href="/"><Logo variant="mobile" /></a>
|
|
77
77
|
</div>
|
|
78
78
|
)}
|
|
79
79
|
<slot />
|
package/src/pages/fse/form.astro
CHANGED
|
@@ -7,13 +7,19 @@
|
|
|
7
7
|
* row) the same form renders disabled and without a save button. An
|
|
8
8
|
* existing row also shows its allowed row actions and links to its nested
|
|
9
9
|
* models and link tables; a nested model links back to its parent row.
|
|
10
|
+
* Below the form: `related` — the rows of other models (and modules)
|
|
11
|
+
* that point at this one through a foreign key, one panel each — then the
|
|
12
|
+
* `record-panels` slot: extra panels from a child theme or modules
|
|
13
|
+
* (`src/slots/record-panels/*.astro`, which check `meta.table` to pick the
|
|
14
|
+
* models they extend).
|
|
10
15
|
*/
|
|
16
|
+
import Slot from "fse-ssr/Slot.astro";
|
|
11
17
|
import { ssr } from "fse-ssr/ssr";
|
|
12
18
|
import type { SsrValue } from "fse-ssr/ssr";
|
|
13
19
|
import Layout from "../../layouts/Layout.astro";
|
|
14
20
|
import type { ModelFormPage } from "../../types";
|
|
15
21
|
|
|
16
|
-
const { t, lang_prefix, meta, row, errors, is_new, can_edit, can_delete, actions, parent } =
|
|
22
|
+
const { t, lang_prefix, meta, row, errors, is_new, can_edit, can_delete, actions, parent, related } =
|
|
17
23
|
ssr<ModelFormPage>();
|
|
18
24
|
const title = t.models[meta.table].title ?? meta.table;
|
|
19
25
|
const formErrors = t.form_errors as Record<string, SsrValue<string>>;
|
|
@@ -155,6 +161,32 @@ const inputClass =
|
|
|
155
161
|
))}
|
|
156
162
|
</div>
|
|
157
163
|
)}
|
|
164
|
+
|
|
165
|
+
{related.map((panel) => (
|
|
166
|
+
<section class="mt-8">
|
|
167
|
+
<h2 class="mb-2 text-base font-semibold text-secondary-900">
|
|
168
|
+
{t.models[panel.table].title ?? panel.table}
|
|
169
|
+
{panel.label_column && (
|
|
170
|
+
<span class="font-normal text-secondary-500"> · {t.models[panel.table].fields[panel.label_column] ?? panel.label_column}</span>
|
|
171
|
+
)}
|
|
172
|
+
<span class="ml-1 text-sm font-normal text-secondary-500">({panel.total})</span>
|
|
173
|
+
</h2>
|
|
174
|
+
<ul class="divide-y divide-secondary-100 rounded-lg border border-secondary-100 bg-white">
|
|
175
|
+
{panel.rows.map((item) => (
|
|
176
|
+
<li>
|
|
177
|
+
<a class="block px-4 py-2 text-sm text-secondary-800 hover:bg-secondary-50" href={`${lang_prefix}${panel.base_path}/${item.id}`}>
|
|
178
|
+
{item.title}
|
|
179
|
+
</a>
|
|
180
|
+
</li>
|
|
181
|
+
))}
|
|
182
|
+
</ul>
|
|
183
|
+
{panel.more && (
|
|
184
|
+
<p class="mt-1 text-xs text-secondary-500">{t.model_ui.more_related ?? "Showing the first 10."}</p>
|
|
185
|
+
)}
|
|
186
|
+
</section>
|
|
187
|
+
))}
|
|
188
|
+
|
|
189
|
+
<Slot name="record-panels" />
|
|
158
190
|
</main>
|
|
159
191
|
|
|
160
192
|
<script>
|
package/src/pages/index.astro
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
|
-
* Default home page: a dashboard linking every model the user may manage
|
|
4
|
-
*
|
|
3
|
+
* Default home page: a dashboard linking every model the user may manage,
|
|
4
|
+
* then the `dashboard` slot (widgets from a child theme or modules:
|
|
5
|
+
* `src/slots/dashboard/*.astro`). Apps usually replace it (a child theme's
|
|
6
|
+
* own src/pages/index.astro).
|
|
5
7
|
*/
|
|
8
|
+
import Slot from "fse-ssr/Slot.astro";
|
|
6
9
|
import { ssr } from "fse-ssr/ssr";
|
|
7
10
|
import Card from "../components/Card.astro";
|
|
8
11
|
import Header from "../components/Header.astro";
|
|
@@ -30,12 +33,14 @@ const { t, nav, user, lang_prefix } = ssr<ShellContext>();
|
|
|
30
33
|
{nav.map((item) => (
|
|
31
34
|
<a href={`${lang_prefix}${item.href}`}>
|
|
32
35
|
<Card accent="primary" hover>
|
|
33
|
-
<h2 class="text-lg font-bold text-secondary-900">{
|
|
36
|
+
<h2 class="text-lg font-bold text-secondary-900">{item.label}</h2>
|
|
34
37
|
<p class="text-sm text-secondary-500">{t.dashboard.manage ?? "Manage entries"}</p>
|
|
35
38
|
</Card>
|
|
36
39
|
</a>
|
|
37
40
|
))}
|
|
38
41
|
</div>
|
|
39
42
|
)}
|
|
43
|
+
|
|
44
|
+
<Slot name="dashboard" />
|
|
40
45
|
</main>
|
|
41
46
|
</Layout>
|
package/src/pages/login.astro
CHANGED
package/src/pages/register.astro
CHANGED
package/src/pages/settings.astro
CHANGED
|
@@ -5,13 +5,12 @@ import Card from "../components/Card.astro";
|
|
|
5
5
|
import Header from "../components/Header.astro";
|
|
6
6
|
import Input from "../components/Input.astro";
|
|
7
7
|
import Modal from "../components/Modal.astro";
|
|
8
|
+
import SettingsSections from "../components/SettingsSections.astro";
|
|
8
9
|
import type { SettingsPage } from "../types/pages";
|
|
9
10
|
|
|
10
11
|
const {
|
|
11
12
|
t,
|
|
12
13
|
current_email,
|
|
13
|
-
first_name,
|
|
14
|
-
last_name,
|
|
15
14
|
success,
|
|
16
15
|
error,
|
|
17
16
|
email_success,
|
|
@@ -29,20 +28,6 @@ const errors = t.errors as Record<string, string>;
|
|
|
29
28
|
<Card>
|
|
30
29
|
<div class="flex flex-col divide-y divide-secondary-100">
|
|
31
30
|
|
|
32
|
-
<!-- Name -->
|
|
33
|
-
{(first_name || last_name) && (
|
|
34
|
-
<div class="pb-6">
|
|
35
|
-
<h3 class="text-base font-semibold text-secondary-900">
|
|
36
|
-
{t.settings.name}
|
|
37
|
-
</h3>
|
|
38
|
-
<div class="mt-3 px-4 py-3 rounded-lg bg-secondary-50 border border-secondary-100">
|
|
39
|
-
<span class="text-sm font-medium text-secondary-900">
|
|
40
|
-
{first_name} {last_name}
|
|
41
|
-
</span>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
)}
|
|
45
|
-
|
|
46
31
|
<!-- Email Setting -->
|
|
47
32
|
<div class="py-6 first:pt-0">
|
|
48
33
|
<div class="flex items-start justify-between mb-3 flex-wrap gap-2">
|
|
@@ -89,6 +74,9 @@ const errors = t.errors as Record<string, string>;
|
|
|
89
74
|
)}
|
|
90
75
|
</div>
|
|
91
76
|
|
|
77
|
+
<!-- The app's sections (default: the account's name) -->
|
|
78
|
+
<SettingsSections />
|
|
79
|
+
|
|
92
80
|
<!-- Danger Zone -->
|
|
93
81
|
<div class="pt-6">
|
|
94
82
|
<div class="flex items-start justify-between flex-wrap gap-3">
|
package/src/pages/user.astro
CHANGED
|
@@ -56,7 +56,7 @@ const roleLabels = t.roles as Record<string, string>;
|
|
|
56
56
|
<!-- Actions -->
|
|
57
57
|
<div class="flex sm:justify-end pt-5 border-t border-secondary-100">
|
|
58
58
|
<button type="submit" class="btn-primary w-full sm:w-auto">
|
|
59
|
-
<span>{t.
|
|
59
|
+
<span>{t.users.save}</span>
|
|
60
60
|
</button>
|
|
61
61
|
</div>
|
|
62
62
|
</form>
|
package/src/types.ts
CHANGED
|
@@ -116,6 +116,22 @@ export interface ModelFormPage {
|
|
|
116
116
|
actions: string[];
|
|
117
117
|
/** The parent row, for a nested model (else null). */
|
|
118
118
|
parent: ParentContext | null;
|
|
119
|
+
/** Rows of other models (and modules) pointing at this one. */
|
|
120
|
+
related: RelatedPanel[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* One model's rows that point at a record through a foreign key — the
|
|
125
|
+
* first ten, with `more` when there are others.
|
|
126
|
+
*/
|
|
127
|
+
export interface RelatedPanel {
|
|
128
|
+
table: string;
|
|
129
|
+
base_path: string;
|
|
130
|
+
/** Set when the model points here through several columns. */
|
|
131
|
+
label_column: string | null;
|
|
132
|
+
total: number;
|
|
133
|
+
more: boolean;
|
|
134
|
+
rows: { id: number; title: string }[];
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
/** `fse/links` — the rows of another model linked to one row. */
|
|
@@ -136,6 +152,25 @@ export interface ModelDetailPage {
|
|
|
136
152
|
export interface NavItem {
|
|
137
153
|
table: string;
|
|
138
154
|
href: string;
|
|
155
|
+
/** Already translated (`t.models.<table>.nav` / `.public_nav` / `.title`). */
|
|
156
|
+
label: string;
|
|
157
|
+
/** A `NavIcon` name. */
|
|
158
|
+
icon: string;
|
|
159
|
+
/** The public list's entry rather than the admin page. */
|
|
160
|
+
public: boolean;
|
|
161
|
+
/** The cargo package defining the model (the app's or a module's). */
|
|
162
|
+
krate: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* `nav` grouped by origin: the app's own models first (`module` and
|
|
167
|
+
* `label` null), then one group per enabled module, labelled
|
|
168
|
+
* `t.modules.<package>.title`.
|
|
169
|
+
*/
|
|
170
|
+
export interface NavGroup {
|
|
171
|
+
module: string | null;
|
|
172
|
+
label: string | null;
|
|
173
|
+
entries: NavItem[];
|
|
139
174
|
}
|
|
140
175
|
|
|
141
176
|
/** The signed-in user as injected by the framework (absent when signed out). */
|
|
@@ -149,6 +184,7 @@ export interface SessionUser {
|
|
|
149
184
|
/** Context keys every page receives, whatever renders it. */
|
|
150
185
|
export interface ShellContext {
|
|
151
186
|
nav: NavItem[];
|
|
187
|
+
nav_groups: NavGroup[];
|
|
152
188
|
user?: SessionUser;
|
|
153
189
|
lang_prefix: string;
|
|
154
190
|
}
|
package/theme.json
CHANGED