@usethrottle/auth 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.
@@ -0,0 +1,354 @@
1
+ import {
2
+ useAuth
3
+ } from "../../chunk-YHIGTY64.js";
4
+ import "../../chunk-NQU7HM23.js";
5
+
6
+ // src/react/forms/SignIn.tsx
7
+ import { useId, useState as useState2 } from "react";
8
+
9
+ // src/react/forms/useFormAction.ts
10
+ import { useCallback, useRef, useState } from "react";
11
+
12
+ // src/react/forms/messages.ts
13
+ function waitCopy(seconds) {
14
+ if (!seconds || !Number.isFinite(seconds)) return "Please wait a moment and try again.";
15
+ if (seconds < 60) return `Please wait ${Math.ceil(seconds)} seconds and try again.`;
16
+ const minutes = Math.ceil(seconds / 60);
17
+ return `Please wait ${minutes} ${minutes === 1 ? "minute" : "minutes"} and try again.`;
18
+ }
19
+ function messageForError(err) {
20
+ const e = err;
21
+ switch (e?.code) {
22
+ case "invalid_credentials":
23
+ return "That email or password is not correct.";
24
+ case "weak_password":
25
+ return e.message || "Please choose a stronger password.";
26
+ case "too_many_attempts":
27
+ return `Too many attempts. ${waitCopy(e.retryAfter)}`;
28
+ case "verification_required":
29
+ return "Please verify your email address to continue.";
30
+ case "step_up_required":
31
+ return "Please re-enter your password to continue.";
32
+ case "session_revoked":
33
+ return "Your session has ended. Please sign in again.";
34
+ case "token_expired":
35
+ case "token_invalid":
36
+ case "token_already_used":
37
+ return "That link is no longer valid. Please request a new link.";
38
+ case "auth_unavailable":
39
+ return "Accounts are briefly unavailable. Please try again shortly.";
40
+ case "origin_not_allowed":
41
+ case "auth_not_enabled":
42
+ case "invalid_api_key":
43
+ case "environment_mismatch":
44
+ return "Accounts are not available right now. Please contact support.";
45
+ case "cart_already_claimed":
46
+ return "That cart belongs to a different account.";
47
+ case "not_found":
48
+ return "We could not find that.";
49
+ default:
50
+ return "Something went wrong. Please try again.";
51
+ }
52
+ }
53
+
54
+ // src/react/forms/useFormAction.ts
55
+ function useFormAction() {
56
+ const [submitting, setSubmitting] = useState(false);
57
+ const [error, setError] = useState(null);
58
+ const [notice, setNotice] = useState(null);
59
+ const inFlight = useRef(false);
60
+ const run = useCallback(async (fn) => {
61
+ if (inFlight.current) return;
62
+ inFlight.current = true;
63
+ setSubmitting(true);
64
+ setError(null);
65
+ setNotice(null);
66
+ try {
67
+ const message = await fn();
68
+ if (typeof message === "string") setNotice(message);
69
+ } catch (err) {
70
+ setError(messageForError(err));
71
+ } finally {
72
+ inFlight.current = false;
73
+ setSubmitting(false);
74
+ }
75
+ }, []);
76
+ return { submitting, error, notice, run };
77
+ }
78
+
79
+ // src/react/forms/SignIn.tsx
80
+ import { jsx, jsxs } from "react/jsx-runtime";
81
+ function SignInForm(props) {
82
+ const { auth } = useAuth();
83
+ const { submitting, error, run } = useFormAction();
84
+ const [email, setEmail] = useState2("");
85
+ const [password, setPassword] = useState2("");
86
+ const ids = { email: `${useId()}-email`, password: `${useId()}-password` };
87
+ const l = { email: "Email", password: "Password", submit: "Sign in", forgot: "Forgot your password?", ...props.labels };
88
+ return /* @__PURE__ */ jsxs(
89
+ "form",
90
+ {
91
+ "data-testid": "throttle-auth-signin",
92
+ className: props.className ?? "throttle-auth-form",
93
+ onSubmit: (e) => {
94
+ e.preventDefault();
95
+ void run(async () => {
96
+ await auth.signIn(email, password);
97
+ props.onSuccess?.();
98
+ });
99
+ },
100
+ children: [
101
+ /* @__PURE__ */ jsx("label", { className: "throttle-auth-label", htmlFor: ids.email, children: l.email }),
102
+ /* @__PURE__ */ jsx(
103
+ "input",
104
+ {
105
+ className: "throttle-auth-input",
106
+ id: ids.email,
107
+ type: "email",
108
+ autoComplete: "email",
109
+ value: email,
110
+ onChange: (e) => setEmail(e.target.value),
111
+ required: true
112
+ }
113
+ ),
114
+ /* @__PURE__ */ jsx("label", { className: "throttle-auth-label", htmlFor: ids.password, children: l.password }),
115
+ /* @__PURE__ */ jsx(
116
+ "input",
117
+ {
118
+ className: "throttle-auth-input",
119
+ id: ids.password,
120
+ type: "password",
121
+ autoComplete: "current-password",
122
+ value: password,
123
+ onChange: (e) => setPassword(e.target.value),
124
+ required: true
125
+ }
126
+ ),
127
+ error && /* @__PURE__ */ jsx("p", { className: "throttle-auth-error", role: "alert", children: error }),
128
+ /* @__PURE__ */ jsx("button", { className: "throttle-auth-submit", type: "submit", disabled: submitting, children: submitting ? "\u2026" : l.submit }),
129
+ props.onForgotPassword && /* @__PURE__ */ jsx("button", { className: "throttle-auth-link", type: "button", onClick: props.onForgotPassword, children: l.forgot })
130
+ ]
131
+ }
132
+ );
133
+ }
134
+
135
+ // src/react/forms/SignUp.tsx
136
+ import { useId as useId2, useState as useState3 } from "react";
137
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
138
+ function SignUpForm(props) {
139
+ const { auth } = useAuth();
140
+ const { submitting, error, notice, run } = useFormAction();
141
+ const [email, setEmail] = useState3("");
142
+ const [password, setPassword] = useState3("");
143
+ const ids = { email: `${useId2()}-email`, password: `${useId2()}-password` };
144
+ const l = {
145
+ email: "Email",
146
+ password: "Password",
147
+ submit: "Create account",
148
+ pending: "Check your email \u2014 we sent you a link to finish signing in.",
149
+ ...props.labels
150
+ };
151
+ return /* @__PURE__ */ jsxs2(
152
+ "form",
153
+ {
154
+ "data-testid": "throttle-auth-signup",
155
+ className: props.className ?? "throttle-auth-form",
156
+ onSubmit: (e) => {
157
+ e.preventDefault();
158
+ void run(async () => {
159
+ const result = await auth.register(email, password);
160
+ if (result.status === "pending") {
161
+ props.onPending?.();
162
+ return l.pending;
163
+ }
164
+ props.onSuccess?.();
165
+ });
166
+ },
167
+ children: [
168
+ /* @__PURE__ */ jsx2("label", { className: "throttle-auth-label", htmlFor: ids.email, children: l.email }),
169
+ /* @__PURE__ */ jsx2(
170
+ "input",
171
+ {
172
+ className: "throttle-auth-input",
173
+ id: ids.email,
174
+ type: "email",
175
+ autoComplete: "email",
176
+ value: email,
177
+ onChange: (e) => setEmail(e.target.value),
178
+ required: true
179
+ }
180
+ ),
181
+ /* @__PURE__ */ jsx2("label", { className: "throttle-auth-label", htmlFor: ids.password, children: l.password }),
182
+ /* @__PURE__ */ jsx2(
183
+ "input",
184
+ {
185
+ className: "throttle-auth-input",
186
+ id: ids.password,
187
+ type: "password",
188
+ autoComplete: "new-password",
189
+ value: password,
190
+ onChange: (e) => setPassword(e.target.value),
191
+ required: true
192
+ }
193
+ ),
194
+ error && /* @__PURE__ */ jsx2("p", { className: "throttle-auth-error", role: "alert", children: error }),
195
+ notice && /* @__PURE__ */ jsx2("p", { className: "throttle-auth-notice", role: "status", children: notice }),
196
+ /* @__PURE__ */ jsx2("button", { className: "throttle-auth-submit", type: "submit", disabled: submitting, children: submitting ? "\u2026" : l.submit })
197
+ ]
198
+ }
199
+ );
200
+ }
201
+
202
+ // src/react/forms/ForgotPassword.tsx
203
+ import { useId as useId3, useState as useState4 } from "react";
204
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
205
+ function ForgotPasswordForm(props) {
206
+ const { auth } = useAuth();
207
+ const { submitting, error, notice, run } = useFormAction();
208
+ const [email, setEmail] = useState4("");
209
+ const id = `${useId3()}-email`;
210
+ const l = {
211
+ email: "Email",
212
+ submit: "Send reset link",
213
+ // Identical copy whether or not the address has an account — the API is
214
+ // deliberately silent on that and this form must be too.
215
+ sent: "If an account exists for that address, we have sent a reset link.",
216
+ ...props.labels
217
+ };
218
+ return /* @__PURE__ */ jsxs3(
219
+ "form",
220
+ {
221
+ "data-testid": "throttle-auth-forgot",
222
+ className: props.className ?? "throttle-auth-form",
223
+ onSubmit: (e) => {
224
+ e.preventDefault();
225
+ void run(async () => {
226
+ await auth.forgotPassword(email);
227
+ props.onSent?.();
228
+ return l.sent;
229
+ });
230
+ },
231
+ children: [
232
+ /* @__PURE__ */ jsx3("label", { className: "throttle-auth-label", htmlFor: id, children: l.email }),
233
+ /* @__PURE__ */ jsx3(
234
+ "input",
235
+ {
236
+ className: "throttle-auth-input",
237
+ id,
238
+ type: "email",
239
+ autoComplete: "email",
240
+ value: email,
241
+ onChange: (e) => setEmail(e.target.value),
242
+ required: true
243
+ }
244
+ ),
245
+ error && /* @__PURE__ */ jsx3("p", { className: "throttle-auth-error", role: "alert", children: error }),
246
+ notice && /* @__PURE__ */ jsx3("p", { className: "throttle-auth-notice", role: "status", children: notice }),
247
+ /* @__PURE__ */ jsx3("button", { className: "throttle-auth-submit", type: "submit", disabled: submitting, children: submitting ? "\u2026" : l.submit })
248
+ ]
249
+ }
250
+ );
251
+ }
252
+
253
+ // src/react/forms/ResetPassword.tsx
254
+ import { useId as useId4, useState as useState5 } from "react";
255
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
256
+ function ResetPasswordForm(props) {
257
+ const { auth } = useAuth();
258
+ const { submitting, error, notice, run } = useFormAction();
259
+ const [password, setPassword] = useState5("");
260
+ const id = `${useId4()}-new-password`;
261
+ const l = {
262
+ password: "New password",
263
+ submit: "Set new password",
264
+ done: "Your password has been reset. Please sign in with your new password.",
265
+ ...props.labels
266
+ };
267
+ return /* @__PURE__ */ jsxs4(
268
+ "form",
269
+ {
270
+ "data-testid": "throttle-auth-reset",
271
+ className: props.className ?? "throttle-auth-form",
272
+ onSubmit: (e) => {
273
+ e.preventDefault();
274
+ void run(async () => {
275
+ await auth.resetPassword(props.token, password);
276
+ props.onSuccess?.();
277
+ return l.done;
278
+ });
279
+ },
280
+ children: [
281
+ /* @__PURE__ */ jsx4("label", { className: "throttle-auth-label", htmlFor: id, children: l.password }),
282
+ /* @__PURE__ */ jsx4(
283
+ "input",
284
+ {
285
+ className: "throttle-auth-input",
286
+ id,
287
+ type: "password",
288
+ autoComplete: "new-password",
289
+ value: password,
290
+ onChange: (e) => setPassword(e.target.value),
291
+ required: true
292
+ }
293
+ ),
294
+ error && /* @__PURE__ */ jsx4("p", { className: "throttle-auth-error", role: "alert", children: error }),
295
+ notice && /* @__PURE__ */ jsx4("p", { className: "throttle-auth-notice", role: "status", children: notice }),
296
+ /* @__PURE__ */ jsx4("button", { className: "throttle-auth-submit", type: "submit", disabled: submitting, children: submitting ? "\u2026" : l.submit })
297
+ ]
298
+ }
299
+ );
300
+ }
301
+
302
+ // src/react/forms/VerifyEmail.tsx
303
+ import { useEffect, useRef as useRef2 } from "react";
304
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
305
+ function VerifyEmailPanel(props) {
306
+ const { auth, emailVerified } = useAuth();
307
+ const { submitting, error, notice, run } = useFormAction();
308
+ const attempted = useRef2(null);
309
+ const l = {
310
+ verifying: "Verifying your email\u2026",
311
+ verified: "Your email address is verified.",
312
+ prompt: "Please verify your email address to unlock your full account.",
313
+ resend: "Resend verification email",
314
+ resent: "Sent. Check your inbox.",
315
+ ...props.labels
316
+ };
317
+ useEffect(() => {
318
+ if (!props.token || attempted.current === props.token) return;
319
+ attempted.current = props.token;
320
+ void run(async () => {
321
+ await auth.verifyEmail(props.token);
322
+ props.onVerified?.();
323
+ });
324
+ }, [props.token]);
325
+ return /* @__PURE__ */ jsxs5("div", { "data-testid": "throttle-auth-verify", className: props.className ?? "throttle-auth-panel", children: [
326
+ props.token && submitting && /* @__PURE__ */ jsx5("p", { className: "throttle-auth-notice", role: "status", children: l.verifying }),
327
+ emailVerified && /* @__PURE__ */ jsx5("p", { className: "throttle-auth-notice", role: "status", children: l.verified }),
328
+ !emailVerified && !props.token && /* @__PURE__ */ jsx5("p", { className: "throttle-auth-prompt", children: l.prompt }),
329
+ error && /* @__PURE__ */ jsx5("p", { className: "throttle-auth-error", role: "alert", children: error }),
330
+ notice && /* @__PURE__ */ jsx5("p", { className: "throttle-auth-notice", role: "status", children: notice }),
331
+ !emailVerified && !props.token && /* @__PURE__ */ jsx5(
332
+ "button",
333
+ {
334
+ className: "throttle-auth-submit",
335
+ type: "button",
336
+ disabled: submitting,
337
+ onClick: () => void run(async () => {
338
+ await auth.resendVerification();
339
+ return l.resent;
340
+ }),
341
+ children: l.resend
342
+ }
343
+ )
344
+ ] });
345
+ }
346
+ export {
347
+ ForgotPasswordForm,
348
+ ResetPasswordForm,
349
+ SignInForm,
350
+ SignUpForm,
351
+ VerifyEmailPanel,
352
+ messageForError
353
+ };
354
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/react/forms/SignIn.tsx","../../../src/react/forms/useFormAction.ts","../../../src/react/forms/messages.ts","../../../src/react/forms/SignUp.tsx","../../../src/react/forms/ForgotPassword.tsx","../../../src/react/forms/ResetPassword.tsx","../../../src/react/forms/VerifyEmail.tsx"],"sourcesContent":["import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface SignInFormProps {\n onSuccess?: () => void;\n onForgotPassword?: () => void;\n className?: string;\n labels?: Partial<{ email: string; password: string; submit: string; forgot: string }>;\n}\n\nexport function SignInForm(props: SignInFormProps) {\n const { auth } = useAuth();\n const { submitting, error, run } = useFormAction();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const ids = { email: `${useId()}-email`, password: `${useId()}-password` };\n const l = { email: 'Email', password: 'Password', submit: 'Sign in', forgot: 'Forgot your password?', ...props.labels };\n\n return (\n <form\n data-testid=\"throttle-auth-signin\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => { await auth.signIn(email, password); props.onSuccess?.(); });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={ids.email}>{l.email}</label>\n <input className=\"throttle-auth-input\" id={ids.email} type=\"email\" autoComplete=\"email\"\n value={email} onChange={(e) => setEmail(e.target.value)} required />\n\n <label className=\"throttle-auth-label\" htmlFor={ids.password}>{l.password}</label>\n <input className=\"throttle-auth-input\" id={ids.password} type=\"password\" autoComplete=\"current-password\"\n value={password} onChange={(e) => setPassword(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n\n {props.onForgotPassword && (\n <button className=\"throttle-auth-link\" type=\"button\" onClick={props.onForgotPassword}>{l.forgot}</button>\n )}\n </form>\n );\n}\n","import { useCallback, useRef, useState } from 'react';\nimport { messageForError } from './messages';\n\nexport interface FormAction {\n submitting: boolean;\n error: string | null;\n notice: string | null;\n run(fn: () => Promise<string | void>): Promise<void>;\n}\n\n/** Submit-once semantics plus mapped error copy, shared by all five forms. */\nexport function useFormAction(): FormAction {\n const [submitting, setSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const inFlight = useRef(false);\n\n const run = useCallback(async (fn: () => Promise<string | void>) => {\n if (inFlight.current) return; // a double-click is one attempt, not two\n inFlight.current = true;\n setSubmitting(true);\n setError(null);\n setNotice(null);\n try {\n const message = await fn();\n if (typeof message === 'string') setNotice(message);\n } catch (err) {\n setError(messageForError(err));\n } finally {\n inFlight.current = false;\n setSubmitting(false);\n }\n }, []);\n\n return { submitting, error, notice, run };\n}\n","import { ThrottleAuthError } from '../../errors';\n\nfunction waitCopy(seconds?: number): string {\n if (!seconds || !Number.isFinite(seconds)) return 'Please wait a moment and try again.';\n if (seconds < 60) return `Please wait ${Math.ceil(seconds)} seconds and try again.`;\n const minutes = Math.ceil(seconds / 60);\n return `Please wait ${minutes} ${minutes === 1 ? 'minute' : 'minutes'} and try again.`;\n}\n\n/**\n * Buyer-facing copy for every code the storefront API returns. Two codes are\n * deliberately NOT passed through from the server: `invalid_credentials` (the\n * server is vague on purpose — enumerating accounts through our own UI would\n * undo that) and anything unrecognised.\n */\nexport function messageForError(err: unknown): string {\n const e = err as Partial<ThrottleAuthError> & { code?: string };\n switch (e?.code) {\n case 'invalid_credentials':\n return 'That email or password is not correct.';\n case 'weak_password':\n // The server's message IS the policy reason (\"Use at least 12 characters\").\n return e.message || 'Please choose a stronger password.';\n case 'too_many_attempts':\n return `Too many attempts. ${waitCopy(e.retryAfter)}`;\n case 'verification_required':\n return 'Please verify your email address to continue.';\n case 'step_up_required':\n return 'Please re-enter your password to continue.';\n case 'session_revoked':\n return 'Your session has ended. Please sign in again.';\n case 'token_expired':\n case 'token_invalid':\n case 'token_already_used':\n return 'That link is no longer valid. Please request a new link.';\n case 'auth_unavailable':\n return 'Accounts are briefly unavailable. Please try again shortly.';\n case 'origin_not_allowed':\n case 'auth_not_enabled':\n case 'invalid_api_key':\n case 'environment_mismatch':\n // Merchant misconfiguration, not something the buyer can fix.\n return 'Accounts are not available right now. Please contact support.';\n case 'cart_already_claimed':\n return 'That cart belongs to a different account.';\n case 'not_found':\n return 'We could not find that.';\n default:\n return 'Something went wrong. Please try again.';\n }\n}\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface SignUpFormProps {\n onSuccess?: () => void;\n /** Called for the 202 shape: the address already has credentials, so we sent an email instead. */\n onPending?: () => void;\n className?: string;\n labels?: Partial<{ email: string; password: string; submit: string; pending: string }>;\n}\n\nexport function SignUpForm(props: SignUpFormProps) {\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const ids = { email: `${useId()}-email`, password: `${useId()}-password` };\n const l = {\n email: 'Email', password: 'Password', submit: 'Create account',\n pending: 'Check your email — we sent you a link to finish signing in.',\n ...props.labels,\n };\n\n return (\n <form\n data-testid=\"throttle-auth-signup\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => {\n const result = await auth.register(email, password);\n // 202: an account already exists for this address. There is no\n // session — say so rather than routing to a signed-in page.\n if (result.status === 'pending') { props.onPending?.(); return l.pending; }\n props.onSuccess?.();\n });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={ids.email}>{l.email}</label>\n <input className=\"throttle-auth-input\" id={ids.email} type=\"email\" autoComplete=\"email\"\n value={email} onChange={(e) => setEmail(e.target.value)} required />\n\n <label className=\"throttle-auth-label\" htmlFor={ids.password}>{l.password}</label>\n <input className=\"throttle-auth-input\" id={ids.password} type=\"password\" autoComplete=\"new-password\"\n value={password} onChange={(e) => setPassword(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n </form>\n );\n}\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface ForgotPasswordFormProps {\n onSent?: () => void;\n className?: string;\n labels?: Partial<{ email: string; submit: string; sent: string }>;\n}\n\nexport function ForgotPasswordForm(props: ForgotPasswordFormProps) {\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const [email, setEmail] = useState('');\n const id = `${useId()}-email`;\n const l = {\n email: 'Email', submit: 'Send reset link',\n // Identical copy whether or not the address has an account — the API is\n // deliberately silent on that and this form must be too.\n sent: 'If an account exists for that address, we have sent a reset link.',\n ...props.labels,\n };\n\n return (\n <form\n data-testid=\"throttle-auth-forgot\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => { await auth.forgotPassword(email); props.onSent?.(); return l.sent; });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={id}>{l.email}</label>\n <input className=\"throttle-auth-input\" id={id} type=\"email\" autoComplete=\"email\"\n value={email} onChange={(e) => setEmail(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n </form>\n );\n}\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface ResetPasswordFormProps {\n /** The token from the emailed link, e.g. `new URLSearchParams(location.search).get('token')`. */\n token: string;\n onSuccess?: () => void;\n className?: string;\n labels?: Partial<{ password: string; submit: string; done: string }>;\n}\n\nexport function ResetPasswordForm(props: ResetPasswordFormProps) {\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const [password, setPassword] = useState('');\n const id = `${useId()}-new-password`;\n const l = {\n password: 'New password', submit: 'Set new password',\n done: 'Your password has been reset. Please sign in with your new password.',\n ...props.labels,\n };\n\n return (\n <form\n data-testid=\"throttle-auth-reset\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => {\n await auth.resetPassword(props.token, password);\n props.onSuccess?.();\n // Every session died with the reset, this one included — the buyer\n // signs in again rather than landing on an account page.\n return l.done;\n });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={id}>{l.password}</label>\n <input className=\"throttle-auth-input\" id={id} type=\"password\" autoComplete=\"new-password\"\n value={password} onChange={(e) => setPassword(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n </form>\n );\n}\n","import { useEffect, useRef } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface VerifyEmailPanelProps {\n /** Token from the emailed link. Omit to render the \"resend\" state instead. */\n token?: string;\n onVerified?: () => void;\n className?: string;\n labels?: Partial<{ verifying: string; verified: string; prompt: string; resend: string; resent: string }>;\n}\n\nexport function VerifyEmailPanel(props: VerifyEmailPanelProps) {\n const { auth, emailVerified } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const attempted = useRef<string | null>(null);\n const l = {\n verifying: 'Verifying your email…',\n verified: 'Your email address is verified.',\n prompt: 'Please verify your email address to unlock your full account.',\n resend: 'Resend verification email',\n resent: 'Sent. Check your inbox.',\n ...props.labels,\n };\n\n useEffect(() => {\n if (!props.token || attempted.current === props.token) return;\n // Once per token. Re-running would burn the single-use token and report\n // `token_already_used` to a buyer who did nothing wrong.\n //\n // useFormAction's `inFlight` flag happens to block the StrictMode\n // double-invoke too, but that is a submit-debounce concern that belongs to\n // form actions generally. Burning a single-use token is this panel's own\n // invariant, and it should not depend on another layer's debounce\n // semantics staying exactly as they are today.\n attempted.current = props.token;\n void run(async () => { await auth.verifyEmail(props.token!); props.onVerified?.(); });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.token]);\n\n return (\n <div data-testid=\"throttle-auth-verify\" className={props.className ?? 'throttle-auth-panel'}>\n {props.token && submitting && <p className=\"throttle-auth-notice\" role=\"status\">{l.verifying}</p>}\n {emailVerified && <p className=\"throttle-auth-notice\" role=\"status\">{l.verified}</p>}\n {!emailVerified && !props.token && <p className=\"throttle-auth-prompt\">{l.prompt}</p>}\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n {!emailVerified && !props.token && (\n <button\n className=\"throttle-auth-submit\" type=\"button\" disabled={submitting}\n onClick={() => void run(async () => { await auth.resendVerification(); return l.resent; })}\n >\n {l.resend}\n </button>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["useState","useState","useId","useState","jsx","jsxs","useState","useId","useId","useState","jsx","jsxs","useState","useId","useId","useState","jsx","jsxs","useState","useId","useRef","jsx","jsxs","useRef"]}