@stackframe/stack 2.6.10 → 2.6.12
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 +20 -0
- package/dist/components/elements/sidebar-layout.js +7 -4
- package/dist/components/elements/sidebar-layout.js.map +1 -1
- package/dist/components/selected-team-switcher.js +2 -2
- package/dist/components/selected-team-switcher.js.map +1 -1
- package/dist/components-page/account-settings.js +287 -103
- package/dist/components-page/account-settings.js.map +1 -1
- package/dist/components-page/stack-handler.js +10 -9
- package/dist/components-page/stack-handler.js.map +1 -1
- package/dist/esm/components/elements/sidebar-layout.js +7 -4
- package/dist/esm/components/elements/sidebar-layout.js.map +1 -1
- package/dist/esm/components/selected-team-switcher.js +2 -2
- package/dist/esm/components/selected-team-switcher.js.map +1 -1
- package/dist/esm/components-page/account-settings.js +288 -104
- package/dist/esm/components-page/account-settings.js.map +1 -1
- package/dist/esm/components-page/stack-handler.js +10 -9
- package/dist/esm/components-page/stack-handler.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 +2137 -1813
- package/dist/esm/generated/quetzal-translations.js.map +1 -1
- package/dist/esm/lib/stack-app.js +132 -11
- 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 +2137 -1813
- package/dist/generated/quetzal-translations.js.map +1 -1
- package/dist/lib/stack-app.d.mts +51 -17
- package/dist/lib/stack-app.d.ts +51 -17
- package/dist/lib/stack-app.js +132 -11
- package/dist/lib/stack-app.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @stackframe/stack
|
|
2
2
|
|
|
3
|
+
## 2.6.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated account settings page
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @stackframe/stack-shared@2.6.12
|
|
10
|
+
- @stackframe/stack-sc@2.6.12
|
|
11
|
+
- @stackframe/stack-ui@2.6.12
|
|
12
|
+
|
|
13
|
+
## 2.6.11
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- fixed account settings bugs
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- @stackframe/stack-shared@2.6.11
|
|
20
|
+
- @stackframe/stack-sc@2.6.11
|
|
21
|
+
- @stackframe/stack-ui@2.6.11
|
|
22
|
+
|
|
3
23
|
## 2.6.10
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -25,18 +25,21 @@ __export(sidebar_layout_exports, {
|
|
|
25
25
|
SidebarLayout: () => SidebarLayout
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(sidebar_layout_exports);
|
|
28
|
+
var import_use_hash = require("@stackframe/stack-shared/dist/hooks/use-hash");
|
|
28
29
|
var import_stack_ui = require("@stackframe/stack-ui");
|
|
29
30
|
var import_lucide_react = require("lucide-react");
|
|
30
31
|
var import_navigation = require("next/navigation");
|
|
31
|
-
var
|
|
32
|
+
var import_react = require("react");
|
|
32
33
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
33
34
|
function SidebarLayout(props) {
|
|
34
35
|
const router = (0, import_navigation.useRouter)();
|
|
35
36
|
const hash = (0, import_use_hash.useHash)();
|
|
36
37
|
const selectedIndex = props.items.findIndex((item) => item.id && item.id === hash);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
(0, import_react.useEffect)(() => {
|
|
39
|
+
if (selectedIndex === -1) {
|
|
40
|
+
router.push("#" + props.items[0].id);
|
|
41
|
+
}
|
|
42
|
+
}, [hash]);
|
|
40
43
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
41
44
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_stack_ui.cn)("hidden sm:flex stack-scope h-full", props.className), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DesktopLayout, { items: props.items, title: props.title, selectedIndex }) }),
|
|
42
45
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_stack_ui.cn)("sm:hidden stack-scope h-full", props.className), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MobileLayout, { items: props.items, title: props.title, selectedIndex }) })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/elements/sidebar-layout.tsx"],"sourcesContent":["'use client';\n\nimport { Button, Typography, cn } from '@stackframe/stack-ui';\nimport { LucideIcon, XIcon } from 'lucide-react';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/components/elements/sidebar-layout.tsx"],"sourcesContent":["'use client';\n\nimport { useHash } from '@stackframe/stack-shared/dist/hooks/use-hash';\nimport { Button, Typography, cn } from '@stackframe/stack-ui';\nimport { LucideIcon, XIcon } from 'lucide-react';\nimport { useRouter } from 'next/navigation';\nimport React, { ReactNode, useEffect } from 'react';\n\nexport type SidebarItem = {\n title: React.ReactNode,\n type: 'item' | 'divider',\n description?: React.ReactNode,\n id?: string,\n icon?: LucideIcon,\n content?: React.ReactNode,\n contentTitle?: React.ReactNode,\n}\n\nexport function SidebarLayout(props: { items: SidebarItem[], title?: ReactNode, className?: string }) {\n const router = useRouter();\n const hash = useHash();\n const selectedIndex = props.items.findIndex(item => item.id && (item.id === hash));\n\n useEffect(() => {\n if (selectedIndex === -1) {\n router.push('#' + props.items[0].id);\n }\n }, [hash]);\n\n return (\n <>\n <div className={cn(\"hidden sm:flex stack-scope h-full\", props.className)}>\n <DesktopLayout items={props.items} title={props.title} selectedIndex={selectedIndex} />\n </div>\n <div className={cn(\"sm:hidden stack-scope h-full\", props.className)}>\n <MobileLayout items={props.items} title={props.title} selectedIndex={selectedIndex} />\n </div>\n </>\n );\n}\n\nfunction Items(props: { items: SidebarItem[], selectedIndex: number }) {\n const router = useRouter();\n\n return props.items.map((item, index) => (\n item.type === 'item' ?\n <Button\n key={index}\n variant='ghost'\n size='sm'\n className={cn(\n props.selectedIndex === index && \"bg-muted\",\n \"justify-start text-md text-zinc-800 dark:text-zinc-300 px-2 text-left\",\n )}\n onClick={() => {\n if (item.id) {\n router.push('#' + item.id);\n }\n }}\n >\n {item.icon && <item.icon className=\"mr-2 h-4 w-4\" />}\n {item.title}\n </Button> :\n <Typography key={index}>\n {item.title}\n </Typography>\n ));\n\n}\n\nfunction DesktopLayout(props: { items: SidebarItem[], title?: ReactNode, selectedIndex: number }) {\n const selectedItem = props.items[props.selectedIndex === -1 ? 0 : props.selectedIndex];\n\n return (\n <div className=\"stack-scope flex w-full h-full max-w-full relative\">\n <div className=\"flex max-w-[200px] min-w-[200px] border-r flex-col items-stretch gap-2 p-2 overflow-y-auto\">\n {props.title && <div className='mb-2 ml-2'>\n <Typography type='h2' className=\"text-lg font-semibold text-zinc-800 dark:text-zinc-300\">{props.title}</Typography>\n </div>}\n\n <Items items={props.items} selectedIndex={props.selectedIndex} />\n </div>\n <div className=\"flex-1 w-0 flex justify-center gap-4 py-2 px-4\">\n <div className='flex flex-col max-w-[800px] w-[800px]'>\n <div className='mt-4 mb-6'>\n <Typography type='h4' className='font-semibold'>{selectedItem.title}</Typography>\n {selectedItem.description && <Typography variant='secondary' type='label'>{selectedItem.description}</Typography>}\n </div>\n <div className='flex-1'>\n {selectedItem.content}\n </div>\n </div>\n </div>\n </div>\n );\n}\n\nfunction MobileLayout(props: { items: SidebarItem[], title?: ReactNode, selectedIndex: number }) {\n const selectedItem = props.items[props.selectedIndex];\n const router = useRouter();\n\n if (props.selectedIndex === -1) {\n return (\n <div className=\"flex flex-col gap-2 p-2\">\n {props.title && <div className='mb-2 ml-2'>\n <Typography type='h2' className=\"text-lg font-semibold text-zinc-800 dark:text-zinc-300\">{props.title}</Typography>\n </div>}\n\n <Items items={props.items} selectedIndex={props.selectedIndex} />\n </div>\n );\n } else {\n return (\n <div className=\"flex-1 flex flex-col gap-4 py-2 px-4\">\n <div className='flex flex-col'>\n <div className='flex justify-between'>\n <Typography type='h4' className='font-semibold'>{selectedItem.title}</Typography>\n <Button\n variant='ghost'\n size='icon'\n onClick={() => { router.push('#'); }}\n >\n <XIcon className='h-5 w-5' />\n </Button>\n </div>\n {selectedItem.description && <Typography variant='secondary' type='label'>{selectedItem.description}</Typography>}\n </div>\n <div className='flex-1'>\n {selectedItem.content}\n </div>\n </div>\n );\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -72,7 +72,7 @@ function SelectedTeamSwitcher(props) {
|
|
|
72
72
|
user?.selectedTeam ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_stack_ui.SelectGroup, { children: [
|
|
73
73
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.SelectLabel, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
74
74
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("Current team") }),
|
|
75
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Button, { variant: "ghost", size: "icon", className: "h-6 w-6", onClick: () => router.push(`${app.urls.accountSettings}
|
|
75
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Button, { variant: "ghost", size: "icon", className: "h-6 w-6", onClick: () => router.push(`${app.urls.accountSettings}#team-${user.selectedTeam?.id}`), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Settings, { className: "h-4 w-4" }) })
|
|
76
76
|
] }) }),
|
|
77
77
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.SelectItem, { value: user.selectedTeam.id, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
78
78
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_team_icon.TeamIcon, { team: user.selectedTeam }),
|
|
@@ -91,7 +91,7 @@ function SelectedTeamSwitcher(props) {
|
|
|
91
91
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
92
92
|
import_stack_ui.Button,
|
|
93
93
|
{
|
|
94
|
-
onClick: () => router.push(`${app.urls.accountSettings}
|
|
94
|
+
onClick: () => router.push(`${app.urls.accountSettings}#team-creation`),
|
|
95
95
|
className: "w-full",
|
|
96
96
|
variant: "ghost",
|
|
97
97
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/selected-team-switcher.tsx"],"sourcesContent":["'use client';\nimport { runAsynchronouslyWithAlert } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport {\n Button,\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n Typography\n} from \"@stackframe/stack-ui\";\nimport { PlusCircle, Settings } from \"lucide-react\";\nimport { useRouter } from \"next/navigation\";\nimport { useEffect, useMemo } from \"react\";\nimport { Team, useStackApp, useUser } from \"..\";\nimport { useTranslation } from \"../lib/translations\";\nimport { TeamIcon } from \"./team-icon\";\n\ntype SelectedTeamSwitcherProps = {\n urlMap?: (team: Team) => string,\n selectedTeam?: Team,\n noUpdateSelectedTeam?: boolean,\n};\n\nexport function SelectedTeamSwitcher(props: SelectedTeamSwitcherProps) {\n const { t } = useTranslation();\n const app = useStackApp();\n const user = useUser();\n const project = app.useProject();\n const router = useRouter();\n const selectedTeam = user?.selectedTeam || props.selectedTeam;\n const rawTeams = user?.useTeams();\n const teams = useMemo(() => rawTeams?.sort((a, b) => b.id === selectedTeam?.id ? 1 : -1), [rawTeams, selectedTeam]);\n\n useEffect(() => {\n if (!props.noUpdateSelectedTeam && props.selectedTeam) {\n runAsynchronouslyWithAlert(user?.setSelectedTeam(props.selectedTeam));\n }\n }, [props.noUpdateSelectedTeam, props.selectedTeam]);\n\n return (\n <Select\n value={selectedTeam?.id}\n onValueChange={(value) => {\n runAsynchronouslyWithAlert(async () => {\n const team = teams?.find(team => team.id === value);\n if (!team) {\n throw new Error('Team not found, this should not happen');\n }\n\n if (!props.noUpdateSelectedTeam) {\n await user?.setSelectedTeam(team);\n }\n if (props.urlMap) {\n router.push(props.urlMap(team));\n }\n });\n }}\n >\n <SelectTrigger className=\"stack-scope max-w-64\">\n <SelectValue placeholder=\"Select team\"/>\n </SelectTrigger>\n <SelectContent className=\"stack-scope\">\n {user?.selectedTeam ? <SelectGroup>\n <SelectLabel>\n <div className=\"flex items-center justify-between\">\n <span>\n {t('Current team')}\n </span>\n <Button variant='ghost' size='icon' className=\"h-6 w-6\" onClick={() => router.push(`${app.urls.accountSettings}
|
|
1
|
+
{"version":3,"sources":["../../src/components/selected-team-switcher.tsx"],"sourcesContent":["'use client';\nimport { runAsynchronouslyWithAlert } from \"@stackframe/stack-shared/dist/utils/promises\";\nimport {\n Button,\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n Typography\n} from \"@stackframe/stack-ui\";\nimport { PlusCircle, Settings } from \"lucide-react\";\nimport { useRouter } from \"next/navigation\";\nimport { useEffect, useMemo } from \"react\";\nimport { Team, useStackApp, useUser } from \"..\";\nimport { useTranslation } from \"../lib/translations\";\nimport { TeamIcon } from \"./team-icon\";\n\ntype SelectedTeamSwitcherProps = {\n urlMap?: (team: Team) => string,\n selectedTeam?: Team,\n noUpdateSelectedTeam?: boolean,\n};\n\nexport function SelectedTeamSwitcher(props: SelectedTeamSwitcherProps) {\n const { t } = useTranslation();\n const app = useStackApp();\n const user = useUser();\n const project = app.useProject();\n const router = useRouter();\n const selectedTeam = user?.selectedTeam || props.selectedTeam;\n const rawTeams = user?.useTeams();\n const teams = useMemo(() => rawTeams?.sort((a, b) => b.id === selectedTeam?.id ? 1 : -1), [rawTeams, selectedTeam]);\n\n useEffect(() => {\n if (!props.noUpdateSelectedTeam && props.selectedTeam) {\n runAsynchronouslyWithAlert(user?.setSelectedTeam(props.selectedTeam));\n }\n }, [props.noUpdateSelectedTeam, props.selectedTeam]);\n\n return (\n <Select\n value={selectedTeam?.id}\n onValueChange={(value) => {\n runAsynchronouslyWithAlert(async () => {\n const team = teams?.find(team => team.id === value);\n if (!team) {\n throw new Error('Team not found, this should not happen');\n }\n\n if (!props.noUpdateSelectedTeam) {\n await user?.setSelectedTeam(team);\n }\n if (props.urlMap) {\n router.push(props.urlMap(team));\n }\n });\n }}\n >\n <SelectTrigger className=\"stack-scope max-w-64\">\n <SelectValue placeholder=\"Select team\"/>\n </SelectTrigger>\n <SelectContent className=\"stack-scope\">\n {user?.selectedTeam ? <SelectGroup>\n <SelectLabel>\n <div className=\"flex items-center justify-between\">\n <span>\n {t('Current team')}\n </span>\n <Button variant='ghost' size='icon' className=\"h-6 w-6\" onClick={() => router.push(`${app.urls.accountSettings}#team-${user.selectedTeam?.id}`)}>\n <Settings className=\"h-4 w-4\"/>\n </Button>\n </div>\n </SelectLabel>\n <SelectItem value={user.selectedTeam.id}>\n <div className=\"flex items-center gap-2\">\n <TeamIcon team={user.selectedTeam} />\n <Typography className=\"max-w-40 truncate\">{user.selectedTeam.displayName}</Typography>\n </div>\n </SelectItem>\n </SelectGroup> : undefined}\n\n {teams?.length ?\n <SelectGroup>\n <SelectLabel>{t('Other teams')}</SelectLabel>\n {teams.filter(team => team.id !== user?.selectedTeam?.id)\n .map(team => (\n <SelectItem value={team.id} key={team.id}>\n <div className=\"flex items-center gap-2\">\n <TeamIcon team={team} />\n <Typography className=\"max-w-64 truncate\">{team.displayName}</Typography>\n </div>\n </SelectItem>\n ))}\n </SelectGroup> :\n <SelectGroup>\n <SelectLabel>{t('No teams yet')}</SelectLabel>\n </SelectGroup>}\n\n {project.config.clientTeamCreationEnabled && <>\n <SelectSeparator/>\n <div>\n <Button\n onClick={() => router.push(`${app.urls.accountSettings}#team-creation`)}\n className=\"w-full\"\n variant='ghost'\n >\n <PlusCircle className=\"mr-2 h-4 w-4\"/> {t('Create a team')}\n </Button>\n </div>\n </>}\n </SelectContent>\n </Select>\n );\n}\n"],"mappings":"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","names":["team"]}
|
|
@@ -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,227 @@ 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();
|
|
188
329
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PageLayout, { children: [
|
|
189
|
-
|
|
330
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmailsSection, {}),
|
|
190
331
|
passwordSection,
|
|
332
|
+
otpSection,
|
|
191
333
|
mfaSection
|
|
192
334
|
] });
|
|
193
335
|
}
|
|
194
|
-
function
|
|
195
|
-
const deleteAccountSection = useDeleteAccountSection();
|
|
196
|
-
const signOutSection = useSignOutSection();
|
|
197
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PageLayout, { children: [
|
|
198
|
-
deleteAccountSection,
|
|
199
|
-
signOutSection
|
|
200
|
-
] });
|
|
201
|
-
}
|
|
202
|
-
function useEmailVerificationSection() {
|
|
336
|
+
function useOtpSection() {
|
|
203
337
|
const { t } = (0, import_translations.useTranslation)();
|
|
204
|
-
const user = (0, import__.useUser)({ or: "
|
|
205
|
-
const
|
|
206
|
-
|
|
338
|
+
const user = (0, import__.useUser)({ or: "throw" });
|
|
339
|
+
const project = (0, import__.useStackApp)().useProject();
|
|
340
|
+
const contactChannels = user.useContactChannels();
|
|
341
|
+
const isLastAuth = user.otpAuthEnabled && !user.hasPassword && user.oauthProviders.length === 0;
|
|
342
|
+
const [disabling, setDisabling] = (0, import_react.useState)(false);
|
|
343
|
+
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
|
|
344
|
+
if (!project.config.magicLinkEnabled) {
|
|
207
345
|
return null;
|
|
208
346
|
}
|
|
209
|
-
|
|
210
|
-
|
|
347
|
+
const handleDisableOTP = async () => {
|
|
348
|
+
await user.update({ otpAuthEnabled: false });
|
|
349
|
+
setDisabling(false);
|
|
350
|
+
};
|
|
351
|
+
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)(
|
|
352
|
+
import_stack_ui.Button,
|
|
211
353
|
{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
children:
|
|
354
|
+
variant: "secondary",
|
|
355
|
+
onClick: () => setDisabling(true),
|
|
356
|
+
children: t("Disable OTP")
|
|
357
|
+
}
|
|
358
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
359
|
+
/* @__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.") }),
|
|
360
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-2", children: [
|
|
361
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
215
362
|
import_stack_ui.Button,
|
|
216
363
|
{
|
|
217
|
-
|
|
218
|
-
onClick:
|
|
219
|
-
|
|
220
|
-
setEmailSent(true);
|
|
221
|
-
},
|
|
222
|
-
children: emailSent ? t("Email sent!") : t("Send Verification Email")
|
|
364
|
+
variant: "destructive",
|
|
365
|
+
onClick: handleDisableOTP,
|
|
366
|
+
children: t("Disable")
|
|
223
367
|
}
|
|
224
|
-
)
|
|
368
|
+
),
|
|
369
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
370
|
+
import_stack_ui.Button,
|
|
371
|
+
{
|
|
372
|
+
variant: "secondary",
|
|
373
|
+
onClick: () => setDisabling(false),
|
|
374
|
+
children: t("Cancel")
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
] })
|
|
378
|
+
] }) : /* @__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)(
|
|
379
|
+
import_stack_ui.Button,
|
|
380
|
+
{
|
|
381
|
+
variant: "secondary",
|
|
382
|
+
onClick: async () => {
|
|
383
|
+
await user.update({ otpAuthEnabled: true });
|
|
384
|
+
},
|
|
385
|
+
children: t("Enable OTP")
|
|
225
386
|
}
|
|
226
|
-
);
|
|
387
|
+
) : /* @__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.") }) }) });
|
|
388
|
+
}
|
|
389
|
+
function SettingsPage() {
|
|
390
|
+
const deleteAccountSection = useDeleteAccountSection();
|
|
391
|
+
const signOutSection = useSignOutSection();
|
|
392
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PageLayout, { children: [
|
|
393
|
+
deleteAccountSection,
|
|
394
|
+
signOutSection
|
|
395
|
+
] });
|
|
227
396
|
}
|
|
228
397
|
function usePasswordSection() {
|
|
229
398
|
const { t } = (0, import_translations.useTranslation)();
|
|
399
|
+
const user = (0, import__.useUser)({ or: "throw" });
|
|
400
|
+
const contactChannels = user.useContactChannels();
|
|
401
|
+
const [changingPassword, setChangingPassword] = (0, import_react.useState)(false);
|
|
402
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
230
403
|
const passwordSchema = (0, import_schema_fields.yupObject)({
|
|
231
|
-
oldPassword: (0, import_schema_fields.yupString)().required(t("Please enter your old password")),
|
|
404
|
+
oldPassword: user.hasPassword ? (0, import_schema_fields.yupString)().required(t("Please enter your old password")) : (0, import_schema_fields.yupString)(),
|
|
232
405
|
newPassword: (0, import_schema_fields.yupString)().required(t("Please enter your password")).test({
|
|
233
406
|
name: "is-valid-password",
|
|
234
407
|
test: (value, ctx) => {
|
|
@@ -242,23 +415,20 @@ function usePasswordSection() {
|
|
|
242
415
|
}),
|
|
243
416
|
newPasswordRepeat: (0, import_schema_fields.yupString)().nullable().oneOf([yup.ref("newPassword"), "", null], t("Passwords do not match")).required(t("Please repeat your password"))
|
|
244
417
|
});
|
|
245
|
-
const user = (0, import__.useUser)({ or: "throw" });
|
|
246
|
-
const [changingPassword, setChangingPassword] = (0, import_react.useState)(false);
|
|
247
418
|
const { register, handleSubmit, setError, formState: { errors }, clearErrors, reset } = (0, import_react_hook_form.useForm)({
|
|
248
419
|
resolver: (0, import_yup.yupResolver)(passwordSchema)
|
|
249
420
|
});
|
|
250
|
-
const
|
|
251
|
-
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
421
|
+
const hasValidEmail = contactChannels.filter((x) => x.type === "email" && x.isVerified && x.usedForAuth).length > 0;
|
|
252
422
|
const onSubmit = async (data) => {
|
|
253
423
|
setLoading(true);
|
|
254
424
|
try {
|
|
255
425
|
const { oldPassword, newPassword } = data;
|
|
256
|
-
const error = await user.updatePassword({ oldPassword, newPassword });
|
|
426
|
+
const error = user.hasPassword ? await user.updatePassword({ oldPassword, newPassword }) : await user.setPassword({ password: newPassword });
|
|
257
427
|
if (error) {
|
|
258
428
|
setError("oldPassword", { type: "manual", message: t("Incorrect password") });
|
|
259
429
|
} else {
|
|
260
430
|
reset();
|
|
261
|
-
|
|
431
|
+
setChangingPassword(false);
|
|
262
432
|
}
|
|
263
433
|
} finally {
|
|
264
434
|
setLoading(false);
|
|
@@ -266,69 +436,83 @@ function usePasswordSection() {
|
|
|
266
436
|
};
|
|
267
437
|
const registerPassword = register("newPassword");
|
|
268
438
|
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
|
-
|
|
439
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
440
|
+
Section,
|
|
441
|
+
{
|
|
442
|
+
title: t("Password"),
|
|
443
|
+
description: user.hasPassword ? t("Update your password") : t("Set a password for your account"),
|
|
444
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-4", children: !changingPassword ? hasValidEmail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
445
|
+
import_stack_ui.Button,
|
|
446
|
+
{
|
|
447
|
+
variant: "secondary",
|
|
448
|
+
onClick: () => setChangingPassword(true),
|
|
449
|
+
children: user.hasPassword ? t("Update password") : t("Set password")
|
|
450
|
+
}
|
|
451
|
+
) : /* @__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)(
|
|
452
|
+
"form",
|
|
453
|
+
{
|
|
454
|
+
onSubmit: (e) => (0, import_promises.runAsynchronouslyWithAlert)(handleSubmit(onSubmit)(e)),
|
|
455
|
+
noValidate: true,
|
|
456
|
+
children: [
|
|
457
|
+
user.hasPassword && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
458
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Label, { htmlFor: "old-password", className: "mb-1", children: t("Old password") }),
|
|
459
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
460
|
+
import_stack_ui.Input,
|
|
461
|
+
{
|
|
462
|
+
id: "old-password",
|
|
463
|
+
type: "password",
|
|
464
|
+
...register("oldPassword")
|
|
465
|
+
}
|
|
466
|
+
),
|
|
467
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_form_warning.FormWarningText, { text: errors.oldPassword?.message?.toString() })
|
|
468
|
+
] }),
|
|
469
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Label, { htmlFor: "new-password", className: "mt-4 mb-1", children: t("New password") }),
|
|
470
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
471
|
+
import_stack_ui.PasswordInput,
|
|
472
|
+
{
|
|
473
|
+
id: "new-password",
|
|
474
|
+
...registerPassword,
|
|
475
|
+
onChange: (e) => {
|
|
476
|
+
clearErrors("newPassword");
|
|
477
|
+
clearErrors("newPasswordRepeat");
|
|
478
|
+
(0, import_promises.runAsynchronously)(registerPassword.onChange(e));
|
|
479
|
+
}
|
|
309
480
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
481
|
+
),
|
|
482
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_form_warning.FormWarningText, { text: errors.newPassword?.message?.toString() }),
|
|
483
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Label, { htmlFor: "repeat-password", className: "mt-4 mb-1", children: t("Repeat new password") }),
|
|
484
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
485
|
+
import_stack_ui.PasswordInput,
|
|
486
|
+
{
|
|
487
|
+
id: "repeat-password",
|
|
488
|
+
...registerPasswordRepeat,
|
|
489
|
+
onChange: (e) => {
|
|
490
|
+
clearErrors("newPassword");
|
|
491
|
+
clearErrors("newPasswordRepeat");
|
|
492
|
+
(0, import_promises.runAsynchronously)(registerPasswordRepeat.onChange(e));
|
|
493
|
+
}
|
|
323
494
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
495
|
+
),
|
|
496
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_form_warning.FormWarningText, { text: errors.newPasswordRepeat?.message?.toString() }),
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-6 flex gap-4", children: [
|
|
498
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stack_ui.Button, { type: "submit", loading, children: user.hasPassword ? t("Update Password") : t("Set Password") }),
|
|
499
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
500
|
+
import_stack_ui.Button,
|
|
501
|
+
{
|
|
502
|
+
variant: "secondary",
|
|
503
|
+
onClick: () => {
|
|
504
|
+
setChangingPassword(false);
|
|
505
|
+
reset();
|
|
506
|
+
},
|
|
507
|
+
children: t("Cancel")
|
|
508
|
+
}
|
|
509
|
+
)
|
|
510
|
+
] })
|
|
511
|
+
]
|
|
512
|
+
}
|
|
513
|
+
) })
|
|
514
|
+
}
|
|
515
|
+
);
|
|
332
516
|
}
|
|
333
517
|
function useMfaSection() {
|
|
334
518
|
const { t } = (0, import_translations.useTranslation)();
|
|
@@ -359,7 +543,7 @@ function useMfaSection() {
|
|
|
359
543
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
360
544
|
Section,
|
|
361
545
|
{
|
|
362
|
-
title: t("Multi-factor
|
|
546
|
+
title: t("Multi-factor authentication"),
|
|
363
547
|
description: isEnabled ? t("Multi-factor authentication is currently enabled.") : t("Multi-factor authentication is currently disabled."),
|
|
364
548
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
365
549
|
!isEnabled && generatedSecret && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -402,18 +586,18 @@ function useMfaSection() {
|
|
|
402
586
|
totpMultiFactorSecret: null
|
|
403
587
|
});
|
|
404
588
|
},
|
|
405
|
-
children: t("Disable")
|
|
589
|
+
children: t("Disable MFA")
|
|
406
590
|
}
|
|
407
591
|
) : !generatedSecret && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
408
592
|
import_stack_ui.Button,
|
|
409
593
|
{
|
|
410
|
-
variant: "
|
|
594
|
+
variant: "secondary",
|
|
411
595
|
onClick: async () => {
|
|
412
596
|
const secret = (0, import_crypto.generateRandomValues)(new Uint8Array(20));
|
|
413
597
|
setQrCodeUrl(await generateTotpQrCode(project, user, secret));
|
|
414
598
|
setGeneratedSecret(secret);
|
|
415
599
|
},
|
|
416
|
-
children: t("Enable")
|
|
600
|
+
children: t("Enable MFA")
|
|
417
601
|
}
|
|
418
602
|
) })
|
|
419
603
|
] })
|