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 +1 -1
- package/src/components/AdminSettingsSections.astro +14 -0
- package/src/components/Sidebar.astro +8 -0
- package/src/layouts/Layout.astro +12 -1
- package/src/pages/admin/settings.astro +105 -0
- package/src/pages/maintenance.astro +32 -0
- package/src/types/pages.ts +7 -0
- package/src/types.ts +5 -0
- package/theme.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fse-theme-default",
|
|
3
|
-
"version": "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">
|
package/src/layouts/Layout.astro
CHANGED
|
@@ -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>
|
package/src/types/pages.ts
CHANGED
|
@@ -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