Skip to content

Commit 2a63899

Browse files
authored
Merge pull request #588 from paycrest/feat/tooltip-for-earn
feat: Add EarnUnavailableModal component and integrate it into Wallet Details
2 parents 4363169 + c5c06fd commit 2a63899

2 files changed

Lines changed: 189 additions & 88 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"use client";
2+
3+
import Image from "next/image";
4+
import { Coins01Icon } from "hugeicons-react";
5+
import { AnimatedModal } from "./AnimatedComponents";
6+
import { primaryBtnClasses } from "./Styles";
7+
import { networks } from "../mocks";
8+
import { getNetworkImageUrl } from "../utils";
9+
import { useActualTheme } from "../hooks/useActualTheme";
10+
11+
const starknetNetwork = networks.find((n) => n.chain.name === "Starknet");
12+
13+
interface EarnUnavailableModalProps {
14+
isOpen: boolean;
15+
onClose: () => void;
16+
}
17+
18+
/** Shown when the manager opens Earn while on a network other than Starknet. */
19+
export const EarnUnavailableModal = ({
20+
isOpen,
21+
onClose,
22+
}: EarnUnavailableModalProps) => {
23+
const isDark = useActualTheme();
24+
25+
return (
26+
<AnimatedModal isOpen={isOpen} onClose={onClose}>
27+
<div className="flex flex-col items-center gap-10 py-6 text-center">
28+
<div className="flex flex-col items-center gap-4">
29+
<div className="relative flex size-[72px] shrink-0 items-center justify-center rounded-full bg-accent-gray text-gray-900 dark:bg-white/5 dark:text-white">
30+
<Coins01Icon className="size-6" strokeWidth={2} />
31+
{starknetNetwork && (
32+
<Image
33+
src={getNetworkImageUrl(starknetNetwork, isDark)}
34+
alt="Starknet"
35+
width={32}
36+
height={32}
37+
className="absolute -bottom-1 -right-1 size-8 rounded-full"
38+
/>
39+
)}
40+
</div>
41+
<div className="flex flex-col items-center gap-2">
42+
<p className="text-lg font-semibold text-text-body dark:text-white">
43+
Earn is currently available on Starknet.
44+
</p>
45+
<p className="max-w-[19rem] text-sm text-text-secondary dark:text-white/50">
46+
Switch your wallet to Starknet to start earning on your USDC.
47+
</p>
48+
</div>
49+
</div>
50+
<button
51+
type="button"
52+
onClick={onClose}
53+
className={primaryBtnClasses}
54+
>
55+
Okay, got it
56+
</button>
57+
</div>
58+
</AnimatedModal>
59+
);
60+
};

app/components/WalletDetails.tsx

Lines changed: 129 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import {
2828
Database01Icon,
2929
CoinsSwapIcon,
3030
Coins01Icon,
31+
InformationCircleIcon,
32+
Cancel01Icon,
3133
} from "hugeicons-react";
3234
import Image from "next/image";
3335
import { useInjectedWallet } from "../context";
@@ -59,8 +61,9 @@ import { CopyAddressWarningModal } from "./CopyAddressWarningModal";
5961
import { useFundWalletHandler } from "../hooks/useFundWalletHandler";
6062
import { useCNGNRate } from "../hooks/useCNGNRate";
6163
import { EarnConsentModal } from "./EarnConsentModal";
64+
import { EarnUnavailableModal } from "./EarnUnavailableModal";
6265
import { useEarnAccess } from "../hooks/useEarnAccess";
63-
import { isEarnUiVisible } from "../lib/earnFeature";
66+
import { isEarnEnabled, isEarnUiVisible } from "../lib/earnFeature";
6467
import { isReferralEnabled, formatTokenAmount } from "../utils";
6568
import { isBridgeUiVisible } from "../lib/bridgeFeature";
6669
import { BridgeForm } from "./bridge/BridgeForm";
@@ -72,6 +75,50 @@ const Divider = () => (
7275
<div className="w-full border border-dashed border-[#EBEBEF] dark:border-[#FFFFFF1A]" />
7376
);
7477

78+
/** Pixel-matched callout for the Earn action-item when it's shown on a
79+
* network Earn doesn't support yet (Figma node 2208:82749). Anchored to the
80+
* bottom-right of the action row with an upward-pointing tail toward Earn. */
81+
const EarnUnavailableTooltip = ({
82+
isOpen,
83+
onClose,
84+
}: {
85+
isOpen: boolean;
86+
onClose: () => void;
87+
}) => (
88+
<AnimatePresence>
89+
{isOpen && (
90+
<motion.div
91+
initial={{ opacity: 0, y: -4 }}
92+
animate={{ opacity: 1, y: 0 }}
93+
exit={{ opacity: 0, y: -4 }}
94+
transition={{ duration: 0.15 }}
95+
className="absolute right-0 top-full z-10 mt-2 w-[266px]"
96+
>
97+
<div
98+
className="absolute -top-1.5 right-8 size-3 rotate-45 rounded-[2px] border-l border-t border-border-light bg-white dark:border-white/10 dark:bg-[#2c2c2c]"
99+
aria-hidden
100+
/>
101+
<div className="relative rounded-2xl border border-border-light bg-white p-2.5 pr-9 shadow-lg dark:border-white/10 dark:bg-[#2c2c2c]">
102+
<p className="text-[11px] font-medium leading-5 text-text-body dark:text-white">
103+
Earn is currently available on Starknet.
104+
</p>
105+
<p className="mt-[3px] text-[10px] leading-[13px] text-text-secondary dark:text-white/50">
106+
Switch your wallet to Starknet to start earning on your USDC.
107+
</p>
108+
<button
109+
type="button"
110+
aria-label="Dismiss"
111+
onClick={onClose}
112+
className="absolute right-2.5 top-2.5 text-text-secondary hover:text-text-body dark:text-white/50 dark:hover:text-white"
113+
>
114+
<Cancel01Icon className="size-4" />
115+
</button>
116+
</div>
117+
</motion.div>
118+
)}
119+
</AnimatePresence>
120+
);
121+
75122
export const WalletDetails = () => {
76123
const { trackBridge } = useBridgeStatusTracker();
77124
const [isTransferModalOpen, setIsTransferModalOpen] =
@@ -80,6 +127,9 @@ export const WalletDetails = () => {
80127
const [isConvertModalOpen, setIsConvertModalOpen] = useState(false);
81128
const [isEarnFormOpen, setIsEarnFormOpen] = useState(false);
82129
const [earnFormTab, setEarnFormTab] = useState<"deposit" | "withdraw">("deposit");
130+
const [isEarnHintOpen, setIsEarnHintOpen] = useState(false);
131+
const [isEarnUnavailableModalOpen, setIsEarnUnavailableModalOpen] =
132+
useState(false);
83133
const [sidebarView, setSidebarView] = useState<
84134
"wallet" | "earn" | "referrals"
85135
>("wallet");
@@ -284,6 +334,8 @@ export const WalletDetails = () => {
284334
setSelectedTransaction(null);
285335
setSelectedEarnActivity(null);
286336
setIsNetworkListOpen(false);
337+
setIsEarnHintOpen(false);
338+
setIsEarnUnavailableModalOpen(false);
287339
};
288340

289341
// Copy wallet address to clipboard with feedback
@@ -480,111 +532,95 @@ export const WalletDetails = () => {
480532
</button>
481533
</div>
482534

483-
{!isInjectedWallet &&
484-
(showEarnUi ? (
485-
<div className="flex flex-row items-start justify-between gap-2">
486-
<button
487-
type="button"
488-
title="Fund wallet"
489-
onClick={() => setIsFundModalOpen(true)}
490-
className="group flex flex-1 flex-col items-center gap-2"
491-
>
492-
<span className="flex size-[60px] items-center justify-center rounded-full bg-lavender-500 text-white transition-all group-hover:scale-[0.98] group-active:scale-95">
493-
<ArrowDownLeft01Icon className="size-6" strokeWidth={2} />
494-
</span>
495-
<span className="text-sm font-medium text-text-body dark:text-white">
496-
Fund
497-
</span>
498-
</button>
535+
{!isInjectedWallet && (
536+
<div className="flex flex-row items-start justify-between gap-2">
537+
<button
538+
type="button"
539+
title="Fund wallet"
540+
onClick={() => setIsFundModalOpen(true)}
541+
className="group flex flex-1 flex-col items-center gap-2"
542+
>
543+
<span className="flex size-[60px] items-center justify-center rounded-full bg-lavender-500 text-white transition-all group-hover:scale-[0.98] group-active:scale-95">
544+
<ArrowDownLeft01Icon className="size-6" strokeWidth={2} />
545+
</span>
546+
<span className="text-sm font-medium text-text-body dark:text-white">
547+
Fund
548+
</span>
549+
</button>
550+
<button
551+
type="button"
552+
title="Transfer funds"
553+
onClick={() => setIsTransferModalOpen(true)}
554+
className="group flex flex-1 flex-col items-center gap-2"
555+
>
556+
<span className="flex size-[60px] items-center justify-center rounded-full bg-accent-gray text-gray-900 transition-all group-hover:scale-[0.98] group-hover:bg-[#EBEBEF] group-active:scale-95 dark:bg-white/5 dark:text-white dark:group-hover:bg-white/10">
557+
<ArrowUpRight01Icon className="size-6" strokeWidth={2} />
558+
</span>
559+
<span className="text-sm font-medium text-text-body dark:text-white">
560+
Transfer
561+
</span>
562+
</button>
563+
{isBridgeUiVisible() && (
499564
<button
500565
type="button"
501-
title="Transfer funds"
502-
onClick={() => setIsTransferModalOpen(true)}
566+
title="Convert tokens"
567+
onClick={() => setIsConvertModalOpen(true)}
503568
className="group flex flex-1 flex-col items-center gap-2"
504569
>
505570
<span className="flex size-[60px] items-center justify-center rounded-full bg-accent-gray text-gray-900 transition-all group-hover:scale-[0.98] group-hover:bg-[#EBEBEF] group-active:scale-95 dark:bg-white/5 dark:text-white dark:group-hover:bg-white/10">
506-
<ArrowUpRight01Icon className="size-6" strokeWidth={2} />
571+
<CoinsSwapIcon className="size-6" strokeWidth={2} />
507572
</span>
508573
<span className="text-sm font-medium text-text-body dark:text-white">
509-
Transfer
574+
Convert
510575
</span>
511576
</button>
512-
{isBridgeUiVisible() && (
513-
<button
514-
type="button"
515-
title="Convert tokens"
516-
onClick={() => setIsConvertModalOpen(true)}
517-
className="group flex flex-1 flex-col items-center gap-2"
518-
>
519-
<span className="flex size-[60px] items-center justify-center rounded-full bg-accent-gray text-gray-900 transition-all group-hover:scale-[0.98] group-hover:bg-[#EBEBEF] group-active:scale-95 dark:bg-white/5 dark:text-white dark:group-hover:bg-white/10">
520-
<CoinsSwapIcon className="size-6" strokeWidth={2} />
521-
</span>
522-
<span className="text-sm font-medium text-text-body dark:text-white">
523-
Convert
524-
</span>
525-
</button>
526-
)}
527-
<button
528-
type="button"
529-
title="Earn yield on USDC via Vesu"
530-
onClick={() =>
531-
requestEarnAccess("earn-hub", onEarnAccessAction)
577+
)}
578+
{isEarnEnabled() && (
579+
<div
580+
className="relative flex flex-1 flex-col items-center"
581+
onMouseEnter={() =>
582+
!showEarnUi && setIsEarnHintOpen(true)
532583
}
533-
className="group flex flex-1 flex-col items-center gap-2"
534-
>
535-
<span className="flex size-[60px] items-center justify-center rounded-full bg-accent-gray text-gray-900 transition-all group-hover:scale-[0.98] group-hover:bg-[#EBEBEF] group-active:scale-95 dark:bg-white/5 dark:text-white dark:group-hover:bg-white/10">
536-
<Coins01Icon className="size-6" strokeWidth={2} />
537-
</span>
538-
<span className="text-sm font-medium text-text-body dark:text-white">
539-
Earn
540-
</span>
541-
</button>
542-
</div>
543-
) : (
544-
<div className="flex flex-row items-start justify-between gap-2">
545-
<button
546-
type="button"
547-
title="Fund wallet"
548-
onClick={() => setIsFundModalOpen(true)}
549-
className="group flex flex-1 flex-col items-center gap-2"
584+
onMouseLeave={() => setIsEarnHintOpen(false)}
550585
>
551-
<span className="flex size-[60px] items-center justify-center rounded-full bg-lavender-500 text-white transition-all group-hover:scale-[0.98] group-active:scale-95">
552-
<ArrowDownLeft01Icon className="size-6" strokeWidth={2} />
553-
</span>
554-
<span className="text-sm font-medium text-text-body dark:text-white">
555-
Fund
556-
</span>
557-
</button>
558-
<button
559-
type="button"
560-
title="Transfer funds"
561-
onClick={() => setIsTransferModalOpen(true)}
562-
className="group flex flex-1 flex-col items-center gap-2"
563-
>
564-
<span className="flex size-[60px] items-center justify-center rounded-full bg-accent-gray text-gray-900 transition-all group-hover:scale-[0.98] group-hover:bg-[#EBEBEF] group-active:scale-95 dark:bg-white/5 dark:text-white dark:group-hover:bg-white/10">
565-
<ArrowUpRight01Icon className="size-6" strokeWidth={2} />
566-
</span>
567-
<span className="text-sm font-medium text-text-body dark:text-white">
568-
Transfer
569-
</span>
570-
</button>
571-
{isBridgeUiVisible() && (
572586
<button
573587
type="button"
574-
title="Convert tokens"
575-
onClick={() => setIsConvertModalOpen(true)}
576-
className="group flex flex-1 flex-col items-center gap-2"
588+
title={
589+
showEarnUi
590+
? "Earn yield on USDC via Vesu"
591+
: "Earn is currently available on Starknet"
592+
}
593+
onClick={() => {
594+
if (showEarnUi) {
595+
requestEarnAccess(
596+
"earn-hub",
597+
onEarnAccessAction,
598+
);
599+
} else {
600+
setIsEarnHintOpen(false);
601+
setIsEarnUnavailableModalOpen(true);
602+
}
603+
}}
604+
className="group flex flex-col items-center gap-2"
577605
>
578606
<span className="flex size-[60px] items-center justify-center rounded-full bg-accent-gray text-gray-900 transition-all group-hover:scale-[0.98] group-hover:bg-[#EBEBEF] group-active:scale-95 dark:bg-white/5 dark:text-white dark:group-hover:bg-white/10">
579-
<CoinsSwapIcon className="size-6" strokeWidth={2} />
607+
<Coins01Icon className="size-6" strokeWidth={2} />
580608
</span>
581-
<span className="text-sm font-medium text-text-body dark:text-white">
582-
Convert
609+
<span className="flex items-center gap-1 text-sm font-medium text-text-body dark:text-white">
610+
Earn
611+
{!showEarnUi && (
612+
<InformationCircleIcon className="size-4 text-text-secondary dark:text-white/50" />
613+
)}
583614
</span>
584615
</button>
585-
)}
586-
</div>
587-
))}
616+
<EarnUnavailableTooltip
617+
isOpen={isEarnHintOpen}
618+
onClose={() => setIsEarnHintOpen(false)}
619+
/>
620+
</div>
621+
)}
622+
</div>
623+
)}
588624
</div>
589625
</div>
590626

@@ -898,6 +934,11 @@ export const WalletDetails = () => {
898934
>
899935
<BridgeForm onClose={() => setIsConvertModalOpen(false)} onBridgeSubmit={trackBridge} />
900936
</AnimatedModal>
937+
938+
<EarnUnavailableModal
939+
isOpen={isEarnUnavailableModalOpen}
940+
onClose={() => setIsEarnUnavailableModalOpen(false)}
941+
/>
901942
</>
902943
)}
903944

0 commit comments

Comments
 (0)