@jant/core 0.3.25 → 0.3.27
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/dist/app.js +70 -563
- package/dist/auth.js +3 -0
- package/dist/client.js +1 -0
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/lib/avatar-upload.js +134 -0
- package/dist/lib/config.js +39 -0
- package/dist/lib/constants.js +10 -10
- package/dist/lib/favicon.js +102 -0
- package/dist/lib/image.js +13 -17
- package/dist/lib/media-helpers.js +2 -2
- package/dist/lib/navigation.js +23 -3
- package/dist/lib/render.js +10 -1
- package/dist/lib/schemas.js +31 -0
- package/dist/lib/timezones.js +388 -0
- package/dist/lib/view.js +1 -1
- package/dist/routes/api/posts.js +1 -1
- package/dist/routes/api/upload.js +3 -3
- package/dist/routes/auth/reset.js +221 -0
- package/dist/routes/auth/setup.js +194 -0
- package/dist/routes/auth/signin.js +176 -0
- package/dist/routes/dash/collections.js +23 -415
- package/dist/routes/dash/media.js +12 -392
- package/dist/routes/dash/pages.js +7 -330
- package/dist/routes/dash/redirects.js +18 -12
- package/dist/routes/dash/settings.js +198 -577
- package/dist/routes/feed/rss.js +2 -1
- package/dist/routes/feed/sitemap.js +4 -2
- package/dist/routes/pages/featured.js +5 -1
- package/dist/routes/pages/home.js +26 -1
- package/dist/routes/pages/latest.js +45 -0
- package/dist/services/post.js +30 -50
- package/dist/types/bindings.js +3 -0
- package/dist/types/config.js +147 -0
- package/dist/types/constants.js +27 -0
- package/dist/types/entities.js +3 -0
- package/dist/types/operations.js +3 -0
- package/dist/types/props.js +3 -0
- package/dist/types/views.js +5 -0
- package/dist/types.js +8 -111
- package/dist/ui/color-themes.js +33 -33
- package/dist/ui/compose/ComposeDialog.js +36 -21
- package/dist/ui/dash/PageForm.js +21 -15
- package/dist/ui/dash/PostForm.js +22 -16
- package/dist/ui/dash/collections/CollectionForm.js +152 -0
- package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
- package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
- package/dist/ui/dash/media/MediaListContent.js +166 -0
- package/dist/ui/dash/media/ViewMediaContent.js +212 -0
- package/dist/ui/dash/pages/LinkFormContent.js +130 -0
- package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
- package/dist/ui/dash/settings/AccountContent.js +209 -0
- package/dist/ui/dash/settings/AppearanceContent.js +259 -0
- package/dist/ui/dash/settings/GeneralContent.js +536 -0
- package/dist/ui/dash/settings/SettingsNav.js +41 -0
- package/dist/ui/font-themes.js +36 -0
- package/dist/ui/layouts/BaseLayout.js +24 -2
- package/dist/ui/layouts/SiteLayout.js +47 -19
- package/package.json +1 -1
- package/src/app.tsx +95 -553
- package/src/auth.ts +4 -1
- package/src/client.ts +1 -0
- package/src/i18n/locales/en.po +240 -175
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +240 -175
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +240 -175
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/lib/__tests__/config.test.ts +192 -0
- package/src/lib/__tests__/favicon.test.ts +151 -0
- package/src/lib/__tests__/image.test.ts +2 -6
- package/src/lib/__tests__/timezones.test.ts +61 -0
- package/src/lib/__tests__/view.test.ts +2 -2
- package/src/lib/avatar-upload.ts +165 -0
- package/src/lib/config.ts +47 -0
- package/src/lib/constants.ts +19 -11
- package/src/lib/favicon.ts +115 -0
- package/src/lib/image.ts +13 -21
- package/src/lib/media-helpers.ts +2 -2
- package/src/lib/navigation.ts +33 -2
- package/src/lib/render.tsx +15 -1
- package/src/lib/schemas.ts +39 -0
- package/src/lib/timezones.ts +325 -0
- package/src/lib/view.ts +1 -1
- package/src/routes/api/posts.ts +1 -1
- package/src/routes/api/upload.ts +2 -3
- package/src/routes/auth/reset.tsx +239 -0
- package/src/routes/auth/setup.tsx +189 -0
- package/src/routes/auth/signin.tsx +163 -0
- package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
- package/src/routes/dash/collections.tsx +17 -366
- package/src/routes/dash/media.tsx +12 -414
- package/src/routes/dash/pages.tsx +8 -348
- package/src/routes/dash/redirects.tsx +20 -14
- package/src/routes/dash/settings.tsx +243 -534
- package/src/routes/feed/__tests__/rss.test.ts +141 -0
- package/src/routes/feed/rss.ts +3 -1
- package/src/routes/feed/sitemap.ts +4 -2
- package/src/routes/pages/featured.tsx +7 -1
- package/src/routes/pages/home.tsx +25 -2
- package/src/routes/pages/latest.tsx +59 -0
- package/src/services/post.ts +34 -66
- package/src/styles/components.css +0 -65
- package/src/styles/tokens.css +1 -1
- package/src/styles/ui.css +24 -40
- package/src/types/bindings.ts +30 -0
- package/src/types/config.ts +183 -0
- package/src/types/constants.ts +26 -0
- package/src/types/entities.ts +109 -0
- package/src/types/operations.ts +88 -0
- package/src/types/props.ts +115 -0
- package/src/types/views.ts +172 -0
- package/src/types.ts +8 -644
- package/src/ui/__tests__/font-themes.test.ts +34 -0
- package/src/ui/color-themes.ts +34 -34
- package/src/ui/compose/ComposeDialog.tsx +40 -21
- package/src/ui/dash/PageForm.tsx +25 -19
- package/src/ui/dash/PostForm.tsx +26 -20
- package/src/ui/dash/collections/CollectionForm.tsx +153 -0
- package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
- package/src/ui/dash/media/MediaListContent.tsx +201 -0
- package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
- package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
- package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
- package/src/ui/dash/settings/AccountContent.tsx +176 -0
- package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
- package/src/ui/dash/settings/GeneralContent.tsx +533 -0
- package/src/ui/dash/settings/SettingsNav.tsx +56 -0
- package/src/ui/font-themes.ts +54 -0
- package/src/ui/layouts/BaseLayout.tsx +17 -0
- package/src/ui/layouts/SiteLayout.tsx +45 -31
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Password Reset Routes
|
|
4
|
+
*
|
|
5
|
+
* One-time token-based password reset flow.
|
|
6
|
+
*/ import { Hono } from "hono";
|
|
7
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
+
import { hashPassword } from "better-auth/crypto";
|
|
9
|
+
import { BaseLayout } from "../../ui/layouts/BaseLayout.js";
|
|
10
|
+
import { dsRedirect, dsToast } from "../../lib/sse.js";
|
|
11
|
+
import { SETTINGS_KEYS } from "../../lib/constants.js";
|
|
12
|
+
import { ResetPasswordSchema } from "../../lib/schemas.js";
|
|
13
|
+
const ResetContent = ({ token })=>{
|
|
14
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
15
|
+
const signals = JSON.stringify({
|
|
16
|
+
password: "",
|
|
17
|
+
confirmPassword: "",
|
|
18
|
+
token
|
|
19
|
+
}).replace(/</g, "\\u003c");
|
|
20
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
21
|
+
class: "min-h-screen flex items-center justify-center",
|
|
22
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
23
|
+
class: "card max-w-md w-full",
|
|
24
|
+
children: [
|
|
25
|
+
/*#__PURE__*/ _jsxs("header", {
|
|
26
|
+
children: [
|
|
27
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
28
|
+
children: $__i18n._({
|
|
29
|
+
id: "KbS2K9",
|
|
30
|
+
message: "Reset Password"
|
|
31
|
+
})
|
|
32
|
+
}),
|
|
33
|
+
/*#__PURE__*/ _jsx("p", {
|
|
34
|
+
children: $__i18n._({
|
|
35
|
+
id: "hWOZIv",
|
|
36
|
+
message: "Enter your new password."
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
]
|
|
40
|
+
}),
|
|
41
|
+
/*#__PURE__*/ _jsx("section", {
|
|
42
|
+
children: /*#__PURE__*/ _jsxs("form", {
|
|
43
|
+
"data-signals": signals,
|
|
44
|
+
"data-on:submit__prevent": "@post('/reset')",
|
|
45
|
+
"data-indicator": "_loading",
|
|
46
|
+
class: "flex flex-col gap-4",
|
|
47
|
+
children: [
|
|
48
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
49
|
+
class: "field",
|
|
50
|
+
children: [
|
|
51
|
+
/*#__PURE__*/ _jsx("label", {
|
|
52
|
+
class: "label",
|
|
53
|
+
children: $__i18n._({
|
|
54
|
+
id: "7vhWI8",
|
|
55
|
+
message: "New Password"
|
|
56
|
+
})
|
|
57
|
+
}),
|
|
58
|
+
/*#__PURE__*/ _jsx("input", {
|
|
59
|
+
type: "password",
|
|
60
|
+
"data-bind": "password",
|
|
61
|
+
class: "input",
|
|
62
|
+
required: true,
|
|
63
|
+
minLength: 8,
|
|
64
|
+
autocomplete: "new-password"
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
}),
|
|
68
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
69
|
+
class: "field",
|
|
70
|
+
children: [
|
|
71
|
+
/*#__PURE__*/ _jsx("label", {
|
|
72
|
+
class: "label",
|
|
73
|
+
children: $__i18n._({
|
|
74
|
+
id: "p2/GCq",
|
|
75
|
+
message: "Confirm Password"
|
|
76
|
+
})
|
|
77
|
+
}),
|
|
78
|
+
/*#__PURE__*/ _jsx("input", {
|
|
79
|
+
type: "password",
|
|
80
|
+
"data-bind": "confirmPassword",
|
|
81
|
+
class: "input",
|
|
82
|
+
required: true,
|
|
83
|
+
minLength: 8,
|
|
84
|
+
autocomplete: "new-password"
|
|
85
|
+
})
|
|
86
|
+
]
|
|
87
|
+
}),
|
|
88
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
89
|
+
type: "submit",
|
|
90
|
+
class: "btn",
|
|
91
|
+
"data-attr:disabled": "$_loading",
|
|
92
|
+
children: [
|
|
93
|
+
/*#__PURE__*/ _jsx("svg", {
|
|
94
|
+
"data-show": "$_loading",
|
|
95
|
+
style: "display:none",
|
|
96
|
+
class: "animate-spin size-4",
|
|
97
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
98
|
+
viewBox: "0 0 24 24",
|
|
99
|
+
fill: "none",
|
|
100
|
+
stroke: "currentColor",
|
|
101
|
+
"stroke-width": "2",
|
|
102
|
+
"stroke-linecap": "round",
|
|
103
|
+
"stroke-linejoin": "round",
|
|
104
|
+
role: "status",
|
|
105
|
+
children: /*#__PURE__*/ _jsx("path", {
|
|
106
|
+
d: "M21 12a9 9 0 1 1-6.219-8.56"
|
|
107
|
+
})
|
|
108
|
+
}),
|
|
109
|
+
$__i18n._({
|
|
110
|
+
id: "KbS2K9",
|
|
111
|
+
message: "Reset Password"
|
|
112
|
+
})
|
|
113
|
+
]
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
]
|
|
119
|
+
})
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
const ResetErrorContent = ()=>{
|
|
123
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
124
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
125
|
+
class: "min-h-screen flex items-center justify-center",
|
|
126
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
127
|
+
class: "card max-w-md w-full",
|
|
128
|
+
children: [
|
|
129
|
+
/*#__PURE__*/ _jsx("header", {
|
|
130
|
+
children: /*#__PURE__*/ _jsx("h2", {
|
|
131
|
+
children: $__i18n._({
|
|
132
|
+
id: "7aECQB",
|
|
133
|
+
message: "Invalid or Expired Link"
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
}),
|
|
137
|
+
/*#__PURE__*/ _jsx("section", {
|
|
138
|
+
children: /*#__PURE__*/ _jsx("p", {
|
|
139
|
+
class: "text-muted-foreground",
|
|
140
|
+
children: $__i18n._({
|
|
141
|
+
id: "GbVAnd",
|
|
142
|
+
message: "This password reset link is invalid or has expired. Please generate a new one."
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
]
|
|
147
|
+
})
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Validate a password reset token against the stored value.
|
|
152
|
+
* Returns true if the token is valid and not expired.
|
|
153
|
+
*/ async function validateResetToken(settings, token) {
|
|
154
|
+
const stored = await settings.get(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
|
|
155
|
+
if (!stored) return false;
|
|
156
|
+
const separatorIndex = stored.lastIndexOf(":");
|
|
157
|
+
const storedToken = stored.substring(0, separatorIndex);
|
|
158
|
+
const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
|
|
159
|
+
const now = Math.floor(Date.now() / 1000);
|
|
160
|
+
return token === storedToken && now <= expiry;
|
|
161
|
+
}
|
|
162
|
+
export const resetRoutes = new Hono();
|
|
163
|
+
resetRoutes.get("/reset", async (c)=>{
|
|
164
|
+
const token = c.req.query("token");
|
|
165
|
+
if (!token) {
|
|
166
|
+
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
167
|
+
title: "Reset Password - Jant",
|
|
168
|
+
c: c,
|
|
169
|
+
children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
const isValid = await validateResetToken(c.var.services.settings, token);
|
|
173
|
+
if (!isValid) {
|
|
174
|
+
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
175
|
+
title: "Reset Password - Jant",
|
|
176
|
+
c: c,
|
|
177
|
+
children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
181
|
+
title: "Reset Password - Jant",
|
|
182
|
+
c: c,
|
|
183
|
+
children: /*#__PURE__*/ _jsx(ResetContent, {
|
|
184
|
+
token: token
|
|
185
|
+
})
|
|
186
|
+
}));
|
|
187
|
+
});
|
|
188
|
+
resetRoutes.post("/reset", async (c)=>{
|
|
189
|
+
const body = await c.req.json();
|
|
190
|
+
const parsed = ResetPasswordSchema.safeParse(body);
|
|
191
|
+
if (!parsed.success) {
|
|
192
|
+
const msg = parsed.error.errors[0]?.message ?? "Invalid input";
|
|
193
|
+
return dsToast(msg, "error");
|
|
194
|
+
}
|
|
195
|
+
const { password, token } = parsed.data;
|
|
196
|
+
// Validate token
|
|
197
|
+
const isValid = await validateResetToken(c.var.services.settings, token);
|
|
198
|
+
if (!isValid) {
|
|
199
|
+
return dsToast("Invalid or expired reset link.", "error");
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const hashedPassword = await hashPassword(password);
|
|
203
|
+
const db = c.env.DB.withSession();
|
|
204
|
+
// Get admin user
|
|
205
|
+
const userResult = await db.prepare("SELECT id FROM user LIMIT 1").first();
|
|
206
|
+
if (!userResult) {
|
|
207
|
+
return dsToast("No user account found.", "error");
|
|
208
|
+
}
|
|
209
|
+
// Update password
|
|
210
|
+
await db.prepare("UPDATE account SET password = ? WHERE user_id = ? AND provider_id = 'credential'").bind(hashedPassword, userResult.id).run();
|
|
211
|
+
// Delete all sessions
|
|
212
|
+
await db.prepare("DELETE FROM session WHERE user_id = ?").bind(userResult.id).run();
|
|
213
|
+
// Delete the reset token
|
|
214
|
+
await c.var.services.settings.remove(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
|
|
215
|
+
return dsRedirect("/signin?reset");
|
|
216
|
+
} catch (err) {
|
|
217
|
+
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
218
|
+
console.error("Password reset error:", err);
|
|
219
|
+
return dsToast("Failed to reset password.", "error");
|
|
220
|
+
}
|
|
221
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Setup Routes
|
|
4
|
+
*
|
|
5
|
+
* Initial admin account creation during first-time setup.
|
|
6
|
+
*/ import { Hono } from "hono";
|
|
7
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
+
import { BaseLayout } from "../../ui/layouts/BaseLayout.js";
|
|
9
|
+
import { dsRedirect, dsToast } from "../../lib/sse.js";
|
|
10
|
+
import { SetupSchema } from "../../lib/schemas.js";
|
|
11
|
+
import { mapIanaToTimezone } from "../../lib/timezones.js";
|
|
12
|
+
const SetupContent = ()=>{
|
|
13
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
14
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
15
|
+
class: "min-h-screen flex items-center justify-center",
|
|
16
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
17
|
+
class: "card max-w-md w-full",
|
|
18
|
+
children: [
|
|
19
|
+
/*#__PURE__*/ _jsxs("header", {
|
|
20
|
+
children: [
|
|
21
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
22
|
+
children: $__i18n._({
|
|
23
|
+
id: "GorKul",
|
|
24
|
+
message: "Welcome to Jant"
|
|
25
|
+
})
|
|
26
|
+
}),
|
|
27
|
+
/*#__PURE__*/ _jsx("p", {
|
|
28
|
+
children: $__i18n._({
|
|
29
|
+
id: "GX2VMa",
|
|
30
|
+
message: "Create your admin account."
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
]
|
|
34
|
+
}),
|
|
35
|
+
/*#__PURE__*/ _jsx("section", {
|
|
36
|
+
children: /*#__PURE__*/ _jsxs("form", {
|
|
37
|
+
"data-signals": "{name: '', email: '', password: '', _timezone: ''}",
|
|
38
|
+
"data-init": "$_timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || ''",
|
|
39
|
+
"data-on:submit__prevent": "@post('/setup')",
|
|
40
|
+
"data-indicator": "_loading",
|
|
41
|
+
class: "flex flex-col gap-4",
|
|
42
|
+
children: [
|
|
43
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
44
|
+
class: "field",
|
|
45
|
+
children: [
|
|
46
|
+
/*#__PURE__*/ _jsx("label", {
|
|
47
|
+
class: "label",
|
|
48
|
+
children: $__i18n._({
|
|
49
|
+
id: "/Rj5P4",
|
|
50
|
+
message: "Your Name"
|
|
51
|
+
})
|
|
52
|
+
}),
|
|
53
|
+
/*#__PURE__*/ _jsx("input", {
|
|
54
|
+
type: "text",
|
|
55
|
+
"data-bind": "name",
|
|
56
|
+
class: "input",
|
|
57
|
+
required: true,
|
|
58
|
+
placeholder: "John Doe"
|
|
59
|
+
})
|
|
60
|
+
]
|
|
61
|
+
}),
|
|
62
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
63
|
+
class: "field",
|
|
64
|
+
children: [
|
|
65
|
+
/*#__PURE__*/ _jsx("label", {
|
|
66
|
+
class: "label",
|
|
67
|
+
children: $__i18n._({
|
|
68
|
+
id: "O3oNi5",
|
|
69
|
+
message: "Email"
|
|
70
|
+
})
|
|
71
|
+
}),
|
|
72
|
+
/*#__PURE__*/ _jsx("input", {
|
|
73
|
+
type: "email",
|
|
74
|
+
"data-bind": "email",
|
|
75
|
+
class: "input",
|
|
76
|
+
required: true,
|
|
77
|
+
placeholder: "you@example.com"
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
}),
|
|
81
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
82
|
+
class: "field",
|
|
83
|
+
children: [
|
|
84
|
+
/*#__PURE__*/ _jsx("label", {
|
|
85
|
+
class: "label",
|
|
86
|
+
children: $__i18n._({
|
|
87
|
+
id: "8ZsakT",
|
|
88
|
+
message: "Password"
|
|
89
|
+
})
|
|
90
|
+
}),
|
|
91
|
+
/*#__PURE__*/ _jsx("input", {
|
|
92
|
+
type: "password",
|
|
93
|
+
"data-bind": "password",
|
|
94
|
+
class: "input",
|
|
95
|
+
required: true,
|
|
96
|
+
minLength: 8
|
|
97
|
+
})
|
|
98
|
+
]
|
|
99
|
+
}),
|
|
100
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
101
|
+
type: "submit",
|
|
102
|
+
class: "btn",
|
|
103
|
+
"data-attr:disabled": "$_loading",
|
|
104
|
+
children: [
|
|
105
|
+
/*#__PURE__*/ _jsx("svg", {
|
|
106
|
+
"data-show": "$_loading",
|
|
107
|
+
style: "display:none",
|
|
108
|
+
class: "animate-spin size-4",
|
|
109
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
110
|
+
viewBox: "0 0 24 24",
|
|
111
|
+
fill: "none",
|
|
112
|
+
stroke: "currentColor",
|
|
113
|
+
"stroke-width": "2",
|
|
114
|
+
"stroke-linecap": "round",
|
|
115
|
+
"stroke-linejoin": "round",
|
|
116
|
+
role: "status",
|
|
117
|
+
children: /*#__PURE__*/ _jsx("path", {
|
|
118
|
+
d: "M21 12a9 9 0 1 1-6.219-8.56"
|
|
119
|
+
})
|
|
120
|
+
}),
|
|
121
|
+
$__i18n._({
|
|
122
|
+
id: "EGwzOK",
|
|
123
|
+
message: "Complete Setup"
|
|
124
|
+
})
|
|
125
|
+
]
|
|
126
|
+
})
|
|
127
|
+
]
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
]
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
export const setupRoutes = new Hono();
|
|
135
|
+
setupRoutes.get("/setup", async (c)=>{
|
|
136
|
+
const isComplete = await c.var.services.settings.isOnboardingComplete();
|
|
137
|
+
if (isComplete) return c.redirect("/");
|
|
138
|
+
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
139
|
+
title: "Setup - Jant",
|
|
140
|
+
c: c,
|
|
141
|
+
children: /*#__PURE__*/ _jsx(SetupContent, {})
|
|
142
|
+
}));
|
|
143
|
+
});
|
|
144
|
+
setupRoutes.post("/setup", async (c)=>{
|
|
145
|
+
const isComplete = await c.var.services.settings.isOnboardingComplete();
|
|
146
|
+
if (isComplete) return c.redirect("/");
|
|
147
|
+
const body = await c.req.json();
|
|
148
|
+
const parsed = SetupSchema.safeParse(body);
|
|
149
|
+
const browserTimezone = body._timezone;
|
|
150
|
+
if (!parsed.success) {
|
|
151
|
+
const msg = parsed.error.errors[0]?.message ?? "Invalid input";
|
|
152
|
+
return dsToast(msg, "error");
|
|
153
|
+
}
|
|
154
|
+
const { name, email, password } = parsed.data;
|
|
155
|
+
if (!c.var.auth) {
|
|
156
|
+
return dsToast("AUTH_SECRET not configured", "error");
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const signUpResponse = await c.var.auth.api.signUpEmail({
|
|
160
|
+
body: {
|
|
161
|
+
name,
|
|
162
|
+
email,
|
|
163
|
+
password
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
if (!signUpResponse || "error" in signUpResponse) {
|
|
167
|
+
return dsToast("Failed to create account", "error");
|
|
168
|
+
}
|
|
169
|
+
await c.var.services.settings.completeOnboarding();
|
|
170
|
+
// Save auto-detected timezone
|
|
171
|
+
if (browserTimezone) {
|
|
172
|
+
const tz = mapIanaToTimezone(browserTimezone);
|
|
173
|
+
if (tz !== "UTC") {
|
|
174
|
+
await c.var.services.settings.set("TIME_ZONE", tz);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Seed default navigation items
|
|
178
|
+
await c.var.services.navItems.create({
|
|
179
|
+
type: "link",
|
|
180
|
+
label: "Featured",
|
|
181
|
+
url: "/featured"
|
|
182
|
+
});
|
|
183
|
+
await c.var.services.navItems.create({
|
|
184
|
+
type: "link",
|
|
185
|
+
label: "Collections",
|
|
186
|
+
url: "/collections"
|
|
187
|
+
});
|
|
188
|
+
return dsRedirect("/signin?setup");
|
|
189
|
+
} catch (err) {
|
|
190
|
+
// eslint-disable-next-line no-console -- Error logging is intentional
|
|
191
|
+
console.error("Setup error:", err);
|
|
192
|
+
return dsToast("Failed to create account", "error");
|
|
193
|
+
}
|
|
194
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Sign-in / Sign-out Routes
|
|
4
|
+
*/ import { Hono } from "hono";
|
|
5
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
6
|
+
import { BaseLayout } from "../../ui/layouts/BaseLayout.js";
|
|
7
|
+
import { dsRedirect, dsToast } from "../../lib/sse.js";
|
|
8
|
+
import { SigninSchema } from "../../lib/schemas.js";
|
|
9
|
+
const SigninContent = ({ demoEmail, demoPassword })=>{
|
|
10
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
11
|
+
const signals = JSON.stringify({
|
|
12
|
+
email: demoEmail || "",
|
|
13
|
+
password: demoPassword || ""
|
|
14
|
+
}).replace(/</g, "\\u003c");
|
|
15
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
16
|
+
class: "min-h-screen flex items-center justify-center",
|
|
17
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
18
|
+
class: "card max-w-md w-full",
|
|
19
|
+
children: [
|
|
20
|
+
/*#__PURE__*/ _jsx("header", {
|
|
21
|
+
children: /*#__PURE__*/ _jsx("h2", {
|
|
22
|
+
children: $__i18n._({
|
|
23
|
+
id: "n1ekoW",
|
|
24
|
+
message: "Sign In"
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
}),
|
|
28
|
+
/*#__PURE__*/ _jsxs("section", {
|
|
29
|
+
children: [
|
|
30
|
+
demoEmail && demoPassword && /*#__PURE__*/ _jsx("p", {
|
|
31
|
+
class: "text-muted-foreground text-sm mb-4",
|
|
32
|
+
children: $__i18n._({
|
|
33
|
+
id: "er8+x7",
|
|
34
|
+
message: "Demo account pre-filled. Just click Sign In."
|
|
35
|
+
})
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ _jsxs("form", {
|
|
38
|
+
"data-signals": signals,
|
|
39
|
+
"data-on:submit__prevent": "@post('/signin')",
|
|
40
|
+
"data-indicator": "_loading",
|
|
41
|
+
class: "flex flex-col gap-4",
|
|
42
|
+
children: [
|
|
43
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
44
|
+
class: "field",
|
|
45
|
+
children: [
|
|
46
|
+
/*#__PURE__*/ _jsx("label", {
|
|
47
|
+
class: "label",
|
|
48
|
+
children: $__i18n._({
|
|
49
|
+
id: "O3oNi5",
|
|
50
|
+
message: "Email"
|
|
51
|
+
})
|
|
52
|
+
}),
|
|
53
|
+
/*#__PURE__*/ _jsx("input", {
|
|
54
|
+
type: "email",
|
|
55
|
+
"data-bind": "email",
|
|
56
|
+
class: "input",
|
|
57
|
+
required: true
|
|
58
|
+
})
|
|
59
|
+
]
|
|
60
|
+
}),
|
|
61
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
62
|
+
class: "field",
|
|
63
|
+
children: [
|
|
64
|
+
/*#__PURE__*/ _jsx("label", {
|
|
65
|
+
class: "label",
|
|
66
|
+
children: $__i18n._({
|
|
67
|
+
id: "8ZsakT",
|
|
68
|
+
message: "Password"
|
|
69
|
+
})
|
|
70
|
+
}),
|
|
71
|
+
/*#__PURE__*/ _jsx("input", {
|
|
72
|
+
type: "password",
|
|
73
|
+
"data-bind": "password",
|
|
74
|
+
class: "input",
|
|
75
|
+
required: true
|
|
76
|
+
})
|
|
77
|
+
]
|
|
78
|
+
}),
|
|
79
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
80
|
+
type: "submit",
|
|
81
|
+
class: "btn",
|
|
82
|
+
"data-attr:disabled": "$_loading",
|
|
83
|
+
children: [
|
|
84
|
+
/*#__PURE__*/ _jsx("svg", {
|
|
85
|
+
"data-show": "$_loading",
|
|
86
|
+
style: "display:none",
|
|
87
|
+
class: "animate-spin size-4",
|
|
88
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
89
|
+
viewBox: "0 0 24 24",
|
|
90
|
+
fill: "none",
|
|
91
|
+
stroke: "currentColor",
|
|
92
|
+
"stroke-width": "2",
|
|
93
|
+
"stroke-linecap": "round",
|
|
94
|
+
"stroke-linejoin": "round",
|
|
95
|
+
role: "status",
|
|
96
|
+
children: /*#__PURE__*/ _jsx("path", {
|
|
97
|
+
d: "M21 12a9 9 0 1 1-6.219-8.56"
|
|
98
|
+
})
|
|
99
|
+
}),
|
|
100
|
+
$__i18n._({
|
|
101
|
+
id: "n1ekoW",
|
|
102
|
+
message: "Sign In"
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
})
|
|
106
|
+
]
|
|
107
|
+
})
|
|
108
|
+
]
|
|
109
|
+
})
|
|
110
|
+
]
|
|
111
|
+
})
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
export const signinRoutes = new Hono();
|
|
115
|
+
signinRoutes.get("/signin", async (c)=>{
|
|
116
|
+
const isSetup = c.req.query("setup") !== undefined;
|
|
117
|
+
const isReset = c.req.query("reset") !== undefined;
|
|
118
|
+
let toast;
|
|
119
|
+
if (isSetup) {
|
|
120
|
+
toast = {
|
|
121
|
+
message: "Account created successfully. Please sign in."
|
|
122
|
+
};
|
|
123
|
+
} else if (isReset) {
|
|
124
|
+
toast = {
|
|
125
|
+
message: "Password reset successfully. Please sign in."
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
129
|
+
title: "Sign In - Jant",
|
|
130
|
+
c: c,
|
|
131
|
+
toast: toast,
|
|
132
|
+
children: /*#__PURE__*/ _jsx(SigninContent, {
|
|
133
|
+
demoEmail: c.env.DEMO_EMAIL,
|
|
134
|
+
demoPassword: c.env.DEMO_PASSWORD
|
|
135
|
+
})
|
|
136
|
+
}));
|
|
137
|
+
});
|
|
138
|
+
signinRoutes.post("/signin", async (c)=>{
|
|
139
|
+
if (!c.var.auth) {
|
|
140
|
+
return dsToast("Auth not configured", "error");
|
|
141
|
+
}
|
|
142
|
+
const body = await c.req.json();
|
|
143
|
+
const parsed = SigninSchema.safeParse(body);
|
|
144
|
+
if (!parsed.success) {
|
|
145
|
+
const msg = parsed.error.errors[0]?.message ?? "Invalid input";
|
|
146
|
+
return dsToast(msg, "error");
|
|
147
|
+
}
|
|
148
|
+
const { email, password } = parsed.data;
|
|
149
|
+
try {
|
|
150
|
+
const { headers } = await c.var.auth.api.signInEmail({
|
|
151
|
+
returnHeaders: true,
|
|
152
|
+
body: {
|
|
153
|
+
email,
|
|
154
|
+
password
|
|
155
|
+
},
|
|
156
|
+
headers: c.req.raw.headers
|
|
157
|
+
});
|
|
158
|
+
return dsRedirect("/dash", {
|
|
159
|
+
headers
|
|
160
|
+
});
|
|
161
|
+
} catch {
|
|
162
|
+
return dsToast("Invalid email or password", "error");
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
signinRoutes.get("/signout", async (c)=>{
|
|
166
|
+
if (c.var.auth) {
|
|
167
|
+
try {
|
|
168
|
+
await c.var.auth.api.signOut({
|
|
169
|
+
headers: c.req.raw.headers
|
|
170
|
+
});
|
|
171
|
+
} catch {
|
|
172
|
+
// Ignore signout errors
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return c.redirect("/");
|
|
176
|
+
});
|