@stackframe/stack 2.5.19 → 2.5.20
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 +10 -0
- package/dist/components/elements/sidebar-layout.d.mts +8 -9
- package/dist/components/elements/sidebar-layout.d.ts +8 -9
- package/dist/components/elements/sidebar-layout.js +45 -59
- package/dist/components/elements/sidebar-layout.js.map +1 -1
- package/dist/components/selected-team-switcher.js +8 -25
- package/dist/components/selected-team-switcher.js.map +1 -1
- package/dist/components/team-icon.d.mts +18 -0
- package/dist/components/team-icon.d.ts +18 -0
- package/dist/components/team-icon.js +50 -0
- package/dist/components/team-icon.js.map +1 -0
- package/dist/components-page/account-settings.d.mts +2 -1
- package/dist/components-page/account-settings.d.ts +2 -1
- package/dist/components-page/account-settings.js +385 -198
- package/dist/components-page/account-settings.js.map +1 -1
- package/dist/components-page/stack-handler.js +2 -24
- package/dist/components-page/stack-handler.js.map +1 -1
- package/dist/esm/components/elements/sidebar-layout.js +44 -47
- package/dist/esm/components/elements/sidebar-layout.js.map +1 -1
- package/dist/esm/components/selected-team-switcher.js +6 -13
- package/dist/esm/components/selected-team-switcher.js.map +1 -1
- package/dist/esm/components/team-icon.js +15 -0
- package/dist/esm/components/team-icon.js.map +1 -0
- package/dist/esm/components-page/account-settings.js +388 -202
- package/dist/esm/components-page/account-settings.js.map +1 -1
- package/dist/esm/components-page/stack-handler.js +2 -24
- package/dist/esm/components-page/stack-handler.js.map +1 -1
- package/dist/esm/lib/stack-app.js +11 -5
- package/dist/esm/lib/stack-app.js.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/lib/stack-app.d.mts +6 -2
- package/dist/lib/stack-app.d.ts +6 -2
- package/dist/lib/stack-app.js +11 -5
- package/dist/lib/stack-app.js.map +1 -1
- package/package.json +4 -4
- package/dist/components-page/team-settings.d.mts +0 -8
- package/dist/components-page/team-settings.d.ts +0 -8
- package/dist/components-page/team-settings.js +0 -139
- package/dist/components-page/team-settings.js.map +0 -1
- package/dist/esm/components-page/team-settings.js +0 -115
- package/dist/esm/components-page/team-settings.js.map +0 -1
|
@@ -2,226 +2,227 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
// src/components-page/account-settings.tsx
|
|
5
|
+
import { yupResolver } from "@hookform/resolvers/yup";
|
|
5
6
|
import { getPasswordError } from "@stackframe/stack-shared/dist/helpers/password";
|
|
6
7
|
import { useAsyncCallback } from "@stackframe/stack-shared/dist/hooks/use-async-callback";
|
|
8
|
+
import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
|
|
7
9
|
import { generateRandomValues } from "@stackframe/stack-shared/dist/utils/crypto";
|
|
8
10
|
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
|
|
9
|
-
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
|
10
|
-
import { Button,
|
|
11
|
-
import { Contact, Settings, ShieldCheck } from "lucide-react";
|
|
11
|
+
import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
|
12
|
+
import { Button, Container, EditableText, Input, Label, PasswordInput, SimpleTooltip, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
|
|
13
|
+
import { CirclePlus, Contact, Settings, ShieldCheck } from "lucide-react";
|
|
12
14
|
import { TOTPController, createTOTPKeyURI } from "oslo/otp";
|
|
13
15
|
import * as QRCode from "qrcode";
|
|
14
16
|
import { useEffect, useState } from "react";
|
|
15
|
-
import {
|
|
17
|
+
import { useForm } from "react-hook-form";
|
|
18
|
+
import * as yup from "yup";
|
|
19
|
+
import { MessageCard, useStackApp, useUser } from "..";
|
|
16
20
|
import { FormWarningText } from "../components/elements/form-warning";
|
|
17
21
|
import { SidebarLayout } from "../components/elements/sidebar-layout";
|
|
18
22
|
import { UserAvatar } from "../components/elements/user-avatar";
|
|
19
|
-
import {
|
|
23
|
+
import { TeamIcon } from "../components/team-icon";
|
|
24
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
20
25
|
function AccountSettings({ fullPage = false }) {
|
|
21
26
|
const user = useUser({ or: "redirect" });
|
|
27
|
+
const teams = user.useTeams();
|
|
28
|
+
const project = useStackApp().useProject();
|
|
22
29
|
const inner = /* @__PURE__ */ jsx(
|
|
23
30
|
SidebarLayout,
|
|
24
31
|
{
|
|
25
32
|
items: [
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
{
|
|
34
|
+
title: "My Profile",
|
|
35
|
+
type: "item",
|
|
36
|
+
subpath: "/profile",
|
|
37
|
+
icon: Contact,
|
|
38
|
+
content: /* @__PURE__ */ jsx(ProfileSection, {})
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: "Security",
|
|
42
|
+
type: "item",
|
|
43
|
+
icon: ShieldCheck,
|
|
44
|
+
subpath: "/security",
|
|
45
|
+
content: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-8", children: [
|
|
46
|
+
/* @__PURE__ */ jsx(EmailVerificationSection, {}),
|
|
47
|
+
/* @__PURE__ */ jsx(PasswordSection, {}),
|
|
48
|
+
/* @__PURE__ */ jsx(MfaSection, {})
|
|
49
|
+
] })
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: "Settings",
|
|
53
|
+
subpath: "/settings",
|
|
54
|
+
type: "item",
|
|
55
|
+
icon: Settings,
|
|
56
|
+
content: /* @__PURE__ */ jsx(SignOutSection, {})
|
|
57
|
+
},
|
|
58
|
+
...teams.length > 0 || project.config.clientTeamCreationEnabled ? [{
|
|
59
|
+
title: "Teams",
|
|
60
|
+
type: "divider"
|
|
61
|
+
}] : [],
|
|
62
|
+
...teams.map((team) => ({
|
|
63
|
+
title: /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
|
|
64
|
+
/* @__PURE__ */ jsx(TeamIcon, { team }),
|
|
65
|
+
team.displayName
|
|
66
|
+
] }),
|
|
67
|
+
type: "item",
|
|
68
|
+
subpath: `/teams/${team.id}`,
|
|
69
|
+
content: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-8", children: [
|
|
70
|
+
/* @__PURE__ */ jsx(ProfileSettings, { team }),
|
|
71
|
+
/* @__PURE__ */ jsx(ManagementSettings, { team }),
|
|
72
|
+
/* @__PURE__ */ jsx(MemberInvitation, { team }),
|
|
73
|
+
/* @__PURE__ */ jsx(MembersSettings, { team }),
|
|
74
|
+
/* @__PURE__ */ jsx(UserSettings, { team })
|
|
75
|
+
] })
|
|
76
|
+
})),
|
|
77
|
+
...project.config.clientTeamCreationEnabled ? [{
|
|
78
|
+
title: "Create a team",
|
|
79
|
+
icon: CirclePlus,
|
|
80
|
+
type: "item",
|
|
81
|
+
subpath: "/team-creation",
|
|
82
|
+
content: /* @__PURE__ */ jsx(TeamCreation, {})
|
|
83
|
+
}] : []
|
|
84
|
+
].filter((p) => p.type === "divider" || p.content),
|
|
85
|
+
title: "Account Settings",
|
|
86
|
+
basePath: "/handler/account-settings"
|
|
35
87
|
}
|
|
36
88
|
);
|
|
37
89
|
if (fullPage) {
|
|
38
|
-
return /* @__PURE__ */ jsx(Container, { size:
|
|
90
|
+
return /* @__PURE__ */ jsx(Container, { size: 1e3, className: "stack-scope", children: inner });
|
|
39
91
|
} else {
|
|
40
92
|
return inner;
|
|
41
93
|
}
|
|
42
94
|
}
|
|
43
|
-
function SettingSection(props) {
|
|
44
|
-
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
45
|
-
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs("div", { children: [
|
|
46
|
-
/* @__PURE__ */ jsx(Typography, { type: "h4", children: props.title }),
|
|
47
|
-
/* @__PURE__ */ jsx(Typography, { type: "label", variant: "secondary", children: props.desc })
|
|
48
|
-
] }) }),
|
|
49
|
-
props.children && /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children: props.children }) }),
|
|
50
|
-
props.buttonText && /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsx("div", { className: "flex justify-end w-full", children: /* @__PURE__ */ jsx(
|
|
51
|
-
Button,
|
|
52
|
-
{
|
|
53
|
-
disabled: props.buttonDisabled,
|
|
54
|
-
onClick: props.onButtonClick,
|
|
55
|
-
variant: props.buttonVariant,
|
|
56
|
-
children: props.buttonText
|
|
57
|
-
}
|
|
58
|
-
) }) })
|
|
59
|
-
] });
|
|
60
|
-
}
|
|
61
95
|
function ProfileSection() {
|
|
62
|
-
const user = useUser();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
desc: "Your profile information",
|
|
70
|
-
buttonDisabled: !changed,
|
|
71
|
-
buttonText: "Save",
|
|
72
|
-
onButtonClick: async () => {
|
|
73
|
-
await user.update(userInfo);
|
|
74
|
-
setChanged(false);
|
|
75
|
-
},
|
|
76
|
-
children: [
|
|
77
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-4 items-center", children: [
|
|
78
|
-
/* @__PURE__ */ jsx(UserAvatar, { user, size: 50 }),
|
|
79
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
80
|
-
/* @__PURE__ */ jsx(Typography, { children: user.displayName }),
|
|
81
|
-
/* @__PURE__ */ jsx(Typography, { variant: "secondary", type: "label", children: user.primaryEmail })
|
|
82
|
-
] })
|
|
83
|
-
] }),
|
|
84
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
85
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "display-name", className: "mb-1", children: "Display Name" }),
|
|
86
|
-
/* @__PURE__ */ jsx(
|
|
87
|
-
Input,
|
|
88
|
-
{
|
|
89
|
-
id: "display-name",
|
|
90
|
-
value: userInfo.displayName,
|
|
91
|
-
onChange: (e) => {
|
|
92
|
-
setUserInfo((i) => ({ ...i, displayName: e.target.value }));
|
|
93
|
-
setChanged(true);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
)
|
|
97
|
-
] })
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
);
|
|
96
|
+
const user = useUser({ or: "redirect" });
|
|
97
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs("div", { children: [
|
|
98
|
+
/* @__PURE__ */ jsx(Label, { children: "Display name" }),
|
|
99
|
+
/* @__PURE__ */ jsx(EditableText, { value: user.displayName || "", onSave: async (newDisplayName) => {
|
|
100
|
+
await user.update({ displayName: newDisplayName });
|
|
101
|
+
} })
|
|
102
|
+
] }) });
|
|
101
103
|
}
|
|
102
104
|
function EmailVerificationSection() {
|
|
103
|
-
const user = useUser();
|
|
105
|
+
const user = useUser({ or: "redirect" });
|
|
104
106
|
const [emailSent, setEmailSent] = useState(false);
|
|
105
|
-
return /* @__PURE__ */ jsx(
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
107
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { children: [
|
|
108
|
+
/* @__PURE__ */ jsx(Label, { children: "Email Verification" }),
|
|
109
|
+
user.primaryEmailVerified ? /* @__PURE__ */ jsx(Typography, { variant: "success", children: "Your email has been verified." }) : /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: "Your email has not been verified." }),
|
|
110
|
+
/* @__PURE__ */ jsx("div", { className: "flex mt-4", children: /* @__PURE__ */ jsx(
|
|
111
|
+
Button,
|
|
112
|
+
{
|
|
113
|
+
disabled: emailSent,
|
|
114
|
+
onClick: async () => {
|
|
115
|
+
await user.sendVerificationEmail();
|
|
116
|
+
setEmailSent(true);
|
|
117
|
+
},
|
|
118
|
+
children: emailSent ? "Email sent!" : "Send Verification Email"
|
|
119
|
+
}
|
|
120
|
+
) })
|
|
121
|
+
] }) });
|
|
119
122
|
}
|
|
123
|
+
var passwordSchema = yupObject({
|
|
124
|
+
oldPassword: yupString().required("Please enter your old password"),
|
|
125
|
+
newPassword: yupString().required("Please enter your password").test({
|
|
126
|
+
name: "is-valid-password",
|
|
127
|
+
test: (value, ctx) => {
|
|
128
|
+
const error = getPasswordError(value);
|
|
129
|
+
if (error) {
|
|
130
|
+
return ctx.createError({ message: error.message });
|
|
131
|
+
} else {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}),
|
|
136
|
+
newPasswordRepeat: yupString().nullable().oneOf([yup.ref("newPassword"), "", null], "Passwords do not match").required("Please repeat your password")
|
|
137
|
+
});
|
|
120
138
|
function PasswordSection() {
|
|
121
139
|
const user = useUser({ or: "throw" });
|
|
122
|
-
const [
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const [
|
|
127
|
-
const [
|
|
128
|
-
const
|
|
140
|
+
const [changingPassword, setChangingPassword] = useState(false);
|
|
141
|
+
const { register, handleSubmit, setError, formState: { errors }, clearErrors, reset } = useForm({
|
|
142
|
+
resolver: yupResolver(passwordSchema)
|
|
143
|
+
});
|
|
144
|
+
const [alreadyReset, setAlreadyReset] = useState(false);
|
|
145
|
+
const [loading, setLoading] = useState(false);
|
|
146
|
+
const onSubmit = async (data) => {
|
|
147
|
+
setLoading(true);
|
|
148
|
+
try {
|
|
149
|
+
const { oldPassword, newPassword } = data;
|
|
150
|
+
const error = await user.updatePassword({ oldPassword, newPassword });
|
|
151
|
+
if (error) {
|
|
152
|
+
setError("oldPassword", { type: "manual", message: "Incorrect password" });
|
|
153
|
+
} else {
|
|
154
|
+
reset();
|
|
155
|
+
setAlreadyReset(true);
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
setLoading(false);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const registerPassword = register("newPassword");
|
|
162
|
+
const registerPasswordRepeat = register("newPasswordRepeat");
|
|
129
163
|
if (!user.hasPassword) {
|
|
130
164
|
return null;
|
|
131
165
|
}
|
|
132
|
-
return /* @__PURE__ */ jsxs(
|
|
133
|
-
|
|
134
|
-
{
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
setRepeatNewPasswordError("Please repeat your new password");
|
|
151
|
-
return;
|
|
152
|
-
} else {
|
|
153
|
-
const errorMessage = getPasswordError(newPassword);
|
|
154
|
-
if (errorMessage) {
|
|
155
|
-
setNewPasswordError(errorMessage.message);
|
|
156
|
-
} else {
|
|
157
|
-
if (newPassword !== repeatNewPassword) {
|
|
158
|
-
setRepeatNewPasswordError("Passwords do not match");
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const errorCode = await user.updatePassword({ oldPassword, newPassword });
|
|
162
|
-
if (errorCode) {
|
|
163
|
-
setOldPasswordError("Incorrect password");
|
|
164
|
-
} else {
|
|
165
|
-
setOldPassword("");
|
|
166
|
-
setNewPassword("");
|
|
167
|
-
setRepeatNewPassword("");
|
|
168
|
-
setPasswordChanged(true);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
children: [
|
|
174
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
175
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "old-password", className: "mb-1", children: "Old Password" }),
|
|
166
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
167
|
+
/* @__PURE__ */ jsx(Label, { children: "Change password" }),
|
|
168
|
+
/* @__PURE__ */ jsx("div", { children: alreadyReset ? /* @__PURE__ */ jsx(Typography, { variant: "success", children: "Password changed successfully!" }) : !changingPassword ? /* @__PURE__ */ jsx(
|
|
169
|
+
Button,
|
|
170
|
+
{
|
|
171
|
+
variant: "secondary",
|
|
172
|
+
onClick: async () => {
|
|
173
|
+
setChangingPassword(true);
|
|
174
|
+
},
|
|
175
|
+
children: "Change Password"
|
|
176
|
+
}
|
|
177
|
+
) : /* @__PURE__ */ jsxs(
|
|
178
|
+
"form",
|
|
179
|
+
{
|
|
180
|
+
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
|
|
181
|
+
noValidate: true,
|
|
182
|
+
children: [
|
|
183
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "old-password", className: "mb-1", children: "Old password" }),
|
|
176
184
|
/* @__PURE__ */ jsx(
|
|
177
|
-
|
|
185
|
+
Input,
|
|
178
186
|
{
|
|
179
187
|
id: "old-password",
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
setOldPassword(e.target.value);
|
|
183
|
-
setOldPasswordError("");
|
|
184
|
-
setPasswordChanged(false);
|
|
185
|
-
}
|
|
188
|
+
type: "password",
|
|
189
|
+
...register("oldPassword")
|
|
186
190
|
}
|
|
187
191
|
),
|
|
188
|
-
/* @__PURE__ */ jsx(FormWarningText, { text:
|
|
189
|
-
|
|
190
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
191
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "new-password", className: "mb-1", children: "New Password" }),
|
|
192
|
+
/* @__PURE__ */ jsx(FormWarningText, { text: errors.oldPassword?.message?.toString() }),
|
|
193
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "new-password", className: "mt-4 mb-1", children: "Password" }),
|
|
192
194
|
/* @__PURE__ */ jsx(
|
|
193
195
|
PasswordInput,
|
|
194
196
|
{
|
|
195
197
|
id: "new-password",
|
|
196
|
-
|
|
198
|
+
...registerPassword,
|
|
197
199
|
onChange: (e) => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
clearErrors("newPassword");
|
|
201
|
+
clearErrors("newPasswordRepeat");
|
|
202
|
+
runAsynchronously(registerPassword.onChange(e));
|
|
201
203
|
}
|
|
202
204
|
}
|
|
203
205
|
),
|
|
204
|
-
/* @__PURE__ */ jsx(FormWarningText, { text:
|
|
205
|
-
|
|
206
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
207
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "repeat-new-password", className: "mb-1", children: "Repeat New Password" }),
|
|
206
|
+
/* @__PURE__ */ jsx(FormWarningText, { text: errors.newPassword?.message?.toString() }),
|
|
207
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "repeat-password", className: "mt-4 mb-1", children: "Repeat password" }),
|
|
208
208
|
/* @__PURE__ */ jsx(
|
|
209
209
|
PasswordInput,
|
|
210
210
|
{
|
|
211
|
-
id: "repeat-
|
|
212
|
-
|
|
211
|
+
id: "repeat-password",
|
|
212
|
+
...registerPasswordRepeat,
|
|
213
213
|
onChange: (e) => {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
clearErrors("newPassword");
|
|
215
|
+
clearErrors("newPasswordRepeat");
|
|
216
|
+
runAsynchronously(registerPasswordRepeat.onChange(e));
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
),
|
|
220
|
-
/* @__PURE__ */ jsx(FormWarningText, { text:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
220
|
+
/* @__PURE__ */ jsx(FormWarningText, { text: errors.newPasswordRepeat?.message?.toString() }),
|
|
221
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-6", loading, children: "Change Password" })
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
) })
|
|
225
|
+
] });
|
|
225
226
|
}
|
|
226
227
|
function MfaSection() {
|
|
227
228
|
const project = useStackApp().useProject();
|
|
@@ -248,29 +249,10 @@ function MfaSection() {
|
|
|
248
249
|
setIsMaybeWrong(true);
|
|
249
250
|
});
|
|
250
251
|
}, [mfaCode, generatedSecret, handleSubmit]);
|
|
251
|
-
return /* @__PURE__ */ jsx(
|
|
252
|
-
|
|
253
|
-
{
|
|
254
|
-
|
|
255
|
-
desc: "Secure your account with an additional layer of security.",
|
|
256
|
-
buttonVariant: "secondary",
|
|
257
|
-
buttonText: isEnabled ? "Disable" : generatedSecret ? "Cancel" : "Enable",
|
|
258
|
-
onButtonClick: async () => {
|
|
259
|
-
if (isEnabled) {
|
|
260
|
-
await user.update({
|
|
261
|
-
totpMultiFactorSecret: null
|
|
262
|
-
});
|
|
263
|
-
} else if (!generatedSecret) {
|
|
264
|
-
const secret = generateRandomValues(new Uint8Array(20));
|
|
265
|
-
setQrCodeUrl(await generateTotpQrCode(project, user, secret));
|
|
266
|
-
setGeneratedSecret(secret);
|
|
267
|
-
} else {
|
|
268
|
-
setGeneratedSecret(null);
|
|
269
|
-
setQrCodeUrl(null);
|
|
270
|
-
setMfaCode("");
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
children: isEnabled ? /* @__PURE__ */ jsx(Typography, { variant: "success", children: "Multi-factor authentication is currently enabled." }) : generatedSecret ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 items-center", children: [
|
|
252
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs("div", { children: [
|
|
253
|
+
/* @__PURE__ */ jsx(Label, { children: "Multi-factor Authentication" }),
|
|
254
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
255
|
+
isEnabled ? /* @__PURE__ */ jsx(Typography, { variant: "success", children: "Multi-factor authentication is currently enabled." }) : generatedSecret ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 items-center", children: [
|
|
274
256
|
/* @__PURE__ */ jsx(Typography, { children: "Scan this QR code with your authenticator app:" }),
|
|
275
257
|
/* @__PURE__ */ jsx("img", { width: 200, height: 200, src: qrCodeUrl ?? throwErr("TOTP QR code failed to generate"), alt: "TOTP multi-factor authentication QR code" }),
|
|
276
258
|
/* @__PURE__ */ jsx(Typography, { children: "Then, enter your six-digit MFA code:" }),
|
|
@@ -288,9 +270,32 @@ function MfaSection() {
|
|
|
288
270
|
}
|
|
289
271
|
),
|
|
290
272
|
isMaybeWrong && mfaCode.length === 6 && /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: "Incorrect code. Please try again." })
|
|
291
|
-
] }) : /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: "Multi-factor authentication is currently disabled." })
|
|
292
|
-
|
|
293
|
-
|
|
273
|
+
] }) : /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: "Multi-factor authentication is currently disabled." }),
|
|
274
|
+
/* @__PURE__ */ jsx(
|
|
275
|
+
Button,
|
|
276
|
+
{
|
|
277
|
+
className: "mt-4",
|
|
278
|
+
variant: isEnabled ? "secondary" : "default",
|
|
279
|
+
onClick: async () => {
|
|
280
|
+
if (isEnabled) {
|
|
281
|
+
await user.update({
|
|
282
|
+
totpMultiFactorSecret: null
|
|
283
|
+
});
|
|
284
|
+
} else if (!generatedSecret) {
|
|
285
|
+
const secret = generateRandomValues(new Uint8Array(20));
|
|
286
|
+
setQrCodeUrl(await generateTotpQrCode(project, user, secret));
|
|
287
|
+
setGeneratedSecret(secret);
|
|
288
|
+
} else {
|
|
289
|
+
setGeneratedSecret(null);
|
|
290
|
+
setQrCodeUrl(null);
|
|
291
|
+
setMfaCode("");
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
children: isEnabled ? "Disable" : generatedSecret ? "Cancel" : "Enable"
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
] })
|
|
298
|
+
] }) });
|
|
294
299
|
}
|
|
295
300
|
async function generateTotpQrCode(project, user, secret) {
|
|
296
301
|
const uri = createTOTPKeyURI(project.displayName, user.primaryEmail ?? user.id, secret);
|
|
@@ -298,18 +303,199 @@ async function generateTotpQrCode(project, user, secret) {
|
|
|
298
303
|
}
|
|
299
304
|
function SignOutSection() {
|
|
300
305
|
const user = useUser({ or: "throw" });
|
|
301
|
-
return /* @__PURE__ */ jsx(
|
|
302
|
-
|
|
306
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
307
|
+
Button,
|
|
303
308
|
{
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
buttonText: "Sign Out",
|
|
308
|
-
onButtonClick: () => user.signOut()
|
|
309
|
+
variant: "secondary",
|
|
310
|
+
onClick: () => user.signOut(),
|
|
311
|
+
children: "Sign Out"
|
|
309
312
|
}
|
|
310
|
-
);
|
|
313
|
+
) }) });
|
|
314
|
+
}
|
|
315
|
+
function UserSettings(props) {
|
|
316
|
+
const app = useStackApp();
|
|
317
|
+
const user = useUser({ or: "redirect" });
|
|
318
|
+
const [leaving, setLeaving] = useState(false);
|
|
319
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: /* @__PURE__ */ jsx("div", { children: !leaving ? /* @__PURE__ */ jsx(
|
|
320
|
+
Button,
|
|
321
|
+
{
|
|
322
|
+
variant: "secondary",
|
|
323
|
+
onClick: async () => setLeaving(true),
|
|
324
|
+
children: "Leave team"
|
|
325
|
+
}
|
|
326
|
+
) : /* @__PURE__ */ jsxs("div", { className: "", children: [
|
|
327
|
+
/* @__PURE__ */ jsx(Typography, { variant: "destructive", children: "Are you sure you want to leave the team?" }),
|
|
328
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
329
|
+
/* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: async () => {
|
|
330
|
+
await user.leaveTeam(props.team);
|
|
331
|
+
window.location.reload();
|
|
332
|
+
}, children: "Leave" }),
|
|
333
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setLeaving(false), children: "Cancel" })
|
|
334
|
+
] })
|
|
335
|
+
] }) }) });
|
|
336
|
+
}
|
|
337
|
+
function ManagementSettings(props) {
|
|
338
|
+
const user = useUser({ or: "redirect" });
|
|
339
|
+
const updateTeamPermission = user.usePermission(props.team, "$update_team");
|
|
340
|
+
if (!updateTeamPermission) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { children: [
|
|
344
|
+
/* @__PURE__ */ jsx(Label, { children: "Team display name" }),
|
|
345
|
+
/* @__PURE__ */ jsx(
|
|
346
|
+
EditableText,
|
|
347
|
+
{
|
|
348
|
+
value: props.team.displayName,
|
|
349
|
+
onSave: async (newDisplayName) => await props.team.update({ displayName: newDisplayName })
|
|
350
|
+
}
|
|
351
|
+
)
|
|
352
|
+
] }) });
|
|
353
|
+
}
|
|
354
|
+
function ProfileSettings(props) {
|
|
355
|
+
const user = useUser({ or: "redirect" });
|
|
356
|
+
const profile = user.useTeamProfile(props.team);
|
|
357
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-col", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
358
|
+
/* @__PURE__ */ jsxs(Label, { className: "flex gap-2", children: [
|
|
359
|
+
"User display name ",
|
|
360
|
+
/* @__PURE__ */ jsx(SimpleTooltip, { tooltip: "This overwrites your user display name in the account setting", type: "info" })
|
|
361
|
+
] }),
|
|
362
|
+
/* @__PURE__ */ jsx(
|
|
363
|
+
EditableText,
|
|
364
|
+
{
|
|
365
|
+
value: profile.displayName || "",
|
|
366
|
+
onSave: async (newDisplayName) => {
|
|
367
|
+
await profile.update({ displayName: newDisplayName });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
] }) });
|
|
372
|
+
}
|
|
373
|
+
var invitationSchema = yupObject({
|
|
374
|
+
email: yupString().email().required("Please enter an email address")
|
|
375
|
+
});
|
|
376
|
+
function MemberInvitation(props) {
|
|
377
|
+
const project = useStackApp().useProject();
|
|
378
|
+
if (!project.config.clientTeamCreationEnabled) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const { register, handleSubmit, formState: { errors }, watch } = useForm({
|
|
382
|
+
resolver: yupResolver(invitationSchema)
|
|
383
|
+
});
|
|
384
|
+
const [loading, setLoading] = useState(false);
|
|
385
|
+
const [invitedEmail, setInvitedEmail] = useState(null);
|
|
386
|
+
const onSubmit = async (data) => {
|
|
387
|
+
setLoading(true);
|
|
388
|
+
try {
|
|
389
|
+
await props.team.inviteUser({ email: data.email });
|
|
390
|
+
setInvitedEmail(data.email);
|
|
391
|
+
} finally {
|
|
392
|
+
setLoading(false);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
setInvitedEmail(null);
|
|
397
|
+
}, [watch("email")]);
|
|
398
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
399
|
+
/* @__PURE__ */ jsx(Label, { children: "Invite a user to team" }),
|
|
400
|
+
/* @__PURE__ */ jsxs(
|
|
401
|
+
"form",
|
|
402
|
+
{
|
|
403
|
+
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
|
|
404
|
+
noValidate: true,
|
|
405
|
+
children: [
|
|
406
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 md:flex-row", children: [
|
|
407
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
408
|
+
Input,
|
|
409
|
+
{
|
|
410
|
+
placeholder: "Email",
|
|
411
|
+
...register("email")
|
|
412
|
+
}
|
|
413
|
+
) }),
|
|
414
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading, children: "Invite User" })
|
|
415
|
+
] }),
|
|
416
|
+
/* @__PURE__ */ jsx(FormWarningText, { text: errors.email?.message?.toString() }),
|
|
417
|
+
invitedEmail && /* @__PURE__ */ jsxs(Typography, { type: "label", variant: "secondary", children: [
|
|
418
|
+
"Invited ",
|
|
419
|
+
invitedEmail
|
|
420
|
+
] })
|
|
421
|
+
]
|
|
422
|
+
}
|
|
423
|
+
)
|
|
424
|
+
] });
|
|
425
|
+
}
|
|
426
|
+
function MembersSettings(props) {
|
|
427
|
+
const user = useUser({ or: "redirect" });
|
|
428
|
+
const readMemberPermission = user.usePermission(props.team, "$read_members");
|
|
429
|
+
const inviteMemberPermission = user.usePermission(props.team, "$invite_members");
|
|
430
|
+
if (!readMemberPermission && !inviteMemberPermission) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
const users = props.team.useUsers();
|
|
434
|
+
if (!readMemberPermission) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
438
|
+
/* @__PURE__ */ jsx(Label, { children: "Members" }),
|
|
439
|
+
/* @__PURE__ */ jsxs(Table, { children: [
|
|
440
|
+
/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
441
|
+
/* @__PURE__ */ jsx(TableHead, { className: "w-[100px]", children: "User" }),
|
|
442
|
+
/* @__PURE__ */ jsx(TableHead, { className: "w-[200px]", children: "Name" })
|
|
443
|
+
] }) }),
|
|
444
|
+
/* @__PURE__ */ jsx(TableBody, { children: users.map(({ id, teamProfile }, i) => /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
445
|
+
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(UserAvatar, { user: teamProfile }) }),
|
|
446
|
+
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { children: teamProfile.displayName }) })
|
|
447
|
+
] }, id)) })
|
|
448
|
+
] })
|
|
449
|
+
] });
|
|
450
|
+
}
|
|
451
|
+
var teamCreationSchema = yupObject({
|
|
452
|
+
displayName: yupString().required("Please enter a team name")
|
|
453
|
+
});
|
|
454
|
+
function TeamCreation() {
|
|
455
|
+
const { register, handleSubmit, formState: { errors } } = useForm({
|
|
456
|
+
resolver: yupResolver(teamCreationSchema)
|
|
457
|
+
});
|
|
458
|
+
const app = useStackApp();
|
|
459
|
+
const project = app.useProject();
|
|
460
|
+
const user = useUser({ or: "redirect" });
|
|
461
|
+
const [loading, setLoading] = useState(false);
|
|
462
|
+
if (!project.config.clientTeamCreationEnabled) {
|
|
463
|
+
return /* @__PURE__ */ jsx(MessageCard, { title: "Team creation is not enabled" });
|
|
464
|
+
}
|
|
465
|
+
const onSubmit = async (data) => {
|
|
466
|
+
setLoading(true);
|
|
467
|
+
try {
|
|
468
|
+
const team = await user.createTeam({ displayName: data.displayName });
|
|
469
|
+
} finally {
|
|
470
|
+
setLoading(false);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
return /* @__PURE__ */ jsx("div", { className: "stack-scope flex flex-col items-stretch", children: /* @__PURE__ */ jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsx(
|
|
474
|
+
"form",
|
|
475
|
+
{
|
|
476
|
+
className: "flex flex-col items-stretch stack-scope",
|
|
477
|
+
onSubmit: (e) => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e)),
|
|
478
|
+
noValidate: true,
|
|
479
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-4", children: [
|
|
480
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
481
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "email", className: "mb-1", children: "Display name" }),
|
|
482
|
+
/* @__PURE__ */ jsx(
|
|
483
|
+
Input,
|
|
484
|
+
{
|
|
485
|
+
id: "email",
|
|
486
|
+
type: "email",
|
|
487
|
+
...register("displayName")
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
] }),
|
|
491
|
+
/* @__PURE__ */ jsx(FormWarningText, { text: errors.displayName?.message?.toString() }),
|
|
492
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-6", loading, children: "Create" })
|
|
493
|
+
] })
|
|
494
|
+
}
|
|
495
|
+
) }) });
|
|
311
496
|
}
|
|
312
497
|
export {
|
|
313
|
-
AccountSettings
|
|
498
|
+
AccountSettings,
|
|
499
|
+
TeamCreation
|
|
314
500
|
};
|
|
315
501
|
//# sourceMappingURL=account-settings.js.map
|