@jant/core 0.2.17 → 0.2.18

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.
Files changed (81) hide show
  1. package/dist/app.d.ts +1 -0
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/app.js +319 -115
  4. package/dist/i18n/context.d.ts +2 -2
  5. package/dist/i18n/context.js +1 -1
  6. package/dist/i18n/i18n.d.ts +1 -1
  7. package/dist/i18n/i18n.js +1 -1
  8. package/dist/i18n/index.d.ts +1 -1
  9. package/dist/i18n/index.js +1 -1
  10. package/dist/i18n/locales/en.d.ts.map +1 -1
  11. package/dist/i18n/locales/en.js +1 -1
  12. package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
  13. package/dist/i18n/locales/zh-Hans.js +1 -1
  14. package/dist/i18n/locales/zh-Hant.d.ts.map +1 -1
  15. package/dist/i18n/locales/zh-Hant.js +1 -1
  16. package/dist/lib/config.d.ts +44 -10
  17. package/dist/lib/config.d.ts.map +1 -1
  18. package/dist/lib/config.js +69 -44
  19. package/dist/lib/constants.d.ts +2 -1
  20. package/dist/lib/constants.d.ts.map +1 -1
  21. package/dist/lib/constants.js +5 -2
  22. package/dist/lib/sse.d.ts +15 -0
  23. package/dist/lib/sse.d.ts.map +1 -1
  24. package/dist/lib/sse.js +13 -0
  25. package/dist/lib/theme.d.ts +44 -0
  26. package/dist/lib/theme.d.ts.map +1 -0
  27. package/dist/lib/theme.js +65 -0
  28. package/dist/routes/dash/appearance.d.ts +13 -0
  29. package/dist/routes/dash/appearance.d.ts.map +1 -0
  30. package/dist/routes/dash/appearance.js +164 -0
  31. package/dist/routes/dash/settings.d.ts.map +1 -1
  32. package/dist/routes/dash/settings.js +38 -37
  33. package/dist/services/settings.d.ts +1 -0
  34. package/dist/services/settings.d.ts.map +1 -1
  35. package/dist/services/settings.js +3 -0
  36. package/dist/theme/color-themes.d.ts +30 -0
  37. package/dist/theme/color-themes.d.ts.map +1 -0
  38. package/dist/theme/color-themes.js +268 -0
  39. package/dist/theme/layouts/BaseLayout.d.ts +5 -0
  40. package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
  41. package/dist/theme/layouts/BaseLayout.js +70 -3
  42. package/dist/theme/layouts/DashLayout.d.ts +2 -0
  43. package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
  44. package/dist/theme/layouts/DashLayout.js +10 -1
  45. package/dist/theme/layouts/index.d.ts +1 -1
  46. package/dist/theme/layouts/index.d.ts.map +1 -1
  47. package/dist/types.d.ts +53 -1
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/types.js +52 -0
  50. package/package.json +1 -1
  51. package/src/app.tsx +272 -55
  52. package/src/db/migrations/{0000_solid_moon_knight.sql → 0000_square_wallflower.sql} +3 -3
  53. package/src/db/migrations/meta/0000_snapshot.json +9 -9
  54. package/src/db/migrations/meta/_journal.json +2 -30
  55. package/src/i18n/context.tsx +2 -2
  56. package/src/i18n/i18n.ts +1 -1
  57. package/src/i18n/index.ts +1 -1
  58. package/src/i18n/locales/en.po +328 -252
  59. package/src/i18n/locales/en.ts +1 -1
  60. package/src/i18n/locales/zh-Hans.po +315 -278
  61. package/src/i18n/locales/zh-Hans.ts +1 -1
  62. package/src/i18n/locales/zh-Hant.po +315 -278
  63. package/src/i18n/locales/zh-Hant.ts +1 -1
  64. package/src/lib/config.ts +73 -47
  65. package/src/lib/constants.ts +3 -0
  66. package/src/lib/sse.ts +38 -0
  67. package/src/lib/theme.ts +86 -0
  68. package/src/preset.css +9 -0
  69. package/src/routes/dash/appearance.tsx +180 -0
  70. package/src/routes/dash/settings.tsx +50 -52
  71. package/src/services/settings.ts +5 -0
  72. package/src/styles/components.css +93 -0
  73. package/src/theme/color-themes.ts +321 -0
  74. package/src/theme/layouts/BaseLayout.tsx +61 -1
  75. package/src/theme/layouts/DashLayout.tsx +13 -2
  76. package/src/theme/layouts/index.ts +5 -1
  77. package/src/types.ts +62 -1
  78. package/src/db/migrations/0001_add_search_fts.sql +0 -40
  79. package/src/db/migrations/0002_collection_path.sql +0 -2
  80. package/src/db/migrations/0003_collection_path_nullable.sql +0 -21
  81. package/src/db/migrations/0004_media_uuid.sql +0 -35
package/dist/app.d.ts CHANGED
@@ -9,6 +9,7 @@ export interface AppVariables {
9
9
  services: Services;
10
10
  auth: Auth;
11
11
  config: JantConfig;
12
+ themeStyle: string;
12
13
  }
13
14
  export type App = Hono<{
14
15
  Bindings: Bindings;
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGlD,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAoCvD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AAExE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,MAAM,GAAE,UAAe,GAAG,GAAG,CA2dtD"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGlD,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAwCvD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AAExE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,MAAM,GAAE,UAAe,GAAG,GAAG,CA+qBtD"}
package/dist/app.js CHANGED
@@ -7,6 +7,8 @@ import { createServices } from "./services/index.js";
7
7
  import { createAuth } from "./auth.js";
8
8
  import { i18nMiddleware } from "./i18n/index.js";
9
9
  import { useLingui as $_useLingui } from "@jant/core/i18n";
10
+ import { SETTINGS_KEYS } from "./lib/constants.js";
11
+ import { hashPassword } from "better-auth/crypto";
10
12
  // Routes - Pages
11
13
  import { homeRoutes } from "./routes/pages/home.js";
12
14
  import { postRoutes } from "./routes/pages/post.js";
@@ -22,6 +24,7 @@ import { mediaRoutes as dashMediaRoutes } from "./routes/dash/media.js";
22
24
  import { settingsRoutes as dashSettingsRoutes } from "./routes/dash/settings.js";
23
25
  import { redirectsRoutes as dashRedirectsRoutes } from "./routes/dash/redirects.js";
24
26
  import { collectionsRoutes as dashCollectionsRoutes } from "./routes/dash/collections.js";
27
+ import { appearanceRoutes as dashAppearanceRoutes } from "./routes/dash/appearance.js";
25
28
  // Routes - API
26
29
  import { postsApiRoutes } from "./routes/api/posts.js";
27
30
  import { uploadApiRoutes } from "./routes/api/upload.js";
@@ -34,6 +37,7 @@ import { requireAuth } from "./middleware/auth.js";
34
37
  // Layouts for auth pages
35
38
  import { BaseLayout } from "./theme/layouts/index.js";
36
39
  import { sse } from "./lib/sse.js";
40
+ import { getAvailableThemes, buildThemeStyle } from "./lib/theme.js";
37
41
  /**
38
42
  * Create a Jant application
39
43
  *
@@ -75,6 +79,15 @@ import { sse } from "./lib/sse.js";
75
79
  }
76
80
  await next();
77
81
  });
82
+ // Theme middleware - resolve active color theme and build CSS
83
+ app.use("*", async (c, next)=>{
84
+ const themeId = await c.var.services.settings.get(SETTINGS_KEYS.THEME);
85
+ const themes = getAvailableThemes(config);
86
+ const activeTheme = themeId ? themes.find((t)=>t.id === themeId) : undefined;
87
+ const themeStyle = buildThemeStyle(activeTheme, config.theme?.cssVariables);
88
+ c.set("themeStyle", themeStyle);
89
+ await next();
90
+ });
78
91
  // i18n middleware
79
92
  app.use("*", i18nMiddleware());
80
93
  // Trailing slash redirect (redirect /foo/ to /foo)
@@ -134,112 +147,85 @@ import { sse } from "./lib/sse.js";
134
147
  }),
135
148
  /*#__PURE__*/ _jsx("p", {
136
149
  children: $__i18n._({
137
- id: "ig4hg2",
138
- message: "Let's set up your site."
150
+ id: "GX2VMa",
151
+ message: "Create your admin account."
139
152
  })
140
153
  })
141
154
  ]
142
155
  }),
143
- /*#__PURE__*/ _jsxs("section", {
144
- children: [
145
- /*#__PURE__*/ _jsx("div", {
146
- id: "setup-message"
147
- }),
148
- /*#__PURE__*/ _jsxs("form", {
149
- "data-signals": "{siteName: '', name: '', email: '', password: ''}",
150
- "data-on:submit__prevent": "@post('/setup')",
151
- class: "flex flex-col gap-4",
152
- children: [
153
- /*#__PURE__*/ _jsxs("div", {
154
- class: "field",
155
- children: [
156
- /*#__PURE__*/ _jsx("label", {
157
- class: "label",
158
- children: $__i18n._({
159
- id: "SJmfuf",
160
- message: "Site Name"
161
- })
162
- }),
163
- /*#__PURE__*/ _jsx("input", {
164
- type: "text",
165
- "data-bind": "siteName",
166
- class: "input",
167
- required: true,
168
- placeholder: $__i18n._({
169
- id: "HfyyXl",
170
- message: "My Blog"
171
- })
172
- })
173
- ]
174
- }),
175
- /*#__PURE__*/ _jsxs("div", {
176
- class: "field",
177
- children: [
178
- /*#__PURE__*/ _jsx("label", {
179
- class: "label",
180
- children: $__i18n._({
181
- id: "/Rj5P4",
182
- message: "Your Name"
183
- })
184
- }),
185
- /*#__PURE__*/ _jsx("input", {
186
- type: "text",
187
- "data-bind": "name",
188
- class: "input",
189
- required: true,
190
- placeholder: "John Doe"
156
+ /*#__PURE__*/ _jsx("section", {
157
+ children: /*#__PURE__*/ _jsxs("form", {
158
+ "data-signals": "{name: '', email: '', password: ''}",
159
+ "data-on:submit__prevent": "@post('/setup')",
160
+ class: "flex flex-col gap-4",
161
+ children: [
162
+ /*#__PURE__*/ _jsxs("div", {
163
+ class: "field",
164
+ children: [
165
+ /*#__PURE__*/ _jsx("label", {
166
+ class: "label",
167
+ children: $__i18n._({
168
+ id: "/Rj5P4",
169
+ message: "Your Name"
191
170
  })
192
- ]
193
- }),
194
- /*#__PURE__*/ _jsxs("div", {
195
- class: "field",
196
- children: [
197
- /*#__PURE__*/ _jsx("label", {
198
- class: "label",
199
- children: $__i18n._({
200
- id: "O3oNi5",
201
- message: "Email"
202
- })
203
- }),
204
- /*#__PURE__*/ _jsx("input", {
205
- type: "email",
206
- "data-bind": "email",
207
- class: "input",
208
- required: true,
209
- placeholder: "you@example.com"
171
+ }),
172
+ /*#__PURE__*/ _jsx("input", {
173
+ type: "text",
174
+ "data-bind": "name",
175
+ class: "input",
176
+ required: true,
177
+ placeholder: "John Doe"
178
+ })
179
+ ]
180
+ }),
181
+ /*#__PURE__*/ _jsxs("div", {
182
+ class: "field",
183
+ children: [
184
+ /*#__PURE__*/ _jsx("label", {
185
+ class: "label",
186
+ children: $__i18n._({
187
+ id: "O3oNi5",
188
+ message: "Email"
210
189
  })
211
- ]
212
- }),
213
- /*#__PURE__*/ _jsxs("div", {
214
- class: "field",
215
- children: [
216
- /*#__PURE__*/ _jsx("label", {
217
- class: "label",
218
- children: $__i18n._({
219
- id: "8ZsakT",
220
- message: "Password"
221
- })
222
- }),
223
- /*#__PURE__*/ _jsx("input", {
224
- type: "password",
225
- "data-bind": "password",
226
- class: "input",
227
- required: true,
228
- minLength: 8
190
+ }),
191
+ /*#__PURE__*/ _jsx("input", {
192
+ type: "email",
193
+ "data-bind": "email",
194
+ class: "input",
195
+ required: true,
196
+ placeholder: "you@example.com"
197
+ })
198
+ ]
199
+ }),
200
+ /*#__PURE__*/ _jsxs("div", {
201
+ class: "field",
202
+ children: [
203
+ /*#__PURE__*/ _jsx("label", {
204
+ class: "label",
205
+ children: $__i18n._({
206
+ id: "8ZsakT",
207
+ message: "Password"
229
208
  })
230
- ]
231
- }),
232
- /*#__PURE__*/ _jsx("button", {
233
- type: "submit",
234
- class: "btn",
235
- children: $__i18n._({
236
- id: "EGwzOK",
237
- message: "Complete Setup"
209
+ }),
210
+ /*#__PURE__*/ _jsx("input", {
211
+ type: "password",
212
+ "data-bind": "password",
213
+ class: "input",
214
+ required: true,
215
+ minLength: 8
238
216
  })
217
+ ]
218
+ }),
219
+ /*#__PURE__*/ _jsx("button", {
220
+ type: "submit",
221
+ class: "btn",
222
+ children: $__i18n._({
223
+ id: "EGwzOK",
224
+ message: "Complete Setup"
239
225
  })
240
- ]
241
- })
242
- ]
226
+ })
227
+ ]
228
+ })
243
229
  })
244
230
  ]
245
231
  })
@@ -259,20 +245,20 @@ import { sse } from "./lib/sse.js";
259
245
  const isComplete = await c.var.services.settings.isOnboardingComplete();
260
246
  if (isComplete) return c.redirect("/");
261
247
  const body = await c.req.json();
262
- const { siteName, name, email, password } = body;
263
- if (!siteName || !name || !email || !password) {
248
+ const { name, email, password } = body;
249
+ if (!name || !email || !password) {
264
250
  return sse(c, async (stream)=>{
265
- await stream.patchElements('<div id="setup-message"><div class="alert-destructive mb-4"><h2>All fields are required</h2></div></div>');
251
+ await stream.toast("All fields are required", "error");
266
252
  });
267
253
  }
268
254
  if (password.length < 8) {
269
255
  return sse(c, async (stream)=>{
270
- await stream.patchElements('<div id="setup-message"><div class="alert-destructive mb-4"><h2>Password must be at least 8 characters</h2></div></div>');
256
+ await stream.toast("Password must be at least 8 characters", "error");
271
257
  });
272
258
  }
273
259
  if (!c.var.auth) {
274
260
  return sse(c, async (stream)=>{
275
- await stream.patchElements('<div id="setup-message"><div class="alert-destructive mb-4"><h2>AUTH_SECRET not configured</h2></div></div>');
261
+ await stream.toast("AUTH_SECRET not configured", "error");
276
262
  });
277
263
  }
278
264
  try {
@@ -285,22 +271,18 @@ import { sse } from "./lib/sse.js";
285
271
  });
286
272
  if (!signUpResponse || "error" in signUpResponse) {
287
273
  return sse(c, async (stream)=>{
288
- await stream.patchElements('<div id="setup-message"><div class="alert-destructive mb-4"><h2>Failed to create account</h2></div></div>');
274
+ await stream.toast("Failed to create account", "error");
289
275
  });
290
276
  }
291
- await c.var.services.settings.setMany({
292
- SITE_NAME: siteName,
293
- SITE_LANGUAGE: "en"
294
- });
295
277
  await c.var.services.settings.completeOnboarding();
296
278
  return sse(c, async (stream)=>{
297
- await stream.redirect("/signin");
279
+ await stream.redirect("/signin?setup");
298
280
  });
299
281
  } catch (err) {
300
282
  // eslint-disable-next-line no-console -- Error logging is intentional
301
283
  console.error("Setup error:", err);
302
284
  return sse(c, async (stream)=>{
303
- await stream.patchElements('<div id="setup-message"><div class="alert-destructive mb-4"><h2>Failed to create account</h2></div></div>');
285
+ await stream.toast("Failed to create account", "error");
304
286
  });
305
287
  }
306
288
  });
@@ -326,9 +308,6 @@ import { sse } from "./lib/sse.js";
326
308
  }),
327
309
  /*#__PURE__*/ _jsxs("section", {
328
310
  children: [
329
- /*#__PURE__*/ _jsx("div", {
330
- id: "signin-message"
331
- }),
332
311
  demoEmail && demoPassword && /*#__PURE__*/ _jsx("p", {
333
312
  class: "text-muted-foreground text-sm mb-4",
334
313
  children: $__i18n._({
@@ -395,9 +374,22 @@ import { sse } from "./lib/sse.js";
395
374
  };
396
375
  // Signin page
397
376
  app.get("/signin", async (c)=>{
377
+ const isSetup = c.req.query("setup") !== undefined;
378
+ const isReset = c.req.query("reset") !== undefined;
379
+ let toast;
380
+ if (isSetup) {
381
+ toast = {
382
+ message: "Account created successfully. Please sign in."
383
+ };
384
+ } else if (isReset) {
385
+ toast = {
386
+ message: "Password reset successfully. Please sign in."
387
+ };
388
+ }
398
389
  return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
399
390
  title: "Sign In - Jant",
400
391
  c: c,
392
+ toast: toast,
401
393
  children: /*#__PURE__*/ _jsx(SigninContent, {
402
394
  demoEmail: c.env.DEMO_EMAIL,
403
395
  demoPassword: c.env.DEMO_PASSWORD
@@ -407,7 +399,7 @@ import { sse } from "./lib/sse.js";
407
399
  app.post("/signin", async (c)=>{
408
400
  if (!c.var.auth) {
409
401
  return sse(c, async (stream)=>{
410
- await stream.patchElements('<div id="signin-message"><div class="alert-destructive mb-4"><h2>Auth not configured</h2></div></div>');
402
+ await stream.toast("Auth not configured", "error");
411
403
  });
412
404
  }
413
405
  const body = await c.req.json();
@@ -426,7 +418,7 @@ import { sse } from "./lib/sse.js";
426
418
  const response = await c.var.auth.handler(signInRequest);
427
419
  if (!response.ok) {
428
420
  return sse(c, async (stream)=>{
429
- await stream.patchElements('<div id="signin-message"><div class="alert-destructive mb-4"><h2>Invalid email or password</h2></div></div>');
421
+ await stream.toast("Invalid email or password", "error");
430
422
  });
431
423
  }
432
424
  // Forward Set-Cookie headers from auth response
@@ -444,7 +436,7 @@ import { sse } from "./lib/sse.js";
444
436
  // eslint-disable-next-line no-console -- Error logging is intentional
445
437
  console.error("Signin error:", err);
446
438
  return sse(c, async (stream)=>{
447
- await stream.patchElements('<div id="signin-message"><div class="alert-destructive mb-4"><h2>Invalid email or password</h2></div></div>');
439
+ await stream.toast("Invalid email or password", "error");
448
440
  });
449
441
  }
450
442
  });
@@ -460,6 +452,217 @@ import { sse } from "./lib/sse.js";
460
452
  }
461
453
  return c.redirect("/");
462
454
  });
455
+ // Password reset via one-time token
456
+ const ResetContent = ({ token })=>{
457
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
458
+ const signals = JSON.stringify({
459
+ password: "",
460
+ confirmPassword: "",
461
+ token
462
+ }).replace(/</g, "\\u003c");
463
+ return /*#__PURE__*/ _jsx("div", {
464
+ class: "min-h-screen flex items-center justify-center",
465
+ children: /*#__PURE__*/ _jsxs("div", {
466
+ class: "card max-w-md w-full",
467
+ children: [
468
+ /*#__PURE__*/ _jsxs("header", {
469
+ children: [
470
+ /*#__PURE__*/ _jsx("h2", {
471
+ children: $__i18n._({
472
+ id: "KbS2K9",
473
+ message: "Reset Password"
474
+ })
475
+ }),
476
+ /*#__PURE__*/ _jsx("p", {
477
+ children: $__i18n._({
478
+ id: "hWOZIv",
479
+ message: "Enter your new password."
480
+ })
481
+ })
482
+ ]
483
+ }),
484
+ /*#__PURE__*/ _jsx("section", {
485
+ children: /*#__PURE__*/ _jsxs("form", {
486
+ "data-signals": signals,
487
+ "data-on:submit__prevent": "@post('/reset')",
488
+ class: "flex flex-col gap-4",
489
+ children: [
490
+ /*#__PURE__*/ _jsxs("div", {
491
+ class: "field",
492
+ children: [
493
+ /*#__PURE__*/ _jsx("label", {
494
+ class: "label",
495
+ children: $__i18n._({
496
+ id: "7vhWI8",
497
+ message: "New Password"
498
+ })
499
+ }),
500
+ /*#__PURE__*/ _jsx("input", {
501
+ type: "password",
502
+ "data-bind": "password",
503
+ class: "input",
504
+ required: true,
505
+ minLength: 8,
506
+ autocomplete: "new-password"
507
+ })
508
+ ]
509
+ }),
510
+ /*#__PURE__*/ _jsxs("div", {
511
+ class: "field",
512
+ children: [
513
+ /*#__PURE__*/ _jsx("label", {
514
+ class: "label",
515
+ children: $__i18n._({
516
+ id: "p2/GCq",
517
+ message: "Confirm Password"
518
+ })
519
+ }),
520
+ /*#__PURE__*/ _jsx("input", {
521
+ type: "password",
522
+ "data-bind": "confirmPassword",
523
+ class: "input",
524
+ required: true,
525
+ minLength: 8,
526
+ autocomplete: "new-password"
527
+ })
528
+ ]
529
+ }),
530
+ /*#__PURE__*/ _jsx("button", {
531
+ type: "submit",
532
+ class: "btn",
533
+ children: $__i18n._({
534
+ id: "KbS2K9",
535
+ message: "Reset Password"
536
+ })
537
+ })
538
+ ]
539
+ })
540
+ })
541
+ ]
542
+ })
543
+ });
544
+ };
545
+ const ResetErrorContent = ()=>{
546
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
547
+ return /*#__PURE__*/ _jsx("div", {
548
+ class: "min-h-screen flex items-center justify-center",
549
+ children: /*#__PURE__*/ _jsxs("div", {
550
+ class: "card max-w-md w-full",
551
+ children: [
552
+ /*#__PURE__*/ _jsx("header", {
553
+ children: /*#__PURE__*/ _jsx("h2", {
554
+ children: $__i18n._({
555
+ id: "7aECQB",
556
+ message: "Invalid or Expired Link"
557
+ })
558
+ })
559
+ }),
560
+ /*#__PURE__*/ _jsx("section", {
561
+ children: /*#__PURE__*/ _jsx("p", {
562
+ class: "text-muted-foreground",
563
+ children: $__i18n._({
564
+ id: "GbVAnd",
565
+ message: "This password reset link is invalid or has expired. Please generate a new one."
566
+ })
567
+ })
568
+ })
569
+ ]
570
+ })
571
+ });
572
+ };
573
+ app.get("/reset", async (c)=>{
574
+ const token = c.req.query("token");
575
+ if (!token) {
576
+ return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
577
+ title: "Reset Password - Jant",
578
+ c: c,
579
+ children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
580
+ }));
581
+ }
582
+ const stored = await c.var.services.settings.get(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
583
+ if (!stored) {
584
+ return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
585
+ title: "Reset Password - Jant",
586
+ c: c,
587
+ children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
588
+ }));
589
+ }
590
+ const separatorIndex = stored.lastIndexOf(":");
591
+ const storedToken = stored.substring(0, separatorIndex);
592
+ const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
593
+ const now = Math.floor(Date.now() / 1000);
594
+ if (token !== storedToken || now > expiry) {
595
+ return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
596
+ title: "Reset Password - Jant",
597
+ c: c,
598
+ children: /*#__PURE__*/ _jsx(ResetErrorContent, {})
599
+ }));
600
+ }
601
+ return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
602
+ title: "Reset Password - Jant",
603
+ c: c,
604
+ children: /*#__PURE__*/ _jsx(ResetContent, {
605
+ token: token
606
+ })
607
+ }));
608
+ });
609
+ app.post("/reset", async (c)=>{
610
+ const body = await c.req.json();
611
+ const { password, confirmPassword, token } = body;
612
+ // Validate token
613
+ const stored = await c.var.services.settings.get(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
614
+ if (!stored) {
615
+ return sse(c, async (stream)=>{
616
+ await stream.toast("Invalid or expired reset link.", "error");
617
+ });
618
+ }
619
+ const separatorIndex = stored.lastIndexOf(":");
620
+ const storedToken = stored.substring(0, separatorIndex);
621
+ const expiry = parseInt(stored.substring(separatorIndex + 1), 10);
622
+ const now = Math.floor(Date.now() / 1000);
623
+ if (token !== storedToken || now > expiry) {
624
+ return sse(c, async (stream)=>{
625
+ await stream.toast("Invalid or expired reset link.", "error");
626
+ });
627
+ }
628
+ // Validate passwords
629
+ if (!password || password.length < 8) {
630
+ return sse(c, async (stream)=>{
631
+ await stream.toast("Password must be at least 8 characters.", "error");
632
+ });
633
+ }
634
+ if (password !== confirmPassword) {
635
+ return sse(c, async (stream)=>{
636
+ await stream.toast("Passwords do not match.", "error");
637
+ });
638
+ }
639
+ try {
640
+ const hashedPassword = await hashPassword(password);
641
+ const db = c.env.DB.withSession();
642
+ // Get admin user
643
+ const userResult = await db.prepare("SELECT id FROM user LIMIT 1").first();
644
+ if (!userResult) {
645
+ return sse(c, async (stream)=>{
646
+ await stream.toast("No user account found.", "error");
647
+ });
648
+ }
649
+ // Update password
650
+ await db.prepare("UPDATE account SET password = ? WHERE user_id = ? AND provider_id = 'credential'").bind(hashedPassword, userResult.id).run();
651
+ // Delete all sessions
652
+ await db.prepare("DELETE FROM session WHERE user_id = ?").bind(userResult.id).run();
653
+ // Delete the reset token
654
+ await c.var.services.settings.remove(SETTINGS_KEYS.PASSWORD_RESET_TOKEN);
655
+ return sse(c, async (stream)=>{
656
+ await stream.redirect("/signin?reset");
657
+ });
658
+ } catch (err) {
659
+ // eslint-disable-next-line no-console -- Error logging is intentional
660
+ console.error("Password reset error:", err);
661
+ return sse(c, async (stream)=>{
662
+ await stream.toast("Failed to reset password.", "error");
663
+ });
664
+ }
665
+ });
463
666
  // Dashboard routes (protected)
464
667
  app.use("/dash/*", requireAuth());
465
668
  app.route("/dash", dashIndexRoutes);
@@ -469,6 +672,7 @@ import { sse } from "./lib/sse.js";
469
672
  app.route("/dash/settings", dashSettingsRoutes);
470
673
  app.route("/dash/redirects", dashRedirectsRoutes);
471
674
  app.route("/dash/collections", dashCollectionsRoutes);
675
+ app.route("/dash/appearance", dashAppearanceRoutes);
472
676
  // API routes
473
677
  app.route("/api/upload", uploadApiRoutes);
474
678
  app.route("/api/search", searchApiRoutes);
@@ -21,7 +21,7 @@ type TranslationDescriptor = {
21
21
  *
22
22
  * @example
23
23
  * ```tsx
24
- * import { I18nProvider } from "@/i18n";
24
+ * import { I18nProvider } from "../i18n/index.js";
25
25
  *
26
26
  * return c.html(
27
27
  * <I18nProvider c={c}>
@@ -41,7 +41,7 @@ export declare const I18nProvider: FC<I18nProviderProps>;
41
41
  * @example
42
42
  * ```tsx
43
43
  * import { t } from "@lingui/core/macro";
44
- * import { useLingui } from "@/i18n";
44
+ * import { useLingui } from "../i18n/index.js";
45
45
  *
46
46
  * function MyComponent() {
47
47
  * const { t: _ } = useLingui(); // Use _ to avoid conflict with macro
@@ -22,7 +22,7 @@ export const I18nProvider = ({ c, children })=>{
22
22
  * @example
23
23
  * ```tsx
24
24
  * import { t } from "@lingui/core/macro";
25
- * import { useLingui } from "@/i18n";
25
+ * import { useLingui } from "../i18n/index.js";
26
26
  *
27
27
  * function MyComponent() {
28
28
  * const { t: _ } = useLingui(); // Use _ to avoid conflict with macro
@@ -21,7 +21,7 @@ export declare function createI18n(locale: Locale): I18n;
21
21
  *
22
22
  * @example
23
23
  * import { msg } from "@lingui/core/macro";
24
- * import { getI18n } from "@/i18n";
24
+ * import { getI18n } from "../i18n/index.js";
25
25
  *
26
26
  * const i18n = getI18n(c);
27
27
  * const title = i18n._(msg({ message: "Dashboard", comment: "@context: Page title" }));
package/dist/i18n/i18n.js CHANGED
@@ -40,7 +40,7 @@ const catalogZhHant = {
40
40
  *
41
41
  * @example
42
42
  * import { msg } from "@lingui/core/macro";
43
- * import { getI18n } from "@/i18n";
43
+ * import { getI18n } from "../i18n/index.js";
44
44
  *
45
45
  * const i18n = getI18n(c);
46
46
  * const title = i18n._(msg({ message: "Dashboard", comment: "@context: Page title" }));
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Usage:
11
11
  * ```tsx
12
- * import { useLingui, Trans, I18nProvider } from "@/i18n";
12
+ * import { useLingui, Trans, I18nProvider } from "../i18n/index.js";
13
13
  *
14
14
  * // Wrap your app in I18nProvider (automatically done by BaseLayout when c is provided)
15
15
  * c.html(
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Usage:
11
11
  * ```tsx
12
- * import { useLingui, Trans, I18nProvider } from "@/i18n";
12
+ * import { useLingui, Trans, I18nProvider } from "../i18n/index.js";
13
13
  *
14
14
  * // Wrap your app in I18nProvider (automatically done by BaseLayout when c is provided)
15
15
  * c.html(
@@ -1 +1 @@
1
- {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../src/i18n/locales/en.ts"],"names":[],"mappings":"AAAkB,OAAO,KAAI,EAAC,QAAQ,EAAC,MAAI,cAAc,CAAC;AAAA,eAAO,MAAM,QAAQ,EAAg4I,QAAQ,CAAC"}
1
+ {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../src/i18n/locales/en.ts"],"names":[],"mappings":"AAAkB,OAAO,KAAI,EAAC,QAAQ,EAAC,MAAI,cAAc,CAAC;AAAA,eAAO,MAAM,QAAQ,EAAs1K,QAAQ,CAAC"}