@stackframe/stack 2.5.35 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/components/credential-sign-in.js +4 -2
- package/dist/components/credential-sign-in.js.map +1 -1
- package/dist/components/credential-sign-up.d.mts +3 -1
- package/dist/components/credential-sign-up.d.ts +3 -1
- package/dist/components/credential-sign-up.js +23 -17
- package/dist/components/credential-sign-up.js.map +1 -1
- package/dist/components/elements/sidebar-layout.js +19 -11
- package/dist/components/elements/sidebar-layout.js.map +1 -1
- package/dist/components/elements/user-avatar.js +1 -1
- package/dist/components/elements/user-avatar.js.map +1 -1
- package/dist/components/magic-link-sign-in.js +82 -30
- package/dist/components/magic-link-sign-in.js.map +1 -1
- package/dist/components/message-cards/predefined-message-card.js +22 -20
- package/dist/components/message-cards/predefined-message-card.js.map +1 -1
- package/dist/components/oauth-button.js +32 -25
- package/dist/components/oauth-button.js.map +1 -1
- package/dist/components/profile-image-editor.js +2 -2
- package/dist/components/profile-image-editor.js.map +1 -1
- package/dist/components/selected-team-switcher.js +1 -1
- package/dist/components/selected-team-switcher.js.map +1 -1
- package/dist/components-page/account-settings.d.mts +4 -2
- package/dist/components-page/account-settings.d.ts +4 -2
- package/dist/components-page/account-settings.js +365 -188
- 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 -5
- package/dist/components-page/auth-page.js.map +1 -1
- package/dist/components-page/email-verification.js +10 -8
- package/dist/components-page/email-verification.js.map +1 -1
- package/dist/components-page/error-page.js +19 -4
- package/dist/components-page/error-page.js.map +1 -1
- package/dist/components-page/magic-link-callback.js +11 -9
- package/dist/components-page/magic-link-callback.js.map +1 -1
- package/dist/components-page/oauth-callback.js +1 -1
- package/dist/components-page/oauth-callback.js.map +1 -1
- package/dist/components-page/password-reset.js +13 -11
- package/dist/components-page/password-reset.js.map +1 -1
- package/dist/components-page/sign-up.d.mts +1 -0
- package/dist/components-page/sign-up.d.ts +1 -0
- package/dist/components-page/sign-up.js +10 -1
- package/dist/components-page/sign-up.js.map +1 -1
- package/dist/components-page/stack-handler.d.mts +1 -0
- package/dist/components-page/stack-handler.d.ts +1 -0
- package/dist/esm/components/credential-sign-in.js +4 -2
- package/dist/esm/components/credential-sign-in.js.map +1 -1
- package/dist/esm/components/credential-sign-up.js +24 -18
- package/dist/esm/components/credential-sign-up.js.map +1 -1
- package/dist/esm/components/elements/sidebar-layout.js +19 -11
- package/dist/esm/components/elements/sidebar-layout.js.map +1 -1
- package/dist/esm/components/elements/user-avatar.js +1 -1
- package/dist/esm/components/elements/user-avatar.js.map +1 -1
- package/dist/esm/components/magic-link-sign-in.js +84 -32
- package/dist/esm/components/magic-link-sign-in.js.map +1 -1
- package/dist/esm/components/message-cards/predefined-message-card.js +22 -20
- package/dist/esm/components/message-cards/predefined-message-card.js.map +1 -1
- package/dist/esm/components/oauth-button.js +32 -25
- package/dist/esm/components/oauth-button.js.map +1 -1
- package/dist/esm/components/profile-image-editor.js +2 -2
- package/dist/esm/components/profile-image-editor.js.map +1 -1
- package/dist/esm/components/selected-team-switcher.js +1 -1
- package/dist/esm/components/selected-team-switcher.js.map +1 -1
- package/dist/esm/components-page/account-settings.js +365 -189
- package/dist/esm/components-page/account-settings.js.map +1 -1
- package/dist/esm/components-page/auth-page.js +5 -5
- package/dist/esm/components-page/auth-page.js.map +1 -1
- package/dist/esm/components-page/email-verification.js +10 -8
- package/dist/esm/components-page/email-verification.js.map +1 -1
- package/dist/esm/components-page/error-page.js +19 -4
- package/dist/esm/components-page/error-page.js.map +1 -1
- package/dist/esm/components-page/magic-link-callback.js +11 -9
- package/dist/esm/components-page/magic-link-callback.js.map +1 -1
- package/dist/esm/components-page/oauth-callback.js +1 -1
- package/dist/esm/components-page/oauth-callback.js.map +1 -1
- package/dist/esm/components-page/password-reset.js +13 -11
- package/dist/esm/components-page/password-reset.js.map +1 -1
- package/dist/esm/components-page/sign-up.js +10 -1
- package/dist/esm/components-page/sign-up.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 +1764 -1356
- package/dist/esm/generated/quetzal-translations.js.map +1 -1
- package/dist/esm/lib/auth.js +4 -3
- package/dist/esm/lib/auth.js.map +1 -1
- package/dist/esm/lib/stack-app.js +54 -45
- package/dist/esm/lib/stack-app.js.map +1 -1
- package/dist/esm/lib/translations.js +6 -2
- package/dist/esm/lib/translations.js.map +1 -1
- package/dist/esm/utils/browser-script.js +9 -7
- package/dist/esm/utils/browser-script.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 +1764 -1356
- package/dist/generated/quetzal-translations.js.map +1 -1
- package/dist/lib/auth.d.mts +16 -6
- package/dist/lib/auth.d.ts +16 -6
- package/dist/lib/auth.js +4 -3
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/stack-app.d.mts +21 -12
- package/dist/lib/stack-app.d.ts +21 -12
- package/dist/lib/stack-app.js +53 -44
- package/dist/lib/stack-app.js.map +1 -1
- package/dist/lib/translations.d.mts +1 -1
- package/dist/lib/translations.d.ts +1 -1
- package/dist/lib/translations.js +6 -2
- package/dist/lib/translations.js.map +1 -1
- package/dist/utils/browser-script.js +9 -7
- package/dist/utils/browser-script.js.map +1 -1
- package/package.json +5 -5
|
@@ -9,20 +9,21 @@ import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-field
|
|
|
9
9
|
import { generateRandomValues } from "@stackframe/stack-shared/dist/utils/crypto";
|
|
10
10
|
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
|
|
11
11
|
import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
|
12
|
-
import { Button, EditableText, Input, Label, PasswordInput,
|
|
13
|
-
import { CirclePlus, Contact,
|
|
12
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, EditableText, Input, Label, PasswordInput, Separator, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
|
|
13
|
+
import { CirclePlus, Contact, Settings, ShieldCheck } from "lucide-react";
|
|
14
|
+
import { useRouter } from "next/navigation";
|
|
14
15
|
import { TOTPController, createTOTPKeyURI } from "oslo/otp";
|
|
15
16
|
import * as QRCode from "qrcode";
|
|
16
|
-
import { useEffect, useState } from "react";
|
|
17
|
+
import React, { useEffect, useState } from "react";
|
|
17
18
|
import { useForm } from "react-hook-form";
|
|
18
19
|
import * as yup from "yup";
|
|
19
20
|
import { MessageCard, useStackApp, useUser } from "..";
|
|
20
21
|
import { FormWarningText } from "../components/elements/form-warning";
|
|
22
|
+
import { MaybeFullPage } from "../components/elements/maybe-full-page";
|
|
21
23
|
import { SidebarLayout } from "../components/elements/sidebar-layout";
|
|
22
24
|
import { UserAvatar } from "../components/elements/user-avatar";
|
|
23
25
|
import { ProfileImageEditor } from "../components/profile-image-editor";
|
|
24
26
|
import { TeamIcon } from "../components/team-icon";
|
|
25
|
-
import { MaybeFullPage } from "../components/elements/maybe-full-page";
|
|
26
27
|
import { useTranslation } from "../lib/translations";
|
|
27
28
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
28
29
|
function AccountSettings(props) {
|
|
@@ -31,7 +32,7 @@ function AccountSettings(props) {
|
|
|
31
32
|
const teams = user.useTeams();
|
|
32
33
|
const stackApp = useStackApp();
|
|
33
34
|
const project = stackApp.useProject();
|
|
34
|
-
return /* @__PURE__ */ jsx(MaybeFullPage, { fullPage: !!props.fullPage, children: /* @__PURE__ */ jsx("div", { style: { alignSelf: "stretch", flexGrow: 1 }, children: /* @__PURE__ */ jsx(
|
|
35
|
+
return /* @__PURE__ */ jsx(MaybeFullPage, { fullPage: !!props.fullPage, children: /* @__PURE__ */ jsx("div", { style: { alignSelf: "stretch", flexGrow: 1, width: 0 }, children: /* @__PURE__ */ jsx(
|
|
35
36
|
SidebarLayout,
|
|
36
37
|
{
|
|
37
38
|
items: [
|
|
@@ -40,25 +41,21 @@ function AccountSettings(props) {
|
|
|
40
41
|
type: "item",
|
|
41
42
|
subpath: "/profile",
|
|
42
43
|
icon: Contact,
|
|
43
|
-
content: /* @__PURE__ */ jsx(
|
|
44
|
+
content: /* @__PURE__ */ jsx(ProfilePage, {})
|
|
44
45
|
},
|
|
45
46
|
{
|
|
46
47
|
title: t("Security"),
|
|
47
48
|
type: "item",
|
|
48
|
-
icon: ShieldCheck,
|
|
49
49
|
subpath: "/security",
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
/* @__PURE__ */ jsx(PasswordSection, {}),
|
|
53
|
-
/* @__PURE__ */ jsx(MfaSection, {})
|
|
54
|
-
] })
|
|
50
|
+
icon: ShieldCheck,
|
|
51
|
+
content: /* @__PURE__ */ jsx(SecurityPage, {})
|
|
55
52
|
},
|
|
56
53
|
{
|
|
57
|
-
title: t("
|
|
58
|
-
subpath: "/sign-out",
|
|
54
|
+
title: t("Settings"),
|
|
59
55
|
type: "item",
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
subpath: "/settings",
|
|
57
|
+
icon: Settings,
|
|
58
|
+
content: /* @__PURE__ */ jsx(SettingsPage, {})
|
|
62
59
|
},
|
|
63
60
|
...props.extraItems?.map((item) => ({
|
|
64
61
|
title: item.title,
|
|
@@ -72,19 +69,13 @@ function AccountSettings(props) {
|
|
|
72
69
|
type: "divider"
|
|
73
70
|
}] : [],
|
|
74
71
|
...teams.map((team) => ({
|
|
75
|
-
title: /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
|
|
72
|
+
title: /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center w-full", children: [
|
|
76
73
|
/* @__PURE__ */ jsx(TeamIcon, { team }),
|
|
77
|
-
team.displayName
|
|
74
|
+
/* @__PURE__ */ jsx(Typography, { className: "max-w-[320px] md:w-[90%] truncate", children: team.displayName })
|
|
78
75
|
] }),
|
|
79
76
|
type: "item",
|
|
80
77
|
subpath: `/teams/${team.id}`,
|
|
81
|
-
content: /* @__PURE__ */
|
|
82
|
-
/* @__PURE__ */ jsx(ProfileSettings, { team }),
|
|
83
|
-
/* @__PURE__ */ jsx(ManagementSettings, { team }),
|
|
84
|
-
/* @__PURE__ */ jsx(MemberInvitation, { team }),
|
|
85
|
-
/* @__PURE__ */ jsx(MembersSettings, { team }),
|
|
86
|
-
/* @__PURE__ */ jsx(UserSettings, { team })
|
|
87
|
-
] })
|
|
78
|
+
content: /* @__PURE__ */ jsx(TeamPage, { team })
|
|
88
79
|
})),
|
|
89
80
|
...project.config.clientTeamCreationEnabled ? [{
|
|
90
81
|
title: t("Create a team"),
|
|
@@ -99,42 +90,93 @@ function AccountSettings(props) {
|
|
|
99
90
|
}
|
|
100
91
|
) }) });
|
|
101
92
|
}
|
|
102
|
-
function
|
|
93
|
+
function Section(props) {
|
|
94
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-2", children: [
|
|
95
|
+
/* @__PURE__ */ jsxs("div", { className: "sm:flex-1 flex flex-col justify-center", children: [
|
|
96
|
+
/* @__PURE__ */ jsx(Typography, { className: "font-medium", children: props.title }),
|
|
97
|
+
props.description && /* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "footnote", children: props.description })
|
|
98
|
+
] }),
|
|
99
|
+
/* @__PURE__ */ jsx("div", { className: "sm:flex-1 sm:items-end flex flex-col gap-2 ", children: props.children })
|
|
100
|
+
] });
|
|
101
|
+
}
|
|
102
|
+
function PageLayout(props) {
|
|
103
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6", children: [
|
|
104
|
+
/* @__PURE__ */ jsx(Separator, {}),
|
|
105
|
+
React.Children.map(props.children, (child) => child && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
106
|
+
child,
|
|
107
|
+
/* @__PURE__ */ jsx(Separator, {})
|
|
108
|
+
] }))
|
|
109
|
+
] });
|
|
110
|
+
}
|
|
111
|
+
function ProfilePage() {
|
|
103
112
|
const { t } = useTranslation();
|
|
104
113
|
const user = useUser({ or: "redirect" });
|
|
105
|
-
return /* @__PURE__ */ jsxs(
|
|
106
|
-
/* @__PURE__ */
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
return /* @__PURE__ */ jsxs(PageLayout, { children: [
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
116
|
+
Section,
|
|
117
|
+
{
|
|
118
|
+
title: t("User name"),
|
|
119
|
+
description: t("This is a display name and is not used for authentication"),
|
|
120
|
+
children: /* @__PURE__ */ jsx(
|
|
121
|
+
EditableText,
|
|
122
|
+
{
|
|
123
|
+
value: user.displayName || "",
|
|
124
|
+
onSave: async (newDisplayName) => {
|
|
125
|
+
await user.update({ displayName: newDisplayName });
|
|
126
|
+
}
|
|
114
127
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
/* @__PURE__ */
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
),
|
|
131
|
+
/* @__PURE__ */ jsx(
|
|
132
|
+
Section,
|
|
133
|
+
{
|
|
134
|
+
title: t("Profile image"),
|
|
135
|
+
description: t("Upload your own image as your avatar"),
|
|
136
|
+
children: /* @__PURE__ */ jsx(
|
|
137
|
+
ProfileImageEditor,
|
|
138
|
+
{
|
|
139
|
+
user,
|
|
140
|
+
onProfileImageUrlChange: async (profileImageUrl) => {
|
|
141
|
+
await user.update({ profileImageUrl });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
)
|
|
124
147
|
] });
|
|
125
148
|
}
|
|
126
|
-
function
|
|
149
|
+
function SecurityPage() {
|
|
150
|
+
const emailVerificationSection = useEmailVerificationSection();
|
|
151
|
+
const passwordSection = usePasswordSection();
|
|
152
|
+
const mfaSection = useMfaSection();
|
|
153
|
+
return /* @__PURE__ */ jsxs(PageLayout, { children: [
|
|
154
|
+
emailVerificationSection,
|
|
155
|
+
passwordSection,
|
|
156
|
+
mfaSection
|
|
157
|
+
] });
|
|
158
|
+
}
|
|
159
|
+
function SettingsPage() {
|
|
160
|
+
const deleteAccountSection = useDeleteAccountSection();
|
|
161
|
+
const signOutSection = useSignOutSection();
|
|
162
|
+
return /* @__PURE__ */ jsxs(PageLayout, { children: [
|
|
163
|
+
deleteAccountSection,
|
|
164
|
+
signOutSection
|
|
165
|
+
] });
|
|
166
|
+
}
|
|
167
|
+
function useEmailVerificationSection() {
|
|
127
168
|
const { t } = useTranslation();
|
|
128
169
|
const user = useUser({ or: "redirect" });
|
|
129
170
|
const [emailSent, setEmailSent] = useState(false);
|
|
130
171
|
if (!user.primaryEmail) {
|
|
131
172
|
return null;
|
|
132
173
|
}
|
|
133
|
-
return /* @__PURE__ */ jsx(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
174
|
+
return /* @__PURE__ */ jsx(
|
|
175
|
+
Section,
|
|
176
|
+
{
|
|
177
|
+
title: t("Email Verification"),
|
|
178
|
+
description: t("Verify your email address to secure your account"),
|
|
179
|
+
children: /* @__PURE__ */ jsx("div", { children: user.primaryEmailVerified ? /* @__PURE__ */ jsx(Typography, { variant: "success", children: t("Your email has been verified.") }) : /* @__PURE__ */ jsx("div", { className: "flex", children: /* @__PURE__ */ jsx(
|
|
138
180
|
Button,
|
|
139
181
|
{
|
|
140
182
|
disabled: emailSent,
|
|
@@ -144,11 +186,11 @@ function EmailVerificationSection() {
|
|
|
144
186
|
},
|
|
145
187
|
children: emailSent ? t("Email sent!") : t("Send Verification Email")
|
|
146
188
|
}
|
|
147
|
-
) })
|
|
148
|
-
|
|
149
|
-
|
|
189
|
+
) }) })
|
|
190
|
+
}
|
|
191
|
+
);
|
|
150
192
|
}
|
|
151
|
-
function
|
|
193
|
+
function usePasswordSection() {
|
|
152
194
|
const { t } = useTranslation();
|
|
153
195
|
const passwordSchema = yupObject({
|
|
154
196
|
oldPassword: yupString().required(t("Please enter your old password")),
|
|
@@ -253,7 +295,7 @@ function PasswordSection() {
|
|
|
253
295
|
) })
|
|
254
296
|
] });
|
|
255
297
|
}
|
|
256
|
-
function
|
|
298
|
+
function useMfaSection() {
|
|
257
299
|
const { t } = useTranslation();
|
|
258
300
|
const project = useStackApp().useProject();
|
|
259
301
|
const user = useUser({ or: "throw" });
|
|
@@ -279,103 +321,165 @@ function MfaSection() {
|
|
|
279
321
|
setIsMaybeWrong(true);
|
|
280
322
|
});
|
|
281
323
|
}, [mfaCode, generatedSecret, handleSubmit]);
|
|
282
|
-
return /* @__PURE__ */ jsx(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
/* @__PURE__ */
|
|
289
|
-
|
|
290
|
-
|
|
324
|
+
return /* @__PURE__ */ jsx(
|
|
325
|
+
Section,
|
|
326
|
+
{
|
|
327
|
+
title: t("Multi-factor Authentication"),
|
|
328
|
+
description: isEnabled ? t("Multi-factor authentication is currently enabled.") : t("Multi-factor authentication is currently disabled."),
|
|
329
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
330
|
+
!isEnabled && generatedSecret && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
331
|
+
/* @__PURE__ */ jsx(Typography, { children: t("Scan this QR code with your authenticator app:") }),
|
|
332
|
+
/* @__PURE__ */ jsx("img", { width: 200, height: 200, src: qrCodeUrl ?? throwErr("TOTP QR code failed to generate"), alt: t("TOTP multi-factor authentication QR code") }),
|
|
333
|
+
/* @__PURE__ */ jsx(Typography, { children: t("Then, enter your six-digit MFA code:") }),
|
|
334
|
+
/* @__PURE__ */ jsx(
|
|
335
|
+
Input,
|
|
336
|
+
{
|
|
337
|
+
value: mfaCode,
|
|
338
|
+
onChange: (e) => {
|
|
339
|
+
setIsMaybeWrong(false);
|
|
340
|
+
setMfaCode(e.target.value);
|
|
341
|
+
},
|
|
342
|
+
placeholder: "123456",
|
|
343
|
+
maxLength: 6,
|
|
344
|
+
disabled: isLoading
|
|
345
|
+
}
|
|
346
|
+
),
|
|
347
|
+
isMaybeWrong && mfaCode.length === 6 && /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Incorrect code. Please try again.") }),
|
|
348
|
+
/* @__PURE__ */ jsx("div", { className: "flex", children: /* @__PURE__ */ jsx(
|
|
349
|
+
Button,
|
|
350
|
+
{
|
|
351
|
+
variant: "secondary",
|
|
352
|
+
onClick: () => {
|
|
353
|
+
setGeneratedSecret(null);
|
|
354
|
+
setQrCodeUrl(null);
|
|
355
|
+
setMfaCode("");
|
|
356
|
+
},
|
|
357
|
+
children: t("Cancel")
|
|
358
|
+
}
|
|
359
|
+
) })
|
|
360
|
+
] }),
|
|
361
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-2", children: isEnabled ? /* @__PURE__ */ jsx(
|
|
362
|
+
Button,
|
|
291
363
|
{
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
setIsMaybeWrong(false);
|
|
295
|
-
setMfaCode(e.target.value);
|
|
296
|
-
},
|
|
297
|
-
placeholder: "123456",
|
|
298
|
-
maxLength: 6,
|
|
299
|
-
disabled: isLoading
|
|
300
|
-
}
|
|
301
|
-
),
|
|
302
|
-
isMaybeWrong && mfaCode.length === 6 && /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Incorrect code. Please try again.") })
|
|
303
|
-
] }) : /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Multi-factor authentication is currently disabled.") }),
|
|
304
|
-
/* @__PURE__ */ jsx(
|
|
305
|
-
Button,
|
|
306
|
-
{
|
|
307
|
-
className: "mt-4",
|
|
308
|
-
variant: isEnabled ? "secondary" : "default",
|
|
309
|
-
onClick: async () => {
|
|
310
|
-
if (isEnabled) {
|
|
364
|
+
variant: "secondary",
|
|
365
|
+
onClick: async () => {
|
|
311
366
|
await user.update({
|
|
312
367
|
totpMultiFactorSecret: null
|
|
313
368
|
});
|
|
314
|
-
}
|
|
369
|
+
},
|
|
370
|
+
children: t("Disable")
|
|
371
|
+
}
|
|
372
|
+
) : !generatedSecret && /* @__PURE__ */ jsx(
|
|
373
|
+
Button,
|
|
374
|
+
{
|
|
375
|
+
variant: "default",
|
|
376
|
+
onClick: async () => {
|
|
315
377
|
const secret = generateRandomValues(new Uint8Array(20));
|
|
316
378
|
setQrCodeUrl(await generateTotpQrCode(project, user, secret));
|
|
317
379
|
setGeneratedSecret(secret);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
)
|
|
327
|
-
] })
|
|
328
|
-
] }) });
|
|
380
|
+
},
|
|
381
|
+
children: t("Enable")
|
|
382
|
+
}
|
|
383
|
+
) })
|
|
384
|
+
] })
|
|
385
|
+
}
|
|
386
|
+
);
|
|
329
387
|
}
|
|
330
388
|
async function generateTotpQrCode(project, user, secret) {
|
|
331
389
|
const uri = createTOTPKeyURI(project.displayName, user.primaryEmail ?? user.id, secret);
|
|
332
390
|
return await QRCode.toDataURL(uri);
|
|
333
391
|
}
|
|
334
|
-
function
|
|
392
|
+
function useSignOutSection() {
|
|
335
393
|
const { t } = useTranslation();
|
|
336
394
|
const user = useUser({ or: "throw" });
|
|
337
|
-
return /* @__PURE__ */ jsx(
|
|
338
|
-
|
|
395
|
+
return /* @__PURE__ */ jsx(
|
|
396
|
+
Section,
|
|
339
397
|
{
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
children:
|
|
398
|
+
title: t("Sign out"),
|
|
399
|
+
description: t("End your current session"),
|
|
400
|
+
children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
401
|
+
Button,
|
|
402
|
+
{
|
|
403
|
+
variant: "secondary",
|
|
404
|
+
onClick: () => user.signOut(),
|
|
405
|
+
children: t("Sign out")
|
|
406
|
+
}
|
|
407
|
+
) })
|
|
343
408
|
}
|
|
344
|
-
)
|
|
409
|
+
);
|
|
345
410
|
}
|
|
346
|
-
function
|
|
411
|
+
function TeamPage(props) {
|
|
412
|
+
const teamUserProfileSection = useTeamUserProfileSection(props);
|
|
413
|
+
const teamProfileImageSection = useTeamProfileImageSection(props);
|
|
414
|
+
const teamDisplayNameSection = useTeamDisplayNameSection(props);
|
|
415
|
+
const leaveTeamSection = useLeaveTeamSection(props);
|
|
416
|
+
const memberInvitationSection = useMemberInvitationSection(props);
|
|
417
|
+
const memberListSection = useMemberListSection(props);
|
|
418
|
+
return /* @__PURE__ */ jsxs(PageLayout, { children: [
|
|
419
|
+
teamUserProfileSection,
|
|
420
|
+
memberInvitationSection,
|
|
421
|
+
memberListSection,
|
|
422
|
+
teamProfileImageSection,
|
|
423
|
+
teamDisplayNameSection,
|
|
424
|
+
leaveTeamSection
|
|
425
|
+
] });
|
|
426
|
+
}
|
|
427
|
+
function useLeaveTeamSection(props) {
|
|
347
428
|
const { t } = useTranslation();
|
|
348
429
|
const user = useUser({ or: "redirect" });
|
|
349
430
|
const [leaving, setLeaving] = useState(false);
|
|
350
|
-
return /* @__PURE__ */ jsx(
|
|
351
|
-
|
|
431
|
+
return /* @__PURE__ */ jsx(
|
|
432
|
+
Section,
|
|
352
433
|
{
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
children:
|
|
434
|
+
title: t("Leave Team"),
|
|
435
|
+
description: t("leave this team and remove your team profile"),
|
|
436
|
+
children: !leaving ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
437
|
+
Button,
|
|
438
|
+
{
|
|
439
|
+
variant: "secondary",
|
|
440
|
+
onClick: () => setLeaving(true),
|
|
441
|
+
children: t("Leave team")
|
|
442
|
+
}
|
|
443
|
+
) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
444
|
+
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to leave the team?") }),
|
|
445
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
446
|
+
/* @__PURE__ */ jsx(
|
|
447
|
+
Button,
|
|
448
|
+
{
|
|
449
|
+
variant: "destructive",
|
|
450
|
+
onClick: async () => {
|
|
451
|
+
await user.leaveTeam(props.team);
|
|
452
|
+
window.location.reload();
|
|
453
|
+
},
|
|
454
|
+
children: t("Leave")
|
|
455
|
+
}
|
|
456
|
+
),
|
|
457
|
+
/* @__PURE__ */ jsx(
|
|
458
|
+
Button,
|
|
459
|
+
{
|
|
460
|
+
variant: "secondary",
|
|
461
|
+
onClick: () => setLeaving(false),
|
|
462
|
+
children: t("Cancel")
|
|
463
|
+
}
|
|
464
|
+
)
|
|
465
|
+
] })
|
|
466
|
+
] })
|
|
356
467
|
}
|
|
357
|
-
)
|
|
358
|
-
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to leave the team?") }),
|
|
359
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
360
|
-
/* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: async () => {
|
|
361
|
-
await user.leaveTeam(props.team);
|
|
362
|
-
window.location.reload();
|
|
363
|
-
}, children: t("Leave") }),
|
|
364
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setLeaving(false), children: t("Cancel") })
|
|
365
|
-
] })
|
|
366
|
-
] }) }) });
|
|
468
|
+
);
|
|
367
469
|
}
|
|
368
|
-
function
|
|
470
|
+
function useTeamProfileImageSection(props) {
|
|
369
471
|
const { t } = useTranslation();
|
|
370
472
|
const user = useUser({ or: "redirect" });
|
|
371
473
|
const updateTeamPermission = user.usePermission(props.team, "$update_team");
|
|
372
474
|
if (!updateTeamPermission) {
|
|
373
475
|
return null;
|
|
374
476
|
}
|
|
375
|
-
return /* @__PURE__ */
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
477
|
+
return /* @__PURE__ */ jsx(
|
|
478
|
+
Section,
|
|
479
|
+
{
|
|
480
|
+
title: t("Team profile image"),
|
|
481
|
+
description: t("Upload an image for your team"),
|
|
482
|
+
children: /* @__PURE__ */ jsx(
|
|
379
483
|
ProfileImageEditor,
|
|
380
484
|
{
|
|
381
485
|
user: props.team,
|
|
@@ -384,40 +488,53 @@ function ManagementSettings(props) {
|
|
|
384
488
|
}
|
|
385
489
|
}
|
|
386
490
|
)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
function useTeamDisplayNameSection(props) {
|
|
495
|
+
const { t } = useTranslation();
|
|
496
|
+
const user = useUser({ or: "redirect" });
|
|
497
|
+
const updateTeamPermission = user.usePermission(props.team, "$update_team");
|
|
498
|
+
if (!updateTeamPermission) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
return /* @__PURE__ */ jsx(
|
|
502
|
+
Section,
|
|
503
|
+
{
|
|
504
|
+
title: t("Team display name"),
|
|
505
|
+
description: t("Change the display name of your team"),
|
|
506
|
+
children: /* @__PURE__ */ jsx(
|
|
391
507
|
EditableText,
|
|
392
508
|
{
|
|
393
509
|
value: props.team.displayName,
|
|
394
510
|
onSave: async (newDisplayName) => await props.team.update({ displayName: newDisplayName })
|
|
395
511
|
}
|
|
396
512
|
)
|
|
397
|
-
|
|
398
|
-
|
|
513
|
+
}
|
|
514
|
+
);
|
|
399
515
|
}
|
|
400
|
-
function
|
|
516
|
+
function useTeamUserProfileSection(props) {
|
|
401
517
|
const { t } = useTranslation();
|
|
402
518
|
const user = useUser({ or: "redirect" });
|
|
403
519
|
const profile = user.useTeamProfile(props.team);
|
|
404
|
-
return /* @__PURE__ */ jsx(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
520
|
+
return /* @__PURE__ */ jsx(
|
|
521
|
+
Section,
|
|
522
|
+
{
|
|
523
|
+
title: t("Team user name"),
|
|
524
|
+
description: t("Overwrite your user display name in this team"),
|
|
525
|
+
children: /* @__PURE__ */ jsx(
|
|
526
|
+
EditableText,
|
|
527
|
+
{
|
|
528
|
+
value: profile.displayName || "",
|
|
529
|
+
onSave: async (newDisplayName) => {
|
|
530
|
+
await profile.update({ displayName: newDisplayName });
|
|
531
|
+
}
|
|
415
532
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
533
|
+
)
|
|
534
|
+
}
|
|
535
|
+
);
|
|
419
536
|
}
|
|
420
|
-
function
|
|
537
|
+
function useMemberInvitationSection(props) {
|
|
421
538
|
const { t } = useTranslation();
|
|
422
539
|
const invitationSchema = yupObject({
|
|
423
540
|
email: yupString().email().required(t("Please enter an email address"))
|
|
@@ -444,35 +561,40 @@ function MemberInvitation(props) {
|
|
|
444
561
|
useEffect(() => {
|
|
445
562
|
setInvitedEmail(null);
|
|
446
563
|
}, [watch("email")]);
|
|
447
|
-
return /* @__PURE__ */
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
"
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
564
|
+
return /* @__PURE__ */ jsx(
|
|
565
|
+
Section,
|
|
566
|
+
{
|
|
567
|
+
title: t("Invite member"),
|
|
568
|
+
description: t("Invite a user to your team through email"),
|
|
569
|
+
children: /* @__PURE__ */ jsxs(
|
|
570
|
+
"form",
|
|
571
|
+
{
|
|
572
|
+
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
|
|
573
|
+
noValidate: true,
|
|
574
|
+
className: "w-full",
|
|
575
|
+
children: [
|
|
576
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row w-full", children: [
|
|
577
|
+
/* @__PURE__ */ jsx(
|
|
578
|
+
Input,
|
|
579
|
+
{
|
|
580
|
+
placeholder: t("Email"),
|
|
581
|
+
...register("email")
|
|
582
|
+
}
|
|
583
|
+
),
|
|
584
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading, children: t("Invite User") })
|
|
585
|
+
] }),
|
|
586
|
+
/* @__PURE__ */ jsx(FormWarningText, { text: errors.email?.message?.toString() }),
|
|
587
|
+
invitedEmail && /* @__PURE__ */ jsxs(Typography, { type: "label", variant: "secondary", children: [
|
|
588
|
+
"Invited ",
|
|
589
|
+
invitedEmail
|
|
590
|
+
] })
|
|
591
|
+
]
|
|
592
|
+
}
|
|
593
|
+
)
|
|
594
|
+
}
|
|
595
|
+
);
|
|
474
596
|
}
|
|
475
|
-
function
|
|
597
|
+
function useMemberListSection(props) {
|
|
476
598
|
const { t } = useTranslation();
|
|
477
599
|
const user = useUser({ or: "redirect" });
|
|
478
600
|
const readMemberPermission = user.usePermission(props.team, "$read_members");
|
|
@@ -485,8 +607,8 @@ function MembersSettings(props) {
|
|
|
485
607
|
return null;
|
|
486
608
|
}
|
|
487
609
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
488
|
-
/* @__PURE__ */ jsx(
|
|
489
|
-
/* @__PURE__ */ jsxs(Table, { children: [
|
|
610
|
+
/* @__PURE__ */ jsx(Typography, { className: "font-medium mb-2", children: t("Members") }),
|
|
611
|
+
/* @__PURE__ */ jsx("div", { className: "border rounded-md", children: /* @__PURE__ */ jsxs(Table, { children: [
|
|
490
612
|
/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
491
613
|
/* @__PURE__ */ jsx(TableHead, { className: "w-[100px]", children: t("User") }),
|
|
492
614
|
/* @__PURE__ */ jsx(TableHead, { className: "w-[200px]", children: t("Name") })
|
|
@@ -495,7 +617,7 @@ function MembersSettings(props) {
|
|
|
495
617
|
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(UserAvatar, { user: teamProfile }) }),
|
|
496
618
|
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { children: teamProfile.displayName }) })
|
|
497
619
|
] }, id)) })
|
|
498
|
-
] })
|
|
620
|
+
] }) })
|
|
499
621
|
] });
|
|
500
622
|
}
|
|
501
623
|
function TeamCreation() {
|
|
@@ -509,44 +631,98 @@ function TeamCreation() {
|
|
|
509
631
|
const app = useStackApp();
|
|
510
632
|
const project = app.useProject();
|
|
511
633
|
const user = useUser({ or: "redirect" });
|
|
634
|
+
const router = useRouter();
|
|
512
635
|
const [loading, setLoading] = useState(false);
|
|
513
636
|
if (!project.config.clientTeamCreationEnabled) {
|
|
514
637
|
return /* @__PURE__ */ jsx(MessageCard, { title: t("Team creation is not enabled") });
|
|
515
638
|
}
|
|
516
639
|
const onSubmit = async (data) => {
|
|
517
640
|
setLoading(true);
|
|
641
|
+
let team;
|
|
518
642
|
try {
|
|
519
|
-
|
|
643
|
+
team = await user.createTeam({ displayName: data.displayName });
|
|
520
644
|
} finally {
|
|
521
645
|
setLoading(false);
|
|
522
646
|
}
|
|
647
|
+
router.push(app.urls.accountSettings + `/teams/${team.id}`);
|
|
523
648
|
};
|
|
524
|
-
return /* @__PURE__ */ jsx(
|
|
649
|
+
return /* @__PURE__ */ jsx(PageLayout, { children: /* @__PURE__ */ jsx(Section, { title: t("Create a Team"), description: t("Enter a display name for your new team"), children: /* @__PURE__ */ jsxs(
|
|
525
650
|
"form",
|
|
526
651
|
{
|
|
527
|
-
className: "flex flex-col items-stretch stack-scope",
|
|
528
652
|
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
|
|
529
653
|
noValidate: true,
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
654
|
+
className: "flex gap-2 flex-col sm:flex-row",
|
|
655
|
+
children: [
|
|
656
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1", children: [
|
|
533
657
|
/* @__PURE__ */ jsx(
|
|
534
658
|
Input,
|
|
535
659
|
{
|
|
536
|
-
id: "
|
|
537
|
-
type: "
|
|
660
|
+
id: "displayName",
|
|
661
|
+
type: "text",
|
|
538
662
|
...register("displayName")
|
|
539
663
|
}
|
|
540
|
-
)
|
|
664
|
+
),
|
|
665
|
+
/* @__PURE__ */ jsx(FormWarningText, { text: errors.displayName?.message?.toString() })
|
|
541
666
|
] }),
|
|
542
|
-
/* @__PURE__ */ jsx(
|
|
543
|
-
|
|
544
|
-
] })
|
|
667
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading, children: t("Create") })
|
|
668
|
+
]
|
|
545
669
|
}
|
|
546
670
|
) }) });
|
|
547
671
|
}
|
|
672
|
+
function useDeleteAccountSection() {
|
|
673
|
+
const { t } = useTranslation();
|
|
674
|
+
const user = useUser({ or: "redirect" });
|
|
675
|
+
const app = useStackApp();
|
|
676
|
+
const project = app.useProject();
|
|
677
|
+
const [deleting, setDeleting] = useState(false);
|
|
678
|
+
if (!project.config.clientUserDeletionEnabled) {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
return /* @__PURE__ */ jsx(
|
|
682
|
+
Section,
|
|
683
|
+
{
|
|
684
|
+
title: t("Delete Account"),
|
|
685
|
+
description: t("Permanently remove your account and all associated data"),
|
|
686
|
+
children: /* @__PURE__ */ jsx("div", { className: "stack-scope flex flex-col items-stretch", children: /* @__PURE__ */ jsx(Accordion, { type: "single", collapsible: true, className: "w-full", children: /* @__PURE__ */ jsxs(AccordionItem, { value: "item-1", children: [
|
|
687
|
+
/* @__PURE__ */ jsx(AccordionTrigger, { children: t("Danger zone") }),
|
|
688
|
+
/* @__PURE__ */ jsx(AccordionContent, { children: !deleting ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
689
|
+
Button,
|
|
690
|
+
{
|
|
691
|
+
variant: "destructive",
|
|
692
|
+
onClick: () => setDeleting(true),
|
|
693
|
+
children: t("Delete account")
|
|
694
|
+
}
|
|
695
|
+
) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
696
|
+
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: t("Are you sure you want to delete your account? This action is IRREVERSIBLE and will delete ALL associated data.") }),
|
|
697
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
698
|
+
/* @__PURE__ */ jsx(
|
|
699
|
+
Button,
|
|
700
|
+
{
|
|
701
|
+
variant: "destructive",
|
|
702
|
+
onClick: async () => {
|
|
703
|
+
await user.delete();
|
|
704
|
+
await app.redirectToHome();
|
|
705
|
+
},
|
|
706
|
+
children: t("Delete Account")
|
|
707
|
+
}
|
|
708
|
+
),
|
|
709
|
+
/* @__PURE__ */ jsx(
|
|
710
|
+
Button,
|
|
711
|
+
{
|
|
712
|
+
variant: "secondary",
|
|
713
|
+
onClick: () => setDeleting(false),
|
|
714
|
+
children: t("Cancel")
|
|
715
|
+
}
|
|
716
|
+
)
|
|
717
|
+
] })
|
|
718
|
+
] }) })
|
|
719
|
+
] }) }) })
|
|
720
|
+
}
|
|
721
|
+
);
|
|
722
|
+
}
|
|
548
723
|
export {
|
|
549
724
|
AccountSettings,
|
|
550
|
-
TeamCreation
|
|
725
|
+
TeamCreation,
|
|
726
|
+
useDeleteAccountSection
|
|
551
727
|
};
|
|
552
728
|
//# sourceMappingURL=account-settings.js.map
|