feat: prepaid/postpaid onboarding; prepaid payment gate on activation ticket; slide-to-confirm on all payments; pay button on dashboard invoices

This commit is contained in:
Nemo
2026-03-24 12:44:55 +08:00
parent 8a3f7564a6
commit 705e375667
2 changed files with 165 additions and 11 deletions

View File

@@ -89,7 +89,8 @@ export default function NewClientScreen() {
const [areaId, setAreaId] = useState('');
// Step 2
const [planId, setPlanId] = useState('');
const [planId, setPlanId] = useState('');
const [subType, setSubType] = useState<'PREPAID' | 'POSTPAID'>('POSTPAID');
const { data: areas = [] } = useQuery({
queryKey: ['areas'],
@@ -142,12 +143,12 @@ export default function NewClientScreen() {
// 2. Create subscription (PENDING until installation confirmed)
await api.post('/api/v1/subscriptions', {
clientId: client.id,
clientId: client.id,
planId,
type: 'POSTPAID',
status: 'PENDING',
billingDay: 5,
startDate: new Date().toISOString(),
type: subType,
status: 'PENDING',
billingDay: 5,
startDate: new Date().toISOString(),
});
// 3. Create installation ticket
@@ -156,6 +157,8 @@ export default function NewClientScreen() {
subject: `New Installation — ${firstName.trim()} ${lastName.trim()}`,
type: 'INSTALLATION',
priority: 'NORMAL',
// Pass subType so activation ticket knows to gate on payment for PREPAID
...(subType === 'PREPAID' ? { priority: 'HIGH' } : {}),
});
const ticket = ticketRes.data;
@@ -252,7 +255,27 @@ export default function NewClientScreen() {
{step === 1 && (
<View>
<Text style={{ fontSize: 20, fontWeight: '700', color: '#1E293B', marginBottom: 4 }}>Select a Plan</Text>
<Text style={{ fontSize: 15, color: '#64748B', marginBottom: 20 }}>Choose the internet plan for this subscriber.</Text>
<Text style={{ fontSize: 15, color: '#64748B', marginBottom: 20 }}>Choose the internet plan and billing type.</Text>
{/* Subscription type toggle */}
<View style={{ marginBottom: 20 }}>
<Text style={{ fontSize: 13, fontWeight: '600', color: '#64748B', marginBottom: 8 }}>Billing Type <Text style={{ color: '#DC2626' }}>*</Text></Text>
<View style={{ flexDirection: 'row', backgroundColor: '#F1F5F9', borderRadius: 12, padding: 4 }}>
{(['POSTPAID', 'PREPAID'] as const).map(t => (
<TouchableOpacity key={t} onPress={() => setSubType(t)} style={{ flex: 1, paddingVertical: 10, borderRadius: 10, alignItems: 'center', backgroundColor: subType === t ? '#fff' : 'transparent' }}>
<Text style={{ fontSize: 14, fontWeight: '700', color: subType === t ? '#0891B2' : '#94A3B8' }}>{t}</Text>
<Text style={{ fontSize: 11, color: subType === t ? '#64748B' : '#CBD5E1', marginTop: 1 }}>
{t === 'POSTPAID' ? 'Pay after billing day' : 'Pay before activation'}
</Text>
</TouchableOpacity>
))}
</View>
{subType === 'PREPAID' && (
<View style={{ backgroundColor: '#FFFBEB', borderRadius: 10, padding: 10, marginTop: 8, borderWidth: 1, borderColor: '#FDE68A' }}>
<Text style={{ fontSize: 13, color: '#92400E' }}>⚠️ Prepaid clients must settle their first payment before the account can be activated.</Text>
</View>
)}
</View>
{plansLoading
? <ActivityIndicator color="#0891B2" />
@@ -319,11 +342,19 @@ export default function NewClientScreen() {
{/* Plan card */}
<View style={{ backgroundColor: '#fff', borderRadius: 14, padding: 18, marginBottom: 12, borderWidth: 1, borderColor: '#E2E8F0' }}>
<Text style={{ fontSize: 13, fontWeight: '700', color: '#94A3B8', marginBottom: 12, letterSpacing: 0.5 }}>PLAN</Text>
<Row label="Plan" value={selectedPlan?.name ?? ''} />
<Row label="Speed" value={`↓ ${selectedPlan?.speedDownMbps} Mbps ↑ ${selectedPlan?.speedUpMbps} Mbps`} />
<Row label="Price" value={`₱${Number(selectedPlan?.monthlyPrice ?? 0).toLocaleString()}/month`} isLast />
<Row label="Plan" value={selectedPlan?.name ?? ''} />
<Row label="Speed" value={`↓ ${selectedPlan?.speedDownMbps} Mbps ↑ ${selectedPlan?.speedUpMbps} Mbps`} />
<Row label="Price" value={`₱${Number(selectedPlan?.monthlyPrice ?? 0).toLocaleString()}/month`} />
<Row label="Billing" value={subType} isLast />
</View>
{subType === 'PREPAID' && (
<View style={{ backgroundColor: '#FFFBEB', borderRadius: 12, padding: 14, marginBottom: 12, borderWidth: 1, borderColor: '#FDE68A' }}>
<Text style={{ fontSize: 14, fontWeight: '700', color: '#92400E', marginBottom: 4 }}>⚠️ Prepaid — Payment Required Before Activation</Text>
<Text style={{ fontSize: 13, color: '#92400E' }}>After installation, a payment of ₱{Number(selectedPlan?.monthlyPrice ?? 0).toLocaleString()} must be collected before the account goes active.</Text>
</View>
)}
{/* What will happen */}
<View style={{ backgroundColor: '#ECFEFF', borderRadius: 12, padding: 16, borderWidth: 1, borderColor: '#A5F3FC' }}>
<Text style={{ fontSize: 14, fontWeight: '700', color: '#0E7490', marginBottom: 8 }}>What happens next</Text>