@stackframe/stack 2.5.35 → 2.5.37

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