From 7298c6e63822a629069ed94bc39a38cbd24358fe Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 23 Dec 2024 18:04:01 +0200 Subject: [PATCH] TW-1599: Rewards page (#1239) * TW-1599 Add layout for 'Rewards' page * TW-1599 Implement fetching rewards * TW-1599 Remove an unused property * TW-1599 Refactoring according to comments * TW-1599 Implement star animation as a webm video * TW-1599 Fix star animation for Chrome --- public/_locales/en/messages.json | 39 +++++ src/app/PageRouter.tsx | 2 + .../atoms/DonationBanner/DonationBanner.tsx | 28 ---- src/app/atoms/DonationBanner/selectors.ts | 3 - .../hooks/use-partners-promotion-settings.tsx | 47 ++++++ src/app/hooks/use-referral-links-settings.tsx | 67 +++++++++ src/app/icons/triangle-down.svg | 4 + src/app/layouts/PageLayout.selectors.ts | 3 +- src/app/layouts/PageLayout.tsx | 25 +--- .../RewardsButton/firefox-star-animation.tsx | 39 +++++ .../PageLayout/RewardsButton/index.tsx | 34 +++++ .../RewardsButton/star-animation.tsx | 65 +++++++++ .../RewardsButton/star_animation.webm | Bin 0 -> 224818 bytes .../RewardsButton/star_animation_poster.gif | Bin 0 -> 216 bytes .../RewardsButton/star_animation_small.gif | Bin 0 -> 6885 bytes src/app/pages/Home/Home.tsx | 1 - src/app/pages/Rewards/achievements/index.tsx | 24 +++ src/app/pages/Rewards/active-features/ads.svg | 3 + .../Rewards/active-features/disabled.svg | 3 + .../pages/Rewards/active-features/enabled.svg | 3 + .../Rewards/active-features/feature-item.tsx | 76 ++++++++++ .../pages/Rewards/active-features/icon_bg.svg | 3 + .../pages/Rewards/active-features/index.tsx | 41 ++++++ .../Rewards/active-features/referrals.svg | 3 + src/app/pages/Rewards/index.tsx | 92 ++++++++++++ .../pages/Rewards/lifetime-earnings/index.tsx | 138 ++++++++++++++++++ .../pages/Rewards/recent-earnings/index.tsx | 54 +++++++ .../recent-earnings.module.css | 9 ++ .../Rewards/recent-earnings/stats-card.tsx | 70 +++++++++ src/app/pages/Rewards/section.tsx | 32 ++++ src/app/pages/Rewards/selectors.ts | 8 + src/app/pages/Rewards/tooltip.tsx | 33 +++++ src/app/pages/Rewards/utils.ts | 7 + src/app/store/rewards/actions.ts | 22 +++ src/app/store/rewards/epics.ts | 44 ++++++ src/app/store/rewards/reducers.ts | 35 +++++ src/app/store/rewards/selectors.ts | 47 ++++++ src/app/store/rewards/state.mock.ts | 7 + src/app/store/rewards/state.ts | 14 ++ src/app/store/root-state.epics.ts | 4 +- src/app/store/root-state.mock.ts | 4 +- src/app/store/root-state.reducer.ts | 4 +- .../partners-promotion-settings.tsx | 49 +------ .../referral-links-settings.tsx | 68 +-------- src/lib/apis/ads-api.ts | 58 ++++++-- src/lib/apis/utils.ts | 9 ++ src/lib/icons/assets/bell-read.svg | 16 ++ src/lib/icons/assets/bell-unread.svg | 20 +++ src/lib/icons/index.ts | 2 + src/lib/notifications/components/bell.tsx | 20 +-- src/lib/utils/numbers.ts | 2 + tailwind.config.js | 4 + 52 files changed, 1199 insertions(+), 186 deletions(-) delete mode 100644 src/app/atoms/DonationBanner/DonationBanner.tsx delete mode 100644 src/app/atoms/DonationBanner/selectors.ts create mode 100644 src/app/hooks/use-partners-promotion-settings.tsx create mode 100644 src/app/hooks/use-referral-links-settings.tsx create mode 100644 src/app/icons/triangle-down.svg create mode 100644 src/app/layouts/PageLayout/RewardsButton/firefox-star-animation.tsx create mode 100644 src/app/layouts/PageLayout/RewardsButton/index.tsx create mode 100644 src/app/layouts/PageLayout/RewardsButton/star-animation.tsx create mode 100644 src/app/layouts/PageLayout/RewardsButton/star_animation.webm create mode 100644 src/app/layouts/PageLayout/RewardsButton/star_animation_poster.gif create mode 100644 src/app/layouts/PageLayout/RewardsButton/star_animation_small.gif create mode 100644 src/app/pages/Rewards/achievements/index.tsx create mode 100644 src/app/pages/Rewards/active-features/ads.svg create mode 100644 src/app/pages/Rewards/active-features/disabled.svg create mode 100644 src/app/pages/Rewards/active-features/enabled.svg create mode 100644 src/app/pages/Rewards/active-features/feature-item.tsx create mode 100644 src/app/pages/Rewards/active-features/icon_bg.svg create mode 100644 src/app/pages/Rewards/active-features/index.tsx create mode 100644 src/app/pages/Rewards/active-features/referrals.svg create mode 100644 src/app/pages/Rewards/index.tsx create mode 100644 src/app/pages/Rewards/lifetime-earnings/index.tsx create mode 100644 src/app/pages/Rewards/recent-earnings/index.tsx create mode 100644 src/app/pages/Rewards/recent-earnings/recent-earnings.module.css create mode 100644 src/app/pages/Rewards/recent-earnings/stats-card.tsx create mode 100644 src/app/pages/Rewards/section.tsx create mode 100644 src/app/pages/Rewards/selectors.ts create mode 100644 src/app/pages/Rewards/tooltip.tsx create mode 100644 src/app/pages/Rewards/utils.ts create mode 100644 src/app/store/rewards/actions.ts create mode 100644 src/app/store/rewards/epics.ts create mode 100644 src/app/store/rewards/reducers.ts create mode 100644 src/app/store/rewards/selectors.ts create mode 100644 src/app/store/rewards/state.mock.ts create mode 100644 src/app/store/rewards/state.ts create mode 100644 src/lib/apis/utils.ts create mode 100644 src/lib/icons/assets/bell-read.svg create mode 100644 src/lib/icons/assets/bell-unread.svg diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 222063a918..fea6aff841 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -3168,5 +3168,44 @@ }, "dropdownNoItems": { "message": "No items" + }, + "earnings": { + "message": "Earnings" + }, + "earningsInfoTooltip": { + "message": "Earnings are only an estimation of your daily and monthly activity, including ad views and interactions with referral links. At the end of each month, your RP (reward points) will be converted into TKEY and distributed to your Tezos address. The more RP you accumulate, the greater your rewards will be." + }, + "ads": { + "message": "Ads" + }, + "today": { + "message": "Today" + }, + "activeFeatures": { + "message": "Active features" + }, + "advertisingFeatureDescription": { + "message": "Get RP by viewing ads" + }, + "advertisingFeatureTooltip": { + "message": "The advertising feature is used to display ads within Temple Wallet and on websites. To earn TKEY and enable ads, we request your permission to share your wallet address and IP, this information is used solely to serve you relevant ads and distribute rewards." + }, + "referralLinksFeatureDescription": { + "message": "Get RP while use favorite websites" + }, + "referralLinksFeatureTooltip": { + "message": "The referral links feature allows users to engage with affiliate offers from our partners. These offers will appear as links while you browse your favorite websites. To enable this feature, we request your permission to share your wallet address and IP address, this information is used solely to serve you relevant offers and distribute rewards." + }, + "achievements": { + "message": "Achievements" + }, + "lifetimeEarnings": { + "message": "Lifetime earnings" + }, + "noEarningsFound": { + "message": "No earnings found" + }, + "somethingWentWrong": { + "message": "Something went wrong" } } diff --git a/src/app/PageRouter.tsx b/src/app/PageRouter.tsx index 3097171b5d..69b6661f5c 100644 --- a/src/app/PageRouter.tsx +++ b/src/app/PageRouter.tsx @@ -30,6 +30,7 @@ import { Notifications, NotificationsItem } from 'lib/notifications/components'; import { useTempleClient } from 'lib/temple/front'; import * as Woozie from 'lib/woozie'; +import { RewardsPage } from './pages/Rewards'; import { StakingPage } from './pages/Staking'; import { WithDataLoading } from './WithDataLoading'; @@ -98,6 +99,7 @@ const ROUTE_MAP = Woozie.createMap([ ['/attention', onlyReady(onlyInFullPage(() => ))], ['/notifications', onlyReady(() => )], ['/notifications/:id', onlyReady(({ id }) => )], + ['/rewards', onlyReady(() => )], ['*', () => ] ]); diff --git a/src/app/atoms/DonationBanner/DonationBanner.tsx b/src/app/atoms/DonationBanner/DonationBanner.tsx deleted file mode 100644 index a3310101fb..0000000000 --- a/src/app/atoms/DonationBanner/DonationBanner.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { FC } from 'react'; - -import { Anchor } from 'app/atoms/Anchor'; -import { ReactComponent as Ukraine } from 'app/icons/ukraine.svg'; -import { T } from 'lib/i18n'; - -import { DonationBannerSelectors } from './selectors'; - -const DONATE_MAD_FISH_URL = 'https://donate.mad.fish'; - -export const DonationBanner: FC = () => ( - -
-
- - - -
-
- -
-
-
-); diff --git a/src/app/atoms/DonationBanner/selectors.ts b/src/app/atoms/DonationBanner/selectors.ts deleted file mode 100644 index cc6fca5c88..0000000000 --- a/src/app/atoms/DonationBanner/selectors.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum DonationBannerSelectors { - ukraineDonationBanner = 'DonationBanner/Ukraine Donation Banner' -} diff --git a/src/app/hooks/use-partners-promotion-settings.tsx b/src/app/hooks/use-partners-promotion-settings.tsx new file mode 100644 index 0000000000..7ad32e9b02 --- /dev/null +++ b/src/app/hooks/use-partners-promotion-settings.tsx @@ -0,0 +1,47 @@ +import { ChangeEvent } from 'react'; + +import { useDispatch } from 'react-redux'; + +import { togglePartnersPromotionAction } from 'app/store/partners-promotion/actions'; +import { useShouldShowPartnersPromoSelector } from 'app/store/partners-promotion/selectors'; +import { t } from 'lib/i18n'; +import { useConfirm } from 'lib/ui/dialog'; + +export const usePartnersPromotionSettings = () => { + const dispatch = useDispatch(); + const confirm = useConfirm(); + + const isEnabled = useShouldShowPartnersPromoSelector(); + + const handleHidePromotion = async () => { + const confirmed = await confirm({ + title: t('closePartnersPromotion'), + children: t('closePartnersPromoConfirm'), + comfirmButtonText: t('disable') + }); + + if (confirmed) { + dispatch(togglePartnersPromotionAction(false)); + } + }; + + const handleShowPromotion = async () => { + const confirmed = await confirm({ + title: t('enablePartnersPromotionConfirm'), + children: t('enablePartnersPromotionDescriptionConfirm'), + comfirmButtonText: t('enable') + }); + + if (confirmed) { + dispatch(togglePartnersPromotionAction(true)); + } + }; + + const setEnabled = (toChecked: boolean, event?: ChangeEvent) => { + event?.preventDefault(); + + return toChecked ? handleShowPromotion() : handleHidePromotion(); + }; + + return { isEnabled, setEnabled }; +}; diff --git a/src/app/hooks/use-referral-links-settings.tsx b/src/app/hooks/use-referral-links-settings.tsx new file mode 100644 index 0000000000..309982e46c --- /dev/null +++ b/src/app/hooks/use-referral-links-settings.tsx @@ -0,0 +1,67 @@ +import React, { ChangeEvent, useCallback } from 'react'; + +import { useDispatch } from 'react-redux'; + +import { setAcceptedTermsVersionAction, setReferralLinksEnabledAction } from 'app/store/settings/actions'; +import { useAcceptedTermsVersionSelector, useReferralLinksEnabledSelector } from 'app/store/settings/selectors'; +import { + PRIVACY_POLICY_URL, + RECENT_TERMS_VERSION, + TERMS_OF_USE_URL, + TERMS_WITH_REFERRALS_VERSION +} from 'lib/constants'; +import { t, T } from 'lib/i18n'; +import { useConfirm } from 'lib/ui/dialog'; + +export const useReferralLinksSettings = () => { + const dispatch = useDispatch(); + const enabled = useReferralLinksEnabledSelector(); + const acceptedTermsVersion = useAcceptedTermsVersionSelector(); + const confirm = useConfirm(); + + const setEnabled = useCallback( + async (toChecked: boolean, event?: ChangeEvent) => { + event?.preventDefault(); + + if (toChecked && acceptedTermsVersion < TERMS_WITH_REFERRALS_VERSION) { + const confirmed = await confirm({ + title: , + description: ( + + + , + + + + ]} + /> + ), + comfirmButtonText: t('agreeAndContinue') + }); + + if (!confirmed) { + return; + } + } + + dispatch(setAcceptedTermsVersionAction(RECENT_TERMS_VERSION)); + dispatch(setReferralLinksEnabledAction(toChecked)); + }, + [acceptedTermsVersion, confirm, dispatch] + ); + + return { isEnabled: enabled, setEnabled }; +}; diff --git a/src/app/icons/triangle-down.svg b/src/app/icons/triangle-down.svg new file mode 100644 index 0000000000..cff0a93f49 --- /dev/null +++ b/src/app/icons/triangle-down.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/app/layouts/PageLayout.selectors.ts b/src/app/layouts/PageLayout.selectors.ts index 17e613c8e2..60e409f646 100644 --- a/src/app/layouts/PageLayout.selectors.ts +++ b/src/app/layouts/PageLayout.selectors.ts @@ -1,4 +1,5 @@ export enum PageLayoutSelectors { backButton = 'Page Layout/Back Button', - skipButton = 'Page Layout/Skip Button' + skipButton = 'Page Layout/Skip Button', + rewardsButton = 'Page Layout/Rewards Button' } diff --git a/src/app/layouts/PageLayout.tsx b/src/app/layouts/PageLayout.tsx index 0ecf2594a9..c3e81736d5 100644 --- a/src/app/layouts/PageLayout.tsx +++ b/src/app/layouts/PageLayout.tsx @@ -14,7 +14,6 @@ import clsx from 'clsx'; import DocBg from 'app/a11y/DocBg'; import { Button } from 'app/atoms/Button'; -import { DonationBanner } from 'app/atoms/DonationBanner/DonationBanner'; import Spinner from 'app/atoms/Spinner/Spinner'; import { useAppEnv } from 'app/env'; import ErrorBoundary from 'app/ErrorBoundary'; @@ -35,6 +34,7 @@ import Header from './PageLayout/Header'; import { NewsletterOverlay } from './PageLayout/NewsletterOverlay/NewsletterOverlay'; import { OnRampOverlay } from './PageLayout/OnRampOverlay/OnRampOverlay'; import { ReactivateAdsOverlay } from './PageLayout/ReactivateAdsOverlay'; +import { RewardsButton } from './PageLayout/RewardsButton'; import { ShortcutAccountSwitchOverlay } from './PageLayout/ShortcutAccountSwitchOverlay'; import { PageLayoutSelectors } from './PageLayout.selectors'; @@ -116,7 +116,6 @@ type ToolbarProps = { hasBackAction?: boolean; step?: number; setStep?: (step: number) => void; - adShow?: boolean; skip?: boolean; attention?: boolean; }; @@ -126,15 +125,7 @@ export let ToolbarElement: HTMLDivElement | null = null; /** Defined for reference in code to highlight relation between multiple sticky elements & their sizes */ export const TOOLBAR_IS_STICKY = true; -const Toolbar: FC = ({ - pageTitle, - hasBackAction = true, - step, - setStep, - adShow = false, - skip, - attention -}) => { +const Toolbar: FC = ({ pageTitle, hasBackAction = true, step, setStep, skip, attention }) => { const { historyPosition, pathname } = useLocation(); const { fullPage } = useAppEnv(); const { setOnboardingCompleted } = useOnboardingProgress(); @@ -203,10 +194,8 @@ const Toolbar: FC = ({ return (
-
- {!isBackButtonAvailable && adShow && } - - {isBackButtonAvailable && ( + {isBackButtonAvailable ? ( +
- )} -
+
+ ) : ( + + )} {pageTitle && (

{pageTitle}

diff --git a/src/app/layouts/PageLayout/RewardsButton/firefox-star-animation.tsx b/src/app/layouts/PageLayout/RewardsButton/firefox-star-animation.tsx new file mode 100644 index 0000000000..40a6e403cc --- /dev/null +++ b/src/app/layouts/PageLayout/RewardsButton/firefox-star-animation.tsx @@ -0,0 +1,39 @@ +import React, { memo, useEffect, useRef } from 'react'; + +const starAnimationVideo = require('./star_animation.webm'); +const starAnimationPoster = require('./star_animation_poster.gif'); + +interface Props { + loop: boolean; +} + +export const FirefoxStarAnimation = memo(({ loop }) => { + const videoRef = useRef(null); + const prevLoopRef = useRef(loop); + + useEffect(() => { + if (loop && !prevLoopRef.current) { + videoRef.current!.play(); + } else if (!loop && prevLoopRef.current) { + videoRef.current!.currentTime = 0; + videoRef.current!.pause(); + } + prevLoopRef.current = loop; + }, [loop]); + + return ( +