This guide shows you how to use the TypeScript types with your new database schema.
// Import specific types
import type {
Mover,
Booking,
Payment,
Rating,
MoverLocation,
BookingRequest,
Notification,
Database
} from '@/types/database';
// Import enums
import type {
VerificationStatus,
AvailabilityStatus,
BookingStatus,
PaymentStatus,
PaymentMethod,
VehicleType,
RatingType
} from '@/types/database';
// Import Supabase client (already typed)
import { supabase } from '@/integrations/supabase/client';// Get all verified movers
const getVerifiedMovers = async () => {
const { data, error } = await supabase
const { data, error } = await supabase
.from('movers')
.select('*')
.eq('verification_status', 'verified')
.is('deleted_at', null);
if (error) throw error;
// 'data' is automatically typed as Mover[]
return data;
};
// Get single mover with type safety
const getMover = async (moverId: string): Promise<Mover | null> => {
const { data, error } = await supabase
.from('movers')
.select('*')
.eq('id', moverId)
.single();
if (error) throw error;
return data; // Typed as Mover
};interface CreateMoverInput {
user_id: string;
business_name: string;
phone_primary: string;
vehicle_types: VehicleType[];
service_radius_km?: number;
}
const createMoverProfile = async (input: CreateMoverInput) => {
const { data, error } = await supabase
.from('movers')
.insert({
user_id: input.user_id,
business_name: input.business_name,
phone_primary: input.phone_primary,
vehicle_types: input.vehicle_types,
service_radius_km: input.service_radius_km || 20,
// TypeScript will autocomplete all available fields!
verification_status: 'pending',
availability_status: 'offline',
is_accepting_bookings: false,
has_helpers: false,
helper_count: 0
})
.select()
.single();
if (error) throw error;
return data; // Typed as Mover
};interface CreateBookingInput {
customer_id: string;
pickup_address: string;
pickup_latitude: number;
pickup_longitude: number;
dropoff_address: string;
dropoff_latitude: number;
dropoff_longitude: number;
scheduled_date: string; // ISO date
scheduled_time_start: string; // HH:MM
property_size: string;
estimated_price: number;
}
const createBooking = async (input: CreateBookingInput) => {
const { data, error } = await supabase
.from('bookings')
.insert({
customer_id: input.customer_id,
pickup_address: input.pickup_address,
pickup_location: `POINT(${input.pickup_longitude} ${input.pickup_latitude})`,
dropoff_address: input.dropoff_address,
dropoff_location: `POINT(${input.dropoff_longitude} ${input.dropoff_latitude})`,
scheduled_date: input.scheduled_date,
scheduled_time_start: input.scheduled_time_start,
property_size: input.property_size,
estimated_price: input.estimated_price,
status: 'pending' as BookingStatus, // Type-safe enum
booking_number: '' // Auto-generated by trigger
})
.select()
.single();
if (error) throw error;
return data; // Typed as Booking
};interface NearbyMover {
mover_id: string;
business_name: string;
distance_km: number;
rating: number;
total_moves: number;
vehicle_types: VehicleType[];
}
const findNearbyMovers = async (
latitude: number,
longitude: number,
radiusKm: number = 20,
minRating: number = 3.0
): Promise<NearbyMover[]> => {
const { data, error } = await supabase.rpc('find_nearby_movers', {
p_location: `POINT(${longitude} ${latitude})`,
p_radius_km: radiusKm,
p_min_rating: minRating
});
if (error) throw error;
return data || [];
};interface CreatePaymentInput {
booking_id: string;
customer_id: string;
mover_id: string;
amount: number;
payment_method: PaymentMethod;
commission_rate: number; // e.g., 15.00 for 15%
mpesa_phone_number?: string;
}
const createPayment = async (input: CreatePaymentInput) => {
// Validate commission rate is within acceptable range
if (input.commission_rate < 0 || input.commission_rate > 100) {
throw new Error('Commission rate must be between 0 and 100');
}
const commissionAmount = (input.amount * input.commission_rate) / 100;
const moverPayoutAmount = input.amount - commissionAmount;
const { data, error } = await supabase
.from('payments')
.insert({
booking_id: input.booking_id,
customer_id: input.customer_id,
mover_id: input.mover_id,
amount: input.amount,
payment_method: input.payment_method,
payment_status: 'pending' as PaymentStatus,
commission_rate: input.commission_rate,
commission_amount: commissionAmount,
mover_payout_amount: moverPayoutAmount,
mpesa_phone_number: input.mpesa_phone_number,
payment_reference: '' // Auto-generated
})
.select()
.single();
if (error) throw error;
return data; // Typed as Payment
};interface CreateRatingInput {
booking_id: string;
rater_id: string;
rated_id: string;
rating_type: RatingType;
rating: number; // 1-5
review_text?: string;
punctuality_rating?: number;
professionalism_rating?: number;
care_of_items_rating?: number;
}
const createRating = async (input: CreateRatingInput) => {
const { data, error } = await supabase
.from('ratings')
.insert({
booking_id: input.booking_id,
rater_id: input.rater_id,
rated_id: input.rated_id,
rating_type: input.rating_type,
rating: input.rating,
review_text: input.review_text,
punctuality_rating: input.punctuality_rating,
professionalism_rating: input.professionalism_rating,
care_of_items_rating: input.care_of_items_rating
})
.select()
.single();
if (error) throw error;
return data; // Typed as Rating
};interface TrackLocationInput {
mover_id: string;
booking_id?: string;
latitude: number;
longitude: number;
heading?: number;
speed?: number;
accuracy?: number;
}
const trackMoverLocation = async (input: TrackLocationInput) => {
const { data, error } = await supabase
.from('mover_locations')
.insert({
mover_id: input.mover_id,
booking_id: input.booking_id,
location: `POINT(${input.longitude} ${input.latitude})`,
latitude: input.latitude,
longitude: input.longitude,
heading: input.heading,
speed: input.speed,
accuracy: input.accuracy,
location_source: 'gps'
})
.select()
.single();
if (error) throw error;
return data; // Typed as MoverLocation
};const getCustomerBookings = async (
customerId: string,
status?: BookingStatus
) => {
let query = supabase
.from('bookings')
.select('*')
.eq('customer_id', customerId)
.order('created_at', { ascending: false });
if (status) {
query = query.eq('status', status);
}
const { data, error } = await query;
if (error) throw error;
return data; // Typed as Booking[]
};const getMoverBookings = async (
moverId: string,
statuses: BookingStatus[] = ['pending', 'accepted', 'in_progress']
) => {
const { data, error } = await supabase
.from('bookings')
.select('*')
.eq('mover_id', moverId)
.in('status', statuses)
.order('scheduled_date', { ascending: true });
if (error) throw error;
return data; // Typed as Booking[]
};interface MoverStats {
total_bookings: number;
completed_bookings: number;
cancelled_bookings: number;
total_earnings: number;
average_rating: number;
acceptance_rate: number;
completion_rate: number;
this_month_earnings: number;
this_month_bookings: number;
}
const getMoverStats = async (moverId: string): Promise<MoverStats> => {
const { data, error } = await supabase.rpc('get_mover_stats', {
p_mover_id: moverId
});
if (error) throw error;
return data;
};import { useEffect, useState } from 'react';
import { supabase } from '@/integrations/supabase/client';
import type { Mover } from '@/types/database';
export const MoverList = () => {
const [movers, setMovers] = useState<Mover[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadMovers();
}, []);
const loadMovers = async () => {
try {
const { data, error } = await supabase
.from('movers')
.select('*')
.eq('verification_status', 'verified')
.order('rating', { ascending: false });
if (error) throw error;
setMovers(data || []);
} catch (error) {
console.error('Error loading movers:', error);
} finally {
setLoading(false);
}
};
if (loading) return <div>Loading...</div>;
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{movers.map((mover) => (
<div key={mover.id} className="border rounded-lg p-4">
<h3 className="font-bold">{mover.business_name}</h3>
<p>Rating: {mover.rating} ⭐</p>
<p>Total Moves: {mover.total_moves}</p>
<p>Phone: {mover.phone_primary}</p>
<p>Vehicles: {mover.vehicle_types.join(', ')}</p>
</div>
))}
</div>
);
};import { useState } from 'react';
import { supabase } from '@/integrations/supabase/client';
import type { BookingStatus } from '@/types/database';
import { useAuth } from '@/hooks/useAuth';
interface BookingFormData {
pickup_address: string;
dropoff_address: string;
scheduled_date: string;
scheduled_time_start: string;
property_size: string;
}
export const BookingForm = () => {
const { user } = useAuth();
const [formData, setFormData] = useState<BookingFormData>({
pickup_address: '',
dropoff_address: '',
scheduled_date: '',
scheduled_time_start: '',
property_size: '1BR'
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!user) return;
try {
const { data, error } = await supabase
.from('bookings')
.insert({
customer_id: user.id,
pickup_address: formData.pickup_address,
pickup_location: 'POINT(36.8219 -1.2921)', // Get from geocoding
dropoff_address: formData.dropoff_address,
dropoff_location: 'POINT(36.8219 -1.2921)', // Get from geocoding
scheduled_date: formData.scheduled_date,
scheduled_time_start: formData.scheduled_time_start,
property_size: formData.property_size,
estimated_price: 5000, // Calculate from quote
status: 'pending' as BookingStatus,
booking_number: ''
})
.select()
.single();
if (error) throw error;
alert('Booking created: ' + data.booking_number);
} catch (error) {
console.error('Error creating booking:', error);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<input
type="text"
placeholder="Pickup Address"
value={formData.pickup_address}
onChange={(e) => setFormData({ ...formData, pickup_address: e.target.value })}
className="w-full p-2 border rounded"
/>
{/* Add more form fields */}
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
Create Booking
</button>
</form>
);
};import { useEffect } from 'react';
import { supabase } from '@/integrations/supabase/client';
import type { Booking } from '@/types/database';
export const useBookingSubscription = (customerId: string) => {
useEffect(() => {
const channel = supabase
.channel('booking-updates')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'bookings',
filter: `customer_id=eq.${customerId}`
},
(payload) => {
console.log('Booking updated:', payload.new as Booking);
// Update your state here
}
)
.subscribe((status, err) => {
// Handle channel lifecycle status
if (status === 'SUBSCRIBED') {
console.log('✅ Successfully subscribed to booking updates');
} else if (status === 'CLOSED') {
console.log('⚠️ Channel closed');
// NOTE: Implement reconnection logic here (e.g., exponential backoff retry)
// Consider using a reconnection strategy to re-establish the subscription
} else if (status === 'CHANNEL_ERROR') {
console.error('❌ Channel error:', err);
// NOTE: Implement error recovery and backoff logic
// Common causes: network issues, auth token expiration, rate limits
// Recommended: Use exponential backoff (1s, 2s, 4s, 8s, etc.) before retrying
} else {
console.log('Channel status:', status);
}
});
return () => {
supabase.removeChannel(channel);
};
}, [customerId]);
};Create reusable helper functions:
// src/services/movers.ts
import { supabase } from '@/integrations/supabase/client';
import type { Mover, VerificationStatus } from '@/types/database';
export const moverService = {
getAll: async (status?: VerificationStatus): Promise<Mover[]> => {
let query = supabase.from('movers').select('*');
if (status) {
query = query.eq('verification_status', status);
}
const { data, error } = await query;
if (error) throw error;
return data || [];
},
getById: async (id: string): Promise<Mover | null> => {
const { data, error } = await supabase
.from('movers')
.select('*')
.eq('id', id)
.single();
if (error) throw error;
return data;
},
updateAvailability: async (id: string, isAvailable: boolean) => {
const { data, error } = await supabase
.from('movers')
.update({
is_accepting_bookings: isAvailable,
availability_status: isAvailable ? 'online' : 'offline'
})
.eq('id', id)
.select()
.single();
if (error) throw error;
return data;
}
};- Always use types for function parameters and return values
- Create service layers for database operations
- Use React hooks for data fetching with types
- Leverage TypeScript's autocomplete - it knows all table columns!
- Handle errors properly with typed error responses
- Use enums for status values to prevent typos
- ✅ Import types in your components
- ✅ Update existing API calls to use new tables
- ✅ Create service files for each table (movers, bookings, payments, etc.)
- ✅ Build React hooks for common queries
- ✅ Implement real-time subscriptions for live updates
Your types are fully integrated with Supabase and will give you amazing autocomplete and type safety! 🎉