diff --git a/apps/wallet/src/features/Loyalty/ActionCard/index.tsx b/apps/wallet/src/features/Loyalty/ActionCard/index.tsx index 662ef2c00..268348d3b 100644 --- a/apps/wallet/src/features/Loyalty/ActionCard/index.tsx +++ b/apps/wallet/src/features/Loyalty/ActionCard/index.tsx @@ -31,6 +31,7 @@ import { useSnapshot } from 'utils/hooks'; import PointTag from '../components/PointTag'; import { + checkIfUserCanDoAction, extractDataFromMetadata, getActionLogo, getCycleEndTime, @@ -46,21 +47,25 @@ import VerificationNeededTag from './VerificationNeededTag'; interface Props { style?: ViewStyle; action: Action; - canUserPerformAction: boolean; } -const ActionCard: FC = ({ style, action, canUserPerformAction }) => { - const { userProgress } = useSnapshot(loyaltyState); +const ActionCard: FC = ({ style, action }) => { + const { userProgress, typeActionMap } = useSnapshot(loyaltyState); const { name, desc, icon, ctaText, ctaType, cta } = useMemo(() => { return extractDataFromMetadata(action.metadata as ActionMetadata[]); }, [action]); const [isDoingAction, setIsDoingAction] = useState(false); + const canUserDoAction = checkIfUserCanDoAction( + userProgress as UserProgress, + action, + ); + const initialTimeRemaining = useMemo(() => { if ( !userProgress || - canUserPerformAction || + canUserDoAction || action.category !== ActionCategory.Recurring || !action.cycleInHours ) { @@ -82,6 +87,32 @@ const ActionCard: FC = ({ style, action, canUserPerformAction }) => { return cycleEndTime.getTime() - Date.now(); }, [userProgress]); + const isRecorded = useMemo(() => { + if (!userProgress?.actionRecords) return false; + + const relatedRecurringAction = typeActionMap + .get(action.type!) + ?.find((a) => a.category === ActionCategory.Recurring); + + if (relatedRecurringAction?.cycleInHours) { + const lastRecord = userProgress.actionRecords.findLast( + (record) => record!.actionId === relatedRecurringAction.id, + ); + if (!lastRecord) { + return false; + } + + const cycleEndTime = getCycleEndTime( + new Date(lastRecord.timestamp), + relatedRecurringAction.cycleInHours, + ); + + return new Date() < cycleEndTime; + } + + return false; + }, [action, userProgress, typeActionMap]); + const handleDoAction = async () => { if (ctaType === 'internal') { navigateInternalByCta(cta); @@ -169,7 +200,7 @@ const ActionCard: FC = ({ style, action, canUserPerformAction }) => { }, [stat, action.streak]); const isPassthrough = - !canUserPerformAction && action.category !== ActionCategory.Streak; + !canUserDoAction && action.category !== ActionCategory.Streak; return ( @@ -224,14 +255,14 @@ const ActionCard: FC = ({ style, action, canUserPerformAction }) => { )} - {canUserPerformAction && + {canUserDoAction && action.category !== ActionCategory.Milestone && action.category !== ActionCategory.Streak && (isDoingAction ? ( @@ -309,6 +340,10 @@ const styles = StyleSheet.create({ paddingVertical: 8, paddingHorizontal: 20, }, + streakBarContainer: { + marginLeft: 8, + flexGrow: 1, + }, }); export default ActionCard; diff --git a/apps/wallet/src/features/Loyalty/PartnerTab/PartnerQuest.tsx b/apps/wallet/src/features/Loyalty/PartnerTab/PartnerQuest.tsx index dd5865f49..eb6ece12e 100644 --- a/apps/wallet/src/features/Loyalty/PartnerTab/PartnerQuest.tsx +++ b/apps/wallet/src/features/Loyalty/PartnerTab/PartnerQuest.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react'; import type { ViewStyle } from 'react-native'; import { ScrollView, StyleSheet } from 'react-native'; -import type { Action, UserProgress } from '@walless/graphql'; +import type { Action } from '@walless/graphql'; import type { ModalConfigs } from '@walless/gui'; import { AnimateDirections, @@ -10,13 +10,10 @@ import { SwipeDownGesture, } from '@walless/gui'; import { ModalId } from 'modals/types'; -import { loyaltyState } from 'state/loyalty'; import { useSafeAreaInsets } from 'utils/hooks'; -import { useSnapshot } from 'valtio'; import ActionCard from '../ActionCard'; import ModalHeader from '../components/ModalHeader'; -import { canUserPerformAction } from '../internal'; interface PartnerQuestProps { partner: string; @@ -28,7 +25,6 @@ type Props = PartnerQuestProps & { }; const PartnerQuestModal: FC = ({ config, partner, actions }) => { - const { userProgress } = useSnapshot(loyaltyState); const safeAreaInsets = useSafeAreaInsets(); const safeAreaStyle: ViewStyle = { @@ -55,10 +51,6 @@ const PartnerQuestModal: FC = ({ config, partner, actions }) => { { - const { userProgress, wallessActions } = useSnapshot(loyaltyState); + const { wallessActions } = useSnapshot(loyaltyState); return ( @@ -21,14 +20,7 @@ const WallessTab = () => { )} {wallessActions.map((action) => ( - + ))} ); diff --git a/apps/wallet/src/features/Loyalty/index.tsx b/apps/wallet/src/features/Loyalty/index.tsx index 418fa1f3e..239bea7b2 100644 --- a/apps/wallet/src/features/Loyalty/index.tsx +++ b/apps/wallet/src/features/Loyalty/index.tsx @@ -67,12 +67,21 @@ const LoyaltyFeature = () => { const wallessActions: Action[] = []; const partnerActionMap: Map = new Map(); + const typeActionMap: Map = new Map(); sortedActions.forEach((action) => { + if (!action.type) return; + const extractedMetadata = extractDataFromMetadata( action.metadata as ActionMetadata[], ); + if (typeActionMap.has(action.type)) { + typeActionMap.get(action.type)!.push(action); + } else { + typeActionMap.set(action.type, [action]); + } + if (extractedMetadata.partner === '') { wallessActions.push(action); } else if (partnerActionMap.has(extractedMetadata.partner)) { @@ -84,6 +93,7 @@ const LoyaltyFeature = () => { loyaltyActions.setWallessActions(wallessActions); loyaltyActions.setPartnerActionMap(partnerActionMap); + loyaltyActions.setTypeActionMap(typeActionMap); } catch (err) { console.error(err); } diff --git a/apps/wallet/src/features/Loyalty/internal.tsx b/apps/wallet/src/features/Loyalty/internal.tsx index ed4a15eb6..83e8ef437 100644 --- a/apps/wallet/src/features/Loyalty/internal.tsx +++ b/apps/wallet/src/features/Loyalty/internal.tsx @@ -186,7 +186,7 @@ export const formatCountdownTime = (timeRemaining: number) => { return `${hours}h:${minutes}m:${seconds}s`; }; -export const canUserPerformAction = ( +export const checkIfUserCanDoAction = ( userProgress: UserProgress, action: Action, ) => { diff --git a/apps/wallet/src/state/loyalty/index.ts b/apps/wallet/src/state/loyalty/index.ts index 3b17146b0..3b5ded2ab 100644 --- a/apps/wallet/src/state/loyalty/index.ts +++ b/apps/wallet/src/state/loyalty/index.ts @@ -12,6 +12,9 @@ export const loyaltyActions = { setPartnerActionMap: (map: Map) => { loyaltyState.partnerActionMap = map; }, + setTypeActionMap: (map: Map) => { + loyaltyState.typeActionMap = map; + }, }; export * from './internal'; diff --git a/apps/wallet/src/state/loyalty/internal.ts b/apps/wallet/src/state/loyalty/internal.ts index 14c5d4a0e..1804be2c9 100644 --- a/apps/wallet/src/state/loyalty/internal.ts +++ b/apps/wallet/src/state/loyalty/internal.ts @@ -5,9 +5,11 @@ export interface LoyaltyState { userProgress?: UserProgress; wallessActions: Action[]; partnerActionMap: Map; + typeActionMap: Map; } export const loyaltyState = proxy({ wallessActions: [], partnerActionMap: new Map(), + typeActionMap: new Map(), });