fse-theme-default 0.3.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 +55 -0
- package/astro.config.mjs +22 -0
- package/package.json +38 -0
- package/public/favicon.svg +4 -0
- package/src/assets/logo.svg +5 -0
- package/src/components/Card.astro +31 -0
- package/src/components/Head.astro +9 -0
- package/src/components/Header.astro +55 -0
- package/src/components/Input.astro +56 -0
- package/src/components/Logo.astro +17 -0
- package/src/components/Modal.astro +78 -0
- package/src/components/NavLink.astro +23 -0
- package/src/components/Pagination.astro +81 -0
- package/src/components/SearchBar.astro +65 -0
- package/src/components/Select.astro +60 -0
- package/src/components/Sidebar.astro +207 -0
- package/src/components/SidebarLinks.astro +10 -0
- package/src/components/Table.astro +157 -0
- package/src/components/TableFilters.astro +137 -0
- package/src/env.d.ts +2 -0
- package/src/layouts/EmailLayout.astro +45 -0
- package/src/layouts/Layout.astro +95 -0
- package/src/pages/emails/password-reset.astro +39 -0
- package/src/pages/emails/verify-email-change.astro +39 -0
- package/src/pages/emails/verify.astro +39 -0
- package/src/pages/error.astro +26 -0
- package/src/pages/forgot-password.astro +67 -0
- package/src/pages/fse/form.astro +169 -0
- package/src/pages/fse/links.astro +104 -0
- package/src/pages/fse/list.astro +166 -0
- package/src/pages/fse/public-detail.astro +29 -0
- package/src/pages/fse/public-list.astro +54 -0
- package/src/pages/index.astro +41 -0
- package/src/pages/login.astro +88 -0
- package/src/pages/public/error.astro +29 -0
- package/src/pages/register-success.astro +31 -0
- package/src/pages/register.astro +99 -0
- package/src/pages/reset-password.astro +117 -0
- package/src/pages/settings.astro +188 -0
- package/src/pages/user.astro +65 -0
- package/src/pages/users.astro +52 -0
- package/src/styles/global.css +11 -0
- package/src/styles/theme.css +408 -0
- package/src/types/pages.ts +58 -0
- package/src/types.ts +154 -0
- package/theme.json +5 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Generic public detail page for models with `public_read`: the row's title
|
|
4
|
+
* plus a definition list of its visible columns.
|
|
5
|
+
*/
|
|
6
|
+
import { ssr } from "fse-ssr/ssr";
|
|
7
|
+
import Layout from "../../layouts/Layout.astro";
|
|
8
|
+
import type { ModelDetailPage } from "../../types";
|
|
9
|
+
|
|
10
|
+
const { t, lang_prefix, meta, row } = ssr<ModelDetailPage>();
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<Layout title={row[meta.title_field] ?? meta.table}>
|
|
14
|
+
<main class="max-w-2xl">
|
|
15
|
+
<a class="btn-ghost mb-4" href={`${lang_prefix}/${meta.table}`}>{t.model_ui.back ?? "Back"}</a>
|
|
16
|
+
<h1 class="section-title mb-6">{row[meta.title_field]}</h1>
|
|
17
|
+
|
|
18
|
+
<dl class="bg-white border border-secondary-200 rounded-xl p-6 grid grid-cols-[auto_1fr] gap-x-6 gap-y-3 text-sm">
|
|
19
|
+
{meta.list_columns.map((col) => (
|
|
20
|
+
<>
|
|
21
|
+
<dt class="font-medium text-secondary-500">
|
|
22
|
+
{t.models[meta.table].fields[col.name] ?? col.name}
|
|
23
|
+
</dt>
|
|
24
|
+
<dd>{row[col.name]}</dd>
|
|
25
|
+
</>
|
|
26
|
+
))}
|
|
27
|
+
</dl>
|
|
28
|
+
</main>
|
|
29
|
+
</Layout>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Generic public catalog page for models with `public_read` — a card grid
|
|
4
|
+
* linking each row to its public detail page by the public-read column.
|
|
5
|
+
*/
|
|
6
|
+
import { ssr } from "fse-ssr/ssr";
|
|
7
|
+
import Layout from "../../layouts/Layout.astro";
|
|
8
|
+
import type { ModelListPage } from "../../types";
|
|
9
|
+
|
|
10
|
+
const { t, lang_prefix, meta, rows, page, total_pages, has_prev, has_next, prev_page, next_page, search } =
|
|
11
|
+
ssr<ModelListPage>();
|
|
12
|
+
const title = t.models[meta.table].title ?? meta.table;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<Layout title={title}>
|
|
16
|
+
<main>
|
|
17
|
+
<h1 class="section-title mb-6">{title}</h1>
|
|
18
|
+
|
|
19
|
+
<form method="get" class="mb-6">
|
|
20
|
+
<input
|
|
21
|
+
type="search"
|
|
22
|
+
name="search"
|
|
23
|
+
value={search ?? ""}
|
|
24
|
+
placeholder={t.model_ui.search ?? "Search..."}
|
|
25
|
+
class="w-full max-w-md px-3 py-2 text-sm border border-secondary-200 rounded-lg bg-white focus:outline-hidden focus:ring-2 focus:ring-primary-500"
|
|
26
|
+
/>
|
|
27
|
+
</form>
|
|
28
|
+
|
|
29
|
+
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
30
|
+
{rows.map((row) => (
|
|
31
|
+
<a
|
|
32
|
+
href={`${lang_prefix}/${meta.table}/${row[meta.public_read]}`}
|
|
33
|
+
class="block bg-white border border-secondary-200 rounded-xl p-5 hover:border-primary-400 transition-colors"
|
|
34
|
+
>
|
|
35
|
+
<h2 class="font-semibold text-secondary-900">{row[meta.title_field]}</h2>
|
|
36
|
+
</a>
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="mt-8 flex items-center justify-center gap-3 text-sm text-secondary-500">
|
|
41
|
+
{has_prev && (
|
|
42
|
+
<a class="btn-ghost" href={`?page=${prev_page}&search=${search ?? ""}`}>
|
|
43
|
+
{t.model_ui.prev ?? "Previous"}
|
|
44
|
+
</a>
|
|
45
|
+
)}
|
|
46
|
+
<span>{page} / {total_pages}</span>
|
|
47
|
+
{has_next && (
|
|
48
|
+
<a class="btn-ghost" href={`?page=${next_page}&search=${search ?? ""}`}>
|
|
49
|
+
{t.model_ui.next ?? "Next"}
|
|
50
|
+
</a>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
</main>
|
|
54
|
+
</Layout>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Default home page: a dashboard linking every model the user may manage.
|
|
4
|
+
* Apps usually replace it (a child theme's own src/pages/index.astro).
|
|
5
|
+
*/
|
|
6
|
+
import { ssr } from "fse-ssr/ssr";
|
|
7
|
+
import Card from "../components/Card.astro";
|
|
8
|
+
import Header from "../components/Header.astro";
|
|
9
|
+
import Layout from "../layouts/Layout.astro";
|
|
10
|
+
import type { ShellContext } from "../types";
|
|
11
|
+
|
|
12
|
+
const { t, nav, user, lang_prefix } = ssr<ShellContext>();
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<Layout title={t.nav.home ?? "Home"}>
|
|
16
|
+
<main class="w-full max-w-5xl">
|
|
17
|
+
<Header>{t.nav.home ?? "Home"}</Header>
|
|
18
|
+
|
|
19
|
+
{!user && (
|
|
20
|
+
<Card>
|
|
21
|
+
<p class="text-sm text-secondary-600">{t.dashboard.signed_out ?? "Sign in to manage your data."}</p>
|
|
22
|
+
<div>
|
|
23
|
+
<a class="btn-primary" href={`${lang_prefix}/login`}>{t.nav.login ?? "Login"}</a>
|
|
24
|
+
</div>
|
|
25
|
+
</Card>
|
|
26
|
+
)}
|
|
27
|
+
|
|
28
|
+
{user && (
|
|
29
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
30
|
+
{nav.map((item) => (
|
|
31
|
+
<a href={`${lang_prefix}${item.href}`}>
|
|
32
|
+
<Card accent="primary" hover>
|
|
33
|
+
<h2 class="text-lg font-bold text-secondary-900">{t.models[item.table].title ?? item.table}</h2>
|
|
34
|
+
<p class="text-sm text-secondary-500">{t.dashboard.manage ?? "Manage entries"}</p>
|
|
35
|
+
</Card>
|
|
36
|
+
</a>
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
</main>
|
|
41
|
+
</Layout>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ssr } from "fse-ssr/ssr";
|
|
3
|
+
import Input from "../components/Input.astro";
|
|
4
|
+
import Logo from "../components/Logo.astro";
|
|
5
|
+
import Layout from "../layouts/Layout.astro";
|
|
6
|
+
|
|
7
|
+
const { t, error, success } = ssr<{ error?: string; success?: string }>();
|
|
8
|
+
const errorLogin = t.login as Record<string, string>;
|
|
9
|
+
const errors = t.errors as Record<string, string>;
|
|
10
|
+
const messages = t.messages as Record<string, string>;
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<Layout title={t.login.title} sidebar={false}>
|
|
14
|
+
<main class="min-h-screen w-full grid place-items-center bg-secondary-50 px-4 py-12">
|
|
15
|
+
<div class="w-full max-w-sm">
|
|
16
|
+
<!-- Logo -->
|
|
17
|
+
<div class="flex justify-center mb-8">
|
|
18
|
+
<a href="/">
|
|
19
|
+
<Logo class="h-12 w-auto" />
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<!-- Card -->
|
|
24
|
+
<div class="bg-white border border-secondary-200 rounded-xl overflow-hidden">
|
|
25
|
+
<div class="px-6 pt-6 pb-2">
|
|
26
|
+
<h1 class="text-lg font-semibold text-secondary-900 mb-1">
|
|
27
|
+
{t.login.title}
|
|
28
|
+
</h1>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<form action="/login" method="POST" data-astro-reload>
|
|
32
|
+
<div class="px-6 py-4 flex flex-col gap-4">
|
|
33
|
+
<Input
|
|
34
|
+
id="email"
|
|
35
|
+
type="email"
|
|
36
|
+
placeholder={t.login.email}
|
|
37
|
+
required={true}
|
|
38
|
+
/>
|
|
39
|
+
<Input
|
|
40
|
+
id="password"
|
|
41
|
+
type="password"
|
|
42
|
+
placeholder={t.login.password}
|
|
43
|
+
required={true}
|
|
44
|
+
/>
|
|
45
|
+
<div class="flex justify-end -mt-1">
|
|
46
|
+
<a href="/forgot-password" class="text-xs text-secondary-500 hover:text-primary-600 hover:underline transition-colors">
|
|
47
|
+
{t.login.forgot_password}
|
|
48
|
+
</a>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="px-6 pb-5">
|
|
53
|
+
<button
|
|
54
|
+
class="btn-primary w-full h-10 disabled:cursor-not-allowed disabled:opacity-50"
|
|
55
|
+
>
|
|
56
|
+
<span>{t.login.submit}</span>
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
{error && (
|
|
61
|
+
<div class="px-6 pb-4">
|
|
62
|
+
<div class="w-full px-4 py-3 text-sm text-red-600 border border-red-200 rounded-lg bg-red-50" role="alert">
|
|
63
|
+
<p>{errors[error] ?? errorLogin[error] ?? error}</p>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{success && (
|
|
69
|
+
<div class="px-6 pb-4">
|
|
70
|
+
<div class="w-full px-4 py-3 text-sm text-primary-700 border border-primary-200 rounded-lg bg-primary-50" role="alert">
|
|
71
|
+
<p>{messages[success] ?? success}</p>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</form>
|
|
76
|
+
|
|
77
|
+
<div class="px-6 py-4 border-t border-secondary-100 text-center">
|
|
78
|
+
<p class="text-sm text-secondary-500">
|
|
79
|
+
{t.login.no_account}
|
|
80
|
+
<a href="/register" class="text-primary-600 hover:text-primary-700 font-medium hover:underline ml-1">
|
|
81
|
+
{t.login.register}
|
|
82
|
+
</a>
|
|
83
|
+
</p>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</main>
|
|
88
|
+
</Layout>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** Error page for visitors who aren't signed in (the framework renders `public/error`). */
|
|
3
|
+
import { ssr } from "fse-ssr/ssr";
|
|
4
|
+
import Layout from "../../layouts/Layout.astro";
|
|
5
|
+
import type { ErrorPage } from "../../types/pages";
|
|
6
|
+
|
|
7
|
+
const { t, error, status, lang_prefix } = ssr<ErrorPage & { status: number; lang_prefix: string }>();
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<Layout title={t.common.error ?? "Error"} sidebar={false}>
|
|
11
|
+
<main class="min-h-screen grid place-items-center bg-secondary-50 px-4">
|
|
12
|
+
<div class="w-full max-w-md text-center flex flex-col items-center gap-5">
|
|
13
|
+
<div class="w-12 h-12 rounded-full bg-red-50 border border-red-200 flex items-center justify-center">
|
|
14
|
+
<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-red-500">
|
|
15
|
+
<circle cx="12" cy="12" r="10"></circle><path d="M12 8v4m0 4h.01"></path>
|
|
16
|
+
</svg>
|
|
17
|
+
</div>
|
|
18
|
+
<div>
|
|
19
|
+
<p class="text-xs font-semibold text-secondary-400 mb-1">{status}</p>
|
|
20
|
+
<h1 class="text-base font-semibold text-secondary-900 mb-1">{t.common.error ?? "Error"}</h1>
|
|
21
|
+
<p class="text-sm text-secondary-500">{error}</p>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="flex gap-2">
|
|
24
|
+
<a href={`${lang_prefix}/`} class="btn-secondary">{t.common.back_home ?? "Back to home"}</a>
|
|
25
|
+
<a href={`${lang_prefix}/login`} class="btn-primary">{t.nav.login ?? "Login"}</a>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</main>
|
|
29
|
+
</Layout>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ssr } from "fse-ssr/ssr";
|
|
3
|
+
import Layout from "../layouts/Layout.astro";
|
|
4
|
+
|
|
5
|
+
const { t } = ssr();
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<Layout title={t.register_success.title} sidebar={false}>
|
|
9
|
+
<main class="grid w-screen h-screen place-items-center">
|
|
10
|
+
<div class="overflow-hidden w-[400px] bg-white rounded-xl border border-secondary-200 text-secondary-500">
|
|
11
|
+
<div class="p-6">
|
|
12
|
+
<header class="mb-4 text-center">
|
|
13
|
+
<h3 class="text-xl font-medium text-black">{t.register_success.header}</h3>
|
|
14
|
+
</header>
|
|
15
|
+
<div class="flex flex-col gap-4 mt-8 text-center text-sm">
|
|
16
|
+
<p>
|
|
17
|
+
{t.register_success.success}
|
|
18
|
+
</p>
|
|
19
|
+
<p>
|
|
20
|
+
{t.register_success.desc}
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="flex justify-end p-6 border-t border-secondary-100">
|
|
25
|
+
<a href="/login" class="inline-flex items-center justify-center w-full h-10 gap-2 whitespace-nowrap btn-primary">
|
|
26
|
+
<span>{t.register_success.return}</span>
|
|
27
|
+
</a>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</main>
|
|
31
|
+
</Layout>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ssr } from "fse-ssr/ssr";
|
|
3
|
+
import Input from "../components/Input.astro";
|
|
4
|
+
import Logo from "../components/Logo.astro";
|
|
5
|
+
import Layout from "../layouts/Layout.astro";
|
|
6
|
+
|
|
7
|
+
const { t, error } = ssr<{ error?: string }>();
|
|
8
|
+
const errors = t.errors as Record<string, string>;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<Layout title={t.register.title} sidebar={false}>
|
|
12
|
+
<main class="min-h-screen w-full grid place-items-center bg-secondary-50 px-4 py-12">
|
|
13
|
+
<div class="w-full max-w-sm">
|
|
14
|
+
<!-- Logo -->
|
|
15
|
+
<div class="flex justify-center mb-8">
|
|
16
|
+
<a href="/">
|
|
17
|
+
<Logo class="h-12 w-auto" />
|
|
18
|
+
</a>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<!-- Card -->
|
|
22
|
+
<div class="bg-white border border-secondary-200 rounded-xl overflow-hidden">
|
|
23
|
+
<div class="px-6 pt-6 pb-2">
|
|
24
|
+
<h1 class="text-lg font-semibold text-secondary-900 mb-1">
|
|
25
|
+
{t.register.title}
|
|
26
|
+
</h1>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<form id="login-form" action="/register" method="POST" data-astro-reload enctype="multipart/form-data">
|
|
30
|
+
<div class="px-6 py-4 flex flex-col gap-4">
|
|
31
|
+
<div class="grid grid-cols-2 gap-4">
|
|
32
|
+
<Input id="first_name" type="text" placeholder={t.register.first_name} required={true} />
|
|
33
|
+
<Input id="last_name" type="text" placeholder={t.register.last_name} required={true} />
|
|
34
|
+
</div>
|
|
35
|
+
<Input id="email" type="email" placeholder={t.register.email} required={true} />
|
|
36
|
+
<Input id="password" type="password" placeholder={t.register.password} required={true} />
|
|
37
|
+
<Input id="repeat_password" type="password" placeholder={t.register.repeat_password} required={true} />
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="px-6 pb-5 pt-1">
|
|
41
|
+
<button
|
|
42
|
+
id="submit-btn"
|
|
43
|
+
type="submit"
|
|
44
|
+
class="btn-primary w-full h-10 disabled:cursor-not-allowed disabled:opacity-50"
|
|
45
|
+
>
|
|
46
|
+
<span>{t.register.submit}</span>
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
{error && (
|
|
51
|
+
<div class="px-6 pb-4">
|
|
52
|
+
<div class="w-full px-4 py-3 text-sm text-red-600 border border-red-200 rounded-lg bg-red-50" role="alert">
|
|
53
|
+
<p>{errors[error] ?? error}</p>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
</form>
|
|
58
|
+
|
|
59
|
+
<div class="px-6 py-4 border-t border-secondary-100 text-center">
|
|
60
|
+
<p class="text-sm text-secondary-500">
|
|
61
|
+
{t.register.has_account}
|
|
62
|
+
<a href="/login" class="text-primary-600 hover:text-primary-700 font-medium hover:underline ml-1">
|
|
63
|
+
{t.register.login}
|
|
64
|
+
</a>
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</main>
|
|
70
|
+
|
|
71
|
+
<script>
|
|
72
|
+
import { pageProps } from "fse-ssr/client";
|
|
73
|
+
|
|
74
|
+
function setupRegisterForm() {
|
|
75
|
+
const form = document.getElementById("login-form") as HTMLFormElement;
|
|
76
|
+
const password = document.getElementById("password") as HTMLInputElement;
|
|
77
|
+
const repeatPassword = document.getElementById("repeat_password") as HTMLInputElement;
|
|
78
|
+
const submitBtn = document.getElementById("submit-btn") as HTMLButtonElement;
|
|
79
|
+
|
|
80
|
+
if (form && password && repeatPassword && submitBtn) {
|
|
81
|
+
form.addEventListener("submit", (e) => {
|
|
82
|
+
if (password.value !== repeatPassword.value) {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
const { t } = pageProps<{ t: { register: { passwords_mismatch?: string } } }>();
|
|
85
|
+
alert(t.register.passwords_mismatch ?? "Passwords do not match");
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
function checkPasswords() {
|
|
90
|
+
submitBtn.disabled = password.value !== repeatPassword.value;
|
|
91
|
+
}
|
|
92
|
+
password.addEventListener("input", checkPasswords);
|
|
93
|
+
repeatPassword.addEventListener("input", checkPasswords);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
document.addEventListener("astro:page-load", setupRegisterForm);
|
|
98
|
+
</script>
|
|
99
|
+
</Layout>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ssr } from "fse-ssr/ssr";
|
|
3
|
+
import Input from "../components/Input.astro";
|
|
4
|
+
import Layout from "../layouts/Layout.astro";
|
|
5
|
+
|
|
6
|
+
const { t, token, error, success } = ssr<{
|
|
7
|
+
token: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
success?: string;
|
|
10
|
+
}>();
|
|
11
|
+
const errors = t.errors as Record<string, string>;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<Layout title={t.reset_password.title} sidebar={false}>
|
|
15
|
+
<main class="grid w-screen h-screen place-items-center">
|
|
16
|
+
<form
|
|
17
|
+
id="reset-form"
|
|
18
|
+
class="overflow-hidden w-[400px] bg-white rounded-xl border border-secondary-200 text-secondary-500"
|
|
19
|
+
action="/reset-password"
|
|
20
|
+
method="POST" data-astro-reload
|
|
21
|
+
>
|
|
22
|
+
<input type="hidden" name="token" value={token} />
|
|
23
|
+
<div class="p-6">
|
|
24
|
+
<header class="mb-4 text-center">
|
|
25
|
+
<h3 class="text-xl font-medium text-black">
|
|
26
|
+
{t.reset_password.title}
|
|
27
|
+
</h3>
|
|
28
|
+
</header>
|
|
29
|
+
<div class="flex flex-col gap-4 mt-12">
|
|
30
|
+
<Input
|
|
31
|
+
id="password"
|
|
32
|
+
type="password"
|
|
33
|
+
placeholder={t.reset_password.new_password}
|
|
34
|
+
required={true}
|
|
35
|
+
/>
|
|
36
|
+
<Input
|
|
37
|
+
id="repeat_password"
|
|
38
|
+
type="password"
|
|
39
|
+
placeholder={t.reset_password.repeat_new_password}
|
|
40
|
+
required={true}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="flex justify-end p-6">
|
|
45
|
+
<button
|
|
46
|
+
id="submit-btn"
|
|
47
|
+
type="submit"
|
|
48
|
+
class="inline-flex items-center justify-center w-full h-10 gap-2 whitespace-nowrap btn-primary disabled:cursor-not-allowed disabled:border-primary-300 disabled:bg-primary-300 disabled:shadow-none"
|
|
49
|
+
>
|
|
50
|
+
<span>{t.reset_password.submit}</span>
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
{error && (
|
|
54
|
+
<div class="px-6 py-3">
|
|
55
|
+
<div
|
|
56
|
+
class="w-full px-4 py-3 mb-4 text-sm text-pink-500 border border-pink-100 rounded-sm bg-pink-50"
|
|
57
|
+
role="alert"
|
|
58
|
+
>
|
|
59
|
+
<p>{errors[error] ?? error}</p>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{success && (
|
|
65
|
+
<div class="px-6 py-3">
|
|
66
|
+
<div
|
|
67
|
+
class="w-full px-4 py-3 mb-4 text-sm text-green-600 border border-green-100 rounded-sm bg-green-50"
|
|
68
|
+
role="alert"
|
|
69
|
+
>
|
|
70
|
+
<p>{success}</p>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
</form>
|
|
75
|
+
</main>
|
|
76
|
+
|
|
77
|
+
<script>
|
|
78
|
+
import { pageProps } from "fse-ssr/client";
|
|
79
|
+
|
|
80
|
+
function setupResetForm() {
|
|
81
|
+
const form = document.getElementById("reset-form");
|
|
82
|
+
const password = document.getElementById("password") as HTMLInputElement;
|
|
83
|
+
const repeatPassword = document.getElementById(
|
|
84
|
+
"repeat_password",
|
|
85
|
+
) as HTMLInputElement;
|
|
86
|
+
const submitBtn = document.getElementById(
|
|
87
|
+
"submit-btn",
|
|
88
|
+
) as HTMLButtonElement;
|
|
89
|
+
|
|
90
|
+
if (form && password && repeatPassword && submitBtn) {
|
|
91
|
+
form.addEventListener("submit", (e) => {
|
|
92
|
+
if (password.value !== repeatPassword.value) {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
const { t } = pageProps<{ t: { register: { passwords_mismatch?: string } } }>();
|
|
95
|
+
alert(t.register.passwords_mismatch ?? "Passwords do not match");
|
|
96
|
+
submitBtn.disabled = true;
|
|
97
|
+
} else {
|
|
98
|
+
submitBtn.disabled = false;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
password.addEventListener("input", checkPasswords);
|
|
103
|
+
repeatPassword.addEventListener("input", checkPasswords);
|
|
104
|
+
|
|
105
|
+
function checkPasswords() {
|
|
106
|
+
if (password.value === repeatPassword.value) {
|
|
107
|
+
submitBtn.disabled = false;
|
|
108
|
+
} else {
|
|
109
|
+
submitBtn.disabled = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
document.addEventListener("astro:page-load", setupResetForm);
|
|
116
|
+
</script>
|
|
117
|
+
</Layout>
|