@stackframe/stack 2.5.18 → 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 +20 -0
- package/dist/components/credential-sign-in.js +1 -3
- package/dist/components/credential-sign-in.js.map +1 -1
- package/dist/components/elements/sidebar-layout.d.mts +20 -0
- package/dist/components/elements/sidebar-layout.d.ts +20 -0
- package/dist/components/elements/sidebar-layout.js +111 -0
- package/dist/components/elements/sidebar-layout.js.map +1 -0
- package/dist/components/elements/user-avatar.d.mts +5 -12
- package/dist/components/elements/user-avatar.d.ts +5 -12
- package/dist/components/elements/user-avatar.js.map +1 -1
- package/dist/components/selected-team-switcher.js +36 -23
- 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 +405 -220
- package/dist/components-page/account-settings.js.map +1 -1
- package/dist/components-page/stack-handler.js +3 -5
- package/dist/components-page/stack-handler.js.map +1 -1
- package/dist/components-page/team-creation.d.mts +7 -0
- package/dist/components-page/team-creation.d.ts +7 -0
- package/dist/components-page/team-creation.js +92 -0
- package/dist/components-page/team-creation.js.map +1 -0
- package/dist/esm/components/credential-sign-in.js +1 -3
- package/dist/esm/components/credential-sign-in.js.map +1 -1
- package/dist/esm/components/elements/sidebar-layout.js +87 -0
- package/dist/esm/components/elements/sidebar-layout.js.map +1 -0
- package/dist/esm/components/elements/user-avatar.js.map +1 -1
- package/dist/esm/components/selected-team-switcher.js +40 -15
- 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 +399 -215
- package/dist/esm/components-page/account-settings.js.map +1 -1
- package/dist/esm/components-page/stack-handler.js +3 -5
- package/dist/esm/components-page/stack-handler.js.map +1 -1
- package/dist/esm/components-page/team-creation.js +68 -0
- package/dist/esm/components-page/team-creation.js.map +1 -0
- package/dist/esm/generated/global-css.js +1 -1
- package/dist/esm/generated/global-css.js.map +1 -1
- package/dist/esm/lib/auth.js +4 -0
- package/dist/esm/lib/auth.js.map +1 -1
- package/dist/esm/lib/stack-app.js +184 -34
- 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/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/lib/auth.js +4 -0
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/stack-app.d.mts +39 -5
- package/dist/lib/stack-app.d.ts +39 -5
- package/dist/lib/stack-app.js +184 -34
- package/dist/lib/stack-app.js.map +1 -1
- package/package.json +7 -6
|
@@ -2,209 +2,230 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
// src/components-page/account-settings.tsx
|
|
5
|
-
import {
|
|
6
|
-
import { useStackApp, useUser } from "..";
|
|
7
|
-
import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card";
|
|
8
|
-
import { UserAvatar } from "../components/elements/user-avatar";
|
|
9
|
-
import { useState } from "react";
|
|
10
|
-
import { FormWarningText } from "../components/elements/form-warning";
|
|
5
|
+
import { yupResolver } from "@hookform/resolvers/yup";
|
|
11
6
|
import { getPasswordError } from "@stackframe/stack-shared/dist/helpers/password";
|
|
12
|
-
import {
|
|
7
|
+
import { useAsyncCallback } from "@stackframe/stack-shared/dist/hooks/use-async-callback";
|
|
8
|
+
import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
|
|
13
9
|
import { generateRandomValues } from "@stackframe/stack-shared/dist/utils/crypto";
|
|
10
|
+
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
|
|
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";
|
|
14
14
|
import { TOTPController, createTOTPKeyURI } from "oslo/otp";
|
|
15
15
|
import * as QRCode from "qrcode";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
onClick: props.onButtonClick,
|
|
32
|
-
variant: props.buttonVariant,
|
|
33
|
-
children: props.buttonText
|
|
34
|
-
}
|
|
35
|
-
) }) })
|
|
36
|
-
] });
|
|
37
|
-
}
|
|
38
|
-
function ProfileSection() {
|
|
39
|
-
const user = useUser();
|
|
40
|
-
const [userInfo, setUserInfo] = useState({ displayName: user.displayName || "" });
|
|
41
|
-
const [changed, setChanged] = useState(false);
|
|
42
|
-
return /* @__PURE__ */ jsxs(
|
|
43
|
-
SettingSection,
|
|
16
|
+
import { useEffect, useState } from "react";
|
|
17
|
+
import { useForm } from "react-hook-form";
|
|
18
|
+
import * as yup from "yup";
|
|
19
|
+
import { MessageCard, useStackApp, useUser } from "..";
|
|
20
|
+
import { FormWarningText } from "../components/elements/form-warning";
|
|
21
|
+
import { SidebarLayout } from "../components/elements/sidebar-layout";
|
|
22
|
+
import { UserAvatar } from "../components/elements/user-avatar";
|
|
23
|
+
import { TeamIcon } from "../components/team-icon";
|
|
24
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
25
|
+
function AccountSettings({ fullPage = false }) {
|
|
26
|
+
const user = useUser({ or: "redirect" });
|
|
27
|
+
const teams = user.useTeams();
|
|
28
|
+
const project = useStackApp().useProject();
|
|
29
|
+
const inner = /* @__PURE__ */ jsx(
|
|
30
|
+
SidebarLayout,
|
|
44
31
|
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
32
|
+
items: [
|
|
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, {})
|
|
59
49
|
] })
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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"
|
|
76
87
|
}
|
|
77
88
|
);
|
|
89
|
+
if (fullPage) {
|
|
90
|
+
return /* @__PURE__ */ jsx(Container, { size: 1e3, className: "stack-scope", children: inner });
|
|
91
|
+
} else {
|
|
92
|
+
return inner;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function ProfileSection() {
|
|
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
|
+
] }) });
|
|
78
103
|
}
|
|
79
104
|
function EmailVerificationSection() {
|
|
80
|
-
const user = useUser();
|
|
105
|
+
const user = useUser({ or: "redirect" });
|
|
81
106
|
const [emailSent, setEmailSent] = useState(false);
|
|
82
|
-
return /* @__PURE__ */ jsx(
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
] }) });
|
|
96
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
|
+
});
|
|
97
138
|
function PasswordSection() {
|
|
98
139
|
const user = useUser({ or: "throw" });
|
|
99
|
-
const [
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const [
|
|
104
|
-
const [
|
|
105
|
-
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");
|
|
106
163
|
if (!user.hasPassword) {
|
|
107
164
|
return null;
|
|
108
165
|
}
|
|
109
|
-
return /* @__PURE__ */ jsxs(
|
|
110
|
-
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
setRepeatNewPasswordError("Please repeat your new password");
|
|
128
|
-
return;
|
|
129
|
-
} else {
|
|
130
|
-
const errorMessage = getPasswordError(newPassword);
|
|
131
|
-
if (errorMessage) {
|
|
132
|
-
setNewPasswordError(errorMessage.message);
|
|
133
|
-
} else {
|
|
134
|
-
if (newPassword !== repeatNewPassword) {
|
|
135
|
-
setRepeatNewPasswordError("Passwords do not match");
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const errorCode = await user.updatePassword({ oldPassword, newPassword });
|
|
139
|
-
if (errorCode) {
|
|
140
|
-
setOldPasswordError("Incorrect password");
|
|
141
|
-
} else {
|
|
142
|
-
setOldPassword("");
|
|
143
|
-
setNewPassword("");
|
|
144
|
-
setRepeatNewPassword("");
|
|
145
|
-
setPasswordChanged(true);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
children: [
|
|
151
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
152
|
-
/* @__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" }),
|
|
153
184
|
/* @__PURE__ */ jsx(
|
|
154
|
-
|
|
185
|
+
Input,
|
|
155
186
|
{
|
|
156
187
|
id: "old-password",
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
setOldPassword(e.target.value);
|
|
160
|
-
setOldPasswordError("");
|
|
161
|
-
setPasswordChanged(false);
|
|
162
|
-
}
|
|
188
|
+
type: "password",
|
|
189
|
+
...register("oldPassword")
|
|
163
190
|
}
|
|
164
191
|
),
|
|
165
|
-
/* @__PURE__ */ jsx(FormWarningText, { text:
|
|
166
|
-
|
|
167
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
168
|
-
/* @__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" }),
|
|
169
194
|
/* @__PURE__ */ jsx(
|
|
170
195
|
PasswordInput,
|
|
171
196
|
{
|
|
172
197
|
id: "new-password",
|
|
173
|
-
|
|
198
|
+
...registerPassword,
|
|
174
199
|
onChange: (e) => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
200
|
+
clearErrors("newPassword");
|
|
201
|
+
clearErrors("newPasswordRepeat");
|
|
202
|
+
runAsynchronously(registerPassword.onChange(e));
|
|
178
203
|
}
|
|
179
204
|
}
|
|
180
205
|
),
|
|
181
|
-
/* @__PURE__ */ jsx(FormWarningText, { text:
|
|
182
|
-
|
|
183
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
184
|
-
/* @__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" }),
|
|
185
208
|
/* @__PURE__ */ jsx(
|
|
186
209
|
PasswordInput,
|
|
187
210
|
{
|
|
188
|
-
id: "repeat-
|
|
189
|
-
|
|
211
|
+
id: "repeat-password",
|
|
212
|
+
...registerPasswordRepeat,
|
|
190
213
|
onChange: (e) => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
214
|
+
clearErrors("newPassword");
|
|
215
|
+
clearErrors("newPasswordRepeat");
|
|
216
|
+
runAsynchronously(registerPasswordRepeat.onChange(e));
|
|
194
217
|
}
|
|
195
218
|
}
|
|
196
219
|
),
|
|
197
|
-
/* @__PURE__ */ jsx(FormWarningText, { text:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
+
] });
|
|
202
226
|
}
|
|
203
227
|
function MfaSection() {
|
|
204
228
|
const project = useStackApp().useProject();
|
|
205
|
-
if (project.config.oauthProviders.length !== 0 || project.config.magicLinkEnabled) {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
229
|
const user = useUser({ or: "throw" });
|
|
209
230
|
const [generatedSecret, setGeneratedSecret] = useState(null);
|
|
210
231
|
const [qrCodeUrl, setQrCodeUrl] = useState(null);
|
|
@@ -228,29 +249,10 @@ function MfaSection() {
|
|
|
228
249
|
setIsMaybeWrong(true);
|
|
229
250
|
});
|
|
230
251
|
}, [mfaCode, generatedSecret, handleSubmit]);
|
|
231
|
-
return /* @__PURE__ */ jsx(
|
|
232
|
-
|
|
233
|
-
{
|
|
234
|
-
|
|
235
|
-
desc: "Secure your account with an additional layer of security.",
|
|
236
|
-
buttonVariant: "secondary",
|
|
237
|
-
buttonText: isEnabled ? "Disable" : generatedSecret ? "Cancel" : "Enable",
|
|
238
|
-
onButtonClick: async () => {
|
|
239
|
-
if (isEnabled) {
|
|
240
|
-
await user.update({
|
|
241
|
-
totpMultiFactorSecret: null
|
|
242
|
-
});
|
|
243
|
-
} else if (!generatedSecret) {
|
|
244
|
-
const secret = generateRandomValues(new Uint8Array(20));
|
|
245
|
-
setQrCodeUrl(await generateTotpQrCode(project, user, secret));
|
|
246
|
-
setGeneratedSecret(secret);
|
|
247
|
-
} else {
|
|
248
|
-
setGeneratedSecret(null);
|
|
249
|
-
setQrCodeUrl(null);
|
|
250
|
-
setMfaCode("");
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
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: [
|
|
254
256
|
/* @__PURE__ */ jsx(Typography, { children: "Scan this QR code with your authenticator app:" }),
|
|
255
257
|
/* @__PURE__ */ jsx("img", { width: 200, height: 200, src: qrCodeUrl ?? throwErr("TOTP QR code failed to generate"), alt: "TOTP multi-factor authentication QR code" }),
|
|
256
258
|
/* @__PURE__ */ jsx(Typography, { children: "Then, enter your six-digit MFA code:" }),
|
|
@@ -268,9 +270,32 @@ function MfaSection() {
|
|
|
268
270
|
}
|
|
269
271
|
),
|
|
270
272
|
isMaybeWrong && mfaCode.length === 6 && /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: "Incorrect code. Please try again." })
|
|
271
|
-
] }) : /* @__PURE__ */ jsx(Typography, { variant: "destructive", children: "Multi-factor authentication is currently disabled." })
|
|
272
|
-
|
|
273
|
-
|
|
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
|
+
] }) });
|
|
274
299
|
}
|
|
275
300
|
async function generateTotpQrCode(project, user, secret) {
|
|
276
301
|
const uri = createTOTPKeyURI(project.displayName, user.primaryEmail ?? user.id, secret);
|
|
@@ -278,40 +303,199 @@ async function generateTotpQrCode(project, user, secret) {
|
|
|
278
303
|
}
|
|
279
304
|
function SignOutSection() {
|
|
280
305
|
const user = useUser({ or: "throw" });
|
|
281
|
-
return /* @__PURE__ */ jsx(
|
|
282
|
-
|
|
306
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
307
|
+
Button,
|
|
283
308
|
{
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
buttonText: "Sign Out",
|
|
288
|
-
onButtonClick: () => user.signOut()
|
|
309
|
+
variant: "secondary",
|
|
310
|
+
onClick: () => user.signOut(),
|
|
311
|
+
children: "Sign Out"
|
|
289
312
|
}
|
|
290
|
-
);
|
|
313
|
+
) }) });
|
|
291
314
|
}
|
|
292
|
-
function
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
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;
|
|
296
342
|
}
|
|
297
|
-
|
|
298
|
-
/* @__PURE__ */
|
|
299
|
-
|
|
300
|
-
|
|
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" })
|
|
301
361
|
] }),
|
|
302
|
-
/* @__PURE__ */ jsx(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
+
)
|
|
307
424
|
] });
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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;
|
|
312
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
|
+
) }) });
|
|
313
496
|
}
|
|
314
497
|
export {
|
|
315
|
-
AccountSettings
|
|
498
|
+
AccountSettings,
|
|
499
|
+
TeamCreation
|
|
316
500
|
};
|
|
317
501
|
//# sourceMappingURL=account-settings.js.map
|