Skip to content

Commit 4fb80f5

Browse files
atrakhConvex, Inc.
authored and
Convex, Inc.
committed
dashboard: add list of authorized application to project settings page (#34508)
GitOrigin-RevId: 96bc36d44efd944ce52fe72fa9d83766a526f71c
1 parent 655aa01 commit 4fb80f5

File tree

7 files changed

+130
-21
lines changed

7 files changed

+130
-21
lines changed

Diff for: npm-packages/dashboard/src/api/accessTokens.ts

+11
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ export function useInstanceAccessTokens(deploymentName?: string) {
2424
return accessTokens;
2525
}
2626

27+
export function useProjectAppAccessTokens(projectId?: number) {
28+
const { data: accessTokens } = useBBQuery({
29+
path: "/projects/{project_id}/app_access_tokens",
30+
pathParams: {
31+
project_id: projectId?.toString() || "",
32+
},
33+
});
34+
35+
return accessTokens;
36+
}
37+
2738
export function useProjectAccessTokens(projectId?: number) {
2839
const { data: accessTokens } = useBBQuery({
2940
path: "/projects/{project_id}/access_tokens",

Diff for: npm-packages/dashboard/src/components/AuthorizeProject.tsx

+18-16
Original file line numberDiff line numberDiff line change
@@ -192,24 +192,26 @@ export function AuthorizeProject() {
192192
</div>
193193
) : (
194194
<div className="flex flex-wrap items-end gap-2">
195-
<div className="flex flex-col gap-1">
196-
<Combobox
197-
options={
198-
projects?.map((project) => ({
199-
label: project.name,
200-
value: project.id,
201-
})) ?? []
202-
}
203-
label="Select a project"
204-
labelHidden={false}
205-
selectedOption={selectedProjectId}
206-
setSelectedOption={setSelectedProjectId}
207-
disabled={projects === null}
208-
/>
209-
</div>
195+
{projects && projects.length > 0 && (
196+
<div className="flex flex-col gap-1">
197+
<Combobox
198+
options={
199+
projects.map((project) => ({
200+
label: project.name,
201+
value: project.id,
202+
})) ?? []
203+
}
204+
label="Select a project"
205+
labelHidden={false}
206+
selectedOption={selectedProjectId}
207+
setSelectedOption={setSelectedProjectId}
208+
disabled={projects === null}
209+
/>
210+
</div>
211+
)}
210212
{!didCreateProject && (
211213
<div className="flex items-center gap-2">
212-
or
214+
{projects && projects.length > 0 && "or"}
213215
<Button
214216
variant="neutral"
215217
onClick={() => {

Diff for: npm-packages/dashboard/src/components/deploymentSettings/DeploymentAccessTokenListItem.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function DeploymentAccessTokenListItem({
3030
useEffect(() => {
3131
shouldShow && setShowToken(shouldShow);
3232
}, [shouldShow]);
33-
const [showDeleteConfirmatino, setShowDeleteConfirmation] = useState(false);
33+
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
3434

3535
const member = members?.find((m) => m.id === token.creator);
3636

@@ -103,7 +103,7 @@ export function DeploymentAccessTokenListItem({
103103
</div>
104104
)}
105105
</div>
106-
{showDeleteConfirmatino && (
106+
{showDeleteConfirmation && (
107107
<ConfirmationDialog
108108
onClose={() => {
109109
setShowDeleteConfirmation(false);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { AppAccessTokenResponse, ProjectDetails } from "generatedApi";
2+
3+
import { Sheet } from "dashboard-common/elements/Sheet";
4+
import { useProjectAppAccessTokens } from "api/accessTokens";
5+
import { LoadingTransition } from "dashboard-common/elements/Loading";
6+
import { TimestampDistance } from "dashboard-common/elements/TimestampDistance";
7+
import { Button } from "dashboard-common/elements/Button";
8+
import { Cross2Icon } from "@radix-ui/react-icons";
9+
10+
export function AuthorizedApplications({
11+
project,
12+
}: {
13+
project: ProjectDetails;
14+
}) {
15+
const projectAccessTokens = useProjectAppAccessTokens(project.id);
16+
17+
return (
18+
<Sheet>
19+
<h3 className="mb-2">Authorized Applications</h3>
20+
<p className="text-sm text-content-primary">
21+
These 3rd-party applications have been authorized to access this project
22+
on your behalf.
23+
</p>
24+
<p className="mb-2 mt-1 text-sm text-content-primary">
25+
You cannot see applications that other members of your team have
26+
authorized.
27+
</p>
28+
<LoadingTransition
29+
loadingProps={{ fullHeight: false, className: "h-14 w-full" }}
30+
>
31+
{projectAccessTokens && (
32+
<div className="flex w-full flex-col gap-2">
33+
{projectAccessTokens.length ? (
34+
projectAccessTokens.map((token, idx) => (
35+
<AuthorizedApplicationListItem key={idx} token={token} />
36+
))
37+
) : (
38+
<div className="my-2 text-content-secondary">
39+
You have not authorized any applications yet.
40+
</div>
41+
)}
42+
</div>
43+
)}
44+
</LoadingTransition>
45+
</Sheet>
46+
);
47+
}
48+
49+
function AuthorizedApplicationListItem({
50+
token,
51+
}: {
52+
token: AppAccessTokenResponse;
53+
}) {
54+
return (
55+
<div className="flex w-full flex-col">
56+
<div className="mt-2 flex flex-wrap items-center justify-between gap-2">
57+
<div>{token.appName}</div>
58+
<div className="flex flex-wrap items-center gap-4">
59+
<div className="flex flex-col items-end">
60+
{token.lastUsedTime !== null && token.lastUsedTime !== undefined ? (
61+
<TimestampDistance
62+
prefix="Last used "
63+
date={new Date(token.lastUsedTime)}
64+
/>
65+
) : (
66+
<div className="text-xs text-content-secondary">Never used</div>
67+
)}
68+
<TimestampDistance
69+
prefix="Created "
70+
date={new Date(token.creationTime)}
71+
/>
72+
<Button
73+
variant="danger"
74+
icon={<Cross2Icon />}
75+
disabled
76+
tip="Coming soon."
77+
>
78+
Revoke
79+
</Button>
80+
</div>
81+
</div>
82+
</div>
83+
</div>
84+
);
85+
}

Diff for: npm-packages/dashboard/src/pages/t/[team]/[project]/settings.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
LostAccessDescription,
2424
} from "components/projects/modals/LostAccessModal";
2525
import { withAuthenticatedPage } from "lib/withAuthenticatedPage";
26-
import { DefaultEnvironmentVariables } from "components/deploymentSettings/DefaultEnvironmentVariables";
26+
import { DefaultEnvironmentVariables } from "components/projectSettings/DefaultEnvironmentVariables";
2727
import {
2828
getAccessTokenBasedDeployKey,
2929
getAccessTokenBasedDeployKeyForPreview,
@@ -35,16 +35,18 @@ import Head from "next/head";
3535
import { useAccessToken } from "hooks/useServerSideData";
3636
import { MemberProjectRoles } from "components/projects/MemberProjectRoles";
3737
import { DeploymentAccessTokenList } from "components/deploymentSettings/DeploymentAccessTokenList";
38-
import { CustomDomains } from "components/deploymentSettings/CustomDomains";
38+
import { CustomDomains } from "components/projectSettings/CustomDomains";
3939
import { TransferProject } from "components/projects/TransferProject";
4040
import { cn } from "dashboard-common/lib/cn";
41+
import { AuthorizedApplications } from "components/projectSettings/AuthorizedApplications";
4142

4243
const SECTION_IDS = {
4344
projectForm: "project-form",
4445
projectRoles: "project-roles",
4546
projectUsage: "project-usage",
4647
customDomains: "custom-domains",
4748
deployKeys: "deploy-keys",
49+
authorizedApplications: "authorized-applications",
4850
envVars: "env-vars",
4951
lostAccess: "lost-access",
5052
transferProject: "transfer-project",
@@ -69,6 +71,10 @@ function SettingsNavigation() {
6971
{ id: SECTION_IDS.projectUsage, label: "Project Usage" },
7072
{ id: SECTION_IDS.customDomains, label: "Custom Domains" },
7173
{ id: SECTION_IDS.deployKeys, label: "Deploy Keys" },
74+
{
75+
id: SECTION_IDS.authorizedApplications,
76+
label: "Authorized Applications",
77+
},
7278
{ id: SECTION_IDS.envVars, label: "Environment Variables" },
7379
{ id: SECTION_IDS.lostAccess, label: "Lost Access" },
7480
{ id: SECTION_IDS.transferProject, label: "Transfer Project" },
@@ -129,7 +135,7 @@ function SettingsNavigation() {
129135
return (
130136
<nav
131137
data-settings-nav
132-
className="sticky top-24 hidden h-fit max-h-[calc(100vh-12rem)] w-[13rem] shrink-0 overflow-visible pr-8 md:block"
138+
className="sticky top-24 hidden h-fit max-h-[calc(100vh-12rem)] w-[14rem] shrink-0 overflow-visible pr-8 md:block"
133139
aria-label="Settings navigation"
134140
>
135141
<div
@@ -264,6 +270,11 @@ function ProjectSettings() {
264270
/>
265271
</div>
266272
)}
273+
{project && (
274+
<div id={SECTION_IDS.authorizedApplications}>
275+
<AuthorizedApplications project={project} />
276+
</div>
277+
)}
267278
<div id={SECTION_IDS.envVars}>
268279
<DefaultEnvironmentVariables />
269280
</div>

0 commit comments

Comments
 (0)