fse-theme-default 0.5.0 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fse-theme-default",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
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",
@@ -0,0 +1,14 @@
1
+ ---
2
+ /**
3
+ * Extension point: the app's and modules' own sections of the admin
4
+ * settings page, rendered below maintenance mode. Filled through the
5
+ * `admin-settings-sections` slot: every
6
+ * `src/slots/admin-settings-sections/*.astro` of a child theme or a module.
7
+ * Their data comes through `page_context("admin/settings", ...)`, their
8
+ * forms post to the app's or module's own routes. Overriding this file
9
+ * replaces every contribution.
10
+ */
11
+ import Slot from "fse-ssr/Slot.astro";
12
+ ---
13
+
14
+ <Slot name="admin-settings-sections" />
@@ -116,6 +116,14 @@ const { t, nav_groups, user, lang_prefix } = ssr<ShellContext>();
116
116
  </NavLink>
117
117
  )}
118
118
 
119
+ {user && user.can_admin_settings && (
120
+ <NavLink href={`${lang_prefix}/admin/settings`} label={t.nav.admin_settings ?? "Admin settings"}>
121
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
122
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
123
+ </svg>
124
+ </NavLink>
125
+ )}
126
+
119
127
  {user && (
120
128
  <NavLink href={`${lang_prefix}/settings`} label={t.nav.settings ?? "Settings"}>
121
129
  <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
@@ -12,6 +12,7 @@ import type { SsrValue } from "fse-ssr/ssr";
12
12
  import Head from "../components/Head.astro";
13
13
  import Logo from "../components/Logo.astro";
14
14
  import Sidebar from "../components/Sidebar.astro";
15
+ import type { ShellContext } from "../types";
15
16
 
16
17
  interface Props {
17
18
  title: string | SsrValue<string>;
@@ -19,7 +20,7 @@ interface Props {
19
20
  }
20
21
 
21
22
  const { title, sidebar = true } = Astro.props;
22
- const { lang } = ssr();
23
+ const { t, lang, lang_prefix, user, maintenance_mode } = ssr<ShellContext>();
23
24
  ---
24
25
 
25
26
  <!doctype html>
@@ -54,6 +55,16 @@ const { lang } = ssr();
54
55
  </script>
55
56
  </head>
56
57
  <body class="bg-secondary-50 text-secondary-900">
58
+ {maintenance_mode && (
59
+ <div role="status" class="relative z-50 flex flex-wrap items-center justify-center gap-x-3 gap-y-1 px-4 py-2 text-sm font-medium text-amber-900 bg-amber-50 border-b border-amber-200 dark:border-amber-900">
60
+ <span>{t.admin_settings.banner}</span>
61
+ {user && user.can_admin_settings && (
62
+ <a href={`${lang_prefix}/admin/settings`} class="underline underline-offset-4 hover:text-amber-700">
63
+ {t.nav.admin_settings}
64
+ </a>
65
+ )}
66
+ </div>
67
+ )}
57
68
  <div class="relative flex min-h-screen">
58
69
  {sidebar && <Sidebar />}
59
70
  <div
@@ -0,0 +1,105 @@
1
+ ---
2
+ /**
3
+ * `/admin/settings` (framework `settings` module): operator switches,
4
+ * maintenance mode first, then the AdminSettingsSections extension point.
5
+ */
6
+ import { ssr } from "fse-ssr/ssr";
7
+ import Layout from "../../layouts/Layout.astro";
8
+ import Card from "../../components/Card.astro";
9
+ import Header from "../../components/Header.astro";
10
+ import Modal from "../../components/Modal.astro";
11
+ import AdminSettingsSections from "../../components/AdminSettingsSections.astro";
12
+ import type { AdminSettingsPage } from "../../types/pages";
13
+
14
+ const { t, lang_prefix, maintenance_enabled, can_write } =
15
+ ssr<AdminSettingsPage & { lang_prefix: string }>();
16
+ ---
17
+
18
+ <Layout title={t.nav.admin_settings}>
19
+ <main class="max-w-2xl mx-auto">
20
+ <Header>{t.nav.admin_settings}</Header>
21
+
22
+ <Card>
23
+ <div class="flex flex-col divide-y divide-secondary-100">
24
+
25
+ <!-- Maintenance mode -->
26
+ <div class="py-6 first:pt-0 last:pb-0">
27
+ <div class="flex items-start justify-between flex-wrap gap-3">
28
+ <div class="flex-1 min-w-60">
29
+ <div class="flex items-center gap-2">
30
+ <h3 class="text-base font-semibold text-secondary-900">
31
+ {t.admin_settings.maintenance_title}
32
+ </h3>
33
+ {maintenance_enabled && (
34
+ <span class="px-2 py-0.5 text-xs font-medium rounded-full bg-amber-50 text-amber-800 border border-amber-200 dark:border-amber-900">
35
+ {t.admin_settings.status_on}
36
+ </span>
37
+ )}
38
+ {!maintenance_enabled && (
39
+ <span class="px-2 py-0.5 text-xs font-medium rounded-full bg-secondary-100 text-secondary-600 border border-secondary-200">
40
+ {t.admin_settings.status_off}
41
+ </span>
42
+ )}
43
+ </div>
44
+ <p class="text-sm text-secondary-500 mt-1">
45
+ {t.admin_settings.maintenance_desc}
46
+ </p>
47
+ </div>
48
+ {can_write && maintenance_enabled && (
49
+ <button type="button" class="btn-primary shrink-0" data-open-modal="maintenance-modal">
50
+ {t.admin_settings.disable}
51
+ </button>
52
+ )}
53
+ {can_write && !maintenance_enabled && (
54
+ <button type="button" class="btn-danger shrink-0" data-open-modal="maintenance-modal">
55
+ {t.admin_settings.enable}
56
+ </button>
57
+ )}
58
+ </div>
59
+ </div>
60
+
61
+ <AdminSettingsSections />
62
+
63
+ </div>
64
+ </Card>
65
+ </main>
66
+
67
+ {maintenance_enabled && (
68
+ <Modal id="maintenance-modal" title={t.admin_settings.disable}>
69
+ <p class="text-sm text-secondary-600 mb-5">{t.admin_settings.disable_confirm}</p>
70
+ <form action={`${lang_prefix}/admin/settings/maintenance`} method="POST" data-astro-reload class="flex flex-col gap-2">
71
+ <input type="hidden" name="enabled" value="0" />
72
+ <button type="submit" class="w-full btn-primary">{t.admin_settings.disable}</button>
73
+ <button type="button" class="w-full btn-secondary modal-close-btn" data-modal-id="maintenance-modal">
74
+ {t.settings.cancel}
75
+ </button>
76
+ </form>
77
+ </Modal>
78
+ )}
79
+ {!maintenance_enabled && (
80
+ <Modal id="maintenance-modal" title={t.admin_settings.enable}>
81
+ <p class="text-sm text-secondary-600 mb-5">{t.admin_settings.enable_confirm}</p>
82
+ <form action={`${lang_prefix}/admin/settings/maintenance`} method="POST" data-astro-reload class="flex flex-col gap-2">
83
+ <input type="hidden" name="enabled" value="1" />
84
+ <button type="submit" class="w-full btn-danger">{t.admin_settings.enable}</button>
85
+ <button type="button" class="w-full btn-secondary modal-close-btn" data-modal-id="maintenance-modal">
86
+ {t.settings.cancel}
87
+ </button>
88
+ </form>
89
+ </Modal>
90
+ )}
91
+
92
+ <script>
93
+ document.addEventListener("astro:page-load", () => {
94
+ document.querySelectorAll<HTMLElement>("[data-open-modal]").forEach((button) => {
95
+ if (button.dataset.bound) return;
96
+ button.dataset.bound = "1";
97
+ button.addEventListener("click", () => {
98
+ const id = button.dataset.openModal;
99
+ const modal = id ? (document.getElementById(id) as HTMLDialogElement | null) : null;
100
+ if (modal && !modal.open) modal.showModal();
101
+ });
102
+ });
103
+ });
104
+ </script>
105
+ </Layout>
@@ -0,0 +1,32 @@
1
+ ---
2
+ /**
3
+ * What visitors get while maintenance mode is on (the framework answers
4
+ * 503 with this page; see framework/src/maintenance.rs). Signing in still
5
+ * works, so an administrator can get past it.
6
+ */
7
+ import { ssr } from "fse-ssr/ssr";
8
+ import Logo from "../components/Logo.astro";
9
+ import Layout from "../layouts/Layout.astro";
10
+
11
+ const { t, lang_prefix } = ssr<{ lang_prefix: string }>();
12
+ ---
13
+
14
+ <Layout title={t.maintenance.heading} sidebar={false}>
15
+ <main class="min-h-screen w-full grid place-items-center bg-secondary-50 px-4 py-12">
16
+ <div class="w-full max-w-md text-center flex flex-col items-center gap-6">
17
+ <Logo variant="auth" />
18
+ <div class="w-12 h-12 rounded-full bg-primary-50 border border-primary-200 flex items-center justify-center">
19
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-primary-600">
20
+ <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path>
21
+ </svg>
22
+ </div>
23
+ <div>
24
+ <h1 class="text-lg font-semibold text-secondary-900 mb-2">{t.maintenance.heading}</h1>
25
+ <p class="text-sm text-secondary-500">{t.maintenance.text}</p>
26
+ </div>
27
+ <a href={`${lang_prefix}/login`} class="text-sm text-secondary-500 hover:text-secondary-800 underline underline-offset-4">
28
+ {t.maintenance.admin_login}
29
+ </a>
30
+ </div>
31
+ </main>
32
+ </Layout>
@@ -53,6 +53,13 @@ export interface SettingsPage {
53
53
  email_error?: string;
54
54
  }
55
55
 
56
+ /** `/admin/settings` (framework `settings` module). */
57
+ export interface AdminSettingsPage {
58
+ maintenance_enabled: boolean;
59
+ /** Holds `admin_settings.write`: may change settings. */
60
+ can_write: boolean;
61
+ }
62
+
56
63
  export interface ErrorPage {
57
64
  error: string;
58
65
  }
package/src/types.ts CHANGED
@@ -179,6 +179,8 @@ export interface SessionUser {
179
179
  role: string;
180
180
  is_admin: boolean;
181
181
  can_read_users: boolean;
182
+ /** Holds `admin_settings.read`: may open `/admin/settings`. */
183
+ can_admin_settings: boolean;
182
184
  }
183
185
 
184
186
  /** Context keys every page receives, whatever renders it. */
@@ -187,4 +189,7 @@ export interface ShellContext {
187
189
  nav_groups: NavGroup[];
188
190
  user?: SessionUser;
189
191
  lang_prefix: string;
192
+ /** Present (true) only while maintenance mode is on — on pages an
193
+ * admin sees, and on the login page. */
194
+ maintenance_mode?: boolean;
190
195
  }
package/theme.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "fse-theme-default",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Default full_stack_engine theme: app shell, design system, auth pages, emails and the generic model CRUD pages."
5
5
  }