Skip to content

Commit

Permalink
mark/increment code as consumed
Browse files Browse the repository at this point in the history
  • Loading branch information
kirkas committed Oct 15, 2024
1 parent eccd22c commit 7ac0cde
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 23 deletions.
16 changes: 13 additions & 3 deletions apps/web/pages/api/proofs/discountCode/consume/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,28 @@ import { incrementDiscountCodeUsage } from 'apps/web/src/utils/proofs/discount_c
/*
this endpoint will increment the discount code usage to prevent abuse
*/

type DiscountCodeRequest = {
code: string;
};

async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'method not allowed' });
return res.status(405).json({ error: 'Method not allowed' });
}

try {
const { code } = req.body as DiscountCodeRequest;

if (!code || typeof code !== 'string') {
return res.status(500).json({ error: 'Invalid request' });
}

// 1. get the database model
await incrementDiscountCodeUsage('LA_DINNER_TEST');
await incrementDiscountCodeUsage(code);

return res.status(200).json({ success: true });
} catch (error: unknown) {
console.log({ error });
logger.error('error incrementing the discount code', error);
}
// If error is not an instance of Error, return a generic error message
Expand Down
21 changes: 18 additions & 3 deletions apps/web/pages/api/proofs/discountCode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export type DiscountCodeResponse = {

/*
this endpoint returns whether or a discount code is valid
*/
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
Expand All @@ -30,9 +29,25 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
// 1. get the database model
const discountCodes = await getDiscountCode('LA_DINNER_TEST');

// 2. Validation: Coupon exists
if (!discountCodes || discountCodes.length === 0) {
return res.status(500).json({ error: 'Discount code invalid' });
}

const discountCode = discountCodes[0];

// 2. Sign the payload
// 2.1 Validation: Coupon is expired
if (new Date(discountCode.expires_at) < new Date()) {
return res.status(500).json({ error: 'Discount code invalid' });
}

// 2.2 Validation: Coupon can be redeemed
if (Number(discountCode.usage_count) >= Number(discountCode.usage_limit)) {
return res.status(500).json({ error: 'Discount code invalid' });
}

// 3. Sign the validationData
const couponCodeUuid = stringToHex(discountCode.code, { size: 32 });
const expirationTimeUnix = Math.floor(discountCode.expires_at.getTime() / 1000);

Expand All @@ -43,6 +58,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
expirationTimeUnix,
);

// 4. Return the discount data
const result: DiscountCodeResponse = {
discountValidatorAddress: USERNAME_DISCOUNT_CODE_VALIDATORS[baseSepolia.id],
address: address as Address,
Expand All @@ -51,7 +67,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {

return res.status(200).json(result);
} catch (error: unknown) {
console.log({ error });
logger.error('error getting proofs for discount code', error);
}
// If error is not an instance of Error, return a generic error message
Expand Down
43 changes: 41 additions & 2 deletions apps/web/src/components/Basenames/RegistrationContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import useBaseEnsName from 'apps/web/src/hooks/useBaseEnsName';
import useBasenameChain from 'apps/web/src/hooks/useBasenameChain';
import { Discount, formatBaseEthDomain, isValidDiscount } from 'apps/web/src/utils/usernames';
import { ActionType } from 'libs/base-ui/utils/logEvent';
import { useRouter } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import {
Dispatch,
ReactNode,
Expand All @@ -20,6 +20,7 @@ import {
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useInterval } from 'usehooks-ts';
Expand Down Expand Up @@ -119,8 +120,12 @@ export default function RegistrationProvider({ children }: RegistrationProviderP
address,
});

// Discount code from URL
const searchParams = useSearchParams();
const code = searchParams?.get('code');

// Username discount states
const { data: discounts, loading: loadingDiscounts } = useAggregatedDiscountValidators();
const { data: discounts, loading: loadingDiscounts } = useAggregatedDiscountValidators(code);
const discount = findFirstValidDiscount(discounts);

const allActiveDiscounts = useMemo(
Expand Down Expand Up @@ -210,12 +215,46 @@ export default function RegistrationProvider({ children }: RegistrationProviderP
transactionIsSuccess,
]);

// Move from search to claim
useEffect(() => {
if (selectedName.length) {
setRegistrationStep(RegistrationSteps.Claim);
}
}, [selectedName.length]);

// On registration success with discount code: mark as consumed
const hasRun = useRef(false);

useEffect(() => {
const consumeDiscountCode = async () => {
if (
!hasRun.current &&
registrationStep === RegistrationSteps.Success &&
code &&
discount &&
discount.discount === Discount.DISCOUNT_CODE
) {
hasRun.current = true;
const response = await fetch('/api/proofs/discountCode/consume', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ code }),
});

if (!response.ok) {
throw new Error('Failed to record discount code consumption');
}
}
};

consumeDiscountCode().catch((error) => {
logError(error, 'Error recording discount code consumption');
hasRun.current = false;
});
}, [discount, code, registrationStep, logError]);

// Log user moving through the flow
useEffect(() => {
logEventWithContext(`step_${registrationStep}`, ActionType.change);
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/hooks/useAggregatedDiscountValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function findFirstValidDiscount(
return sortedDiscounts.find((data) => data?.discountKey) ?? undefined;
}

export function useAggregatedDiscountValidators() {
export function useAggregatedDiscountValidators(code: string | null | undefined) {
const { data: activeDiscountValidators, isLoading: loadingActiveDiscounts } =
useActiveDiscountValidators();
const { data: CBIDData, loading: loadingCBIDAttestations } = useCheckCBIDAttestations();
Expand All @@ -50,8 +50,8 @@ export function useAggregatedDiscountValidators() {
const { data: BuildathonData, loading: loadingBuildathon } = useBuildathonAttestations();
const { data: BaseDotEthData, loading: loadingBaseDotEth } = useBaseDotEthAttestations();
const { data: BNSData, loading: loadingBNS } = useBNSAttestations();

const { data: DiscountCodeData, loading: loadingDiscountCode } = useDiscountCodeAttestations();
const { data: DiscountCodeData, loading: loadingDiscountCode } =
useDiscountCodeAttestations(code);

const loadingDiscounts =
loadingCoinbaseAttestations ||
Expand Down
23 changes: 11 additions & 12 deletions apps/web/src/hooks/useAttestations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,23 +490,24 @@ export function useBNSAttestations() {
}

// returns info about Discount Codes attestations
export function useDiscountCodeAttestations() {
export function useDiscountCodeAttestations(code: string | null | undefined) {
const { logError } = useErrors();
const { address } = useAccount();
const [loading, setLoading] = useState(false);
const [discountCodeResponse, setDiscountCodeResponse] = useState<DiscountCodeResponse | null>(
null,
);

const { basenameChain } = useBasenameChain();

useEffect(() => {
async function checkDiscountCode(a: string) {
async function checkDiscountCode(a: string, c: string) {
try {
setLoading(true);
const params = new URLSearchParams();
params.append('address', a);
params.append('chain', basenameChain.id.toString());
params.append('discountCode', 'LA_DINNER'.toString());
params.append('discountCode', c.toString());
const response = await fetch(`/api/proofs/discountCode?${params}`);
const result = (await response.json()) as DiscountCodeResponse;
if (response.ok) {
Expand All @@ -519,31 +520,29 @@ export function useDiscountCodeAttestations() {
}
}

if (address && !IS_EARLY_ACCESS) {
checkDiscountCode(address).catch((error) => {
if (address && !IS_EARLY_ACCESS && !!code) {
checkDiscountCode(address, code).catch((error) => {
logError(error, 'Error checking Discount code');
});
}
}, [address, basenameChain.id, logError]);
}, [address, basenameChain.id, code, logError]);

const signature = discountCodeResponse?.signedMessage;
console.log({ signature, address });
const readContractArgs = useMemo(() => {
if (!address || !signature) {
if (!address || !signature || !code) {
return {};
}

console.log('READ');
return {
address: discountCodeResponse?.discountValidatorAddress,
abi: AttestationValidatorABI,
functionName: 'isValidDiscountRegistration',
args: [address, signature],
};
}, [address, discountCodeResponse?.discountValidatorAddress, signature]);
}, [address, code, discountCodeResponse?.discountValidatorAddress, signature]);

const { data: isValid, isLoading, error } = useReadContract(readContractArgs);

const { data: isValid, isLoading, error, isError } = useReadContract(readContractArgs);
console.log({ isValid, isLoading, error, isError });
if (isValid && discountCodeResponse && address && signature) {
return {
data: {
Expand Down

0 comments on commit 7ac0cde

Please sign in to comment.