@draftlab/auth 0.0.3 → 0.1.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.
Files changed (78) hide show
  1. package/dist/allow.d.ts +58 -1
  2. package/dist/allow.js +61 -2
  3. package/dist/client.d.ts +2 -3
  4. package/dist/client.js +2 -2
  5. package/dist/core.d.ts +128 -8
  6. package/dist/core.js +496 -12
  7. package/dist/error.d.ts +242 -1
  8. package/dist/error.js +235 -1
  9. package/dist/index.d.ts +1 -8
  10. package/dist/index.js +1 -12
  11. package/dist/keys.d.ts +1 -1
  12. package/dist/keys.js +138 -3
  13. package/dist/pkce.js +160 -1
  14. package/dist/provider/code.d.ts +227 -3
  15. package/dist/provider/code.js +27 -14
  16. package/dist/provider/facebook.d.ts +2 -3
  17. package/dist/provider/facebook.js +1 -5
  18. package/dist/provider/github.d.ts +2 -3
  19. package/dist/provider/github.js +1 -5
  20. package/dist/provider/google.d.ts +2 -3
  21. package/dist/provider/google.js +1 -5
  22. package/dist/provider/oauth2.d.ts +175 -3
  23. package/dist/provider/oauth2.js +153 -5
  24. package/dist/provider/password.d.ts +384 -3
  25. package/dist/provider/password.js +4 -4
  26. package/dist/provider/provider.d.ts +226 -2
  27. package/dist/random.js +85 -1
  28. package/dist/storage/memory.d.ts +2 -2
  29. package/dist/storage/memory.js +1 -1
  30. package/dist/storage/storage.d.ts +161 -1
  31. package/dist/storage/storage.js +60 -1
  32. package/dist/storage/turso.d.ts +1 -1
  33. package/dist/storage/turso.js +1 -1
  34. package/dist/storage/unstorage.d.ts +2 -2
  35. package/dist/storage/unstorage.js +2 -2
  36. package/dist/subject.d.ts +61 -2
  37. package/dist/themes/theme.d.ts +208 -1
  38. package/dist/themes/theme.js +118 -1
  39. package/dist/ui/base.d.ts +22 -35
  40. package/dist/ui/base.js +388 -3
  41. package/dist/ui/code.d.ts +22 -137
  42. package/dist/ui/code.js +199 -161
  43. package/dist/ui/form.d.ts +8 -6
  44. package/dist/ui/form.js +57 -1
  45. package/dist/ui/icon.d.ts +7 -84
  46. package/dist/ui/icon.js +69 -2
  47. package/dist/ui/password.d.ts +30 -37
  48. package/dist/ui/password.js +340 -237
  49. package/dist/ui/select.d.ts +19 -218
  50. package/dist/ui/select.js +91 -4
  51. package/dist/util.d.ts +71 -1
  52. package/dist/util.js +106 -1
  53. package/package.json +5 -3
  54. package/dist/allow-CixonwTW.d.ts +0 -59
  55. package/dist/allow-DX5cehSc.js +0 -63
  56. package/dist/base-DRutbxgL.js +0 -422
  57. package/dist/code-DJxdFR7p.d.ts +0 -212
  58. package/dist/core-BZHEAefX.d.ts +0 -129
  59. package/dist/core-CDM5o4rs.js +0 -498
  60. package/dist/error-CWAdNAzm.d.ts +0 -243
  61. package/dist/error-DgAKK7b2.js +0 -237
  62. package/dist/form-6XKM_cOk.js +0 -61
  63. package/dist/icon-Ci5uqGB_.js +0 -192
  64. package/dist/keys-EEfxEGfO.js +0 -140
  65. package/dist/oauth2-B7-6Z7Lc.js +0 -155
  66. package/dist/oauth2-CXHukHf2.d.ts +0 -176
  67. package/dist/password-C4KLmO0O.d.ts +0 -385
  68. package/dist/pkce-276Za_rZ.js +0 -162
  69. package/dist/provider-tndlqCzp.d.ts +0 -227
  70. package/dist/random-SXMYlaVr.js +0 -87
  71. package/dist/select-BjySLL8I.js +0 -280
  72. package/dist/storage-BEaqEPNQ.js +0 -62
  73. package/dist/storage-CxKerLlc.d.ts +0 -162
  74. package/dist/subject-DMIMVtaT.d.ts +0 -62
  75. package/dist/theme-C9by7VXf.d.ts +0 -209
  76. package/dist/theme-CswaLtbW.js +0 -120
  77. package/dist/util-CSdHUFOo.js +0 -108
  78. package/dist/util-DbSKG1Xm.d.ts +0 -72
@@ -1,11 +1,9 @@
1
- import "../theme-CswaLtbW.js";
2
- import { Layout } from "../base-DRutbxgL.js";
3
- import { FormAlert } from "../form-6XKM_cOk.js";
1
+ import { Layout, renderToHTML } from "./base.js";
2
+ import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
4
3
 
5
- //#region src/ui/password.ts
4
+ //#region src/ui/password.tsx
6
5
  /**
7
- * Default text copy for all password authentication UI screens.
8
- * All text can be customized via the copy prop.
6
+ * Default copy text for password authentication UI
9
7
  */
10
8
  const DEFAULT_COPY = {
11
9
  error_email_taken: "There is already an account with this email.",
@@ -33,7 +31,81 @@ const DEFAULT_COPY = {
33
31
  logo: "A"
34
32
  };
35
33
  /**
36
- * Creates a complete UI configuration for password-based authentication.
34
+ * FormAlert component for displaying error messages
35
+ */
36
+ const FormAlert = ({ message, color = "danger" }) => {
37
+ if (!message) return null;
38
+ return /* @__PURE__ */ jsxs("div", {
39
+ "data-component": "form-alert",
40
+ "data-color": color,
41
+ children: [/* @__PURE__ */ jsx("i", {
42
+ "data-slot": color === "success" ? "icon-success" : "icon-danger",
43
+ children: color === "success" ? /* @__PURE__ */ jsx("svg", {
44
+ fill: "none",
45
+ stroke: "currentColor",
46
+ viewBox: "0 0 24 24",
47
+ xmlns: "http://www.w3.org/2000/svg",
48
+ "aria-label": "Success",
49
+ role: "img",
50
+ children: /* @__PURE__ */ jsx("path", {
51
+ strokeLinecap: "round",
52
+ strokeLinejoin: "round",
53
+ strokeWidth: 2,
54
+ d: "M5 13l4 4L19 7"
55
+ })
56
+ }) : /* @__PURE__ */ jsx("svg", {
57
+ fill: "none",
58
+ stroke: "currentColor",
59
+ viewBox: "0 0 24 24",
60
+ xmlns: "http://www.w3.org/2000/svg",
61
+ "aria-label": "Error",
62
+ role: "img",
63
+ children: /* @__PURE__ */ jsx("path", {
64
+ strokeLinecap: "round",
65
+ strokeLinejoin: "round",
66
+ strokeWidth: 2,
67
+ d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.232 16.5c-.77.833.192 2.5 1.732 2.5z"
68
+ })
69
+ })
70
+ }), /* @__PURE__ */ jsx("span", {
71
+ "data-slot": "message",
72
+ children: message
73
+ })]
74
+ });
75
+ };
76
+ /**
77
+ * Input component with consistent styling
78
+ */
79
+ const Input = ({ type, name, placeholder, value, required, autoComplete,...props }) => /* @__PURE__ */ jsx("input", {
80
+ type,
81
+ name,
82
+ placeholder,
83
+ value,
84
+ required,
85
+ autoComplete,
86
+ "data-component": "input",
87
+ ...props
88
+ });
89
+ /**
90
+ * Button component with consistent styling
91
+ */
92
+ const Button = ({ type = "submit", children,...props }) => /* @__PURE__ */ jsx("button", {
93
+ type,
94
+ "data-component": "button",
95
+ ...props,
96
+ children
97
+ });
98
+ /**
99
+ * Link component with consistent styling
100
+ */
101
+ const Link = ({ href, children,...props }) => /* @__PURE__ */ jsx("a", {
102
+ href,
103
+ "data-component": "link",
104
+ ...props,
105
+ children
106
+ });
107
+ /**
108
+ * Creates a complete UI configuration for password-based authentication
37
109
  */
38
110
  const PasswordUI = (options) => {
39
111
  const copy = {
@@ -41,253 +113,284 @@ const PasswordUI = (options) => {
41
113
  ...options.copy
42
114
  };
43
115
  /**
44
- * Gets the appropriate error message for display.
116
+ * Gets the appropriate error message for display
45
117
  */
46
118
  const getErrorMessage = (error) => {
47
- if (!error?.type) return;
119
+ if (!error?.type) return void 0;
48
120
  if (error.type === "validation_error" && "message" in error && error.message) return error.message;
49
- return copy[`error_${error.type}`];
121
+ const errorKey = `error_${error.type}`;
122
+ return copy[errorKey];
123
+ };
124
+ /**
125
+ * Renders the login form with email and password inputs
126
+ */
127
+ const renderLogin = (form, error) => /* @__PURE__ */ jsx(Layout, { children: /* @__PURE__ */ jsxs("form", {
128
+ "data-component": "form",
129
+ method: "post",
130
+ children: [
131
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
132
+ /* @__PURE__ */ jsx(Input, {
133
+ type: "email",
134
+ name: "email",
135
+ placeholder: copy.input_email,
136
+ value: form?.get("email")?.toString() || "",
137
+ autoComplete: "email",
138
+ required: true
139
+ }),
140
+ /* @__PURE__ */ jsx(Input, {
141
+ type: "password",
142
+ name: "password",
143
+ placeholder: copy.input_password,
144
+ autoComplete: "current-password",
145
+ required: true
146
+ }),
147
+ /* @__PURE__ */ jsx(Button, {
148
+ type: "submit",
149
+ children: copy.button_continue
150
+ }),
151
+ /* @__PURE__ */ jsxs("div", {
152
+ "data-component": "form-footer",
153
+ children: [/* @__PURE__ */ jsxs("span", { children: [
154
+ copy.register_prompt,
155
+ " ",
156
+ /* @__PURE__ */ jsx(Link, {
157
+ href: "./register",
158
+ children: copy.register
159
+ })
160
+ ] }), /* @__PURE__ */ jsx(Link, {
161
+ href: "./change",
162
+ children: copy.change_prompt
163
+ })]
164
+ })
165
+ ]
166
+ }) });
167
+ /**
168
+ * Renders the registration form based on current state
169
+ */
170
+ const renderRegister = (state, form, error) => {
171
+ const emailError = ["invalid_email", "email_taken"].includes(error?.type || "");
172
+ const passwordError = [
173
+ "invalid_password",
174
+ "password_mismatch",
175
+ "validation_error"
176
+ ].includes(error?.type || "");
177
+ return /* @__PURE__ */ jsx(Layout, { children: state.type === "start" ? /* @__PURE__ */ jsxs("form", {
178
+ "data-component": "form",
179
+ method: "post",
180
+ children: [
181
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
182
+ /* @__PURE__ */ jsx("input", {
183
+ name: "action",
184
+ type: "hidden",
185
+ value: "register"
186
+ }),
187
+ /* @__PURE__ */ jsx(Input, {
188
+ type: "email",
189
+ name: "email",
190
+ placeholder: copy.input_email,
191
+ value: emailError ? "" : form?.get("email")?.toString() || "",
192
+ autoComplete: "email",
193
+ required: true
194
+ }),
195
+ /* @__PURE__ */ jsx(Input, {
196
+ type: "password",
197
+ name: "password",
198
+ placeholder: copy.input_password,
199
+ value: passwordError ? "" : form?.get("password")?.toString() || "",
200
+ autoComplete: "new-password",
201
+ required: true
202
+ }),
203
+ /* @__PURE__ */ jsx(Input, {
204
+ type: "password",
205
+ name: "repeat",
206
+ placeholder: copy.input_repeat,
207
+ autoComplete: "new-password",
208
+ required: true
209
+ }),
210
+ /* @__PURE__ */ jsx(Button, {
211
+ type: "submit",
212
+ children: copy.button_continue
213
+ }),
214
+ /* @__PURE__ */ jsx("div", {
215
+ "data-component": "form-footer",
216
+ children: /* @__PURE__ */ jsxs("span", { children: [
217
+ copy.login_prompt,
218
+ " ",
219
+ /* @__PURE__ */ jsx(Link, {
220
+ href: "./authorize",
221
+ children: copy.login
222
+ })
223
+ ] })
224
+ })
225
+ ]
226
+ }) : /* @__PURE__ */ jsxs("form", {
227
+ "data-component": "form",
228
+ method: "post",
229
+ children: [
230
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
231
+ /* @__PURE__ */ jsx("input", {
232
+ name: "action",
233
+ type: "hidden",
234
+ value: "verify"
235
+ }),
236
+ /* @__PURE__ */ jsx(Input, {
237
+ type: "text",
238
+ name: "code",
239
+ placeholder: copy.input_code,
240
+ "aria-label": "6-digit verification code",
241
+ autoComplete: "one-time-code",
242
+ inputMode: "numeric",
243
+ maxLength: 6,
244
+ minLength: 6,
245
+ pattern: "[0-9]{6}",
246
+ autoFocus: true,
247
+ required: true
248
+ }),
249
+ /* @__PURE__ */ jsx(Button, {
250
+ type: "submit",
251
+ children: copy.button_continue
252
+ })
253
+ ]
254
+ }) });
255
+ };
256
+ /**
257
+ * Renders the password change form based on current state
258
+ */
259
+ const renderChange = (state, form, error) => {
260
+ const passwordError = [
261
+ "invalid_password",
262
+ "password_mismatch",
263
+ "validation_error"
264
+ ].includes(error?.type || "");
265
+ return /* @__PURE__ */ jsx(Layout, { children: state.type === "start" ? /* @__PURE__ */ jsxs("form", {
266
+ "data-component": "form",
267
+ method: "post",
268
+ children: [
269
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
270
+ /* @__PURE__ */ jsx("input", {
271
+ name: "action",
272
+ type: "hidden",
273
+ value: "code"
274
+ }),
275
+ /* @__PURE__ */ jsx(Input, {
276
+ type: "email",
277
+ name: "email",
278
+ placeholder: copy.input_email,
279
+ value: form?.get("email")?.toString() || "",
280
+ autoComplete: "email",
281
+ required: true
282
+ }),
283
+ /* @__PURE__ */ jsx(Button, {
284
+ type: "submit",
285
+ children: copy.button_continue
286
+ })
287
+ ]
288
+ }) : state.type === "code" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("form", {
289
+ "data-component": "form",
290
+ method: "post",
291
+ children: [
292
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
293
+ /* @__PURE__ */ jsx("input", {
294
+ name: "action",
295
+ type: "hidden",
296
+ value: "verify"
297
+ }),
298
+ /* @__PURE__ */ jsx(Input, {
299
+ type: "text",
300
+ name: "code",
301
+ placeholder: copy.input_code,
302
+ "aria-label": "6-digit verification code",
303
+ autoComplete: "one-time-code",
304
+ inputMode: "numeric",
305
+ maxLength: 6,
306
+ minLength: 6,
307
+ pattern: "[0-9]{6}",
308
+ autoFocus: true,
309
+ required: true
310
+ }),
311
+ /* @__PURE__ */ jsx(Button, {
312
+ type: "submit",
313
+ children: copy.button_continue
314
+ })
315
+ ]
316
+ }), /* @__PURE__ */ jsxs("form", {
317
+ method: "post",
318
+ children: [
319
+ /* @__PURE__ */ jsx("input", {
320
+ name: "action",
321
+ type: "hidden",
322
+ value: "code"
323
+ }),
324
+ /* @__PURE__ */ jsx("input", {
325
+ name: "email",
326
+ type: "hidden",
327
+ value: state.email
328
+ }),
329
+ /* @__PURE__ */ jsxs("div", {
330
+ "data-component": "form-footer",
331
+ children: [/* @__PURE__ */ jsxs("span", { children: [
332
+ copy.code_return,
333
+ " ",
334
+ /* @__PURE__ */ jsx(Link, {
335
+ href: "./authorize",
336
+ children: copy.login.toLowerCase()
337
+ })
338
+ ] }), /* @__PURE__ */ jsx(Button, {
339
+ type: "submit",
340
+ "data-component": "link",
341
+ children: copy.code_resend
342
+ })]
343
+ })
344
+ ]
345
+ })] }) : /* @__PURE__ */ jsxs("form", {
346
+ "data-component": "form",
347
+ method: "post",
348
+ children: [
349
+ /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error) }),
350
+ /* @__PURE__ */ jsx("input", {
351
+ name: "action",
352
+ type: "hidden",
353
+ value: "update"
354
+ }),
355
+ /* @__PURE__ */ jsx(Input, {
356
+ type: "password",
357
+ name: "password",
358
+ placeholder: copy.input_password,
359
+ value: passwordError ? "" : form?.get("password")?.toString() || "",
360
+ autoComplete: "new-password",
361
+ required: true
362
+ }),
363
+ /* @__PURE__ */ jsx(Input, {
364
+ type: "password",
365
+ name: "repeat",
366
+ placeholder: copy.input_repeat,
367
+ value: passwordError ? "" : form?.get("repeat")?.toString() || "",
368
+ autoComplete: "new-password",
369
+ required: true
370
+ }),
371
+ /* @__PURE__ */ jsx(Button, {
372
+ type: "submit",
373
+ children: copy.button_continue
374
+ })
375
+ ]
376
+ }) });
50
377
  };
51
378
  return {
52
379
  validatePassword: options.validatePassword,
53
380
  sendCode: options.sendCode,
54
381
  login: async (_req, form, error) => {
55
- const formContent = `
56
- <form data-component="form" method="post">
57
- ${FormAlert({ message: getErrorMessage(error) })}
58
-
59
- <input
60
- autocomplete="email"
61
- data-component="input"
62
- value="${form?.get("email")?.toString() || ""}"
63
- name="email"
64
- placeholder="${copy.input_email}"
65
- required
66
- type="email"
67
- />
68
-
69
- <input
70
- autocomplete="current-password"
71
- data-component="input"
72
- name="password"
73
- placeholder="${copy.input_password}"
74
- required
75
- type="password"
76
- />
77
-
78
- <button data-component="button" type="submit">
79
- ${copy.button_continue}
80
- </button>
81
-
82
- <div data-component="form-footer">
83
- <span>
84
- ${copy.register_prompt}
85
- <a data-component="link" href="./register">
86
- ${copy.register}
87
- </a>
88
- </span>
89
- <a data-component="link" href="./change">
90
- ${copy.change_prompt}
91
- </a>
92
- </div>
93
- </form>
94
- `;
95
- const html = Layout({ children: formContent });
382
+ const html = renderToHTML(renderLogin(form, error));
96
383
  return new Response(html, {
97
384
  status: error ? 401 : 200,
98
385
  headers: { "Content-Type": "text/html" }
99
386
  });
100
387
  },
101
388
  register: async (_req, state, form, error) => {
102
- const emailError = ["invalid_email", "email_taken"].includes(error?.type || "");
103
- const passwordError = [
104
- "invalid_password",
105
- "password_mismatch",
106
- "validation_error"
107
- ].includes(error?.type || "");
108
- let formContent = "";
109
- if (state.type === "start") formContent = `
110
- <form data-component="form" method="post">
111
- ${FormAlert({ message: getErrorMessage(error) })}
112
-
113
- <input name="action" type="hidden" value="register" />
114
-
115
- <input
116
- autocomplete="email"
117
- data-component="input"
118
- value="${emailError ? "" : form?.get("email")?.toString() || ""}"
119
- name="email"
120
- placeholder="${copy.input_email}"
121
- required
122
- type="email"
123
- />
124
-
125
- <input
126
- autocomplete="new-password"
127
- data-component="input"
128
- value="${passwordError ? "" : form?.get("password")?.toString() || ""}"
129
- name="password"
130
- placeholder="${copy.input_password}"
131
- required
132
- type="password"
133
- />
134
-
135
- <input
136
- autocomplete="new-password"
137
- data-component="input"
138
- name="repeat"
139
- placeholder="${copy.input_repeat}"
140
- required
141
- type="password"
142
- />
143
-
144
- <button data-component="button" type="submit">
145
- ${copy.button_continue}
146
- </button>
147
-
148
- <div data-component="form-footer">
149
- <span>
150
- ${copy.login_prompt}
151
- <a data-component="link" href="./authorize">
152
- ${copy.login}
153
- </a>
154
- </span>
155
- </div>
156
- </form>
157
- `;
158
- else if (state.type === "code") formContent = `
159
- <form data-component="form" method="post">
160
- ${FormAlert({ message: getErrorMessage(error) })}
161
-
162
- <input name="action" type="hidden" value="verify" />
163
-
164
- <input
165
- aria-label="6-digit verification code"
166
- autocomplete="one-time-code"
167
- data-component="input"
168
- inputmode="numeric"
169
- maxlength="6"
170
- minlength="6"
171
- name="code"
172
- pattern="[0-9]{6}"
173
- placeholder="${copy.input_code}"
174
- required
175
- type="text"
176
- />
177
-
178
- <button data-component="button" type="submit">
179
- ${copy.button_continue}
180
- </button>
181
- </form>
182
- `;
183
- const html = Layout({ children: formContent });
389
+ const html = renderToHTML(renderRegister(state, form, error));
184
390
  return new Response(html, { headers: { "Content-Type": "text/html" } });
185
391
  },
186
392
  change: async (_req, state, form, error) => {
187
- const passwordError = [
188
- "invalid_password",
189
- "password_mismatch",
190
- "validation_error"
191
- ].includes(error?.type || "");
192
- let formContent = "";
193
- let additionalForms = "";
194
- if (state.type === "start") formContent = `
195
- <form data-component="form" method="post">
196
- ${FormAlert({ message: getErrorMessage(error) })}
197
-
198
- <input name="action" type="hidden" value="code" />
199
-
200
- <input
201
- autocomplete="email"
202
- data-component="input"
203
- value="${form?.get("email")?.toString() || ""}"
204
- name="email"
205
- placeholder="${copy.input_email}"
206
- required
207
- type="email"
208
- />
209
-
210
- <button data-component="button" type="submit">
211
- ${copy.button_continue}
212
- </button>
213
- </form>
214
- `;
215
- else if (state.type === "code") {
216
- formContent = `
217
- <form data-component="form" method="post">
218
- ${FormAlert({ message: getErrorMessage(error) })}
219
-
220
- <input name="action" type="hidden" value="verify" />
221
-
222
- <input
223
- aria-label="6-digit verification code"
224
- autocomplete="one-time-code"
225
- data-component="input"
226
- inputmode="numeric"
227
- maxlength="6"
228
- minlength="6"
229
- name="code"
230
- pattern="[0-9]{6}"
231
- placeholder="${copy.input_code}"
232
- required
233
- type="text"
234
- />
235
-
236
- <button data-component="button" type="submit">
237
- ${copy.button_continue}
238
- </button>
239
- </form>
240
- `;
241
- additionalForms = `
242
- <form method="post">
243
- <input name="action" type="hidden" value="code" />
244
- <input name="email" type="hidden" value="${state.email}" />
245
-
246
- <div data-component="form-footer">
247
- <span>
248
- ${copy.code_return}
249
- <a data-component="link" href="./authorize">
250
- ${copy.login.toLowerCase()}
251
- </a>
252
- </span>
253
- <button data-component="link" type="submit">
254
- ${copy.code_resend}
255
- </button>
256
- </div>
257
- </form>
258
- `;
259
- } else if (state.type === "update") formContent = `
260
- <form data-component="form" method="post">
261
- ${FormAlert({ message: getErrorMessage(error) })}
262
-
263
- <input name="action" type="hidden" value="update" />
264
-
265
- <input
266
- autocomplete="new-password"
267
- data-component="input"
268
- value="${passwordError ? "" : form?.get("password")?.toString() || ""}"
269
- name="password"
270
- placeholder="${copy.input_password}"
271
- required
272
- type="password"
273
- />
274
-
275
- <input
276
- autocomplete="new-password"
277
- data-component="input"
278
- value="${passwordError ? "" : form?.get("repeat")?.toString() || ""}"
279
- name="repeat"
280
- placeholder="${copy.input_repeat}"
281
- required
282
- type="password"
283
- />
284
-
285
- <button data-component="button" type="submit">
286
- ${copy.button_continue}
287
- </button>
288
- </form>
289
- `;
290
- const html = Layout({ children: formContent + additionalForms });
393
+ const html = renderToHTML(renderChange(state, form, error));
291
394
  return new Response(html, {
292
395
  status: error ? 400 : 200,
293
396
  headers: { "Content-Type": "text/html" }