← back to Costa Rica App
app/index.tsx
79 lines
import { useEffect, useState } from 'react';
import { View, Text, FlatList, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native';
import { Link, router } from 'expo-router';
import { api, money, isAuthed } from '../src/api';
const VERTICALS = [
{ k: '', label: 'All' },
{ k: 'tourism_hotel', label: 'Stays' },
{ k: 'tourism_tour', label: 'Tours' },
{ k: 'tourism_restaurant', label: 'Eat' },
{ k: 'service_beauty', label: 'Beauty' },
];
export default function Directory() {
const [items, setItems] = useState<any[]>([]);
const [q, setQ] = useState('');
const [vert, setVert] = useState('');
const [loading, setLoading] = useState(true);
async function load() {
setLoading(true);
try {
const r = await api.listings({ ...(q && { q }), ...(vert && { vertical: vert }), limit: '40' });
setItems(r.listings || []);
} catch (e) { setItems([]); } finally { setLoading(false); }
}
useEffect(() => { load(); }, [vert]);
return (
<View style={s.wrap}>
<View style={s.searchRow}>
<TextInput style={s.search} placeholder="Search stays, tours, services…" value={q}
onChangeText={setQ} onSubmitEditing={load} returnKeyType="search" />
<TouchableOpacity style={s.mybtn} onPress={() => router.push(isAuthed() ? '/bookings' : '/login')}>
<Text style={{ color: '#fff' }}>{isAuthed() ? 'Trips' : 'Sign in'}</Text>
</TouchableOpacity>
</View>
<FlatList horizontal showsHorizontalScrollIndicator={false} data={VERTICALS} keyExtractor={x => x.k}
style={s.chips} renderItem={({ item }) => (
<TouchableOpacity style={[s.chip, vert === item.k && s.chipOn]} onPress={() => setVert(item.k)}>
<Text style={vert === item.k ? { color: '#fff' } : { color: '#0a7d55' }}>{item.label}</Text>
</TouchableOpacity>
)} />
{loading ? <ActivityIndicator style={{ marginTop: 40 }} /> : (
<FlatList data={items} keyExtractor={x => x.slug} contentContainerStyle={{ padding: 12 }}
ListEmptyComponent={<Text style={s.empty}>No bookable listings yet in this filter.</Text>}
renderItem={({ item }) => (
<Link href={`/listing/${item.slug}`} asChild>
<TouchableOpacity style={s.card}>
<Text style={s.name}>{item.name}</Text>
<Text style={s.meta}>{item.region} · {String(item.vertical).replace(/_/g, ' ')}</Text>
<Text style={s.price}>{money(item.base_price, item.currency)}<Text style={s.per}> / {item.booking_type === 'nightly' ? 'night' : 'person'}</Text></Text>
{item.rating ? <Text style={s.rating}>★ {item.rating}</Text> : null}
</TouchableOpacity>
</Link>
)} />
)}
</View>
);
}
const s = StyleSheet.create({
wrap: { flex: 1, backgroundColor: '#f6f7f5' },
searchRow: { flexDirection: 'row', padding: 12, gap: 8, alignItems: 'center' },
search: { flex: 1, backgroundColor: '#fff', borderRadius: 10, paddingHorizontal: 14, height: 44, borderWidth: 1, borderColor: '#e2e5e0' },
iconbtn: { backgroundColor: '#fff', width: 44, height: 44, borderRadius: 10, alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: '#e2e5e0' },
mybtn: { backgroundColor: '#0a7d55', paddingHorizontal: 14, height: 44, borderRadius: 10, justifyContent: 'center' },
chips: { flexGrow: 0, paddingHorizontal: 8 },
chip: { paddingHorizontal: 14, paddingVertical: 8, marginHorizontal: 4, borderRadius: 20, borderWidth: 1, borderColor: '#0a7d55', backgroundColor: '#fff' },
chipOn: { backgroundColor: '#0a7d55' },
card: { backgroundColor: '#fff', borderRadius: 12, padding: 14, marginBottom: 10, borderWidth: 1, borderColor: '#e8ebe6' },
name: { fontSize: 16, fontWeight: '700', color: '#1a2b22' },
meta: { color: '#6b756e', marginTop: 2, fontSize: 13 },
price: { marginTop: 8, fontSize: 15, fontWeight: '700', color: '#0a7d55' },
per: { fontWeight: '400', color: '#6b756e' },
rating: { position: 'absolute', top: 14, right: 14, color: '#b8860b' },
empty: { textAlign: 'center', color: '#6b756e', marginTop: 40 },
});