@usethrottle/auth 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/dist/AccountDashboard-V63ghvjY.d.ts +90 -0
- package/dist/AccountModal-KA54WUFW.js +13 -0
- package/dist/AccountModal-KA54WUFW.js.map +1 -0
- package/dist/AccountModal-pIMgledL.d.ts +141 -0
- package/dist/AuthModal-T2GYI6ZF.js +13 -0
- package/dist/AuthModal-T2GYI6ZF.js.map +1 -0
- package/dist/SignUpPage-BJcMUb2V.d.ts +160 -0
- package/dist/chunk-3B4DNX5T.js +2 -0
- package/dist/chunk-3B4DNX5T.js.map +1 -0
- package/dist/chunk-5EUZYJJL.js +29 -0
- package/dist/chunk-5EUZYJJL.js.map +1 -0
- package/dist/chunk-6WN3J5MM.js +2075 -0
- package/dist/chunk-6WN3J5MM.js.map +1 -0
- package/dist/chunk-CUNMJ2PH.js +1320 -0
- package/dist/chunk-CUNMJ2PH.js.map +1 -0
- package/dist/chunk-ETYOOYYV.js +2 -0
- package/dist/chunk-ETYOOYYV.js.map +1 -0
- package/dist/chunk-JSDZMPCV.js +520 -0
- package/dist/chunk-JSDZMPCV.js.map +1 -0
- package/dist/chunk-QPVS6JXP.js +39 -0
- package/dist/chunk-QPVS6JXP.js.map +1 -0
- package/dist/chunk-RCNUWITB.js +747 -0
- package/dist/chunk-RCNUWITB.js.map +1 -0
- package/dist/{chunk-YY6YATXT.js → chunk-TDQC3LMQ.js} +17 -3
- package/dist/chunk-TDQC3LMQ.js.map +1 -0
- package/dist/{chunk-EFMKBOTL.js → chunk-UFGW76AZ.js} +2 -2
- package/dist/chunk-VDK23T6L.js +230 -0
- package/dist/chunk-VDK23T6L.js.map +1 -0
- package/dist/chunk-X6D35EAX.js +19 -0
- package/dist/chunk-X6D35EAX.js.map +1 -0
- package/dist/client-7LVKfuXO.d.ts +319 -0
- package/dist/hooks-DkpLrEiC.d.ts +148 -0
- package/dist/index.cjs +16 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/localization-SnIAZXJn.d.ts +389 -0
- package/dist/react/components/account/index.d.ts +37 -0
- package/dist/react/components/account/index.js +44 -0
- package/dist/react/components/account/index.js.map +1 -0
- package/dist/react/components/auth/index.d.ts +17 -0
- package/dist/react/components/auth/index.js +40 -0
- package/dist/react/components/auth/index.js.map +1 -0
- package/dist/react/components/guards.d.ts +21 -0
- package/dist/react/components/guards.js +13 -0
- package/dist/react/components/guards.js.map +1 -0
- package/dist/react/components/index.cjs +3561 -2805
- package/dist/react/components/index.cjs.map +1 -1
- package/dist/react/components/index.d.cts +14 -2
- package/dist/react/components/index.d.ts +14 -821
- package/dist/react/components/index.js +90 -4680
- package/dist/react/components/index.js.map +1 -1
- package/dist/react/components/provider.d.ts +79 -0
- package/dist/react/components/provider.js +18 -0
- package/dist/react/components/provider.js.map +1 -0
- package/dist/react/forms/index.js +1 -1
- package/dist/react/index.cjs +16 -2
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +34 -3
- package/dist/react/index.d.ts +5 -433
- package/dist/react/index.js +3 -2
- package/dist/styles.css +7 -4
- package/package.json +4 -1
- package/dist/chunk-YY6YATXT.js.map +0 -1
- /package/dist/{chunk-EFMKBOTL.js.map → chunk-UFGW76AZ.js.map} +0 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
useBrandLogo
|
|
4
|
+
} from "./chunk-X6D35EAX.js";
|
|
5
|
+
import {
|
|
6
|
+
useAction
|
|
7
|
+
} from "./chunk-QPVS6JXP.js";
|
|
8
|
+
import {
|
|
9
|
+
Alert,
|
|
10
|
+
AppearanceProvider,
|
|
11
|
+
Button,
|
|
12
|
+
CardBody,
|
|
13
|
+
CardFooter,
|
|
14
|
+
CardHeader,
|
|
15
|
+
Field,
|
|
16
|
+
FieldGroup,
|
|
17
|
+
Modal,
|
|
18
|
+
Spinner,
|
|
19
|
+
useAppearance,
|
|
20
|
+
useAppearanceProviderValue,
|
|
21
|
+
useCardClass,
|
|
22
|
+
useCopy
|
|
23
|
+
} from "./chunk-CUNMJ2PH.js";
|
|
24
|
+
import {
|
|
25
|
+
useAuth
|
|
26
|
+
} from "./chunk-TDQC3LMQ.js";
|
|
27
|
+
|
|
28
|
+
// src/react/components/auth/AuthModal.tsx
|
|
29
|
+
import { useId } from "react";
|
|
30
|
+
|
|
31
|
+
// src/react/components/auth/AuthFlow.tsx
|
|
32
|
+
import { useEffect as useEffect2, useState as useState6 } from "react";
|
|
33
|
+
|
|
34
|
+
// src/react/components/auth/SignIn.tsx
|
|
35
|
+
import { useState } from "react";
|
|
36
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
37
|
+
function SignIn(props) {
|
|
38
|
+
const a = useAppearance(props.appearance);
|
|
39
|
+
const cardClass = useCardClass();
|
|
40
|
+
const scoped = useAppearanceProviderValue(props.appearance);
|
|
41
|
+
const { t } = useCopy(props.localization);
|
|
42
|
+
const { auth } = useAuth();
|
|
43
|
+
const { submitting, error, run } = useAction();
|
|
44
|
+
const [email, setEmail] = useState("");
|
|
45
|
+
const [password, setPassword] = useState("");
|
|
46
|
+
const variant = props.variant ?? "quiet";
|
|
47
|
+
const branded = variant === "branded";
|
|
48
|
+
const grouped = variant === "grouped";
|
|
49
|
+
const logo = useBrandLogo(props.logo);
|
|
50
|
+
const forgotLink = props.onForgotPassword ? /* @__PURE__ */ jsx(Button, { variant: "link", size: "xs", elementKey: "signIn.forgotLink", onClick: props.onForgotPassword, children: t.signIn.forgotLink }) : null;
|
|
51
|
+
const emailField = /* @__PURE__ */ jsx(
|
|
52
|
+
Field,
|
|
53
|
+
{
|
|
54
|
+
label: t.signIn.email,
|
|
55
|
+
type: "email",
|
|
56
|
+
autoComplete: "email",
|
|
57
|
+
required: true,
|
|
58
|
+
value: email,
|
|
59
|
+
onChange: setEmail,
|
|
60
|
+
elementKey: "signIn.emailField",
|
|
61
|
+
grouped
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
const passwordField = /* @__PURE__ */ jsx(
|
|
65
|
+
Field,
|
|
66
|
+
{
|
|
67
|
+
label: t.signIn.password,
|
|
68
|
+
type: "password",
|
|
69
|
+
autoComplete: "current-password",
|
|
70
|
+
required: true,
|
|
71
|
+
value: password,
|
|
72
|
+
onChange: setPassword,
|
|
73
|
+
elementKey: "signIn.passwordField",
|
|
74
|
+
grouped,
|
|
75
|
+
labelExtra: grouped ? void 0 : forgotLink
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
return /* @__PURE__ */ jsx(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs(
|
|
79
|
+
"form",
|
|
80
|
+
{
|
|
81
|
+
...a.el("signIn.root", cardClass),
|
|
82
|
+
onSubmit: (e) => {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
void run(async () => {
|
|
85
|
+
await auth.signIn(email, password);
|
|
86
|
+
props.onSuccess?.();
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
CardHeader,
|
|
92
|
+
{
|
|
93
|
+
title: t.signIn.title,
|
|
94
|
+
description: branded ? t.signIn.description : void 0,
|
|
95
|
+
titleKey: "signIn.title",
|
|
96
|
+
logo: branded ? logo : null,
|
|
97
|
+
logoKey: "signIn.logo",
|
|
98
|
+
branded
|
|
99
|
+
}
|
|
100
|
+
),
|
|
101
|
+
/* @__PURE__ */ jsxs(CardBody, { children: [
|
|
102
|
+
grouped ? /* @__PURE__ */ jsxs(FieldGroup, { children: [
|
|
103
|
+
emailField,
|
|
104
|
+
passwordField
|
|
105
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
106
|
+
emailField,
|
|
107
|
+
passwordField
|
|
108
|
+
] }),
|
|
109
|
+
grouped && forgotLink ? /* @__PURE__ */ jsx("div", { className: "throttle-field-group__forgot", children: forgotLink }) : null,
|
|
110
|
+
error ? /* @__PURE__ */ jsx(Alert, { tone: "error", children: error }) : null,
|
|
111
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading: submitting, elementKey: "signIn.submitButton", children: t.signIn.submitButton })
|
|
112
|
+
] }),
|
|
113
|
+
props.onSignUp ? /* @__PURE__ */ jsxs(CardFooter, { ruled: !branded, children: [
|
|
114
|
+
/* @__PURE__ */ jsxs("span", { className: "throttle-muted", children: [
|
|
115
|
+
t.signIn.switchPrompt,
|
|
116
|
+
" "
|
|
117
|
+
] }),
|
|
118
|
+
/* @__PURE__ */ jsx(Button, { variant: "link", elementKey: "signIn.switchLink", onClick: props.onSignUp, children: t.signIn.switchLink })
|
|
119
|
+
] }) : null
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
) }) });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/react/components/auth/SignUp.tsx
|
|
126
|
+
import { useState as useState2 } from "react";
|
|
127
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
128
|
+
function SignUp(props) {
|
|
129
|
+
const a = useAppearance(props.appearance);
|
|
130
|
+
const cardClass = useCardClass();
|
|
131
|
+
const scoped = useAppearanceProviderValue(props.appearance);
|
|
132
|
+
const { t } = useCopy(props.localization);
|
|
133
|
+
const { auth } = useAuth();
|
|
134
|
+
const { submitting, error, notice, run } = useAction();
|
|
135
|
+
const [email, setEmail] = useState2("");
|
|
136
|
+
const [password, setPassword] = useState2("");
|
|
137
|
+
const variant = props.variant ?? "quiet";
|
|
138
|
+
const branded = variant === "branded";
|
|
139
|
+
const grouped = variant === "grouped";
|
|
140
|
+
const logo = useBrandLogo(props.logo);
|
|
141
|
+
const emailField = /* @__PURE__ */ jsx2(
|
|
142
|
+
Field,
|
|
143
|
+
{
|
|
144
|
+
label: t.signUp.email,
|
|
145
|
+
type: "email",
|
|
146
|
+
autoComplete: "email",
|
|
147
|
+
required: true,
|
|
148
|
+
value: email,
|
|
149
|
+
onChange: setEmail,
|
|
150
|
+
elementKey: "signUp.emailField",
|
|
151
|
+
grouped
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
const passwordField = /* @__PURE__ */ jsx2(
|
|
155
|
+
Field,
|
|
156
|
+
{
|
|
157
|
+
label: t.signUp.password,
|
|
158
|
+
type: "password",
|
|
159
|
+
autoComplete: "new-password",
|
|
160
|
+
required: true,
|
|
161
|
+
value: password,
|
|
162
|
+
onChange: setPassword,
|
|
163
|
+
elementKey: "signUp.passwordField",
|
|
164
|
+
grouped
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
return /* @__PURE__ */ jsx2(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx2("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs2(
|
|
168
|
+
"form",
|
|
169
|
+
{
|
|
170
|
+
...a.el("signUp.root", cardClass),
|
|
171
|
+
onSubmit: (e) => {
|
|
172
|
+
e.preventDefault();
|
|
173
|
+
void run(async () => {
|
|
174
|
+
const result = await auth.register(email, password);
|
|
175
|
+
if (result.status === "active") {
|
|
176
|
+
props.onSuccess?.();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
return t.signUp.pendingNotice;
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
children: [
|
|
183
|
+
/* @__PURE__ */ jsx2(
|
|
184
|
+
CardHeader,
|
|
185
|
+
{
|
|
186
|
+
title: t.signUp.title,
|
|
187
|
+
description: branded ? t.signUp.description : void 0,
|
|
188
|
+
titleKey: "signUp.title",
|
|
189
|
+
logo: branded ? logo : null,
|
|
190
|
+
logoKey: "signUp.logo",
|
|
191
|
+
branded
|
|
192
|
+
}
|
|
193
|
+
),
|
|
194
|
+
/* @__PURE__ */ jsxs2(CardBody, { children: [
|
|
195
|
+
grouped ? /* @__PURE__ */ jsxs2(FieldGroup, { children: [
|
|
196
|
+
emailField,
|
|
197
|
+
passwordField
|
|
198
|
+
] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
199
|
+
emailField,
|
|
200
|
+
passwordField
|
|
201
|
+
] }),
|
|
202
|
+
error ? /* @__PURE__ */ jsx2(Alert, { tone: "error", children: error }) : null,
|
|
203
|
+
notice ? /* @__PURE__ */ jsx2(Alert, { tone: "info", children: notice }) : null,
|
|
204
|
+
/* @__PURE__ */ jsx2(Button, { type: "submit", loading: submitting, elementKey: "signUp.submitButton", children: t.signUp.submitButton })
|
|
205
|
+
] }),
|
|
206
|
+
props.onSignIn ? /* @__PURE__ */ jsxs2(CardFooter, { ruled: !branded, children: [
|
|
207
|
+
/* @__PURE__ */ jsxs2("span", { className: "throttle-muted", children: [
|
|
208
|
+
t.signUp.switchPrompt,
|
|
209
|
+
" "
|
|
210
|
+
] }),
|
|
211
|
+
/* @__PURE__ */ jsx2(Button, { variant: "link", elementKey: "signUp.switchLink", onClick: props.onSignIn, children: t.signUp.switchLink })
|
|
212
|
+
] }) : null
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
) }) });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/react/components/auth/ForgotPassword.tsx
|
|
219
|
+
import { useState as useState3 } from "react";
|
|
220
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
221
|
+
function ForgotPassword(props) {
|
|
222
|
+
const a = useAppearance(props.appearance);
|
|
223
|
+
const cardClass = useCardClass();
|
|
224
|
+
const scoped = useAppearanceProviderValue(props.appearance);
|
|
225
|
+
const { t } = useCopy(props.localization);
|
|
226
|
+
const { auth } = useAuth();
|
|
227
|
+
const { submitting, error, notice, run } = useAction();
|
|
228
|
+
const [email, setEmail] = useState3("");
|
|
229
|
+
const branded = (props.variant ?? "quiet") === "branded";
|
|
230
|
+
const logo = useBrandLogo(props.logo);
|
|
231
|
+
return /* @__PURE__ */ jsx3(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx3("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs3(
|
|
232
|
+
"form",
|
|
233
|
+
{
|
|
234
|
+
...a.el("forgotPassword.root", cardClass),
|
|
235
|
+
onSubmit: (e) => {
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
void run(async () => {
|
|
238
|
+
await auth.forgotPassword(email);
|
|
239
|
+
return t.forgotPassword.sentNotice;
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
children: [
|
|
243
|
+
/* @__PURE__ */ jsx3(
|
|
244
|
+
CardHeader,
|
|
245
|
+
{
|
|
246
|
+
title: t.forgotPassword.title,
|
|
247
|
+
description: branded ? t.forgotPassword.description : void 0,
|
|
248
|
+
titleKey: "forgotPassword.title",
|
|
249
|
+
logo: branded ? logo : null,
|
|
250
|
+
logoKey: "forgotPassword.logo",
|
|
251
|
+
branded
|
|
252
|
+
}
|
|
253
|
+
),
|
|
254
|
+
/* @__PURE__ */ jsxs3(CardBody, { children: [
|
|
255
|
+
/* @__PURE__ */ jsx3(
|
|
256
|
+
Field,
|
|
257
|
+
{
|
|
258
|
+
label: t.forgotPassword.email,
|
|
259
|
+
type: "email",
|
|
260
|
+
autoComplete: "email",
|
|
261
|
+
required: true,
|
|
262
|
+
value: email,
|
|
263
|
+
onChange: setEmail,
|
|
264
|
+
elementKey: "forgotPassword.emailField"
|
|
265
|
+
}
|
|
266
|
+
),
|
|
267
|
+
error ? /* @__PURE__ */ jsx3(Alert, { tone: "error", children: error }) : null,
|
|
268
|
+
notice ? /* @__PURE__ */ jsx3(Alert, { tone: "success", children: notice }) : null,
|
|
269
|
+
/* @__PURE__ */ jsx3(Button, { type: "submit", loading: submitting, elementKey: "forgotPassword.submitButton", children: t.forgotPassword.submitButton })
|
|
270
|
+
] }),
|
|
271
|
+
props.onBack ? /* @__PURE__ */ jsx3(CardFooter, { ruled: !branded, children: /* @__PURE__ */ jsx3(Button, { variant: "link", elementKey: "forgotPassword.backLink", onClick: props.onBack, children: t.forgotPassword.backLink }) }) : null
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
) }) });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/react/components/auth/ResetPassword.tsx
|
|
278
|
+
import { useState as useState4 } from "react";
|
|
279
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
280
|
+
function ResetPassword(props) {
|
|
281
|
+
const a = useAppearance(props.appearance);
|
|
282
|
+
const cardClass = useCardClass();
|
|
283
|
+
const scoped = useAppearanceProviderValue(props.appearance);
|
|
284
|
+
const { t } = useCopy(props.localization);
|
|
285
|
+
const { auth } = useAuth();
|
|
286
|
+
const { submitting, error, notice, run } = useAction();
|
|
287
|
+
const [password, setPassword] = useState4("");
|
|
288
|
+
const branded = (props.variant ?? "quiet") === "branded";
|
|
289
|
+
const logo = useBrandLogo(props.logo);
|
|
290
|
+
return /* @__PURE__ */ jsx4(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx4("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs4("div", { ...a.el("resetPassword.root", cardClass), children: [
|
|
291
|
+
/* @__PURE__ */ jsx4(
|
|
292
|
+
CardHeader,
|
|
293
|
+
{
|
|
294
|
+
title: t.resetPassword.title,
|
|
295
|
+
titleKey: "resetPassword.title",
|
|
296
|
+
logo: branded ? logo : null,
|
|
297
|
+
logoKey: "resetPassword.logo",
|
|
298
|
+
branded
|
|
299
|
+
}
|
|
300
|
+
),
|
|
301
|
+
/* @__PURE__ */ jsx4(CardBody, { children: !props.token ? (
|
|
302
|
+
// No form at all without a token. A submit button that can only
|
|
303
|
+
// fail is worse than an explanation.
|
|
304
|
+
/* @__PURE__ */ jsx4(Alert, { tone: "error", children: t.resetPassword.missingToken })
|
|
305
|
+
) : /* @__PURE__ */ jsx4(
|
|
306
|
+
"form",
|
|
307
|
+
{
|
|
308
|
+
onSubmit: (e) => {
|
|
309
|
+
e.preventDefault();
|
|
310
|
+
void run(async () => {
|
|
311
|
+
await auth.resetPassword(props.token, password);
|
|
312
|
+
props.onSuccess?.();
|
|
313
|
+
return t.resetPassword.doneNotice;
|
|
314
|
+
});
|
|
315
|
+
},
|
|
316
|
+
children: /* @__PURE__ */ jsxs4("div", { className: "throttle-stack", children: [
|
|
317
|
+
/* @__PURE__ */ jsx4(
|
|
318
|
+
Field,
|
|
319
|
+
{
|
|
320
|
+
label: t.resetPassword.password,
|
|
321
|
+
type: "password",
|
|
322
|
+
autoComplete: "new-password",
|
|
323
|
+
required: true,
|
|
324
|
+
value: password,
|
|
325
|
+
onChange: setPassword,
|
|
326
|
+
elementKey: "resetPassword.passwordField"
|
|
327
|
+
}
|
|
328
|
+
),
|
|
329
|
+
error ? /* @__PURE__ */ jsx4(Alert, { tone: "error", children: error }) : null,
|
|
330
|
+
notice ? /* @__PURE__ */ jsx4(Alert, { tone: "success", children: notice }) : null,
|
|
331
|
+
/* @__PURE__ */ jsx4(Button, { type: "submit", loading: submitting, elementKey: "resetPassword.submitButton", children: t.resetPassword.submitButton })
|
|
332
|
+
] })
|
|
333
|
+
}
|
|
334
|
+
) })
|
|
335
|
+
] }) }) });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/react/components/auth/VerifyEmail.tsx
|
|
339
|
+
import { useEffect, useRef, useState as useState5 } from "react";
|
|
340
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
341
|
+
function VerifyEmail(props) {
|
|
342
|
+
const a = useAppearance(props.appearance);
|
|
343
|
+
const cardClass = useCardClass();
|
|
344
|
+
const scoped = useAppearanceProviderValue(props.appearance);
|
|
345
|
+
const { t, errorMessage } = useCopy(props.localization);
|
|
346
|
+
const { auth } = useAuth();
|
|
347
|
+
const { submitting, notice, run } = useAction();
|
|
348
|
+
const [state, setState] = useState5(props.token ? "verifying" : "idle");
|
|
349
|
+
const [error, setError] = useState5(null);
|
|
350
|
+
const branded = (props.variant ?? "quiet") === "branded";
|
|
351
|
+
const logo = useBrandLogo(props.logo);
|
|
352
|
+
const attempted = useRef(null);
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
const token = props.token;
|
|
355
|
+
if (!token || attempted.current === token) return;
|
|
356
|
+
attempted.current = token;
|
|
357
|
+
setState("verifying");
|
|
358
|
+
auth.verifyEmail(token).then(() => {
|
|
359
|
+
setState("done");
|
|
360
|
+
props.onSuccess?.();
|
|
361
|
+
}).catch((err) => {
|
|
362
|
+
setError(errorMessage(err));
|
|
363
|
+
setState("failed");
|
|
364
|
+
});
|
|
365
|
+
}, [props.token, auth]);
|
|
366
|
+
return /* @__PURE__ */ jsx5(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsx5("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs5("div", { ...a.el("verifyEmail.root", cardClass), children: [
|
|
367
|
+
/* @__PURE__ */ jsx5(
|
|
368
|
+
CardHeader,
|
|
369
|
+
{
|
|
370
|
+
title: t.verifyEmail.title,
|
|
371
|
+
titleKey: "verifyEmail.title",
|
|
372
|
+
logo: branded ? logo : null,
|
|
373
|
+
logoKey: "verifyEmail.logo",
|
|
374
|
+
branded
|
|
375
|
+
}
|
|
376
|
+
),
|
|
377
|
+
/* @__PURE__ */ jsxs5(CardBody, { children: [
|
|
378
|
+
state === "verifying" ? /* @__PURE__ */ jsxs5("p", { children: [
|
|
379
|
+
/* @__PURE__ */ jsx5(Spinner, {}),
|
|
380
|
+
" ",
|
|
381
|
+
t.verifyEmail.verifying
|
|
382
|
+
] }) : null,
|
|
383
|
+
state === "done" ? /* @__PURE__ */ jsx5(Alert, { tone: "success", children: t.verifyEmail.success }) : null,
|
|
384
|
+
state === "failed" && error ? /* @__PURE__ */ jsx5(Alert, { tone: "error", children: error }) : null,
|
|
385
|
+
state === "idle" ? /* @__PURE__ */ jsx5("p", { className: "throttle-muted", children: t.verifyEmail.instructions }) : null,
|
|
386
|
+
notice ? /* @__PURE__ */ jsx5(Alert, { tone: "success", children: notice }) : null,
|
|
387
|
+
state !== "done" ? /* @__PURE__ */ jsx5(
|
|
388
|
+
Button,
|
|
389
|
+
{
|
|
390
|
+
variant: "secondary",
|
|
391
|
+
loading: submitting,
|
|
392
|
+
elementKey: "verifyEmail.resendButton",
|
|
393
|
+
onClick: () => void run(async () => {
|
|
394
|
+
await auth.resendVerification();
|
|
395
|
+
return t.verifyEmail.resentNotice;
|
|
396
|
+
}),
|
|
397
|
+
children: t.verifyEmail.resendButton
|
|
398
|
+
}
|
|
399
|
+
) : null
|
|
400
|
+
] })
|
|
401
|
+
] }) }) });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/react/components/auth/AuthFlow.tsx
|
|
405
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
406
|
+
var HASH_FOR = {
|
|
407
|
+
signIn: "#/sign-in",
|
|
408
|
+
signUp: "#/sign-up",
|
|
409
|
+
forgotPassword: "#/forgot-password",
|
|
410
|
+
resetPassword: "#/reset-password",
|
|
411
|
+
verifyEmail: "#/verify-email"
|
|
412
|
+
};
|
|
413
|
+
var VIEW_FOR_HASH = Object.fromEntries(
|
|
414
|
+
Object.entries(HASH_FOR).map(([view, hash]) => [hash, view])
|
|
415
|
+
);
|
|
416
|
+
function readLocation() {
|
|
417
|
+
if (typeof window === "undefined") return { view: null };
|
|
418
|
+
const params = new URLSearchParams(window.location.search);
|
|
419
|
+
const token = params.get("token") ?? void 0;
|
|
420
|
+
const hashView = VIEW_FOR_HASH[window.location.hash];
|
|
421
|
+
if (hashView) return { view: hashView, token };
|
|
422
|
+
if (!token) return { view: null };
|
|
423
|
+
const path = window.location.pathname.toLowerCase();
|
|
424
|
+
if (path.includes("reset")) return { view: "resetPassword", token };
|
|
425
|
+
if (path.includes("verif")) return { view: "verifyEmail", token };
|
|
426
|
+
return { view: null, token };
|
|
427
|
+
}
|
|
428
|
+
function AuthFlow(props) {
|
|
429
|
+
const a = useAppearance(props.appearance);
|
|
430
|
+
const controlled = props.view !== void 0;
|
|
431
|
+
const [internal, setInternal] = useState6(props.initialView ?? "signIn");
|
|
432
|
+
const [urlToken, setUrlToken] = useState6(void 0);
|
|
433
|
+
useEffect2(() => {
|
|
434
|
+
const found = readLocation();
|
|
435
|
+
if (found.token) setUrlToken(found.token);
|
|
436
|
+
if (found.view && !controlled && props.initialView === void 0) setInternal(found.view);
|
|
437
|
+
}, []);
|
|
438
|
+
const view = controlled ? props.view : internal;
|
|
439
|
+
const token = props.token ?? urlToken;
|
|
440
|
+
const go = (next) => {
|
|
441
|
+
props.onViewChange?.(next);
|
|
442
|
+
if (!controlled) setInternal(next);
|
|
443
|
+
if (props.routing === "hash" && typeof window !== "undefined") {
|
|
444
|
+
window.location.hash = HASH_FOR[next];
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
useEffect2(() => {
|
|
448
|
+
if (props.routing !== "hash" || typeof window === "undefined") return;
|
|
449
|
+
const onHashChange = () => {
|
|
450
|
+
if (controlled) return;
|
|
451
|
+
const hash = window.location.hash;
|
|
452
|
+
if (hash === "") {
|
|
453
|
+
setInternal(props.initialView ?? "signIn");
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const next = VIEW_FOR_HASH[hash];
|
|
457
|
+
if (next) setInternal(next);
|
|
458
|
+
};
|
|
459
|
+
window.addEventListener("hashchange", onHashChange);
|
|
460
|
+
return () => window.removeEventListener("hashchange", onHashChange);
|
|
461
|
+
}, [props.routing, controlled]);
|
|
462
|
+
const shared = { appearance: props.appearance, localization: props.localization };
|
|
463
|
+
return /* @__PURE__ */ jsx6("div", { ...a.rootProps, children: /* @__PURE__ */ jsxs6("div", { ...a.el("authFlow.root"), children: [
|
|
464
|
+
view === "signIn" ? /* @__PURE__ */ jsx6(
|
|
465
|
+
SignIn,
|
|
466
|
+
{
|
|
467
|
+
...shared,
|
|
468
|
+
onSuccess: props.afterSignIn,
|
|
469
|
+
onForgotPassword: () => go("forgotPassword"),
|
|
470
|
+
onSignUp: () => go("signUp")
|
|
471
|
+
}
|
|
472
|
+
) : null,
|
|
473
|
+
view === "signUp" ? /* @__PURE__ */ jsx6(SignUp, { ...shared, onSuccess: props.afterSignUp, onSignIn: () => go("signIn") }) : null,
|
|
474
|
+
view === "forgotPassword" ? /* @__PURE__ */ jsx6(ForgotPassword, { ...shared, onBack: () => go("signIn") }) : null,
|
|
475
|
+
view === "resetPassword" ? /* @__PURE__ */ jsx6(ResetPassword, { ...shared, token, onSuccess: () => go("signIn") }) : null,
|
|
476
|
+
view === "verifyEmail" ? /* @__PURE__ */ jsx6(VerifyEmail, { ...shared, token }) : null
|
|
477
|
+
] }) });
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/react/components/auth/AuthModal.tsx
|
|
481
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
482
|
+
function AuthModal(props) {
|
|
483
|
+
const scoped = useAppearanceProviderValue(props.appearance);
|
|
484
|
+
const titleId = useId();
|
|
485
|
+
return /* @__PURE__ */ jsx7(AppearanceProvider, { value: scoped, children: /* @__PURE__ */ jsxs7(
|
|
486
|
+
Modal,
|
|
487
|
+
{
|
|
488
|
+
open: props.open,
|
|
489
|
+
onClose: props.onClose,
|
|
490
|
+
labelledBy: titleId,
|
|
491
|
+
size: "compact",
|
|
492
|
+
overlayKey: "authModal.overlay",
|
|
493
|
+
containerKey: "authModal.container",
|
|
494
|
+
children: [
|
|
495
|
+
/* @__PURE__ */ jsx7("h2", { id: titleId, style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }, children: "Account" }),
|
|
496
|
+
/* @__PURE__ */ jsx7(
|
|
497
|
+
AuthFlow,
|
|
498
|
+
{
|
|
499
|
+
initialView: props.initialView,
|
|
500
|
+
afterSignIn: props.afterSignIn,
|
|
501
|
+
afterSignUp: props.afterSignUp,
|
|
502
|
+
appearance: props.appearance,
|
|
503
|
+
localization: props.localization
|
|
504
|
+
}
|
|
505
|
+
)
|
|
506
|
+
]
|
|
507
|
+
}
|
|
508
|
+
) });
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export {
|
|
512
|
+
SignIn,
|
|
513
|
+
SignUp,
|
|
514
|
+
ForgotPassword,
|
|
515
|
+
ResetPassword,
|
|
516
|
+
VerifyEmail,
|
|
517
|
+
AuthFlow,
|
|
518
|
+
AuthModal
|
|
519
|
+
};
|
|
520
|
+
//# sourceMappingURL=chunk-JSDZMPCV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/components/auth/AuthModal.tsx","../src/react/components/auth/AuthFlow.tsx","../src/react/components/auth/SignIn.tsx","../src/react/components/auth/SignUp.tsx","../src/react/components/auth/ForgotPassword.tsx","../src/react/components/auth/ResetPassword.tsx","../src/react/components/auth/VerifyEmail.tsx"],"sourcesContent":["import { useId } from 'react';\nimport { AppearanceProvider, useAppearanceProviderValue, type ThrottleAppearance } from '../appearance';\nimport type { ThrottleLocalization } from '../localization';\nimport { Modal } from '../primitives';\nimport { AuthFlow, type AuthView } from './AuthFlow';\n\nexport interface AuthModalProps {\n open: boolean;\n onClose(): void;\n initialView?: AuthView;\n afterSignIn?(): void;\n afterSignUp?(): void;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n}\n\nexport function AuthModal(props: AuthModalProps) {\n const scoped = useAppearanceProviderValue(props.appearance);\n const titleId = useId();\n return (\n <AppearanceProvider value={scoped}>\n <Modal\n open={props.open}\n onClose={props.onClose}\n labelledBy={titleId}\n size=\"compact\"\n overlayKey=\"authModal.overlay\"\n containerKey=\"authModal.container\"\n >\n {/* The flow swaps its own heading per view, so the dialog carries a\n stable accessible name of its own rather than pointing at a heading\n that disappears when the view changes. */}\n <h2 id={titleId} style={{ position: 'absolute', width: 1, height: 1, overflow: 'hidden', clip: 'rect(0 0 0 0)' }}>\n Account\n </h2>\n <AuthFlow\n initialView={props.initialView}\n afterSignIn={props.afterSignIn}\n afterSignUp={props.afterSignUp}\n appearance={props.appearance}\n localization={props.localization}\n />\n </Modal>\n </AppearanceProvider>\n );\n}\n","import { useEffect, useState } from 'react';\nimport { useAppearance, type ThrottleAppearance } from '../appearance';\nimport type { ThrottleLocalization } from '../localization';\nimport { SignIn } from './SignIn';\nimport { SignUp } from './SignUp';\nimport { ForgotPassword } from './ForgotPassword';\nimport { ResetPassword } from './ResetPassword';\nimport { VerifyEmail } from './VerifyEmail';\n\nexport type AuthView = 'signIn' | 'signUp' | 'forgotPassword' | 'resetPassword' | 'verifyEmail';\n\nconst HASH_FOR: Record<AuthView, string> = {\n signIn: '#/sign-in',\n signUp: '#/sign-up',\n forgotPassword: '#/forgot-password',\n resetPassword: '#/reset-password',\n verifyEmail: '#/verify-email',\n};\nconst VIEW_FOR_HASH: Record<string, AuthView> = Object.fromEntries(\n Object.entries(HASH_FOR).map(([view, hash]) => [hash, view as AuthView]),\n) as Record<string, AuthView>;\n\nexport interface AuthFlowProps {\n initialView?: AuthView;\n /** Controlled mode: pass both to own the value yourself. */\n view?: AuthView;\n onViewChange?(view: AuthView): void;\n /** Wins over the token in the URL. */\n token?: string;\n routing?: 'virtual' | 'hash';\n afterSignIn?(): void;\n afterSignUp?(): void;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n}\n\n/**\n * Reads the location on the client only. Returns nothing while server\n * rendering, so the server and the first client render agree; the effect below\n * applies the URL-derived view afterwards.\n */\nfunction readLocation(): { view: AuthView | null; token?: string } {\n if (typeof window === 'undefined') return { view: null };\n const params = new URLSearchParams(window.location.search);\n const token = params.get('token') ?? undefined;\n const hashView = VIEW_FOR_HASH[window.location.hash];\n if (hashView) return { view: hashView, token };\n if (!token) return { view: null };\n // A token in the URL means the buyer arrived from an email. Which email is\n // not knowable in general — merchants choose their own paths — so sniff the\n // common ones and let `initialView` override when they differ.\n const path = window.location.pathname.toLowerCase();\n if (path.includes('reset')) return { view: 'resetPassword', token };\n if (path.includes('verif')) return { view: 'verifyEmail', token };\n return { view: null, token };\n}\n\nexport function AuthFlow(props: AuthFlowProps) {\n const a = useAppearance(props.appearance);\n const controlled = props.view !== undefined;\n const [internal, setInternal] = useState<AuthView>(props.initialView ?? 'signIn');\n const [urlToken, setUrlToken] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n const found = readLocation();\n if (found.token) setUrlToken(found.token);\n // An explicit initialView or a controlled view is the merchant's decision\n // and outranks whatever the URL implies.\n if (found.view && !controlled && props.initialView === undefined) setInternal(found.view);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const view = controlled ? props.view! : internal;\n const token = props.token ?? urlToken;\n\n const go = (next: AuthView) => {\n props.onViewChange?.(next);\n if (!controlled) setInternal(next);\n if (props.routing === 'hash' && typeof window !== 'undefined') {\n window.location.hash = HASH_FOR[next];\n }\n };\n\n useEffect(() => {\n if (props.routing !== 'hash' || typeof window === 'undefined') return;\n const onHashChange = () => {\n if (controlled) return;\n const hash = window.location.hash;\n // An empty hash means Back navigated past the first pushed view entry,\n // back to the plain (view-less) URL — that always means sign-in, the\n // same default the component starts on. A no-op here left the browser\n // address bar and the rendered view disagreeing after Back.\n if (hash === '') {\n setInternal(props.initialView ?? 'signIn');\n return;\n }\n const next = VIEW_FOR_HASH[hash];\n if (next) setInternal(next);\n };\n window.addEventListener('hashchange', onHashChange);\n return () => window.removeEventListener('hashchange', onHashChange);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.routing, controlled]);\n\n const shared = { appearance: props.appearance, localization: props.localization };\n\n return (\n <div {...a.rootProps}>\n <div {...a.el('authFlow.root')}>\n {view === 'signIn' ? (\n <SignIn {...shared} onSuccess={props.afterSignIn}\n onForgotPassword={() => go('forgotPassword')} onSignUp={() => go('signUp')} />\n ) : null}\n {view === 'signUp' ? (\n <SignUp {...shared} onSuccess={props.afterSignUp} onSignIn={() => go('signIn')} />\n ) : null}\n {view === 'forgotPassword' ? (\n <ForgotPassword {...shared} onBack={() => go('signIn')} />\n ) : null}\n {view === 'resetPassword' ? (\n <ResetPassword {...shared} token={token} onSuccess={() => go('signIn')} />\n ) : null}\n {view === 'verifyEmail' ? <VerifyEmail {...shared} token={token} /> : null}\n </div>\n </div>\n );\n}\n","import { useState } from 'react';\nimport type { ReactNode } from 'react';\nimport { useAuth } from '../../context';\nimport { AppearanceProvider, useAppearance, useAppearanceProviderValue, type ThrottleAppearance } from '../appearance';\nimport { useBrandLogo } from '../branding';\nimport { useCopy, type ThrottleLocalization } from '../localization';\nimport { useCardClass } from '../surface';\nimport { useAction } from '../useAction';\nimport { Alert, Button, CardBody, CardFooter, CardHeader, Field, FieldGroup } from '../primitives';\n\nexport type AuthCardVariant = 'quiet' | 'grouped' | 'branded';\n\nexport interface SignInProps {\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n /** Composition of the card. Default `'quiet'`. */\n variant?: AuthCardVariant;\n /** Rendered above the title in the `branded` variant. Falls back to `ThrottleProvider`'s `logo`. */\n logo?: ReactNode;\n onSuccess?(): void;\n onForgotPassword?(): void;\n onSignUp?(): void;\n}\n\nexport function SignIn(props: SignInProps) {\n const a = useAppearance(props.appearance);\n const cardClass = useCardClass();\n const scoped = useAppearanceProviderValue(props.appearance);\n const { t } = useCopy(props.localization);\n const { auth } = useAuth();\n const { submitting, error, run } = useAction();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const variant = props.variant ?? 'quiet';\n const branded = variant === 'branded';\n const grouped = variant === 'grouped';\n const logo = useBrandLogo(props.logo);\n\n // `quiet` puts this beside the Password label instead of as its own\n // full-width row under the primary button, so the card has only one call\n // to action. `grouped` instead sits it right-aligned below the field\n // group, since there is no per-field label row to share once the inputs\n // are joined into one control.\n const forgotLink = props.onForgotPassword ? (\n <Button variant=\"link\" size=\"xs\" elementKey=\"signIn.forgotLink\" onClick={props.onForgotPassword}>\n {t.signIn.forgotLink}\n </Button>\n ) : null;\n\n const emailField = (\n <Field label={t.signIn.email} type=\"email\" autoComplete=\"email\" required\n value={email} onChange={setEmail} elementKey=\"signIn.emailField\" grouped={grouped} />\n );\n const passwordField = (\n <Field label={t.signIn.password} type=\"password\" autoComplete=\"current-password\" required\n value={password} onChange={setPassword} elementKey=\"signIn.passwordField\" grouped={grouped}\n labelExtra={grouped ? undefined : forgotLink} />\n );\n\n return (\n <AppearanceProvider value={scoped}>\n <div {...a.rootProps}>\n <form\n {...a.el('signIn.root', cardClass)}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => { await auth.signIn(email, password); props.onSuccess?.(); });\n }}\n >\n <CardHeader\n title={t.signIn.title}\n description={branded ? t.signIn.description : undefined}\n titleKey=\"signIn.title\"\n logo={branded ? logo : null}\n logoKey=\"signIn.logo\"\n branded={branded}\n />\n <CardBody>\n {grouped ? <FieldGroup>{emailField}{passwordField}</FieldGroup> : <>{emailField}{passwordField}</>}\n {grouped && forgotLink ? <div className=\"throttle-field-group__forgot\">{forgotLink}</div> : null}\n {error ? <Alert tone=\"error\">{error}</Alert> : null}\n <Button type=\"submit\" loading={submitting} elementKey=\"signIn.submitButton\">\n {t.signIn.submitButton}\n </Button>\n </CardBody>\n {props.onSignUp ? (\n <CardFooter ruled={!branded}>\n <span className=\"throttle-muted\">{t.signIn.switchPrompt} </span>\n <Button variant=\"link\" elementKey=\"signIn.switchLink\" onClick={props.onSignUp}>\n {t.signIn.switchLink}\n </Button>\n </CardFooter>\n ) : null}\n </form>\n </div>\n </AppearanceProvider>\n );\n}\n","import { useState } from 'react';\nimport type { ReactNode } from 'react';\nimport { useAuth } from '../../context';\nimport { AppearanceProvider, useAppearance, useAppearanceProviderValue, type ThrottleAppearance } from '../appearance';\nimport { useBrandLogo } from '../branding';\nimport { useCopy, type ThrottleLocalization } from '../localization';\nimport { useCardClass } from '../surface';\nimport { useAction } from '../useAction';\nimport { Alert, Button, CardBody, CardFooter, CardHeader, Field, FieldGroup } from '../primitives';\nimport type { AuthCardVariant } from './SignIn';\n\nexport interface SignUpProps {\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n /** Composition of the card. Default `'quiet'`. */\n variant?: AuthCardVariant;\n /** Rendered above the title in the `branded` variant. Falls back to `ThrottleProvider`'s `logo`. */\n logo?: ReactNode;\n onSuccess?(): void;\n onSignIn?(): void;\n}\n\nexport function SignUp(props: SignUpProps) {\n const a = useAppearance(props.appearance);\n const cardClass = useCardClass();\n const scoped = useAppearanceProviderValue(props.appearance);\n const { t } = useCopy(props.localization);\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useAction();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const variant = props.variant ?? 'quiet';\n const branded = variant === 'branded';\n const grouped = variant === 'grouped';\n const logo = useBrandLogo(props.logo);\n\n const emailField = (\n <Field label={t.signUp.email} type=\"email\" autoComplete=\"email\" required\n value={email} onChange={setEmail} elementKey=\"signUp.emailField\" grouped={grouped} />\n );\n const passwordField = (\n <Field label={t.signUp.password} type=\"password\" autoComplete=\"new-password\" required\n value={password} onChange={setPassword} elementKey=\"signUp.passwordField\" grouped={grouped} />\n );\n\n return (\n <AppearanceProvider value={scoped}>\n <div {...a.rootProps}>\n <form\n {...a.el('signUp.root', cardClass)}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => {\n const result = await auth.register(email, password);\n if (result.status === 'active') { props.onSuccess?.(); return; }\n // `pending` covers two different server outcomes — a new account\n // awaiting verification, and a registration onto an address that\n // already has one, which sends nothing. The copy must be true for\n // both, so it says \"if that email is new\" rather than \"we sent\".\n return t.signUp.pendingNotice;\n });\n }}\n >\n <CardHeader\n title={t.signUp.title}\n description={branded ? t.signUp.description : undefined}\n titleKey=\"signUp.title\"\n logo={branded ? logo : null}\n logoKey=\"signUp.logo\"\n branded={branded}\n />\n <CardBody>\n {grouped ? <FieldGroup>{emailField}{passwordField}</FieldGroup> : <>{emailField}{passwordField}</>}\n {error ? <Alert tone=\"error\">{error}</Alert> : null}\n {notice ? <Alert tone=\"info\">{notice}</Alert> : null}\n <Button type=\"submit\" loading={submitting} elementKey=\"signUp.submitButton\">\n {t.signUp.submitButton}\n </Button>\n </CardBody>\n {props.onSignIn ? (\n <CardFooter ruled={!branded}>\n <span className=\"throttle-muted\">{t.signUp.switchPrompt} </span>\n <Button variant=\"link\" elementKey=\"signUp.switchLink\" onClick={props.onSignIn}>\n {t.signUp.switchLink}\n </Button>\n </CardFooter>\n ) : null}\n </form>\n </div>\n </AppearanceProvider>\n );\n}\n","import { useState } from 'react';\nimport type { ReactNode } from 'react';\nimport { useAuth } from '../../context';\nimport { AppearanceProvider, useAppearance, useAppearanceProviderValue, type ThrottleAppearance } from '../appearance';\nimport { useBrandLogo } from '../branding';\nimport { useCopy, type ThrottleLocalization } from '../localization';\nimport { useCardClass } from '../surface';\nimport { useAction } from '../useAction';\nimport { Alert, Button, CardBody, CardFooter, CardHeader, Field } from '../primitives';\nimport type { AuthCardVariant } from './SignIn';\n\nexport interface ForgotPasswordProps {\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n /** Composition of the card. Default `'quiet'`. A single input, so `'grouped'` renders the same as `'quiet'`. */\n variant?: AuthCardVariant;\n /** Rendered above the title in the `branded` variant. Falls back to `ThrottleProvider`'s `logo`. */\n logo?: ReactNode;\n onBack?(): void;\n}\n\nexport function ForgotPassword(props: ForgotPasswordProps) {\n const a = useAppearance(props.appearance);\n const cardClass = useCardClass();\n const scoped = useAppearanceProviderValue(props.appearance);\n const { t } = useCopy(props.localization);\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useAction();\n const [email, setEmail] = useState('');\n const branded = (props.variant ?? 'quiet') === 'branded';\n const logo = useBrandLogo(props.logo);\n\n return (\n <AppearanceProvider value={scoped}>\n <div {...a.rootProps}>\n <form\n {...a.el('forgotPassword.root', cardClass)}\n onSubmit={(e) => {\n e.preventDefault();\n // The server answers identically whether or not the address exists,\n // and so must this: a notice that varied would enumerate accounts.\n void run(async () => { await auth.forgotPassword(email); return t.forgotPassword.sentNotice; });\n }}\n >\n <CardHeader\n title={t.forgotPassword.title}\n description={branded ? t.forgotPassword.description : undefined}\n titleKey=\"forgotPassword.title\"\n logo={branded ? logo : null}\n logoKey=\"forgotPassword.logo\"\n branded={branded}\n />\n <CardBody>\n <Field label={t.forgotPassword.email} type=\"email\" autoComplete=\"email\" required\n value={email} onChange={setEmail} elementKey=\"forgotPassword.emailField\" />\n {error ? <Alert tone=\"error\">{error}</Alert> : null}\n {notice ? <Alert tone=\"success\">{notice}</Alert> : null}\n <Button type=\"submit\" loading={submitting} elementKey=\"forgotPassword.submitButton\">\n {t.forgotPassword.submitButton}\n </Button>\n </CardBody>\n {props.onBack ? (\n <CardFooter ruled={!branded}>\n <Button variant=\"link\" elementKey=\"forgotPassword.backLink\" onClick={props.onBack}>\n {t.forgotPassword.backLink}\n </Button>\n </CardFooter>\n ) : null}\n </form>\n </div>\n </AppearanceProvider>\n );\n}\n","import { useState } from 'react';\nimport type { ReactNode } from 'react';\nimport { useAuth } from '../../context';\nimport { AppearanceProvider, useAppearance, useAppearanceProviderValue, type ThrottleAppearance } from '../appearance';\nimport { useBrandLogo } from '../branding';\nimport { useCopy, type ThrottleLocalization } from '../localization';\nimport { useCardClass } from '../surface';\nimport { useAction } from '../useAction';\nimport { Alert, Button, CardBody, CardHeader, Field } from '../primitives';\nimport type { AuthCardVariant } from './SignIn';\n\nexport interface ResetPasswordProps {\n token?: string;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n /** Composition of the card. Default `'quiet'`. A single input, so `'grouped'` renders the same as `'quiet'`. */\n variant?: AuthCardVariant;\n /** Rendered above the title in the `branded` variant. Falls back to `ThrottleProvider`'s `logo`. */\n logo?: ReactNode;\n onSuccess?(): void;\n}\n\nexport function ResetPassword(props: ResetPasswordProps) {\n const a = useAppearance(props.appearance);\n const cardClass = useCardClass();\n const scoped = useAppearanceProviderValue(props.appearance);\n const { t } = useCopy(props.localization);\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useAction();\n const [password, setPassword] = useState('');\n const branded = (props.variant ?? 'quiet') === 'branded';\n const logo = useBrandLogo(props.logo);\n\n return (\n <AppearanceProvider value={scoped}>\n <div {...a.rootProps}>\n <div {...a.el('resetPassword.root', cardClass)}>\n <CardHeader\n title={t.resetPassword.title}\n titleKey=\"resetPassword.title\"\n logo={branded ? logo : null}\n logoKey=\"resetPassword.logo\"\n branded={branded}\n />\n <CardBody>\n {!props.token ? (\n // No form at all without a token. A submit button that can only\n // fail is worse than an explanation.\n <Alert tone=\"error\">{t.resetPassword.missingToken}</Alert>\n ) : (\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => {\n await auth.resetPassword(props.token!, password);\n props.onSuccess?.();\n return t.resetPassword.doneNotice;\n });\n }}\n >\n <div className=\"throttle-stack\">\n <Field label={t.resetPassword.password} type=\"password\" autoComplete=\"new-password\" required\n value={password} onChange={setPassword} elementKey=\"resetPassword.passwordField\" />\n {error ? <Alert tone=\"error\">{error}</Alert> : null}\n {notice ? <Alert tone=\"success\">{notice}</Alert> : null}\n <Button type=\"submit\" loading={submitting} elementKey=\"resetPassword.submitButton\">\n {t.resetPassword.submitButton}\n </Button>\n </div>\n </form>\n )}\n </CardBody>\n </div>\n </div>\n </AppearanceProvider>\n );\n}\n","import { useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport { useAuth } from '../../context';\nimport { AppearanceProvider, useAppearance, useAppearanceProviderValue, type ThrottleAppearance } from '../appearance';\nimport { useBrandLogo } from '../branding';\nimport { useCopy, type ThrottleLocalization } from '../localization';\nimport { useCardClass } from '../surface';\nimport { useAction } from '../useAction';\nimport { Alert, Button, CardBody, CardHeader, Spinner } from '../primitives';\nimport type { AuthCardVariant } from './SignIn';\n\nexport interface VerifyEmailProps {\n token?: string;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n /** Composition of the card. Default `'quiet'`. This card has no inputs, so `'grouped'` renders the same as `'quiet'`. */\n variant?: AuthCardVariant;\n /** Rendered above the title in the `branded` variant. Falls back to `ThrottleProvider`'s `logo`. */\n logo?: ReactNode;\n onSuccess?(): void;\n}\n\nexport function VerifyEmail(props: VerifyEmailProps) {\n const a = useAppearance(props.appearance);\n const cardClass = useCardClass();\n const scoped = useAppearanceProviderValue(props.appearance);\n const { t, errorMessage } = useCopy(props.localization);\n const { auth } = useAuth();\n const { submitting, notice, run } = useAction();\n const [state, setState] = useState<'idle' | 'verifying' | 'done' | 'failed'>(props.token ? 'verifying' : 'idle');\n const [error, setError] = useState<string | null>(null);\n const branded = (props.variant ?? 'quiet') === 'branded';\n const logo = useBrandLogo(props.logo);\n // StrictMode mounts effects twice in development, and a verification token is\n // single-use: a second call would fail with token_already_used and show the\n // buyer an error for a link that worked.\n const attempted = useRef<string | null>(null);\n\n useEffect(() => {\n const token = props.token;\n if (!token || attempted.current === token) return;\n attempted.current = token;\n setState('verifying');\n auth\n .verifyEmail(token)\n .then(() => { setState('done'); props.onSuccess?.(); })\n .catch((err) => { setError(errorMessage(err)); setState('failed'); });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.token, auth]);\n\n return (\n <AppearanceProvider value={scoped}>\n <div {...a.rootProps}>\n <div {...a.el('verifyEmail.root', cardClass)}>\n <CardHeader\n title={t.verifyEmail.title}\n titleKey=\"verifyEmail.title\"\n logo={branded ? logo : null}\n logoKey=\"verifyEmail.logo\"\n branded={branded}\n />\n <CardBody>\n {state === 'verifying' ? <p><Spinner /> {t.verifyEmail.verifying}</p> : null}\n {state === 'done' ? <Alert tone=\"success\">{t.verifyEmail.success}</Alert> : null}\n {state === 'failed' && error ? <Alert tone=\"error\">{error}</Alert> : null}\n {state === 'idle' ? <p className=\"throttle-muted\">{t.verifyEmail.instructions}</p> : null}\n {notice ? <Alert tone=\"success\">{notice}</Alert> : null}\n {state !== 'done' ? (\n <Button\n variant=\"secondary\"\n loading={submitting}\n elementKey=\"verifyEmail.resendButton\"\n onClick={() => void run(async () => {\n await auth.resendVerification();\n return t.verifyEmail.resentNotice;\n })}\n >\n {t.verifyEmail.resendButton}\n </Button>\n ) : null}\n </CardBody>\n </div>\n </div>\n </AppearanceProvider>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","useState","Fragment","jsx","jsxs","useState","useState","jsx","jsxs","useState","useState","jsx","jsxs","useState","useState","jsx","jsxs","useState","jsx","jsxs","useState","useEffect","jsx","jsxs"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
useCopy
|
|
4
|
+
} from "./chunk-CUNMJ2PH.js";
|
|
5
|
+
|
|
6
|
+
// src/react/components/useAction.ts
|
|
7
|
+
import { useCallback, useRef, useState } from "react";
|
|
8
|
+
function useAction() {
|
|
9
|
+
const { errorMessage } = useCopy();
|
|
10
|
+
const [submitting, setSubmitting] = useState(false);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const [notice, setNotice] = useState(null);
|
|
13
|
+
const inFlight = useRef(false);
|
|
14
|
+
const run = useCallback(
|
|
15
|
+
async (fn) => {
|
|
16
|
+
if (inFlight.current) return;
|
|
17
|
+
inFlight.current = true;
|
|
18
|
+
setSubmitting(true);
|
|
19
|
+
setError(null);
|
|
20
|
+
setNotice(null);
|
|
21
|
+
try {
|
|
22
|
+
const message = await fn();
|
|
23
|
+
if (typeof message === "string") setNotice(message);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
setError(errorMessage(err));
|
|
26
|
+
} finally {
|
|
27
|
+
inFlight.current = false;
|
|
28
|
+
setSubmitting(false);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
[errorMessage]
|
|
32
|
+
);
|
|
33
|
+
return { submitting, error, notice, run };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
useAction
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=chunk-QPVS6JXP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/components/useAction.ts"],"sourcesContent":["import { useCallback, useRef, useState } from 'react';\nimport { useCopy } from './localization';\n\nexport interface Action {\n submitting: boolean;\n error: string | null;\n notice: string | null;\n run(fn: () => Promise<string | void>): Promise<void>;\n}\n\n/**\n * Submit-once semantics plus localized error copy. Deliberately a separate hook\n * from `../forms/useFormAction`: that one maps errors through `messageForError`\n * with no path to a merchant's `localization.errors`, and `./forms` is a\n * published entry point whose contract is that it does not change.\n */\nexport function useAction(): Action {\n const { errorMessage } = useCopy();\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(\n 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(errorMessage(err));\n } finally {\n inFlight.current = false;\n setSubmitting(false);\n }\n },\n [errorMessage],\n );\n\n return { submitting, error, notice, run };\n}\n"],"mappings":";;;;;;AAAA,SAAS,aAAa,QAAQ,gBAAgB;AAgBvC,SAAS,YAAoB;AAClC,QAAM,EAAE,aAAa,IAAI,QAAQ;AACjC,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwB,IAAI;AACxD,QAAM,WAAW,OAAO,KAAK;AAE7B,QAAM,MAAM;AAAA,IACV,OAAO,OAAqC;AAC1C,UAAI,SAAS,QAAS;AACtB,eAAS,UAAU;AACnB,oBAAc,IAAI;AAClB,eAAS,IAAI;AACb,gBAAU,IAAI;AACd,UAAI;AACF,cAAM,UAAU,MAAM,GAAG;AACzB,YAAI,OAAO,YAAY,SAAU,WAAU,OAAO;AAAA,MACpD,SAAS,KAAK;AACZ,iBAAS,aAAa,GAAG,CAAC;AAAA,MAC5B,UAAE;AACA,iBAAS,UAAU;AACnB,sBAAc,KAAK;AAAA,MACrB;AAAA,IACF;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,SAAO,EAAE,YAAY,OAAO,QAAQ,IAAI;AAC1C;","names":[]}
|