import React, { useState, useEffect, useMemo, useCallback, FC } from "react"; import { VStack, HStack, Pressable, Box, Spinner, useToast } from "native-base"; import { Linking } from "react-native"; import AsyncStorage from "@react-native-async-storage/async-storage"; import moment from "moment"; import { TransText, TransButton } from "../../core/layout"; import { SAMPLE_TASKS } from "./mockData"; import BasicStyledModal from "../../core/web3/modals/BasicStyledModal"; import { noop } from "lodash"; export interface TaskReward { type: "points" | "tokens" | "badge"; amount?: number; description: string; } export const useClaimerTasks = () => { const [dismissedTasks, setDismissedTasks] = useState<{ [key: string]: number }>({}); const [completedTasks, setCompletedTasks] = useState([]); const [loading, setLoading] = useState(true); const [dismissing, setDismissing] = useState(false); const toast = useToast(); useEffect(() => { const loadData = async () => { try { const [dismissed, completed] = await Promise.all([ AsyncStorage.getItem("dismissed_claimer_tasks"), AsyncStorage.getItem("completed_claimer_tasks") ]); if (dismissed) setDismissedTasks(JSON.parse(dismissed)); if (completed) setCompletedTasks(JSON.parse(completed)); } catch (error) { console.warn("Failed to load task data:", error); toast.show({ title: "Error", description: "Failed to load tasks. Please try again." }); } finally { setLoading(false); } }; void loadData(); }, []); const dismissTask = async (taskId: string, skipToast = false) => { const now = Date.now(); const updated = { ...dismissedTasks, [taskId]: now }; setDismissedTasks(updated); try { await AsyncStorage.setItem("dismissed_claimer_tasks", JSON.stringify(updated)); if (!skipToast) { toast.show({ title: "Task Dismissed", description: "Task has been dismissed." }); } } catch (e) { console.warn("Failed to store dismissed task:", e); toast.show({ title: "Error", description: "Failed to dismiss task. Please try again." }); } }; const dismissAllTasks = async (taskIds: string[], onTaskDismiss?: (taskId: string) => void) => { setDismissing(true); const now = Date.now(); const updated = taskIds.reduce((acc, taskId) => ({ ...acc, [taskId]: now }), dismissedTasks); setDismissedTasks(updated); try { await AsyncStorage.setItem("dismissed_claimer_tasks", JSON.stringify(updated)); taskIds.forEach(taskId => { if (onTaskDismiss) onTaskDismiss(taskId); }); toast.show({ title: "Tasks Dismissed", description: "All tasks have been dismissed." }); } catch (e) { console.warn("Failed to store dismissed tasks:", e); toast.show({ title: "Error", description: "Failed to dismiss tasks. Please try again." }); } finally { setDismissing(false); } }; const completeTask = useCallback(async (taskId: string) => { if (completedTasks.includes(taskId)) { toast.show({ title: "Task Already Completed", description: "This task has already been completed." }); return; } const updated = [...completedTasks, taskId]; setCompletedTasks(updated); try { await AsyncStorage.setItem("completed_claimer_tasks", JSON.stringify(updated)); toast.show({ title: "Success", description: "Task completed successfully!" }); } catch (e) { console.warn("Failed to store completed task:", e); toast.show({ title: "Error", description: "Failed to mark task as completed. Please try again." }); } }, [completedTasks, toast]); const availableTasks = useMemo(() => { const now = moment(); const fourDaysDuration = moment.duration(4, 'days'); return SAMPLE_TASKS.filter(task => { const startTime = moment(task.duration.startDate); const endTime = moment(task.duration.endDate); if (now.isBefore(startTime) || now.isAfter(endTime)) return false; if (completedTasks.includes(task.id)) return false; const dismissed = dismissedTasks[task.id]; if (dismissed) { const dismissedTime = moment(dismissed); if (now.diff(dismissedTime) < fourDaysDuration.asMilliseconds()) return false; } return true; }); }, [dismissedTasks, completedTasks]); const mainTask = availableTasks.find(t => t.priority === "main"); const secondaryTasks = availableTasks.filter(t => t.priority === "secondary"); return { mainTask, secondaryTasks, loading, dismissing, hasActiveTasks: availableTasks.length > 0, dismissTask, dismissAllTasks, completeTask }; }; export interface ClaimerTask { type: "noTasks" | "activeTasks" | "secondaryTasks" | "modalContent" | "learn"; taskId?: string; content?: string; id: string; title: string; description: string; category: "social" | "donation" | "referral" | "engagement"; reward?: TaskReward; duration: { startDate: string; endDate: string }; actionUrl?: string; icon?: string; rewardAmount?: string; rewardColor?: string; priority?: "main" | "secondary"; } interface ManagerTaskProps { type: ClaimerTask["type"]; task?: ClaimerTask; isPending: boolean; customTitle?: string; onClose?: () => void; onPress?: () => void; fontStyles?: any; } const TaskModalContent: React.FC<{ task: ClaimerTask; fontStyles?: any }> = ({ task, fontStyles }) => { const { subHeading, subContent } = fontStyles ?? {}; return ( {/* Icon */} {/* Task Title */} {/* Task Description */} {/* Reward */} {task.rewardAmount && ( )} ); }; export const ManagerTask: FC = ({ onClose = noop, task, isPending, customTitle, onPress, fontStyles, ...props }) => { if (!task) return null; return ( } withOverlay="dark" withCloseButton={true} footer={ onPress && ( ) } /> ); }; interface ClaimerTasksCardProps { tasks?: ClaimerTask[]; onTaskComplete?: (taskId: string) => void; onTaskDismiss?: (taskId: string) => void; fontStyles?: any; ContentComponent?: any; showAsModal?: boolean; } const ManagerModalContent = ({ content }: { content: string }) => ( ); const ManagerContentDismiss = () => ( ); const ManagerSecondaryTasks = () => ( ); const ManagerDoNextTasks = () => ( ); const DefaultContentComponent = { noTasks: ManagerContentDismiss, activeTasks: ManagerDoNextTasks, secondaryTasks: ManagerSecondaryTasks, modalContent: ManagerModalContent }; const TasksCardContent: React.FC<{ mainTask?: ClaimerTask; secondaryTasks: ClaimerTask[]; dismissing: boolean; fontStyles?: any; ContentComponent: any; onTaskSelect: (task: ClaimerTask) => void; onDismissAll: () => void; ManagerModalContent: any; }> = ({ mainTask, secondaryTasks, dismissing, fontStyles, ContentComponent, onTaskSelect, onDismissAll, ManagerModalContent }) => { const { secondaryTasks: ManagerSecondaryTasks } = ContentComponent; return ( {/* Main Task */} {mainTask && ( {/* Icon */} {/* Task Title */} {/* CTA Button */} onTaskSelect(mainTask)} testID="main-task-button" variant="ghost" /> )} {/* Secondary Tasks */} {secondaryTasks.length > 0 && ( {secondaryTasks.map(task => ( onTaskSelect(task)} _pressed={{ bg: "gray.50" }} px={4} py={4} bg="white" borderRadius="xl" borderWidth={1} borderColor="gray.200" testID={`task-${task.id}`} w="100%" > ))} )} {/* Footer Button */} ); }; export const ClaimerTasksCard: React.FC = ({ onTaskComplete, onTaskDismiss, fontStyles, ContentComponent = DefaultContentComponent, showAsModal = true }) => { const { mainTask, secondaryTasks, loading, dismissing, hasActiveTasks, dismissAllTasks, completeTask } = useClaimerTasks(); const [selectedTask, setSelectedTask] = useState(null); const [showMainModal, setShowMainModal] = useState(false); const ManagerModalContent = ContentComponent.modalContent; const ManagerContentDismiss = ContentComponent.noTasks; const ManagerDoNextTasks = ContentComponent.activeTasks; const handleDismissAll = async () => { const taskIds = [...(mainTask ? [mainTask.id] : []), ...secondaryTasks.map(task => task.id)]; if (taskIds.length > 0) { await dismissAllTasks(taskIds, onTaskDismiss); setShowMainModal(false); } }; const openTask = useCallback(async (task: ClaimerTask) => { if (task.actionUrl) { try { await Linking.openURL(task.actionUrl); await completeTask(task.id); if (onTaskComplete) onTaskComplete(task.id); setSelectedTask(null); } catch (error) { console.warn("Failed to open task URL:", error); } } }, [completeTask, onTaskComplete]); const handleTaskSelect = (task: ClaimerTask) => { if (task.id === mainTask?.id) { void openTask(task); } else { setSelectedTask(task); } }; useEffect(() => { if (!loading && hasActiveTasks && showAsModal) { setShowMainModal(true); } }, [loading, hasActiveTasks, showAsModal]); if (loading) { return ( ); } if (!hasActiveTasks) { return ( {/* handle close - maybe navigate away? */}} title="" body={} withOverlay="dark" withCloseButton={true} /> ); } if (!showAsModal) { return ( {/* Individual Task Modal */} {selectedTask && ( setSelectedTask(null)} onPress={() => void openTask(selectedTask)} fontStyles={fontStyles} /> )} ); } return ( <> {/* Main Tasks Modal */} setShowMainModal(false)} title="" body={ } withOverlay="dark" withCloseButton={true} /> {/* Individual Task Modal */} {selectedTask && ( setSelectedTask(null)} onPress={() => void openTask(selectedTask)} fontStyles={fontStyles} /> )} ); }; export { ClaimerTasksCard as ClaimerTasksCompact };