Files
appcakes-builds/src/pages/SupportFlowPage.tsx
T
2026-07-04 10:50:55 +00:00

1916 lines
67 KiB
TypeScript

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<LocationState>();
const { user } = useAuth();
const [step, setStep] = useState(1);
const [recipients, setRecipients] = useState<RecipientOption[]>([]);
const [merchants, setMerchants] = useState<MerchantOption[]>([]);
const [selectedRecipientId, setSelectedRecipientId] = useState<string | null>(
location.state?.recipientId ?? null
);
const [serviceType, setServiceType] = useState<ServiceType | null>(
location.state?.serviceType ?? null
);
const [merchantId, setMerchantId] = useState<string | null>(null);
const [network, setNetwork] = useState<string | null>(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<string | null>(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<string | null>(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<RecipientOption[]>(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<MerchantOption[]>(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 (
<div className="support-payment-result-card">
<div className={`sprc-icon-shell ${isSuccess ? 'success' : 'failure'}`}>
<IonIcon
icon={isSuccess ? checkmarkCircleOutline : closeCircleOutline}
/>
</div>
<div>
<p className="sprc-eyebrow">
Payment {isSuccess ? 'Confirmed' : 'Declined'}
</p>
<h2 className="sprc-title">
{isSuccess ? 'Payment successful' : 'Payment failed'}
</h2>
<p className="sprc-body">{paymentResultMessage}</p>
</div>
<div className="sprc-summary-slab">
<div className="sprc-summary-row">
<span>Recipient</span>
<strong>
{selectedRecipient
? `${selectedRecipient.first_name} ${selectedRecipient.last_name}`
: ''}
</strong>
</div>
<div className="sprc-summary-row">
<span>Support type</span>
<strong>{selectedService?.label}</strong>
</div>
<div className="sprc-summary-row">
<span>Total paid</span>
<strong>{formatMoney(totalAmount)}</strong>
</div>
</div>
<div className="sprc-actions">
{isSuccess ? (
<>
<IonButton
className="sprc-primary-btn"
expand="block"
onClick={handleViewOrderDetails}
>
View order details
</IonButton>
<IonButton
className="sprc-secondary-btn"
expand="block"
onClick={handlePaymentResultSecondaryAction}
>
Back to activity
</IonButton>
</>
) : (
<>
<IonButton
className="sprc-primary-btn"
expand="block"
onClick={handleRetryPayment}
disabled={submitting}
>
{submitting ? 'Retrying...' : 'Try payment again'}
</IonButton>
<IonButton
className="sprc-secondary-btn"
expand="block"
onClick={handlePaymentResultSecondaryAction}
>
Back to review
</IonButton>
</>
)}
</div>
</div>
);
};
const selectedRecipient =
recipients.find((recipient) => recipient.id === selectedRecipientId) ??
null;
const selectedRecipientPhoto = getRecipientPhoto(selectedRecipient);
const selectedPaymentMethod =
paymentMethods.find((method) => method.value === paymentMethod) ??
paymentMethods[0];
return (
<IonPage style={{ backgroundColor: '#fafafa' }}>
<IonHeader className="ion-no-border">
<IonToolbar className="support-toolbar" style={themeStyle}>
<IonButtons slot="start">
<IonButton
className="support-back-button"
fill="clear"
onClick={handleBack}
aria-label={step > 1 ? 'Go to previous step' : 'Go back'}
>
<IonIcon icon={chevronBackOutline} slot="icon-only" />
</IonButton>
</IonButtons>
<IonTitle className="support-toolbar-title">
{selectedService && (
<span className="support-toolbar-service-icon" aria-hidden="true">
{selectedService.imageSrc ? (
<img
src={selectedService.imageSrc}
alt=""
className="support-toolbar-service-image"
/>
) : (
<IonIcon icon={selectedService.icon} />
)}
</span>
)}
<span>{flowTitle}</span>
</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent
style={
{
'--background': pageBackground,
'--padding-top': '8px',
'--padding-bottom': '32px',
} as React.CSSProperties
}
>
<div className="support-page-shell" style={themeStyle}>
{!isShowingPaymentResult && (
<div className="support-step-indicator">
{visibleFlowSteps.map((item, index) => {
const itemVisualStep = index + 1;
return (
<React.Fragment key={item}>
<div
className={`ssi-chip ${itemVisualStep === visualStep ? 'active' : itemVisualStep < visualStep ? 'completed' : ''}`}
>
{itemVisualStep}
</div>
{index < visibleFlowSteps.length - 1 && (
<div
className={`ssi-line ${itemVisualStep < visualStep ? 'active' : ''}`}
/>
)}
</React.Fragment>
);
})}
</div>
)}
{error && !isShowingPaymentResult && (
<p
style={{ margin: '0 20px 12px', color: '#dc2626', fontSize: 13 }}
>
{error}
</p>
)}
{isShowingPaymentResult && renderPaymentResultCard()}
{!isShowingPaymentResult && step === 1 && !hasPresetService && (
<div className="support-form-card">
<h2 className="sfc-title">Who are you supporting?</h2>
<button
type="button"
className="support-recipient-trigger"
onClick={() => !loading && setIsRecipientSheetOpen(true)}
disabled={loading}
>
<div className="support-recipient-trigger-left">
<IonAvatar className="support-recipient-avatar">
{selectedRecipientPhoto ? (
<img
src={selectedRecipientPhoto}
alt={
selectedRecipient
? `${selectedRecipient.first_name} ${selectedRecipient.last_name}`
: 'Recipient'
}
/>
) : (
<div className="support-recipient-avatar-fallback">
{selectedRecipient
? `${selectedRecipient.first_name.charAt(0)}${selectedRecipient.last_name.charAt(0)}`
: '?'}
</div>
)}
</IonAvatar>
<div className="support-recipient-copy">
<span className="support-recipient-name">
{selectedRecipient
? `${selectedRecipient.first_name} ${selectedRecipient.last_name}`
: 'Choose loved one'}
</span>
</div>
</div>
<IonIcon
icon={chevronDownOutline}
className="support-recipient-chevron"
/>
</button>
{!loading && recipients.length === 0 && (
<IonButton
fill="clear"
onClick={() => history.push('/recipients/new')}
>
Add your first loved one
</IonButton>
)}
</div>
)}
{!isShowingPaymentResult &&
((step === 1 && hasPresetService) ||
(step === 2 && !hasPresetService)) && (
<div className="support-form-card">
<h2 className="sfc-title">
{hasPresetService
? 'Who are you supporting?'
: 'Choose support type'}
</h2>
{hasPresetService && selectedService ? (
<button
type="button"
className="support-recipient-trigger"
onClick={() => !loading && setIsRecipientSheetOpen(true)}
disabled={loading}
>
<div className="support-recipient-trigger-left">
<IonAvatar className="support-recipient-avatar">
{selectedRecipientPhoto ? (
<img
src={selectedRecipientPhoto}
alt={
selectedRecipient
? `${selectedRecipient.first_name} ${selectedRecipient.last_name}`
: 'Recipient'
}
/>
) : (
<div className="support-recipient-avatar-fallback">
{selectedRecipient
? `${selectedRecipient.first_name.charAt(0)}${selectedRecipient.last_name.charAt(0)}`
: '?'}
</div>
)}
</IonAvatar>
<div className="support-recipient-copy">
<span className="support-recipient-name">
{selectedRecipient
? `${selectedRecipient.first_name} ${selectedRecipient.last_name}`
: 'Choose loved one'}
</span>
<span className="support-recipient-hint">
{selectedService.label} already selected
</span>
</div>
</div>
<IonIcon
icon={chevronDownOutline}
className="support-recipient-chevron"
/>
</button>
) : (
<div className="service-type-grid">
{services.map((service) => (
<button
key={service.type}
type="button"
className={`service-type-card service-${service.type} ${serviceType === service.type ? 'selected' : ''}`}
style={
{
'--service-accent': service.color,
'--service-tint': service.selectedTint,
'--service-card-bg': service.cardBg,
} as React.CSSProperties
}
onClick={() => handleServiceSelect(service.type)}
>
<div
className="stc-icon-box"
style={{ background: service.bg }}
>
{service.imageSrc ? (
<img
src={service.imageSrc}
alt={service.imageAlt ?? service.label}
className={`support-service-image ${service.type === 'grocery' ? 'support-service-image-grocery' : ''}`}
/>
) : (
<IonIcon
icon={service.icon}
style={{ color: service.color }}
/>
)}
</div>
<span className="stc-label">{service.label}</span>
</button>
))}
</div>
)}
</div>
)}
{!isShowingPaymentResult && step === 3 && (
<>
{(serviceType === 'airtime' || serviceType === 'electricity') && (
<div className="support-form-card">
<h2 className="sfc-title">Support details</h2>
{serviceType === 'airtime' && (
<IonSelect
className="support-select"
interface="action-sheet"
placeholder="Choose network"
value={network}
onIonChange={(event) => setNetwork(event.detail.value)}
>
{networks.map((item) => (
<IonSelectOption key={item} value={item}>
{item}
</IonSelectOption>
))}
</IonSelect>
)}
{serviceType === 'electricity' && (
<IonInput
className="support-text-input"
type="text"
inputMode="numeric"
placeholder="e.g. 12345678901"
label="Meter number"
labelPlacement="floating"
value={meterNumber}
onIonInput={(event) =>
setMeterNumber(
(event.detail.value ?? '').replace(/\D/g, '')
)
}
/>
)}
</div>
)}
</>
)}
{!isShowingPaymentResult && step === amountStep && (
<div className="support-form-card support-form-card-themed">
{selectedService && (
<div className="support-context-row support-context-row-amount">
<div
className="support-context-avatar-wrap"
aria-hidden="true"
>
<IonAvatar className="support-context-avatar">
{selectedRecipientPhoto ? (
<img src={selectedRecipientPhoto} alt="" />
) : (
<div className="support-recipient-avatar-fallback">
{selectedRecipient
? `${selectedRecipient.first_name.charAt(0)}${selectedRecipient.last_name.charAt(0)}`
: '?'}
</div>
)}
</IonAvatar>
<div className="support-context-badge">
{selectedService.imageSrc ? (
<img
src={selectedService.imageSrc}
alt=""
className="support-context-badge-image"
/>
) : (
<IonIcon icon={selectedService.icon} />
)}
</div>
</div>
<div>
<p className="support-step-kicker">
{selectedRecipient
? `For ${selectedRecipient.first_name} ${selectedRecipient.last_name}`
: 'Selected recipient'}
</p>
<h2 className="sfc-title support-themed-title">
{selectedService.amountHeading}
</h2>
<p className="support-amount-hint">
{selectedService.amountHint}
</p>
</div>
</div>
)}
{!selectedService && (
<h2 className="sfc-title support-themed-title">
Choose amount
</h2>
)}
<div className="amount-preset-chips">
{amountPresets.map((amount) => (
<button
key={amount}
type="button"
className={`amount-chip ${!isCustomAmountSelected && Number(amountDisplay) === amount ? 'active' : ''}`}
onClick={() => {
setIsCustomAmountSelected(false);
setAmountDisplay(amount.toFixed(2));
}}
>
${amount}
</button>
))}
<button
type="button"
className={`amount-chip ${isCustomAmountSelected ? 'active' : ''}`}
onClick={() => {
setIsCustomAmountSelected(true);
if (isPresetAmountSelected) {
setAmountDisplay('');
}
}}
>
Custom
</button>
</div>
{isCustomAmountSelected && (
<IonItem
lines="none"
style={
{
'--background': 'var(--flow-card-soft, #fafafa)',
'--border-radius': '12px',
} as React.CSSProperties
}
>
<span
slot="start"
style={{ fontWeight: 700, color: '#6b7280' }}
>
$
</span>
<IonInput
className="custom-amount-input"
type="text"
inputMode="decimal"
placeholder="0.00"
value={amountDisplay}
onIonInput={(event) =>
setAmountDisplay(sanitizeAmount(event.detail.value ?? ''))
}
/>
</IonItem>
)}
{parsedAmount && parsedAmount > 0 && (
<div
style={{
background: '#ffffff',
borderRadius: '24px',
padding: '20px',
margin: '24px 0 0',
display: 'flex',
flexDirection: 'column',
gap: '14px',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
}}
>
<div className="support-recurring-icon">
<IonIcon icon={timeOutline} />
</div>
<div>
<h3
style={{
margin: '0 0 2px',
fontSize: 16,
fontWeight: 700,
color: '#171827',
}}
>
Repeat support
</h3>
<p
style={{
margin: 0,
fontSize: 13,
fontWeight: 500,
color: 'rgba(23,24,39,0.62)',
lineHeight: 1.45,
}}
>
Set up automatic reminders
</p>
</div>
</div>
<div
onClick={() => 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,
}}
>
<div
style={{
width: 20,
height: 20,
background: '#fff',
borderRadius: 10,
position: 'absolute',
top: 2,
left: isRecurringEnabled ? 22 : 2,
transition: '0.2s',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
}}
/>
</div>
</div>
{isRecurringEnabled && (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '14px',
marginTop: '6px',
}}
>
<div style={{ display: 'flex', gap: '8px' }}>
<button
type="button"
onClick={() => setFrequencyUnit('weekly')}
style={{
flex: 1,
height: 40,
borderRadius: 999,
fontSize: 14,
fontWeight: 600,
transition: '0.2s',
background:
frequencyUnit === 'weekly'
? 'var(--flow-accent, #6d28d9)'
: 'var(--flow-accent-soft, rgba(109, 40, 217, 0.08))',
color:
frequencyUnit === 'weekly'
? '#ffffff'
: 'var(--flow-accent, #6d28d9)',
}}
>
Weekly
</button>
<button
type="button"
onClick={() => setFrequencyUnit('monthly')}
style={{
flex: 1,
height: 40,
borderRadius: 999,
fontSize: 14,
fontWeight: 600,
transition: '0.2s',
background:
frequencyUnit === 'monthly'
? 'var(--flow-accent, #6d28d9)'
: 'var(--flow-accent-soft, rgba(109, 40, 217, 0.08))',
color:
frequencyUnit === 'monthly'
? '#ffffff'
: 'var(--flow-accent, #6d28d9)',
}}
>
Monthly
</button>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
background: '#f5f3f8',
borderRadius: 12,
height: 48,
padding: '0 16px',
}}
>
<span
style={{
fontSize: 14,
fontWeight: 600,
color: '#171827',
}}
>
Every
</span>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '16px',
}}
>
<button
type="button"
onClick={() =>
setFrequencyInterval(
Math.max(1, frequencyInterval - 1)
)
}
style={{
width: 24,
height: 24,
borderRadius: 12,
background: 'rgba(109,40,217,0.1)',
color: '#6d28d9',
fontSize: 16,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
-
</button>
<span
style={{
fontSize: 15,
fontWeight: 700,
color: '#171827',
width: 20,
textAlign: 'center',
}}
>
{frequencyInterval}
</span>
<button
type="button"
onClick={() =>
setFrequencyInterval(
Math.min(12, frequencyInterval + 1)
)
}
style={{
width: 24,
height: 24,
borderRadius: 12,
background: 'rgba(109,40,217,0.1)',
color: '#6d28d9',
fontSize: 16,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
+
</button>
</div>
</div>
</div>
)}
</div>
)}
</div>
)}
{!isShowingPaymentResult && step === reviewStep && (
<div className="support-form-card">
<h2 className="sfc-title">Review and pay</h2>
<div className="mbl-row support-review-primary-row">
<span>{selectedService?.reviewLabel ?? 'Support amount'}</span>
<strong>{formatMoney(parsedAmount ?? 0)}</strong>
</div>
{(serviceType === 'grocery' || serviceType === 'medication') && (
<div className="support-redemption-note">
<div className="support-redemption-note-icon">
{selectedService?.imageSrc ? (
<img src={selectedService.imageSrc} alt="" />
) : (
<IonIcon icon={selectedService?.icon} />
)}
</div>
<div className="support-redemption-note-copy">
<p className="support-redemption-note-title">
Redeemable at any Kumusha{' '}
{serviceType === 'grocery' ? 'grocery' : 'pharmacy'}{' '}
partner
</p>
<p className="support-redemption-note-subtitle">
The recipient can use this voucher with approved
participating merchants.
</p>
</div>
</div>
)}
<div className="mbl-row">
<span>Service fee</span>
<strong>{formatMoney(platformFee)}</strong>
</div>
<div className="mbl-row total">
<span>Total</span>
<strong>{formatMoney(totalAmount)}</strong>
</div>
{isRecurringEnabled && (
<div
style={{
background:
'var(--flow-accent-soft, rgba(109, 40, 217, 0.05))',
borderRadius: 12,
padding: 12,
marginTop: 12,
display: 'flex',
alignItems: 'center',
gap: 10,
}}
>
<IonIcon
icon={timeOutline}
style={{
color: 'var(--flow-accent, #6d28d9)',
fontSize: 18,
}}
/>
<span
style={{
fontSize: 13,
fontWeight: 600,
color: 'var(--flow-accent, #6d28d9)',
}}
>
Remind me every{' '}
{frequencyInterval === 1
? frequencyUnit === 'weekly'
? 'week'
: 'month'
: `${frequencyInterval} ${frequencyUnit === 'weekly' ? 'weeks' : 'months'}`}
</span>
</div>
)}
<div className="support-payment-select-wrap">
<span className="support-payment-select-label">
Payment method
</span>
<button
type="button"
className="support-payment-preview support-payment-preview-button"
onClick={() => setIsPaymentSheetOpen(true)}
aria-label="Choose payment method"
>
<div
className={`support-payment-method-icon support-payment-method-icon-${selectedPaymentMethod.kind}`}
aria-hidden="true"
>
{selectedPaymentMethod.kind === 'card' && (
<IonIcon icon={cardOutline} />
)}
{selectedPaymentMethod.kind === 'apple' && (
<IonIcon icon={logoApple} />
)}
{selectedPaymentMethod.kind === 'google' && (
<span
className="support-google-pay-mark"
aria-label="Google Pay"
>
<svg
className="support-google-pay-g"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
fill="#4285F4"
/>
<path
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
fill="#34A853"
/>
<path
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z"
fill="#FBBC05"
/>
<path
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
fill="#EA4335"
/>
</svg>
<span className="support-google-pay-pay">Pay</span>
</span>
)}
{selectedPaymentMethod.kind === 'paypal' && (
<IonIcon icon={logoPaypal} />
)}
</div>
<div className="support-payment-trigger-copy">
<span className="support-payment-trigger-name">
{selectedPaymentMethod.label}
</span>
<span className="support-payment-trigger-detail">
{selectedPaymentMethod.detail}
</span>
</div>
<IonIcon
icon={chevronDownOutline}
className="support-payment-preview-chevron"
/>
</button>
</div>
<IonLabel
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
marginTop: 12,
color: '#6b7280',
fontSize: 13,
}}
>
<IonIcon icon={cardOutline} /> Saved card will be reused for
future support in the live version.
</IonLabel>
</div>
)}
{!isShowingPaymentResult && (
<div className="support-inline-action-card">
<div
className={`ssb-info ${!showInlineTotalCard ? 'ssb-info-placeholder' : ''}`}
aria-hidden={!showInlineTotalCard}
>
<p className="ssb-label">Total</p>
<p className="ssb-total">{formatMoney(totalAmount)}</p>
</div>
<IonButton
className="ssb-btn"
disabled={submitting || loading}
onClick={
step === reviewStep ? handleConfirmPayment : handleContinue
}
>
{ctaLabel}
</IonButton>
</div>
)}
</div>
<IonModal
isOpen={isPaymentSheetOpen}
onDidDismiss={() => setIsPaymentSheetOpen(false)}
className="support-payment-modal"
initialBreakpoint={0.58}
breakpoints={[0, 0.58, 0.84]}
handle={true}
>
<IonContent
className="support-payment-sheet-content"
scrollY={true}
style={{ '--background': '#ffffff' } as React.CSSProperties}
>
<div className="support-payment-sheet">
<div className="support-payment-sheet-header">
<div>
<h3 className="support-payment-sheet-title">
Payment method
</h3>
<p className="support-payment-sheet-subtitle">
Choose how you want to pay for this support.
</p>
</div>
<button
type="button"
className="support-payment-sheet-close"
onClick={() => setIsPaymentSheetOpen(false)}
aria-label="Close payment method sheet"
>
<IonIcon icon={closeOutline} />
</button>
</div>
<IonList className="support-payment-sheet-list">
{paymentMethods.map((method) => {
const isSelected = method.value === paymentMethod;
return (
<button
key={method.value}
type="button"
className={`support-payment-sheet-option ${isSelected ? 'selected' : ''}`}
onClick={() => {
setPaymentMethod(method.value);
setIsPaymentSheetOpen(false);
}}
>
<div
className={`support-payment-method-icon support-payment-method-icon-${method.kind}`}
aria-hidden="true"
>
{method.kind === 'card' && (
<IonIcon icon={cardOutline} />
)}
{method.kind === 'apple' && (
<IonIcon icon={logoApple} />
)}
{method.kind === 'google' && (
<span
className="support-google-pay-mark"
aria-label="Google Pay"
>
<svg
className="support-google-pay-g"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
fill="#4285F4"
/>
<path
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
fill="#34A853"
/>
<path
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z"
fill="#FBBC05"
/>
<path
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
fill="#EA4335"
/>
</svg>
<span className="support-google-pay-pay">Pay</span>
</span>
)}
{method.kind === 'paypal' && (
<IonIcon icon={logoPaypal} />
)}
</div>
<div className="support-payment-trigger-copy">
<span className="support-payment-trigger-name">
{method.label}
</span>
<span className="support-payment-trigger-detail">
{method.detail}
</span>
</div>
{isSelected && (
<div className="support-payment-selected-dot" />
)}
</button>
);
})}
</IonList>
</div>
</IonContent>
</IonModal>
<IonModal
isOpen={isRecipientSheetOpen}
onDidDismiss={() => setIsRecipientSheetOpen(false)}
className="support-recipient-modal"
initialBreakpoint={0.55}
breakpoints={[0, 0.55, 0.82]}
handle={true}
>
<IonContent
style={{ '--background': '#ffffff' } as React.CSSProperties}
>
<div className="support-recipient-sheet">
<div className="support-recipient-sheet-header">
<div>
<h3 className="support-recipient-sheet-title">
Choose recipient
</h3>
<p className="support-recipient-sheet-subtitle">
Pick who should receive this support.
</p>
</div>
<button
type="button"
className="support-recipient-sheet-close"
onClick={() => setIsRecipientSheetOpen(false)}
aria-label="Close recipient sheet"
>
<IonIcon icon={closeOutline} />
</button>
</div>
<div className="support-recipient-sheet-list">
{recipients.map((recipient) => {
const photo = getRecipientPhoto(recipient);
const isSelected = recipient.id === selectedRecipientId;
return (
<button
key={recipient.id}
type="button"
className={`support-recipient-sheet-item ${isSelected ? 'selected' : ''}`}
onClick={() => {
setSelectedRecipientId(recipient.id);
setIsRecipientSheetOpen(false);
}}
>
<div className="support-recipient-sheet-item-left">
<IonAvatar className="support-recipient-avatar support-recipient-sheet-avatar">
{photo ? (
<img
src={photo}
alt={`${recipient.first_name} ${recipient.last_name}`}
/>
) : (
<div className="support-recipient-avatar-fallback">
{`${recipient.first_name.charAt(0)}${recipient.last_name.charAt(0)}`}
</div>
)}
</IonAvatar>
<div className="support-recipient-copy">
<span className="support-recipient-name">
{recipient.first_name} {recipient.last_name}
</span>
<span className="support-recipient-hint">
{isSelected
? 'Currently selected'
: 'Tap to choose recipient'}
</span>
</div>
</div>
{isSelected && (
<div className="support-recipient-selected-dot" />
)}
</button>
);
})}
</div>
</div>
</IonContent>
</IonModal>
</IonContent>
</IonPage>
);
};
export default SupportFlowPage;