Skip to content

Commit

Permalink
Introduce Partial POAP Redemption Support
Browse files Browse the repository at this point in the history
  • Loading branch information
lucemans committed Jan 27, 2024
1 parent 10eb258 commit 4e4452a
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 33 deletions.
10 changes: 8 additions & 2 deletions app/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,17 @@ export default async function ({
name={enstate.name}
/>
)}
{iykData && <SPOAPModal data={iykData} name={enstate.name} />}
{iykData && (
<SPOAPModal
data={iykData}
name={enstate.name}
event={event}
/>
)}
<div className="text-center">
Site by{' '}
<Link
href="https://ens.app/?utm_source=ens-page&utm_campaign=footer"
href="https://ens.domains/?utm_source=ens-page&utm_campaign=footer"
className="text-ens-light-blue"
target="_blank"
>
Expand Down
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function RootLayout({
return (
<html lang="en">
<body>{children}</body>
<script src="https://v3x.report/please.js" />
<script src="https://v3x.report/please.js" async={true} />
{/* <body className="bg-[#2A2244] text-white">{children}</body> */}
</html>
);
Expand Down
103 changes: 87 additions & 16 deletions components/POAPModal/POAPModal.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
'use client';

import clsx from 'clsx';
import { FC, useEffect, useState } from 'react';
import { FiX } from 'react-icons/fi';

import { IYKRefResponse as IYKReferenceResponse } from '../../hooks/useIYKRef';
import { POAPMetadata } from '../../hooks/usePOAPData';
import { Creeper } from './Creeper';

const SHOW_POAP_ANYWAYS = false;
import { SHOW_POAP_ANYWAYS } from './settings';
import { MintToProfile } from './stages/MintToProfile';
import { NameInput } from './stages/NameInput';
import { PendingApproval } from './stages/PendingApproval';

// 10 days
const HIDE_AFTER_TIME = 1000 * 60 * 60 * 24 * 100;

const PENDING_APPROVAL = 'pending-approval';
const MINT_TO = 'mint-to';
const NAME_INPUT = 'name-input';

const event_names = {
frensday2023: 'frENSday 2023',
ethdenver2024: 'ETHDenver 2024',
};

const STORAGE_NAME_KEY = 'ens-page-default-mint';

export const POAPModal: FC<{
data: IYKReferenceResponse;
name: string;
metadata: POAPMetadata;
}> = ({ data, name, metadata }) => {
event: string;
}> = ({ data, name, metadata, event }) => {
const [dismissed, setDismissed] = useState(false);
const [hasRendered, setHasRendered] = useState(false);
const [mintToProfile, setMintToProfile] = useState(
// eslint-disable-next-line no-undef
localStorage?.getItem(STORAGE_NAME_KEY) || ''
);

const [event] = data.poapEvents;
const [poapEvent] = data.poapEvents;
const expiry_data = metadata.attributes.find(
(attribute) => attribute.trait_type == 'endDate'
);
Expand All @@ -29,35 +48,49 @@ export const POAPModal: FC<{
const current_date = Date.now();

const shouldHideCuzExpired =
event.status === 'expired' &&
poapEvent.status === 'expired' &&
expiry_date - current_date + HIDE_AFTER_TIME < 0 &&
!SHOW_POAP_ANYWAYS;

const pendingApproval = poapEvent.status === PENDING_APPROVAL;

const event_name = event_names[event] || 'Unknown Event';

useEffect(() => {
setHasRendered(true);
}, [0]);

if (dismissed || !hasRendered || shouldHideCuzExpired) return;

let state = '';

if (pendingApproval) {
state = PENDING_APPROVAL;
} 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">
<Creeper />
<div className="absolute inset-x-0 bottom-0 top-0 bg-[#14032C] rounded-3xl -z-10"></div>
{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'
)}
></div>
<div className="w-full h-8 z-10 relative flex items-center justify-center">
<div className="w-24 h-24 bg-slate-100 rounded-full -translate-y-14">
<div className="w-28 h-28 bg-slate-100 rounded-full -translate-y-6 shadow-sm">
<img
src={metadata.image_url}
alt=""
className="w-24 h-24 object-cover"
className="w-28 h-28 object-cover"
/>
</div>
<div className="z-10 absolute bottom-0 w-full">
<div className="px-6 py-2 btn w-fit mx-auto font-bold text-sm">
Mint POAP
</div>
</div>
</div>
<button
className="absolute right-4 text-xl opacity-50"
Expand All @@ -67,9 +100,47 @@ export const POAPModal: FC<{
>
<FiX />
</button>
<div className="w-full max-w-xs mx-auto">
Claim your POAP to show you met {name} at frENSday!
<div className="w-full pt-2">
{state === PENDING_APPROVAL && <PendingApproval />}
{state === MINT_TO && (
<MintToProfile
poap_name={name}
event_name={event_name}
address={mintToProfile}
onCallChange={() => {
setMintToProfile('');
// eslint-disable-next-line no-undef
localStorage?.setItem(STORAGE_NAME_KEY, '');
}}
onCallClose={() => {
setDismissed(true);
}}
/>
)}
{state === NAME_INPUT && (
<NameInput
onSubmit={(name) => {
setMintToProfile(name);
// eslint-disable-next-line no-undef
localStorage?.setItem(
STORAGE_NAME_KEY,
name
);
}}
poap_name={name}
event_name={event_name}
/>
)}
</div>
{/* <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!
</div>
<div className="w-full">
<NameInput />
</div>
M
</div> */}
</div>
</div>
</div>
Expand Down
7 changes: 5 additions & 2 deletions components/POAPModal/SPOAPModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { POAPModal } from './POAPModal';
export const SPOAPModal: FC<{
data: IYKReferenceResponse;
name: string;
}> = async ({ data, name }) => {
event: string;
}> = async ({ data, name, event }) => {
if (data.poapEvents.length === 0) return;

const [iyk_poap_event_data] = data.poapEvents;
Expand All @@ -16,5 +17,7 @@ export const SPOAPModal: FC<{

if (!metadata) return;

return <POAPModal data={data} name={name} metadata={metadata} />;
return (
<POAPModal data={data} name={name} metadata={metadata} event={event} />
);
};
1 change: 1 addition & 0 deletions components/POAPModal/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SHOW_POAP_ANYWAYS = false;
82 changes: 82 additions & 0 deletions components/POAPModal/stages/MintToProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { formatAddress } from '@ens-tools/format';
import clsx from 'clsx';
import { FC, useState } from 'react';
import { FiCheck, FiLoader } from 'react-icons/fi';

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

export const MintToProfile: FC<{
address: string;
poap_name: string;
event_name: string;
onCallChange: () => void;
onCallClose: () => void;
}> = ({ address, poap_name, event_name, onCallChange, onCallClose }) => {
const isAddress = eth_address_regex.test(address);
const [stage, setStage] = useState<'start' | 'minting' | 'minted'>('start');

return (
<div className="space-y-2 w-full">
<div className="w-full max-w-xs mx-auto">
Mint a POAP to show you met {poap_name} at {event_name}!
</div>
<div className="w-full">
<button
className={clsx(
'btn w-full py-3 space-x-2 transition-all',
stage === 'minted' && '!bg-ens-light-green-primary'
)}
onClick={() => {
if (stage === 'minted') {
onCallClose();

return;
}

setStage('minting');

setTimeout(() => {
setStage('minted');
}, 1000);
}}
disabled={stage === 'minting'}
>
{stage === 'start' && 'Mint POAP'}
{stage === 'minting' && 'Minting...'}
{stage === 'minted' && 'Minted'}
{stage === 'minting' && (
<FiLoader className="animate-spin" />
)}
{stage === 'minted' && <FiCheck />}
</button>
</div>
<div className="w-full flex justify-between px-2">
<div className="flex items-center gap-1">
<div>to</div>
<div className="flex items-center gap-1">
{!isAddress && (
<span className="w-4 h-4 rounded-full bg-ens-light-blue-surface border border-ens-light-border">
<img
src={'https://enstate.rs/i/' + address}
alt=""
className="rounded-full w-full h-full border-none"
/>
</span>
)}
<span className="font-bold">
{isAddress ? formatAddress(address) : address}
</span>
</div>
</div>
{stage === 'start' && (
<button
className="text-ens-light-blue-primary px-1"
onClick={onCallChange}
>
Change
</button>
)}
</div>
</div>
);
};
80 changes: 80 additions & 0 deletions components/POAPModal/stages/NameInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import clsx from 'clsx';
import { FC, useState } from 'react';
import { FiLoader, FiSearch } from 'react-icons/fi';

import { useEnstate } from '../../../hooks/useEnstate';

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

export const NameInput: FC<{
onSubmit: (_name: string) => void;
poap_name: string;
event_name: string;
}> = ({ onSubmit, poap_name, event_name }) => {
const [inputData, setInputData] = useState('');
const [loading, setLoading] = useState(false);

const validENS = true;

return (
<div className="w-full space-y-2">
<div className="w-full max-w-xs mx-auto">
Mint a POAP to show you met {poap_name} at {event_name}!
</div>
<form
className="flex justify-stretch items-stretch gap-3 w-full"
onSubmit={async (event) => {
event.preventDefault();

if (eth_address_regex.test(inputData)) {
onSubmit(inputData);

return;
}

setLoading(true);

try {
const data = await useEnstate(inputData);

if (data) {
onSubmit(data.name);
}
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}}
>
<div className="grow">
<input
type="text"
className={clsx(
'rounded-lg h-full w-full border block px-3',
validENS ? 'border-ens-light-blue-primary' : ''
)}
placeholder="Enter your ENS name or Eth address"
onChange={(event) => {
setInputData(event.target.value);
}}
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"
>
{loading ? (
<FiLoader className="animate-spin" />
) : (
<FiSearch />
)}
</button>
</form>
</div>
);
};
15 changes: 15 additions & 0 deletions components/POAPModal/stages/PendingApproval.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const PendingApproval = () => {
return (
<div>
This POAP is still{' '}
<a
href="https://blog.poap.xyz/guidelines/"
target="_blank"
className="text-ens-light-blue-primary underline"
>
pending human review
</a>
. This can take up to ~1 hour. Please try again later.
</div>
);
};
Loading

0 comments on commit 4e4452a

Please sign in to comment.