@stackframe/stack 2.8.1 → 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.
- package/CHANGELOG.md +20 -0
- package/dist/components/api-key-dialogs.js +184 -0
- package/dist/components/api-key-dialogs.js.map +1 -0
- package/dist/components/api-key-table.js +149 -0
- package/dist/components/api-key-table.js.map +1 -0
- package/dist/components-page/account-settings.js +134 -15
- package/dist/components-page/account-settings.js.map +1 -1
- package/dist/esm/components/api-key-dialogs.js +157 -0
- package/dist/esm/components/api-key-dialogs.js.map +1 -0
- package/dist/esm/components/api-key-table.js +125 -0
- package/dist/esm/components/api-key-table.js.map +1 -0
- package/dist/esm/components-page/account-settings.js +132 -15
- package/dist/esm/components-page/account-settings.js.map +1 -1
- package/dist/esm/lib/auth.js +6 -6
- package/dist/esm/lib/auth.js.map +1 -1
- package/dist/esm/lib/stack-app/api-keys/index.js +14 -6
- package/dist/esm/lib/stack-app/api-keys/index.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js +26 -23
- package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +106 -19
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/common.js +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js +150 -16
- package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/interfaces/server-app.js.map +1 -1
- package/dist/esm/lib/stack-app/index.js.map +1 -1
- package/dist/esm/lib/stack-app/internal-api-keys/index.js +14 -0
- package/dist/esm/lib/stack-app/internal-api-keys/index.js.map +1 -0
- package/dist/esm/lib/stack-app/projects/index.js +3 -1
- package/dist/esm/lib/stack-app/projects/index.js.map +1 -1
- package/dist/esm/lib/stack-app/teams/index.js.map +1 -1
- package/dist/esm/lib/stack-app/users/index.js.map +1 -1
- package/dist/esm/utils/url.js +2 -2
- package/dist/esm/utils/url.js.map +1 -1
- package/dist/index.d.mts +100 -35
- package/dist/index.d.ts +100 -35
- package/dist/lib/auth.js +6 -6
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/stack-app/api-keys/index.js +16 -7
- package/dist/lib/stack-app/api-keys/index.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/admin-app-impl.js +25 -22
- package/dist/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js +106 -19
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/common.js +1 -1
- package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/server-app-impl.js +150 -16
- package/dist/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/lib/stack-app/apps/interfaces/server-app.js.map +1 -1
- package/dist/lib/stack-app/index.js.map +1 -1
- package/dist/lib/stack-app/internal-api-keys/index.js +39 -0
- package/dist/lib/stack-app/internal-api-keys/index.js.map +1 -0
- package/dist/lib/stack-app/project-configs/index.js.map +1 -1
- package/dist/lib/stack-app/projects/index.js +3 -1
- package/dist/lib/stack-app/projects/index.js.map +1 -1
- package/dist/lib/stack-app/teams/index.js.map +1 -1
- package/dist/lib/stack-app/users/index.js.map +1 -1
- package/dist/utils/url.js +2 -2
- package/dist/utils/url.js.map +1 -1
- 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":"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","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(
|
|
806
|
-
/* @__PURE__ */ jsx(
|
|
807
|
-
/* @__PURE__ */ jsx(
|
|
808
|
-
/* @__PURE__ */ jsx(
|
|
809
|
-
/* @__PURE__ */ jsx(
|
|
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__ */
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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
|
-
|
|
1291
|
+
ApiKeysPage,
|
|
1292
|
+
EditableText,
|
|
1293
|
+
TeamApiKeysSection
|
|
1177
1294
|
};
|
|
1178
1295
|
//# sourceMappingURL=account-settings.js.map
|