import React, { useState, useEffect, useRef } from 'react'; import { View, Text } from 'react-native'; import { usePaywallContext } from '../../context/PaywallContext'; import { applyStyles, parseColor, parseSize } from '../../utils/styles'; import { NamiEventEmitter, PAYWALL_ACTION_EVENT, NamiPaywallAction } from '@namiml/sdk-core'; import type { TCountdownTimerTextComponent } from '@namiml/sdk-core'; interface Props { component: TCountdownTimerTextComponent; scaleFactor: number; parentDirection?: string; } export const NamiCountdownTimer: React.FC = ({ component, scaleFactor, parentDirection }) => { const ctx = usePaywallContext(); const timerId = component.id ?? 'default'; const mode = component.mode ?? 'duration'; const computeInitialSeconds = (): number => { const saved = ctx.getTimerState(timerId); if (saved) return Math.max(0, saved.remainingSeconds); if (mode === 'targetDateTime' && component.targetDateTime) { const target = new Date(component.targetDateTime).getTime(); const diff = Math.floor((target - Date.now()) / 1000); return Math.max(0, diff); } return component.durationSeconds ?? 60; }; const [remaining, setRemaining] = useState(computeInitialSeconds); const intervalRef = useRef | null>(null); useEffect(() => { if (remaining <= 0) { if (intervalRef.current) clearInterval(intervalRef.current); const saved = ctx.getTimerState(timerId); if (!saved?.hasEmittedCompletion) { const eventData = ctx.getPaywallActionEventData(); NamiEventEmitter.getInstance().emit(PAYWALL_ACTION_EVENT, { ...eventData, action: NamiPaywallAction.COUNTDOWN_TIMER_COMPLETED, }); ctx.setTimerState(timerId, 0, Date.now(), true); } return; } intervalRef.current = setInterval(() => { setRemaining(prev => { const next = prev - 1; ctx.setTimerState(timerId, next, Date.now(), false); return next; }); }, 1000); return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; }, [remaining <= 0]); // Handle showWhenCompleted if (remaining <= 0 && component.showWhenCompleted === 'hidden') return null; const containerStyle = applyStyles(component, scaleFactor, false, parentDirection); const units = component.units ?? { hours: true, minutes: true, seconds: true }; const labels = component.labels; const zeroPad = component.zeroPad !== false; const separator = component.separator ?? ':'; const isHorizontal = (component.direction ?? 'horizontal') === 'horizontal'; const labelPosition = component.labelPosition ?? 'none'; const hrs = Math.floor(remaining / 3600); const mins = Math.floor((remaining % 3600) / 60); const secs = remaining % 60; const parts: { value: number; label?: string }[] = []; if (units.days) { const days = Math.floor(remaining / 86400); parts.push({ value: days, label: labels?.days }); } if (units.hours) parts.push({ value: hrs, label: labels?.hours }); if (units.minutes) parts.push({ value: mins, label: labels?.minutes }); if (units.seconds) parts.push({ value: secs, label: labels?.seconds }); const valueFontStyle = { fontSize: parseSize(component.fontSize, scaleFactor) ?? 24, color: parseColor(component.fontColor) ?? '#000', fontFamily: component.fontName, includeFontPadding: false, }; const labelFontStyle = { fontSize: parseSize(component.labelFontSize, scaleFactor) ?? 12, color: parseColor(component.labelFontColor) ?? '#666', fontFamily: component.labelFontName, marginTop: labelPosition === 'bottom' || labelPosition === 'below' ? 2 : 0, marginBottom: labelPosition === 'top' || labelPosition === 'above' ? 2 : 0, includeFontPadding: false, }; return ( {parts.map((part, i) => ( {i > 0 && isHorizontal && ( {separator} )} {labelPosition === 'top' || labelPosition === 'above' ? ( {part.label} ) : null} {zeroPad ? String(part.value).padStart(2, '0') : String(part.value)} {labelPosition === 'bottom' || labelPosition === 'below' ? ( {part.label} ) : null} ))} ); };