Skip to content

Commit e1189c8

Browse files
committed
feat: add whitelabel functionality
1 parent 95b6735 commit e1189c8

File tree

13 files changed

+9480
-9375
lines changed

13 files changed

+9480
-9375
lines changed

config/features.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import colors from 'tailwindcss/colors';
2+
import {
3+
mainnet,
4+
optimism,
5+
optimismSepolia,
6+
sepolia,
7+
zksync,
8+
zksyncSepoliaTestnet,
9+
} from 'wagmi/chains';
10+
11+
type WHITELABEL_ENV = 'OPTIMISM' | 'ZK_SYNC';
12+
13+
const _WHITELABEL_ENV = process.env.NEXT_PUBLIC_WHITELABEL_ENV;
14+
15+
if (!_WHITELABEL_ENV) {
16+
throw new Error('NEXT_PUBLIC_WHITELABEL_ENV is not set');
17+
}
18+
19+
if (!(_WHITELABEL_ENV === 'OPTIMISM' || _WHITELABEL_ENV === 'ZK_SYNC')) {
20+
throw new Error('NEXT_PUBLIC_WHITELABEL_ENV is not set to a valid value');
21+
}
22+
23+
export const WHITELABEL_ENV = _WHITELABEL_ENV;
24+
25+
interface Features {
26+
DELEGATION_REQUIRED: boolean;
27+
DELEGATION_ENABLED: boolean;
28+
}
29+
30+
const featureMatrix: Record<WHITELABEL_ENV, Features> = {
31+
OPTIMISM: {
32+
DELEGATION_REQUIRED: false,
33+
DELEGATION_ENABLED: false,
34+
},
35+
ZK_SYNC: {
36+
DELEGATION_REQUIRED: true,
37+
DELEGATION_ENABLED: true,
38+
},
39+
};
40+
41+
export const FEATURES = featureMatrix[_WHITELABEL_ENV];
42+
43+
const projectId = process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID;
44+
45+
if (!projectId) {
46+
throw new Error('WalletConnect Project ID is not defined');
47+
}
48+
49+
export const getChainConfig = () => {
50+
switch (WHITELABEL_ENV) {
51+
case 'OPTIMISM':
52+
return {
53+
appName: 'OP Claim Tool',
54+
chains: [mainnet, optimism, optimismSepolia, sepolia],
55+
};
56+
case 'ZK_SYNC':
57+
return {
58+
appName: 'ZKsync Claim Tool',
59+
chains: [mainnet, zksync, zksyncSepoliaTestnet],
60+
};
61+
}
62+
};
63+
64+
interface WhitelabelThemeColors {
65+
bgClaimcardHeader: string;
66+
primaryAction: string;
67+
primaryActionButtonBg: string;
68+
}
69+
70+
export const getWhitelabelThemeColors = (): WhitelabelThemeColors => {
71+
switch (WHITELABEL_ENV) {
72+
case 'OPTIMISM':
73+
return {
74+
bgClaimcardHeader: colors.red[200],
75+
primaryAction: colors.red[500],
76+
primaryActionButtonBg: colors.red[900],
77+
};
78+
case 'ZK_SYNC':
79+
return {
80+
bgClaimcardHeader: colors.blue[200],
81+
primaryAction: colors.blue[500],
82+
primaryActionButtonBg: colors.blue[900],
83+
};
84+
}
85+
};

public/zksync_logo_dark.svg

Lines changed: 1 addition & 0 deletions
Loading

src/app/client-layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function ClientLayout({
2020
return (
2121
<div className="min-h-screen flex flex-col">
2222
<Header />
23-
<main className="flex-grow py-20">
23+
<main className="flex-grow py-20 px-3">
2424
<div className="mx-auto max-w-5xl">
2525
{isConnecting || isSessionLoading ? (
2626
<div className="flex justify-center items-center h-[calc(100vh-5rem)]">

src/components/ClaimButton.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ const ClaimButton = ({ grantIds }: { grantIds: string[] }) => {
2121

2222
return (
2323
<>
24-
<Button className="p-6" onClick={handleClick} variant="destructive">
24+
<Button
25+
className="p-6 text-white bg-primaryActionButtonBg hover:bg-initial"
26+
onClick={handleClick}
27+
>
2528
{isConnected ? 'Claim now' : 'Connect to claim'}
2629
</Button>
2730
<ClaimDialog isOpen={showClaimDialog} setOpen={setShowClaimDialog} />

src/components/Footer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import RaidGuild from './common/images/RaidGuild';
77

88
const Footer = () => {
99
return (
10-
<div className="h-24 border-t border-neutral-200">
10+
<div className="h-24 border-t border-neutral-200 px-3">
1111
<div className="h-full mx-auto max-w-5xl flex items-center justify-between">
1212
<div className="flex items-center gap-7">
1313
<Link href="https://gitcoin.co/" target="_blank">

src/components/Header.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,30 @@ import ConnectButton from '@/components/auth/ConnectButton';
44
import { RiArrowRightUpLine } from '@remixicon/react';
55
import Link from 'next/link';
66
import { usePathname } from 'next/navigation';
7+
import { FEATURES } from '../../config/features';
78
import Logo from './common/images/Logo';
89
import { Button } from './ui/button';
910

11+
const { DELEGATION_ENABLED } = FEATURES;
12+
1013
const links = [
1114
{
1215
title: 'Grants',
1316
href: '/grants',
1417
isExternal: false,
1518
},
16-
{
19+
DELEGATION_ENABLED && {
1720
title: 'Delegates',
1821
href: 'https://vote.optimism.io/delegates',
1922
isExternal: true,
2023
},
21-
];
24+
].filter((x) => !!x);
2225

2326
const Header = () => {
2427
const pathname = usePathname();
2528

2629
return (
27-
<div className="h-20 bg-white border-b border-neutral-200">
30+
<div className="h-20 bg-white border-b border-neutral-200 px-3">
2831
<div className="h-full mx-auto max-w-5xl flex items-center justify-between">
2932
<Link href="/">
3033
<Logo />

src/components/common/ClaimCard.tsx

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,22 @@ import {
2626
FormMessage,
2727
} from '../ui/form';
2828
import { Input } from '../ui/input';
29-
import { Switch } from '../ui/switch';
3029
import SuccessCheckmark from './images/SuccessCheckmark';
3130

31+
import { FEATURES } from '../../../config/features';
32+
33+
const { DELEGATION_REQUIRED } = FEATURES;
34+
3235
const FormSchema = z
3336
.object({
3437
delegateAddress: z.string().optional(),
35-
enableDelegate: z.boolean(),
38+
isDelegationRequired: z.boolean(),
3639
})
37-
.superRefine(({ enableDelegate, delegateAddress }, refinementCtx) => {
38-
console.log('Running super refine', { enableDelegate, delegateAddress });
39-
if (enableDelegate && !/^0x[a-fA-F0-9]{40}$/.test(delegateAddress || '')) {
40+
.superRefine(({ isDelegationRequired, delegateAddress }, refinementCtx) => {
41+
if (
42+
isDelegationRequired &&
43+
!/^0x[a-fA-F0-9]{40}$/.test(delegateAddress || '')
44+
) {
4045
return refinementCtx.addIssue({
4146
code: z.ZodIssueCode.custom,
4247
message: 'Invalid Ethereum address',
@@ -62,7 +67,7 @@ export default function ClaimCard({ grant }: { grant: Grant }) {
6267
resolver: zodResolver(FormSchema),
6368
defaultValues: {
6469
delegateAddress: '',
65-
enableDelegate: true,
70+
isDelegationRequired: DELEGATION_REQUIRED,
6671
},
6772
mode: 'onChange',
6873
reValidateMode: 'onChange',
@@ -79,7 +84,7 @@ export default function ClaimCard({ grant }: { grant: Grant }) {
7984
}
8085
try {
8186
const receipt = await claimAndDelegate({
82-
delegateeAddress: data.enableDelegate
87+
delegateeAddress: data.isDelegationRequired
8388
? (data.delegateAddress as `0x${string}`)
8489
: undefined,
8590
claim,
@@ -115,28 +120,15 @@ export default function ClaimCard({ grant }: { grant: Grant }) {
115120
router.push('/claim');
116121
}
117122

118-
const enableDelegate = form.watch('enableDelegate');
123+
const isDelegationRequired = form.watch('isDelegationRequired');
119124

120125
return (
121126
<Card className="bg-transparent border border-neutral-300 shadow-none p-10 w-[634px]">
122127
{step === 'form' && (
123128
<Form {...form}>
124129
<form onSubmit={form.handleSubmit(onSubmit)}>
125-
<CardContent className="space-y-6">
126-
<div className="flex justify-between">
127-
<p className="text-lg font-semibold">
128-
Opt-in to delegate the awarded token
129-
</p>
130-
<Switch
131-
checked={enableDelegate}
132-
onCheckedChange={(newEnableDelegate) => {
133-
form.setValue('enableDelegate', newEnableDelegate);
134-
form.trigger('enableDelegate');
135-
}}
136-
color="red"
137-
/>
138-
</div>
139-
{enableDelegate ? (
130+
{DELEGATION_REQUIRED && (
131+
<CardContent className="space-y-6">
140132
<>
141133
<div className="grid w-full max-w-sm items-center gap-3">
142134
<FormField
@@ -173,17 +165,15 @@ export default function ClaimCard({ grant }: { grant: Grant }) {
173165
delegate the token to yourself.
174166
</p>
175167
</>
176-
) : (
177-
<></>
178-
)}
179-
</CardContent>
168+
</CardContent>
169+
)}
180170
<CardFooter className="py-0">
181171
<Button
182172
type="submit"
183-
variant="destructive"
173+
className="bg-primaryActionButtonBg hover:bg-initial"
184174
disabled={!form.formState.isValid || isPending}
185175
>
186-
{enableDelegate ? 'Delegate' : 'Claim'}
176+
{isDelegationRequired ? 'Delegate and claim' : 'Claim'}
187177
</Button>
188178
</CardFooter>
189179
</form>

src/components/common/GrantCard.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,14 @@ const GrantCard = ({
5555
isClaimDialogOpen &&
5656
isConnected &&
5757
!isCorrectChain && (
58-
<div className="flex items-center justify-between bg-red-200 px-10 py-2 rounded-t-lg">
58+
<div className="flex items-center justify-between bg-bgClaimcardHeader px-10 py-2 rounded-t-lg">
5959
<p className="text-sm">
6060
You are eligible to claim this grant on <b>{chain.name}</b>
6161
</p>
6262
</div>
6363
)}
6464
{grant.currentUserCanClaim && !isClaimDialogOpen && isConnected && (
65-
<div className="flex items-center justify-between bg-red-200 px-10 py-2 rounded-t-lg">
65+
<div className="flex items-center justify-between bg-bgClaimcardHeader px-10 py-2 rounded-t-lg">
6666
<p className="text-sm">
6767
You are eligible to claim this grant
6868
{!isCorrectChain && (
@@ -75,7 +75,7 @@ const GrantCard = ({
7575
{isCorrectChain ? (
7676
<Button
7777
variant="link"
78-
className="text-red-500 font-semibold hover:no-underline p-0"
78+
className="text-primaryAction font-semibold hover:no-underline p-0"
7979
onClick={handleClaim}
8080
>
8181
Claim now
@@ -88,7 +88,7 @@ const GrantCard = ({
8888
<Button
8989
disabled
9090
variant="link"
91-
className="text-red-500 font-semibold hover:no-underline p-0 cursor-not-allowed"
91+
className="text-primaryAction font-semibold hover:no-underline p-0 cursor-not-allowed"
9292
>
9393
Claim now
9494
</Button>
@@ -145,25 +145,29 @@ const GrantCard = ({
145145
/>
146146
</Link>
147147
</div>
148-
<Separator orientation="vertical" />
149-
<div className="flex items-center gap-2">
150-
<p>Latest claim: </p>
151-
<a
152-
target="_blank"
153-
className="group flex items-center font-semibold text-black"
154-
href={generateBlockExplorerUrl(
155-
grant.chainId,
156-
grant.latestClaimHash,
157-
)}
158-
rel="noreferrer"
159-
>
160-
{truncate(grant.latestClaimHash, 11)}{' '}
161-
<RiArrowRightUpLine
162-
className="ml-1 text-neutral-500 w-4 h-4 opacity-70 transition-transform duration-300 ease-in-out group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:opacity-100"
163-
aria-hidden="true"
164-
/>
165-
</a>
166-
</div>
148+
{grant.latestClaimHash && (
149+
<>
150+
<Separator orientation="vertical" />
151+
<div className="flex items-center gap-2">
152+
<p>Latest claim: </p>
153+
<a
154+
target="_blank"
155+
className="group flex items-center font-semibold text-black"
156+
href={generateBlockExplorerUrl(
157+
grant.chainId,
158+
grant.latestClaimHash,
159+
)}
160+
rel="noreferrer"
161+
>
162+
{truncate(grant.latestClaimHash, 11)}{' '}
163+
<RiArrowRightUpLine
164+
className="ml-1 text-neutral-500 w-4 h-4 opacity-70 transition-transform duration-300 ease-in-out group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:opacity-100"
165+
aria-hidden="true"
166+
/>
167+
</a>
168+
</div>
169+
</>
170+
)}
167171
</div>
168172
</div>
169173
</div>

src/components/common/images/Logo.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import Image from 'next/image';
2+
import { WHITELABEL_ENV } from '../../../../config/features';
23
import OpLogo from '../../../../public/op-logo.svg';
4+
import ZkSyncLogo from '../../../../public/zksync_logo_dark.svg';
35
const Logo = () => {
4-
return <Image src={OpLogo} alt="OP Logo" />;
6+
switch (WHITELABEL_ENV) {
7+
case 'OPTIMISM':
8+
return <Image src={OpLogo} alt="OP Logo" />;
9+
case 'ZK_SYNC':
10+
return <Image src={ZkSyncLogo} alt="ZKSync Logo" height={40} />;
11+
default:
12+
throw new Error('Invalid WHITE_LABEL_ENV');
13+
}
514
};
615

716
export default Logo;

src/context/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import '@rainbow-me/rainbowkit/styles.css';
55
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
66
import type { ReactNode } from 'react';
77
import { WagmiProvider } from 'wagmi';
8-
import { mainnet, optimism, optimismSepolia, sepolia } from 'wagmi/chains';
8+
import { getChainConfig } from '../../config/features';
99
import { GrantsProvider } from './GrantsContext';
1010

1111
const projectId = process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID;
@@ -17,9 +17,8 @@ if (!projectId) {
1717
const queryClient = new QueryClient();
1818

1919
const config = getDefaultConfig({
20-
appName: 'OP Claim Tool',
20+
...getChainConfig(),
2121
projectId,
22-
chains: [mainnet, optimism, optimismSepolia, sepolia],
2322
ssr: true,
2423
});
2524

0 commit comments

Comments
 (0)