Skip to content

Commit ab67fd5

Browse files
committed
feat: rewrite authorize page to use deploymentId, fetch deployment from DB
- Rewrite authorize page to take single deploymentId search param - Fetch deployment details from DB via getById instead of URL params - Handle awaiting_approval, already authorized, and failed states - Simplify authorize mutation input to just deploymentId - Add gitCommitMessage, gitCommitAuthorHandle, gitCommitAuthorAvatarUrl, projectId to getById response - Register getById in tRPC router
1 parent 1f49fd9 commit ab67fd5

File tree

6 files changed

+134
-156
lines changed

6 files changed

+134
-156
lines changed

web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/authorize/page.tsx

Lines changed: 100 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,29 @@ import {
1313
import { Button } from "@unkey/ui";
1414
import { useParams, useRouter } from "next/navigation";
1515
import { parseAsString, useQueryStates } from "nuqs";
16-
import { useProjectData } from "../(overview)/data-provider";
1716

1817
const searchParamsParsers = {
19-
branch: parseAsString.withDefault(""),
20-
sha: parseAsString.withDefault(""),
21-
sender: parseAsString.withDefault(""),
22-
message: parseAsString.withDefault(""),
23-
repo: parseAsString.withDefault(""),
18+
deploymentId: parseAsString.withDefault(""),
2419
};
2520

2621
export default function AuthorizeDeploymentPage() {
2722
const params = useParams<{ workspaceSlug: string; projectId: string }>();
2823
const router = useRouter();
29-
const { project } = useProjectData();
3024

31-
const [{ branch, sha, sender, message, repo }] = useQueryStates(searchParamsParsers);
25+
const [{ deploymentId }] = useQueryStates(searchParamsParsers);
26+
27+
const deployment = trpc.deploy.deployment.getById.useQuery(
28+
{ deploymentId },
29+
{ enabled: !!deploymentId },
30+
);
3231

3332
const authorize = trpc.deploy.deployment.authorize.useMutation({
3433
onSuccess: () => {
3534
router.push(`/${params.workspaceSlug}/projects/${params.projectId}/deployments`);
3635
},
3736
});
3837

39-
const shortSha = sha.slice(0, 7);
40-
const commitURL = `https://github.com/${repo}/commit/${sha}`;
41-
const branchURL = `https://github.com/${repo}/tree/${branch}`;
42-
const senderURL = `https://github.com/${sender}`;
43-
44-
if (!branch || !sha || !/^[0-9a-f]{40}$/.test(sha)) {
38+
if (!deploymentId) {
4539
return (
4640
<div className="flex items-center justify-center min-h-[60vh]">
4741
<div className="text-center space-y-2">
@@ -55,82 +49,79 @@ export default function AuthorizeDeploymentPage() {
5549
);
5650
}
5751

58-
const isStaleCommit = authorize.error?.message?.includes("no longer the HEAD");
59-
const newHead = isStaleCommit ? parseNewHead(authorize.error?.message) : null;
52+
if (deployment.isLoading) {
53+
return (
54+
<div className="flex items-center justify-center min-h-[60vh]">
55+
<div className="text-center space-y-2">
56+
<p className="text-sm text-content-subtle">Loading deployment details...</p>
57+
</div>
58+
</div>
59+
);
60+
}
6061

61-
if (authorize.isSuccess) {
62+
if (deployment.error) {
63+
return (
64+
<div className="flex items-center justify-center min-h-[60vh]">
65+
<div className="text-center space-y-2">
66+
<CircleXMark iconSize="2xl-thin" className="text-error-9 mx-auto" />
67+
<h2 className="text-lg font-semibold text-content">Deployment not found</h2>
68+
<p className="text-content-subtle text-sm">{deployment.error.message}</p>
69+
</div>
70+
</div>
71+
);
72+
}
73+
74+
const data = deployment.data;
75+
76+
// Already authorized — deployment is no longer awaiting approval
77+
if (data.status !== "awaiting_approval") {
78+
const isSuccess = data.status !== "failed";
6279
return (
6380
<div className="flex items-center justify-center min-h-[60vh]">
6481
<div className="text-center space-y-3">
65-
<CircleCheck iconSize="2xl-thin" className="text-success-9 mx-auto" />
66-
<h2 className="text-lg font-semibold text-content">Deployment Authorized</h2>
82+
{isSuccess ? (
83+
<CircleCheck iconSize="2xl-thin" className="text-success-9 mx-auto" />
84+
) : (
85+
<CircleXMark iconSize="2xl-thin" className="text-error-9 mx-auto" />
86+
)}
87+
<h2 className="text-lg font-semibold text-content">
88+
{isSuccess ? "Deployment Already Authorized" : "Deployment Failed"}
89+
</h2>
6790
<p className="text-content-subtle text-sm">
68-
The deployment has been authorized and is now in progress.
91+
{isSuccess
92+
? "This deployment has already been authorized and is in progress."
93+
: "This deployment has failed."}
6994
</p>
95+
<Button
96+
variant="outline"
97+
size="xlg"
98+
onClick={() =>
99+
router.push(`/${params.workspaceSlug}/projects/${params.projectId}/deployments`)
100+
}
101+
>
102+
View Deployments
103+
</Button>
70104
</div>
71105
</div>
72106
);
73107
}
74108

75-
if (isStaleCommit && newHead) {
76-
const newAuthorizeURL = `/${params.workspaceSlug}/projects/${params.projectId}/authorize?${new URLSearchParams(
77-
{
78-
branch,
79-
sha: newHead.sha,
80-
sender: newHead.author,
81-
message: newHead.message,
82-
repo,
83-
},
84-
).toString()}`;
85-
109+
if (authorize.isSuccess) {
86110
return (
87111
<div className="flex items-center justify-center min-h-[60vh]">
88-
<div className="w-full max-w-md space-y-6">
89-
<div className="text-center space-y-3">
90-
<ShieldAlert iconSize="2xl-thin" className="text-warning-9 mx-auto" />
91-
<h2 className="text-lg font-semibold text-content">Commit is outdated</h2>
92-
<p className="text-sm text-content-subtle">
93-
The branch <strong className="text-content font-mono">{branch}</strong> has new
94-
commits since this link was created. The latest commit is{" "}
95-
<a
96-
href={`https://github.com/${repo}/commit/${newHead.sha}`}
97-
target="_blank"
98-
rel="noopener noreferrer"
99-
className="font-mono text-accent-11 hover:text-accent-12"
100-
>
101-
{newHead.sha.slice(0, 7)}
102-
</a>
103-
.
104-
</p>
105-
</div>
106-
<div className="flex gap-3">
107-
<Button
108-
variant="primary"
109-
size="xlg"
110-
className="flex-1"
111-
onClick={() => {
112-
authorize.reset();
113-
router.push(newAuthorizeURL);
114-
}}
115-
>
116-
View Latest Commit
117-
</Button>
118-
<Button
119-
variant="outline"
120-
size="xlg"
121-
className="flex-1"
122-
onClick={() =>
123-
router.push(`/${params.workspaceSlug}/projects/${params.projectId}/deployments`)
124-
}
125-
>
126-
Dismiss
127-
</Button>
128-
</div>
112+
<div className="text-center space-y-3">
113+
<CircleCheck iconSize="2xl-thin" className="text-success-9 mx-auto" />
114+
<h2 className="text-lg font-semibold text-content">Deployment Authorized</h2>
115+
<p className="text-content-subtle text-sm">
116+
The deployment has been authorized and is now in progress.
117+
</p>
129118
</div>
130119
</div>
131120
);
132121
}
133122

123+
const shortSha = data.gitCommitSha?.slice(0, 7) ?? "";
124+
134125
return (
135126
<div className="flex items-center justify-center min-h-[60vh]">
136127
<div className="w-full max-w-md space-y-6">
@@ -153,57 +144,53 @@ export default function AuthorizeDeploymentPage() {
153144
<div className="text-center space-y-2">
154145
<h1 className="text-xl font-semibold text-content">Authorization Required</h1>
155146
<p className="text-sm text-content-subtle">
156-
An external contributor pushed to{" "}
157-
<strong className="text-content">{project?.name ?? "this project"}</strong>. A team
158-
member must authorize this deployment before it can proceed.
147+
An external contributor pushed a commit. A team member must authorize this deployment
148+
before it can proceed.
159149
</p>
160150
</div>
161151

162152
{/* Commit details */}
163153
<div className="border border-border rounded-lg divide-y divide-border">
164-
<div className="flex items-center gap-3 px-4 py-3">
165-
<CodeBranch iconSize="md-thin" className="text-content-subtle shrink-0" />
166-
<span className="text-sm text-content-subtle">Branch</span>
167-
<a
168-
href={branchURL}
169-
target="_blank"
170-
rel="noopener noreferrer"
171-
className="ml-auto text-sm font-mono text-accent-11 hover:text-accent-12 bg-background-subtle px-2 py-0.5 rounded"
172-
>
173-
{branch}
174-
</a>
175-
</div>
154+
{data.gitBranch && (
155+
<div className="flex items-center gap-3 px-4 py-3">
156+
<CodeBranch iconSize="md-thin" className="text-content-subtle shrink-0" />
157+
<span className="text-sm text-content-subtle">Branch</span>
158+
<span className="ml-auto text-sm font-mono text-content bg-background-subtle px-2 py-0.5 rounded">
159+
{data.gitBranch}
160+
</span>
161+
</div>
162+
)}
176163

177-
<div className="flex items-center gap-3 px-4 py-3">
178-
<CodeCommit iconSize="md-thin" className="text-content-subtle shrink-0" />
179-
<span className="text-sm text-content-subtle">Commit</span>
180-
<a
181-
href={commitURL}
182-
target="_blank"
183-
rel="noopener noreferrer"
184-
className="ml-auto text-sm font-mono text-accent-11 hover:text-accent-12 bg-background-subtle px-2 py-0.5 rounded"
185-
>
186-
{shortSha}
187-
</a>
188-
</div>
164+
{shortSha && (
165+
<div className="flex items-center gap-3 px-4 py-3">
166+
<CodeCommit iconSize="md-thin" className="text-content-subtle shrink-0" />
167+
<span className="text-sm text-content-subtle">Commit</span>
168+
<span className="ml-auto text-sm font-mono text-content bg-background-subtle px-2 py-0.5 rounded">
169+
{shortSha}
170+
</span>
171+
</div>
172+
)}
189173

190-
{message && (
174+
{data.gitCommitMessage && (
191175
<div className="px-4 py-3">
192-
<p className="text-sm text-content truncate">{message}</p>
176+
<p className="text-sm text-content truncate">{data.gitCommitMessage}</p>
193177
</div>
194178
)}
195179

196-
<div className="flex items-center gap-3 px-4 py-3">
197-
<User iconSize="md-thin" className="text-content-subtle shrink-0" />
198-
<a
199-
href={senderURL}
200-
target="_blank"
201-
rel="noopener noreferrer"
202-
className="text-sm text-accent-11 hover:text-accent-12"
203-
>
204-
{sender}
205-
</a>
206-
</div>
180+
{data.gitCommitAuthorHandle && (
181+
<div className="flex items-center gap-3 px-4 py-3">
182+
{data.gitCommitAuthorAvatarUrl ? (
183+
<img
184+
src={data.gitCommitAuthorAvatarUrl}
185+
alt={data.gitCommitAuthorHandle}
186+
className="w-5 h-5 rounded-full shrink-0"
187+
/>
188+
) : (
189+
<User iconSize="md-thin" className="text-content-subtle shrink-0" />
190+
)}
191+
<span className="text-sm text-content">{data.gitCommitAuthorHandle}</span>
192+
</div>
193+
)}
207194
</div>
208195

209196
{/* Error */}
@@ -221,13 +208,7 @@ export default function AuthorizeDeploymentPage() {
221208
size="xlg"
222209
className="flex-1"
223210
loading={authorize.isLoading}
224-
onClick={() =>
225-
authorize.mutate({
226-
projectId: params.projectId,
227-
branch,
228-
commitSha: sha,
229-
})
230-
}
211+
onClick={() => authorize.mutate({ deploymentId })}
231212
>
232213
Authorize Deployment
233214
</Button>
@@ -251,24 +232,3 @@ export default function AuthorizeDeploymentPage() {
251232
</div>
252233
);
253234
}
254-
255-
function parseNewHead(
256-
errorMessage: string | undefined,
257-
): { sha: string; message: string; author: string } | null {
258-
if (!errorMessage) {
259-
return null;
260-
}
261-
const shaMatch = errorMessage.match(/current_head_sha=([0-9a-f]{40})/);
262-
const messageMatch = errorMessage.match(
263-
/current_head_message=(.+?)(?:\s+current_head_author=|$)/,
264-
);
265-
const authorMatch = errorMessage.match(/current_head_author=(\S+)/);
266-
if (!shaMatch) {
267-
return null;
268-
}
269-
return {
270-
sha: shaMatch[1],
271-
message: messageMatch?.[1] ?? "",
272-
author: authorMatch?.[1] ?? "",
273-
};
274-
}

web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/components/deployment-status-badge.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Hammer2,
99
LayerFront,
1010
Pulse,
11+
ShieldAlert,
1112
Sparkle3,
1213
} from "@unkey/icons";
1314
import type { IconProps } from "@unkey/icons/src/props";
@@ -85,6 +86,13 @@ const statusConfigs: Record<DeploymentStatus, StatusConfig> = {
8586
textColor: "text-errorA-11",
8687
iconColor: "text-error-11",
8788
},
89+
awaiting_approval: {
90+
icon: ShieldAlert,
91+
label: "Awaiting Approval",
92+
bgColor: "bg-warningA-3",
93+
textColor: "text-warningA-11",
94+
iconColor: "text-warning-11",
95+
},
8896
};
8997

9098
type DeploymentStatusBadgeProps = {

web/apps/dashboard/lib/collections/deploy/deployment-status.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const DEPLOYMENT_STATUSES = [
77
"finalizing",
88
"ready",
99
"failed",
10+
"awaiting_approval",
1011
] as const;
1112

1213
export type DeploymentStatus = (typeof DEPLOYMENT_STATUSES)[number];

0 commit comments

Comments
 (0)