Skip to content

Commit

Permalink
Introduce POAP Claiming
Browse files Browse the repository at this point in the history
  • Loading branch information
lucemans committed Jan 27, 2024
1 parent b0af8b2 commit 1e5d847
Show file tree
Hide file tree
Showing 16 changed files with 552 additions and 25 deletions.
1 change: 1 addition & 0 deletions app/[slug]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import '../styles/frensday.css';
import '../styles/ethdenver24.css';
import '../styles/generic.css';

import React from 'react';
Expand Down
14 changes: 12 additions & 2 deletions app/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import { useWarpcast } from '../../hooks/useWarpcast';

const theme2Class = {
frensday2023: 'theme-frensday2023',
ethdenver2024: 'theme-ethdenver24',
};

const theme2Color = {
frensday2023: '#2A2244',
ethdenver2024: '#844AFF',
};

export default async function ({
Expand Down Expand Up @@ -84,15 +90,15 @@ export default async function ({
Site by{' '}
<Link
href="https://ens.domains/?utm_source=ens-page&utm_campaign=footer"
className="text-ens-light-blue"
className="link"
target="_blank"
>
ENS
</Link>{' '}
&{' '}
<Link
href="https://v3x.company/?utm_source=ens-page&utm_campaign=footer"
className="text-ens-light-blue"
className="link"
target="_blank"
>
V3X
Expand All @@ -107,11 +113,14 @@ export default async function ({

export async function generateMetadata({
params: { slug },
searchParams: { event, iykRef },
}: {
params: { slug: string };
searchParams: { event?: string; iykRef?: string };
}) {
const raw_name = slug;
const name = ens_normalize(raw_name.toLowerCase());
const theme_color = theme2Color[event] || '#fff';

if (raw_name.toLowerCase() !== name) {
throw new Error('Invalid ENS name');
Expand All @@ -124,5 +133,6 @@ export async function generateMetadata({
description:
data.records?.description || `View ${data.name}'s ENS Page`,
icons: data.avatar,
themeColor: theme_color,
} as Metadata;
}
4 changes: 2 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import './global.css';
import React from 'react';

export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
title: 'ENS Page',
description: 'Created with <3',
};

export default function RootLayout({
Expand Down
22 changes: 22 additions & 0 deletions app/styles/ethdenver24.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
html:has(.theme-ethdenver24) {
body, html {
@apply bg-gradient-to-b from-[#844AFF] to-[#844AFF] text-white;
}
/* body:before {
content: '';
@apply fixed inset-0 -z-10 brightness-75;
background: url('/ethdenver24/bg.svg') no-repeat center center;
} */
.btn {
@apply bg-[#FF65AF] hover:bg-[#FF65AF] active:bg-[#FF65AF] text-white;
}
.card-bg {
@apply bg-[#F8F8F9] shadow-lg;
}
.card-body {
@apply text-black;
}
.link {
@apply text-[#FF65AF];
}
}
6 changes: 6 additions & 0 deletions app/styles/frensday.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@ html:has(.theme-frensday2023) {
.btn {
@apply bg-[#7116EB] hover:bg-[#781dff] active:bg-[#5e0fc8];
}
.card-bg {
@apply bg-[#14032C];
}
.link {
@apply text-ens-light-blue;
}
}
9 changes: 9 additions & 0 deletions app/styles/generic.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,13 @@ html:has(.theme-generic) {
.btn-url {
@apply bg-ens-light-purple-primary hover:bg-ens-light-purple-bright active:bg-ens-light-purple-active;
}
.card-bg {
@apply bg-ens-light-background-primary border border-ens-light-border shadow-xl;
}
.card-body {
@apply text-black;
}
.link {
@apply text-ens-light-blue;
}
}
36 changes: 26 additions & 10 deletions components/POAPModal/POAPModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { IYKRefResponse as IYKReferenceResponse } from '../../hooks/useIYKRef';
import { POAPMetadata } from '../../hooks/usePOAPData';
import { Creeper } from './Creeper';
import { SHOW_POAP_ANYWAYS } from './settings';
import { ClaimError } from './stages/ClaimError';
import { ExpiredPOAP } from './stages/Expired';
import { MintToProfile } from './stages/MintToProfile';
import { NameInput } from './stages/NameInput';
Expand All @@ -20,6 +21,7 @@ const PENDING_APPROVAL = 'pending-approval';
const MINT_TO = 'mint-to';
const NAME_INPUT = 'name-input';
const EXPIRED_STATE = 'expired';
const ERROR_STATE = 'error-state';

const event_names = {
frensday2023: 'frENSday 2023',
Expand All @@ -40,6 +42,7 @@ export const POAPModal: FC<{
// eslint-disable-next-line no-undef
localStorage?.getItem(STORAGE_NAME_KEY) || ''
);
const [mintToProfileError, setMintToProfileError] = useState<unknown>();

const [poapEvent] = data.poapEvents;
const expiry_data = metadata.attributes.find(
Expand All @@ -66,27 +69,28 @@ export const POAPModal: FC<{

let state = '';

if (pendingApproval) {
state = PENDING_APPROVAL;
if (mintToProfileError) {
state = ERROR_STATE;
} else {
if (poapEvent.status == 'expired') {
state = EXPIRED_STATE;
if (pendingApproval) {
state = PENDING_APPROVAL;
} else {
state = mintToProfile ? MINT_TO : NAME_INPUT;
if (poapEvent.status == 'expired') {
state = EXPIRED_STATE;
} else {
state = mintToProfile ? MINT_TO : NAME_INPUT;
}
}
}

return (
<div className="fixed bottom-0 inset-x-0 px-2 pb-4">
<div className="w-full max-w-md3 mx-auto">
<div className="p-6 gap-4 text-center relative flex flex-col items-center">
<div className="p-6 gap-4 text-center relative flex flex-col items-center card-body">
{event == 'frensday2023' && <Creeper />}
<div
className={clsx(
'absolute inset-x-0 bottom-0 top-0 rounded-3xl -z-10',
event == 'frensday2023'
? 'bg-[#14032C]'
: 'bg-ens-light-background-primary border border-ens-light-border shadow-xl'
'absolute inset-x-0 bottom-0 top-0 rounded-3xl -z-10 card-bg'
)}
></div>
<div className="w-full h-8 z-10 relative flex items-center justify-center">
Expand All @@ -113,6 +117,7 @@ export const POAPModal: FC<{
poap_name={name}
event_name={event_name}
address={mintToProfile}
iykData={data}
onCallChange={() => {
setMintToProfile('');
// eslint-disable-next-line no-undef
Expand All @@ -121,6 +126,9 @@ export const POAPModal: FC<{
onCallClose={() => {
setDismissed(true);
}}
onMintToProfileError={(error) => {
setMintToProfileError(error);
}}
/>
)}
{state === NAME_INPUT && (
Expand All @@ -138,7 +146,15 @@ export const POAPModal: FC<{
/>
)}
{state === EXPIRED_STATE && <ExpiredPOAP />}
{state === ERROR_STATE && (
<ClaimError
data={mintToProfileError}
recipient={mintToProfile}
/>
)}
</div>
{/* {poapEvent.status} */}
{/* {JSON.stringify(data)} */}
{/* <div className="pt-2 space-y-2">
<div className="w-full max-w-xs mx-auto">
Claim your POAP to show you met {name} at frENSday!
Expand Down
18 changes: 18 additions & 0 deletions components/POAPModal/stages/AlreadyClaimed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FC } from 'react';

export const AlreadyClaimed: FC<{ to: string }> = ({ to }) => {
return (
<div className="space-y-2 w-full">
<p>It appears you already have this POAP 🎉</p>
<p>You should see it in your collection</p>
<a
// href={'https://collectors.poap.xyz/token/' + poap_id}
href={'https://collectors.poap.xyz/scan/' + to}
target="_blank"
className="btn w-full p-4"
>
View Collection
</a>
</div>
);
};
26 changes: 26 additions & 0 deletions components/POAPModal/stages/ClaimError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// eslint-disable-next-line unicorn/prefer-node-protocol, simple-import-sort/imports
import { inspect } from 'util';
import { FC } from 'react';
import { AlreadyClaimed } from './AlreadyClaimed';

export const ClaimError: FC<{ data: unknown; recipient: string }> = ({
data,
recipient,
}) => {
if (
data['statusCode'] == 400 &&
data['error'] == 'Bad Request' &&
data['message'] == 'You already minted a POAP for this drop.'
) {
return <AlreadyClaimed to={recipient} />;
}

return (
<div className="space-y-2 w-full">
<p>There was an error claiming this POAP.</p>
<pre className="text-xs whitespace-break-spaces text-start bg-neutral-200/20 p-2 rounded-lg border">
<code>{inspect(data)}</code>
</pre>
</div>
);
};
2 changes: 1 addition & 1 deletion components/POAPModal/stages/Expired.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const ExpiredPOAP: FC<{}> = () => {
<a
href="https://iyk.app/admin?utm_source=ens-page&utm_campaign=expired-poap"
target="_blank"
className="text-ens-light-blue-primary underline"
className="link underline"
>
via the IYK Dashboard
</a>
Expand Down
33 changes: 29 additions & 4 deletions components/POAPModal/stages/MintToProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import clsx from 'clsx';
import { FC, useState } from 'react';
import { FiCheck, FiLoader } from 'react-icons/fi';

import { mintPOAP } from '../../../hooks/mintPOAP';
import { IYKRefResponse as IYKReferenceResponse } from '../../../hooks/useIYKRef';

const eth_address_regex = /^0x[\dA-Fa-f]{40}$/;

export const MintToProfile: FC<{
Expand All @@ -11,7 +14,17 @@ export const MintToProfile: FC<{
event_name: string;
onCallChange: () => void;
onCallClose: () => void;
}> = ({ address, poap_name, event_name, onCallChange, onCallClose }) => {
iykData: IYKReferenceResponse;
onMintToProfileError: (error: unknown) => void;
}> = ({
address,
poap_name,
event_name,
iykData,
onCallChange,
onCallClose,
onMintToProfileError,
}) => {
const isAddress = eth_address_regex.test(address);
const [stage, setStage] = useState<'start' | 'minting' | 'minted'>('start');

Expand All @@ -26,7 +39,7 @@ export const MintToProfile: FC<{
'btn w-full py-3 space-x-2 transition-all',
stage === 'minted' && '!bg-ens-light-green-primary'
)}
onClick={() => {
onClick={async () => {
if (stage === 'minted') {
onCallClose();

Expand All @@ -35,9 +48,21 @@ export const MintToProfile: FC<{

setStage('minting');

setTimeout(() => {
try {
const [data, error] = await mintPOAP(
iykData.poapEvents[0].otp,
address,
iykData.poapEvents[0].poapEventId
);

if (error) {
throw error;
}

setStage('minted');
}, 1000);
} catch (error) {
onMintToProfileError(error);
}
}}
disabled={stage === 'minting'}
>
Expand Down
3 changes: 0 additions & 3 deletions components/POAPModal/stages/NameInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,6 @@ export const NameInput: FC<{
disabled={loading}
/>
</div>
{/* <button className="px-6 py-2 btn w-fit mx-auto font-bold text-sm">
Mint POAP
</button> */}
<button
type="submit"
className="p-4 btn w-fit mx-auto font-bold text-sm aspect-square"
Expand Down
2 changes: 1 addition & 1 deletion components/POAPModal/stages/PendingApproval.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const PendingApproval = () => {
<a
href="https://blog.poap.xyz/guidelines/"
target="_blank"
className="text-ens-light-blue-primary underline"
className="link underline"
>
pending human review
</a>
Expand Down
39 changes: 39 additions & 0 deletions hooks/mintPOAP.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
type ErrorResponse = {
error: 'Bad Request';
message: 'You already minted a POAP for this drop.';
statusCode: 400;
};

export const mintPOAP = async (
otpCode: string,
recipient: string,
poapEventId: number
) => {
const headers = new Headers();

headers.append('Content-Type', 'application/json');
headers.append('x-iyk-code', otpCode);

const response = await fetch('https://api.iyk.app/poap-events/mint', {
method: 'POST',
headers,
body: JSON.stringify({
recipient,
poapEventId,
}),
});

if (!response.ok) {
if (response.status == 400) {
const error: ErrorResponse = await response.json();

return [undefined, error];
}

return [undefined, response];
}

const data = await response.json();

return [data, undefined];
};
Loading

0 comments on commit 1e5d847

Please sign in to comment.