@stackframe/stack 2.8.2 → 2.8.3

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 (55) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/components/api-key-dialogs.js +184 -0
  3. package/dist/components/api-key-dialogs.js.map +1 -0
  4. package/dist/components/api-key-table.js +149 -0
  5. package/dist/components/api-key-table.js.map +1 -0
  6. package/dist/components-page/account-settings.js +134 -15
  7. package/dist/components-page/account-settings.js.map +1 -1
  8. package/dist/esm/components/api-key-dialogs.js +157 -0
  9. package/dist/esm/components/api-key-dialogs.js.map +1 -0
  10. package/dist/esm/components/api-key-table.js +125 -0
  11. package/dist/esm/components/api-key-table.js.map +1 -0
  12. package/dist/esm/components-page/account-settings.js +132 -15
  13. package/dist/esm/components-page/account-settings.js.map +1 -1
  14. package/dist/esm/lib/stack-app/api-keys/index.js +14 -6
  15. package/dist/esm/lib/stack-app/api-keys/index.js.map +1 -1
  16. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js +26 -23
  17. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
  18. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +90 -0
  19. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  20. package/dist/esm/lib/stack-app/apps/implementations/common.js +1 -1
  21. package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
  22. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js +148 -8
  23. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
  24. package/dist/esm/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
  25. package/dist/esm/lib/stack-app/apps/interfaces/server-app.js.map +1 -1
  26. package/dist/esm/lib/stack-app/index.js.map +1 -1
  27. package/dist/esm/lib/stack-app/internal-api-keys/index.js +14 -0
  28. package/dist/esm/lib/stack-app/internal-api-keys/index.js.map +1 -0
  29. package/dist/esm/lib/stack-app/projects/index.js +3 -1
  30. package/dist/esm/lib/stack-app/projects/index.js.map +1 -1
  31. package/dist/esm/lib/stack-app/teams/index.js.map +1 -1
  32. package/dist/esm/lib/stack-app/users/index.js.map +1 -1
  33. package/dist/index.d.mts +99 -35
  34. package/dist/index.d.ts +99 -35
  35. package/dist/lib/stack-app/api-keys/index.js +16 -7
  36. package/dist/lib/stack-app/api-keys/index.js.map +1 -1
  37. package/dist/lib/stack-app/apps/implementations/admin-app-impl.js +25 -22
  38. package/dist/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
  39. package/dist/lib/stack-app/apps/implementations/client-app-impl.js +90 -0
  40. package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  41. package/dist/lib/stack-app/apps/implementations/common.js +1 -1
  42. package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
  43. package/dist/lib/stack-app/apps/implementations/server-app-impl.js +148 -8
  44. package/dist/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
  45. package/dist/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
  46. package/dist/lib/stack-app/apps/interfaces/server-app.js.map +1 -1
  47. package/dist/lib/stack-app/index.js.map +1 -1
  48. package/dist/lib/stack-app/internal-api-keys/index.js +39 -0
  49. package/dist/lib/stack-app/internal-api-keys/index.js.map +1 -0
  50. package/dist/lib/stack-app/project-configs/index.js.map +1 -1
  51. package/dist/lib/stack-app/projects/index.js +3 -1
  52. package/dist/lib/stack-app/projects/index.js.map +1 -1
  53. package/dist/lib/stack-app/teams/index.js.map +1 -1
  54. package/dist/lib/stack-app/users/index.js.map +1 -1
  55. package/package.json +5 -4
@@ -0,0 +1,125 @@
1
+ "use client";
2
+ "use client";
3
+
4
+ // src/components/api-key-table.tsx
5
+ import { ActionCell, ActionDialog, BadgeCell, DataTable, DataTableColumnHeader, DataTableFacetedFilter, DateCell, SearchToolbarItem, TextCell, standardFilterFn } from "@stackframe/stack-ui";
6
+ import { useMemo, useState } from "react";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ function toolbarRender(table) {
9
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
10
+ /* @__PURE__ */ jsx(SearchToolbarItem, { table, placeholder: "Search table" }),
11
+ /* @__PURE__ */ jsx(
12
+ DataTableFacetedFilter,
13
+ {
14
+ column: table.getColumn("status"),
15
+ title: "Status",
16
+ options: ["valid", "expired", "revoked"].map((provider) => ({
17
+ value: provider,
18
+ label: provider
19
+ }))
20
+ }
21
+ )
22
+ ] });
23
+ }
24
+ function RevokeDialog(props) {
25
+ return /* @__PURE__ */ jsx(
26
+ ActionDialog,
27
+ {
28
+ open: props.open,
29
+ onOpenChange: props.onOpenChange,
30
+ title: "Revoke API Key",
31
+ danger: true,
32
+ cancelButton: true,
33
+ okButton: { label: "Revoke Key", onClick: async () => {
34
+ await props.apiKey.revoke();
35
+ } },
36
+ confirmText: "I understand this will unlink all the apps using this API key",
37
+ children: `Are you sure you want to revoke API key *****${props.apiKey.value.lastFour}?`
38
+ }
39
+ );
40
+ }
41
+ function Actions({ row }) {
42
+ const [isRevokeModalOpen, setIsRevokeModalOpen] = useState(false);
43
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
44
+ /* @__PURE__ */ jsx(RevokeDialog, { apiKey: row.original, open: isRevokeModalOpen, onOpenChange: setIsRevokeModalOpen }),
45
+ /* @__PURE__ */ jsx(
46
+ ActionCell,
47
+ {
48
+ invisible: row.original.status !== "valid",
49
+ items: [{
50
+ item: "Revoke",
51
+ danger: true,
52
+ onClick: () => setIsRevokeModalOpen(true)
53
+ }]
54
+ }
55
+ )
56
+ ] });
57
+ }
58
+ var columns = [
59
+ {
60
+ accessorKey: "description",
61
+ header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, columnTitle: "Description" }),
62
+ cell: ({ row }) => /* @__PURE__ */ jsx(TextCell, { size: 100, children: row.original.description })
63
+ },
64
+ {
65
+ accessorKey: "status",
66
+ header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, columnTitle: "Status" }),
67
+ cell: ({ row }) => /* @__PURE__ */ jsx(BadgeCell, { badges: [row.original.status] }),
68
+ filterFn: standardFilterFn
69
+ },
70
+ {
71
+ id: "value",
72
+ accessorFn: (row) => row.value.lastFour,
73
+ header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, columnTitle: "Client Key" }),
74
+ cell: ({ row }) => /* @__PURE__ */ jsxs(TextCell, { children: [
75
+ "*******",
76
+ row.original.value.lastFour
77
+ ] }),
78
+ enableSorting: false
79
+ },
80
+ {
81
+ accessorKey: "expiresAt",
82
+ header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, columnTitle: "Expires At" }),
83
+ cell: ({ row }) => {
84
+ if (row.original.status === "revoked") return /* @__PURE__ */ jsx(TextCell, { children: "-" });
85
+ return row.original.expiresAt ? /* @__PURE__ */ jsx(DateCell, { date: row.original.expiresAt, ignoreAfterYears: 50 }) : /* @__PURE__ */ jsx(TextCell, { children: "Never" });
86
+ }
87
+ },
88
+ {
89
+ accessorKey: "createdAt",
90
+ header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, columnTitle: "Created At" }),
91
+ cell: ({ row }) => /* @__PURE__ */ jsx(DateCell, { date: row.original.createdAt, ignoreAfterYears: 50 })
92
+ },
93
+ {
94
+ id: "actions",
95
+ cell: ({ row }) => /* @__PURE__ */ jsx(Actions, { row })
96
+ }
97
+ ];
98
+ function ApiKeyTable(props) {
99
+ const extendedApiKeys = useMemo(() => {
100
+ const keys = props.apiKeys.map((apiKey) => ({
101
+ ...apiKey,
102
+ status: { "valid": "valid", "manually-revoked": "revoked", "expired": "expired" }[apiKey.whyInvalid() || "valid"]
103
+ }));
104
+ return keys.sort((a, b) => {
105
+ if (a.status === b.status) {
106
+ return a.createdAt < b.createdAt ? 1 : -1;
107
+ }
108
+ return a.status === "valid" ? -1 : 1;
109
+ });
110
+ }, [props.apiKeys]);
111
+ return /* @__PURE__ */ jsx(
112
+ DataTable,
113
+ {
114
+ data: extendedApiKeys,
115
+ columns,
116
+ toolbarRender,
117
+ defaultColumnFilters: [],
118
+ defaultSorting: []
119
+ }
120
+ );
121
+ }
122
+ export {
123
+ ApiKeyTable
124
+ };
125
+ //# sourceMappingURL=api-key-table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/api-key-table.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY\n//===========================================\nimport { ActionCell, ActionDialog, BadgeCell, DataTable, DataTableColumnHeader, DataTableFacetedFilter, DateCell, SearchToolbarItem, TextCell, standardFilterFn } from \"@stackframe/stack-ui\";\nimport { ColumnDef, Row, Table } from \"@tanstack/react-table\";\nimport { useMemo, useState } from \"react\";\nimport { ApiKey } from \"../lib/stack-app/api-keys\";\n\ntype ExtendedApiKey = ApiKey & {\n status: 'valid' | 'expired' | 'revoked',\n};\n\nfunction toolbarRender<TData>(table: Table<TData>) {\n return (\n <>\n <SearchToolbarItem table={table} placeholder=\"Search table\" />\n <DataTableFacetedFilter\n column={table.getColumn(\"status\")}\n title=\"Status\"\n options={['valid', 'expired', 'revoked'].map((provider) => ({\n value: provider,\n label: provider,\n }))}\n />\n </>\n );\n}\n\nfunction RevokeDialog(props: {\n apiKey: ExtendedApiKey,\n open: boolean,\n onOpenChange: (open: boolean) => void,\n}) {\n return <ActionDialog\n open={props.open}\n onOpenChange={props.onOpenChange}\n title=\"Revoke API Key\"\n danger\n cancelButton\n okButton={{ label: \"Revoke Key\", onClick: async () => { await props.apiKey.revoke(); } }}\n confirmText=\"I understand this will unlink all the apps using this API key\"\n >\n {`Are you sure you want to revoke API key *****${props.apiKey.value.lastFour}?`}\n </ActionDialog>;\n}\n\nfunction Actions({ row }: { row: Row<ExtendedApiKey> }) {\n const [isRevokeModalOpen, setIsRevokeModalOpen] = useState(false);\n return (\n <>\n <RevokeDialog apiKey={row.original} open={isRevokeModalOpen} onOpenChange={setIsRevokeModalOpen} />\n <ActionCell\n invisible={row.original.status !== 'valid'}\n items={[{\n item: \"Revoke\",\n danger: true,\n onClick: () => setIsRevokeModalOpen(true),\n }]}\n />\n </>\n );\n}\n\nconst columns: ColumnDef<ExtendedApiKey>[] = [\n {\n accessorKey: \"description\",\n header: ({ column }) => <DataTableColumnHeader column={column} columnTitle=\"Description\" />,\n cell: ({ row }) => <TextCell size={100}>{row.original.description}</TextCell>,\n },\n {\n accessorKey: \"status\",\n header: ({ column }) => <DataTableColumnHeader column={column} columnTitle=\"Status\" />,\n cell: ({ row }) => <BadgeCell badges={[row.original.status]} />,\n filterFn: standardFilterFn,\n },\n {\n id: \"value\",\n accessorFn: (row) => row.value.lastFour,\n header: ({ column }) => <DataTableColumnHeader column={column} columnTitle=\"Client Key\" />,\n cell: ({ row }) => <TextCell>*******{row.original.value.lastFour}</TextCell>,\n enableSorting: false,\n },\n {\n accessorKey: \"expiresAt\",\n header: ({ column }) => <DataTableColumnHeader column={column} columnTitle=\"Expires At\" />,\n cell: ({ row }) => {\n if (row.original.status === 'revoked') return <TextCell>-</TextCell>;\n return row.original.expiresAt ? <DateCell date={row.original.expiresAt} ignoreAfterYears={50} /> : <TextCell>Never</TextCell>;\n },\n },\n {\n accessorKey: \"createdAt\",\n header: ({ column }) => <DataTableColumnHeader column={column} columnTitle=\"Created At\" />,\n cell: ({ row }) => <DateCell date={row.original.createdAt} ignoreAfterYears={50} />\n },\n {\n id: \"actions\",\n cell: ({ row }) => <Actions row={row} />,\n },\n];\n\nexport function ApiKeyTable(props: { apiKeys: ApiKey[] }) {\n const extendedApiKeys = useMemo(() => {\n const keys = props.apiKeys.map((apiKey) => ({\n ...apiKey,\n status: ({ 'valid': 'valid', 'manually-revoked': 'revoked', 'expired': 'expired' } as const)[apiKey.whyInvalid() || 'valid'],\n } satisfies ExtendedApiKey));\n // first sort based on status, then by createdAt\n return keys.sort((a, b) => {\n if (a.status === b.status) {\n return a.createdAt < b.createdAt ? 1 : -1;\n }\n return a.status === 'valid' ? -1 : 1;\n });\n }, [props.apiKeys]);\n\n return <DataTable\n data={extendedApiKeys}\n columns={columns}\n toolbarRender={toolbarRender}\n defaultColumnFilters={[]}\n defaultSorting={[]}\n />;\n}\n"],"mappings":";;;AAMA,SAAS,YAAY,cAAc,WAAW,WAAW,uBAAuB,wBAAwB,UAAU,mBAAmB,UAAU,wBAAwB;AAEvK,SAAS,SAAS,gBAAgB;AAS9B,mBACE,KADF;AAFJ,SAAS,cAAqB,OAAqB;AACjD,SACE,iCACE;AAAA,wBAAC,qBAAkB,OAAc,aAAY,gBAAe;AAAA,IAC5D;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,MAAM,UAAU,QAAQ;AAAA,QAChC,OAAM;AAAA,QACN,SAAS,CAAC,SAAS,WAAW,SAAS,EAAE,IAAI,CAAC,cAAc;AAAA,UAC1D,OAAO;AAAA,UACP,OAAO;AAAA,QACT,EAAE;AAAA;AAAA,IACJ;AAAA,KACF;AAEJ;AAEA,SAAS,aAAa,OAInB;AACD,SAAO;AAAA,IAAC;AAAA;AAAA,MACN,MAAM,MAAM;AAAA,MACZ,cAAc,MAAM;AAAA,MACpB,OAAM;AAAA,MACN,QAAM;AAAA,MACN,cAAY;AAAA,MACZ,UAAU,EAAE,OAAO,cAAc,SAAS,YAAY;AAAE,cAAM,MAAM,OAAO,OAAO;AAAA,MAAG,EAAE;AAAA,MACvF,aAAY;AAAA,MAEX,0DAAgD,MAAM,OAAO,MAAM,QAAQ;AAAA;AAAA,EAC9E;AACF;AAEA,SAAS,QAAQ,EAAE,IAAI,GAAiC;AACtD,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAS,KAAK;AAChE,SACE,iCACE;AAAA,wBAAC,gBAAa,QAAQ,IAAI,UAAU,MAAM,mBAAmB,cAAc,sBAAsB;AAAA,IACjG;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,IAAI,SAAS,WAAW;AAAA,QACnC,OAAO,CAAC;AAAA,UACN,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,SAAS,MAAM,qBAAqB,IAAI;AAAA,QAC1C,CAAC;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEA,IAAM,UAAwC;AAAA,EAC5C;AAAA,IACE,aAAa;AAAA,IACb,QAAQ,CAAC,EAAE,OAAO,MAAM,oBAAC,yBAAsB,QAAgB,aAAY,eAAc;AAAA,IACzF,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,YAAS,MAAM,KAAM,cAAI,SAAS,aAAY;AAAA,EACpE;AAAA,EACA;AAAA,IACE,aAAa;AAAA,IACb,QAAQ,CAAC,EAAE,OAAO,MAAM,oBAAC,yBAAsB,QAAgB,aAAY,UAAS;AAAA,IACpF,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,aAAU,QAAQ,CAAC,IAAI,SAAS,MAAM,GAAG;AAAA,IAC7D,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,YAAY,CAAC,QAAQ,IAAI,MAAM;AAAA,IAC/B,QAAQ,CAAC,EAAE,OAAO,MAAM,oBAAC,yBAAsB,QAAgB,aAAY,cAAa;AAAA,IACxF,MAAM,CAAC,EAAE,IAAI,MAAM,qBAAC,YAAS;AAAA;AAAA,MAAQ,IAAI,SAAS,MAAM;AAAA,OAAS;AAAA,IACjE,eAAe;AAAA,EACjB;AAAA,EACA;AAAA,IACE,aAAa;AAAA,IACb,QAAQ,CAAC,EAAE,OAAO,MAAM,oBAAC,yBAAsB,QAAgB,aAAY,cAAa;AAAA,IACxF,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,UAAI,IAAI,SAAS,WAAW,UAAW,QAAO,oBAAC,YAAS,eAAC;AACzD,aAAO,IAAI,SAAS,YAAY,oBAAC,YAAS,MAAM,IAAI,SAAS,WAAW,kBAAkB,IAAI,IAAK,oBAAC,YAAS,mBAAK;AAAA,IACpH;AAAA,EACF;AAAA,EACA;AAAA,IACE,aAAa;AAAA,IACb,QAAQ,CAAC,EAAE,OAAO,MAAM,oBAAC,yBAAsB,QAAgB,aAAY,cAAa;AAAA,IACxF,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,YAAS,MAAM,IAAI,SAAS,WAAW,kBAAkB,IAAI;AAAA,EACnF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,WAAQ,KAAU;AAAA,EACxC;AACF;AAEO,SAAS,YAAY,OAA8B;AACxD,QAAM,kBAAkB,QAAQ,MAAM;AACpC,UAAM,OAAO,MAAM,QAAQ,IAAI,CAAC,YAAY;AAAA,MAC1C,GAAG;AAAA,MACH,QAAS,EAAE,SAAS,SAAS,oBAAoB,WAAW,WAAW,UAAU,EAAY,OAAO,WAAW,KAAK,OAAO;AAAA,IAC7H,EAA2B;AAE3B,WAAO,KAAK,KAAK,CAAC,GAAG,MAAM;AACzB,UAAI,EAAE,WAAW,EAAE,QAAQ;AACzB,eAAO,EAAE,YAAY,EAAE,YAAY,IAAI;AAAA,MACzC;AACA,aAAO,EAAE,WAAW,UAAU,KAAK;AAAA,IACrC,CAAC;AAAA,EACH,GAAG,CAAC,MAAM,OAAO,CAAC;AAElB,SAAO;AAAA,IAAC;AAAA;AAAA,MACN,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,sBAAsB,CAAC;AAAA,MACvB,gBAAgB,CAAC;AAAA;AAAA,EACnB;AACF;","names":[]}
@@ -10,7 +10,7 @@ import { useAsyncCallback } from "@stackframe/stack-shared/dist/hooks/use-async-
10
10
  import { passwordSchema as schemaFieldsPasswordSchema, strictEmailSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
11
11
  import { generateRandomValues } from "@stackframe/stack-shared/dist/utils/crypto";
12
12
  import { fromNow } from "@stackframe/stack-shared/dist/utils/dates";
13
- import { captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
13
+ import { StackAssertionError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
14
14
  import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
15
15
  import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActionCell, Badge, Button, Input, Label, PasswordInput, Separator, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
16
16
  import { Edit, Trash, icons } from "lucide-react";
@@ -19,6 +19,8 @@ import { Suspense, useEffect, useState } from "react";
19
19
  import { useForm } from "react-hook-form";
20
20
  import * as yup from "yup";
21
21
  import { MessageCard, useStackApp, useUser } from "..";
22
+ import { CreateApiKeyDialog, ShowApiKeyDialog } from "../components/api-key-dialogs";
23
+ import { ApiKeyTable } from "../components/api-key-table";
22
24
  import { FormWarningText } from "../components/elements/form-warning";
23
25
  import { MaybeFullPage } from "../components/elements/maybe-full-page";
24
26
  import { SidebarLayout } from "../components/elements/sidebar-layout";
@@ -62,6 +64,13 @@ function AccountSettings(props) {
62
64
  icon: /* @__PURE__ */ jsx(Icon, { name: "Monitor" }),
63
65
  content: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ActiveSessionsPageSkeleton, {}), children: /* @__PURE__ */ jsx(ActiveSessionsPage, {}) })
64
66
  },
67
+ ...project.config.allowUserApiKeys ? [{
68
+ title: t("API Keys"),
69
+ type: "item",
70
+ id: "api-keys",
71
+ icon: /* @__PURE__ */ jsx(Icon, { name: "Key" }),
72
+ content: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ApiKeysPageSkeleton, {}), children: /* @__PURE__ */ jsx(ApiKeysPage, {}) })
73
+ }] : [],
65
74
  {
66
75
  title: t("Settings"),
67
76
  type: "item",
@@ -95,14 +104,14 @@ function AccountSettings(props) {
95
104
  ] }),
96
105
  type: "item",
97
106
  id: `team-${team.id}`,
98
- content: /* @__PURE__ */ jsx(TeamPage, { team })
107
+ content: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(TeamPageSkeleton, {}), children: /* @__PURE__ */ jsx(TeamPage, { team }) })
99
108
  })),
100
109
  ...project.config.clientTeamCreationEnabled ? [{
101
110
  title: t("Create a team"),
102
111
  icon: /* @__PURE__ */ jsx(Icon, { name: "CirclePlus" }),
103
112
  type: "item",
104
113
  id: "team-creation",
105
- content: /* @__PURE__ */ jsx(TeamCreation, {})
114
+ content: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(TeamCreationSkeleton, {}), children: /* @__PURE__ */ jsx(TeamCreation, {}) })
106
115
  }] : []
107
116
  ].filter((p) => p.type === "divider" || p.content),
108
117
  title: t("Account Settings")
@@ -124,6 +133,88 @@ function Section(props) {
124
133
  function PageLayout(props) {
125
134
  return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-6", children: props.children });
126
135
  }
136
+ function ApiKeysPage() {
137
+ const { t } = useTranslation();
138
+ const user = useUser({ or: "redirect" });
139
+ const apiKeys = user.useApiKeys();
140
+ const [isNewApiKeyDialogOpen, setIsNewApiKeyDialogOpen] = useState(false);
141
+ const [returnedApiKey, setReturnedApiKey] = useState(null);
142
+ const CreateDialog = CreateApiKeyDialog;
143
+ const ShowDialog = ShowApiKeyDialog;
144
+ return /* @__PURE__ */ jsxs(PageLayout, { children: [
145
+ /* @__PURE__ */ jsx(Button, { onClick: () => setIsNewApiKeyDialogOpen(true), children: t("Create API Key") }),
146
+ /* @__PURE__ */ jsx(ApiKeyTable, { apiKeys }),
147
+ /* @__PURE__ */ jsx(
148
+ CreateDialog,
149
+ {
150
+ open: isNewApiKeyDialogOpen,
151
+ onOpenChange: setIsNewApiKeyDialogOpen,
152
+ onKeyCreated: setReturnedApiKey,
153
+ createApiKey: async (data) => {
154
+ const apiKey = await user.createApiKey(data);
155
+ return apiKey;
156
+ }
157
+ }
158
+ ),
159
+ /* @__PURE__ */ jsx(
160
+ ShowDialog,
161
+ {
162
+ apiKey: returnedApiKey,
163
+ onClose: () => setReturnedApiKey(null)
164
+ }
165
+ )
166
+ ] });
167
+ }
168
+ function TeamApiKeysSection(props) {
169
+ const user = useUser({ or: "redirect" });
170
+ const team = user.useTeam(props.team.id);
171
+ if (!team) {
172
+ throw new StackAssertionError("Team not found");
173
+ }
174
+ const manageApiKeysPermission = user.usePermission(props.team, "$manage_api_keys");
175
+ if (!manageApiKeysPermission) {
176
+ return null;
177
+ }
178
+ return /* @__PURE__ */ jsx(TeamApiKeysSectionInner, { team: props.team });
179
+ }
180
+ function TeamApiKeysSectionInner(props) {
181
+ const { t } = useTranslation();
182
+ const [isNewApiKeyDialogOpen, setIsNewApiKeyDialogOpen] = useState(false);
183
+ const [returnedApiKey, setReturnedApiKey] = useState(null);
184
+ const apiKeys = props.team.useApiKeys();
185
+ const CreateDialog = CreateApiKeyDialog;
186
+ const ShowDialog = ShowApiKeyDialog;
187
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
188
+ /* @__PURE__ */ jsx(
189
+ Section,
190
+ {
191
+ title: t("API Keys"),
192
+ description: t("API keys grant programmatic access to your team."),
193
+ children: /* @__PURE__ */ jsx(Button, { onClick: () => setIsNewApiKeyDialogOpen(true), children: t("Create API Key") })
194
+ }
195
+ ),
196
+ /* @__PURE__ */ jsx(ApiKeyTable, { apiKeys }),
197
+ /* @__PURE__ */ jsx(
198
+ CreateDialog,
199
+ {
200
+ open: isNewApiKeyDialogOpen,
201
+ onOpenChange: setIsNewApiKeyDialogOpen,
202
+ onKeyCreated: setReturnedApiKey,
203
+ createApiKey: async (data) => {
204
+ const apiKey = await props.team.createApiKey(data);
205
+ return apiKey;
206
+ }
207
+ }
208
+ ),
209
+ /* @__PURE__ */ jsx(
210
+ ShowDialog,
211
+ {
212
+ apiKey: returnedApiKey,
213
+ onClose: () => setReturnedApiKey(null)
214
+ }
215
+ )
216
+ ] });
217
+ }
127
218
  function ProfilePage() {
128
219
  const { t } = useTranslation();
129
220
  const user = useUser({ or: "redirect" });
@@ -801,12 +892,13 @@ function SignOutSection() {
801
892
  }
802
893
  function TeamPage(props) {
803
894
  return /* @__PURE__ */ jsxs(PageLayout, { children: [
804
- /* @__PURE__ */ jsx(TeamUserProfileSection, { team: props.team }),
805
- /* @__PURE__ */ jsx(MemberListSection, { team: props.team }),
806
- /* @__PURE__ */ jsx(MemberInvitationSection, { team: props.team }),
807
- /* @__PURE__ */ jsx(TeamProfileImageSection, { team: props.team }),
808
- /* @__PURE__ */ jsx(TeamDisplayNameSection, { team: props.team }),
809
- /* @__PURE__ */ jsx(LeaveTeamSection, { team: props.team })
895
+ /* @__PURE__ */ jsx(TeamUserProfileSection, { team: props.team }, `user-profile-${props.team.id}`),
896
+ /* @__PURE__ */ jsx(TeamProfileImageSection, { team: props.team }, `profile-image-${props.team.id}`),
897
+ /* @__PURE__ */ jsx(TeamDisplayNameSection, { team: props.team }, `display-name-${props.team.id}`),
898
+ /* @__PURE__ */ jsx(MemberListSection, { team: props.team }, `member-list-${props.team.id}`),
899
+ /* @__PURE__ */ jsx(MemberInvitationSection, { team: props.team }, `member-invitation-${props.team.id}`),
900
+ /* @__PURE__ */ jsx(TeamApiKeysSection, { team: props.team }, `api-keys-${props.team.id}`),
901
+ /* @__PURE__ */ jsx(LeaveTeamSection, { team: props.team }, `leave-team-${props.team.id}`)
810
902
  ] });
811
903
  }
812
904
  function LeaveTeamSection(props) {
@@ -938,11 +1030,14 @@ function MemberInvitationsSectionInvitationsList(props) {
938
1030
  /* @__PURE__ */ jsx(TableHead, { className: "w-[60px]", children: t("Expires") }),
939
1031
  /* @__PURE__ */ jsx(TableHead, { className: "w-[36px] max-w-[36px]" })
940
1032
  ] }) }),
941
- /* @__PURE__ */ jsx(TableBody, { children: invitationsToShow.map((invitation, i) => /* @__PURE__ */ jsxs(TableRow, { children: [
942
- /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { children: invitation.recipientEmail }) }),
943
- /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { variant: "secondary", children: invitation.expiresAt.toLocaleString() }) }),
944
- /* @__PURE__ */ jsx(TableCell, { align: "right", className: "max-w-[36px]", children: removeMemberPermission && /* @__PURE__ */ jsx(Button, { onClick: async () => await invitation.revoke(), size: "icon", variant: "ghost", children: /* @__PURE__ */ jsx(Trash, { className: "w-4 h-4" }) }) })
945
- ] }, invitation.id)) })
1033
+ /* @__PURE__ */ jsxs(TableBody, { children: [
1034
+ invitationsToShow.map((invitation, i) => /* @__PURE__ */ jsxs(TableRow, { children: [
1035
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { children: invitation.recipientEmail }) }),
1036
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Typography, { variant: "secondary", children: invitation.expiresAt.toLocaleString() }) }),
1037
+ /* @__PURE__ */ jsx(TableCell, { align: "right", className: "max-w-[36px]", children: removeMemberPermission && /* @__PURE__ */ jsx(Button, { onClick: async () => await invitation.revoke(), size: "icon", variant: "ghost", children: /* @__PURE__ */ jsx(Trash, { className: "w-4 h-4" }) }) })
1038
+ ] }, invitation.id)),
1039
+ invitationsToShow.length === 0 && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colSpan: 3, children: /* @__PURE__ */ jsx(Typography, { variant: "secondary", children: t("No outstanding invitations") }) }) })
1040
+ ] })
946
1041
  ] }) });
947
1042
  }
948
1043
  function MemberInvitationSectionInner(props) {
@@ -1171,8 +1266,30 @@ function EditableText(props) {
1171
1266
  /* @__PURE__ */ jsx(Button, { onClick: () => setEditing(true), size: "icon", variant: "ghost", children: /* @__PURE__ */ jsx(Edit, { className: "w-4 h-4" }) })
1172
1267
  ] }) });
1173
1268
  }
1269
+ function ApiKeysPageSkeleton() {
1270
+ return /* @__PURE__ */ jsxs(PageLayout, { children: [
1271
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
1272
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-[200px] w-full mt-1 rounded-md" })
1273
+ ] });
1274
+ }
1275
+ function TeamPageSkeleton() {
1276
+ return /* @__PURE__ */ jsxs(PageLayout, { children: [
1277
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
1278
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
1279
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
1280
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-[200px] w-full mt-1 rounded-md" })
1281
+ ] });
1282
+ }
1283
+ function TeamCreationSkeleton() {
1284
+ return /* @__PURE__ */ jsxs(PageLayout, { children: [
1285
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" }),
1286
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full mt-1" })
1287
+ ] });
1288
+ }
1174
1289
  export {
1175
1290
  AccountSettings,
1176
- EditableText
1291
+ ApiKeysPage,
1292
+ EditableText,
1293
+ TeamApiKeysSection
1177
1294
  };
1178
1295
  //# sourceMappingURL=account-settings.js.map