@stackframe/stack 2.6.11 → 2.6.15
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/CHANGELOG.md +38 -0
- package/dist/components/passkey-button.d.mts +7 -0
- package/dist/components/passkey-button.d.ts +7 -0
- package/dist/components/passkey-button.js +58 -0
- package/dist/components/passkey-button.js.map +1 -0
- package/dist/components-page/account-settings.js +343 -102
- package/dist/components-page/account-settings.js.map +1 -1
- package/dist/components-page/auth-page.d.mts +1 -0
- package/dist/components-page/auth-page.d.ts +1 -0
- package/dist/components-page/auth-page.js +5 -1
- package/dist/components-page/auth-page.js.map +1 -1
- package/dist/esm/components/passkey-button.js +34 -0
- package/dist/esm/components/passkey-button.js.map +1 -0
- package/dist/esm/components-page/account-settings.js +344 -103
- package/dist/esm/components-page/account-settings.js.map +1 -1
- package/dist/esm/components-page/auth-page.js +5 -1
- package/dist/esm/components-page/auth-page.js.map +1 -1
- package/dist/esm/generated/global-css.js +1 -1
- package/dist/esm/generated/global-css.js.map +1 -1
- package/dist/esm/generated/quetzal-translations.js +2268 -1824
- package/dist/esm/generated/quetzal-translations.js.map +1 -1
- package/dist/esm/lib/stack-app.js +197 -13
- package/dist/esm/lib/stack-app.js.map +1 -1
- package/dist/generated/global-css.d.mts +1 -1
- package/dist/generated/global-css.d.ts +1 -1
- package/dist/generated/global-css.js +1 -1
- package/dist/generated/global-css.js.map +1 -1
- package/dist/generated/quetzal-translations.d.mts +2 -2
- package/dist/generated/quetzal-translations.d.ts +2 -2
- package/dist/generated/quetzal-translations.js +2268 -1824
- package/dist/generated/quetzal-translations.js.map +1 -1
- package/dist/lib/stack-app.d.mts +60 -19
- package/dist/lib/stack-app.d.ts +60 -19
- package/dist/lib/stack-app.js +197 -13
- package/dist/lib/stack-app.js.map +1 -1
- package/package.json +5 -4
|
@@ -80,11 +80,11 @@ function AccountSettings(props) {
|
|
|
80
80
|
content: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProfilePage, {})
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
|
-
title: t("
|
|
83
|
+
title: t("Emails & Auth"),
|
|
84
84
|
type: "item",
|
|
85
|
-
id: "
|
|
85
|
+
id: "auth",
|
|
86
86
|
icon: import_lucide_react.ShieldCheck,
|
|
87
|
-
content: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
87
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmailsAndAuthPage, {})
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
title: t("Settings"),
|
|
@@ -181,54 +181,284 @@ function ProfilePage() {
|
|
|
181
181
|
)
|
|
182
182
|
] });
|
|
183
183
|
}
|
|
184
|
-
function
|
|
185
|
-
const
|
|
184
|
+
function EmailsSection() {
|
|
185
|
+
const { t } = (0, import_translations.useTranslation)();
|
|
186
|
+
const user = (0, import__.useUser)({ or: "redirect" });
|
|
187
|
+
const contactChannels = user.useContactChannels();
|
|
188
|
+
const [addingEmail, setAddingEmail] = (0, import_react.useState)(contactChannels.length === 0);
|
|
189
|
+
const [addingEmailLoading, setAddingEmailLoading] = (0, import_react.useState)(false);
|
|
190
|
+
const [addedEmail, setAddedEmail] = (0, import_react.useState)(null);
|
|
191
|
+
const isLastEmail = contactChannels.filter((x) => x.usedForAuth && x.type === "email").length === 1;
|
|
192
|
+
(0, import_react.useEffect)(() => {
|
|
193
|
+
if (addedEmail) {
|
|
194
|
+
(0, import_promises.runAsynchronously)(async () => {
|
|
195
|
+
const cc = contactChannels.find((x) => x.value === addedEmail);
|
|
196
|
+
if (cc && !cc.isVerified) {
|
|
197
|
+
await cc.sendVerificationEmail();
|
|
198
|
+
}
|
|
199
|
+
setAddedEmail(null);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}, [contactChannels, addedEmail]);
|
|
203
|
+
const emailSchema = (0, import_schema_fields.yupObject)({
|
|
204
|
+
email: (0, import_schema_fields.yupString)().email(t("Please enter a valid email address")).notOneOf(contactChannels.map((x) => x.value), t("Email already exists")).required(t("Email is required"))
|
|
205
|
+
});
|
|
206
|
+
const { register, handleSubmit, formState: { errors }, reset } = (0, import_react_hook_form.useForm)({
|
|
207
|
+
resolver: (0, import_yup.yupResolver)(emailSchema)
|
|
208
|
+
});
|
|
209
|
+
const onSubmit = async (data) => {
|
|
210
|
+
setAddingEmailLoading(true);
|
|
211
|
+
try {
|
|
212
|
+
await user.createContactChannel({ type: "email", value: data.email, usedForAuth: false });
|
|
213
|
+
setAddedEmail(data.email);
|
|
214
|
+
} finally {
|
|
215
|
+
setAddingEmailLoading(false);
|
|
216
|
+
}
|
|
217
|
+
setAddingEmail(false);
|
|
218
|
+
reset();
|
|
219
|
+
};
|
|
220
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
221
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col md:flex-row justify-between mb-4 gap-4", children: [
|
|
222
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Typography, { className: "font-medium", children: t("Emails") }),
|
|
223
|
+
addingEmail ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
224
|
+
"form",
|
|
225
|
+
{
|
|
226
|
+
onSubmit: (e) => {
|
|
227
|
+
e.preventDefault();
|
|
228
|
+
(0, import_promises.runAsynchronously)(handleSubmit(onSubmit));
|
|
229
|
+
},
|
|
230
|
+
className: "flex flex-col",
|
|
231
|
+
children: [
|
|
232
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-2", children: [
|
|
233
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
234
|
+
import_stack_ui.Input,
|
|
235
|
+
{
|
|
236
|
+
...register("email"),
|
|
237
|
+
placeholder: t("Enter email")
|
|
238
|
+
}
|
|
239
|
+
),
|
|
240
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Button, { type: "submit", loading: addingEmailLoading, children: t("Add") }),
|
|
241
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
242
|
+
import_stack_ui.Button,
|
|
243
|
+
{
|
|
244
|
+
variant: "secondary",
|
|
245
|
+
onClick: () => {
|
|
246
|
+
setAddingEmail(false);
|
|
247
|
+
reset();
|
|
248
|
+
},
|
|
249
|
+
children: t("Cancel")
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
] }),
|
|
253
|
+
errors.email && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_form_warning.FormWarningText, { text: errors.email.message })
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex md:justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Button, { variant: "secondary", onClick: () => setAddingEmail(true), children: t("Add an email") }) })
|
|
257
|
+
] }),
|
|
258
|
+
contactChannels.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "border rounded-md", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Table, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.TableBody, { children: contactChannels.filter((x) => x.type === "email").sort((a, b) => {
|
|
259
|
+
if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1;
|
|
260
|
+
if (a.isVerified !== b.isVerified) return a.isVerified ? -1 : 1;
|
|
261
|
+
return 0;
|
|
262
|
+
}).map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_stack_ui.TableRow, { children: [
|
|
263
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col md:flex-row gap-2 md:gap-4", children: [
|
|
264
|
+
x.value,
|
|
265
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-2", children: [
|
|
266
|
+
x.isPrimary ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Badge, { children: t("Primary") }) : null,
|
|
267
|
+
!x.isVerified ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Badge, { variant: "destructive", children: t("Unverified") }) : null,
|
|
268
|
+
x.usedForAuth ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Badge, { variant: "outline", children: t("Used for sign-in") }) : null
|
|
269
|
+
] })
|
|
270
|
+
] }) }),
|
|
271
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.TableCell, { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.ActionCell, { items: [
|
|
272
|
+
...!x.isVerified ? [{
|
|
273
|
+
item: t("Send verification email"),
|
|
274
|
+
onClick: async () => {
|
|
275
|
+
await x.sendVerificationEmail();
|
|
276
|
+
}
|
|
277
|
+
}] : [],
|
|
278
|
+
...!x.isPrimary && x.isVerified ? [{
|
|
279
|
+
item: t("Set as primary"),
|
|
280
|
+
onClick: async () => {
|
|
281
|
+
await x.update({ isPrimary: true });
|
|
282
|
+
}
|
|
283
|
+
}] : !x.isPrimary ? [{
|
|
284
|
+
item: t("Set as primary"),
|
|
285
|
+
onClick: async () => {
|
|
286
|
+
},
|
|
287
|
+
disabled: true,
|
|
288
|
+
disabledTooltip: t("Please verify your email first")
|
|
289
|
+
}] : [],
|
|
290
|
+
...!x.usedForAuth && x.isVerified ? [{
|
|
291
|
+
item: t("Use for sign-in"),
|
|
292
|
+
onClick: async () => {
|
|
293
|
+
await x.update({ usedForAuth: true });
|
|
294
|
+
}
|
|
295
|
+
}] : [],
|
|
296
|
+
...x.usedForAuth && !isLastEmail ? [{
|
|
297
|
+
item: t("Stop using for sign-in"),
|
|
298
|
+
onClick: async () => {
|
|
299
|
+
await x.update({ usedForAuth: false });
|
|
300
|
+
}
|
|
301
|
+
}] : x.usedForAuth ? [{
|
|
302
|
+
item: t("Stop using for sign-in"),
|
|
303
|
+
onClick: async () => {
|
|
304
|
+
},
|
|
305
|
+
disabled: true,
|
|
306
|
+
disabledTooltip: t("You can not remove your last sign-in email")
|
|
307
|
+
}] : [],
|
|
308
|
+
...!isLastEmail || !x.usedForAuth ? [{
|
|
309
|
+
item: t("Remove"),
|
|
310
|
+
onClick: async () => {
|
|
311
|
+
await x.delete();
|
|
312
|
+
},
|
|
313
|
+
danger: true
|
|
314
|
+
}] : [{
|
|
315
|
+
item: t("Remove"),
|
|
316
|
+
onClick: async () => {
|
|
317
|
+
},
|
|
318
|
+
disabled: true,
|
|
319
|
+
disabledTooltip: t("You can not remove your last sign-in email")
|
|
320
|
+
}]
|
|
321
|
+
] }) })
|
|
322
|
+
] }, x.id)) }) }) }) : null
|
|
323
|
+
] });
|
|
324
|
+
}
|
|
325
|
+
function EmailsAndAuthPage() {
|
|
186
326
|
const passwordSection = usePasswordSection();
|
|
187
327
|
const mfaSection = useMfaSection();
|
|
328
|
+
const otpSection = useOtpSection();
|
|
329
|
+
const passkeySection = usePasskeySection();
|
|
188
330
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PageLayout, { children: [
|
|
189
|
-
|
|
331
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmailsSection, {}),
|
|
190
332
|
passwordSection,
|
|
333
|
+
passkeySection,
|
|
334
|
+
otpSection,
|
|
191
335
|
mfaSection
|
|
192
336
|
] });
|
|
193
337
|
}
|
|
194
|
-
function
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
338
|
+
function usePasskeySection() {
|
|
339
|
+
const { t } = (0, import_translations.useTranslation)();
|
|
340
|
+
const user = (0, import__.useUser)({ or: "throw" });
|
|
341
|
+
const stackApp = (0, import__.useStackApp)();
|
|
342
|
+
const project = stackApp.useProject();
|
|
343
|
+
const contactChannels = user.useContactChannels();
|
|
344
|
+
const has_passkey = user.passkeyAuthEnabled;
|
|
345
|
+
const isLastAuth = user.passkeyAuthEnabled && !user.hasPassword && user.oauthProviders.length === 0 && !user.otpAuthEnabled;
|
|
346
|
+
const [showConfirmationModal, setShowConfirmationModal] = (0, import_react.useState)(false);
|
|
347
|
+
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
|
|
348
|
+
if (!project.config.passkeyEnabled) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const handleDeletePasskey = async () => {
|
|
352
|
+
await user.update({ passkeyAuthEnabled: false });
|
|
353
|
+
setShowConfirmationModal(false);
|
|
354
|
+
};
|
|
355
|
+
const handleAddNewPasskey = async () => {
|
|
356
|
+
await user.registerPasskey();
|
|
357
|
+
};
|
|
358
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { title: t("Passkey"), description: has_passkey ? t("Passkey registered") : t("Register a passkey"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex md:justify-end", children: [
|
|
359
|
+
!hasValidEmail && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Typography, { variant: "secondary", type: "label", children: t("To enable Passkey sign-in, please add a verified email and set it as your sign-in email.") }),
|
|
360
|
+
hasValidEmail && has_passkey && isLastAuth && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Typography, { variant: "secondary", type: "label", children: t("Passkey sign-in is enabled and cannot be disabled as it is currently the only sign-in method") }),
|
|
361
|
+
!has_passkey && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Button, { onClick: handleAddNewPasskey, variant: "secondary", children: t("Add new passkey") }) }),
|
|
362
|
+
hasValidEmail && has_passkey && !isLastAuth && !showConfirmationModal && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
363
|
+
import_stack_ui.Button,
|
|
364
|
+
{
|
|
365
|
+
variant: "secondary",
|
|
366
|
+
onClick: () => setShowConfirmationModal(true),
|
|
367
|
+
children: t("Delete Passkey")
|
|
368
|
+
}
|
|
369
|
+
),
|
|
370
|
+
hasValidEmail && has_passkey && !isLastAuth && showConfirmationModal && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
371
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Typography, { variant: "destructive", children: t("Are you sure you want to disable Passkey sign-in? You will not be able to sign in with your passkey anymore.") }),
|
|
372
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-2", children: [
|
|
373
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
374
|
+
import_stack_ui.Button,
|
|
375
|
+
{
|
|
376
|
+
variant: "destructive",
|
|
377
|
+
onClick: handleDeletePasskey,
|
|
378
|
+
children: t("Disable")
|
|
379
|
+
}
|
|
380
|
+
),
|
|
381
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
382
|
+
import_stack_ui.Button,
|
|
383
|
+
{
|
|
384
|
+
variant: "secondary",
|
|
385
|
+
onClick: () => setShowConfirmationModal(false),
|
|
386
|
+
children: t("Cancel")
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
] })
|
|
390
|
+
] })
|
|
391
|
+
] }) }) });
|
|
201
392
|
}
|
|
202
|
-
function
|
|
393
|
+
function useOtpSection() {
|
|
203
394
|
const { t } = (0, import_translations.useTranslation)();
|
|
204
|
-
const user = (0, import__.useUser)({ or: "
|
|
205
|
-
const
|
|
206
|
-
|
|
395
|
+
const user = (0, import__.useUser)({ or: "throw" });
|
|
396
|
+
const project = (0, import__.useStackApp)().useProject();
|
|
397
|
+
const contactChannels = user.useContactChannels();
|
|
398
|
+
const isLastAuth = user.otpAuthEnabled && !user.hasPassword && user.oauthProviders.length === 0 && !user.passkeyAuthEnabled;
|
|
399
|
+
const [disabling, setDisabling] = (0, import_react.useState)(false);
|
|
400
|
+
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
|
|
401
|
+
if (!project.config.magicLinkEnabled) {
|
|
207
402
|
return null;
|
|
208
403
|
}
|
|
209
|
-
|
|
210
|
-
|
|
404
|
+
const handleDisableOTP = async () => {
|
|
405
|
+
await user.update({ otpAuthEnabled: false });
|
|
406
|
+
setDisabling(false);
|
|
407
|
+
};
|
|
408
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { title: t("OTP sign-in"), description: user.otpAuthEnabled ? t("OTP/magic link sign-in is currently enabled.") : t("Enable sign-in via magic link or OTP sent to your sign-in emails."), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex md:justify-end", children: hasValidEmail ? user.otpAuthEnabled ? !isLastAuth ? !disabling ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
409
|
+
import_stack_ui.Button,
|
|
211
410
|
{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
children:
|
|
411
|
+
variant: "secondary",
|
|
412
|
+
onClick: () => setDisabling(true),
|
|
413
|
+
children: t("Disable OTP")
|
|
414
|
+
}
|
|
415
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
416
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Typography, { variant: "destructive", children: t("Are you sure you want to disable OTP sign-in? You will not be able to sign in with only emails anymore.") }),
|
|
417
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-2", children: [
|
|
418
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
215
419
|
import_stack_ui.Button,
|
|
216
420
|
{
|
|
217
|
-
|
|
218
|
-
onClick:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
421
|
+
variant: "destructive",
|
|
422
|
+
onClick: handleDisableOTP,
|
|
423
|
+
children: t("Disable")
|
|
424
|
+
}
|
|
425
|
+
),
|
|
426
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
427
|
+
import_stack_ui.Button,
|
|
428
|
+
{
|
|
429
|
+
variant: "secondary",
|
|
430
|
+
onClick: () => setDisabling(false),
|
|
431
|
+
children: t("Cancel")
|
|
223
432
|
}
|
|
224
|
-
)
|
|
433
|
+
)
|
|
434
|
+
] })
|
|
435
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Typography, { variant: "secondary", type: "label", children: t("OTP sign-in is enabled and cannot be disabled as it is currently the only sign-in method") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
436
|
+
import_stack_ui.Button,
|
|
437
|
+
{
|
|
438
|
+
variant: "secondary",
|
|
439
|
+
onClick: async () => {
|
|
440
|
+
await user.update({ otpAuthEnabled: true });
|
|
441
|
+
},
|
|
442
|
+
children: t("Enable OTP")
|
|
225
443
|
}
|
|
226
|
-
);
|
|
444
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Typography, { variant: "secondary", type: "label", children: t("To enable OTP sign-in, please add a verified email and set it as your sign-in email.") }) }) });
|
|
445
|
+
}
|
|
446
|
+
function SettingsPage() {
|
|
447
|
+
const deleteAccountSection = useDeleteAccountSection();
|
|
448
|
+
const signOutSection = useSignOutSection();
|
|
449
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PageLayout, { children: [
|
|
450
|
+
deleteAccountSection,
|
|
451
|
+
signOutSection
|
|
452
|
+
] });
|
|
227
453
|
}
|
|
228
454
|
function usePasswordSection() {
|
|
229
455
|
const { t } = (0, import_translations.useTranslation)();
|
|
456
|
+
const user = (0, import__.useUser)({ or: "throw" });
|
|
457
|
+
const contactChannels = user.useContactChannels();
|
|
458
|
+
const [changingPassword, setChangingPassword] = (0, import_react.useState)(false);
|
|
459
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
230
460
|
const passwordSchema = (0, import_schema_fields.yupObject)({
|
|
231
|
-
oldPassword: (0, import_schema_fields.yupString)().required(t("Please enter your old password")),
|
|
461
|
+
oldPassword: user.hasPassword ? (0, import_schema_fields.yupString)().required(t("Please enter your old password")) : (0, import_schema_fields.yupString)(),
|
|
232
462
|
newPassword: (0, import_schema_fields.yupString)().required(t("Please enter your password")).test({
|
|
233
463
|
name: "is-valid-password",
|
|
234
464
|
test: (value, ctx) => {
|
|
@@ -242,23 +472,20 @@ function usePasswordSection() {
|
|
|
242
472
|
}),
|
|
243
473
|
newPasswordRepeat: (0, import_schema_fields.yupString)().nullable().oneOf([yup.ref("newPassword"), "", null], t("Passwords do not match")).required(t("Please repeat your password"))
|
|
244
474
|
});
|
|
245
|
-
const user = (0, import__.useUser)({ or: "throw" });
|
|
246
|
-
const [changingPassword, setChangingPassword] = (0, import_react.useState)(false);
|
|
247
475
|
const { register, handleSubmit, setError, formState: { errors }, clearErrors, reset } = (0, import_react_hook_form.useForm)({
|
|
248
476
|
resolver: (0, import_yup.yupResolver)(passwordSchema)
|
|
249
477
|
});
|
|
250
|
-
const
|
|
251
|
-
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
478
|
+
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
|
|
252
479
|
const onSubmit = async (data) => {
|
|
253
480
|
setLoading(true);
|
|
254
481
|
try {
|
|
255
482
|
const { oldPassword, newPassword } = data;
|
|
256
|
-
const error = await user.updatePassword({ oldPassword, newPassword });
|
|
483
|
+
const error = user.hasPassword ? await user.updatePassword({ oldPassword, newPassword }) : await user.setPassword({ password: newPassword });
|
|
257
484
|
if (error) {
|
|
258
485
|
setError("oldPassword", { type: "manual", message: t("Incorrect password") });
|
|
259
486
|
} else {
|
|
260
487
|
reset();
|
|
261
|
-
|
|
488
|
+
setChangingPassword(false);
|
|
262
489
|
}
|
|
263
490
|
} finally {
|
|
264
491
|
setLoading(false);
|
|
@@ -266,69 +493,83 @@ function usePasswordSection() {
|
|
|
266
493
|
};
|
|
267
494
|
const registerPassword = register("newPassword");
|
|
268
495
|
const registerPasswordRepeat = register("newPasswordRepeat");
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
496
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
497
|
+
Section,
|
|
498
|
+
{
|
|
499
|
+
title: t("Password"),
|
|
500
|
+
description: user.hasPassword ? t("Update your password") : t("Set a password for your account"),
|
|
501
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-4", children: !changingPassword ? hasValidEmail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
502
|
+
import_stack_ui.Button,
|
|
503
|
+
{
|
|
504
|
+
variant: "secondary",
|
|
505
|
+
onClick: () => setChangingPassword(true),
|
|
506
|
+
children: user.hasPassword ? t("Update password") : t("Set password")
|
|
507
|
+
}
|
|
508
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Typography, { variant: "secondary", type: "label", children: t("To set a password, please add a verified email and set it as your sign-in email.") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
509
|
+
"form",
|
|
510
|
+
{
|
|
511
|
+
onSubmit: (e) => (0, import_promises.runAsynchronouslyWithAlert)(handleSubmit(onSubmit)(e)),
|
|
512
|
+
noValidate: true,
|
|
513
|
+
children: [
|
|
514
|
+
user.hasPassword && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
515
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Label, { htmlFor: "old-password", className: "mb-1", children: t("Old password") }),
|
|
516
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
517
|
+
import_stack_ui.Input,
|
|
518
|
+
{
|
|
519
|
+
id: "old-password",
|
|
520
|
+
type: "password",
|
|
521
|
+
...register("oldPassword")
|
|
522
|
+
}
|
|
523
|
+
),
|
|
524
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_form_warning.FormWarningText, { text: errors.oldPassword?.message?.toString() })
|
|
525
|
+
] }),
|
|
526
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Label, { htmlFor: "new-password", className: "mt-4 mb-1", children: t("New password") }),
|
|
527
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
528
|
+
import_stack_ui.PasswordInput,
|
|
529
|
+
{
|
|
530
|
+
id: "new-password",
|
|
531
|
+
...registerPassword,
|
|
532
|
+
onChange: (e) => {
|
|
533
|
+
clearErrors("newPassword");
|
|
534
|
+
clearErrors("newPasswordRepeat");
|
|
535
|
+
(0, import_promises.runAsynchronously)(registerPassword.onChange(e));
|
|
536
|
+
}
|
|
309
537
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
538
|
+
),
|
|
539
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_form_warning.FormWarningText, { text: errors.newPassword?.message?.toString() }),
|
|
540
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Label, { htmlFor: "repeat-password", className: "mt-4 mb-1", children: t("Repeat new password") }),
|
|
541
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
542
|
+
import_stack_ui.PasswordInput,
|
|
543
|
+
{
|
|
544
|
+
id: "repeat-password",
|
|
545
|
+
...registerPasswordRepeat,
|
|
546
|
+
onChange: (e) => {
|
|
547
|
+
clearErrors("newPassword");
|
|
548
|
+
clearErrors("newPasswordRepeat");
|
|
549
|
+
(0, import_promises.runAsynchronously)(registerPasswordRepeat.onChange(e));
|
|
550
|
+
}
|
|
323
551
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
552
|
+
),
|
|
553
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_form_warning.FormWarningText, { text: errors.newPasswordRepeat?.message?.toString() }),
|
|
554
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-6 flex gap-4", children: [
|
|
555
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Button, { type: "submit", loading, children: user.hasPassword ? t("Update Password") : t("Set Password") }),
|
|
556
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
557
|
+
import_stack_ui.Button,
|
|
558
|
+
{
|
|
559
|
+
variant: "secondary",
|
|
560
|
+
onClick: () => {
|
|
561
|
+
setChangingPassword(false);
|
|
562
|
+
reset();
|
|
563
|
+
},
|
|
564
|
+
children: t("Cancel")
|
|
565
|
+
}
|
|
566
|
+
)
|
|
567
|
+
] })
|
|
568
|
+
]
|
|
569
|
+
}
|
|
570
|
+
) })
|
|
571
|
+
}
|
|
572
|
+
);
|
|
332
573
|
}
|
|
333
574
|
function useMfaSection() {
|
|
334
575
|
const { t } = (0, import_translations.useTranslation)();
|
|
@@ -359,7 +600,7 @@ function useMfaSection() {
|
|
|
359
600
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
360
601
|
Section,
|
|
361
602
|
{
|
|
362
|
-
title: t("Multi-factor
|
|
603
|
+
title: t("Multi-factor authentication"),
|
|
363
604
|
description: isEnabled ? t("Multi-factor authentication is currently enabled.") : t("Multi-factor authentication is currently disabled."),
|
|
364
605
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
365
606
|
!isEnabled && generatedSecret && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -402,18 +643,18 @@ function useMfaSection() {
|
|
|
402
643
|
totpMultiFactorSecret: null
|
|
403
644
|
});
|
|
404
645
|
},
|
|
405
|
-
children: t("Disable")
|
|
646
|
+
children: t("Disable MFA")
|
|
406
647
|
}
|
|
407
648
|
) : !generatedSecret && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
408
649
|
import_stack_ui.Button,
|
|
409
650
|
{
|
|
410
|
-
variant: "
|
|
651
|
+
variant: "secondary",
|
|
411
652
|
onClick: async () => {
|
|
412
653
|
const secret = (0, import_crypto.generateRandomValues)(new Uint8Array(20));
|
|
413
654
|
setQrCodeUrl(await generateTotpQrCode(project, user, secret));
|
|
414
655
|
setGeneratedSecret(secret);
|
|
415
656
|
},
|
|
416
|
-
children: t("Enable")
|
|
657
|
+
children: t("Enable MFA")
|
|
417
658
|
}
|
|
418
659
|
) })
|
|
419
660
|
] })
|