diff --git a/src/app/[lng]/quests/[slug]/page.tsx b/src/app/[lng]/quests/[slug]/page.tsx new file mode 100644 index 000000000..acbc428c9 --- /dev/null +++ b/src/app/[lng]/quests/[slug]/page.tsx @@ -0,0 +1,19 @@ +import { getQuestBySlug } from 'src/app/lib/getQuestBySlug'; +import QuestPage from 'src/app/ui/quests/QuestMissionPage'; + +// todo: adjust metadata for quests-page +// export async function generateMetadata(): Promise { +// return { +// title: 'Jumper | Quests', +// description: 'Dive into the Quests', +// alternates: { +// canonical: `${process.env.NEXT_PUBLIC_SITE_URL}/quests/`, +// }, +// }; +// } + +export default async function Page({ params }: { params: { slug: string } }) { + const { data, url } = await getQuestBySlug(params.slug); + + return ; +} diff --git a/src/app/[lng]/quests/layout.tsx b/src/app/[lng]/quests/layout.tsx new file mode 100644 index 000000000..f8b2041c2 --- /dev/null +++ b/src/app/[lng]/quests/layout.tsx @@ -0,0 +1,22 @@ +import { ThemeProviderV2 } from '@/providers/ThemeProviderV2'; +import { ThemeProvider as NextThemeProvider } from 'next-themes'; +import React from 'react'; +import { Layout } from 'src/Layout'; + +export default async function PartnerThemeLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} diff --git a/src/app/ui/quests/QuestMissionPage.tsx b/src/app/ui/quests/QuestMissionPage.tsx new file mode 100644 index 000000000..d172c4e43 --- /dev/null +++ b/src/app/ui/quests/QuestMissionPage.tsx @@ -0,0 +1,12 @@ +'use client'; + +import { QuestsMissionPage } from 'src/components/Quests/QuestPage/QuestsMissionPage'; +import { JUMPER_LOYALTY_PATH } from 'src/const/urls'; + +const QuestPage = ({ quest, url }: any) => { + return ( + + ); +}; + +export default QuestPage; diff --git a/src/app/ui/quests/Quests.tsx b/src/app/ui/quests/Quests.tsx new file mode 100644 index 000000000..d276fbbff --- /dev/null +++ b/src/app/ui/quests/Quests.tsx @@ -0,0 +1,9 @@ +'use client'; + +import { Quests as QuestsComponent } from 'src/components/Quests'; + +const Quests = () => { + return ; +}; + +export default Quests; diff --git a/src/components/Alerts/ChainAlert.tsx b/src/components/Alerts/ChainAlert.tsx index a107752d1..f5d421ad6 100644 --- a/src/components/Alerts/ChainAlert.tsx +++ b/src/components/Alerts/ChainAlert.tsx @@ -4,6 +4,8 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { InfoAlert } from '.'; import { InfoAlertClickable } from './InfoAlert/InfoAlertClickable'; +import { useCheckWalletLinking } from 'src/hooks/useCheckWalletLinking'; +import { useAccounts } from 'src/hooks/useAccounts'; export const ChainAlert = () => { const { t } = useTranslation(); @@ -14,12 +16,17 @@ export const ChainAlert = () => { const [title, setTitle] = useState(''); const [subtitle, setSubtitle] = useState(''); const [buttonText, setButtontext] = useState(''); + const { account } = useAccounts(); + const { isSuccess: isWalletCheckSuccess, isWalletLinked } = + useCheckWalletLinking({ + userAddress: account?.address, + checkWalletLinking: + sourceChainToken?.chainId === ChainId.SEI || + destinationChainToken?.chainId === ChainId.SEI, + }); useEffect(() => { - if ( - sourceChainToken?.chainId === ChainId.SEI || - destinationChainToken?.chainId === ChainId.SEI - ) { + if (!isWalletLinked && isWalletCheckSuccess) { setIsClickable(true); setChainId(ChainId.SEI); setTitle(t('seiAlert.title')); @@ -29,7 +36,7 @@ export const ChainAlert = () => { setIsClickable(false); setChainId(0); } - }, [destinationChainToken, sourceChainToken, t]); + }, [destinationChainToken, sourceChainToken, t, isWalletLinked]); return ( <> diff --git a/src/components/Blog/BlogArticle/BlogArticle.style.ts b/src/components/Blog/BlogArticle/BlogArticle.style.ts index 0eed1363b..b228b07a2 100644 --- a/src/components/Blog/BlogArticle/BlogArticle.style.ts +++ b/src/components/Blog/BlogArticle/BlogArticle.style.ts @@ -170,11 +170,11 @@ export const BlogArticleContentContainer = styled(Box)(({ theme }) => ({ }, [theme.breakpoints.up('sm' as Breakpoint)]: { - margin: theme.spacing(4, 'auto'), + margin: theme.spacing(0, 'auto'), maxWidth: '100%', }, [theme.breakpoints.up('md' as Breakpoint)]: { - margin: theme.spacing(8, 'auto'), + margin: theme.spacing(0, 'auto'), maxWidth: '100%', }, [theme.breakpoints.up('lg' as Breakpoint)]: { @@ -195,10 +195,10 @@ export const BlogMetaContainer = styled(Box)(({ theme }) => ({ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', - marginTop: theme.spacing(8), gap: theme.spacing(2), flexDirection: 'column', [theme.breakpoints.up('sm' as Breakpoint)]: { + marginTop: theme.spacing(8), alignItems: 'center', flexDirection: 'row', }, diff --git a/src/components/Blog/BlogArticle/BlogArticle.tsx b/src/components/Blog/BlogArticle/BlogArticle.tsx index 39600c4cb..16d6d6caf 100644 --- a/src/components/Blog/BlogArticle/BlogArticle.tsx +++ b/src/components/Blog/BlogArticle/BlogArticle.tsx @@ -75,58 +75,72 @@ export const BlogArticle = ({ return ( <> - - {!!tags?.data[0]?.attributes.Title ? ( - - {tags.data[0].attributes?.Title} - - ) : ( - - )} - {!!createdAt ? ( - - - {formatDate(publishedAt || createdAt)} - - {t('blog.minRead', { minRead: minRead })} - - ) : ( - - )} - - {title ? ( - {title} - ) : ( - - )} - - - {author?.data?.attributes?.Avatar.data?.attributes?.url ? ( - + + + {tags?.data[0]?.attributes.Title ? ( + + {tags.data[0].attributes?.Title} + ) : ( - + )} - {author?.data ? ( - - {author.data?.attributes.Name} - + {createdAt ? ( + + + {formatDate(publishedAt || createdAt)} + + {t('blog.minRead', { minRead: minRead })} + ) : ( - + )} - - - - + + {title ? ( + {title} + ) : ( + + )} + + {subtitle ? ( + + {subtitle} + + ) : ( + + )} + + + + {author?.data?.attributes?.Avatar.data?.attributes?.url ? ( + + ) : ( + + )} + {author?.data ? ( + + {author.data?.attributes.Name} + + ) : ( + + )} + + + + + {image?.data && ( - {subtitle ? ( - - {subtitle} - - ) : ( - - )} {content ? ( ({ border: 'unset', padding: theme.spacing(2), borderRadius: '32px', - background: - theme.palette.mode === 'light' - ? theme.palette.white.main - : theme.palette.alphaLight200.main, + background: theme.palette.bgTertiary.main, transition: 'background-color 250ms', [theme.breakpoints.up('sm' as Breakpoint)]: { minWidth: 250, diff --git a/src/components/Blog/BlogCarousel/BlogCarousel.style.ts b/src/components/Blog/BlogCarousel/BlogCarousel.style.ts index 9306a1e8a..fdad431e2 100644 --- a/src/components/Blog/BlogCarousel/BlogCarousel.style.ts +++ b/src/components/Blog/BlogCarousel/BlogCarousel.style.ts @@ -1,19 +1,13 @@ import type { BoxProps, Breakpoint } from '@mui/material'; -import { Box, alpha, styled } from '@mui/material'; +import { Box, styled } from '@mui/material'; import { ButtonPrimary } from '../../Button'; export const BlogCarouselContainer = styled(Box)(({ theme }) => ({ - backgroundColor: - theme.palette.mode === 'light' - ? alpha(theme.palette.white.main, 0.48) - : alpha(theme.palette.white.main, 0.12), + backgroundColor: theme.palette.bgSecondary.main, borderRadius: '32px', padding: theme.spacing(2), margin: theme.spacing(6, 2, 0), - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, [theme.breakpoints.up('sm' as Breakpoint)]: { margin: theme.spacing(2, 8, 0), padding: theme.spacing(3), diff --git a/src/components/Blog/CTAs/InstructionsAccordion/InstructionsAccordionItem.tsx b/src/components/Blog/CTAs/InstructionsAccordion/InstructionsAccordionItem.tsx index 2962f715b..86c250494 100644 --- a/src/components/Blog/CTAs/InstructionsAccordion/InstructionsAccordionItem.tsx +++ b/src/components/Blog/CTAs/InstructionsAccordion/InstructionsAccordionItem.tsx @@ -4,7 +4,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import type { Breakpoint } from '@mui/material'; import { Box, Typography, useMediaQuery, useTheme } from '@mui/material'; import type { MouseEventHandler } from 'react'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { sora } from 'src/fonts/fonts'; import type { InstructionItemProps } from '.'; import { @@ -63,11 +63,16 @@ export const InstructionsAccordionItem = ({ const isSuperfest = variant === 'superfest'; + useEffect(() => { + if (variant === 'superfest') { + setOpen(true); + } + }, []); + return ( @@ -142,6 +147,10 @@ export const InstructionsAccordionItem = ({ component={'span'} mr={'8px'} sx={{ + color: + theme.palette.mode === 'light' + ? '#000000' + : '#FFFFFF', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 208, @@ -152,7 +161,14 @@ export const InstructionsAccordionItem = ({ > {buttonTitles[i]} - + diff --git a/src/components/Blog/CustomRichBlocks.style.ts b/src/components/Blog/CustomRichBlocks.style.ts index a8b942c9b..a29e3dd08 100644 --- a/src/components/Blog/CustomRichBlocks.style.ts +++ b/src/components/Blog/CustomRichBlocks.style.ts @@ -34,12 +34,7 @@ export const BlogParagraph = styled(Typography, { return { display: 'inline', fontWeight: bold ? 700 : 400, - color: alpha( - theme.palette.mode === 'light' - ? theme.palette.black.main - : theme.palette.white.main, - 0.75, - ), + color: theme.palette.text.secondary, textDecoration: textDecoration, fontStyle: italic ? 'italic' : 'normal', fontSize: '18px', diff --git a/src/components/Blog/CustomRichBlocks.tsx b/src/components/Blog/CustomRichBlocks.tsx index 856312f2a..bf8cec473 100644 --- a/src/components/Blog/CustomRichBlocks.tsx +++ b/src/components/Blog/CustomRichBlocks.tsx @@ -141,7 +141,6 @@ export const CustomRichBlocks = ({ }, paragraph: ({ children }: ParagraphElement) => { - console.log('PARAGRAPH', children); if (children[0].props.text.includes(' ); } catch (error) { - //// console.log(error); + console.log(error); return; } } else { diff --git a/src/components/Blog/FeaturedArticle/FeaturedArticle.style.ts b/src/components/Blog/FeaturedArticle/FeaturedArticle.style.ts index 2d9d9b083..6e8743e06 100644 --- a/src/components/Blog/FeaturedArticle/FeaturedArticle.style.ts +++ b/src/components/Blog/FeaturedArticle/FeaturedArticle.style.ts @@ -9,15 +9,9 @@ export const FeaturedArticleLink = styled(Link, { })(({ theme }) => ({ position: 'relative', borderRadius: 32, - backgroundColor: - theme.palette.mode === 'light' - ? alpha(theme.palette.white.main, 0.48) - : alpha(theme.palette.white.main, 0.12), + backgroundColor: theme.palette.bgSecondary.main, transition: 'background-color 250ms', - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, display: 'grid', gridTemplateRows: '1fr', textDecoration: 'none', diff --git a/src/components/FeatureCards/FeatureCard.style.tsx b/src/components/FeatureCards/FeatureCard.style.tsx index 7243b95cd..5713066aa 100644 --- a/src/components/FeatureCards/FeatureCard.style.tsx +++ b/src/components/FeatureCards/FeatureCard.style.tsx @@ -36,10 +36,7 @@ export const FCard = styled(MuiCard, { isDarkCard || theme.palette.mode === 'dark' ? '#20223D' : '#FFFFFF' } 506px 349px)`, backgroundSize: 'contain', - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, ':last-child': { marginBottom: 0, }, diff --git a/src/components/JoinDiscordBanner/JoinDiscordBanner.style.ts b/src/components/JoinDiscordBanner/JoinDiscordBanner.style.ts index 082d1a256..90444d683 100644 --- a/src/components/JoinDiscordBanner/JoinDiscordBanner.style.ts +++ b/src/components/JoinDiscordBanner/JoinDiscordBanner.style.ts @@ -17,14 +17,8 @@ export const DiscordBannerLink = styled(Link)(({ theme }) => ({ justifyContent: 'center', gap: theme.spacing(1.5), alignItems: 'center', - backgroundColor: - theme.palette.mode === 'light' - ? alpha(theme.palette.white.main, 0.48) - : alpha(theme.palette.white.main, 0.12), - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + backgroundColor: theme.palette.bgSecondary.main, + boxShadow: theme.palette.shadow.main, borderRadius: '32px', cursor: 'pointer', padding: theme.spacing(6), diff --git a/src/components/MultisigConfirmationModal/MultisigConfirmationModal.style.ts b/src/components/MultisigConfirmationModal/MultisigConfirmationModal.style.ts index 8453f4a81..ba10faccc 100644 --- a/src/components/MultisigConfirmationModal/MultisigConfirmationModal.style.ts +++ b/src/components/MultisigConfirmationModal/MultisigConfirmationModal.style.ts @@ -27,10 +27,7 @@ export const MultisigConfirmationModalContainer = styled(Box)(({ theme }) => ({ theme.palette.mode === 'dark' ? theme.palette.surface2.main : theme.palette.surface1.main, - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, })); export const MultisigConfirmationModalButton = styled(Button)(({ theme }) => ({ diff --git a/src/components/MultisigConnectedAlert/MultisigConnectedAlert.style.ts b/src/components/MultisigConnectedAlert/MultisigConnectedAlert.style.ts index 42cdd203b..d15714d6d 100644 --- a/src/components/MultisigConnectedAlert/MultisigConnectedAlert.style.ts +++ b/src/components/MultisigConnectedAlert/MultisigConnectedAlert.style.ts @@ -25,10 +25,7 @@ export const MultisigConnectedAlertContainer = styled(Box)(({ theme }) => ({ theme.palette.mode === 'dark' ? theme.palette.surface2.main : theme.palette.surface1.main, - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, })); export const MultisigConnectedAlertButton = styled(Button)(({ theme }) => ({ diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index 8f308b0ad..fdfb75b79 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -4,6 +4,7 @@ import { usePathname, useRouter } from 'next/navigation'; import { JUMPER_LEARN_PATH, JUMPER_LOYALTY_PATH, + JUMPER_QUESTS_PATH, JUMPER_SCAN_PATH, JUMPER_TX_PATH, JUMPER_WALLET_PATH, @@ -28,6 +29,7 @@ export const Navbar = ({ disableNavbar = false }) => { pathname?.includes(JUMPER_SCAN_PATH) || pathname?.includes(JUMPER_TX_PATH) || pathname?.includes(JUMPER_WALLET_PATH); + const isQuestsPage = pathname?.includes(JUMPER_QUESTS_PATH); const { isSuperfest } = useSuperfest(); const { setWelcomeScreenClosed } = useWelcomeScreen(); @@ -56,7 +58,9 @@ export const Navbar = ({ disableNavbar = false }) => { /> {!isScanPage && !isLearnPage && !disableNavbar && ( - + )} diff --git a/src/components/ProfilePage/Leaderboard/Leaderboard.style.ts b/src/components/ProfilePage/Leaderboard/Leaderboard.style.ts index ffbd70253..166d5f353 100644 --- a/src/components/ProfilePage/Leaderboard/Leaderboard.style.ts +++ b/src/components/ProfilePage/Leaderboard/Leaderboard.style.ts @@ -1,17 +1,10 @@ -import type { Breakpoint } from '@mui/material'; -import { Box, alpha, styled } from '@mui/material'; +import { Box, styled } from '@mui/material'; export const LeaderboardContainer = styled(Box)(({ theme }) => ({ - backgroundColor: - theme.palette.mode === 'light' - ? alpha(theme.palette.white.main, 0.48) - : alpha(theme.palette.white.main, 0.12), + backgroundColor: theme.palette.bgSecondary.main, borderRadius: '32px', width: '100%', padding: theme.spacing(4), margin: theme.spacing(4, 0, 0), - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, })); diff --git a/src/components/ProfilePage/ProfilePage.style.ts b/src/components/ProfilePage/ProfilePage.style.ts index d2a4ae7ac..6a28d32f4 100644 --- a/src/components/ProfilePage/ProfilePage.style.ts +++ b/src/components/ProfilePage/ProfilePage.style.ts @@ -16,10 +16,7 @@ export const ProfilePageHeaderBox = styled(Box)(({ theme }) => ({ ? theme.palette.grey[100] : alpha(theme.palette.grey[100], 0.08), borderRadius: '24px', - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, })); export const NoSelectTypography = styled(Typography)(({ theme }) => ({ diff --git a/src/components/ProfilePage/ProfilePage.tsx b/src/components/ProfilePage/ProfilePage.tsx index d31a8f97d..e58c83918 100644 --- a/src/components/ProfilePage/ProfilePage.tsx +++ b/src/components/ProfilePage/ProfilePage.tsx @@ -12,6 +12,13 @@ import { import { QuestCarousel } from './QuestCarousel/QuestCarousel'; import { QuestCompletedList } from './QuestsCompleted/QuestsCompletedList'; import { Leaderboard } from './Leaderboard/Leaderboard'; +import { RewardsCarousel } from './Rewards/RewardsCarousel'; +import { useMerklRewards } from 'src/hooks/useMerklRewardsOnSpecificToken'; +import { + REWARD_TOKEN_ADDRESS, + REWARD_TOKEN_CHAINID, + REWARDS_CHAIN_IDS, +} from 'src/const/partnerRewardsTheme'; export const ProfilePage = () => { const { account } = useAccounts(); @@ -19,39 +26,67 @@ export const ProfilePage = () => { const { imageLink } = useMercleNft({ userAddress: account?.address }); const { quests, isQuestLoading } = useOngoingQuests(); + const { + availableRewards, + activeCampaigns, + pastCampaigns, + isLoading: isRewardLoading, + isSuccess: isRewardSuccess, + } = useMerklRewards({ + rewardChainId: REWARD_TOKEN_CHAINID, + userAddress: account?.address, + rewardToken: REWARD_TOKEN_ADDRESS, + }); + return ( - - - - - - - - - - - - - + <> + + + + + + + + + + + + + + - - - + + + + - - + + ); }; diff --git a/src/components/ProfilePage/QuestCard/QuestCard.tsx b/src/components/ProfilePage/QuestCard/QuestCard.tsx index 88b68d991..7d30312f0 100644 --- a/src/components/ProfilePage/QuestCard/QuestCard.tsx +++ b/src/components/ProfilePage/QuestCard/QuestCard.tsx @@ -3,6 +3,12 @@ import { useTheme } from '@mui/material'; import Image from 'next/image'; import Link from 'next/link'; import { useTranslation } from 'react-i18next'; +import { + TrackingAction, + TrackingCategory, + TrackingEventParameter, +} from 'src/const/trackingKeys'; +import { useUserTracking } from 'src/hooks/userTracking'; import { Button } from '../../Button'; import { XPIcon } from '../../illustrations/XPIcon'; import { @@ -22,8 +28,10 @@ import { interface QuestCardProps { active?: boolean; - title?: string; + title: string; + id?: number | string; image?: string; + label?: string; points?: number; link?: string; startDate?: string; @@ -43,8 +51,10 @@ function getStringDateFormatted(startDate: string, endDate: string): string { export const QuestCard = ({ active, title, + id, image, points, + label, link, startDate, endDate, @@ -54,11 +64,27 @@ export const QuestCard = ({ const theme = useTheme(); const { t } = useTranslation(); + const { trackEvent } = useUserTracking(); + const handleClick = () => { + trackEvent({ + category: TrackingCategory.Quests, + action: TrackingAction.ClickQuestCard, + label: 'click-quest-card', + data: { + [TrackingEventParameter.QuestCardTitle]: title, + [TrackingEventParameter.QuestCardLabel]: label || '', + [TrackingEventParameter.QuestCardId]: id || '', + [TrackingEventParameter.QuestCardPlatform]: platformName || '', + }, + }); + }; + return ( {image && ( @@ -162,27 +188,20 @@ export const QuestCard = ({ ) : null} {active && link ? ( - - - + {t('questCard.join')} + + ) : null} diff --git a/src/components/ProfilePage/QuestCard/VoidQuestCard.style.ts b/src/components/ProfilePage/QuestCard/VoidQuestCard.style.ts index 868e9440c..eb2a4f6c9 100644 --- a/src/components/ProfilePage/QuestCard/VoidQuestCard.style.ts +++ b/src/components/ProfilePage/QuestCard/VoidQuestCard.style.ts @@ -6,8 +6,8 @@ export const VoidQuestCardContainer = styled(Box)(({ theme }) => ({ justifyContent: 'center', backgroundColor: theme.palette.mode === 'light' - ? theme.palette.grey[100] - : alpha(theme.palette.grey[100], 0.08), + ? '#FFFFFF' + : alpha(theme.palette.white.main, 0.08), height: '416px', width: '272px', borderRadius: '24px', diff --git a/src/components/ProfilePage/QuestCardDetailled/QuestCard.style.ts b/src/components/ProfilePage/QuestCardDetailled/QuestCard.style.ts new file mode 100644 index 000000000..2eda18ae6 --- /dev/null +++ b/src/components/ProfilePage/QuestCardDetailled/QuestCard.style.ts @@ -0,0 +1,98 @@ +import type { BoxProps } from '@mui/material'; +import { Box, alpha, styled } from '@mui/material'; + +export const QuestCardMainBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + height: 450, + width: 288, + textAlign: 'center', + borderBottomLeftRadius: '16px', + borderBottomRightRadius: '16px', +})); + +export const QuestCardBottomBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + height: '33%', + flexGrow: 1, + paddingTop: '16px', + paddingBottom: '24px', + paddingLeft: '16px', + paddingRight: '16px', + backgroundColor: + theme.palette.mode === 'light' + ? '#FFFFFF' + : alpha(theme.palette.white.main, 0.08), + borderBottomLeftRadius: '8px', + borderBottomRightRadius: '8px', +})); + +export const QuestCardTitleBox = styled(Box)(() => ({ + display: 'flex', + alignItems: 'center', + alignContent: 'center', + textAlign: 'left', +})); + +export interface QuestCardInfoBoxProps extends Omit { + points?: number; +} + +export const QuestCardInfoBox = styled(Box, { + shouldForwardProp: (prop) => prop !== 'points', +})(({ points }) => ({ + display: 'flex', + flexDirection: 'column', +})); + +export const CompletedBox = styled(Box)(() => ({ + display: 'flex', + alignItems: 'center', + backgroundColor: '#d6ffe7', + borderRadius: '128px', + padding: '4px', + width: '50%', +})); + +export interface QuestPlatformMainBoxProps extends Omit { + platformName?: string; +} + +export const QuestPlatformMainBox = styled(Box, { + shouldForwardProp: (prop) => prop !== 'platformName', +})(({ platformName }) => ({ + display: 'flex', + justifyContent: platformName ? 'space-between' : 'flex-end', + alignItems: 'center', +})); + +export interface XPDisplayBoxProps extends Omit { + active?: boolean; +} + +export const XPDisplayBox = styled(Box, { + shouldForwardProp: (prop) => prop !== 'active', +})(({ active }) => ({ + marginRight: active ? '8px' : undefined, + display: 'flex', + height: '28px', + alignContent: 'center', + justifyContent: 'space-between', + alignItems: 'center', + borderRadius: '128px', + padding: '8px', +})); + +export const XPIconBox = styled(Box)(({ theme }) => ({ + display: 'flex', + alignContent: 'flex-end', + justifyContent: 'flex-end', +})); + +export const OPBadgeRelativeBox = styled(Box)(({ theme }) => ({ + position: 'relative', + bottom: '16px', + right: '32px', +})); diff --git a/src/components/ProfilePage/QuestCardDetailled/QuestCardDetailled.tsx b/src/components/ProfilePage/QuestCardDetailled/QuestCardDetailled.tsx new file mode 100644 index 000000000..d4acc2354 --- /dev/null +++ b/src/components/ProfilePage/QuestCardDetailled/QuestCardDetailled.tsx @@ -0,0 +1,212 @@ +import Image from 'next/image'; +import { useRouter } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; +import { Button } from '../../Button'; +import { SuperfestXPIcon } from '../../illustrations/XPIcon'; +import Link from 'next/link'; +import { + OPBadgeRelativeBox, + QuestCardBottomBox, + QuestCardInfoBox, + QuestCardMainBox, + QuestCardTitleBox, + XPDisplayBox, + XPIconBox, +} from './QuestCard.style'; +import { OPBadge } from 'src/components/illustrations/OPBadge'; +import { Box, Typography, useTheme } from '@mui/material'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import { useMissionsMaxAPY } from 'src/hooks/useMissionsMaxAPY'; +import { APYIcon } from 'src/components/illustrations/APYIcon'; +import { Chain } from 'src/components/Superfest/SuperfestPage/Banner/Banner'; +import { FlexSpaceBetweenBox } from 'src/components/Superfest/Superfest.style'; +import { FlexCenterRowBox } from 'src/components/Superfest/SuperfestPage/SuperfestMissionPage.style'; +import { + PROFILE_CAMPAIGN_DARK_COLOR, + PROFILE_CAMPAIGN_LIGHT_COLOR, +} from 'src/const/partnerRewardsTheme'; + +export interface RewardsInterface { + logo: string; + name: string; + amount: number; +} + +interface QuestCardProps { + active?: boolean; + title?: string; + image?: string; + points?: number; + link?: string; + startDate?: string; + endDate?: string; + platformName?: string; + platformImage?: string; + slug?: string; + chains?: Chain[]; + rewards?: RewardsInterface; + completed?: boolean; + claimingIds?: string[]; + variableWeeklyAPY?: boolean; + rewardRange?: string; +} + +export const QuestCardDetailled = ({ + active, + title, + image, + points, + link, + startDate, + endDate, + slug, + chains, + rewards, + completed, + claimingIds, + variableWeeklyAPY, + rewardRange, +}: QuestCardProps) => { + const theme = useTheme(); + const { t } = useTranslation(); + const router = useRouter(); + const { apy, isLoading, isSuccess } = useMissionsMaxAPY(claimingIds); + + return ( + + + + {image && ( + Quest Card Image + )} + + + + + {title && title.length > 22 ? `${title.slice(0, 21)}...` : title} + + + + + {chains?.map((elem: Chain, i: number) => { + return ( + {elem.name} + ); + })} + + {points ? ( + + {apy > 0 && !variableWeeklyAPY && ( + + + {`${Number(apy).toFixed(1)}%`} + + + + + + )} + {variableWeeklyAPY && ( + + + {rewardRange ? rewardRange : `VAR.%`} + + + + + + )} + + + {`${points}`} + + + {!completed ? ( + + ) : ( + + )} + + + + ) : undefined} + + + {active && slug ? ( + + ) : null} + + + + + ); +}; diff --git a/src/components/ProfilePage/QuestCardDetailled/QuestCardSkeleton.style.ts b/src/components/ProfilePage/QuestCardDetailled/QuestCardSkeleton.style.ts new file mode 100644 index 000000000..cd83e39de --- /dev/null +++ b/src/components/ProfilePage/QuestCardDetailled/QuestCardSkeleton.style.ts @@ -0,0 +1,17 @@ +import { Box, alpha, styled } from '@mui/material'; + +export const QuestCardSkeletonContainer = styled(Box)(({ theme }) => ({ + backgroundColor: + theme.palette.mode === 'light' + ? '#f5f5f5' + : alpha(theme.palette.white.main, 0.08), + height: 450, + width: 288, + textAlign: 'center', + borderRadius: '8px', + border: 16, + borderColor: + theme.palette.mode === 'light' + ? '#ffffff' + : alpha(theme.palette.white.main, 0.08), +})); diff --git a/src/components/ProfilePage/QuestCardDetailled/QuestCardSkeleton.tsx b/src/components/ProfilePage/QuestCardDetailled/QuestCardSkeleton.tsx new file mode 100644 index 000000000..21f38f1d7 --- /dev/null +++ b/src/components/ProfilePage/QuestCardDetailled/QuestCardSkeleton.tsx @@ -0,0 +1,20 @@ +import { Skeleton } from '@mui/material'; +import { QuestCardSkeletonContainer } from './QuestCardSkeleton.style'; + +export const QuestCardSkeleton = () => { + return ( + + + + ); +}; diff --git a/src/components/ProfilePage/QuestCardDetailled/VoidQuestCard.style.ts b/src/components/ProfilePage/QuestCardDetailled/VoidQuestCard.style.ts new file mode 100644 index 000000000..4b6caa64f --- /dev/null +++ b/src/components/ProfilePage/QuestCardDetailled/VoidQuestCard.style.ts @@ -0,0 +1,19 @@ +import { Box, alpha, styled } from '@mui/material'; + +export const VoidQuestCardContainer = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: + theme.palette.mode === 'light' + ? theme.palette.grey[100] + : alpha(theme.palette.grey[100], 0.08), + height: '416px', + width: '256px', + borderRadius: '8px', + border: 16, + borderColor: + theme.palette.mode === 'light' + ? theme.palette.white.main + : alpha(theme.palette.white.main, 0.08), +})); diff --git a/src/components/ProfilePage/QuestCardDetailled/VoidQuestCard.tsx b/src/components/ProfilePage/QuestCardDetailled/VoidQuestCard.tsx new file mode 100644 index 000000000..0bba66993 --- /dev/null +++ b/src/components/ProfilePage/QuestCardDetailled/VoidQuestCard.tsx @@ -0,0 +1,21 @@ +import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; +import { alpha, useTheme } from '@mui/material'; +import { VoidQuestCardContainer } from './VoidQuestCard.style'; + +export const VoidQuestCard = () => { + const theme = useTheme(); + return ( + + + + ); +}; diff --git a/src/components/ProfilePage/QuestCarousel/QuestCarousel.style.ts b/src/components/ProfilePage/QuestCarousel/QuestCarousel.style.ts index faaab9f2d..c0a79e9f6 100644 --- a/src/components/ProfilePage/QuestCarousel/QuestCarousel.style.ts +++ b/src/components/ProfilePage/QuestCarousel/QuestCarousel.style.ts @@ -1,19 +1,13 @@ import type { BoxProps, Breakpoint } from '@mui/material'; -import { Box, alpha, styled } from '@mui/material'; +import { Box, styled } from '@mui/material'; import { ButtonPrimary } from '../../Button'; export const QuestCarouselContainer = styled(Box)(({ theme }) => ({ - backgroundColor: - theme.palette.mode === 'light' - ? alpha(theme.palette.white.main, 0.48) - : alpha(theme.palette.white.main, 0.12), + backgroundColor: theme.palette.bgSecondary.main, borderRadius: '32px', padding: theme.spacing(2), margin: theme.spacing(6, 2, 0), - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, [theme.breakpoints.up('sm' as Breakpoint)]: { margin: theme.spacing(2, 8, 0), padding: theme.spacing(3), diff --git a/src/components/ProfilePage/QuestCarousel/QuestCarousel.tsx b/src/components/ProfilePage/QuestCarousel/QuestCarousel.tsx index 7d12d2de0..b43a13400 100644 --- a/src/components/ProfilePage/QuestCarousel/QuestCarousel.tsx +++ b/src/components/ProfilePage/QuestCarousel/QuestCarousel.tsx @@ -3,15 +3,23 @@ import { useOngoingQuests } from '@/hooks/useOngoingQuests'; import type { Quest } from '@/types/loyaltyPass'; import { useTranslation } from 'react-i18next'; import { QuestCard } from '../QuestCard/QuestCard'; -import { QuestCardSkeleton } from '../QuestCard/QuestCardSkeleton'; import { QuestCarouselContainer } from './QuestCarousel.style'; +import { TempTitle } from './TempTitle/TempTitle'; +import { QuestCardDetailled } from '../QuestCardDetailled/QuestCardDetailled'; +import { QuestCardSkeleton } from '../QuestCardDetailled/QuestCardSkeleton'; +import { checkInclusion } from 'src/components/Superfest/ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel'; interface QuestCarouselProps { quests?: Quest[]; loading: boolean; + pastCampaigns?: string[]; } -export const QuestCarousel = ({ quests, loading }: QuestCarouselProps) => { +export const QuestCarousel = ({ + quests, + loading, + pastCampaigns, +}: QuestCarouselProps) => { const { url } = useOngoingQuests(); const { t } = useTranslation(); @@ -21,21 +29,36 @@ export const QuestCarousel = ({ quests, loading }: QuestCarouselProps) => { <> {!isNotLive ? ( + {!loading ? quests?.map((quest: Quest, index: number) => { + const baseURL = quest.attributes.Image?.data?.attributes?.url; + const imgURL = new URL(baseURL, url.origin); + const rewards = + quest.attributes.CustomInformation?.['rewards']; + const rewardType = + quest.attributes?.CustomInformation?.['rewardType']; + const rewardRange = + quest.attributes?.CustomInformation?.['rewardRange']; + const chains = quest.attributes.CustomInformation?.['chains']; + const claimingIds = + quest.attributes?.CustomInformation?.['claimingIds']; + const rewardsIds = + quest.attributes?.CustomInformation?.['rewardsIds']; + + //todo: exclude in a dedicated helper function + let completed = false; + if (rewardsIds && pastCampaigns) { + completed = checkInclusion(pastCampaigns, rewardsIds); + } + return ( - { quest?.attributes.quests_platform?.data?.attributes ?.Name } - platformImage={new URL( - quest.attributes.quests_platform?.data?.attributes?.Logo?.data?.attributes?.url, - url.origin, - ).toString()} + slug={quest?.attributes.Slug} + chains={chains} + rewards={rewards} + completed={completed} + claimingIds={claimingIds} + variableWeeklyAPY={ + quest?.attributes.Points > 0 && rewardType === 'weekly' + } + rewardRange={rewardRange} /> ); }) diff --git a/src/components/ProfilePage/QuestCarousel/TempTitle/TempTitle.tsx b/src/components/ProfilePage/QuestCarousel/TempTitle/TempTitle.tsx new file mode 100644 index 000000000..3a6798f84 --- /dev/null +++ b/src/components/ProfilePage/QuestCarousel/TempTitle/TempTitle.tsx @@ -0,0 +1,67 @@ +import { Box, Typography, useTheme } from '@mui/material'; +import Image from 'next/image'; +import { EarnedTypography } from '../../Rewards/RewardsCarousel.style'; +import { FlexCenterRowBox } from 'src/components/Superfest/SuperfestPage/SuperfestMissionPage.style'; +import { + PROFILE_CAMPAIGN_DARK_CHAIN, + PROFILE_CAMPAIGN_DARK_COLOR, + PROFILE_CAMPAIGN_LIGHT_CHAIN, + PROFILE_CAMPAIGN_LIGHT_COLOR, +} from 'src/const/partnerRewardsTheme'; + +export const TempTitle = () => { + const theme = useTheme(); + + const IMAGE_LOGO = + theme.palette.mode === 'dark' + ? PROFILE_CAMPAIGN_DARK_CHAIN + : PROFILE_CAMPAIGN_LIGHT_CHAIN; + + return ( + + + token image + + + + Super SEIyan Week + + {/* + $300,000 SEI rewards to win + */} + + + ); +}; diff --git a/src/components/ProfilePage/QuestsCompleted/QuestsCompletedList.style.ts b/src/components/ProfilePage/QuestsCompleted/QuestsCompletedList.style.ts index c11e5ad8f..037332bac 100644 --- a/src/components/ProfilePage/QuestsCompleted/QuestsCompletedList.style.ts +++ b/src/components/ProfilePage/QuestsCompleted/QuestsCompletedList.style.ts @@ -8,10 +8,7 @@ export const CompletedQuestContainer = styled(Box)(({ theme }) => ({ : alpha(theme.palette.white.main, 0.08), padding: theme.spacing(2), borderRadius: '32px', - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, [theme.breakpoints.up('sm' as Breakpoint)]: { margin: theme.spacing(8, 8, 0), padding: theme.spacing(6, 3), diff --git a/src/components/ProfilePage/QuestsCompleted/QuestsCompletedList.tsx b/src/components/ProfilePage/QuestsCompleted/QuestsCompletedList.tsx index d89867a59..3494bb7ef 100644 --- a/src/components/ProfilePage/QuestsCompleted/QuestsCompletedList.tsx +++ b/src/components/ProfilePage/QuestsCompleted/QuestsCompletedList.tsx @@ -43,6 +43,7 @@ export const QuestCompletedList = ({ return ( 0 ? 6 - pdas.length : 3 }, + { length: pdas && pdas?.length > 0 ? 4 - pdas.length : 2 }, () => 42, ).map((_, idx) => ( 42).map((_, idx) => ( + ? Array.from({ length: 2 }, () => 42).map((_, idx) => ( )) : null} diff --git a/src/components/ProfilePage/Rewards/RewardsAmountBox/RewardsAmountBox.style.ts b/src/components/ProfilePage/Rewards/RewardsAmountBox/RewardsAmountBox.style.ts new file mode 100644 index 000000000..3d6683e3f --- /dev/null +++ b/src/components/ProfilePage/Rewards/RewardsAmountBox/RewardsAmountBox.style.ts @@ -0,0 +1,16 @@ +import { Box, styled } from '@mui/material'; + +export const RewardsdMainBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', +})); + +export const RewardsRightBox = styled(Box)(({ theme }) => ({ + marginTop: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + flexGrow: 1, +})); diff --git a/src/components/ProfilePage/Rewards/RewardsAmountBox/RewardsAmountBox.tsx b/src/components/ProfilePage/Rewards/RewardsAmountBox/RewardsAmountBox.tsx new file mode 100644 index 000000000..a8f334e5f --- /dev/null +++ b/src/components/ProfilePage/Rewards/RewardsAmountBox/RewardsAmountBox.tsx @@ -0,0 +1,91 @@ +import { + Box, + type Theme, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; +import Image from 'next/image'; +import { FlexCenterRowBox } from 'src/components/Superfest/SuperfestPage/SuperfestMissionPage.style'; +import { + PROFILE_CAMPAIGN_DARK_CHAIN, + PROFILE_CAMPAIGN_DARK_TOKEN, + PROFILE_CAMPAIGN_LIGHT_CHAIN, + PROFILE_CAMPAIGN_LIGHT_TOKEN, + REWARDS_DECIMALS, +} from 'src/const/partnerRewardsTheme'; +import { useAccounts } from 'src/hooks/useAccounts'; + +export const RewardsAmountBox = ({ + isSuccess, + isConfirmed, + rewardAmount, +}: { + isSuccess: boolean; + isConfirmed: boolean; + rewardAmount: number; +}) => { + //HOOKS + const { account } = useAccounts(); + const theme = useTheme(); + const isMobile = useMediaQuery((theme: Theme) => + theme.breakpoints.down('md'), + ); + + //CONST + const REWARD_CHAIN_LOGO = + theme.palette.mode === 'dark' + ? PROFILE_CAMPAIGN_DARK_CHAIN + : PROFILE_CAMPAIGN_LIGHT_CHAIN; + const REWARD_TOKEN_LOGO = + theme.palette.mode === 'dark' + ? PROFILE_CAMPAIGN_DARK_TOKEN + : PROFILE_CAMPAIGN_LIGHT_TOKEN; + + return ( + + + token image + {isMobile ? undefined : ( + token image + )} + + + + {!account?.address || + (isSuccess && (rewardAmount === 0 || !rewardAmount)) || + isConfirmed + ? '0' + : rewardAmount + ? rewardAmount.toFixed(REWARDS_DECIMALS) + : '...'} + + + + ); +}; diff --git a/src/components/ProfilePage/Rewards/RewardsCarousel.style.ts b/src/components/ProfilePage/Rewards/RewardsCarousel.style.ts new file mode 100644 index 000000000..0e59fd853 --- /dev/null +++ b/src/components/ProfilePage/Rewards/RewardsCarousel.style.ts @@ -0,0 +1,121 @@ +import type { BoxProps, Breakpoint } from '@mui/material'; +import { + Box, + Typography, + styled, + IconButton as MuiIconButton, + alpha, + darken, +} from '@mui/material'; +import { sequel65 } from 'src/fonts/fonts'; +import { getContrastAlphaColor } from '@/utils/colors'; +import type { IconButtonProps } from '@mui/material'; +import { + PROFILE_CAMPAIGN_DARK_COLOR, + PROFILE_CAMPAIGN_LIGHT_COLOR, +} from 'src/const/partnerRewardsTheme'; + +export const RewardsCarouselContainer = styled(Box)(({ theme }) => ({ + backgroundColor: + theme.palette.mode === 'light' + ? '#FFFFFF' + : alpha(theme.palette.white.main, 0.08), + // backgroundColor: theme.palette.mode === 'dark' ? '#322153' : '#F6F5FA', + // backgroundColor: + // theme.palette.mode === 'dark' + // ? PROFILE_CAMPAIGN_DARK_COLOR + // : PROFILE_CAMPAIGN_LIGHT_COLOR, + display: 'flex', + width: '100%', + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', +})); + +export const RewardsCarouselHeader = styled(Box, { + shouldForwardProp: (prop) => prop !== 'styles', +})(({ theme }) => ({ + display: 'flex', + ...(theme.palette.mode === 'dark' && { + color: theme.palette.white.main, + }), + justifyContent: 'space-between', +})); + +export const RewardsCarouselTitle = styled(Typography, { + shouldForwardProp: (prop) => prop !== 'styles' && prop !== 'show', +})(({ theme }) => ({ + fontWeight: 700, + fontSize: '24px', + lineHeight: '32px', + color: 'inherit', + margin: theme.spacing(3, 1.5, 0), + [theme.breakpoints.up('sm' as Breakpoint)]: { + margin: theme.spacing(0, 1.5, 0), + }, + [theme.breakpoints.up('lg' as Breakpoint)]: { + justifyContent: 'flex-start', + margin: 0, + }, +})); + +export const RewardsCarouselMainBox = styled(Box)(({ theme }) => ({ + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + alignContent: 'center', + padding: '32px', + [theme.breakpoints.down('md' as Breakpoint)]: { + flexDirection: 'column', + alignItems: 'center', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + flexDirection: 'row', + }, +})); + +export const ClaimButtonBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', + [theme.breakpoints.down('md' as Breakpoint)]: { + width: '85%', + marginTop: '16px', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + flexDirection: 'row', + marginLeft: '32px', + width: '15%', + }, +})); + +export const EarnedTypography = styled(Typography)(({ theme }) => ({ + [theme.breakpoints.down('md' as Breakpoint)]: { + fontSize: '32px', + lineHeight: '32px', + fontWeight: 700, + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + fontSize: '32px', + lineHeight: '48px', + fontWeight: 700, + }, +})); + +export const RewardsOpenIconButton = styled(MuiIconButton, { + shouldForwardProp: (prop) => prop !== 'styles', +})(({ theme }) => ({ + color: theme.palette.white.main, + transition: 'background 0.3s', + width: '48px', + height: '48px', + backgroundColor: theme.palette.primary.main, + ':hover': { + color: '#ffffff', + backgroundColor: darken(theme.palette.primary.main, 0.16), + }, +})); diff --git a/src/components/ProfilePage/Rewards/RewardsCarousel.tsx b/src/components/ProfilePage/Rewards/RewardsCarousel.tsx new file mode 100644 index 000000000..9452b2ce5 --- /dev/null +++ b/src/components/ProfilePage/Rewards/RewardsCarousel.tsx @@ -0,0 +1,181 @@ +import { Box, Typography, useTheme } from '@mui/material'; +import { + RewardsCarouselContainer, + RewardsCarouselMainBox, + ClaimButtonBox, + EarnedTypography, + RewardsOpenIconButton, +} from './RewardsCarousel.style'; +import { RewardsAmountBox } from './RewardsAmountBox/RewardsAmountBox'; +import { Button } from 'src/components/Button'; +import { + useAccount, + useWriteContract, + useWaitForTransactionReceipt, + useSwitchChain, +} from 'wagmi'; +import { ChainId } from '@lifi/types'; +import { MerklDistribABI } from '../../../const/abi/merklABI'; +import OpenInNewIcon from '@mui/icons-material/OpenInNew'; +import { FlexCenterRowBox } from 'src/components/Superfest/SuperfestPage/SuperfestMissionPage.style'; +import { + PROFILE_CAMPAIGN_SCANNER, + REWARD_CLAIMING_ADDRESS, + REWARD_TOKEN_ADDRESS, + REWARD_TOKEN_CHAINID, +} from 'src/const/partnerRewardsTheme'; + +interface RewardsCarouselProps { + hideComponent: boolean; + rewardAmount: number; + accumulatedAmountForContractBN: string; + isMerklSuccess: boolean; + proof: string[]; +} + +const CLAIMING_CONTRACT_ADDRESS = REWARD_CLAIMING_ADDRESS; +const REWARD_TOKEN = REWARD_TOKEN_ADDRESS; +//TESTING +// const TEST_TOKEN = '0x41A65AAE5d1C8437288d5a29B4D049897572758E'; + +export const RewardsCarousel = ({ + hideComponent, + rewardAmount, + accumulatedAmountForContractBN, + isMerklSuccess, + proof, +}: RewardsCarouselProps) => { + const { address } = useAccount(); + const theme = useTheme(); + const { switchChainAsync } = useSwitchChain(); + const { data: hash, isPending, writeContract } = useWriteContract(); + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }); + + async function handleClick() { + try { + const { id } = await switchChainAsync({ + chainId: REWARD_TOKEN_CHAINID, + }); + + const canClaim = + id === REWARD_TOKEN_CHAINID && + address && + isMerklSuccess && + proof && + rewardAmount > 0; + + if (canClaim) { + writeContract({ + address: CLAIMING_CONTRACT_ADDRESS, + abi: MerklDistribABI, + functionName: 'claim', + // args: [[address], [REWARD_TOKEN], [rewardAmountBN], [proof]], // function claim(address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) + // TESTING + args: [ + [address], + [REWARD_TOKEN], + [accumulatedAmountForContractBN], + [proof], + ], // function claim(address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) + // TESTING + // args: [ + // [address], + // [TEST_TOKEN], + // [accumulatedAmountForContractBN], + // [proof], + // ], // function claim(address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) + }); + } + } catch (err) { + console.log(err); + } + } + + return ( + <> + {!hideComponent ? ( + // && rewardAmount && rewardAmount > 0 + + + + + + You've earned: + + + + + + + + {hash ? ( + + + + + + ) : undefined} + + + ) : undefined} + + ); +}; diff --git a/src/components/Quests/ActiveQuestsMissionsCarousel/ActiveQuestsMissionsCarousel.style.ts b/src/components/Quests/ActiveQuestsMissionsCarousel/ActiveQuestsMissionsCarousel.style.ts new file mode 100644 index 000000000..11b16ebdd --- /dev/null +++ b/src/components/Quests/ActiveQuestsMissionsCarousel/ActiveQuestsMissionsCarousel.style.ts @@ -0,0 +1,61 @@ +import type { BoxProps, Breakpoint } from '@mui/material'; +import { Box, styled } from '@mui/material'; +import { ButtonPrimary } from '../../Button'; + +export const QuestsCarouselContainer = styled(Box)(({ theme }) => ({ + backgroundColor: '#fdfbef', + borderRadius: '12px', + width: '90%', + padding: theme.spacing(2), + boxShadow: theme.palette.shadow.main, + [theme.breakpoints.up('sm' as Breakpoint)]: { + margin: theme.spacing(2, 4, 0), + padding: theme.spacing(3), + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + padding: theme.spacing(4), + margin: theme.spacing(12, 4, 0), + }, + [theme.breakpoints.up('lg' as Breakpoint)]: { + padding: theme.spacing(6, 4), + }, + [theme.breakpoints.up('xl' as Breakpoint)]: { + margin: `${theme.spacing(12, 'auto', 0)}`, + maxWidth: theme.breakpoints.values.xl, + }, +})); + +export interface SeeAllButtonContainerProps + extends Omit { + show: boolean; +} + +export const SeeAllButtonContainer = styled(Box, { + shouldForwardProp: (prop) => prop !== 'show', +})(({ theme, show }) => ({ + width: '100%', + display: 'flex', + justifyContent: 'center', + marginTop: show ? theme.spacing(2) : 0, + [theme.breakpoints.up('sm' as Breakpoint)]: { + marginTop: theme.spacing(4), + }, + [theme.breakpoints.up('lg' as Breakpoint)]: { + marginTop: theme.spacing(6), + }, +})); + +export const SeeAllButton = styled(ButtonPrimary)(({ theme }) => ({ + color: 'inherit', + backgroundColor: + theme.palette.mode === 'light' + ? theme.palette.alphaDark100.main + : theme.palette.alphaLight400.main, + width: 320, + '&:hover': { + backgroundColor: + theme.palette.mode === 'light' + ? theme.palette.alphaDark200.main + : theme.palette.alphaLight500.main, + }, +})); diff --git a/src/components/Quests/ActiveQuestsMissionsCarousel/ActiveQuestsMissionsCarousel.tsx b/src/components/Quests/ActiveQuestsMissionsCarousel/ActiveQuestsMissionsCarousel.tsx new file mode 100644 index 000000000..a723aa67e --- /dev/null +++ b/src/components/Quests/ActiveQuestsMissionsCarousel/ActiveQuestsMissionsCarousel.tsx @@ -0,0 +1,112 @@ +import { CarouselContainer } from '@/components/Blog/BlogCarousel/CarouselContainer'; +import type { Quest } from '@/types/loyaltyPass'; +import { useOngoingFestMissions } from 'src/hooks/useOngoingFestMissions'; +import { QuestCard } from '../QuestCard/QuestCard'; +import { QuestCardSkeleton } from '../QuestCard/QuestCardSkeleton'; +import { QuestsCarouselContainer } from './ActiveQuestsMissionsCarousel.style'; + +export function checkInclusion( + activeCampaigns: string[], + claimingIds: string[], +): boolean { + const lowerActiveCampaigns = activeCampaigns.map((cId) => cId.toLowerCase()); + for (const id of claimingIds) { + if (lowerActiveCampaigns.includes(id.toLowerCase())) { + return true; + } + } + return false; +} + +interface QuestCarouselProps { + quests?: Quest[]; + loading: boolean; + activeCampaigns: string[]; + pastCampaigns?: string[]; + path: string; +} + +export const ActiveQuestsMissionsCarousel = ({ + quests, + loading, + activeCampaigns, + pastCampaigns, + path, +}: QuestCarouselProps) => { + const { url } = useOngoingFestMissions(); + + const isNotLive = + !loading && (!activeCampaigns || activeCampaigns.length === 0); + return ( + <> + {!isNotLive ? ( + + + {!loading ? ( + quests?.map((quest: Quest, index: number) => { + const claimingIds = + quest.attributes?.CustomInformation?.['claimingIds']; + const rewardsIds = + quest.attributes?.CustomInformation?.['rewardsIds']; + const rewardType = + quest.attributes?.CustomInformation?.['rewardType']; + const rewardRange = + quest.attributes?.CustomInformation?.['rewardRange']; + let included = false; + let completed = false; + if (claimingIds && activeCampaigns) { + included = checkInclusion(activeCampaigns, claimingIds); + } + + if (rewardsIds && pastCampaigns) { + completed = checkInclusion(pastCampaigns, rewardsIds); + } + + const baseURL = quest.attributes.Image?.data?.attributes?.url; + const imgURL = new URL(baseURL, url.origin); + + if (included) { + return ( + 0 && rewardType === 'weekly' + } + rewardRange={rewardRange} + /> + ); + } + }) + ) : ( + <> + {Array.from({ length: 3 }, () => 42).map((_, idx) => ( + + ))} + + )} + + + ) : null} + + ); +}; diff --git a/src/components/Quests/AvailableMissionsList/AvailableMissionsList.style.ts b/src/components/Quests/AvailableMissionsList/AvailableMissionsList.style.ts new file mode 100644 index 000000000..334844ed4 --- /dev/null +++ b/src/components/Quests/AvailableMissionsList/AvailableMissionsList.style.ts @@ -0,0 +1,64 @@ +import type { BoxProps, Breakpoint } from '@mui/material'; +import { Box, Stack, Typography, styled } from '@mui/material'; + +export const AvailableMissionsContainer = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.bgSecondary.main, + padding: theme.spacing(2), + borderRadius: '12px', + width: '90%', + boxShadow: theme.palette.shadow?.main, + [theme.breakpoints.down('md' as Breakpoint)]: { + marginTop: '64px', + }, + [theme.breakpoints.up('sm' as Breakpoint)]: { + margin: theme.spacing(2, 4, 0), + padding: theme.spacing(3), + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + padding: theme.spacing(4), + margin: theme.spacing(12, 4, 0), + }, + [theme.breakpoints.up('lg' as Breakpoint)]: { + padding: theme.spacing(6, 4), + }, + [theme.breakpoints.up('xl' as Breakpoint)]: { + margin: `${theme.spacing(12, 'auto', 0)}`, + maxWidth: theme.breakpoints.values.xl, + }, +})); + +export const AvailableMissionsHeader = styled(Box, { + shouldForwardProp: (prop) => prop !== 'styles', +})(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + alignContent: 'center', + ...(theme.palette.mode === 'dark' && { + color: theme.palette.white.main, + }), + justifyContent: 'space-between', +})); + +export const AvailableMissionsTitle = styled(Typography, { + shouldForwardProp: (prop) => prop !== 'styles' && prop !== 'show', +})(({ theme }) => ({ + fontWeight: 700, + fontSize: '24px', + lineHeight: '32px', + color: 'inherit', + margin: theme.spacing(3, 1.5, 0), + [theme.breakpoints.up('sm' as Breakpoint)]: { + margin: theme.spacing(0, 1.5, 0), + }, + [theme.breakpoints.up('lg' as Breakpoint)]: { + justifyContent: 'flex-start', + margin: 0, + }, +})); + +export const AvailableMissionsStack = styled(Stack)(() => ({ + marginTop: 32, + alignItems: 'center', + display: 'flex', + justifyContent: 'center', +})); diff --git a/src/components/Quests/AvailableMissionsList/AvailableMissionsList.tsx b/src/components/Quests/AvailableMissionsList/AvailableMissionsList.tsx new file mode 100644 index 000000000..8633929e3 --- /dev/null +++ b/src/components/Quests/AvailableMissionsList/AvailableMissionsList.tsx @@ -0,0 +1,191 @@ +import type { Quest } from '@/types/loyaltyPass'; +import { + Box, + type SelectChangeEvent, + type Theme, + useMediaQuery, +} from '@mui/material'; +import { useState } from 'react'; +import { useOngoingFestMissions } from 'src/hooks/useOngoingFestMissions'; +import { checkInclusion } from '../ActiveQuestsMissionsCarousel/ActiveQuestsMissionsCarousel'; +import { MissionsFilter } from '../MissionsFilter/MissionsFilter'; +import { QuestCard } from '../QuestCard/QuestCard'; +import { QuestCardSkeleton } from '../QuestCard/QuestCardSkeleton'; +import { + AvailableMissionsContainer, + AvailableMissionsHeader, + AvailableMissionsStack, + AvailableMissionsTitle, +} from './AvailableMissionsList.style'; + +const chains = ['Optimism', 'Base', 'Mode', 'Fraxtal']; + +const category = [ + 'AMM', + 'Money Market', + 'Liquid Staking', + 'Derivatives', + 'Yield', +]; + +interface AvailableMissionsListProps { + quests?: Quest[]; + pastCampaigns?: string[]; + loading: boolean; + path: string; + activeCampaign?: 'superfest'; +} + +export const AvailableMissionsList = ({ + quests, + pastCampaigns, + loading, + path, + activeCampaign, +}: AvailableMissionsListProps) => { + const isMobile = useMediaQuery((theme: Theme) => + theme.breakpoints.down('md'), + ); + const [chainsFilter, setChainsFilter] = useState([]); + const [categoryFilter, setCategoryFilter] = useState([]); + const { url } = useOngoingFestMissions(); + + const handleChainChange = (event: SelectChangeEvent) => { + const { + target: { value }, + } = event; + setChainsFilter(typeof value === 'string' ? value.split(',') : value); + }; + + const handleCategoryChange = ( + event: SelectChangeEvent, + ) => { + const { + target: { value }, + } = event; + setCategoryFilter(typeof value === 'string' ? value.split(',') : value); + }; + + return ( + + + {'Available Missions'} + + {isMobile ? ( + + ) : ( + <> + + + + + + )} + + + + {!loading && quests + ? quests?.map((quest: Quest, index: number) => { + const baseURL = quest.attributes.Image?.data?.attributes?.url; + const imgURL = new URL(baseURL, url.origin); + const rewards = quest.attributes.CustomInformation?.['rewards']; + const rewardType = + quest.attributes?.CustomInformation?.['rewardType']; + const rewardRange = + quest.attributes?.CustomInformation?.['rewardRange']; + const chains = quest.attributes.CustomInformation?.['chains']; + const claimingIds = + quest.attributes?.CustomInformation?.['claimingIds']; + const rewardsIds = + quest.attributes?.CustomInformation?.['rewardsIds']; + + //todo: exclude in a dedicated helper function + if (chains && chainsFilter && chainsFilter.length > 0) { + let included = false; + for (const chain of chains) { + if ( + chainsFilter + .map((elemChain) => elemChain.toLowerCase()) + .includes(chain.name.toLowerCase()) + ) { + included = true; + break; + } + } + if (!included) { + return undefined; + } + } + if ( + categoryFilter && + categoryFilter.length > 0 && + (!quest.attributes.Category || + (quest.attributes.Category && + !categoryFilter.includes(quest.attributes.Category))) + ) { + return undefined; + } + + let completed = false; + if (rewardsIds && pastCampaigns) { + completed = checkInclusion(pastCampaigns, rewardsIds); + } + + return ( + 0 && rewardType === 'weekly' + } + rewardRange={rewardRange} + /> + ); + }) + : null} + {loading + ? Array.from({ length: 12 }, () => 42).map((_, idx) => ( + + )) + : null} + + + ); +}; diff --git a/src/components/Quests/MissionsFilter/MissionsFilter.tsx b/src/components/Quests/MissionsFilter/MissionsFilter.tsx new file mode 100644 index 000000000..c62897e9b --- /dev/null +++ b/src/components/Quests/MissionsFilter/MissionsFilter.tsx @@ -0,0 +1,82 @@ +import { type Theme, Typography, useTheme } from '@mui/material'; +import Checkbox from '@mui/material/Checkbox'; +import FormControl from '@mui/material/FormControl'; +import ListItemText from '@mui/material/ListItemText'; +import MenuItem from '@mui/material/MenuItem'; +import OutlinedInput from '@mui/material/OutlinedInput'; +import Select, { type SelectChangeEvent } from '@mui/material/Select'; + +const ITEM_HEIGHT = 48; +const ITEM_PADDING_TOP = 8; + +function getStyles(name: string, personName: readonly string[], theme: Theme) { + return { + fontWeight: + personName.indexOf(name) === -1 + ? theme.typography.fontWeightRegular + : theme.typography.fontWeightMedium, + }; +} + +interface MissionsFilterProps { + title: string; + options: string[]; + activeChoices: string[]; + handleChange: (event: SelectChangeEvent) => void; +} + +export const MissionsFilter = ({ + title, + options, + activeChoices, + handleChange, +}: MissionsFilterProps) => { + const theme = useTheme(); + + const MenuProps = { + PaperProps: { + style: { + maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, + width: 250, + backgroundColor: theme.palette.bgTertiary.main, //'#fff0ca', + }, + }, + }; + + return ( + + + + ); +}; diff --git a/src/components/Quests/QuestCard/QuestCard.style.ts b/src/components/Quests/QuestCard/QuestCard.style.ts new file mode 100644 index 000000000..cf31858cf --- /dev/null +++ b/src/components/Quests/QuestCard/QuestCard.style.ts @@ -0,0 +1,98 @@ +import type { BoxProps } from '@mui/material'; +import { Box, alpha, styled } from '@mui/material'; + +export const QuestCardMainBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + backgroundColor: + theme.palette.mode === 'light' + ? '#FFFFFF' + : alpha(theme.palette.white.main, 0.08), + height: 450, + width: 288, + textAlign: 'center', + borderRadius: '8px', +})); + +export const QuestCardBottomBox = styled(Box)(({ theme }) => ({ + display: 'flex', + color: theme.palette.text.primary, + flexDirection: 'column', + justifyContent: 'space-between', + flexGrow: 1, + paddingTop: '16px', + paddingBottom: '24px', + paddingLeft: '16px', + paddingRight: '16px', + backgroundColor: theme.palette.bgTertiary.main, + borderBottomLeftRadius: '8px', + borderBottomRightRadius: '8px', +})); + +export const QuestCardTitleBox = styled(Box)(() => ({ + display: 'flex', + alignItems: 'center', + alignContent: 'center', + textAlign: 'left', +})); + +export interface QuestCardInfoBoxProps extends Omit { + points?: number; +} + +export const QuestCardInfoBox = styled(Box, { + shouldForwardProp: (prop) => prop !== 'points', +})(({ points }) => ({ + display: 'flex', + flexDirection: 'column', +})); + +export const CompletedBox = styled(Box)(() => ({ + display: 'flex', + alignItems: 'center', + backgroundColor: '#d6ffe7', + borderRadius: '128px', + padding: '4px', + width: '50%', +})); + +export interface QuestPlatformMainBoxProps extends Omit { + platformName?: string; +} + +export const QuestPlatformMainBox = styled(Box, { + shouldForwardProp: (prop) => prop !== 'platformName', +})(({ platformName }) => ({ + display: 'flex', + justifyContent: platformName ? 'space-between' : 'flex-end', + alignItems: 'center', +})); + +export interface XPDisplayBoxProps extends Omit { + active?: boolean; +} + +export const XPDisplayBox = styled(Box, { + shouldForwardProp: (prop) => prop !== 'active', +})(({ active }) => ({ + marginRight: active ? '8px' : undefined, + display: 'flex', + height: '28px', + alignContent: 'center', + justifyContent: 'space-between', + alignItems: 'center', + borderRadius: '128px', + padding: '8px', +})); + +export const XPIconBox = styled(Box)(({ theme }) => ({ + display: 'flex', + alignContent: 'flex-end', + justifyContent: 'flex-end', +})); + +export const OPBadgeRelativeBox = styled(Box)(({ theme }) => ({ + position: 'relative', + bottom: '16px', + right: '32px', +})); diff --git a/src/components/Quests/QuestCard/QuestCard.tsx b/src/components/Quests/QuestCard/QuestCard.tsx new file mode 100644 index 000000000..f2ae905fb --- /dev/null +++ b/src/components/Quests/QuestCard/QuestCard.tsx @@ -0,0 +1,224 @@ +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import { Box, Typography, useTheme } from '@mui/material'; +import Image from 'next/image'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; +import { APYIcon } from 'src/components/illustrations/APYIcon'; +import { OPBadge } from 'src/components/illustrations/OPBadge'; +import { + TrackingAction, + TrackingCategory, + TrackingEventParameter, +} from 'src/const/trackingKeys'; +import { useMissionsMaxAPY } from 'src/hooks/useMissionsMaxAPY'; +import { useUserTracking } from 'src/hooks/userTracking'; +import { ButtonSecondary } from '../../Button'; +import { SuperfestXPIcon } from '../../illustrations/XPIcon'; +import type { Chain } from '../QuestPage/Banner/Banner'; +import { FlexCenterRowBox } from '../QuestPage/QuestsMissionPage.style'; +import { FlexSpaceBetweenBox } from '../Quests.style'; +import { + OPBadgeRelativeBox, + QuestCardBottomBox, + QuestCardInfoBox, + QuestCardMainBox, + QuestCardTitleBox, + XPDisplayBox, + XPIconBox, +} from './QuestCard.style'; + +export interface RewardsInterface { + logo: string; + name: string; + amount: number; +} + +interface QuestCardProps { + active?: boolean; + title?: string; + image?: string; + points?: number; + activeCampaign?: 'superfest'; + path: string; + link?: string; + id?: string | number; + label?: string; + startDate?: string; + endDate?: string; + platformName?: string; + platformImage?: string; + slug?: string; + chains?: Chain[]; + rewards?: RewardsInterface; + completed?: boolean; + claimingIds?: string[]; + variableWeeklyAPY?: boolean; + rewardRange?: string; +} + +export const QuestCard = ({ + active, + title, + image, + points, + id, + activeCampaign, + path, + label, + link, + startDate, + endDate, + slug, + chains, + rewards, + completed, + claimingIds, + variableWeeklyAPY, + rewardRange, +}: QuestCardProps) => { + const { t } = useTranslation(); + const theme = useTheme(); + const router = useRouter(); + const { apy } = useMissionsMaxAPY(claimingIds); + + const { trackEvent } = useUserTracking(); + const handleClick = () => { + trackEvent({ + category: TrackingCategory.Quests, + action: TrackingAction.ClickQuestCard, + label: 'click-quest-card', + data: { + [TrackingEventParameter.QuestCardTitle]: title || '', + [TrackingEventParameter.QuestCardId]: id || '', + [TrackingEventParameter.QuestCardPlatform]: activeCampaign || '', + [TrackingEventParameter.QuestCardLabel]: label || '', + }, + }); + }; + + return ( + + + + {image && ( + Quest Card Image + )} + {activeCampaign === 'superfest' && ( + + {rewards && rewards?.amount > 0 ? : undefined} + + )} + + + + + + {title && title.length > 22 ? `${title.slice(0, 21)}...` : title} + + + + + {chains?.map((elem: Chain, i: number) => { + return ( + {elem.name} + ); + })} + + {points ? ( + + {apy > 0 && !variableWeeklyAPY && ( + + + {`${Number(apy).toFixed(1)}%`} + + + + + + )} + {variableWeeklyAPY && ( + + + {rewardRange ? rewardRange : `VAR.%`} + + + + + + )} + + + {`${points}`} + + + {!completed ? ( + + ) : ( + + )} + + + + ) : undefined} + + + {active && slug ? ( + router.push(slug)} + > + + {String(t('questCard.join')).toUpperCase()} + + + ) : null} + + + + ); +}; diff --git a/src/components/Quests/QuestCard/QuestCardSkeleton.style.ts b/src/components/Quests/QuestCard/QuestCardSkeleton.style.ts new file mode 100644 index 000000000..862e8d670 --- /dev/null +++ b/src/components/Quests/QuestCard/QuestCardSkeleton.style.ts @@ -0,0 +1,14 @@ +import { Box, alpha, styled } from '@mui/material'; + +export const QuestCardSkeletonContainer = styled(Box)(({ theme }) => ({ + backgroundColor: '#F8F3E0', + height: 450, + width: 288, + textAlign: 'center', + borderRadius: '8px', + border: 16, + borderColor: + theme.palette.mode === 'light' + ? '#ffffff' + : alpha(theme.palette.white.main, 0.08), +})); diff --git a/src/components/Quests/QuestCard/QuestCardSkeleton.tsx b/src/components/Quests/QuestCard/QuestCardSkeleton.tsx new file mode 100644 index 000000000..21f38f1d7 --- /dev/null +++ b/src/components/Quests/QuestCard/QuestCardSkeleton.tsx @@ -0,0 +1,20 @@ +import { Skeleton } from '@mui/material'; +import { QuestCardSkeletonContainer } from './QuestCardSkeleton.style'; + +export const QuestCardSkeleton = () => { + return ( + + + + ); +}; diff --git a/src/components/Quests/QuestCard/VoidQuestCard.style.ts b/src/components/Quests/QuestCard/VoidQuestCard.style.ts new file mode 100644 index 000000000..4b6caa64f --- /dev/null +++ b/src/components/Quests/QuestCard/VoidQuestCard.style.ts @@ -0,0 +1,19 @@ +import { Box, alpha, styled } from '@mui/material'; + +export const VoidQuestCardContainer = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: + theme.palette.mode === 'light' + ? theme.palette.grey[100] + : alpha(theme.palette.grey[100], 0.08), + height: '416px', + width: '256px', + borderRadius: '8px', + border: 16, + borderColor: + theme.palette.mode === 'light' + ? theme.palette.white.main + : alpha(theme.palette.white.main, 0.08), +})); diff --git a/src/components/Quests/QuestCard/VoidQuestCard.tsx b/src/components/Quests/QuestCard/VoidQuestCard.tsx new file mode 100644 index 000000000..0bba66993 --- /dev/null +++ b/src/components/Quests/QuestCard/VoidQuestCard.tsx @@ -0,0 +1,21 @@ +import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; +import { alpha, useTheme } from '@mui/material'; +import { VoidQuestCardContainer } from './VoidQuestCard.style'; + +export const VoidQuestCard = () => { + const theme = useTheme(); + return ( + + + + ); +}; diff --git a/src/components/Quests/QuestPage/BackButton/BackButton.style.ts b/src/components/Quests/QuestPage/BackButton/BackButton.style.ts new file mode 100644 index 000000000..50ac9a8d0 --- /dev/null +++ b/src/components/Quests/QuestPage/BackButton/BackButton.style.ts @@ -0,0 +1,25 @@ +import { Box, type Breakpoint, Typography, styled } from '@mui/material'; +import { urbanist } from 'src/fonts/fonts'; + +export const BackButtonMainBox = styled(Box)(({ theme }) => ({ + width: '80%', + maxWidth: '1210px', + display: 'flex', + flexDirection: 'row', + justifyContent: 'left', + alignContent: 'center', + alignItems: 'center', + marginBottom: '16px', +})); + +export const ButtonTypography = styled(Typography)(({ theme }) => ({ + marginLeft: '4px', + fontSize: '14px', + typography: urbanist.style.fontFamily, + overflow: 'hidden', + textOverflow: 'ellipsis', + maxWidth: 232, + [theme.breakpoints.up('sm' as Breakpoint)]: { + maxWidth: 168, + }, +})); diff --git a/src/components/Quests/QuestPage/BackButton/BackButton.tsx b/src/components/Quests/QuestPage/BackButton/BackButton.tsx new file mode 100644 index 000000000..2b4fd0cc7 --- /dev/null +++ b/src/components/Quests/QuestPage/BackButton/BackButton.tsx @@ -0,0 +1,43 @@ +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { Typography } from '@mui/material'; +import { useRouter } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; +import { Button } from 'src/components/Button'; +import { BackButtonMainBox } from './BackButton.style'; + +interface BackButtonProps { + path: string; + title?: string; +} + +export const BackButton = ({ path, title }: BackButtonProps) => { + const router = useRouter(); + const { t } = useTranslation(); + + return ( + + + + ); +}; diff --git a/src/components/Quests/QuestPage/Banner/Banner.style.ts b/src/components/Quests/QuestPage/Banner/Banner.style.ts new file mode 100644 index 000000000..ad95aeb69 --- /dev/null +++ b/src/components/Quests/QuestPage/Banner/Banner.style.ts @@ -0,0 +1,201 @@ +import type { BoxProps, Breakpoint } from '@mui/material'; +import { Box, Typography, alpha, styled } from '@mui/material'; +import { urbanist } from 'src/fonts/fonts'; + +export const BannerMainBox = styled(Box)(({ theme }) => ({ + width: '80%', + maxWidth: '1210px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignContent: 'center', + backgroundColor: + theme.palette.mode === 'light' + ? '#FFFFFF' + : alpha(theme.palette.white.main, 0.08), + textAlign: 'center', + overflow: 'hidden', + borderRadius: '8px', + [theme.breakpoints.down('md' as Breakpoint)]: { + height: '900px', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + height: '620px', + }, +})); + +export const BannerBottomBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + color: theme.palette.text.primary, + padding: '32px', + backgroundColor: theme.palette.bgSecondary.main, + [theme.breakpoints.down('md' as Breakpoint)]: { + height: '55%', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + height: '35%', + }, +})); + +export const BannerTitleBox = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + [theme.breakpoints.down('md' as Breakpoint)]: { + textAlign: 'center', + marginTop: '16px', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + justifyContent: 'space-between', + textAlign: 'left', + }, +})); + +export interface BannerInfoBoxProps extends Omit { + points?: number; +} + +export const BannerInfoBox = styled(Box, { + shouldForwardProp: (prop) => prop !== 'points', +})(({ points }) => ({ + display: 'flex', + flexDirection: 'column', +})); + +export const QuestDatesBox = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + backgroundColor: + theme.palette.mode === 'light' + ? alpha(theme.palette.black.main, 0.04) + : theme.palette.alphaLight300.main, + paddingTop: '4px', + paddingBottom: '4px', + paddingLeft: '8px', + paddingRight: '8px', + borderRadius: '128px', + justifyContent: 'center', +})); + +export const QuestsPageMainBox = styled(Box)(({ theme }) => ({ + width: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', +})); + +export const BannerImageBox = styled(Box)(({ theme }) => ({ + position: 'relative', + width: '100%', + flexGrow: 1, +})); + +export const DateChainBox = styled(Box)(({ theme }) => ({ + display: 'flex', + justifyContent: 'space-between', + alignContent: 'center', + [theme.breakpoints.down('md' as Breakpoint)]: { + flexDirection: 'column', + gap: '8px', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + flexDirection: 'row', + }, +})); + +export const SupportedChainsBox = styled(Box)(() => ({ + display: 'flex', + alignContent: 'flex-start', + justifyContent: 'flex-start', + alignItems: 'center', +})); + +export const RewardBottomBox = styled(Box)(() => ({ + marginTop: '8px', + display: 'flex', + flexDirection: 'row', + textAlign: 'center', + alignContent: 'center', + alignItems: 'center', +})); + +export const RewardMainBox = styled(Box)(({ theme }) => ({ + display: 'flex', + justifyContent: 'space-between', + marginTop: '8px', + [theme.breakpoints.down('md' as Breakpoint)]: { + gap: '16px', + alignItems: 'left', + alignContent: 'flex-start', + textAlign: 'left', + flexDirection: 'column', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + flexDirection: 'row', + alignContent: 'center', + }, +})); + +export const BannerTitleTypography = styled(Typography)(({ theme }) => ({ + fontFamily: urbanist.style.fontFamily, + [theme.breakpoints.down('md' as Breakpoint)]: { + fontSize: '28px', + fontWeight: 700, + lineHeight: '32px', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + fontSize: '40px', + fontWeight: 700, + lineHeight: '48px', + }, +})); + +export const BannerLabelBox = styled(Box)(({ theme }) => ({ + border: '2px dotted', + borderColor: '#000000', + borderRadius: '8px', + padding: '8px', + zindex: 100, + backgroundColor: 'red', + animation: 'spin 30s linear infinite', + '@keyframes spin': { + '0%': { + transform: 'rotate(360deg)', + }, + '100%': { + transform: 'rotate(0deg)', + }, + }, +})); + +export const RotatingBox = styled(Box)(() => ({ + animation: 'spin 120s linear infinite', + '@keyframes spin': { + '0%': { + transform: 'rotate(0deg)', + }, + '100%': { + transform: 'rotate(360deg)', + }, + }, +})); + +export const BadgeRelativeBox = styled(Box)(({ theme }) => ({ + position: 'relative', + left: '5%', + bottom: '42px', +})); + +export const BadgeMainBox = styled(Box)(() => ({ + height: '1px', + width: '80%', + maxWidth: '1210px', + display: 'flex', + justifyContent: 'flex-end', + top: '32px', + left: '32px', + zIndex: 500, +})); diff --git a/src/components/Quests/QuestPage/Banner/Banner.tsx b/src/components/Quests/QuestPage/Banner/Banner.tsx new file mode 100644 index 000000000..b9fff9fc6 --- /dev/null +++ b/src/components/Quests/QuestPage/Banner/Banner.tsx @@ -0,0 +1,131 @@ +import type { Theme } from '@mui/material'; +import { useMediaQuery } from '@mui/material'; +import Image from 'next/image'; +import { type Quest } from 'src/types/loyaltyPass'; +import { checkInclusion } from '../../ActiveQuestsMissionsCarousel/ActiveQuestsMissionsCarousel'; +import { + BadgeMainBox, + BadgeRelativeBox, + BannerBottomBox, + BannerImageBox, + BannerMainBox, + BannerTitleBox, + BannerTitleTypography, + RewardMainBox, + RotatingBox, +} from './Banner.style'; +import { RewardBox } from './Rewards/RewardBox'; + +interface SuperfestMissionPageVar { + quest: Quest; + baseUrl: string; + pastCampaigns: string[]; + activeCampaign?: 'superfest'; + rotatingBadge?: JSX.Element; +} + +export interface Chain { + logo: string; + name: string; +} + +export const BannerBox = ({ + quest, + baseUrl, + pastCampaigns, + rotatingBadge, +}: SuperfestMissionPageVar) => { + const isMobile = useMediaQuery((theme: Theme) => + theme.breakpoints.down('md'), + ); + const attributes = quest?.attributes; + const rewardsIds = quest.attributes?.CustomInformation?.['rewardsIds']; + const rewards = attributes?.CustomInformation?.['rewards']; + const chains = attributes?.CustomInformation?.['chains']; + const partners = attributes?.CustomInformation?.['partner']; + // const rewardType = attributes?.CustomInformation?.['rewardType']; + const bannerImageURL = isMobile + ? attributes.Image?.data?.attributes?.url + : attributes?.BannerImage?.data[0]?.attributes?.url; + const imgURL = new URL(bannerImageURL, baseUrl); + + let completed = false; + if (rewardsIds && pastCampaigns) { + completed = checkInclusion(pastCampaigns, rewardsIds); + } + + return ( + <> + {rotatingBadge && rewards && rewards.amount && ( + + + {rotatingBadge} + + + )} + + + Banner Image + + + + {attributes?.Title} + + + {/* chains */} + {chains && chains.length > 0 ? ( + chain.logo)} + title={'Supported Chains'} + value={chains.length > 0 ? chains[0].name : ''} + /> + ) : undefined} + {/* partner */} + {partners && partners.length > 0 ? ( + partner.logo)} + title={'Applications'} + value={partners.length > 0 ? partners[0].name : ''} + /> + ) : undefined} + {/* rewards */} + {rewards && rewards.amount ? ( + + ) : undefined} + {/* Points */} + {attributes && attributes?.Points ? ( + + ) : undefined} + + + + + ); +}; diff --git a/src/components/Quests/QuestPage/Banner/Rewards/RewardBox.style.ts b/src/components/Quests/QuestPage/Banner/Rewards/RewardBox.style.ts new file mode 100644 index 000000000..e73f0e13b --- /dev/null +++ b/src/components/Quests/QuestPage/Banner/Rewards/RewardBox.style.ts @@ -0,0 +1,15 @@ +import { Box, styled } from '@mui/material'; + +export const RewardTopBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + alignContent: 'flex-start', + justifyContent: 'flex-start', +})); + +export const RewardSubtitleBox = styled(Box)(({ theme }) => ({ + display: 'flex', + textAlign: 'left', + alignContent: 'flex-start', + justifyContent: 'flex-start', +})); diff --git a/src/components/Quests/QuestPage/Banner/Rewards/RewardBox.tsx b/src/components/Quests/QuestPage/Banner/Rewards/RewardBox.tsx new file mode 100644 index 000000000..b85a84ce9 --- /dev/null +++ b/src/components/Quests/QuestPage/Banner/Rewards/RewardBox.tsx @@ -0,0 +1,62 @@ +import { Typography, useTheme } from '@mui/material'; +import Image from 'next/image'; +import { RewardBottomBox, SupportedChainsBox } from '../Banner.style'; +import { RewardSubtitleBox, RewardTopBox } from './RewardBox.style'; + +interface RewardBoxProps { + title: string; + logos: string[]; + value: string; +} + +export const RewardBox = ({ title, logos, value }: RewardBoxProps) => { + const theme = useTheme(); + return ( + + + + {title} + + + + {logos.length > 1 ? ( + + {logos.map((logo: string, i: number) => { + return ( + {'logo + ); + })} + + ) : ( + <> + logos-reward + + {value} + + + )} + + + ); +}; diff --git a/src/components/Quests/QuestPage/CTA/MissionCTA.style.ts b/src/components/Quests/QuestPage/CTA/MissionCTA.style.ts new file mode 100644 index 000000000..3a953d217 --- /dev/null +++ b/src/components/Quests/QuestPage/CTA/MissionCTA.style.ts @@ -0,0 +1,170 @@ +import { inter, urbanist } from '@/fonts/fonts'; +import type { BoxProps, Breakpoint } from '@mui/material'; +import { Box, Typography, alpha, darken } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { IconButtonPrimary } from 'src/components/IconButton'; + +export const MissionCtaContainer = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: theme.spacing(8), + gap: theme.spacing(1.5), + cursor: 'pointer', + overflow: 'hidden', + textAlign: 'center', + margin: theme.spacing(6, 0), + transition: 'background-color 250ms', + borderRadius: '16px', + backgroundColor: '#69d7ff', + '&:hover': { + cursor: 'pointer', + backgroundColor: + theme.palette.mode === 'light' + ? darken('#69d7ff', 0.02) //todo: add to theme + : alpha('#69d7ff', 0.16), + }, + [theme.breakpoints.up('sm' as Breakpoint)]: { + gap: theme.spacing(4), + flexDirection: 'row', + }, +})); + +export const SeveralMissionCtaContainer = styled(Box)( + ({ theme }) => ({ + width: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'center', + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), + paddingLeft: theme.spacing(4), + paddingRight: theme.spacing(4), + mardingBottom: '16px', + gap: theme.spacing(1.5), + cursor: 'pointer', + overflow: 'hidden', + textAlign: 'center', + transition: 'background-color 250ms', + borderRadius: '16px', + backgroundColor: theme.palette.bgTertiary.main, + '&:hover': { + cursor: 'pointer', + backgroundColor: + theme.palette.mode === 'light' + ? darken(theme.palette.bgTertiary.main, 0.02) //todo: add to theme + : alpha(theme.palette.bgTertiary.main, 0.16), + }, + [theme.breakpoints.up('sm' as Breakpoint)]: { + gap: theme.spacing(4), + flexDirection: 'row', + }, + }), +); + +export const MissionCtaTitle = styled(Box)(({ theme }) => ({ + fontFamily: inter.style.fontFamily, + fontWeight: 700, + color: theme.palette.black.main, + fontSize: '32px', + lineHeight: '38px', + userSelect: 'none', + + [theme.breakpoints.up('sm' as Breakpoint)]: { + fontSize: '40px', + lineHeight: '56px', + textDecoration: 'auto', + }, +})); + +export const MissionCtaButtonSF = styled(IconButtonPrimary)(({ theme }) => ({ + backgroundColor: 'transparent', + border: '2px dotted', + borderColor: '#000000', + width: '32px', + height: '32px', + ':hover': { + backgroundColor: darken('#f9ebc5', 0.02), + }, + [theme.breakpoints.up('sm' as Breakpoint)]: { + display: 'flex', + }, +})); + +export const CTABox = styled(Box)(({ theme }) => ({ + width: '80%', + maxWidth: '1210px', + display: 'flex', + marginTop: '32px', + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', +})); + +export const SeveralCTABox = styled(Box)(({ theme }) => ({ + width: '100%', + display: 'flex', + alignContent: 'center', + alignItems: 'center', + marginTop: '32px', + flexDirection: 'column', + justifyContent: 'center', + [theme.breakpoints.down('md' as Breakpoint)]: { + justifyContent: 'flex-start', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + justifyContent: 'center', + }, +})); + +export const StartedTitleTypography = styled(Typography)(({ theme }) => ({ + fontFamily: urbanist.style.fontFamily, + [theme.breakpoints.down('md' as Breakpoint)]: { + fontSize: '16px', + fontWeight: 600, + lineHeight: '14px', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + fontSize: '20px', + fontWeight: 600, + lineHeight: '20px', + }, +})); + +export const StartedTitleBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-start', + textAlign: 'left', +})); + +export const CTAMainBox = styled(Box)(({ theme }) => ({ + display: 'flex', + color: theme.palette.text.primary, + flexDirection: 'column', + width: '80%', + maxWidth: '1210px', + marginTop: '64px', + borderRadius: '8px', + padding: '32px', + backgroundColor: theme.palette.bgSecondary.main, //'#fdfbef', +})); + +export const CTAExplanationBox = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + [theme.breakpoints.down('md' as Breakpoint)]: { + flexDirection: 'column', + justifyContent: 'flex-start', + alignContent: 'flex-start', + textAlign: 'left', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + }, +})); diff --git a/src/components/Quests/QuestPage/CTA/MissionCTA.tsx b/src/components/Quests/QuestPage/CTA/MissionCTA.tsx new file mode 100644 index 000000000..bbfb28756 --- /dev/null +++ b/src/components/Quests/QuestPage/CTA/MissionCTA.tsx @@ -0,0 +1,258 @@ +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { + Box, + type Theme, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; +import Image from 'next/image'; +import Link from 'next/link'; +import { IconButtonPrimary } from 'src/components/IconButton'; +import { APYIcon } from 'src/components/illustrations/APYIcon'; +import { XPDisplayBox } from 'src/components/ProfilePage/QuestCard/QuestCard.style'; +import { + TrackingAction, + TrackingCategory, + TrackingEventParameter, +} from 'src/const/trackingKeys'; +import { useUserTracking } from 'src/hooks/userTracking'; +import { XPIconBox } from '../../QuestCard/QuestCard.style'; +import { FlexCenterRowBox } from '../QuestsMissionPage.style'; +import { SignatureCTA } from '../SignatureCTA/SignatureCTA'; +import { + CTAExplanationBox, + CTAMainBox, + MissionCtaButtonSF, + SeveralCTABox, + SeveralMissionCtaContainer, + StartedTitleBox, + StartedTitleTypography, +} from './MissionCTA.style'; + +interface MissionCTAButtonProps { + activeCampaign?: string; + onClick: () => void; +} + +const MissionCTAButton = ({ + activeCampaign, + onClick, +}: MissionCTAButtonProps) => { + const theme = useTheme(); + if (activeCampaign === 'superfest') { + return ( + + + + ); + } else { + return ( + + + + ); + } +}; + +export interface CTALinkInt { + logo: string; + text: string; + link: string; + claimingId: string; + rewardId?: string; + apy?: number; + weeklyApy?: string; +} + +interface MissionCtaProps { + title?: string; + url?: string; + rewards?: number; + id?: number; + label?: string; + CTAs: CTALinkInt[]; + variableWeeklyAPY?: boolean; + signature?: boolean; + rewardRange?: string; + activeCampaign?: string; +} + +export const MissionCTA = ({ + CTAs, + rewards, + title, + id, + label, + variableWeeklyAPY, + signature, + rewardRange, + activeCampaign, +}: MissionCtaProps) => { + // const { trackEvent } = useUserTracking(); + const isMobile = useMediaQuery((theme: Theme) => + theme.breakpoints.down('md'), + ); + const theme = useTheme(); + const { trackEvent } = useUserTracking(); + + const handleClick = ({ + rewardId, + id, + claimingId, + activeCampaign, + title, + }: { + rewardId?: string; + id?: number; + claimingId: string; + activeCampaign?: string; + title?: string; + }) => { + trackEvent({ + category: TrackingCategory.Missions, + action: TrackingAction.ClickMissionCta, + label: `click-mission-cta-${id}`, + data: { + [TrackingEventParameter.MissionCtaRewardId]: rewardId || '', + [TrackingEventParameter.MissionCtaClaimingId]: claimingId || '', + [TrackingEventParameter.MissionCtaTitle]: title || '', + [TrackingEventParameter.MissionCtaLabel]: label || '', + [TrackingEventParameter.MissionCtaPartnerId]: id || '', + [TrackingEventParameter.MissionCtaCampaign]: activeCampaign || '', + }, + }); + }; + + return ( + + + Get Started + {!signature && rewards ? ( + + + Completing any mission below makes you eligible for rewards and + XP. + + + ) : undefined} + + + {signature && } + {CTAs.map((CTA: CTALinkInt, i: number) => { + return ( + + + handleClick({ + id, + rewardId: CTA.rewardId, + claimingId: CTA.claimingId, + title: CTA.text, + }) + } + > + + {`Image + + {CTA.text ?? 'Go to Protocol Page'} + + + + {CTA.apy && !variableWeeklyAPY && ( + + + {`${Number(CTA.apy).toFixed(1)}%`} + + + + + + )} + {variableWeeklyAPY && ( + + + {CTA?.weeklyApy + ? CTA?.weeklyApy + : rewardRange + ? rewardRange + : `VAR.%`} + + + + + + )} + {!isMobile && ( + + handleClick({ + id, + title, + claimingId: CTA.claimingId, + rewardId: CTA.rewardId, + }) + } + activeCampaign={activeCampaign} + /> + )} + + + + ); + })} + + + ); +}; diff --git a/src/components/Quests/QuestPage/DescriptionBox/DescriptionBox.style.ts b/src/components/Quests/QuestPage/DescriptionBox/DescriptionBox.style.ts new file mode 100644 index 000000000..53b56c354 --- /dev/null +++ b/src/components/Quests/QuestPage/DescriptionBox/DescriptionBox.style.ts @@ -0,0 +1,15 @@ +import type { Breakpoint } from '@mui/material'; +import { Typography, styled } from '@mui/material'; +import { urbanist } from 'src/fonts/fonts'; + +export const DescriptionTitleTypography = styled(Typography)(({ theme }) => ({ + fontFamily: urbanist.style.fontFamily, + [theme.breakpoints.down('md' as Breakpoint)]: { + fontSize: '32px', + fontWeight: 700, + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + fontSize: '48px', + fontWeight: 700, + }, +})); diff --git a/src/components/Quests/QuestPage/DescriptionBox/DescriptionBox.tsx b/src/components/Quests/QuestPage/DescriptionBox/DescriptionBox.tsx new file mode 100644 index 000000000..0eb286548 --- /dev/null +++ b/src/components/Quests/QuestPage/DescriptionBox/DescriptionBox.tsx @@ -0,0 +1,24 @@ +import { Typography } from '@mui/material'; +import { + LeftTextBox, + QuestsPageElementContainer, +} from '../QuestsMissionPage.style'; +import { DescriptionTitleTypography } from './DescriptionBox.style'; + +interface DescriptionBox { + longTitle?: string; + description?: string; +} + +export const DescriptionBox = ({ longTitle, description }: DescriptionBox) => { + return ( + + {longTitle} + + + {description} + + + + ); +}; diff --git a/src/components/Quests/QuestPage/InformationBox/InformationAlertBox.style.ts b/src/components/Quests/QuestPage/InformationBox/InformationAlertBox.style.ts new file mode 100644 index 000000000..d86829bf1 --- /dev/null +++ b/src/components/Quests/QuestPage/InformationBox/InformationAlertBox.style.ts @@ -0,0 +1,18 @@ +import { Box, styled } from '@mui/material'; + +export const InformationBox = styled(Box)(({ theme }) => ({ + width: '80%', + maxWidth: '960px', + display: 'flex', + color: theme.palette.text.primary, + marginTop: '32px', + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', + overflow: 'hidden', + border: '2px solid', + padding: '32px', + borderRadius: '24px', + borderColor: theme.palette.text.primary, +})); diff --git a/src/components/Quests/QuestPage/InformationBox/InformationAlertBox.tsx b/src/components/Quests/QuestPage/InformationBox/InformationAlertBox.tsx new file mode 100644 index 000000000..f0c7ed42e --- /dev/null +++ b/src/components/Quests/QuestPage/InformationBox/InformationAlertBox.tsx @@ -0,0 +1,21 @@ +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import { Typography } from '@mui/material'; +import { LeftTextBox } from '../QuestsMissionPage.style'; +import { InformationBox } from './InformationAlertBox.style'; + +interface InformationAlertBoxProps { + information?: string; +} + +export const InformationAlertBox = ({ + information, +}: InformationAlertBoxProps) => { + return ( + + + + {information} + + + ); +}; diff --git a/src/components/Quests/QuestPage/QuestsMissionPage.style.ts b/src/components/Quests/QuestPage/QuestsMissionPage.style.ts new file mode 100644 index 000000000..9287a8958 --- /dev/null +++ b/src/components/Quests/QuestPage/QuestsMissionPage.style.ts @@ -0,0 +1,43 @@ +import { Box, styled } from '@mui/material'; + +export const QuestsPageElementContainer = styled(Box)(({ theme }) => ({ + width: '80%', + color: theme.palette.text.primary, + maxWidth: '960px', + display: 'flex', + marginTop: '64px', + flexDirection: 'column', + justifyContent: 'center', + alignContent: 'center', + textAlign: 'left', + overflow: 'hidden', +})); + +export const LeftTextBox = styled(Box)(({ theme }) => ({ + display: 'flex', + textAlign: 'left', +})); + +export const QuestsPageMainBox = styled(Box)(({ theme }) => ({ + width: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', +})); + +export const FlexCenterRowBox = styled(Box)(() => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', +})); + +export const FlexCenterSpaceRowBox = styled(Box)(() => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignContent: 'center', +})); diff --git a/src/components/Quests/QuestPage/QuestsMissionPage.tsx b/src/components/Quests/QuestPage/QuestsMissionPage.tsx new file mode 100644 index 000000000..715e08a63 --- /dev/null +++ b/src/components/Quests/QuestPage/QuestsMissionPage.tsx @@ -0,0 +1,91 @@ +import { useAccounts } from '@/hooks/useAccounts'; +import generateKey from 'src/app/lib/generateKey'; +import { useMerklRewards } from 'src/hooks/useMerklRewardsOnSpecificToken'; +import { useMissionsAPY } from 'src/hooks/useMissionsAPY'; +import { type Quest } from 'src/types/loyaltyPass'; +import { QuestPageMainBox, QuestsContainer } from '../Quests.style'; +import { BackButton } from './BackButton/BackButton'; +import { BannerBox } from './Banner/Banner'; +import { MissionCTA } from './CTA/MissionCTA'; +import { DescriptionBox } from './DescriptionBox/DescriptionBox'; +import { InformationAlertBox } from './InformationBox/InformationAlertBox'; +import { StepsBox } from './StepsBox/StepsBox'; +import { + REWARD_TOKEN_ADDRESS, + REWARD_TOKEN_CHAINID, +} from 'src/const/partnerRewardsTheme'; + +interface QuestsMissionPageVar { + quest: Quest; + baseUrl: string; + activeCampaign?: string; + path: string; +} + +export const QuestsMissionPage = ({ + quest, + baseUrl, + activeCampaign, + path, +}: QuestsMissionPageVar) => { + const attributes = quest?.attributes; + const CTAs = quest?.attributes?.CustomInformation?.['CTA']; + const missionType = quest?.attributes?.CustomInformation?.['missionType']; + const rewardType = attributes?.CustomInformation?.['rewardType']; + const rewardRange = attributes?.CustomInformation?.['rewardRange']; + const rewards = quest.attributes.CustomInformation?.['rewards']; + const points = quest?.attributes?.Points; + + const { account } = useAccounts(); + const { pastCampaigns } = useMerklRewards({ + rewardChainId: REWARD_TOKEN_CHAINID, + userAddress: account?.address, + rewardToken: REWARD_TOKEN_ADDRESS, + }); + const { CTAsWithAPYs } = useMissionsAPY(CTAs); + + return ( + + + {/* button to go back */} + + {/* big component with the main information */} + + {/* Big CTA */} + {CTAsWithAPYs?.length > 0 && ( + 0 && rewardType === 'weekly'} + signature={missionType === 'turtle_signature'} + rewardRange={rewardRange} + /> + )} + {/* Subtitle and description */} + {attributes?.Subtitle && ( + + )} + {/* Steps */} + {attributes?.Steps && attributes?.Steps?.length > 0 ? ( + + ) : undefined} + {/* Additional Info */} + {attributes?.Information && ( + + )} + + + ); +}; diff --git a/src/components/Quests/QuestPage/SignatureCTA/SignatureCTA.tsx b/src/components/Quests/QuestPage/SignatureCTA/SignatureCTA.tsx new file mode 100644 index 000000000..fa0f470ae --- /dev/null +++ b/src/components/Quests/QuestPage/SignatureCTA/SignatureCTA.tsx @@ -0,0 +1,160 @@ +'use client'; + +import { useUserTracking } from '@/hooks/userTracking/useUserTracking'; +import { Box, Typography } from '@mui/material'; +import Image from 'next/image'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { SiweMessage, generateNonce } from 'siwe'; +import { useAccounts } from 'src/hooks/useAccounts'; +import { useTurtleMember } from 'src/hooks/useTurtleMember'; +import { useSignMessage } from 'wagmi'; +import { + CTAExplanationBox, + SeveralMissionCtaContainer, +} from '../CTA/MissionCTA.style'; + +interface SignatureInt { + isLive: boolean; + message: string; +} + +interface SignatureCtaProps { + signature?: SignatureInt; +} + +export const SignatureCTA = ({ signature }: SignatureCtaProps) => { + const { t } = useTranslation(); + const { trackEvent } = useUserTracking(); + const { account } = useAccounts(); + const [messageToSign, setMessageToSign] = useState( + undefined, + ); + const [messagedHasBeenSigned, setMessagedHasBeenSigned] = + useState(false); + const { signMessageAsync } = useSignMessage(); + const { + isMember, + isLoading: isMemberCheckLoading, + isSuccess: isMemberCheckSuccess, + } = useTurtleMember({ + userAddress: account?.address, + }); + + const handleSignatureClick = async () => { + try { + const POST_ENDPOINT = 'https://points.turtle.club/user/verify_siwe'; + if (messageToSign && account?.address) { + const domain = 'jumper.exchange'; + const origin = 'https://jumper.exchange'; + const nonce = generateNonce(); + + const siweMess = new SiweMessage({ + version: '1', + domain, + uri: origin, + address: account.address, + chainId: 1, + statement: messageToSign, + nonce, + }).toMessage(); + + const sig = await signMessageAsync({ + account: account.address as `0x${string}`, + message: String(siweMess), + }); + const payload = { + message: siweMess, // same message as from before + signature: sig, // hex signature + referral: 'JUMPER', // referral string + }; + const res = await fetch(POST_ENDPOINT, { + body: JSON.stringify(payload), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + if (!res.ok) { + throw new Error(`Response status: ${res.status}`); + } + if (res.status === 200) { + setMessagedHasBeenSigned(true); + } + } + } catch (err) { + console.log(err); + } + }; + + useEffect(() => { + async function fetchMessage() { + try { + const GET_ENDPOINT = 'https://points.turtle.club/user/siwe_message'; + const response = await fetch(`${GET_ENDPOINT}`); + const message = await response.text(); + if (message) { + setMessageToSign(message); + } + } catch (err) { + console.log(err); + } + } + fetchMessage(); + }, []); + + return ( + <> + {(messagedHasBeenSigned || isMember) && ( + + + + {`logo + + { + 'Congrats, you are a Turtle Club member now. Pour some whisky and enjoy the boosted yields on your existing DeFi positions.' + } + + + + + )} + {!messagedHasBeenSigned && !isMember && ( + + + + {`logo + + {'Click to sign the agreement to become part of the club.'} + + + + + )} + + ); +}; diff --git a/src/components/Quests/QuestPage/StepsBox/StepsBox.tsx b/src/components/Quests/QuestPage/StepsBox/StepsBox.tsx new file mode 100644 index 000000000..6ec57bde3 --- /dev/null +++ b/src/components/Quests/QuestPage/StepsBox/StepsBox.tsx @@ -0,0 +1,30 @@ +import { type RootNode } from 'node_modules/@strapi/blocks-react-renderer/dist/BlocksRenderer'; +import { CustomRichBlocks } from 'src/components/Blog'; +import { DescriptionTitleTypography } from '../DescriptionBox/DescriptionBox.style'; +import { + LeftTextBox, + QuestsPageElementContainer, +} from '../QuestsMissionPage.style'; + +interface StepsBoxProps { + steps?: RootNode[]; + baseUrl: string; +} + +export const StepsBox = ({ steps, baseUrl }: StepsBoxProps) => { + return ( + + + + Steps to complete the mission + + + + + ); +}; diff --git a/src/components/Quests/Quests.style.ts b/src/components/Quests/Quests.style.ts new file mode 100644 index 000000000..5763453b0 --- /dev/null +++ b/src/components/Quests/Quests.style.ts @@ -0,0 +1,29 @@ +import { Box, styled } from '@mui/material'; + +export const QuestsContainer = styled(Box)(() => ({ + background: 'transparent', + position: 'relative', + width: '100% !important', + overflow: 'hidden', + paddingBottom: 20, +})); + +export const CenteredBox = styled(Box)(() => ({ + display: 'flex', + alignItems: 'center', +})); + +export const FlexSpaceBetweenBox = styled(Box)(() => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +})); + +export const QuestPageMainBox = styled(Box)(() => ({ + minWidth: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + alignContent: 'center', +})); diff --git a/src/components/Quests/Quests.tsx b/src/components/Quests/Quests.tsx new file mode 100644 index 000000000..7cdd2ba67 --- /dev/null +++ b/src/components/Quests/Quests.tsx @@ -0,0 +1,66 @@ +import { useAccounts } from '@/hooks/useAccounts'; +import { JUMPER_QUESTS_PATH } from 'src/const/urls'; +import { useMerklRewards } from 'src/hooks/useMerklRewardsOnSpecificToken'; +import { useOngoingFestMissions } from 'src/hooks/useOngoingFestMissions'; +import { + SuperfestContainer, + SuperfestMainBox, +} from '../Superfest/Superfest.style'; +import { ActiveQuestsMissionsCarousel } from './ActiveQuestsMissionsCarousel/ActiveQuestsMissionsCarousel'; +import { AvailableMissionsList } from './AvailableMissionsList/AvailableMissionsList'; +import { RewardsCarousel } from './Rewards/RewardsCarousel'; +import { + REWARD_TOKEN_ADDRESS, + REWARD_TOKEN_CHAINID, +} from 'src/const/partnerRewardsTheme'; + +export const Quests = () => { + //HOOKS + const { account } = useAccounts(); + const { quests, isQuestLoading } = useOngoingFestMissions(); + const { + availableRewards, + activeCampaigns, + pastCampaigns, + isLoading: isRewardLoading, + isSuccess: isRewardSuccess, + } = useMerklRewards({ + rewardChainId: REWARD_TOKEN_CHAINID, + userAddress: account?.address, + rewardToken: REWARD_TOKEN_ADDRESS, + }); + + return ( + + + + {!account?.address || + isQuestLoading || + !activeCampaigns || + activeCampaigns.length === 0 ? undefined : ( + + )} + + + + ); +}; diff --git a/src/components/Quests/Rewards/RewardsAmountBox/RewardsAmountBox.style.ts b/src/components/Quests/Rewards/RewardsAmountBox/RewardsAmountBox.style.ts new file mode 100644 index 000000000..3d6683e3f --- /dev/null +++ b/src/components/Quests/Rewards/RewardsAmountBox/RewardsAmountBox.style.ts @@ -0,0 +1,16 @@ +import { Box, styled } from '@mui/material'; + +export const RewardsdMainBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', +})); + +export const RewardsRightBox = styled(Box)(({ theme }) => ({ + marginTop: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + flexGrow: 1, +})); diff --git a/src/components/Quests/Rewards/RewardsAmountBox/RewardsAmountBox.tsx b/src/components/Quests/Rewards/RewardsAmountBox/RewardsAmountBox.tsx new file mode 100644 index 000000000..b48508fdb --- /dev/null +++ b/src/components/Quests/Rewards/RewardsAmountBox/RewardsAmountBox.tsx @@ -0,0 +1,52 @@ +import { Box, type Theme, Typography, useMediaQuery } from '@mui/material'; +import Image from 'next/image'; +import { FlexCenterRowBox } from '../../QuestPage/QuestsMissionPage.style'; + +export const RewardsAmountBox = ({ + rewardAmount, + isConfirmed, +}: { + rewardAmount: number; + isConfirmed: boolean; +}) => { + const isMobile = useMediaQuery((theme: Theme) => + theme.breakpoints.down('md'), + ); + + return ( + + + token image + {isMobile ? undefined : ( + token image + )} + + + + {isConfirmed ? '0' : rewardAmount ? rewardAmount : '...'} + + + + ); +}; diff --git a/src/components/Quests/Rewards/RewardsCarousel.style.ts b/src/components/Quests/Rewards/RewardsCarousel.style.ts new file mode 100644 index 000000000..dabfaeaef --- /dev/null +++ b/src/components/Quests/Rewards/RewardsCarousel.style.ts @@ -0,0 +1,107 @@ +import { getContrastAlphaColor } from '@/utils/colors'; +import type { BoxProps, Breakpoint, IconButtonProps } from '@mui/material'; +import { + Box, + IconButton as MuiIconButton, + Typography, + styled, +} from '@mui/material'; +import { urbanist } from 'src/fonts/fonts'; + +export const RewardsCarouselContainer = styled(Box)(({ theme }) => ({ + backgroundColor: '#fdfbef', + display: 'flex', + width: '100%', + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', +})); + +export const RewardsCarouselHeader = styled(Box, { + shouldForwardProp: (prop) => prop !== 'styles', +})(({ theme }) => ({ + display: 'flex', + ...(theme.palette.mode === 'dark' && { + color: theme.palette.white.main, + }), + justifyContent: 'space-between', +})); + +export const RewardsCarouselTitle = styled(Typography, { + shouldForwardProp: (prop) => prop !== 'styles' && prop !== 'show', +})(({ theme }) => ({ + fontWeight: 700, + fontSize: '24px', + lineHeight: '32px', + color: 'inherit', + margin: theme.spacing(3, 1.5, 0), + [theme.breakpoints.up('sm' as Breakpoint)]: { + margin: theme.spacing(0, 1.5, 0), + }, + [theme.breakpoints.up('lg' as Breakpoint)]: { + justifyContent: 'flex-start', + margin: 0, + }, +})); + +export const RewardsCarouselMainBox = styled(Box)(({ theme }) => ({ + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + alignContent: 'center', + padding: '32px', + [theme.breakpoints.down('md' as Breakpoint)]: { + flexDirection: 'column', + alignItems: 'center', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + flexDirection: 'row', + }, +})); + +export const ClaimButtonBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', + [theme.breakpoints.down('md' as Breakpoint)]: { + width: '85%', + marginTop: '16px', + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + flexDirection: 'row', + marginLeft: '32px', + width: '15%', + }, +})); + +export const EarnedTypography = styled(Typography)(({ theme }) => ({ + typography: urbanist.style.fontFamily, + [theme.breakpoints.down('md' as Breakpoint)]: { + fontSize: '32px', + lineHeight: '32px', + fontWeight: 700, + }, + [theme.breakpoints.up('md' as Breakpoint)]: { + fontSize: '32px', + lineHeight: '48px', + fontWeight: 700, + }, +})); + +export const RewardsOpenIconButton = styled(MuiIconButton, { + shouldForwardProp: (prop) => prop !== 'styles', +})(({ theme }) => ({ + color: getContrastAlphaColor(theme, '84%'), + transition: 'background 0.3s', + width: '48px', + height: '48px', + backgroundColor: '#fef0ca', + '&:hover': { + color: '#ffffff', + backgroundColor: '#FF0420', + }, +})); diff --git a/src/components/Quests/Rewards/RewardsCarousel.tsx b/src/components/Quests/Rewards/RewardsCarousel.tsx new file mode 100644 index 000000000..776917299 --- /dev/null +++ b/src/components/Quests/Rewards/RewardsCarousel.tsx @@ -0,0 +1,153 @@ +import { ChainId } from '@lifi/types'; +import OpenInNewIcon from '@mui/icons-material/OpenInNew'; +import { Box, Typography } from '@mui/material'; +import { Button } from 'src/components/Button'; +import { + useAccount, + useSwitchChain, + useWaitForTransactionReceipt, + useWriteContract, +} from 'wagmi'; +import { MerklDistribABI } from '../../../const/abi/merklABI'; +import { FlexCenterRowBox } from '../QuestPage/QuestsMissionPage.style'; +import { RewardsAmountBox } from './RewardsAmountBox/RewardsAmountBox'; +import { + ClaimButtonBox, + EarnedTypography, + RewardsCarouselContainer, + RewardsCarouselMainBox, + RewardsOpenIconButton, +} from './RewardsCarousel.style'; +import { PROFILE_CAMPAIGN_SCANNER } from 'src/const/partnerRewardsTheme'; + +interface RewardsCarouselProps { + hideComponent: boolean; + rewardAmount: number; + accumulatedAmountForContractBN: string; + isMerklSuccess: boolean; + proof: string[]; +} + +const CLAIMING_CONTRACT_ADDRESS = '0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae'; +const OP_TOKEN = '0x4200000000000000000000000000000000000042'; +//TESTING +// const TEST_TOKEN = '0x41A65AAE5d1C8437288d5a29B4D049897572758E'; + +export const RewardsCarousel = ({ + hideComponent, + rewardAmount, + accumulatedAmountForContractBN, + isMerklSuccess, + proof, +}: RewardsCarouselProps) => { + const { address } = useAccount(); + const { switchChainAsync } = useSwitchChain(); + const { data: hash, isPending, writeContract } = useWriteContract(); + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }); + + async function handleClick() { + try { + const { id } = await switchChainAsync({ + chainId: ChainId.OPT, + }); + + const canClaim = + id === ChainId.OPT && + address && + isMerklSuccess && + proof && + rewardAmount > 0; + + if (canClaim) { + writeContract({ + address: CLAIMING_CONTRACT_ADDRESS, + abi: MerklDistribABI, + functionName: 'claim', + // args: [[address], [OP_TOKEN], [rewardAmountBN], [proof]], // function claim(address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) + // TESTING + args: [ + [address], + [OP_TOKEN], + [accumulatedAmountForContractBN], + [proof], + ], // function claim(address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) + // TESTING + // args: [ + // [address], + // [TEST_TOKEN], + // [accumulatedAmountForContractBN], + // [proof], + // ], // function claim(address[] calldata users, address[] calldata tokens, uint256[] calldata amounts, bytes32[][] calldata proofs) + }); + } + } catch (err) { + console.log(err); + } + } + + return ( + <> + {!hideComponent && rewardAmount && rewardAmount > 0 ? ( + + + + + You've earned: + + + + + + + {hash ? ( + + + + + + ) : undefined} + + + ) : undefined} + + ); +}; diff --git a/src/components/Quests/index.ts b/src/components/Quests/index.ts new file mode 100644 index 000000000..d4b98f4e3 --- /dev/null +++ b/src/components/Quests/index.ts @@ -0,0 +1 @@ +export * from './Quests'; diff --git a/src/components/Superfest/ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel.style.ts b/src/components/Superfest/ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel.style.ts index aeb3624a1..172981c36 100644 --- a/src/components/Superfest/ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel.style.ts +++ b/src/components/Superfest/ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel.style.ts @@ -7,10 +7,7 @@ export const SuperfestCarouselContainer = styled(Box)(({ theme }) => ({ borderRadius: '12px', width: '90%', padding: theme.spacing(2), - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, [theme.breakpoints.up('sm' as Breakpoint)]: { margin: theme.spacing(2, 4, 0), padding: theme.spacing(3), diff --git a/src/components/Superfest/ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel.tsx b/src/components/Superfest/ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel.tsx index aefeecdc2..451ddc36a 100644 --- a/src/components/Superfest/ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel.tsx +++ b/src/components/Superfest/ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel.tsx @@ -4,7 +4,6 @@ import { useOngoingFestMissions } from 'src/hooks/useOngoingFestMissions'; import { QuestCard } from '../QuestCard/QuestCard'; import { QuestCardSkeleton } from '../QuestCard/QuestCardSkeleton'; import { SuperfestCarouselContainer } from './ActiveSuperfestMissionsCarousel.style'; - export function checkInclusion( activeCampaigns: string[], claimingIds: string[], @@ -17,7 +16,6 @@ export function checkInclusion( } return false; } - interface QuestCarouselProps { quests?: Quest[]; loading: boolean; @@ -58,14 +56,11 @@ export const ActiveSuperfestMissionsCarousel = ({ if (claimingIds && activeCampaigns) { included = checkInclusion(activeCampaigns, claimingIds); } - if (rewardsIds && pastCampaigns) { completed = checkInclusion(pastCampaigns, rewardsIds); } - const baseURL = quest.attributes.Image?.data?.attributes?.url; const imgURL = new URL(baseURL, url.origin); - if (included) { return ( ({ - backgroundColor: '#fdfbef', + backgroundColor: theme.palette.bgSecondary.main, padding: theme.spacing(2), borderRadius: '12px', width: '90%', - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow?.main, [theme.breakpoints.down('md' as Breakpoint)]: { marginTop: '64px', }, diff --git a/src/components/Superfest/AvailableMissionsList/AvailableMissionsList.tsx b/src/components/Superfest/AvailableMissionsList/AvailableMissionsList.tsx index 53efa9657..88b0460b5 100644 --- a/src/components/Superfest/AvailableMissionsList/AvailableMissionsList.tsx +++ b/src/components/Superfest/AvailableMissionsList/AvailableMissionsList.tsx @@ -1,13 +1,4 @@ import type { Quest } from '@/types/loyaltyPass'; -import { QuestCard } from '../QuestCard/QuestCard'; -import { QuestCardSkeleton } from '../QuestCard/QuestCardSkeleton'; -import { - AvailableMissionsContainer, - AvailableMissionsHeader, - AvailableMissionsStack, - AvailableMissionsTitle, -} from './AvailableMissionsList.style'; -import { useOngoingFestMissions } from 'src/hooks/useOngoingFestMissions'; import { Box, type SelectChangeEvent, @@ -15,8 +6,17 @@ import { useMediaQuery, } from '@mui/material'; import { useState } from 'react'; -import { MissionsFilter } from '../MissionsFilter/MissionsFilter'; +import { useOngoingFestMissions } from 'src/hooks/useOngoingFestMissions'; import { checkInclusion } from '../ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel'; +import { MissionsFilter } from '../MissionsFilter/MissionsFilter'; +import { QuestCard } from '../QuestCard/QuestCard'; +import { QuestCardSkeleton } from '../QuestCard/QuestCardSkeleton'; +import { + AvailableMissionsContainer, + AvailableMissionsHeader, + AvailableMissionsStack, + AvailableMissionsTitle, +} from './AvailableMissionsList.style'; const chains = ['Optimism', 'Base', 'Mode', 'Fraxtal']; @@ -27,7 +27,6 @@ const category = [ 'Derivatives', 'Yield', ]; - interface AvailableMissionsListProps { quests?: Quest[]; pastCampaigns?: string[]; @@ -47,14 +46,12 @@ export const AvailableMissionsList = ({ const [chainsFilter, setChainsFilter] = useState([]); const [categoryFilter, setCategoryFilter] = useState([]); const { url } = useOngoingFestMissions(); - const handleChainChange = (event: SelectChangeEvent) => { const { target: { value }, } = event; setChainsFilter(typeof value === 'string' ? value.split(',') : value); }; - const handleCategoryChange = ( event: SelectChangeEvent, ) => { @@ -63,7 +60,6 @@ export const AvailableMissionsList = ({ } = event; setCategoryFilter(typeof value === 'string' ? value.split(',') : value); }; - return ( @@ -118,7 +114,6 @@ export const AvailableMissionsList = ({ quest.attributes?.CustomInformation?.['claimingIds']; const rewardsIds = quest.attributes?.CustomInformation?.['rewardsIds']; - //todo: exclude in a dedicated helper function if (chains && chainsFilter && chainsFilter.length > 0) { let included = false; @@ -145,7 +140,6 @@ export const AvailableMissionsList = ({ ) { return undefined; } - let completed = false; if (rewardsIds && pastCampaigns) { completed = checkInclusion(pastCampaigns, rewardsIds); @@ -164,6 +158,8 @@ export const AvailableMissionsList = ({ active={true} title={quest?.attributes.Title} image={String(imgURL)} + id={quest.id} + label={quest?.attributes.Label} points={quest?.attributes.Points} link={quest?.attributes.Link} startDate={quest?.attributes.StartDate} diff --git a/src/components/Superfest/MissionsFilter/MissionsFilter.tsx b/src/components/Superfest/MissionsFilter/MissionsFilter.tsx index 6d7835407..ad5ad0b64 100644 --- a/src/components/Superfest/MissionsFilter/MissionsFilter.tsx +++ b/src/components/Superfest/MissionsFilter/MissionsFilter.tsx @@ -1,10 +1,10 @@ import { type Theme, useTheme } from '@mui/material'; -import OutlinedInput from '@mui/material/OutlinedInput'; -import MenuItem from '@mui/material/MenuItem'; +import Checkbox from '@mui/material/Checkbox'; import FormControl from '@mui/material/FormControl'; import ListItemText from '@mui/material/ListItemText'; +import MenuItem from '@mui/material/MenuItem'; +import OutlinedInput from '@mui/material/OutlinedInput'; import Select, { type SelectChangeEvent } from '@mui/material/Select'; -import Checkbox from '@mui/material/Checkbox'; import { SoraTypography } from '../Superfest.style'; const ITEM_HEIGHT = 48; @@ -27,14 +27,12 @@ function getStyles(name: string, personName: readonly string[], theme: Theme) { : theme.typography.fontWeightMedium, }; } - interface MissionsFilterProps { title: string; options: string[]; activeChoices: string[]; handleChange: (event: SelectChangeEvent) => void; } - export const MissionsFilter = ({ title, options, diff --git a/src/components/Superfest/NFTClaimingBox/NFTCard/NFTCard.style.ts b/src/components/Superfest/NFTClaimingBox/NFTCard/NFTCard.style.ts index b5c9c061c..d1a920319 100644 --- a/src/components/Superfest/NFTClaimingBox/NFTCard/NFTCard.style.ts +++ b/src/components/Superfest/NFTClaimingBox/NFTCard/NFTCard.style.ts @@ -8,8 +8,7 @@ export const NFTCardMainBox = styled(Box)(({ theme }) => ({ })); export const NFTCardBotomBox = styled(Box)(({ theme }) => ({ - backgroundColor: '#fff0ca', - + backgroundColor: theme.palette.bgTertiary.main, height: '72px', justifyContent: 'center', alignContent: 'center', diff --git a/src/components/Superfest/NFTClaimingBox/NFTClaimingBox.style.ts b/src/components/Superfest/NFTClaimingBox/NFTClaimingBox.style.ts index 13f5f0ac6..f6d10c2e1 100644 --- a/src/components/Superfest/NFTClaimingBox/NFTClaimingBox.style.ts +++ b/src/components/Superfest/NFTClaimingBox/NFTClaimingBox.style.ts @@ -7,10 +7,7 @@ export const NFTClaimingContainer = styled(Box)(({ theme }) => ({ padding: theme.spacing(2), borderRadius: '12px', width: '90%', - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, [theme.breakpoints.down('md' as Breakpoint)]: { marginTop: '64px', }, diff --git a/src/components/Superfest/QuestCard/QuestCard.style.ts b/src/components/Superfest/QuestCard/QuestCard.style.ts index 414a8561e..41c845e57 100644 --- a/src/components/Superfest/QuestCard/QuestCard.style.ts +++ b/src/components/Superfest/QuestCard/QuestCard.style.ts @@ -16,6 +16,7 @@ export const QuestCardMainBox = styled(Box)(({ theme }) => ({ export const QuestCardBottomBox = styled(Box)(({ theme }) => ({ display: 'flex', + color: theme.palette.text.primary, flexDirection: 'column', justifyContent: 'space-between', flexGrow: 1, @@ -23,7 +24,7 @@ export const QuestCardBottomBox = styled(Box)(({ theme }) => ({ paddingBottom: '24px', paddingLeft: '16px', paddingRight: '16px', - backgroundColor: '#fff0ca', + backgroundColor: theme.palette.bgTertiary.main, // backgroundColor: '#fff0ca', borderBottomLeftRadius: '8px', borderBottomRightRadius: '8px', })); diff --git a/src/components/Superfest/QuestCard/QuestCard.tsx b/src/components/Superfest/QuestCard/QuestCard.tsx index 935fef5cc..7f6ccdc89 100644 --- a/src/components/Superfest/QuestCard/QuestCard.tsx +++ b/src/components/Superfest/QuestCard/QuestCard.tsx @@ -1,12 +1,23 @@ +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import { Box } from '@mui/material'; import Image from 'next/image'; +import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useTranslation } from 'react-i18next'; +import { APYIcon } from 'src/components/illustrations/APYIcon'; +import { OPBadge } from 'src/components/illustrations/OPBadge'; +import { + TrackingAction, + TrackingCategory, + TrackingEventParameter, +} from 'src/const/trackingKeys'; +import { useMissionsMaxAPY } from 'src/hooks/useMissionsMaxAPY'; +import { useUserTracking } from 'src/hooks/userTracking'; import { Button } from '../../Button'; import { SuperfestXPIcon } from '../../illustrations/XPIcon'; import { FlexSpaceBetweenBox, SoraTypography } from '../Superfest.style'; import type { Chain } from '../SuperfestPage/Banner/Banner'; import { FlexCenterRowBox } from '../SuperfestPage/SuperfestMissionPage.style'; -import Link from 'next/link'; import { OPBadgeRelativeBox, QuestCardBottomBox, @@ -16,11 +27,6 @@ import { XPDisplayBox, XPIconBox, } from './QuestCard.style'; -import { OPBadge } from 'src/components/illustrations/OPBadge'; -import { Box } from '@mui/material'; -import CheckCircleIcon from '@mui/icons-material/CheckCircle'; -import { useMissionsMaxAPY } from 'src/hooks/useMissionsMaxAPY'; -import { APYIcon } from 'src/components/illustrations/APYIcon'; export interface RewardsInterface { logo: string; @@ -32,7 +38,9 @@ interface QuestCardProps { active?: boolean; title?: string; image?: string; + id?: string | number; points?: number; + label?: string; link?: string; startDate?: string; endDate?: string; @@ -46,13 +54,14 @@ interface QuestCardProps { variableWeeklyAPY?: boolean; rewardRange?: string; } - export const QuestCard = ({ active, title, image, + id, points, link, + label, startDate, endDate, slug, @@ -66,10 +75,23 @@ export const QuestCard = ({ const { t } = useTranslation(); const router = useRouter(); const { apy, isLoading, isSuccess } = useMissionsMaxAPY(claimingIds); - + const { trackEvent } = useUserTracking(); + const handleClick = () => { + trackEvent({ + category: TrackingCategory.Quests, + action: TrackingAction.ClickQuestCard, + label: 'click-quest-card', + data: { + [TrackingEventParameter.QuestCardTitle]: title || '', + [TrackingEventParameter.QuestCardId]: id || '', + [TrackingEventParameter.QuestCardPlatform]: 'superfest', + [TrackingEventParameter.QuestCardLabel]: label || '', + }, + }); + }; return ( - + {image && ( theme.breakpoints.down('md'), ); - return ( diff --git a/src/components/Superfest/Superfest.style.ts b/src/components/Superfest/Superfest.style.ts index 4796cd2c4..2011893aa 100644 --- a/src/components/Superfest/Superfest.style.ts +++ b/src/components/Superfest/Superfest.style.ts @@ -1,5 +1,5 @@ import { Box, Typography, styled } from '@mui/material'; -import { sequel65, sequel85, sora } from 'src/fonts/fonts'; +import { sora } from 'src/fonts/fonts'; export const SuperfestContainer = styled(Box)(() => ({ background: 'transparent', @@ -32,11 +32,3 @@ export const SuperfestMainBox = styled(Box)(() => ({ export const SoraTypography = styled(Typography)(() => ({ fontFamily: sora.style.fontFamily, })); - -export const Sequel85Typography = styled(Typography)(() => ({ - fontFamily: sequel85.style.fontFamily, -})); - -export const Sequel65Typography = styled(Typography)(() => ({ - fontFamily: sequel65.style.fontFamily, -})); diff --git a/src/components/Superfest/Superfest.tsx b/src/components/Superfest/Superfest.tsx index 17e95f465..5c7738f2f 100644 --- a/src/components/Superfest/Superfest.tsx +++ b/src/components/Superfest/Superfest.tsx @@ -1,13 +1,13 @@ import { useAccounts } from '@/hooks/useAccounts'; -import { SuperfestContainer, SuperfestMainBox } from './Superfest.style'; -import { RewardsCarousel } from './Rewards/RewardsCarousel'; -import { NFTClaimingBox } from './NFTClaimingBox/NFTClaimingBox'; -import { HeroBox } from './HeroBox/HeroBox'; -import { AvailableMissionsList } from './AvailableMissionsList/AvailableMissionsList'; -import { ActiveSuperfestMissionsCarousel } from './ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel'; -import { useOngoingFestMissions } from 'src/hooks/useOngoingFestMissions'; import { useMerklRewards } from 'src/hooks/useMerklRewardsOnSpecificToken'; +import { useOngoingFestMissions } from 'src/hooks/useOngoingFestMissions'; import { useTurtleMember } from 'src/hooks/useTurtleMember'; +import { ActiveSuperfestMissionsCarousel } from './ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel'; +import { AvailableMissionsList } from './AvailableMissionsList/AvailableMissionsList'; +import { HeroBox } from './HeroBox/HeroBox'; +import { NFTClaimingBox } from './NFTClaimingBox/NFTClaimingBox'; +import { RewardsCarousel } from './Rewards/RewardsCarousel'; +import { SuperfestContainer, SuperfestMainBox } from './Superfest.style'; export const Superfest = () => { //HOOKS @@ -22,6 +22,7 @@ export const Superfest = () => { } = useMerklRewards({ rewardChainId: 10, userAddress: account?.address, + rewardToken: '0x4200000000000000000000000000000000000042', // OP }); const { isMember, diff --git a/src/components/Superfest/SuperfestPage/BackButton/BackButton.tsx b/src/components/Superfest/SuperfestPage/BackButton/BackButton.tsx index 055b83f5b..d72633e50 100644 --- a/src/components/Superfest/SuperfestPage/BackButton/BackButton.tsx +++ b/src/components/Superfest/SuperfestPage/BackButton/BackButton.tsx @@ -1,9 +1,9 @@ -import { Button } from 'src/components/Button'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import { useRouter } from 'next/navigation'; +import { Button } from 'src/components/Button'; import { JUMPER_FEST_PATH } from 'src/const/urls'; -import { BackButtonMainBox } from './BackButton.style'; import { SoraTypography } from '../../Superfest.style'; +import { BackButtonMainBox } from './BackButton.style'; export const BackButton = () => { const router = useRouter(); diff --git a/src/components/Superfest/SuperfestPage/Banner/Banner.style.ts b/src/components/Superfest/SuperfestPage/Banner/Banner.style.ts index 44c1e2be9..a159662a0 100644 --- a/src/components/Superfest/SuperfestPage/Banner/Banner.style.ts +++ b/src/components/Superfest/SuperfestPage/Banner/Banner.style.ts @@ -28,8 +28,9 @@ export const BannerBottomBox = styled(Box)(({ theme }) => ({ display: 'flex', flexDirection: 'column', justifyContent: 'space-between', + color: theme.palette.text.primary, padding: '32px', - backgroundColor: '#fdfbef', + backgroundColor: theme.palette.bgSecondary.main, // backgroundColor: '#fdfbef', [theme.breakpoints.down('md' as Breakpoint)]: { height: '55%', }, diff --git a/src/components/Superfest/SuperfestPage/Banner/Banner.tsx b/src/components/Superfest/SuperfestPage/Banner/Banner.tsx index 775ef716d..12d41f070 100644 --- a/src/components/Superfest/SuperfestPage/Banner/Banner.tsx +++ b/src/components/Superfest/SuperfestPage/Banner/Banner.tsx @@ -1,25 +1,22 @@ +import type { Theme } from '@mui/material'; +import { useMediaQuery } from '@mui/material'; import Image from 'next/image'; +import { SuperfestDailyRewards } from 'src/components/illustrations/SuperfestDailyRewards'; +import { SuperfestWeeklyRewards } from 'src/components/illustrations/SuperfestWeeklyRewards'; import { type Quest } from 'src/types/loyaltyPass'; +import { checkInclusion } from '../../ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel'; import { - BannerImageBox, + BadgeMainBox, + BadgeRelativeBox, BannerBottomBox, + BannerImageBox, BannerMainBox, BannerTitleBox, - RewardMainBox, BannerTitleTypography, - BannerLabelBox, + RewardMainBox, RotatingBox, - BadgeRelativeBox, - BadgeMainBox, } from './Banner.style'; import { RewardBox } from './Rewards/RewardBox'; -import { checkInclusion } from '../../ActiveSuperfestMissionsCarousel/ActiveSuperfestMissionsCarousel'; -import type { Theme } from '@mui/material'; -import { Box, useMediaQuery } from '@mui/material'; -import { SoraTypography } from '../../Superfest.style'; -import { OPBadge } from 'src/components/illustrations/OPBadge'; -import { SuperfestDailyRewards } from 'src/components/illustrations/SuperfestDailyRewards'; -import { SuperfestWeeklyRewards } from 'src/components/illustrations/SuperfestWeeklyRewards'; interface SuperfestMissionPageVar { quest: Quest; @@ -32,7 +29,6 @@ export interface Chain { logo: string; name: string; } - export const BannerBox = ({ quest, baseUrl, @@ -52,7 +48,6 @@ export const BannerBox = ({ ? attributes.Image?.data?.attributes?.url : attributes?.BannerImage?.data[0]?.attributes?.url; const imgURL = new URL(bannerImageURL, baseUrl); - let completed = false; if (rewardsIds && pastCampaigns) { completed = checkInclusion(pastCampaigns, rewardsIds); diff --git a/src/components/Superfest/SuperfestPage/Banner/Rewards/RewardBox.tsx b/src/components/Superfest/SuperfestPage/Banner/Rewards/RewardBox.tsx index 2e0333e57..8d64b3030 100644 --- a/src/components/Superfest/SuperfestPage/Banner/Rewards/RewardBox.tsx +++ b/src/components/Superfest/SuperfestPage/Banner/Rewards/RewardBox.tsx @@ -1,7 +1,7 @@ import Image from 'next/image'; -import { RewardBottomBox, SupportedChainsBox } from '../Banner.style'; import { SoraTypography } from 'src/components/Superfest/Superfest.style'; -import { RewardTopBox, RewardSubtitleBox } from './RewardBox.style'; +import { RewardBottomBox, SupportedChainsBox } from '../Banner.style'; +import { RewardSubtitleBox, RewardTopBox } from './RewardBox.style'; interface RewardBoxProps { title: string; diff --git a/src/components/Superfest/SuperfestPage/CTA/MissionCTA.style.ts b/src/components/Superfest/SuperfestPage/CTA/MissionCTA.style.ts index 7beba7be8..48e28215b 100644 --- a/src/components/Superfest/SuperfestPage/CTA/MissionCTA.style.ts +++ b/src/components/Superfest/SuperfestPage/CTA/MissionCTA.style.ts @@ -1,6 +1,6 @@ import { sequel65, sora } from '@/fonts/fonts'; import type { BoxProps, Breakpoint } from '@mui/material'; -import { Box, Typography, alpha, lighten, darken } from '@mui/material'; +import { Box, Typography, alpha, darken } from '@mui/material'; import { styled } from '@mui/material/styles'; import { IconButtonPrimary } from 'src/components/IconButton'; @@ -30,7 +30,6 @@ export const MissionCtaContainer = styled(Box)(({ theme }) => ({ flexDirection: 'row', }, })); - export const SeveralMissionCtaContainer = styled(Box)( ({ theme }) => ({ width: '100%', @@ -49,13 +48,17 @@ export const SeveralMissionCtaContainer = styled(Box)( textAlign: 'center', transition: 'background-color 250ms', borderRadius: '16px', - backgroundColor: '#fff0ca', + backgroundColor: theme.palette.bgTertiary.main, // backgroundColor: '#fff0ca', '&:hover': { cursor: 'pointer', backgroundColor: theme.palette.mode === 'light' - ? darken('#fff0ca', 0.02) //todo: add to theme - : alpha('#fff0ca', 0.16), + ? darken(theme.palette.bgTertiary.main, 0.02) //todo: add to theme + : alpha(theme.palette.bgTertiary.main, 0.16), + // backgroundColor: + // theme.palette.mode === 'light' + // ? darken('#fff0ca', 0.02) //todo: add to theme + // : alpha('#fff0ca', 0.16), }, [theme.breakpoints.up('sm' as Breakpoint)]: { gap: theme.spacing(4), @@ -63,7 +66,6 @@ export const SeveralMissionCtaContainer = styled(Box)( }, }), ); - export const MissionCtaTitle = styled(Box)(({ theme }) => ({ fontFamily: sora.style.fontFamily, fontWeight: 700, @@ -71,7 +73,6 @@ export const MissionCtaTitle = styled(Box)(({ theme }) => ({ fontSize: '32px', lineHeight: '38px', userSelect: 'none', - [theme.breakpoints.up('sm' as Breakpoint)]: { fontSize: '40px', lineHeight: '56px', @@ -92,7 +93,6 @@ export const MissionCtaButton = styled(IconButtonPrimary)(({ theme }) => ({ display: 'flex', }, })); - export const CTABox = styled(Box)(({ theme }) => ({ width: '80%', maxWidth: '1210px', @@ -103,7 +103,6 @@ export const CTABox = styled(Box)(({ theme }) => ({ alignContent: 'center', alignItems: 'center', })); - export const SeveralCTABox = styled(Box)(({ theme }) => ({ width: '100%', display: 'flex', @@ -119,7 +118,6 @@ export const SeveralCTABox = styled(Box)(({ theme }) => ({ justifyContent: 'center', }, })); - export const StartedTitleTypography = styled(Typography)(({ theme }) => ({ fontFamily: sequel65.style.fontFamily, [theme.breakpoints.down('md' as Breakpoint)]: { @@ -133,7 +131,6 @@ export const StartedTitleTypography = styled(Typography)(({ theme }) => ({ lineHeight: '20px', }, })); - export const StartedTitleBox = styled(Box)(({ theme }) => ({ display: 'flex', flexDirection: 'column', @@ -149,7 +146,7 @@ export const CTAMainBox = styled(Box)(({ theme }) => ({ marginTop: '64px', borderRadius: '8px', padding: '32px', - backgroundColor: '#fdfbef', + backgroundColor: theme.palette.bgSecondary.main, //'#fdfbef', })); export const CTAExplanationBox = styled(Box)(({ theme }) => ({ diff --git a/src/components/Superfest/SuperfestPage/CTA/MissionCTA.tsx b/src/components/Superfest/SuperfestPage/CTA/MissionCTA.tsx index 4e5937e24..9738ec6ed 100644 --- a/src/components/Superfest/SuperfestPage/CTA/MissionCTA.tsx +++ b/src/components/Superfest/SuperfestPage/CTA/MissionCTA.tsx @@ -1,8 +1,19 @@ import { useUserTracking } from '@/hooks/userTracking/useUserTracking'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import { Box, type Theme, useMediaQuery, useTheme } from '@mui/material'; +import { Box, type Theme, useMediaQuery } from '@mui/material'; +import Image from 'next/image'; import Link from 'next/link'; -import { useTranslation } from 'react-i18next'; +import { APYIcon } from 'src/components/illustrations/APYIcon'; +import { XPDisplayBox } from 'src/components/ProfilePage/QuestCard/QuestCard.style'; +import { + TrackingAction, + TrackingCategory, + TrackingEventParameter, +} from 'src/const/trackingKeys'; +import { XPIconBox } from '../../QuestCard/QuestCard.style'; +import { SoraTypography } from '../../Superfest.style'; +import { SignatureCTA } from '../SignatureCTA/SignatureCTA'; +import { FlexCenterRowBox } from '../SuperfestMissionPage.style'; import { CTAExplanationBox, CTAMainBox, @@ -12,13 +23,6 @@ import { StartedTitleBox, StartedTitleTypography, } from './MissionCTA.style'; -import Image from 'next/image'; -import { SoraTypography } from '../../Superfest.style'; -import { SignatureCTA } from '../SignatureCTA/SignatureCTA'; -import { FlexCenterRowBox } from '../SuperfestMissionPage.style'; -import { XPDisplayBox } from 'src/components/ProfilePage/QuestCard/QuestCard.style'; -import { XPIconBox } from '../../QuestCard/QuestCard.style'; -import { APYIcon } from 'src/components/illustrations/APYIcon'; export interface CTALinkInt { logo: string; @@ -29,12 +33,12 @@ export interface CTALinkInt { apy?: number; weeklyApy?: string; } - interface MissionCtaProps { title?: string; url?: string; rewards?: number; id?: number; + label?: string; CTAs: CTALinkInt[]; variableWeeklyAPY?: boolean; signature?: boolean; @@ -45,31 +49,41 @@ interface MissionCtaProps { export const MissionCTA = ({ CTAs, rewards, + id, + label, variableWeeklyAPY, signature, rewardRange, isTurtleMember, }: MissionCtaProps) => { - const { t } = useTranslation(); const { trackEvent } = useUserTracking(); - const theme = useTheme(); const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'), ); - - const handleClick = () => { - // trackEvent({ - // category: TrackingCategory.BlogArticle, - // // action: TrackingAction.ClickMissionCta, - // label: 'click-blog-cta', - // disableTrackingTool: [EventTrackingTool.ARCx, EventTrackingTool.Cookie3], - // data: { - // [TrackingEventParameter.ArticleTitle]: title || '', - // [TrackingEventParameter.ArticleID]: id || '', - // }, - // }); + const handleClick = ({ + rewardId, + id, + claimingId, + title, + }: { + rewardId?: string; + id?: number; + claimingId: string; + title?: string; + }) => { + trackEvent({ + category: TrackingCategory.Missions, + action: TrackingAction.ClickMissionCta, + label: `click-mission-cta-${id}`, + data: { + [TrackingEventParameter.MissionCtaRewardId]: rewardId || '', + [TrackingEventParameter.MissionCtaClaimingId]: claimingId || '', + [TrackingEventParameter.MissionCtaTitle]: title || '', + [TrackingEventParameter.MissionCtaPartnerId]: id || '', + [TrackingEventParameter.MissionCtaLabel]: label || '', + }, + }); }; - return ( @@ -102,7 +116,16 @@ export const MissionCTA = ({ href={CTA.link || '/'} target="_blank" > - + + handleClick({ + id, + rewardId: CTA.rewardId, + claimingId: CTA.claimingId, + title: CTA.text, + }) + } + > )} {!isMobile && ( - + + handleClick({ + id, + rewardId: CTA.rewardId, + title: CTA.text, + claimingId: CTA.claimingId, + }) + } + > { +export const DescriptionBoxSF = ({ + longTitle, + description, +}: DescriptionBox) => { return ( {longTitle} diff --git a/src/components/Superfest/SuperfestPage/InformationBox/InformationAlertBox.style.ts b/src/components/Superfest/SuperfestPage/InformationBox/InformationAlertBox.style.ts index 191a73421..d86829bf1 100644 --- a/src/components/Superfest/SuperfestPage/InformationBox/InformationAlertBox.style.ts +++ b/src/components/Superfest/SuperfestPage/InformationBox/InformationAlertBox.style.ts @@ -4,6 +4,7 @@ export const InformationBox = styled(Box)(({ theme }) => ({ width: '80%', maxWidth: '960px', display: 'flex', + color: theme.palette.text.primary, marginTop: '32px', flexDirection: 'row', justifyContent: 'center', @@ -13,5 +14,5 @@ export const InformationBox = styled(Box)(({ theme }) => ({ border: '2px solid', padding: '32px', borderRadius: '24px', - borderColor: theme.palette.black.main, + borderColor: theme.palette.text.primary, })); diff --git a/src/components/Superfest/SuperfestPage/StepsBox/StepsBox.tsx b/src/components/Superfest/SuperfestPage/StepsBox/StepsBox.tsx index cc6d6012b..f1b0af2d4 100644 --- a/src/components/Superfest/SuperfestPage/StepsBox/StepsBox.tsx +++ b/src/components/Superfest/SuperfestPage/StepsBox/StepsBox.tsx @@ -1,10 +1,10 @@ +import { type RootNode } from 'node_modules/@strapi/blocks-react-renderer/dist/BlocksRenderer'; +import { CustomRichBlocks } from 'src/components/Blog'; +import { DescriptionTitleTypography } from '../DescriptionBoxSF/DescriptionBoxSF.style'; import { LeftTextBox, SuperfestPageElementContainer, } from '../SuperfestMissionPage.style'; -import { CustomRichBlocks } from 'src/components/Blog'; -import { type RootNode } from 'node_modules/@strapi/blocks-react-renderer/dist/BlocksRenderer'; -import { DescriptionTitleTypography } from '../DescriptionBox/DescriptionBox.style'; interface StepsBoxProps { steps?: RootNode[]; @@ -19,14 +19,12 @@ export const StepsBox = ({ steps, baseUrl }: StepsBoxProps) => { Steps to complete the mission - <> - - + ); }; diff --git a/src/components/Superfest/SuperfestPage/SuperfestMisisonPage.tsx b/src/components/Superfest/SuperfestPage/SuperfestMisisonPage.tsx index 7da3b57d0..86b79ebc6 100644 --- a/src/components/Superfest/SuperfestPage/SuperfestMisisonPage.tsx +++ b/src/components/Superfest/SuperfestPage/SuperfestMisisonPage.tsx @@ -1,17 +1,17 @@ -import { SuperfestContainer } from '../Superfest.style'; -import { SuperfestPageMainBox } from './SuperfestMissionPage.style'; +import { useAccounts } from '@/hooks/useAccounts'; import generateKey from 'src/app/lib/generateKey'; -import { MissionCTA } from './CTA/MissionCTA'; +import { useMerklRewards } from 'src/hooks/useMerklRewardsOnSpecificToken'; +import { useMissionsAPY } from 'src/hooks/useMissionsAPY'; +import { useTurtleMember } from 'src/hooks/useTurtleMember'; import { type Quest } from 'src/types/loyaltyPass'; +import { SuperfestContainer } from '../Superfest.style'; import { BackButton } from './BackButton/BackButton'; import { BannerBox } from './Banner/Banner'; -import { DescriptionBox } from './DescriptionBox/DescriptionBox'; -import { StepsBox } from './StepsBox/StepsBox'; +import { MissionCTA } from './CTA/MissionCTA'; +import { DescriptionBoxSF } from './DescriptionBoxSF/DescriptionBoxSF'; import { InformationAlertBox } from './InformationBox/InformationAlertBox'; -import { useMerklRewards } from 'src/hooks/useMerklRewardsOnSpecificToken'; -import { useAccounts } from '@/hooks/useAccounts'; -import { useMissionsAPY } from 'src/hooks/useMissionsAPY'; -import { useTurtleMember } from 'src/hooks/useTurtleMember'; +import { StepsBox } from './StepsBox/StepsBox'; +import { SuperfestPageMainBox } from './SuperfestMissionPage.style'; interface SuperfestMissionPageVar { quest: Quest; @@ -34,6 +34,7 @@ export const SuperfestMissionPage = ({ const { pastCampaigns } = useMerklRewards({ rewardChainId: 10, userAddress: account?.address, + rewardToken: '0x4200000000000000000000000000000000000042', // OP }); const { isMember, @@ -42,7 +43,7 @@ export const SuperfestMissionPage = ({ } = useTurtleMember({ userAddress: account?.address, }); - const { isLoading, isSuccess, CTAsWithAPYs } = useMissionsAPY(CTAs); + const { CTAsWithAPYs } = useMissionsAPY(CTAs); return ( @@ -62,27 +63,29 @@ export const SuperfestMissionPage = ({ } /> {/* Big CTA */} - 0 && rewardType === 'weekly'} - signature={missionType === 'turtle_signature'} - isTurtleMember={isMember} - rewardRange={rewardRange} - /> + {CTAsWithAPYs?.length > 0 && ( + 0 && rewardType === 'weekly'} + signature={missionType === 'turtle_signature'} + rewardRange={rewardRange} + /> + )} {/* Subtitle and description */} - {/* Steps */} {/* Todo: remove the check for steps */} - {attributes?.Steps && attributes?.Steps?.length > 1 ? ( + {attributes?.Steps && attributes?.Steps?.length > 0 && ( - ) : undefined} + )} {/* Additional Info */} {attributes?.Information && ( diff --git a/src/components/Superfest/SuperfestPage/SuperfestMissionPage.style.ts b/src/components/Superfest/SuperfestPage/SuperfestMissionPage.style.ts index 08b0e24c0..6ddd1ee6a 100644 --- a/src/components/Superfest/SuperfestPage/SuperfestMissionPage.style.ts +++ b/src/components/Superfest/SuperfestPage/SuperfestMissionPage.style.ts @@ -2,6 +2,7 @@ import { Box, styled } from '@mui/material'; export const SuperfestPageElementContainer = styled(Box)(({ theme }) => ({ width: '80%', + color: theme.palette.text.primary, maxWidth: '960px', display: 'flex', marginTop: '64px', diff --git a/src/components/WelcomeScreen/ToolCard/ToolCard.style.ts b/src/components/WelcomeScreen/ToolCard/ToolCard.style.ts index 0e72e86f3..db152165e 100644 --- a/src/components/WelcomeScreen/ToolCard/ToolCard.style.ts +++ b/src/components/WelcomeScreen/ToolCard/ToolCard.style.ts @@ -18,10 +18,7 @@ export const ToolCardContainer = styled('div')(({ theme }) => ({ ? theme.palette.accent1Alt.main : theme.palette.primary.main, userSelect: 'none', - backgroundColor: - theme.palette.mode === 'light' - ? alpha(theme.palette.white.main, 0.48) - : alpha(theme.palette.white.main, 0.08), + backgroundColor: theme.palette.bgSecondary.main, borderRadius: '16px', transitionProperty: 'box-shadow, background', transitionDuration: '.3s', diff --git a/src/components/WelcomeScreen/ToolModal/ToolModal.style.ts b/src/components/WelcomeScreen/ToolModal/ToolModal.style.ts index a830bfb66..be8859cd2 100644 --- a/src/components/WelcomeScreen/ToolModal/ToolModal.style.ts +++ b/src/components/WelcomeScreen/ToolModal/ToolModal.style.ts @@ -28,10 +28,7 @@ export const ModalContainer = styled(Box)(({ theme }) => ({ margin: 'auto', paddingBottom: theme.spacing(3), borderRadius: '12px', - boxShadow: - theme.palette.mode === 'dark' - ? '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)' - : '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', + boxShadow: theme.palette.shadow.main, width: `calc( 100% - ${theme.spacing(3)})`, maxWidth: 640, maxHeight: '85%', diff --git a/src/const/partnerRewardsTheme.ts b/src/const/partnerRewardsTheme.ts new file mode 100644 index 000000000..ad0733cbc --- /dev/null +++ b/src/const/partnerRewardsTheme.ts @@ -0,0 +1,26 @@ +export const PROFILE_CAMPAIGN_DARK_COLOR = '#ECEEF0'; +export const PROFILE_CAMPAIGN_LIGHT_COLOR = '#9E1E1A'; + +export const PROFILE_CAMPAIGN_DARK_TOKEN = + 'https://strapi.li.finance/uploads/Sei_Symbol_Gradient_127605e2de.png'; +export const PROFILE_CAMPAIGN_LIGHT_TOKEN = + 'https://strapi.li.finance/uploads/Sei_Symbol_Gradient_127605e2de.png'; + +export const PROFILE_CAMPAIGN_DARK_CHAIN = + 'https://strapi.li.finance/uploads/Sei_Symbol_Gradient_127605e2de.png'; +export const PROFILE_CAMPAIGN_LIGHT_CHAIN = + 'https://strapi.li.finance/uploads/Sei_Symbol_Gradient_127605e2de.png'; + +export const PROFILE_CAMPAIGN_SCANNER = 'https://seitrace.com'; + +export const REWARDS_DECIMALS = 1; + +export const REWARD_TOKEN_CHAINID = 1329; +export const REWARD_TOKEN_ADDRESS = + '0xE30feDd158A2e3b13e9badaeABaFc5516e95e8C7'; +export const REWARD_CLAIMING_ADDRESS = + '0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae'; + +export const REWARDS_CHAIN_IDS = ['1329']; + +export const MERKL_CREATOR_TAG = 'sei-jumper'; diff --git a/src/const/trackingKeys.ts b/src/const/trackingKeys.ts index 1d4037ecd..ea85db677 100644 --- a/src/const/trackingKeys.ts +++ b/src/const/trackingKeys.ts @@ -62,6 +62,10 @@ export enum TrackingAction { ClickShareArticleX = 'action_share_article_x', ClickShareArticleLink = 'action_share_article_link', + // Quests + ClickQuestCard = 'action_click_quest_card', + ClickMissionCta = 'action_click_mission_cta', + // Pagination ClickPagination = 'action_click_pagination', @@ -95,6 +99,8 @@ export enum TrackingCategory { BlogFeaturedArticle = 'cat_blog_featured_article', BlogArticlesBoard = 'cat_blog_articles_board', BlogArticle = 'cat_blog_article', + Missions = 'cat_missions', + Quests = 'cat_quests', } // can be used as custom dimensions / metrics @@ -172,4 +178,16 @@ export enum TrackingEventParameter { // Pagination Pagination = 'param_pagination', PaginationCat = 'param_pagination_cat', + + // Quests + QuestCardTitle = 'param_quest_card_title', + QuestCardLabel = 'param_quest_card_label', + QuestCardId = 'param_quest_card_id', + QuestCardPlatform = 'param_quest_card_platform', + MissionCtaRewardId = 'param_mission_cta_reward_id', + MissionCtaClaimingId = 'param_mission_cta_claiming_id', + MissionCtaTitle = 'param_mission_cta_title', + MissionCtaLabel = 'param_mission_cta_label', + MissionCtaPartnerId = 'param_mission_cta_partner_id', + MissionCtaCampaign = 'param_mission_cta_campaign', } diff --git a/src/const/urls.ts b/src/const/urls.ts index bcc04a795..e0a349fc1 100644 --- a/src/const/urls.ts +++ b/src/const/urls.ts @@ -15,6 +15,7 @@ export const JUMPER_TX_PATH = '/tx/'; export const JUMPER_WALLET_PATH = '/wallet/'; export const JUMPER_FEST_PATH = '/superfest/'; export const JUMPER_MEMECOIN_PATH = '/memecoins/'; +export const JUMPER_QUESTS_PATH = '/quests/'; export const GALXE_ENDPOINT = 'https://graphigo.prd.galaxy.eco/query'; diff --git a/src/hooks/useCheckWalletLinking.ts b/src/hooks/useCheckWalletLinking.ts new file mode 100644 index 000000000..a163ab915 --- /dev/null +++ b/src/hooks/useCheckWalletLinking.ts @@ -0,0 +1,73 @@ +import type { Chain, ChainId, ExtendedChain } from '@lifi/types'; +import { useQuery } from '@tanstack/react-query'; +import { ChainType, getChains } from '@lifi/sdk'; + +export interface useCheckWalletLinkingProps { + userAddress?: string; + checkWalletLinking: boolean; +} + +export interface WalletLinkCheckProps { + isSuccess: boolean; + isWalletLinked?: boolean; +} + +export const useCheckWalletLinking = ({ + userAddress, + checkWalletLinking, +}: useCheckWalletLinkingProps): WalletLinkCheckProps => { + const POST_ENDPOINT = 'https://li.quest/v1/advanced/routes'; + const { + data: isWalletLinked, + isSuccess, + isLoading, + } = useQuery({ + queryKey: ['SeiWalletLinking' + userAddress], + queryFn: async () => { + const payload = { + fromAddress: userAddress, + fromAmount: '1000000000000000000', + fromChainId: 1329, + fromTokenAddress: '0x0000000000000000000000000000000000000000', + toChainId: 1329, + toTokenAddress: '0xE30feDd158A2e3b13e9badaeABaFc5516e95e8C7', + options: { + integrator: 'check.jmp.exchange', + order: 'CHEAPEST', + slippage: 0.005, + maxPriceImpact: 0.4, + allowSwitchChain: true, + }, + }; + const res = await fetch(POST_ENDPOINT, { + body: JSON.stringify(payload), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + if (!res.ok) { + return false; + } + + const data = await res.json(); + const errorReason = data?.unavailableRoutes?.filteredOut?.[0]?.reason; + const isSeiErrorMessage = String(errorReason).includes( + 'wallet is not linked to the original SEI address, please go to https://app.sei.io/', + ); + const walletIsNotLink = isSeiErrorMessage && data?.routes.length === 0; + + if (data && !walletIsNotLink) { + return true; + } + return false; + }, + enabled: !!userAddress && checkWalletLinking, + refetchInterval: 1000 * 60 * 60, + }); + + return { + isWalletLinked, + isSuccess, + }; +}; diff --git a/src/hooks/useMerklRewardsOnSpecificToken.ts b/src/hooks/useMerklRewardsOnSpecificToken.ts index 9dc68f46b..50139e424 100644 --- a/src/hooks/useMerklRewardsOnSpecificToken.ts +++ b/src/hooks/useMerklRewardsOnSpecificToken.ts @@ -1,5 +1,10 @@ 'use client'; import { useQuery } from '@tanstack/react-query'; +import { + MERKL_CREATOR_TAG, + REWARD_TOKEN_ADDRESS, + REWARDS_CHAIN_IDS, +} from 'src/const/partnerRewardsTheme'; interface TokenData { accumulated: string; @@ -58,13 +63,11 @@ export interface UseMerklRewardsProps { rewardToken?: string; } -const ACTIVE_CHAINS = ['10', '252', '8453', '34443']; +const ACTIVE_CHAINS = REWARDS_CHAIN_IDS; +const CREATOR_TAG = MERKL_CREATOR_TAG; const MERKL_API = 'https://api.merkl.xyz/v3'; -const CREATOR_TAG = 'superfest'; - -const TOKEN = '0x4200000000000000000000000000000000000042'; // TESTING // const TOKEN = '0x41A65AAE5d1C8437288d5a29B4D049897572758E'; @@ -146,7 +149,8 @@ export const useMerklRewards = ({ }; }) .filter( - (elem) => elem.address.toLowerCase() === String(TOKEN).toLowerCase(), + (elem) => + elem.address.toLowerCase() === String(rewardToken).toLowerCase(), ); } diff --git a/src/hooks/useMissionsMaxAPY.ts b/src/hooks/useMissionsMaxAPY.ts index 17a27ddb7..af24c7544 100644 --- a/src/hooks/useMissionsMaxAPY.ts +++ b/src/hooks/useMissionsMaxAPY.ts @@ -1,10 +1,14 @@ 'use client'; import { useQuery } from '@tanstack/react-query'; import type { MerklApyRes } from './useMissionsAPY'; +import { + MERKL_CREATOR_TAG, + REWARDS_CHAIN_IDS, +} from 'src/const/partnerRewardsTheme'; -const ACTIVE_CHAINS = ['10', '8453', '252', '34443']; +const ACTIVE_CHAINS = REWARDS_CHAIN_IDS; const MERKL_API = 'https://api.merkl.xyz/v3'; -const CREATOR_TAG = 'superfest'; +const CREATOR_TAG = MERKL_CREATOR_TAG; interface useMissionsAPYRes { isLoading: boolean; @@ -15,7 +19,7 @@ interface useMissionsAPYRes { export const useMissionsMaxAPY = ( claimingIds: string[] | undefined, ): useMissionsAPYRes => { - const MERKL_CAMPAIGN_API = `${MERKL_API}/campaigns?chainIds=${ACTIVE_CHAINS.join(',')}&creatorTag=${CREATOR_TAG}`; + const MERKL_CAMPAIGN_API = `${MERKL_API}/campaigns?chainIds=${ACTIVE_CHAINS.join(',')}`; //&creatorTag=${CREATOR_TAG}`; const { data, isSuccess, isLoading } = useQuery({ queryKey: ['accountCampaignInfo'], diff --git a/src/hooks/useOngoingQuests.ts b/src/hooks/useOngoingQuests.ts index 60440f4f9..af1a5a5bc 100644 --- a/src/hooks/useOngoingQuests.ts +++ b/src/hooks/useOngoingQuests.ts @@ -22,6 +22,10 @@ export const useOngoingQuests = (): UseQuestsProps => { apiUrl.searchParams.set('fields[2]', 'Link'); // link apiUrl.searchParams.set('fields[3]', 'StartDate'); // startDate apiUrl.searchParams.set('fields[4]', 'EndDate'); // endDate + apiUrl.searchParams.set('fields[5]', 'Slug'); // Slug + apiUrl.searchParams.set('fields[6]', 'Label'); // label + apiUrl.searchParams.set('fields[7]', 'CustomInformation'); // CustomInformation + apiUrl.searchParams.set('fields[8]', 'Category'); // Category //populate url apiUrl.searchParams.set('populate[0]', 'Image'); apiUrl.searchParams.set('populate[1]', 'quests_platform'); diff --git a/src/providers/ThemeProviderV2.tsx b/src/providers/ThemeProviderV2.tsx index 64796f334..eed1079e2 100644 --- a/src/providers/ThemeProviderV2.tsx +++ b/src/providers/ThemeProviderV2.tsx @@ -1,20 +1,18 @@ 'use client'; -import * as React from 'react'; -import { useTheme } from 'next-themes'; -import { CssBaseline } from '@mui/material'; -import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles'; -import { useEffect, useState } from 'react'; -import { darkTheme, lightTheme } from 'src/theme'; -import { useCookies } from 'react-cookie'; +import { useSettingsStore } from '@/stores/settings'; import { formatConfig, formatTheme, getAvailableThemeModes, } from '@/utils/formatTheme'; +import { CssBaseline } from '@mui/material'; +import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles'; import { deepmerge } from '@mui/utils'; -import { useSettingsStore } from '@/stores/settings'; -import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from 'next-themes'; +import { useEffect, useState } from 'react'; +import { useCookies } from 'react-cookie'; +import { darkTheme, lightTheme } from 'src/theme'; function getPartnerTheme(themes: any[], activeTheme: string) { return themes?.find((d) => d.attributes.uid === activeTheme)?.attributes; diff --git a/src/theme/theme.ts b/src/theme/theme.ts index fc0e5016f..cae2b08b4 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -2,7 +2,7 @@ import type { BackgroundContainerProps } from '@/components/Background'; import type { ComponentsOverrides, ComponentsVariants } from '@mui/material'; import type { Breakpoint, Theme } from '@mui/material/styles'; -import { createTheme } from '@mui/material/styles'; +import { alpha, createTheme } from '@mui/material/styles'; import { deepmerge } from '@mui/utils'; import type React from 'react'; import { inter, urbanist } from 'src/fonts/fonts'; @@ -48,6 +48,8 @@ declare module '@mui/material/styles' { dataBg: Palette['primary']; dataOutline: Palette['primary']; bg: Palette['primary']; + bgSecondary: Palette['primary']; + bgTertiary: Palette['primary']; shadow: Palette['primary']; alphaDark100: Palette['primary']; alphaDark200: Palette['primary']; @@ -81,6 +83,8 @@ declare module '@mui/material/styles' { dataBg?: Palette['primary']; dataOutline?: Palette['primary']; bg?: PaletteOptions['primary']; + bgSecondary?: PaletteOptions['primary']; + bgTertiary?: PaletteOptions['primary']; shadow?: PaletteOptions['primary']; alphaDark100?: PaletteOptions['primary']; alphaDark200?: PaletteOptions['primary']; @@ -156,6 +160,8 @@ declare module '@mui/material/Button' { dataBg: true; dataOutline: true; bg: true; + bgSecondary: true; + bgTertiary: true; shadow: true; alphaDark100: true; alphaDark200: true; @@ -595,6 +601,7 @@ export const lightTheme = createTheme( }, text: { primary: '#000', + secondary: alpha(themeCustomized.palette.black.main, 0.75), }, grey: { 300: '#E5E1EB', @@ -604,10 +611,14 @@ export const lightTheme = createTheme( main: '#F3EBFF', dark: '#F3EBFF', }, + bgSecondary: { + main: alpha(themeCustomized.palette.white.main, 0.48), + }, + bgTertiary: { + main: themeCustomized.palette.white.main, + }, shadow: { - light: '#F3EBFF', - main: '#F3EBFF', - dark: '#F3EBFF', + main: '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.08)', }, primary: { light: '#31007A', @@ -707,6 +718,7 @@ export const darkTheme = createTheme( }, text: { primary: '#fff', + secondary: alpha(themeCustomized.palette.white.main, 0.75), }, grey: { 800: '#302B52', @@ -716,10 +728,14 @@ export const darkTheme = createTheme( main: '#030014', dark: '#030014', }, + bgSecondary: { + main: alpha(themeCustomized.palette.white.main, 0.12), + }, + bgTertiary: { + main: themeCustomized.palette.alphaLight200.main, + }, shadow: { - light: '#F3EBFF', - main: '#F3EBFF', - dark: '#F3EBFF', + main: '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 8px 16px rgba(0, 0, 0, 0.16)', }, primary: { light: '#653BA3', diff --git a/src/utils/strapi/StrapiApi.ts b/src/utils/strapi/StrapiApi.ts index e98594ccd..02643cf8f 100644 --- a/src/utils/strapi/StrapiApi.ts +++ b/src/utils/strapi/StrapiApi.ts @@ -163,6 +163,8 @@ class QuestStrapiApi extends StrapiApi { super({ contentType: 'quests' }); // Set content type to "blog-articles" automatically const questParams = new QuestParams(this.apiUrl); this.apiUrl = questParams.addParams(); + process.env.NEXT_PUBLIC_ENVIRONMENT !== 'production' && + this.apiUrl.searchParams.set('publicationState', 'preview'); } sort(order: 'asc' | 'desc'): this { diff --git a/tests/e2e.spec.ts b/tests/e2e.spec.ts index 33679a873..72a1df180 100644 --- a/tests/e2e.spec.ts +++ b/tests/e2e.spec.ts @@ -5,6 +5,9 @@ import { tabInHeader, openMainMenu, expectMenuToBeVisible, + expectBackgroundColorToHaveCss, + itemInSettingsMenu, + itemInSettingsMenuToBeVisible, } from './testData/commonFunctions'; import values from '../tests/testData/values.json'; @@ -36,6 +39,38 @@ test.describe('Jumper full e2e flow', () => { ).toBeVisible(); await expect(featureCard).toBeVisible(); }); + test('Should open Settings menu', async ({ page }) => { + const settingsTitle = page.locator( + 'xpath=//p[normalize-space(text())="Settings"]', + ); + const bestReturnButton = page.locator( + 'xpath=//button[normalize-space(text())="Best Return"]', + ); + const fastestButton = page.locator( + 'xpath=//button[normalize-space(text())="Fastest"]', + ); + const slowGasPrice = page.locator( + 'xpath=//button[normalize-space(text())="Slow"]', + ); + const fastGasPrice = page.locator( + 'xpath=//button[normalize-space(text())="Fast"]', + ); + const customSlippage = page.locator('xpath=//input[@placeholder="Custom"]'); + await tabInHeader(page, 'Exchange'); + await page.locator('xpath=//div[@class="MuiBox-root mui-afg6ra"]').click(); + await expect(settingsTitle).toBeVisible(); + itemInSettingsMenu(page, 'Route priority'); + await expect(bestReturnButton).toBeEnabled(); + itemInSettingsMenuToBeVisible(page, 'Fastest'); + await fastestButton.click(); + itemInSettingsMenuToBeVisible(page, 'Reset settings'); + itemInSettingsMenu(page, 'Gas price'); + expect(slowGasPrice).toBeEnabled(); + expect(fastGasPrice).toBeEnabled(); + itemInSettingsMenu(page, 'Max. slippage'); + itemInSettingsMenuToBeVisible(page, '0.5'); + await expect(customSlippage).toBeVisible(); + }); test.skip('Should handle welcome screen', async ({ page }) => { const headerText = 'Find the best route'; @@ -70,8 +105,9 @@ test.describe('Jumper full e2e flow', () => { await expect(page.getByRole('menu')).not.toBeVisible(); }); - test('Should be able to navigate to profile and open Explore Fluid Mission', async ({ - page,context + test.skip('Should be able to navigate to profile and open Explore Fluid Mission', async ({ + page, + context, }) => { let profileUrl = `${await page.url()}profile/`; // await closeWelcomeScreen(page); @@ -83,8 +119,7 @@ test.describe('Jumper full e2e flow', () => { await page .locator('xpath=//p[normalize-space(text())="Explore Fluid"]') .click(); - const newPage = await context.waitForEvent('page'); - expect(newPage.url()).toBe(values.exploreFluidURL); + expect(page.url()).toBe(values.exploreFluidURL); }); test('Should be able to navigate to jumper learn', async ({ page }) => { @@ -115,6 +150,22 @@ test.describe('Jumper full e2e flow', () => { await expect(page).toHaveURL(values.localSuperfestURL); }); + test('Should be able to open quests mission page and switch background color', async ({ + page, + }) => { + const jumperProfileBackButton = await page.locator( + 'xpath=//p[normalize-space(text())="JUMPER PROFILE"]', + ); + await page.goto(values.aerodromeQuestsURL); + expect(jumperProfileBackButton).toBeVisible(); + await openMainMenu(page); + await page.locator('xpath=//*[@id="tab-key-1"]').click(); //switch to Dark theme + expectBackgroundColorToHaveCss(page, 'rgb(18, 15, 41)'); + await page.locator('xpath=//*[@id="tab-key-0"]').click(); //switch to Light theme + await openMainMenu(page); + expectBackgroundColorToHaveCss(page, 'rgb(243, 235, 255)'); + }); + test('Should be able to navigate to X', async ({ page, context }) => { // await closeWelcomeScreen(page); await openMainMenu(page); diff --git a/tests/testData/commonFunctions.ts b/tests/testData/commonFunctions.ts index 522e99d80..55028861d 100644 --- a/tests/testData/commonFunctions.ts +++ b/tests/testData/commonFunctions.ts @@ -1,4 +1,5 @@ -import { expect , Page } from '@playwright/test'; +import type { Page } from '@playwright/test'; +import { expect } from '@playwright/test'; export async function findTheBestRoute(page) { await page.getByRole('heading', { name: 'Find the best route' }); @@ -19,6 +20,21 @@ export async function tabInHeader(page, name: string) { await page.getByRole('tab', { name }).click(); } -export async function expectMenuToBeVisible(page){ +export async function expectMenuToBeVisible(page) { await expect(page.getByRole('menu')).toBeVisible(); -} \ No newline at end of file +} +export async function expectBackgroundColorToHaveCss(page, rgb: string) { + const backgroundColor = await page.locator('xpath=/html/body/div[1]'); + expect(backgroundColor).toHaveCSS(`background-color`, rgb); +} +export async function itemInSettingsMenu(page, selector: string) { + await page + .locator(`xpath=//p[normalize-space(text())="${selector}"]`) + .click(); +} +export async function itemInSettingsMenuToBeVisible(page, selector: string) { + const itemName = await page.locator( + `xpath=//button[normalize-space(text())="${selector}"]`, + ); + expect(itemName).toBeVisible(); +} diff --git a/tests/testData/values.json b/tests/testData/values.json index 8419d000c..69f696d1a 100644 --- a/tests/testData/values.json +++ b/tests/testData/values.json @@ -1,7 +1,8 @@ { - "localJumperScanURL":"http://localhost:3000/scan/", - "localSuperfestURL":"http://localhost:3000/superfest/", - "xUrl": "https://x.com/JumperExchange", - "discordURL": "https://discord.com/invite/jumperexchange", - "exploreFluidURL":"https://jumper.exchange/superfest/rewards-from-fluid-on-base/" -} \ No newline at end of file + "localJumperScanURL": "http://localhost:3000/scan/", + "localSuperfestURL": "http://localhost:3000/superfest/", + "xUrl": "https://x.com/JumperExchange", + "discordURL": "https://discord.com/invite/jumperexchange", + "exploreFluidURL": "https://jumper.exchange/superfest/rewards-from-fluid-on-base/", + "aerodromeQuestsURL": "http://localhost:3000/quests/rewards-from-aerodrome-on-base/" +}