Skip to content

Commit 9222766

Browse files
committed
feat(flags): disable attestation and marketplace buttons
As we don't support all features of the hypercerts protocol on all chains, we're introducing feature flags to programmatically disable features based on the chain the user is connected to. Based on the connected chain, the button for the creator feed, evaluations and creating marketplace listing is disabled. The Vercel flags SDK supports feature flags based on server side rendereing. We pass the outcome of the flag to the client-side components i.e. buttons.
1 parent 9b25c9e commit 9222766

File tree

8 files changed

+207
-4
lines changed

8 files changed

+207
-4
lines changed

Diff for: app/hypercerts/[hypercertId]/page.tsx

+27-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ import {
1717
AccordionItem,
1818
AccordionTrigger,
1919
} from "@/components/ui/accordion";
20+
import {
21+
creatorFeedFlag,
22+
evaluationsFlag,
23+
marketplaceListingsFlag,
24+
} from "@/flags/chain-actions-flag";
2025

2126
type Props = {
2227
params: { hypercertId: string };
@@ -47,17 +52,32 @@ export default async function HypercertPage({ params, searchParams }: Props) {
4752
const { hypercertId } = params;
4853

4954
const [hypercert] = await Promise.all([getHypercert(hypercertId)]);
55+
const isCreatorFeedEnabledOnChain = await creatorFeedFlag();
56+
const isEvaluationsEnabledOnChain = await evaluationsFlag();
57+
const isMarketplaceListingsEnabledOnChain = await marketplaceListingsFlag();
5058

5159
if (!hypercert) {
5260
return (
5361
<ErrorState message="Hypercert not found" hypercertId={hypercertId} />
5462
);
5563
}
5664

65+
const defaultAccordionItems = isMarketplaceListingsEnabledOnChain
66+
? ["item-3"]
67+
: isEvaluationsEnabledOnChain
68+
? ["item-2"]
69+
: isCreatorFeedEnabledOnChain
70+
? ["item-1"]
71+
: [];
72+
5773
return (
5874
<main className="flex flex-col p-8 md:px-24 md:pt-14 pb-24 space-y-4 flex-1">
5975
<HypercertDetails hypercertId={hypercertId} />
60-
<Accordion type="multiple" defaultValue={["item-3"]} className="w-full">
76+
<Accordion
77+
type="multiple"
78+
defaultValue={defaultAccordionItems}
79+
className="w-full"
80+
>
6181
<AccordionItem value="item-1">
6282
{/* creator feed */}
6383
<AccordionTrigger className="uppercase text-sm text-slate-500 font-medium tracking-wider">
@@ -68,6 +88,7 @@ export default async function HypercertPage({ params, searchParams }: Props) {
6888
<CreatorFeedButton
6989
hypercertId={hypercertId}
7090
creatorAddress={hypercert.creator_address!}
91+
disabledForChain={!isCreatorFeedEnabledOnChain}
7192
/>
7293
</div>
7394
<CreatorFeeds hypercertId={hypercertId} />
@@ -86,6 +107,7 @@ export default async function HypercertPage({ params, searchParams }: Props) {
86107
<HypercertEvaluations
87108
hypercertId={hypercertId}
88109
searchParams={searchParams}
110+
disabledForChain={!isEvaluationsEnabledOnChain}
89111
/>
90112
</AccordionContent>
91113
</AccordionItem>
@@ -99,7 +121,10 @@ export default async function HypercertPage({ params, searchParams }: Props) {
99121
<div className="flex justify-end mb-4">
100122
<div className="flex gap-2">
101123
<CurrencyButtons />
102-
<ListForSaleButton hypercert={hypercert} />
124+
<ListForSaleButton
125+
hypercert={hypercert}
126+
disabledForChain={!isMarketplaceListingsEnabledOnChain}
127+
/>
103128
</div>
104129
</div>
105130
<HypercertListings

Diff for: components/creator-feed/creator-feed-button.tsx

+19
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,33 @@ import { getAddress } from "viem";
1717
export default function CreatorFeedButton({
1818
hypercertId,
1919
creatorAddress,
20+
disabledForChain = false,
2021
}: {
2122
hypercertId: string;
2223
creatorAddress: string;
24+
disabledForChain?: boolean;
2325
}) {
2426
const { isConnected, address } = useAccount();
2527

2628
const { chainId } = useAccount();
2729

30+
if (disabledForChain) {
31+
return (
32+
<TooltipProvider>
33+
<Tooltip>
34+
<TooltipTrigger asChild>
35+
<div>
36+
<Button disabled={true}>Submit Addtional Information</Button>
37+
</div>
38+
</TooltipTrigger>
39+
<TooltipContent>
40+
This feature is disabled on the connected chain.
41+
</TooltipContent>
42+
</Tooltip>
43+
</TooltipProvider>
44+
);
45+
}
46+
2847
const getTooltipMessage = () => {
2948
if (!isConnected) {
3049
return "Connect your wallet to access this feature.";

Diff for: components/evaluations/hypercert-evaluations.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ const getAttestationData = cache(async (params: GetAttestationsParams) => {
2626
export default async function HypercertEvaluations({
2727
hypercertId,
2828
searchParams,
29+
disabledForChain = false,
2930
}: {
3031
hypercertId: string;
3132
searchParams: Record<string, string>;
33+
disabledForChain?: boolean;
3234
}) {
3335
const currentPage = Number(searchParams?.evaluations) || 1;
3436
const offset = Math.max(0, LISTINGS_PER_PAGE * (currentPage - 1));

Diff for: components/hypercert/evaluate-button.tsx

+19
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import { useAccount } from "wagmi";
1717

1818
export default function EvaluateButton({
1919
hypercertId,
20+
disabledForChain = false,
2021
}: {
2122
hypercertId: string;
23+
disabledForChain?: boolean;
2224
}) {
2325
const { isConnected, address } = useAccount();
2426
const [evaluator, setEvaluator] = useState<TrustedAttestor>();
@@ -38,6 +40,23 @@ export default function EvaluateButton({
3840
}
3941
}, [address]);
4042

43+
if (disabledForChain) {
44+
return (
45+
<TooltipProvider>
46+
<Tooltip>
47+
<TooltipTrigger asChild>
48+
<div>
49+
<Button disabled={true}>Evaluate</Button>
50+
</div>
51+
</TooltipTrigger>
52+
<TooltipContent>
53+
This feature is disabled on the connected chain.
54+
</TooltipContent>
55+
</Tooltip>
56+
</TooltipProvider>
57+
);
58+
}
59+
4160
const getTooltipMessage = () => {
4261
if (!isConnected) {
4362
return "Connect your wallet to access this feature.";

Diff for: components/marketplace/list-for-sale-button.tsx

+24-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ import { getAddress } from "viem";
1919

2020
import { isChainIdSupported } from "@/lib/isChainIdSupported";
2121

22-
export function ListForSaleButton({ hypercert }: { hypercert: HypercertFull }) {
22+
export function ListForSaleButton({
23+
hypercert,
24+
disabledForChain = false,
25+
}: {
26+
hypercert: HypercertFull;
27+
disabledForChain?: boolean;
28+
}) {
2329
const { isConnected, address } = useAccount();
2430
const { client } = useHypercertClient();
2531

@@ -54,6 +60,23 @@ export function ListForSaleButton({ hypercert }: { hypercert: HypercertFull }) {
5460

5561
const [isOpen, setIsOpen] = useState(false);
5662

63+
if (disabledForChain) {
64+
return (
65+
<TooltipProvider>
66+
<Tooltip>
67+
<TooltipTrigger asChild>
68+
<div>
69+
<Button disabled>List for sale</Button>
70+
</div>
71+
</TooltipTrigger>
72+
<TooltipContent>
73+
This feature is disabled on the connected chain.
74+
</TooltipContent>
75+
</Tooltip>
76+
</TooltipProvider>
77+
);
78+
}
79+
5780
const fractions = hypercert.fractions?.data || [];
5881
const fractionsOwnedByUser = fractions.filter(
5982
(fraction) => fraction.owner_address === address,

Diff for: flags/chain-actions-flag.ts

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { flag, dedupe } from "@vercel/flags/next";
2+
import type { ReadonlyRequestCookies } from "@vercel/flags";
3+
4+
interface Entities {
5+
user?: { chainId: number | undefined };
6+
}
7+
8+
const creatorFeedDisabledChains = [314, 314159];
9+
const evaluationsDisabledChains = [314, 314159];
10+
const marketplaceListingsDisabledChains = [314, 314159];
11+
12+
const identify = dedupe(
13+
({ cookies }: { cookies: ReadonlyRequestCookies }): Entities => {
14+
const wagmiStore = cookies.get("wagmi.store")?.value;
15+
16+
const chainId = getChainIdFromWagmiCookie(wagmiStore);
17+
18+
return { user: { chainId } };
19+
},
20+
);
21+
22+
const getChainIdFromWagmiCookie = (
23+
cookieValue: string | undefined,
24+
): number | undefined => {
25+
if (!cookieValue) {
26+
console.debug("No wagmiStore cookie found");
27+
return undefined;
28+
}
29+
30+
try {
31+
const parsedCookie = JSON.parse(cookieValue);
32+
const chainId = parsedCookie?.state?.chainId;
33+
34+
if (typeof chainId !== "number") {
35+
console.debug("Invalid chainId format in wagmiStore cookie");
36+
return undefined;
37+
}
38+
39+
return chainId;
40+
} catch (error) {
41+
console.error("Error parsing wagmiStore cookie:", error);
42+
return undefined;
43+
}
44+
};
45+
46+
export const creatorFeedFlag = flag<boolean>({
47+
key: "chain-actions-creator-feed",
48+
identify,
49+
defaultValue: true,
50+
decide({ entities }) {
51+
return !creatorFeedDisabledChains.includes(entities?.user?.chainId);
52+
},
53+
});
54+
55+
export const evaluationsFlag = flag<boolean>({
56+
key: "chain-actions-evaluations",
57+
identify,
58+
defaultValue: true,
59+
decide({ entities }) {
60+
return !evaluationsDisabledChains.includes(entities?.user?.chainId);
61+
},
62+
});
63+
64+
export const marketplaceListingsFlag = flag<boolean>({
65+
key: "chain-actions-marketplace-listings",
66+
identify,
67+
defaultValue: true,
68+
decide({ entities }) {
69+
return !marketplaceListingsDisabledChains.includes(entities?.user?.chainId);
70+
},
71+
});

Diff for: package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@
4747
"@tanstack/react-table": "^8.17.3",
4848
"@types/lodash": "^4.17.5",
4949
"@types/validator": "^13.12.2",
50-
"@wagmi/core": "^2.16.3",
5150
"@vercel/analytics": "^1.4.1",
51+
"@vercel/flags": "^3.1.0",
52+
"@wagmi/core": "^2.16.3",
5253
"@yaireo/tagify": "^4.21.1",
5354
"class-variance-authority": "^0.7.0",
5455
"clsx": "^2.1.1",

Diff for: pnpm-lock.yaml

+43
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)