@stackframe/stack 2.5.35 → 2.6.0

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