From f644b2366a95bb46ce27d57bda3df80516a53678 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Thu, 2 Nov 2023 15:12:59 -0400 Subject: [PATCH] points hook --- src/graphql/queries/metadata.graphql | 29 ++ src/languages/en_US.json | 10 +- src/resources/points.ts | 33 ++ src/screens/PointsScreen.tsx | 742 ++++++++++++++++----------- 4 files changed, 515 insertions(+), 299 deletions(-) create mode 100644 src/resources/points.ts diff --git a/src/graphql/queries/metadata.graphql b/src/graphql/queries/metadata.graphql index 3bfdc661b0c..069e4c697d5 100644 --- a/src/graphql/queries/metadata.graphql +++ b/src/graphql/queries/metadata.graphql @@ -118,3 +118,32 @@ query getdApp($shortName: String!, $url: String!, $status: Boolean!) { shortName } } + +query getPointsDataForWallet($address: String!) { + points(address: $address) { + meta { + distribution { + next + } + status + } + leaderboard { + accounts { + address + earnings { + total + } + ens + avatarURL + } + } + earnings { + total + } + stats { + position { + current + } + } + } +} diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 3c483a519f6..21ac5f96535 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -1240,7 +1240,15 @@ "longest_yet": "Longest yet", "day": "day", "days": "days", - "leaderboard": "Leaderboard" + "leaderboard": "Leaderboard", + "sunday": "Sunday", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "error": "Failed to fetch data" }, "pools": { "deposit": "Deposit", diff --git a/src/resources/points.ts b/src/resources/points.ts new file mode 100644 index 00000000000..36b0df70b36 --- /dev/null +++ b/src/resources/points.ts @@ -0,0 +1,33 @@ +import { POINTS, useExperimentalFlag } from '@/config'; +import { metadataClient } from '@/graphql'; +import { GetPointsDataForWalletQuery } from '@/graphql/__generated__/metadata'; +import config from '@/model/config'; +import { createQueryKey } from '@/react-query'; +import { useQuery } from '@tanstack/react-query'; + +export function pointsQueryKey({ address }: { address: string }) { + return createQueryKey('points', { address }, { persisterVersion: 1 }); +} + +export function usePoints({ walletAddress }: { walletAddress: string }) { + const pointsEnabled = + (useExperimentalFlag(POINTS) || config.points_fully_enabled) && + config.points_enabled; + const queryKey = pointsQueryKey({ + address: walletAddress, + }); + + const query = useQuery( + queryKey, + async () => + await metadataClient.getPointsDataForWallet({ + address: walletAddress, + }), + { + enabled: pointsEnabled && !!walletAddress, + cacheTime: Infinity, + } + ); + + return query; +} diff --git a/src/screens/PointsScreen.tsx b/src/screens/PointsScreen.tsx index 3f4bfc471f2..73aacbebddd 100644 --- a/src/screens/PointsScreen.tsx +++ b/src/screens/PointsScreen.tsx @@ -1,4 +1,3 @@ -import lang from 'i18n-js'; import React from 'react'; import { Image, View } from 'react-native'; import Animated, { @@ -23,8 +22,6 @@ import { useNavigation } from '@/navigation'; import { Bleed, Box, - Cover, - DebugLayout, Inline, Separator, Stack, @@ -32,12 +29,7 @@ import { globalColors, useForegroundColor, } from '@/design-system'; -import { - useAccountAccentColor, - useAccountProfile, - useClipboard, - useDimensions, -} from '@/hooks'; +import { useAccountProfile, useClipboard, useDimensions } from '@/hooks'; import { useTheme } from '@/theme'; import { POINTS, useExperimentalFlag } from '@/config'; import config from '@/model/config'; @@ -45,7 +37,6 @@ import { ScrollView } from 'react-native-gesture-handler'; import MaskedView from '@react-native-masked-view/masked-view'; import BlurredRainbow from '@/assets/blurredRainbow.png'; import Planet from '@/assets/planet.png'; -import { BlurView } from '@react-native-community/blur'; import LinearGradient from 'react-native-linear-gradient'; import { IS_IOS, IS_TEST } from '@/env'; import { haptics, safeAreaInsetValues } from '@/utils'; @@ -57,6 +48,20 @@ import { addressCopiedToastAtom } from '@/recoil/addressCopiedToastAtom'; import { useRecoilState } from 'recoil'; import { Toast, ToastPositionContainer } from '@/components/toasts'; import * as i18n from '@/languages'; +import { pointsQueryKey, usePoints } from '@/resources/points'; +import { maybeSignUri } from '@/handlers/imgix'; +import { isNil } from 'lodash'; +import { getFormattedTimeQuantity } from '@/helpers/utilities'; +import { address as formatAddress } from '@/utils/abbreviations'; +import ActivityIndicator from '@/components/ActivityIndicator'; +import Spinner from '@/components/Spinner'; +import { queryClient } from '@/react-query'; + +const STREAKS_ENABLED = false; +const REFERRALS_ENABLED = false; +const ONE_WEEK_MS = 604_800_000; + +const LoadingSpinner = IS_IOS ? ActivityIndicator : Spinner; const fallConfig = { duration: 2000, @@ -73,8 +78,49 @@ const flyUpConfig = { const infoCircleColor = 'rgba(245, 248, 255, 0.25)'; -const LeaderboardRow = () => { - const rank: number = 1; +const displayNextDistribution = (seconds: number) => { + const days = [ + i18n.t(i18n.l.points.sunday), + i18n.t(i18n.l.points.monday), + i18n.t(i18n.l.points.tuesday), + i18n.t(i18n.l.points.wednesday), + i18n.t(i18n.l.points.thursday), + i18n.t(i18n.l.points.friday), + i18n.t(i18n.l.points.saturday), + ]; + + const ms = seconds * 1000; + const date = new Date(ms); + let hours = date.getHours(); + const ampm = hours >= 12 ? 'pm' : 'am'; + hours = hours % 12; + hours = hours ? hours : 12; // the hour '0' should be '12' + + if (ms - Date.now() > ONE_WEEK_MS) { + return `${hours}${ampm} ${date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + })}`; + } else { + const dayOfWeek = days[date.getDay()]; + + return `${hours}${ampm} ${dayOfWeek}`; + } +}; + +const LeaderboardRow = ({ + address, + ens, + avatarURL, + points, + rank, +}: { + address: string; + ens?: string; + avatarURL?: string; + points: number; + rank: number; +}) => { let gradient; let icon; switch (rank) { @@ -116,7 +162,7 @@ const LeaderboardRow = () => { break; } - const address = '0x4cefdsfdsfa8a8a8a8'; + const formattedPoints = points.toLocaleString('en-US'); return ( { alignItems="center" > - - - + ) : ( + - {`${address.slice(0, 5)}…${address.slice( - address.length - 4, - address.length - )}`} - - - - 􀙬 + + 􀉪 - - {`40 ${i18n.t(i18n.l.points.days)}`} + + )} + + + + {ens ? ens : formatAddress(address, 4, 5)} - + + {STREAKS_ENABLED && ( + + + 􀙬 + + + {`40 ${i18n.t(i18n.l.points.days)}`} + + + )} @@ -160,13 +229,13 @@ const LeaderboardRow = () => { - 102,687 + {formattedPoints} } > { ) : ( - 102,687 + {formattedPoints} )} + {/* eslint-disable-next-line no-nested-ternary */} {pointsFullyEnabled ? ( - - - - {/* */} - - - 10,428 - - } - > - + + + - - - - - {/* */} - - - - {}} - title={i18n.t(i18n.l.points.next_reward)} - mainText="20h 19m" - icon="􀉉" - subtitle="12pm Monday" - accentColor={labelSecondary} - /> - {}} - title={i18n.t(i18n.l.points.streak)} - mainText={`36 ${i18n.t(i18n.l.points.days)}`} - icon="􀙬" - subtitle={i18n.t(i18n.l.points.longest_yet)} - accentColor={pink} - /> - {}} - title={i18n.t(i18n.l.points.referrals)} - mainText="12" - icon="􀇯" - subtitle="8,200" - accentColor={yellow} - /> - - - - - - - - {i18n.t(i18n.l.points.referral_link)} + maskElement={ + + {data?.points?.earnings?.total.toLocaleString('en-US')} - - 􀅵 - - - - - + - - 􀈂 + /> + + + + + + + + {!isNil(nextDistributionSeconds) && ( + {}} + title={i18n.t(i18n.l.points.next_reward)} + mainText={getFormattedTimeQuantity( + nextDistributionSeconds + )} + icon="􀉉" + subtitle={displayNextDistribution( + nextDistributionSeconds + )} + accentColor={labelSecondary} + /> + )} + {STREAKS_ENABLED && ( + {}} + title={i18n.t(i18n.l.points.streak)} + mainText={`36 ${i18n.t(i18n.l.points.days)}`} + icon="􀙬" + subtitle={i18n.t(i18n.l.points.longest_yet)} + accentColor={pink} + /> + )} + {REFERRALS_ENABLED && ( + {}} + title={i18n.t(i18n.l.points.referrals)} + mainText="12" + icon="􀇯" + subtitle="8,200" + accentColor={yellow} + /> + )} + + + {REFERRALS_ENABLED && ( + + + + + + {i18n.t(i18n.l.points.referral_link)} - {i18n.t(i18n.l.points.share)} + 􀅵 - - } - > - - + + + - - - - - {/* @ts-ignore */} - - {({ onNewEmoji }: { onNewEmoji: () => void }) => ( - onPressCopy(onNewEmoji)}> - + + 􀈂 + + + {i18n.t(i18n.l.points.share)} + + + } + > + + + + + + + {/* @ts-ignore */} + + {({ onNewEmoji }: { onNewEmoji: () => void }) => ( + onPressCopy(onNewEmoji)} + > + + + 􀉣 + + + + rainbow.me/points?ref=0x2e6786983232jkl + + + + + )} + + + )} + + + + {i18n.t(i18n.l.points.leaderboard)} + + {data?.points?.stats?.position.current && ( + + - - 􀉣 - - - + - rainbow.me/points?ref=0x2e6786983232jkl - - - - + + + {accountENS + ? accountENS + : formatAddress(accountAddress, 4, 5)} + + + + {`#${data?.points?.stats?.position.current.toLocaleString( + 'en-US' + )}`} + + + + + )} - - - - - - {i18n.t(i18n.l.points.leaderboard)} - - - - + } > - - - skillet.eth - - - #20 - - - - - - - - } - > - - - - - - - - - - - + {data?.points?.leaderboard?.accounts?.map( + (account, index) => ( + + ) + )} + + + - - + + ) : ( + + {!isFetching ? ( + + + {i18n.t(i18n.l.points.error)} + + { + // only allow refresh if data is at least 30 seconds old + if (!dataUpdatedAt || Date.now() - dataUpdatedAt > 30_000) { + queryClient.invalidateQueries({ + queryKey: pointsQueryKey({ + address: accountAddress, + }), + }); + } + }} + > + + 􀅈 + + + + ) : ( + + )} + + ) ) : ( <>