import React, { useEffect, useMemo, useState } from 'react'; import type { CSSProperties } from 'react'; import { IonAvatar, IonButton, IonButtons, IonContent, IonHeader, IonIcon, IonInput, IonItem, IonLabel, IonModal, IonPage, IonTitle, IonToolbar, IonSelect, IonSelectOption, IonList, useIonViewWillEnter, } from '@ionic/react'; import { addOutline, cardOutline, chevronBackOutline, chevronDownOutline, checkmarkCircleOutline, closeCircleOutline, closeOutline, flashOutline, logoApple, logoPaypal, medkitOutline, phonePortraitOutline, timeOutline, } from 'ionicons/icons'; import { useHistory, useLocation } from 'react-router-dom'; import { supabase } from '../supabase'; import { useAuth } from '../contexts/AuthContext'; import momImage from '../assets/mom.jpg'; import dadImage from '../assets/dad.jpg'; import basketIcon from '../assets/basket.png'; import { formatMoney } from '../utils/formatMoney'; import { buildCacheKey, readCache, writeCache } from '../utils/localCache'; import '../styles/support.css'; type ServiceType = 'grocery' | 'medication' | 'airtime' | 'electricity'; type RecipientOption = { id: string; first_name: string; last_name: string; photo_url?: string | null; photo_path?: string | null; }; type MerchantOption = { id: string; name: string; branch_name: string | null; merchant_type: string; }; type LocationState = { recipientId?: string; serviceType?: ServiceType; amount?: number; merchantId?: string | null; network?: string | null; meterNumber?: string | null; startAtAmount?: boolean; parentRoot?: string; }; const services: Array<{ type: ServiceType; label: string; reviewLabel: string; amountHeading: string; amountHint: string; icon?: string; imageSrc?: string; imageAlt?: string; color: string; bg: string; cardBg: string; selectedTint: string; }> = [ { type: 'grocery', label: 'Groceries', reviewLabel: 'Grocery voucher', amountHeading: 'Voucher amount', amountHint: 'You are sending a grocery voucher.', imageSrc: basketIcon, imageAlt: 'Groceries', color: '#6d28d9', bg: 'rgba(109,40,217,0.12)', cardBg: 'rgba(109,40,217,0.03)', selectedTint: 'rgba(109,40,217,0.08)', }, { type: 'medication', label: 'Medication', reviewLabel: 'Medication voucher', amountHeading: 'Voucher amount', amountHint: 'You are sending a medication voucher.', icon: medkitOutline, color: '#16a34a', bg: 'rgba(22,163,74,0.14)', cardBg: 'rgba(22,163,74,0.06)', selectedTint: 'rgba(22,163,74,0.08)', }, { type: 'airtime', label: 'Airtime & Data', reviewLabel: 'Airtime & data', amountHeading: 'Choose airtime & data amount', amountHint: 'You are topping up airtime or data.', icon: phonePortraitOutline, color: '#3b82f6', bg: 'rgba(59,130,246,0.1)', cardBg: 'rgba(59,130,246,0.03)', selectedTint: 'rgba(59,130,246,0.08)', }, { type: 'electricity', label: 'Electricity', reviewLabel: 'Electricity', amountHeading: 'Choose electricity amount', amountHint: 'You are buying an electricity token.', icon: flashOutline, color: '#f59e0b', bg: 'rgba(245,158,11,0.1)', cardBg: 'rgba(245,158,11,0.03)', selectedTint: 'rgba(245,158,11,0.08)', }, ]; const amountPresets = [10, 20, 50, 100]; const networks = ['Econet', 'NetOne', 'Telecel']; const previewUserId = '00000000-0000-0000-0000-000000000000'; const previewRecipients: RecipientOption[] = [ { id: 'preview-mum', first_name: 'Mum', last_name: 'Moyo', photo_url: momImage, }, { id: 'preview-dad', first_name: 'Dad', last_name: 'Moyo', photo_url: dadImage, }, ]; const paymentMethods = [ { value: 'card', label: 'Visa ending 4242', detail: 'Default card • reusable checkout', kind: 'card' as const, }, { value: 'apple_pay', label: 'Apple Pay', detail: 'Wallet checkout', kind: 'apple' as const, }, { value: 'google_pay', label: 'Google Pay', detail: 'Fast wallet checkout', kind: 'google' as const, }, { value: 'paypal', label: 'PayPal', detail: 'Pay with PayPal', kind: 'paypal' as const, }, ] as const; const getRecipientPhoto = (recipient?: RecipientOption | null) => { if (!recipient) return null; const photoPath = recipient.photo_path?.toLowerCase() ?? ''; if (photoPath === 'mom.jpg' || photoPath === 'mum.jpg') return momImage; if (photoPath === 'dad.jpg' || photoPath === 'father.jpg') return dadImage; if (recipient.photo_url) return recipient.photo_url; if (recipient.first_name.toLowerCase() === 'mum') return momImage; if (recipient.first_name.toLowerCase() === 'dad') return dadImage; return null; }; const sanitizeAmount = (raw: string) => { const cleaned = raw.replace(/[^\d.]/g, ''); const parts = cleaned.split('.'); const normalized = parts.length <= 1 ? cleaned : `${parts[0]}.${parts.slice(1).join('')}`; const dotIndex = normalized.indexOf('.'); return dotIndex === -1 ? normalized : normalized.slice(0, dotIndex + 3); }; const SupportFlowPage: React.FC = () => { const history = useHistory(); const location = useLocation(); const { user } = useAuth(); const [step, setStep] = useState(1); const [recipients, setRecipients] = useState([]); const [merchants, setMerchants] = useState([]); const [selectedRecipientId, setSelectedRecipientId] = useState( location.state?.recipientId ?? null ); const [serviceType, setServiceType] = useState( location.state?.serviceType ?? null ); const [merchantId, setMerchantId] = useState(null); const [network, setNetwork] = useState(null); const [meterNumber, setMeterNumber] = useState(''); const [amountDisplay, setAmountDisplay] = useState(''); const [isCustomAmountSelected, setIsCustomAmountSelected] = useState(false); const [platformFee, setPlatformFee] = useState(0); const [totalAmount, setTotalAmount] = useState(0); const [orderId, setOrderId] = useState(null); const [paymentMethod, setPaymentMethod] = useState('card'); const [isPaymentSheetOpen, setIsPaymentSheetOpen] = useState(false); const [loading, setLoading] = useState(!user); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const [isRecipientSheetOpen, setIsRecipientSheetOpen] = useState(false); const parentRoot = location.state?.parentRoot ?? '/home'; const hasPresetRecipient = Boolean(location.state?.recipientId); const hasPresetService = Boolean(location.state?.serviceType); const [paymentResult, setPaymentResult] = useState< 'idle' | 'success' | 'failure' >('idle'); const [paymentResultMessage, setPaymentResultMessage] = useState< string | null >(null); const isShowingPaymentResult = paymentResult !== 'idle'; const [isRecurringEnabled, setIsRecurringEnabled] = useState(false); const [frequencyUnit, setFrequencyUnit] = useState<'weekly' | 'monthly'>( 'weekly' ); const [frequencyInterval, setFrequencyInterval] = useState(1); const requiresSupportDetails = serviceType === 'airtime' || serviceType === 'electricity'; const totalSteps = requiresSupportDetails ? 5 : 4; const amountStep = requiresSupportDetails ? 4 : 3; const reviewStep = requiresSupportDetails ? 5 : 4; const visibleFlowSteps = useMemo(() => { if (!hasPresetService) { return Array.from({ length: totalSteps }, (_, index) => index + 1); } return requiresSupportDetails ? [1, 3, 4, 5] : [1, 3, 4]; }, [hasPresetService, requiresSupportDetails, totalSteps]); const visualStep = Math.max(1, visibleFlowSteps.indexOf(step) + 1); const parsedAmount = useMemo(() => { if (!amountDisplay || amountDisplay === '.') return null; const value = Number(amountDisplay); return Number.isFinite(value) && value > 0 ? value : null; }, [amountDisplay]); const selectedService = useMemo( () => services.find((item) => item.type === serviceType) ?? null, [serviceType] ); const pageBackground = selectedService ? `linear-gradient(180deg, ${selectedService.selectedTint} 0%, #fafafa 240px)` : '#fafafa'; const themeStyle = selectedService ? ({ '--flow-accent': selectedService.color, '--flow-accent-soft': selectedService.selectedTint, '--flow-icon-soft': selectedService.bg, '--flow-card-soft': selectedService.cardBg, } as CSSProperties) : undefined; const flowTitle = selectedService?.label ?? 'Send support'; const isPresetAmountSelected = amountPresets.includes(Number(amountDisplay)); useEffect(() => { void loadInitialData(); }, [user?.id]); useIonViewWillEnter(() => { const presetRecipientId = location.state?.recipientId ?? null; const presetServiceType = location.state?.serviceType ?? null; const presetAmount = location.state?.amount; const presetAmountDisplay = typeof presetAmount === 'number' && Number.isFinite(presetAmount) ? presetAmount.toFixed(2) : ''; const presetStartAtAmount = Boolean(location.state?.startAtAmount); const presetRequiresSupportDetails = presetServiceType === 'airtime' || presetServiceType === 'electricity'; const presetAmountStep = presetRequiresSupportDetails ? 4 : 3; const presetDetailsStep = presetRequiresSupportDetails ? 3 : presetAmountStep; setStep( presetStartAtAmount && presetRecipientId && presetServiceType ? presetAmountStep : presetRecipientId && presetServiceType ? presetDetailsStep : presetRecipientId && !presetServiceType ? 2 : 1 ); setSelectedRecipientId(presetRecipientId); setServiceType(presetServiceType); setMerchantId(location.state?.merchantId ?? null); setNetwork(location.state?.network ?? null); setMeterNumber(location.state?.meterNumber ?? ''); setAmountDisplay(presetAmountDisplay); setIsCustomAmountSelected( Boolean( presetAmountDisplay && !amountPresets.includes(Number(presetAmountDisplay)) ) ); setPlatformFee(0); setTotalAmount(0); setOrderId(null); setPaymentMethod('card'); setIsPaymentSheetOpen(false); setSubmitting(false); setError(null); setPaymentResult('idle'); setPaymentResultMessage(null); setIsRecurringEnabled(false); setFrequencyUnit('weekly'); setFrequencyInterval(1); }); useEffect(() => { if (serviceType === 'grocery' || serviceType === 'medication') { void loadMerchantsForService(serviceType); } else { setMerchantId(null); setMerchants([]); } }, [serviceType]); useEffect(() => { const fee = parsedAmount ? Number((2.99 + parsedAmount * 0.03).toFixed(2)) : 0; setPlatformFee(fee); setTotalAmount(parsedAmount ? Number((parsedAmount + fee).toFixed(2)) : 0); }, [parsedAmount]); useEffect(() => { const loadExistingSchedule = async () => { if (!user || !selectedRecipientId || !serviceType) return; const { data, error: fetchError } = await supabase .from('support_schedules') .select( 'id, frequency_unit, frequency_interval, next_due_at, is_active' ) .eq('recipient_id', selectedRecipientId) .eq('service_type', serviceType) .eq('user_id', user.id) .maybeSingle(); if (fetchError) { showError('Could not load support frequency'); } else if (data) { setIsRecurringEnabled(data.is_active); setFrequencyUnit(data.frequency_unit as 'weekly' | 'monthly'); setFrequencyInterval(data.frequency_interval); } else { setIsRecurringEnabled(false); setFrequencyUnit('weekly'); setFrequencyInterval( serviceType === 'grocery' || serviceType === 'medication' ? 4 : 1 ); } }; void loadExistingSchedule(); }, [user?.id, selectedRecipientId, serviceType]); const showError = (message: string) => { setError(message); setTimeout(() => setError(null), 4000); }; const resolveRecipientOptions = async (rows: RecipientOption[]) => Promise.all( rows.map(async (recipient) => { const seededPhoto = getRecipientPhoto(recipient); if (seededPhoto || !recipient.photo_path) { return { ...recipient, photo_url: seededPhoto ?? recipient.photo_url, }; } const { data: urlData } = await supabase.storage .from('recipient-photos') .createSignedUrl(recipient.photo_path, 3600); return { ...recipient, photo_url: urlData?.signedUrl ?? recipient.photo_url ?? null, }; }) ); const loadInitialData = async () => { const activeUserId = user?.id ?? previewUserId; const cacheKey = buildCacheKey(activeUserId, 'supportRecipients'); let hasCache = false; try { const cached = await readCache(cacheKey); if (cached && Array.isArray(cached) && cached.length > 0) { setRecipients(cached); setSelectedRecipientId((current) => { if (current && cached.some((recipient) => recipient.id === current)) { return current; } return current ?? cached[0]?.id ?? null; }); setLoading(false); hasCache = true; } } catch (err) { console.error('[support recipients cache] error', err); } if (!hasCache) setLoading(true); const query = supabase .from('recipients') .select('id,first_name,last_name,photo_path') .eq('user_id', activeUserId) .order('created_at', { ascending: false }); const { data, error: loadError } = user ? await query.eq('is_active', true) : await query; if (loadError) { showError(loadError.message); if (!hasCache) { const fallbackRecipients = await resolveRecipientOptions(previewRecipients); setRecipients(fallbackRecipients); setSelectedRecipientId( (current) => current ?? fallbackRecipients[0]?.id ?? null ); setLoading(false); } return; } const resolvedRecipients = await resolveRecipientOptions( (data ?? []) as RecipientOption[] ); setRecipients(resolvedRecipients); setSelectedRecipientId((current) => { if ( current && resolvedRecipients.some((recipient) => recipient.id === current) ) { return current; } return current ?? resolvedRecipients[0]?.id ?? null; }); setLoading(false); void writeCache(cacheKey, resolvedRecipients); }; const loadMerchantsForService = async (nextService: ServiceType) => { const merchantType = nextService === 'grocery' ? 'grocery' : 'pharmacy'; const activeUserId = user?.id ?? previewUserId; const cacheKey = buildCacheKey(activeUserId, `merchants_${merchantType}`); try { const cached = await readCache(cacheKey); if (cached && Array.isArray(cached) && cached.length > 0) { setMerchants(cached); } } catch (err) { console.error('[support merchants cache] error', err); } const { data, error: merchantError } = await supabase .from('merchants') .select('id,name,branch_name,merchant_type') .eq('merchant_type', merchantType) .eq('is_active', true) .order('name', { ascending: true }); if (merchantError) { showError(merchantError.message); return; } const freshMerchants = (data ?? []) as MerchantOption[]; setMerchants(freshMerchants); void writeCache(cacheKey, freshMerchants); }; const handleServiceSelect = (nextService: ServiceType) => { setServiceType(nextService); setMerchantId(null); setNetwork(null); setMeterNumber(''); setOrderId(null); setAmountDisplay(''); setIsCustomAmountSelected(false); }; const validateCurrentStep = () => { if (step === 1 && !selectedRecipientId) return 'Choose who will receive support'; if (step === 2 && !serviceType) return 'Choose a support type'; if (step === 3 && requiresSupportDetails) { if (serviceType === 'airtime' && !network) return 'Choose a mobile network'; if (serviceType === 'electricity' && !meterNumber.trim()) return 'Enter the meter number'; } if (step === amountStep && !parsedAmount) return 'Enter a support amount'; return null; }; const createOrderAndIntent = async () => { if (!selectedRecipientId || !serviceType || !parsedAmount) return false; setSubmitting(true); const activeUserId = user?.id ?? previewUserId; let currentOrderId = orderId; if (!currentOrderId) { const { data, error: orderError } = await supabase .from('support_orders') .insert({ user_id: activeUserId, recipient_id: selectedRecipientId, merchant_id: merchantId, service_type: serviceType, amount: parsedAmount, platform_fee: platformFee, total_amount: totalAmount, currency: 'USD', status: 'pending_payment', payment_method: paymentMethod, network, meter_number: meterNumber.trim() || null, }) .select('id') .single(); if (orderError || !data) { showError(orderError?.message ?? 'Could not create order'); setSubmitting(false); return false; } currentOrderId = data.id; setOrderId(currentOrderId); } if (!user) { setSubmitting(false); return true; } const { data: intentData, error: intentError } = await supabase.functions.invoke('calculate-payment-intent', { body: { orderId: currentOrderId, serviceType, amount: parsedAmount, currency: 'USD', paymentMethod, }, }); if (intentError) { showError(intentError.message); setSubmitting(false); return false; } const response = intentData as { platformFee?: number; totalAmount?: number; } | null; setPlatformFee(Number(response?.platformFee ?? platformFee)); setTotalAmount(Number(response?.totalAmount ?? totalAmount)); setSubmitting(false); return true; }; const handleContinue = async () => { const validation = validateCurrentStep(); if (validation) { showError(validation); return; } if (step === amountStep) { const ok = await createOrderAndIntent(); if (!ok) return; } const currentVisibleIndex = visibleFlowSteps.indexOf(step); const nextVisibleStep = visibleFlowSteps[currentVisibleIndex + 1]; setStep(nextVisibleStep ?? reviewStep); }; const handleBack = () => { if (paymentResult === 'failure') { setPaymentResult('idle'); return; } if (paymentResult === 'success') { if (orderId) { history.push(`/orders/${orderId}`, { parentRoot }); } else { history.push(parentRoot); } return; } const currentVisibleIndex = visibleFlowSteps.indexOf(step); if (currentVisibleIndex > 0) { setStep(visibleFlowSteps[currentVisibleIndex - 1]); return; } history.length > 1 ? history.goBack() : history.replace('/home'); }; const handleConfirmPayment = async () => { if (!orderId || !selectedRecipientId || !serviceType) { showError('Order is not ready yet'); return; } setSubmitting(true); setPaymentResult('idle'); setPaymentResultMessage(null); const { error: fulfilError } = await supabase.functions.invoke( 'confirm-payment-and-fulfil', { body: { orderId }, } ); if (fulfilError) { setSubmitting(false); setPaymentResult('failure'); setPaymentResultMessage( fulfilError.message || 'Payment could not be processed at this time.' ); return; } if (isRecurringEnabled) { const scheduleId = await upsertSupportSchedule(); if (scheduleId) { await supabase .from('support_orders') .update({ recurring_schedule_id: scheduleId }) .eq('id', orderId); await createGeneralizedCareAlert(scheduleId); } } setSubmitting(false); setPaymentResult('success'); setPaymentResultMessage( `Your ${selectedService?.reviewLabel.toLowerCase()} order is confirmed and ready for ${selectedRecipient?.first_name}.` ); }; const handleViewOrderDetails = () => { if (orderId) { history.push(`/orders/${orderId}`, { parentRoot }); } else { history.push(parentRoot); } }; const handlePaymentResultSecondaryAction = () => { if (paymentResult === 'success') { history.push(parentRoot); } else { setPaymentResult('idle'); setPaymentResultMessage(null); } }; const handleRetryPayment = () => { setError(null); setPaymentResult('idle'); setPaymentResultMessage(null); void handleConfirmPayment(); }; const calculateNextDueDate = () => { const date = new Date(); if (frequencyUnit === 'weekly') { date.setDate(date.getDate() + 7 * frequencyInterval); } else { date.setMonth(date.getMonth() + frequencyInterval); } return date.toISOString(); }; const upsertSupportSchedule = async () => { if (!user || !selectedRecipientId || !serviceType) return null; const nextDueAt = calculateNextDueDate(); const { data: existing } = await supabase .from('support_schedules') .select('id') .eq('user_id', user.id) .eq('recipient_id', selectedRecipientId) .eq('service_type', serviceType) .maybeSingle(); if (existing) { const { error: updateError } = await supabase .from('support_schedules') .update({ frequency_unit: frequencyUnit, frequency_interval: frequencyInterval, last_supported_at: new Date().toISOString(), next_due_at: nextDueAt, is_active: true, }) .eq('id', existing.id); if (updateError) { showError('Failed to update recurring schedule'); return null; } return existing.id; } const { data: inserted, error: insertError } = await supabase .from('support_schedules') .insert({ user_id: user.id, recipient_id: selectedRecipientId, service_type: serviceType, frequency_unit: frequencyUnit, frequency_interval: frequencyInterval, last_supported_at: new Date().toISOString(), next_due_at: nextDueAt, is_active: true, }) .select('id') .single(); if (insertError || !inserted) { showError('Failed to save recurring schedule'); return null; } return inserted.id; }; const createGeneralizedCareAlert = async (scheduleId: string) => { if (!user || !selectedRecipientId || !serviceType) return; const nextDueAt = calculateNextDueDate(); const recipient = recipients.find((r) => r.id === selectedRecipientId); const firstName = recipient?.first_name || 'Loved one'; let title = ''; if (serviceType === 'grocery') title = 'Grocery support due soon'; else if (serviceType === 'medication') { title = 'Medication support due soon'; } else if (serviceType === 'airtime') { title = 'Airtime running low soon'; } else if (serviceType === 'electricity') { title = 'Electricity top-up may be needed'; } let intervalStr = ''; if (frequencyInterval === 1) { intervalStr = frequencyUnit === 'weekly' ? 'a week' : 'a month'; } else { intervalStr = `${frequencyInterval} ${frequencyUnit === 'weekly' ? 'weeks' : 'months'}`; } const body = `For ${firstName} • in ${intervalStr}`; const { data: existingAlert } = await supabase .from('care_alerts') .select('id') .eq('schedule_id', scheduleId) .is('dismissed_at', null) .maybeSingle(); if (existingAlert) { await supabase .from('care_alerts') .update({ title, body, due_at: nextDueAt, service_type: serviceType, }) .eq('id', existingAlert.id); } else { await supabase.from('care_alerts').insert({ user_id: user.id, recipient_id: selectedRecipientId, schedule_id: scheduleId, service_type: serviceType, alert_type: 'support_due', title, body, severity: 'info', due_at: nextDueAt, }); } }; const showInlineTotalCard = !isShowingPaymentResult && step === amountStep; const ctaLabel = step === amountStep ? submitting ? 'Preparing...' : 'Review payment' : step === reviewStep ? submitting ? 'Confirming...' : 'Confirm payment' : 'Continue'; const renderPaymentResultCard = () => { if (paymentResult === 'idle') return null; const isSuccess = paymentResult === 'success'; return (

Payment {isSuccess ? 'Confirmed' : 'Declined'}

{isSuccess ? 'Payment successful' : 'Payment failed'}

{paymentResultMessage}

Recipient {selectedRecipient ? `${selectedRecipient.first_name} ${selectedRecipient.last_name}` : ''}
Support type {selectedService?.label}
Total paid {formatMoney(totalAmount)}
{isSuccess ? ( <> View order details Back to activity ) : ( <> {submitting ? 'Retrying...' : 'Try payment again'} Back to review )}
); }; const selectedRecipient = recipients.find((recipient) => recipient.id === selectedRecipientId) ?? null; const selectedRecipientPhoto = getRecipientPhoto(selectedRecipient); const selectedPaymentMethod = paymentMethods.find((method) => method.value === paymentMethod) ?? paymentMethods[0]; return ( 1 ? 'Go to previous step' : 'Go back'} > {selectedService && ( )} {flowTitle}
{!isShowingPaymentResult && (
{visibleFlowSteps.map((item, index) => { const itemVisualStep = index + 1; return (
{itemVisualStep}
{index < visibleFlowSteps.length - 1 && (
)} ); })}
)} {error && !isShowingPaymentResult && (

{error}

)} {isShowingPaymentResult && renderPaymentResultCard()} {!isShowingPaymentResult && step === 1 && !hasPresetService && (

Who are you supporting?

{!loading && recipients.length === 0 && ( history.push('/recipients/new')} > Add your first loved one )}
)} {!isShowingPaymentResult && ((step === 1 && hasPresetService) || (step === 2 && !hasPresetService)) && (

{hasPresetService ? 'Who are you supporting?' : 'Choose support type'}

{hasPresetService && selectedService ? ( ) : (
{services.map((service) => ( ))}
)}
)} {!isShowingPaymentResult && step === 3 && ( <> {(serviceType === 'airtime' || serviceType === 'electricity') && (

Support details

{serviceType === 'airtime' && ( setNetwork(event.detail.value)} > {networks.map((item) => ( {item} ))} )} {serviceType === 'electricity' && ( setMeterNumber( (event.detail.value ?? '').replace(/\D/g, '') ) } /> )}
)} )} {!isShowingPaymentResult && step === amountStep && (
{selectedService && (

{selectedRecipient ? `For ${selectedRecipient.first_name} ${selectedRecipient.last_name}` : 'Selected recipient'}

{selectedService.amountHeading}

{selectedService.amountHint}

)} {!selectedService && (

Choose amount

)}
{amountPresets.map((amount) => ( ))}
{isCustomAmountSelected && ( $ setAmountDisplay(sanitizeAmount(event.detail.value ?? '')) } /> )} {parsedAmount && parsedAmount > 0 && (

Repeat support

Set up automatic reminders

setIsRecurringEnabled(!isRecurringEnabled)} style={{ width: 44, height: 24, borderRadius: 12, background: isRecurringEnabled ? 'var(--flow-accent, #6d28d9)' : 'rgba(23,24,39,0.1)', position: 'relative', cursor: 'pointer', transition: '0.2s', flexShrink: 0, }} >
{isRecurringEnabled && (
Every
{frequencyInterval}
)}
)}
)} {!isShowingPaymentResult && step === reviewStep && (

Review and pay

{selectedService?.reviewLabel ?? 'Support amount'} {formatMoney(parsedAmount ?? 0)}
{(serviceType === 'grocery' || serviceType === 'medication') && (
{selectedService?.imageSrc ? ( ) : ( )}

Redeemable at any Kumusha{' '} {serviceType === 'grocery' ? 'grocery' : 'pharmacy'}{' '} partner

The recipient can use this voucher with approved participating merchants.

)}
Service fee {formatMoney(platformFee)}
Total {formatMoney(totalAmount)}
{isRecurringEnabled && (
Remind me every{' '} {frequencyInterval === 1 ? frequencyUnit === 'weekly' ? 'week' : 'month' : `${frequencyInterval} ${frequencyUnit === 'weekly' ? 'weeks' : 'months'}`}
)}
Payment method
Saved card will be reused for future support in the live version.
)} {!isShowingPaymentResult && (

Total

{formatMoney(totalAmount)}

{ctaLabel}
)}
setIsPaymentSheetOpen(false)} className="support-payment-modal" initialBreakpoint={0.58} breakpoints={[0, 0.58, 0.84]} handle={true} >

Payment method

Choose how you want to pay for this support.

{paymentMethods.map((method) => { const isSelected = method.value === paymentMethod; return ( ); })}
setIsRecipientSheetOpen(false)} className="support-recipient-modal" initialBreakpoint={0.55} breakpoints={[0, 0.55, 0.82]} handle={true} >

Choose recipient

Pick who should receive this support.

{recipients.map((recipient) => { const photo = getRecipientPhoto(recipient); const isSelected = recipient.id === selectedRecipientId; return ( ); })}
); }; export default SupportFlowPage;