Skip to content

Commit 07827b2

Browse files
committed
added request transfer from marketplace
1 parent 717a62c commit 07827b2

File tree

5 files changed

+137
-1
lines changed

5 files changed

+137
-1
lines changed

app/dashboard/installation/actions.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {
99
getResourceBalance,
1010
listResources,
1111
} from "@/lib/partner";
12-
import { Balance } from "@/lib/vercel/schemas";
12+
import type { Balance } from "@/lib/vercel/schemas";
1313
import {
14+
requestTransferFromMarketplace,
1415
sendBillingData,
1516
submitPrepaymentBalances,
1617
} from "@/lib/vercel/marketplace-api";
@@ -49,3 +50,15 @@ export async function sendBillingDataAction() {
4950
await sendBillingData(installationId, billingData);
5051
await submitPrepaymentBalances(installationId, balances);
5152
}
53+
54+
export async function requestTransferFromVercelAction(formData: FormData) {
55+
const installationId = formData.get("installationId") as string;
56+
const transferId = Math.random().toString(36).substring(2);
57+
const requester = "Vanessa";
58+
const result = await requestTransferFromMarketplace(
59+
installationId,
60+
transferId,
61+
requester
62+
);
63+
return result;
64+
}

app/dashboard/installation/page.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getAccountInfo } from "@/lib/vercel/marketplace-api";
44
import { Section } from "../components/section";
55
import { addInstallationBalance, sendBillingDataAction } from "./actions";
66
import { FormButton } from "../components/form-button";
7+
import TransferFromVercelRedirect from "./transfer-from-vercel-redirect";
78

89
export const dynamic = "force-dynamic";
910

@@ -74,6 +75,9 @@ export default async function IntallationPage() {
7475
</FormButton>
7576
</form>
7677
</Section>
78+
<Section title="Transfer from Vercel">
79+
<TransferFromVercelRedirect configurationId={session.installation_id} />
80+
</Section>
7781
</main>
7882
);
7983
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { requestTransferFromVercelAction } from "./actions";
5+
6+
interface TransferFromVercelRedirectProps {
7+
configurationId: string;
8+
}
9+
10+
export default function TransferFromVercelRedirect({
11+
configurationId,
12+
}: TransferFromVercelRedirectProps) {
13+
const [continueUrl, setContinueUrl] = useState<string | null>(null);
14+
const [loading, setLoading] = useState(false);
15+
16+
async function handleTransfer() {
17+
setLoading(true);
18+
const formData = new FormData();
19+
formData.append("installationId", configurationId);
20+
21+
try {
22+
const result = await requestTransferFromVercelAction(formData);
23+
setContinueUrl(result.continueUrl);
24+
} catch (error: any) {
25+
console.error("Transfer failed:", error.message);
26+
}
27+
setLoading(false);
28+
}
29+
30+
function handleCancel() {
31+
setContinueUrl(null);
32+
}
33+
34+
return (
35+
<div className="flex flex-col items-center space-y-4">
36+
{continueUrl ? (
37+
<section className="p-4 border rounded text-center">
38+
<p className="mb-4">
39+
Please go to Vercel Marketplace to complete the transfer.
40+
</p>
41+
<div className="flex justify-center gap-4">
42+
<a
43+
href={continueUrl}
44+
target="_blank"
45+
className="rounded bg-green-500 text-white px-4 py-2 inline-block"
46+
rel="noreferrer"
47+
>
48+
Proceed to Vercel
49+
</a>
50+
<button
51+
onClick={handleCancel}
52+
className="rounded bg-red-500 text-white px-4 py-2"
53+
>
54+
Cancel
55+
</button>
56+
</div>
57+
</section>
58+
) : (
59+
<button
60+
onClick={handleTransfer}
61+
disabled={loading}
62+
className="rounded bg-blue-500 text-white px-4 py-2 disabled:opacity-50"
63+
>
64+
{loading ? "Loading..." : "Transfer from Vercel"}
65+
</button>
66+
)}
67+
</div>
68+
);
69+
}

lib/vercel/marketplace-api.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import type {
1212
Invoice,
1313
InvoiceDiscount,
1414
RefundInvoiceRequest,
15+
RequestTransferFromMarketplace,
16+
RequestTransferFromMarketplaceResponse,
1517
RequestTransferToMarketplace,
1618
RequestTransferToMarketplaceResponse,
1719
SubmitPrepaymentBalanceRequest,
@@ -322,3 +324,22 @@ export async function requestTransferToMarketplace(
322324
)) as RequestTransferToMarketplaceResponse;
323325
return result;
324326
}
327+
328+
export async function requestTransferFromMarketplace(
329+
installationId: string,
330+
transferId: string,
331+
requester: string
332+
): Promise<RequestTransferFromMarketplaceResponse> {
333+
const result = (await fetchVercelApi(
334+
`/v1/installations/${installationId}/transfers/from-marketplace`,
335+
{
336+
installationId,
337+
method: "POST",
338+
data: {
339+
transferId,
340+
requester: { name: requester },
341+
} satisfies RequestTransferFromMarketplace,
342+
}
343+
)) as RequestTransferFromMarketplaceResponse;
344+
return result;
345+
}

lib/vercel/schemas.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,3 +746,32 @@ export const TransferInstallationToMarketplaceRequestSchema = z.object({
746746
"The account information for this installation. Use Get Account Info API to re-fetch this data post transfer."
747747
),
748748
});
749+
750+
export type RequestTransferFromMarketplace = z.infer<
751+
typeof requestTransferFromMarketplaceSchema
752+
>;
753+
export const requestTransferFromMarketplaceSchema = z.object({
754+
transferId: z.string().min(1),
755+
requester: z.object({
756+
name: z.string().min(1),
757+
}),
758+
});
759+
760+
export type RequestTransferFromMarketplaceResponse = z.infer<
761+
typeof requestTransferToMarketplaceResponseSchema
762+
>;
763+
export const requestTransferFromMarketplaceResponseSchema = z.object({
764+
continueUrl: z.string().url(),
765+
});
766+
767+
export type TransferInstallationFromMarketplaceRequest = z.infer<
768+
typeof TransferInstallationFromMarketplaceRequestSchema
769+
>;
770+
771+
export const TransferInstallationFromMarketplaceRequestSchema = z.object({
772+
transferId: z.string(),
773+
requester: z.object({
774+
name: z.string().min(1),
775+
}),
776+
scopes: z.array(z.string().min(1)),
777+
});

0 commit comments

Comments
 (0)