Skip to content

Commit a75c6dc

Browse files
authored
Merge pull request #398 from hypercerts-org/feat/feature_flags
Introduce feature flags on view page
2 parents c4430e7 + 5275237 commit a75c6dc

File tree

9 files changed

+237
-14
lines changed

9 files changed

+237
-14
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Notify Discord on Bug Label
2+
3+
on:
4+
issues:
5+
types: [labeled]
6+
7+
jobs:
8+
notify_bug:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Send Bug Notification to Discord
12+
if: github.event.label.name == 'bug'
13+
run: |
14+
curl -H "Content-Type: application/json" \
15+
-X POST \
16+
-d "{
17+
\"content\": \"🐞 **Bug Report Created!**\n**Title:** ${{ github.event.issue.title }}\n**Link:** ${{ github.event.issue.html_url }}\"
18+
}" \
19+
${{ secrets.DISCORD_WEBHOOK_URL }}

app/hypercerts/[hypercertId]/page.tsx

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,26 @@ import { Metadata, ResolvingMetadata } from "next";
22

33
import { getHypercert } from "@/hypercerts/getHypercert";
44

5-
import HypercertDetails from "@/components/hypercert/hypercert-details";
6-
import EvaluateButton from "@/components/hypercert/evaluate-button";
5+
import CreatorFeedButton from "@/components/creator-feed/creator-feed-button";
6+
import CreatorFeeds from "@/components/creator-feed/creator-feeds";
77
import { CurrencyButtons } from "@/components/currency-buttons";
8-
import { ListForSaleButton } from "@/components/marketplace/list-for-sale-button";
8+
import HypercertEvaluations from "@/components/evaluations/hypercert-evaluations";
99
import ErrorState from "@/components/global/error-state";
10+
import EvaluateButton from "@/components/hypercert/evaluate-button";
11+
import HypercertDetails from "@/components/hypercert/hypercert-details";
1012
import HypercertListings from "@/components/marketplace/hypercert-listings";
11-
import HypercertEvaluations from "@/components/evaluations/hypercert-evaluations";
12-
import CreatorFeedButton from "@/components/creator-feed/creator-feed-button";
13-
import CreatorFeeds from "@/components/creator-feed/creator-feeds";
13+
import { ListForSaleButton } from "@/components/marketplace/list-for-sale-button";
1414
import {
1515
Accordion,
1616
AccordionContent,
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 };
@@ -46,19 +51,35 @@ export async function generateMetadata(
4651
export default async function HypercertPage({ params, searchParams }: Props) {
4752
const { hypercertId } = params;
4853

49-
const [hypercert] = await Promise.all([getHypercert(hypercertId)]);
54+
const hypercert = await 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 getDefaultAccordionItems = () => {
66+
if (isMarketplaceListingsEnabledOnChain) return ["marketplace-listings"];
67+
if (isEvaluationsEnabledOnChain) return ["evaluations"];
68+
if (isCreatorFeedEnabledOnChain) return ["creator-feed"];
69+
return [];
70+
};
71+
72+
const defaultAccordionItems = getDefaultAccordionItems();
73+
5774
return (
5875
<main className="flex flex-col p-8 md:px-24 md:pt-14 pb-24 space-y-4 flex-1">
5976
<HypercertDetails hypercertId={hypercertId} />
60-
<Accordion type="multiple" defaultValue={["item-3"]} className="w-full">
61-
<AccordionItem value="item-1">
77+
<Accordion
78+
type="multiple"
79+
defaultValue={defaultAccordionItems}
80+
className="w-full"
81+
>
82+
<AccordionItem value="creator-feed">
6283
{/* creator feed */}
6384
<AccordionTrigger className="uppercase text-sm text-slate-500 font-medium tracking-wider">
6485
CREATOR&apos;S FEED
@@ -68,14 +89,15 @@ export default async function HypercertPage({ params, searchParams }: Props) {
6889
<CreatorFeedButton
6990
hypercertId={hypercertId}
7091
creatorAddress={hypercert.creator_address!}
92+
disabledForChain={!isCreatorFeedEnabledOnChain}
7193
/>
7294
</div>
7395
<CreatorFeeds hypercertId={hypercertId} />
7496
</AccordionContent>
7597
</AccordionItem>
7698

7799
{/* evaluations */}
78-
<AccordionItem value="item-2">
100+
<AccordionItem value="evaluations">
79101
<AccordionTrigger className="uppercase text-sm text-slate-500 font-medium tracking-wider">
80102
Evaluations
81103
</AccordionTrigger>
@@ -86,20 +108,24 @@ export default async function HypercertPage({ params, searchParams }: Props) {
86108
<HypercertEvaluations
87109
hypercertId={hypercertId}
88110
searchParams={searchParams}
111+
disabledForChain={!isEvaluationsEnabledOnChain}
89112
/>
90113
</AccordionContent>
91114
</AccordionItem>
92115

93116
{/* marketplace */}
94-
<AccordionItem value="item-3">
117+
<AccordionItem value="marketplace-listings">
95118
<AccordionTrigger className="uppercase text-sm text-slate-500 font-medium tracking-wider">
96119
Marketplace
97120
</AccordionTrigger>
98121
<AccordionContent>
99122
<div className="flex justify-end mb-4">
100123
<div className="flex gap-2">
101124
<CurrencyButtons />
102-
<ListForSaleButton hypercert={hypercert} />
125+
<ListForSaleButton
126+
hypercert={hypercert}
127+
disabledForChain={!isMarketplaceListingsEnabledOnChain}
128+
/>
103129
</div>
104130
</div>
105131
<HypercertListings

components/creator-feed/creator-feed-button.tsx

Lines changed: 19 additions & 0 deletions
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.";

components/evaluations/hypercert-evaluations.tsx

Lines changed: 2 additions & 0 deletions
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));

components/hypercert/evaluate-button.tsx

Lines changed: 19 additions & 0 deletions
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.";

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

Lines changed: 24 additions & 1 deletion
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,

flags/chain-actions-flag.ts

Lines changed: 71 additions & 0 deletions
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+
});

package.json

Lines changed: 2 additions & 1 deletion
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",

0 commit comments

Comments
 (0)