Skip to content

Commit

Permalink
Evolve Flow modal (#1900)
Browse files Browse the repository at this point in the history
* Adjust collect flow to accommodate collect and evolve actions
- extracted collect modal from card to timeline level

* Added pending state  evolve
- confetti effect re-trigger on evolution

* added evolution level

* evolution/ evolution limit
  • Loading branch information
MGrudule authored Oct 23, 2024
1 parent c9e8a3a commit c079fd3
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 32 deletions.
65 changes: 61 additions & 4 deletions apps/verification-portal/src/lib/features/DOTphin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import {
fetchDotphinData,
submitClaim,
evolveDotphin,
claimStatus,
burnNFT,
} from '$lib/shared/apiServices/DOTphinAPI';
Expand Down Expand Up @@ -40,6 +41,10 @@ export async function updateMultipassStateForAddress(address: string) {
DID: data.dotphinDID,
pending: false,
},
evolution: {
level: data.dotphin ? getLevelAttributeValue(data.dotphin) : 0,
maxLevel: data.dotphinMaxLevel,
},
});

if (data.dotphinDID) {
Expand Down Expand Up @@ -120,7 +125,13 @@ async function fetchNFTDataByDID(did: string) {
console.error('Error fetching NFT data:', error);
}
}

// Get Level Attribute Value
function getLevelAttributeValue(asset: DeepAsset): number {
const levelAttribute = asset.attributes?.find(
(attr) => attr.trait_type === 'level'
);
return levelAttribute ? Number(levelAttribute.value) : 0;
}
// Find Proof by Trait Type
function findProofByTraitType(
proofs: DeepAsset[],
Expand All @@ -139,16 +150,29 @@ function findProofByTraitType(
}

// Claim Proof by Trait Type
export async function claimProofByTraitType(
export async function handleProofByTraitType(
address: string,
element: 'air' | 'water' | 'earth'
element: 'air' | 'water' | 'earth',
action: 'claim' | 'evolve'
) {
const data = get(multipassData);
const proofAddress = data.proofs
? findProofByTraitType(data.proofs, element)
: undefined;
if (proofAddress) {
await claimDOTphinNFT(address, proofAddress);
if (action === 'claim') {
await claimDOTphinNFT(address, proofAddress);
} else if (action === 'evolve') {
const dotphinDID = data.nft.DID;
if (dotphinDID) {
await evolveDOTphinNFT(address, dotphinDID, proofAddress);
} else {
toast.error(
'Error: No DOTphin found for evolution, please refresh the page and try again'
);
console.log('No DOTphin DID found for evolution');
}
}
} else {
toast.error(
'Error finding a valid proof, please refresh the page and try again'
Expand All @@ -157,6 +181,39 @@ export async function claimProofByTraitType(
}
}

// Evolve DOTphin NFT
export async function evolveDOTphinNFT(
address: string,
dotphinDID: string,
proofDID: string
) {
try {
const evolveData = await evolveDotphin(address, dotphinDID, proofDID);
toast.success('Evolution submitted successfully');
const currentState = get(multipassData);
updateState({
...currentState,
proofStats: {
...currentState.proofStats,
available: {
...currentState.proofStats.available,
total: currentState.proofStats.available.total - 1,
},
},
nft: { DID: dotphinDID, data: evolveData, pending: true },
evolution: {
...currentState.evolution,
level: currentState.evolution.level + 1,
},
});
setCookie('claimPending', evolveData.id);
checkClaimStatus(evolveData.id, address);
} catch (error) {
toast.error('Error submitting evolve, please try again');
console.error('Error during evolve submission:', error);
}
}

// Burn DOTphin NFT
export async function burnDOTphinNFT() {
const currentState = get(multipassData);
Expand Down
10 changes: 7 additions & 3 deletions apps/verification-portal/src/lib/features/MultipassStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const canEvolve = isFeatureEnabled('dotphinEvolution');

import {
MAX_EVOLUTION_LEVEL,
EVOLUTION_LIMIT,
initialStepConfig,
STATUS,
} from '$lib/shared/multipassConfig';
Expand Down Expand Up @@ -39,6 +38,7 @@ export type MultipassData = {
};
evolution: {
level: number;
maxLevel: number;
};
};

Expand All @@ -58,6 +58,7 @@ const initialState: MultipassData = {
},
evolution: {
level: 0,
maxLevel: MAX_EVOLUTION_LEVEL, // set limit same as max
},
};

Expand Down Expand Up @@ -112,7 +113,9 @@ export const evolveStepState = derived(
return 'INITIAL';
} else if ($multipassData.evolution.level >= MAX_EVOLUTION_LEVEL) {
return 'COMPLETE';
} else if ($multipassData.evolution.level >= EVOLUTION_LIMIT) {
} else if (
$multipassData.evolution.level >= $multipassData.evolution.maxLevel
) {
return 'LIMITED';
} else {
return 'EVOLVING';
Expand Down Expand Up @@ -159,7 +162,8 @@ export const multipassStepConfig = derived(
: proofsStatus === STATUS.COMPLETE && nftStatus === STATUS.COMPLETE
? $multipassData.evolution.level >= MAX_EVOLUTION_LEVEL
? STATUS.COMPLETE
: $multipassData.evolution.level >= EVOLUTION_LIMIT
: $multipassData.evolution.level >=
$multipassData.evolution.maxLevel
? STATUS.LOCKED
: STATUS.ACTIVE
: STATUS.LOCKED;
Expand Down
16 changes: 16 additions & 0 deletions apps/verification-portal/src/lib/shared/apiServices/DOTphinAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ export async function claimStatus(claimId: string) {
return data.onChain.status === 'success';
}

// Evolve Dotphin
export async function evolveDotphin(
address: string,
dotphinDID: string,
proofDID: string
) {
console.table({ address, dotphinDID, proofDID });
const response = await fetch(`${BASE_URL}/evolve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ address, dotphinDID, proofDID }),
});
if (!response.ok) throw new Error('Failed to evolve DOTphin');
return await response.json();
}

// Burn NFT
export async function burnNFT(dotphinDID: string, owner: string) {
const response = await fetch(`${BASE_URL}/burn`, {
Expand Down
1 change: 1 addition & 0 deletions apps/verification-portal/src/lib/shared/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ const en: BaseTranslation = {
nftSubtitle: `One Eco-evolving Avatar`,
},
pendingCollect: `Hang tight, we're processing your claim! This could take up to a minute. Feel free to check back soon to meet your DOTphin pal! 🐬🌊`,
pendingEvolve: `Hang tight, we're processing your evolution! This could take up to a minute. Feel free to check back soon and see the evolution! 🐬🌊`,
state: {
proofStep: {
stepTitle: 'Proofs',
Expand Down
8 changes: 8 additions & 0 deletions apps/verification-portal/src/lib/shared/i18n/i18n-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,10 @@ type RootTranslation = {
* H​a​n​g​ ​t​i​g​h​t​,​ ​w​e​'​r​e​ ​p​r​o​c​e​s​s​i​n​g​ ​y​o​u​r​ ​c​l​a​i​m​!​ ​T​h​i​s​ ​c​o​u​l​d​ ​t​a​k​e​ ​u​p​ ​t​o​ ​a​ ​m​i​n​u​t​e​.​ ​F​e​e​l​ ​f​r​e​e​ ​t​o​ ​c​h​e​c​k​ ​b​a​c​k​ ​s​o​o​n​ ​t​o​ ​m​e​e​t​ ​y​o​u​r​ ​D​O​T​p​h​i​n​ ​p​a​l​!​ ​�​�​�​�
*/
pendingCollect: string
/**
* H​a​n​g​ ​t​i​g​h​t​,​ ​w​e​'​r​e​ ​p​r​o​c​e​s​s​i​n​g​ ​y​o​u​r​ ​e​v​o​l​u​t​i​o​n​!​ ​T​h​i​s​ ​c​o​u​l​d​ ​t​a​k​e​ ​u​p​ ​t​o​ ​a​ ​m​i​n​u​t​e​.​ ​F​e​e​l​ ​f​r​e​e​ ​t​o​ ​c​h​e​c​k​ ​b​a​c​k​ ​s​o​o​n​ ​a​n​d​ ​s​e​e​ ​t​h​e​ ​e​v​o​l​u​t​i​o​n​!​ ​�​�​�​�
*/
pendingEvolve: string
state: {
proofStep: {
/**
Expand Down Expand Up @@ -1203,6 +1207,10 @@ export type TranslationFunctions = {
* Hang tight, we're processing your claim! This could take up to a minute. Feel free to check back soon to meet your DOTphin pal! 🐬🌊
*/
pendingCollect: () => LocalizedString
/**
* Hang tight, we're processing your evolution! This could take up to a minute. Feel free to check back soon and see the evolution! 🐬🌊
*/
pendingEvolve: () => LocalizedString
state: {
proofStep: {
/**
Expand Down
1 change: 0 additions & 1 deletion apps/verification-portal/src/lib/shared/multipassConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export const MAX_EVOLUTION_LEVEL = 4;
export const EVOLUTION_LIMIT = 3;

// Multipass step states, derived from API data. Used for dynamic UI/UX and translation based on API Data points
export type ProofStepState =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
closeModal,
} from '$lib/widgets/DOTphin/collectModalStore';
import ModalWrapper from '$lib/shared/components/ModalWrapper.svelte';
import Form from './Form.svelte';

Check warning on line 7 in apps/verification-portal/src/lib/widgets/DOTphin/CollectForm/CollectModal.svelte

View workflow job for this annotation

GitHub Actions / code-quality

`./Form.svelte` import should occur before import of `$lib/widgets/DOTphin/collectModalStore`
const title = 'Pick an Element';
</script>

<ModalWrapper
open={$formModal}
open={$formModal.open}
size="sm"
on:close={() => closeModal()}
autoclose={false}
Expand All @@ -20,11 +19,15 @@
<div class="text-center px-4 dark:text-gray-200">
<h2 class="text-3xl font-sans">{title}</h2>
<p class="text-sm">
The element will determine the type of DOTphin that will hatch
{#if $formModal.action === 'claim'}
The element will determine the type of DOTphin that will hatch
{:else if $formModal.action === 'evolve'}
The element will determine the type of next evolution
{/if}
</p>
</div>
<div class="pt-1 pb-16">
<Form />
<Form action={$formModal.action} />
</div>
</svelte:fragment>
</ModalWrapper>
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
<script lang="ts">
import TileIcon from '$lib/widgets/DOTphin/CollectForm/TileIcon.svelte';
import { multipassData } from '$lib/features/MultipassStates';
import { claimProofByTraitType } from '$lib/features/DOTphin';
import { handleProofByTraitType } from '$lib/features/DOTphin';
import Spinner from '$lib/components/icons/Spinner.svelte';
let selectedValue: string | null = null;
import { resetConfetti } from '$lib/widgets/DOTphin/confettiStore';
import { closeModal } from '$lib/widgets/DOTphin/collectModalStore';
let selectedValue: string | null = null;
export let action: 'claim' | 'evolve' = 'claim';
function handleTileClick(value: string) {
selectedValue = value;
handleSubmit();
Expand All @@ -17,11 +20,13 @@
isSubmitting = true;
if ($multipassData.address && selectedValue !== null) {
await claimProofByTraitType(
await handleProofByTraitType(
$multipassData.address,
selectedValue as 'earth' | 'air' | 'water'
selectedValue as 'earth' | 'air' | 'water',
action
).finally(() => {
isSubmitting = false;
resetConfetti(); // so that evolution triggers new state
closeModal();
});
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script lang="ts">
import { openModal } from '$lib/widgets/DOTphin/collectModalStore';
import {
multipassData,
evolveStepState,
Expand All @@ -11,8 +13,8 @@
import TimelineActionButton from '$lib/widgets/DOTphin/TimelineItem/TimelineActionButton.svelte';
import CardSubtitle from '$lib/widgets/DOTphin/TimelineItem/Typography/Subtitle.svelte';
import CardTitle from '$lib/widgets/DOTphin/TimelineItem/Typography/Title.svelte';
import { LL } from '$lib/shared/i18n/i18n-svelte';
const cardLink =
siteConfigs?.contentLinks?.DOTphin?.evolution ||
siteConfigs?.contentLinks?.DOTphin?.default ||
Expand All @@ -35,6 +37,7 @@
umamiID="evolution"
disabled={$multipassStepConfig.evolution.stepStatus !== 'active'}
title={$LL.multipass.state.evolveStep.INITIAL.cta()}
on:click={() => openModal('evolve')}
/>
{/if}
</svelte:fragment>
Expand All @@ -57,6 +60,7 @@
})}
</CardSubtitle>
{/if}

{#if cardLink}
<a
href={cardLink}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
import { LL } from '$lib/shared/i18n/i18n-svelte';
import { Confetti } from 'svelte-confetti';

Check warning on line 13 in apps/verification-portal/src/lib/widgets/DOTphin/Steps/CardNFT.svelte

View workflow job for this annotation

GitHub Actions / code-quality

`svelte-confetti` import should occur before import of `$lib/widgets/DOTphin/collectModalStore`
import TimelineActionButton from '$lib/widgets/DOTphin/TimelineItem/TimelineActionButton.svelte';
import CollectModal from '../CollectForm/CollectModal.svelte';
let showConfetti = false;
import {
showConfetti,
triggerConfetti,
} from '$lib/widgets/DOTphin/confettiStore';
$: if ($multipassData.nft.pending && !showConfetti) {
showConfetti = true;
$: if ($multipassData.nft.pending && !$showConfetti) {
triggerConfetti();
}
</script>

Expand All @@ -33,7 +34,7 @@
umamiID="collect-orbo"
disabled={$multipassStepConfig.nft.stepStatus !== 'active'}
title={$LL.multipass.state.nftStep.UNCLAIMED.cta()}
on:click={() => openModal()}
on:click={() => openModal('claim')}
/>
</svelte:fragment>
<svelte:fragment slot="content">
Expand All @@ -42,7 +43,6 @@
/>
</svelte:fragment>
</TimelineItem>
<CollectModal></CollectModal>
{:else}
<TimelineItem
itemState={$multipassStepConfig.nft.stepStatus}
Expand All @@ -54,7 +54,7 @@
<svelte:fragment slot="featured">
<div class="max-h-[200px] mb-12">
<FeaturedCard item={$multipassData.nft.data} />
{#if showConfetti && !$isLoading}
{#if $showConfetti && !$isLoading}
<Confetti delay={[100, 250]} rounded colorRange={[75, 175]} />
{/if}
</div>
Expand Down
12 changes: 11 additions & 1 deletion apps/verification-portal/src/lib/widgets/DOTphin/Timeline.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import { Timeline } from 'flowbite-svelte';

Check warning on line 7 in apps/verification-portal/src/lib/widgets/DOTphin/Timeline.svelte

View workflow job for this annotation

GitHub Actions / code-quality

`flowbite-svelte` import should occur before import of `$lib/widgets/DOTphin/Steps/CardEvolution.svelte`
import Spinner from '$lib/components/icons/Spinner.svelte';
import { LL } from '$lib/shared/i18n/i18n-svelte';
import CollectModal from '$lib/widgets/DOTphin/CollectForm/CollectModal.svelte';
import {
multipassData,
nftStepState,
evolveStepState,
resetData,
} from '$lib/features/MultipassStates';
import { updateMultipassStateForAddress } from '$lib/features/DOTphin';
Expand Down Expand Up @@ -53,7 +55,11 @@
>
<Spinner className=" w-5 h-5 text-primary-200 fill-primary-400 "
></Spinner>
<span> {$LL.multipass.pendingCollect()}</span>
{#if $evolveStepState === 'EVOLVING'}
<span> {$LL.multipass.pendingEvolve()}</span>
{:else}
<span> {$LL.multipass.pendingCollect()}</span>
{/if}
</div>
{/if}
<Timeline {order}>
Expand All @@ -67,3 +73,7 @@
<EvolutionCard />
</Timeline>
</div>

{#if $nftStepState === 'UNCLAIMED' || $evolveStepState === 'INITIAL' || $evolveStepState === 'EVOLVING'}
<CollectModal />
{/if}
Loading

0 comments on commit c079fd3

Please sign in to comment.