← back to Costa Rica App
app/host.tsx
96 lines
import { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert, ScrollView } from 'react-native';
import { api } from '../src/api';
// Host onboarding: apply -> add a payout method (SINPE for Tico hosts, Plaid for
// foreign hosts) -> list a place for booking.
export default function Host() {
const [legal, setLegal] = useState('');
const [cedula, setCedula] = useState('');
const [country, setCountry] = useState<'CR' | 'US'>('CR');
const [sinpe, setSinpe] = useState('');
const [placeSlug, setPlaceSlug] = useState('');
const [price, setPrice] = useState('120');
const [busy, setBusy] = useState(false);
const [step, setStep] = useState(1);
async function apply() {
setBusy(true);
try { await api.hostApply({ legal_name: legal, cedula, country }); setStep(2); }
catch (e: any) { Alert.alert('Error', e.message); } finally { setBusy(false); }
}
async function addPayout() {
setBusy(true);
try {
if (country === 'CR') {
await api.addPayoutMethod({ kind: 'sinpe_movil', sinpe_phone: sinpe, currency: 'CRC', is_default: true });
} else {
const t = await api.plaidLinkToken(); // open Plaid Link with t.link_token in a real build
Alert.alert('Plaid', `Link token issued (${t.env}). In production this opens Plaid Link; sandbox auto-verifies.`);
await api.plaidExchange('public-sandbox-token');
}
setStep(3);
} catch (e: any) { Alert.alert('Error', e.message); } finally { setBusy(false); }
}
async function listPlace() {
setBusy(true);
try {
await api.hostListing({ place_slug: placeSlug, booking_type: 'nightly', currency: 'USD', base_price: Math.round(+price * 100), max_guests: 4 });
Alert.alert('Listed! 🎉', 'Your place is now bookable.');
} catch (e: any) { Alert.alert('Error', e.message); } finally { setBusy(false); }
}
return (
<ScrollView contentContainerStyle={s.wrap}>
<Text style={s.h}>Become a Host</Text>
<Text style={s.step}>Step {step} of 3</Text>
{step === 1 && (<>
<Field label="Legal / business name" value={legal} onChange={setLegal} />
<Field label="Cédula (juridica/fisica)" value={cedula} onChange={setCedula} />
<View style={s.seg}>
{(['CR', 'US'] as const).map(c => (
<TouchableOpacity key={c} style={[s.segBtn, country === c && s.segOn]} onPress={() => setCountry(c)}>
<Text style={country === c ? { color: '#fff' } : { color: '#0a7d55' }}>{c === 'CR' ? '🇨🇷 Costa Rica' : '🌎 Foreign'}</Text>
</TouchableOpacity>
))}
</View>
<Btn label="Continue" onPress={apply} busy={busy} />
</>)}
{step === 2 && (<>
<Text style={s.info}>{country === 'CR' ? 'Payouts via SINPE Móvil (colones).' : 'Payouts via Plaid-linked bank (ACH).'}</Text>
{country === 'CR'
? <Field label="SINPE Móvil number (+506…)" value={sinpe} onChange={setSinpe} />
: <Text style={s.info}>Tap continue to link your bank via Plaid.</Text>}
<Btn label={country === 'CR' ? 'Save SINPE' : 'Link bank (Plaid)'} onPress={addPayout} busy={busy} />
</>)}
{step === 3 && (<>
<Field label="Place slug to list (from directory)" value={placeSlug} onChange={setPlaceSlug} />
<Field label="Price per night (USD)" value={price} onChange={setPrice} keyboard="number-pad" />
<Btn label="List my place" onPress={listPlace} busy={busy} />
</>)}
</ScrollView>
);
}
const Field = ({ label, value, onChange, keyboard }: any) => (
<View style={{ marginTop: 12 }}>
<Text style={s.lbl}>{label}</Text>
<TextInput style={s.i} value={value} onChangeText={onChange} keyboardType={keyboard || 'default'} autoCapitalize="none" />
</View>
);
const Btn = ({ label, onPress, busy }: any) => (
<TouchableOpacity style={s.cta} onPress={onPress} disabled={busy}><Text style={s.ctaTxt}>{busy ? '…' : label}</Text></TouchableOpacity>
);
const s = StyleSheet.create({
wrap: { padding: 20, backgroundColor: '#f6f7f5', flexGrow: 1 },
h: { fontSize: 24, fontWeight: '800', color: '#1a2b22' },
step: { color: '#6b756e', marginTop: 4, marginBottom: 8 },
info: { color: '#33413a', marginTop: 12, lineHeight: 20 },
lbl: { color: '#6b756e', marginBottom: 4, fontSize: 13 },
i: { backgroundColor: '#fff', borderRadius: 10, height: 46, paddingHorizontal: 14, borderWidth: 1, borderColor: '#e2e5e0' },
seg: { flexDirection: 'row', gap: 8, marginTop: 14 },
segBtn: { flex: 1, height: 46, borderRadius: 10, borderWidth: 1.5, borderColor: '#0a7d55', alignItems: 'center', justifyContent: 'center', backgroundColor: '#fff' },
segOn: { backgroundColor: '#0a7d55' },
cta: { backgroundColor: '#0a7d55', height: 52, borderRadius: 12, alignItems: 'center', justifyContent: 'center', marginTop: 20 },
ctaTxt: { color: '#fff', fontSize: 16, fontWeight: '700' },
});