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