Skip to content

Commit 6b80f75

Browse files
authored
Make RPC optional in chain infrastructure checkout (#7867)
1 parent c43c686 commit 6b80f75

File tree

6 files changed

+120
-19
lines changed

6 files changed

+120
-19
lines changed

apps/dashboard/src/@/analytics/report.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,3 +526,24 @@ export function reportTokenUpsellClicked(params: {
526526
}) {
527527
posthog.capture("token upsell clicked", params);
528528
}
529+
530+
// ----------------------------
531+
// CHAIN INFRASTRUCTURE CHECKOUT
532+
// ----------------------------
533+
/**
534+
* ### Why do we need to report this event?
535+
* - To record explicit user acknowledgement when proceeding to checkout without RPC
536+
* - To measure how often customers choose to omit RPC while purchasing Insight and/or Account Abstraction
537+
* - To correlate potential support issues arising from missing RPC
538+
*
539+
* ### Who is responsible for this event?
540+
* @jnsdls
541+
*/
542+
export function reportChainInfraRpcOmissionAgreed(properties: {
543+
chainId: number;
544+
frequency: "monthly" | "annual";
545+
includeInsight: boolean;
546+
includeAccountAbstraction: boolean;
547+
}) {
548+
posthog.capture("chain infra checkout rpc omission agreed", properties);
549+
}

apps/dashboard/src/@/components/project/create-project-modal/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { zodResolver } from "@hookform/resolvers/zod";
2-
import { DialogDescription } from "@radix-ui/react-dialog";
32
import { useMutation } from "@tanstack/react-query";
43
import type { ProjectService } from "@thirdweb-dev/service-utils";
54
import { SERVICES } from "@thirdweb-dev/service-utils";
@@ -18,6 +17,7 @@ import {
1817
Dialog,
1918
DialogClose,
2019
DialogContent,
20+
DialogDescription,
2121
DialogFooter,
2222
DialogHeader,
2323
DialogTitle,

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/deploy/_components/deploy-infrastructure-form.client.tsx

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
"use client";
22

33
import { useQueryState } from "nuqs";
4-
import { useMemo, useTransition } from "react";
4+
import { useMemo, useState, useTransition } from "react";
55
import { toast } from "sonner";
66
import { getChainInfraCheckoutURL } from "@/actions/billing";
7+
import { reportChainInfraRpcOmissionAgreed } from "@/analytics/report";
78
import { Badge } from "@/components/ui/badge";
89
import { Button } from "@/components/ui/button";
10+
import {
11+
Dialog,
12+
DialogContent,
13+
DialogDescription,
14+
DialogFooter,
15+
DialogHeader,
16+
DialogTitle,
17+
} from "@/components/ui/dialog";
918
import { Switch } from "@/components/ui/switch";
1019
import { InsightIcon } from "@/icons/InsightIcon";
1120
import { RPCIcon } from "@/icons/RPCIcon";
@@ -44,7 +53,7 @@ const SERVICE_CONFIG = {
4453
icon: "RPCIcon",
4554
label: "RPC",
4655
monthlyPrice: 1500,
47-
required: true,
56+
required: false,
4857
sku: "chain:infra:rpc" as const,
4958
},
5059
} satisfies Record<
@@ -80,19 +89,26 @@ export function DeployInfrastructureForm(props: {
8089
searchParams.addons.withOptions({ history: "replace", startTransition }),
8190
);
8291

92+
const [rpcParam, setRpcParam] = useQueryState(
93+
"rpc",
94+
searchParams.rpc.withOptions({ history: "replace", startTransition }),
95+
);
96+
8397
const addons = useMemo(() => {
8498
return addonsStr ? addonsStr.split(",").filter(Boolean) : [];
8599
}, [addonsStr]);
86100

87101
const includeInsight = addons.includes("insight");
88102
const includeAA = addons.includes("aa");
103+
const includeRPC = rpcParam === "on";
89104

90105
const selectedOrder = useMemo(() => {
91-
const arr: (keyof typeof SERVICE_CONFIG)[] = ["rpc"];
106+
const arr: (keyof typeof SERVICE_CONFIG)[] = [];
107+
if (includeRPC) arr.push("rpc");
92108
if (includeInsight) arr.push("insight");
93109
if (includeAA) arr.push("accountAbstraction");
94110
return arr;
95-
}, [includeInsight, includeAA]);
111+
}, [includeInsight, includeAA, includeRPC]);
96112

97113
// NEW: count selected services and prepare bundle discount hint
98114
const selectedCount = selectedOrder.length;
@@ -112,9 +128,9 @@ export function DeployInfrastructureForm(props: {
112128
return {
113129
accountAbstraction: includeAA,
114130
insight: includeInsight,
115-
rpc: true,
131+
rpc: includeRPC,
116132
} as const;
117-
}, [includeInsight, includeAA]);
133+
}, [includeInsight, includeAA, includeRPC]);
118134

119135
const pricePerService = useMemo(() => {
120136
const isAnnual = frequency === "annual";
@@ -172,10 +188,13 @@ export function DeployInfrastructureForm(props: {
172188

173189
const chainId = props.chain.chainId;
174190

175-
const checkout = () => {
191+
const [showRpcWarning, setShowRpcWarning] = useState(false);
192+
193+
const proceedToCheckout = () => {
176194
startTransition(async () => {
177195
try {
178-
const skus: ChainInfraSKU[] = [SERVICE_CONFIG.rpc.sku];
196+
const skus: ChainInfraSKU[] = [];
197+
if (includeRPC) skus.push(SERVICE_CONFIG.rpc.sku);
179198
if (includeInsight) skus.push(SERVICE_CONFIG.insight.sku);
180199
if (includeAA) skus.push(SERVICE_CONFIG.accountAbstraction.sku);
181200

@@ -186,11 +205,9 @@ export function DeployInfrastructureForm(props: {
186205
teamSlug: props.teamSlug,
187206
});
188207

189-
// If the action returns, it means redirect did not happen and we have an error
190208
if (res.status === "error") {
191209
toast.error(res.error);
192210
} else if (res.status === "success") {
193-
// replace the current page with the checkout page (which will then redirect back to us)
194211
window.location.href = res.data;
195212
}
196213
} catch (err) {
@@ -202,6 +219,15 @@ export function DeployInfrastructureForm(props: {
202219
});
203220
};
204221

222+
const checkout = () => {
223+
const hasAddons = includeInsight || includeAA;
224+
if (!includeRPC && hasAddons) {
225+
setShowRpcWarning(true);
226+
return;
227+
}
228+
proceedToCheckout();
229+
};
230+
205231
const periodLabel = frequency === "annual" ? "/yr" : "/mo";
206232
const isAnnual = frequency === "annual";
207233

@@ -211,21 +237,22 @@ export function DeployInfrastructureForm(props: {
211237
<div className="flex flex-col gap-4">
212238
<h3 className="text-lg font-semibold">Select Services</h3>
213239

214-
{/* Required service */}
240+
{/* RPC (now optional) */}
215241
<div className="flex flex-col gap-2 mb-6">
216242
<ServiceCard
217243
description={SERVICE_CONFIG.rpc.description}
218-
disabled
219244
icon={SERVICE_CONFIG.rpc.icon}
220245
label={SERVICE_CONFIG.rpc.label}
221-
onToggle={() => {}}
246+
onToggle={() => {
247+
const newVal = !includeRPC;
248+
setRpcParam(newVal ? "on" : "off");
249+
}}
222250
originalPrice={
223251
isAnnual ? SERVICE_CONFIG.rpc.monthlyPrice * 12 : undefined
224252
}
225253
periodLabel={periodLabel}
226254
price={pricePerService.rpc}
227-
required
228-
selected
255+
selected={includeRPC}
229256
/>
230257
</div>
231258

@@ -350,7 +377,11 @@ export function DeployInfrastructureForm(props: {
350377

351378
<Button
352379
className="w-full"
353-
disabled={isTransitionPending || !props.isOwner}
380+
disabled={
381+
isTransitionPending ||
382+
!props.isOwner ||
383+
selectedOrder.length === 0
384+
}
354385
onClick={checkout}
355386
>
356387
Proceed to Checkout
@@ -362,6 +393,54 @@ export function DeployInfrastructureForm(props: {
362393
)}
363394
</div>
364395
</div>
396+
{/* RPC Omission Warning Modal */}
397+
<Dialog open={showRpcWarning} onOpenChange={setShowRpcWarning}>
398+
<DialogContent>
399+
<DialogHeader>
400+
<DialogTitle>Proceed without RPC (not recommended)</DialogTitle>
401+
<DialogDescription className="space-y-3">
402+
<p>
403+
RPC powers core functionality used by <strong>Insight</strong>{" "}
404+
and <strong>Account Abstraction</strong>.
405+
</p>
406+
<div className="space-y-1">
407+
<p>Without RPC, you may experience:</p>
408+
<ul className="list-disc pl-5 space-y-1">
409+
<li>Delayed or missing data in Insight</li>
410+
<li>
411+
Transaction failures or degraded reliability for Account
412+
Abstraction
413+
</li>
414+
<li>Limited or unsupported features across both services</li>
415+
</ul>
416+
</div>
417+
<p>
418+
thirdweb <strong>cannot guarantee</strong> that Insight or
419+
Account Abstraction will work as expected without RPC. To ensure
420+
reliability, keep RPC enabled.
421+
</p>
422+
<p>If you still want to continue, confirm below.</p>
423+
</DialogDescription>
424+
</DialogHeader>
425+
<DialogFooter className="gap-2">
426+
<Button
427+
onClick={() => {
428+
reportChainInfraRpcOmissionAgreed({
429+
chainId,
430+
frequency: frequency === "annual" ? "annual" : "monthly",
431+
includeInsight,
432+
includeAccountAbstraction: includeAA,
433+
});
434+
setShowRpcWarning(false);
435+
proceedToCheckout();
436+
}}
437+
variant="destructive"
438+
>
439+
I understand — proceed without RPC
440+
</Button>
441+
</DialogFooter>
442+
</DialogContent>
443+
</Dialog>
365444
</div>
366445
);
367446
}

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/infrastructure/deploy/search-params.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ import { parseAsString, parseAsStringEnum } from "nuqs/server";
33
export const searchParams = {
44
addons: parseAsString.withDefault(""),
55
freq: parseAsStringEnum(["monthly", "annual"]).withDefault("monthly"),
6+
rpc: parseAsStringEnum(["on", "off"]).withDefault("on"),
67
};

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/alerts/components/EngineDeleteAlertModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { DialogDescription } from "@radix-ui/react-dialog";
21
import { Button } from "@/components/ui/button";
32
import {
43
Dialog,
54
DialogContent,
5+
DialogDescription,
66
DialogFooter,
77
DialogHeader,
88
DialogTitle,

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/delete-webhook-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { DialogDescription } from "@radix-ui/react-dialog";
21
import { AlertTriangleIcon } from "lucide-react";
32
import type { WebhookConfig } from "@/api/project/webhook-configs";
43
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
54
import { Button } from "@/components/ui/button";
65
import {
76
Dialog,
87
DialogContent,
8+
DialogDescription,
99
DialogFooter,
1010
DialogHeader,
1111
DialogTitle,

0 commit comments

Comments
 (0)