← 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' },
});