@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
package/dist/ui/code.d.ts CHANGED
@@ -1,157 +1,42 @@
1
- import "../storage-CxKerLlc.js";
2
- import "../provider-tndlqCzp.js";
3
- import { CodeProviderError, CodeProviderOptions } from "../code-DJxdFR7p.js";
1
+ import { CodeProviderOptions } from "../provider/code.js";
4
2
 
5
3
  //#region src/ui/code.d.ts
6
4
 
7
5
  /**
8
- * Default text copy for the PIN code authentication UI.
9
- * All text can be customized via the copy prop.
6
+ * Type for customizable UI copy text
10
7
  */
11
- declare const DEFAULT_COPY: {
12
- /** Placeholder text for the email/contact input field */
13
- email_placeholder: string;
14
- /** Error message displayed when the entered email/contact is invalid */
15
- email_invalid: string;
16
- /** Text for the primary action button */
17
- button_continue: string;
18
- /** Informational text explaining that a PIN code will be sent */
19
- code_info: string;
20
- /** Placeholder text for the PIN code input field */
21
- code_placeholder: string;
22
- /** Error message displayed when the entered PIN code is incorrect */
23
- code_invalid: string;
24
- /** Success message prefix when code is initially sent */
25
- code_sent: string;
26
- /** Success message prefix when code is resent */
27
- code_resent: string;
28
- /** Text asking if user didn't receive the code */
29
- code_didnt_get: string;
30
- /** Text for the resend code button */
31
- code_resend: string;
32
- };
33
- /**
34
- * Type for customizable UI copy text.
35
- * All properties are optional to allow partial customization.
36
- */
37
- type CodeUICopy = typeof DEFAULT_COPY;
8
+ interface CodeUICopy {
9
+ readonly email_placeholder: string;
10
+ readonly email_invalid: string;
11
+ readonly button_continue: string;
12
+ readonly code_info: string;
13
+ readonly code_placeholder: string;
14
+ readonly code_invalid: string;
15
+ readonly code_sent: string;
16
+ readonly code_resent: string;
17
+ readonly code_didnt_get: string;
18
+ readonly code_resend: string;
19
+ }
38
20
  /**
39
- * Input mode for the contact field.
40
- * Determines the input type and validation behavior.
21
+ * Input mode for the contact field
41
22
  */
42
23
  type CodeUIMode = "email" | "phone";
43
24
  /**
44
- * Configuration options for the CodeUI component.
25
+ * Configuration options for the CodeUI component
45
26
  */
46
- interface CodeUIOptions {
27
+ interface CodeUIOptions extends Pick<CodeProviderOptions, "sendCode"> {
47
28
  /**
48
- * Callback function for sending PIN codes to users.
49
- * Should handle delivery via email, SMS, or other channels based on the claims.
50
- *
51
- * @param claims - User contact information (email, phone, etc.)
52
- * @param code - The generated PIN code to send
53
- * @returns Promise resolving to undefined on success, or error object on failure
54
- *
55
- * @example
56
- * ```ts
57
- * sendCode: async (claims, code) => {
58
- * if (claims.email) {
59
- * await emailService.send({
60
- * to: claims.email,
61
- * subject: 'Your verification code',
62
- * text: `Your PIN code is: ${code}`
63
- * })
64
- * } else if (claims.phone) {
65
- * await smsService.send(claims.phone, `PIN: ${code}`)
66
- * } else {
67
- * return {
68
- * type: "invalid_claim",
69
- * key: "contact",
70
- * value: "Email or phone required"
71
- * }
72
- * }
73
- * }
74
- * ```
29
+ * Input mode determining the type of contact information to collect
30
+ * @default "email"
75
31
  */
76
- sendCode: (claims: Record<string, string>, code: string) => Promise<CodeProviderError | undefined>;
32
+ readonly mode?: CodeUIMode;
77
33
  /**
78
- * Custom text copy for UI labels and messages.
79
- * Allows full customization of all displayed text.
80
- *
81
- * @example
82
- * ```ts
83
- * copy: {
84
- * email_placeholder: "Enter your email address",
85
- * code_info: "Check your email for a 6-digit verification code",
86
- * button_continue: "Verify",
87
- * code_invalid: "The code you entered is incorrect"
88
- * }
89
- * ```
34
+ * Custom text copy for UI labels, messages, and errors
90
35
  */
91
36
  readonly copy?: Partial<CodeUICopy>;
92
- /**
93
- * Input mode determining the type of contact information to collect.
94
- *
95
- * @default "email"
96
- *
97
- * @example
98
- * ```ts
99
- * mode: "phone" // Collect phone numbers instead of emails
100
- * ```
101
- */
102
- readonly mode?: CodeUIMode;
103
37
  }
104
38
  /**
105
- * Creates a complete UI configuration for PIN code authentication.
106
- * Provides pre-built forms for collecting user contact info and verifying PIN codes.
107
- *
108
- * @param options - Configuration options for the UI
109
- * @returns Complete CodeProvider configuration with UI handlers
110
- *
111
- * @example
112
- * ```ts
113
- * // Basic email-based PIN authentication
114
- * const emailCodeUI = CodeUI({
115
- * sendCode: async (claims, code) => {
116
- * await emailService.send(claims.email, `Code: ${code}`)
117
- * }
118
- * })
119
- *
120
- * // Phone-based PIN authentication with custom copy
121
- * const phoneCodeUI = CodeUI({
122
- * mode: "phone",
123
- * copy: {
124
- * email_placeholder: "Phone number",
125
- * code_info: "We'll send a verification code via SMS",
126
- * email_invalid: "Please enter a valid phone number"
127
- * },
128
- * sendCode: async (claims, code) => {
129
- * await smsService.send(claims.phone, `Verification: ${code}`)
130
- * }
131
- * })
132
- *
133
- * // Multi-mode authentication
134
- * const flexibleCodeUI = CodeUI({
135
- * copy: {
136
- * email_placeholder: "Email or phone number",
137
- * code_info: "We'll send a code to your email or phone"
138
- * },
139
- * sendCode: async (claims, code) => {
140
- * if (claims.email && claims.email.includes('@')) {
141
- * await emailService.send(claims.email, `Code: ${code}`)
142
- * } else if (claims.email) {
143
- * // Treat as phone number if no @ symbol
144
- * await smsService.send(claims.email, `Code: ${code}`)
145
- * } else {
146
- * return {
147
- * type: "invalid_claim",
148
- * key: "contact",
149
- * value: "Email or phone required"
150
- * }
151
- * }
152
- * }
153
- * })
154
- * ```
39
+ * Creates a complete UI configuration for PIN code authentication
155
40
  */
156
41
  declare const CodeUI: (options: CodeUIOptions) => CodeProviderOptions;
157
42
  //#endregion
package/dist/ui/code.js CHANGED
@@ -1,12 +1,9 @@
1
- import { UnknownStateError } from "../error-DgAKK7b2.js";
2
- import "../theme-CswaLtbW.js";
3
- import { Layout } from "../base-DRutbxgL.js";
4
- import { FormAlert } from "../form-6XKM_cOk.js";
1
+ import { Layout, renderToHTML } from "./base.js";
2
+ import { jsx, jsxs } from "preact/jsx-runtime";
5
3
 
6
- //#region src/ui/code.ts
4
+ //#region src/ui/code.tsx
7
5
  /**
8
- * Default text copy for the PIN code authentication UI.
9
- * All text can be customized via the copy prop.
6
+ * Default text copy for the PIN code authentication UI
10
7
  */
11
8
  const DEFAULT_COPY = {
12
9
  email_placeholder: "Email",
@@ -21,174 +18,215 @@ const DEFAULT_COPY = {
21
18
  code_resend: "Resend"
22
19
  };
23
20
  /**
24
- * Creates a complete UI configuration for PIN code authentication.
25
- * Provides pre-built forms for collecting user contact info and verifying PIN codes.
26
- *
27
- * @param options - Configuration options for the UI
28
- * @returns Complete CodeProvider configuration with UI handlers
29
- *
30
- * @example
31
- * ```ts
32
- * // Basic email-based PIN authentication
33
- * const emailCodeUI = CodeUI({
34
- * sendCode: async (claims, code) => {
35
- * await emailService.send(claims.email, `Code: ${code}`)
36
- * }
37
- * })
38
- *
39
- * // Phone-based PIN authentication with custom copy
40
- * const phoneCodeUI = CodeUI({
41
- * mode: "phone",
42
- * copy: {
43
- * email_placeholder: "Phone number",
44
- * code_info: "We'll send a verification code via SMS",
45
- * email_invalid: "Please enter a valid phone number"
46
- * },
47
- * sendCode: async (claims, code) => {
48
- * await smsService.send(claims.phone, `Verification: ${code}`)
49
- * }
50
- * })
51
- *
52
- * // Multi-mode authentication
53
- * const flexibleCodeUI = CodeUI({
54
- * copy: {
55
- * email_placeholder: "Email or phone number",
56
- * code_info: "We'll send a code to your email or phone"
57
- * },
58
- * sendCode: async (claims, code) => {
59
- * if (claims.email && claims.email.includes('@')) {
60
- * await emailService.send(claims.email, `Code: ${code}`)
61
- * } else if (claims.email) {
62
- * // Treat as phone number if no @ symbol
63
- * await smsService.send(claims.email, `Code: ${code}`)
64
- * } else {
65
- * return {
66
- * type: "invalid_claim",
67
- * key: "contact",
68
- * value: "Email or phone required"
69
- * }
70
- * }
71
- * }
72
- * })
73
- * ```
21
+ * FormAlert component for displaying messages
22
+ */
23
+ const FormAlert = ({ message, color = "danger" }) => {
24
+ if (!message) return null;
25
+ return /* @__PURE__ */ jsxs("div", {
26
+ "data-component": "form-alert",
27
+ "data-color": color,
28
+ children: [/* @__PURE__ */ jsx("i", {
29
+ "data-slot": color === "success" ? "icon-success" : "icon-danger",
30
+ children: color === "success" ? /* @__PURE__ */ jsx("svg", {
31
+ fill: "none",
32
+ stroke: "currentColor",
33
+ viewBox: "0 0 24 24",
34
+ xmlns: "http://www.w3.org/2000/svg",
35
+ "aria-label": "Success",
36
+ role: "img",
37
+ children: /* @__PURE__ */ jsx("path", {
38
+ strokeLinecap: "round",
39
+ strokeLinejoin: "round",
40
+ strokeWidth: 2,
41
+ d: "M5 13l4 4L19 7"
42
+ })
43
+ }) : /* @__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": "Error",
49
+ role: "img",
50
+ children: /* @__PURE__ */ jsx("path", {
51
+ strokeLinecap: "round",
52
+ strokeLinejoin: "round",
53
+ strokeWidth: 2,
54
+ 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"
55
+ })
56
+ })
57
+ }), /* @__PURE__ */ jsx("span", {
58
+ "data-slot": "message",
59
+ children: message
60
+ })]
61
+ });
62
+ };
63
+ /**
64
+ * Input component with consistent styling
65
+ */
66
+ const Input = ({ type, name, placeholder, value, required, autoComplete, autoFocus,...props }) => /* @__PURE__ */ jsx("input", {
67
+ type,
68
+ name,
69
+ placeholder,
70
+ value,
71
+ required,
72
+ autoComplete,
73
+ "data-component": "input",
74
+ ...props
75
+ });
76
+ /**
77
+ * Button component with consistent styling
78
+ */
79
+ const Button = ({ type = "submit", children,...props }) => /* @__PURE__ */ jsx("button", {
80
+ type,
81
+ "data-component": "button",
82
+ ...props,
83
+ children
84
+ });
85
+ /**
86
+ * Link component with consistent styling
87
+ */
88
+ /**
89
+ * Gets the appropriate error message for display
90
+ */
91
+ const getErrorMessage = (error, copy) => {
92
+ if (!error?.type) return void 0;
93
+ switch (error.type) {
94
+ case "invalid_code": return copy.code_invalid;
95
+ case "invalid_claim": return copy.email_invalid;
96
+ }
97
+ };
98
+ /**
99
+ * Gets the appropriate success message for display
100
+ */
101
+ const getSuccessMessage = (state, copy) => {
102
+ if (state.type === "start" || !state.claims) return void 0;
103
+ const contact = state.claims.email || state.claims.phone || "";
104
+ const prefix = state.resend ? copy.code_resent : copy.code_sent;
105
+ return {
106
+ message: `${prefix}${contact}`,
107
+ contact
108
+ };
109
+ };
110
+ /**
111
+ * Creates a complete UI configuration for PIN code authentication
74
112
  */
75
113
  const CodeUI = (options) => {
76
114
  const copy = {
77
115
  ...DEFAULT_COPY,
78
116
  ...options.copy
79
117
  };
80
- const inputMode = options.mode ?? "email";
118
+ const mode = options.mode || "email";
81
119
  /**
82
- * Determines the appropriate input field attributes based on the selected mode.
120
+ * Renders the start form for collecting contact information
83
121
  */
84
- const getInputAttributes = () => {
85
- switch (inputMode) {
86
- case "email": return {
87
- type: "email",
88
- name: "email",
89
- inputmode: "email",
90
- autocomplete: "email"
91
- };
92
- case "phone": return {
93
- type: "tel",
94
- name: "phone",
95
- inputmode: "tel",
96
- autocomplete: "tel"
97
- };
98
- }
122
+ const renderStart = (form, error, state) => {
123
+ const success = getSuccessMessage(state || { type: "start" }, copy);
124
+ return /* @__PURE__ */ jsx(Layout, { children: /* @__PURE__ */ jsxs("form", {
125
+ "data-component": "form",
126
+ method: "post",
127
+ children: [
128
+ success ? /* @__PURE__ */ jsx(FormAlert, {
129
+ message: success.message,
130
+ color: "success"
131
+ }) : /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) }),
132
+ /* @__PURE__ */ jsx(Input, {
133
+ type: mode === "email" ? "email" : "tel",
134
+ name: mode,
135
+ placeholder: copy.email_placeholder,
136
+ value: form?.get(mode)?.toString() || "",
137
+ autoComplete: mode,
138
+ required: true
139
+ }),
140
+ /* @__PURE__ */ jsx("input", {
141
+ type: "hidden",
142
+ name: "action",
143
+ value: "request"
144
+ }),
145
+ /* @__PURE__ */ jsx(Button, {
146
+ type: "submit",
147
+ children: copy.button_continue
148
+ }),
149
+ /* @__PURE__ */ jsx("p", {
150
+ style: {
151
+ fontSize: "0.875rem",
152
+ color: "var(--color-high)",
153
+ textAlign: "center",
154
+ margin: "1rem 0 0 0"
155
+ },
156
+ children: copy.code_info
157
+ })
158
+ ]
159
+ }) });
99
160
  };
100
161
  /**
101
- * Gets the appropriate contact value from claims for display purposes.
162
+ * Renders the code verification form
102
163
  */
103
- const getContactValue = (claims) => {
104
- return claims.email || claims.phone || Object.values(claims)[0] || "";
164
+ const renderCode = (_form, error, state) => {
165
+ const success = getSuccessMessage(state, copy);
166
+ const contact = state.type === "code" ? state.claims?.[mode] || "" : "";
167
+ return /* @__PURE__ */ jsxs(Layout, { children: [/* @__PURE__ */ jsxs("form", {
168
+ "data-component": "form",
169
+ method: "post",
170
+ children: [
171
+ success ? /* @__PURE__ */ jsx(FormAlert, {
172
+ message: success.message,
173
+ color: "success"
174
+ }) : /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) }),
175
+ /* @__PURE__ */ jsx("input", {
176
+ name: "action",
177
+ type: "hidden",
178
+ value: "verify"
179
+ }),
180
+ /* @__PURE__ */ jsx(Input, {
181
+ type: "text",
182
+ name: "code",
183
+ placeholder: copy.code_placeholder,
184
+ "aria-label": "6-digit verification code",
185
+ autoComplete: "one-time-code",
186
+ inputMode: "numeric",
187
+ maxLength: 6,
188
+ minLength: 6,
189
+ pattern: "[0-9]{6}",
190
+ autoFocus: true,
191
+ required: true
192
+ }),
193
+ /* @__PURE__ */ jsx(Button, {
194
+ type: "submit",
195
+ children: copy.button_continue
196
+ })
197
+ ]
198
+ }), /* @__PURE__ */ jsxs("form", {
199
+ method: "post",
200
+ children: [
201
+ /* @__PURE__ */ jsx("input", {
202
+ name: "action",
203
+ type: "hidden",
204
+ value: "resend"
205
+ }),
206
+ /* @__PURE__ */ jsx("input", {
207
+ name: mode,
208
+ type: "hidden",
209
+ value: contact
210
+ }),
211
+ /* @__PURE__ */ jsxs("div", {
212
+ "data-component": "form-footer",
213
+ children: [/* @__PURE__ */ jsx("span", {
214
+ style: { fontSize: "0.875rem" },
215
+ children: copy.code_didnt_get
216
+ }), /* @__PURE__ */ jsx(Button, {
217
+ type: "submit",
218
+ "data-component": "link",
219
+ children: copy.code_resend
220
+ })]
221
+ })
222
+ ]
223
+ })] });
105
224
  };
106
225
  return {
107
226
  sendCode: options.sendCode,
108
- length: 6,
109
- request: async (_req, state, _form, error) => {
110
- if (state.type === "start") {
111
- const inputAttrs = getInputAttributes();
112
- const formContent = `
113
- <form data-component="form" method="post">
114
- ${error?.type === "invalid_claim" ? FormAlert({ message: copy.email_invalid }) : ""}
115
-
116
- <input name="action" type="hidden" value="request" />
117
-
118
- <input
119
- autofocus
120
- data-component="input"
121
- placeholder="${copy.email_placeholder}"
122
- required
123
- type="${inputAttrs.type}"
124
- name="${inputAttrs.name}"
125
- inputmode="${inputAttrs.inputmode}"
126
- autocomplete="${inputAttrs.autocomplete}"
127
- />
128
-
129
- <button data-component="button" type="submit">
130
- ${copy.button_continue}
131
- </button>
132
- </form>
133
-
134
- <p data-component="form-footer">${copy.code_info}</p>
135
- `;
136
- const html = Layout({ children: formContent });
137
- return new Response(html, { headers: { "Content-Type": "text/html" } });
138
- }
139
- if (state.type === "code") {
140
- const contactValue = getContactValue(state.claims);
141
- const hiddenInputs = Object.entries(state.claims).map(([key, value]) => `<input name="${key}" type="hidden" value="${value}" />`).join("");
142
- const formContent = `
143
- <form data-component="form" method="post">
144
- ${error?.type === "invalid_code" ? FormAlert({ message: copy.code_invalid }) : ""}
145
-
146
- ${FormAlert({
147
- color: "success",
148
- message: (state.resend ? copy.code_resent : copy.code_sent) + contactValue
149
- })}
150
-
151
- <input name="action" type="hidden" value="verify" />
152
-
153
- <input
154
- aria-label="6-digit verification code"
155
- autocomplete="one-time-code"
156
- autofocus
157
- data-component="input"
158
- inputmode="numeric"
159
- maxlength="6"
160
- minlength="6"
161
- name="code"
162
- pattern="[0-9]{6}"
163
- placeholder="${copy.code_placeholder}"
164
- required
165
- type="text"
166
- />
167
-
168
- <button data-component="button" type="submit">
169
- ${copy.button_continue}
170
- </button>
171
- </form>
172
-
173
- <form method="post">
174
- ${hiddenInputs}
175
-
176
- <input name="action" type="hidden" value="resend" />
177
-
178
- <div data-component="form-footer">
179
- <span>
180
- ${copy.code_didnt_get}
181
- <button data-component="link" type="submit">
182
- ${copy.code_resend}
183
- </button>
184
- </span>
185
- </div>
186
- </form>
187
- `;
188
- const html = Layout({ children: formContent });
189
- return new Response(html, { headers: { "Content-Type": "text/html" } });
190
- }
191
- throw new UnknownStateError();
227
+ request: async (_req, state, form, error) => {
228
+ const html = renderToHTML(state.type === "start" ? renderStart(form, error, state) : renderCode(form, error, state));
229
+ return new Response(html, { headers: { "Content-Type": "text/html" } });
192
230
  }
193
231
  };
194
232
  };
package/dist/ui/form.d.ts CHANGED
@@ -1,8 +1,7 @@
1
+ import { ComponentChildren } from "preact";
2
+
1
3
  //#region src/ui/form.d.ts
2
- /**
3
- * Form alert component for displaying success and error messages.
4
- * Provides consistent styling and iconography for user feedback in authentication forms.
5
- */
4
+
6
5
  /**
7
6
  * Alert color variant determining the visual style and icon.
8
7
  */
@@ -24,8 +23,11 @@ interface FormAlertProps {
24
23
  }
25
24
  /**
26
25
  * Form alert component that displays error or success messages.
27
- * Returns an HTML string for the alert or empty string if no message.
26
+ * Returns a Preact component or null if no message.
28
27
  */
29
- declare const FormAlert: (props: FormAlertProps) => string;
28
+ declare const FormAlert: ({
29
+ message,
30
+ color
31
+ }: FormAlertProps) => ComponentChildren;
30
32
  //#endregion
31
33
  export { FormAlert, FormAlertColor, FormAlertProps };
package/dist/ui/form.js CHANGED
@@ -1,3 +1,59 @@
1
- import { FormAlert } from "../form-6XKM_cOk.js";
1
+ import { jsx, jsxs } from "preact/jsx-runtime";
2
2
 
3
+ //#region src/ui/form.tsx
4
+ /**
5
+ * Success icon component showing a checkmark in a circle.
6
+ * Used for positive feedback messages.
7
+ */
8
+ const SuccessIcon = () => /* @__PURE__ */ jsx("svg", {
9
+ "aria-hidden": "true",
10
+ "data-slot": "icon-success",
11
+ fill: "none",
12
+ stroke: "currentColor",
13
+ strokeWidth: "1.5",
14
+ viewBox: "0 0 24 24",
15
+ xmlns: "http://www.w3.org/2000/svg",
16
+ children: /* @__PURE__ */ jsx("path", {
17
+ d: "M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z",
18
+ strokeLinecap: "round",
19
+ strokeLinejoin: "round"
20
+ })
21
+ });
22
+ /**
23
+ * Danger icon component showing an exclamation mark in a circle.
24
+ * Used for error and warning messages.
25
+ */
26
+ const DangerIcon = () => /* @__PURE__ */ jsx("svg", {
27
+ "aria-hidden": "true",
28
+ "data-slot": "icon-danger",
29
+ fill: "none",
30
+ stroke: "currentColor",
31
+ strokeWidth: "1.5",
32
+ viewBox: "0 0 24 24",
33
+ xmlns: "http://www.w3.org/2000/svg",
34
+ children: /* @__PURE__ */ jsx("path", {
35
+ d: "M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z",
36
+ strokeLinecap: "round",
37
+ strokeLinejoin: "round"
38
+ })
39
+ });
40
+ /**
41
+ * Form alert component that displays error or success messages.
42
+ * Returns a Preact component or null if no message.
43
+ */
44
+ const FormAlert = ({ message, color = "danger" }) => {
45
+ if (!message) return null;
46
+ return /* @__PURE__ */ jsxs("div", {
47
+ "aria-live": "polite",
48
+ "data-color": color,
49
+ "data-component": "form-alert",
50
+ role: "alert",
51
+ children: [color === "success" ? /* @__PURE__ */ jsx(SuccessIcon, {}) : /* @__PURE__ */ jsx(DangerIcon, {}), /* @__PURE__ */ jsx("span", {
52
+ "data-slot": "message",
53
+ children: message
54
+ })]
55
+ });
56
+ };
57
+
58
+ //#endregion
3
59
  export { FormAlert };