Initial commit: SIBU 2.0 MISSION

This commit is contained in:
2026-02-21 09:53:31 -05:00
commit 0c7aa53c8b
400 changed files with 67708 additions and 0 deletions

View File

@ -0,0 +1,22 @@
import { apiClient } from './apiClient'
export interface AnalyticsEvent {
event_name: 'app_open' | 'screen_view' | 'route_selected' | 'stop_selected' | 'schedule_viewed' | 'reminder_created' | 'promo_view' | 'promo_click' | 'taxi_view' | 'taxi_click' | 'shuttle_view' | 'shuttle_contact' | 'business_view' | 'business_contact'
screen_name?: string
item_id?: string
properties?: Record<string, any>
}
export const analyticsService = {
logEvent(event: AnalyticsEvent) {
// Log asynchronously without awaiting to avoid blocking UI
apiClient.post('/api/analytics/event', event).catch(error => {
console.warn('Analytics capture failed:', error)
})
},
async getStats() {
const response = await apiClient.get('/api/analytics/dashboard/stats')
return response.data
}
}

View File

@ -0,0 +1,59 @@
/** Base API client for making HTTP requests to the backend */
import axios from 'axios'
import type { AxiosInstance, AxiosError } from 'axios'
export const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
class ApiClient {
private client: AxiosInstance
constructor() {
this.client = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json',
},
timeout: 10000,
})
// Request interceptor
this.client.interceptors.request.use(
(config) => {
const token = localStorage.getItem('auth_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// Response interceptor
this.client.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
// Handle common errors
if (error.response) {
// Server responded with error status
console.error('API Error:', error.response.status, error.response.data)
} else if (error.request) {
// Request made but no response
console.error('Network Error:', error.request)
} else {
// Something else happened
console.error('Error:', error.message)
}
return Promise.reject(error)
}
)
}
get instance(): AxiosInstance {
return this.client
}
}
export const apiClient = new ApiClient().instance

View File

@ -0,0 +1,55 @@
import { apiClient, API_URL } from './apiClient'
export interface LoginResponse {
access_token: string
token_type: string
role: string
full_name: string
profile_photo_url?: string
}
export const authService = {
async login(params: { email: string; password: string; keep_session?: boolean }): Promise<LoginResponse> {
const response = await apiClient.post<LoginResponse>('/api/auth/login', params)
return response.data
},
async registerPassenger(data: any) {
const response = await apiClient.post('/api/auth/register/passenger', data)
return response.data
},
async registerDriver(formData: FormData) {
const response = await apiClient.post('/api/auth/register/driver', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
return response.data
},
async getCurrentUser() {
const response = await apiClient.get('/api/auth/me')
return response.data
},
async updateMe(formData: FormData) {
const response = await apiClient.patch('/api/auth/me', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
return response.data
},
logout() {
localStorage.removeItem('auth_token')
localStorage.removeItem('user_role')
localStorage.removeItem('user_name')
localStorage.removeItem('profile_photo_url')
},
getApiUrl() {
return API_URL
}
}

View File

@ -0,0 +1,57 @@
/** Service for bus stop-related API calls */
import { apiClient } from './apiClient'
import type { BusStop, Route } from '@/types'
export const busStopsService = {
/** Get all bus stops */
async getAllBusStops(): Promise<BusStop[]> {
const response = await apiClient.get<BusStop[]>('/api/bus-stops')
return response.data
},
/** Get a single bus stop by ID */
async getBusStopById(id: string): Promise<BusStop> {
const response = await apiClient.get<BusStop>(`/api/bus-stops/${id}`)
return response.data
},
/** Get all routes passing through a bus stop */
async getBusStopRoutes(stopId: string): Promise<Route[]> {
const response = await apiClient.get<Route[]>(`/api/bus-stops/${stopId}/routes`)
return response.data
},
/** Get estimated next bus arrivals (Mock Data) */
async getNextBusArrivals(_stopId: string): Promise<{ routeName: string; arrivalTime: string }[]> {
// Mock delay to simulate network request
await new Promise(resolve => setTimeout(resolve, 500));
// Generate some random mock arrivals
const mockArrivals = [
{ routeName: "Ruta Boquete - David", arrivalTime: "5 min" },
{ routeName: "Ruta David - Boquete", arrivalTime: "12 min" },
{ routeName: "Ruta Circular", arrivalTime: "25 min" }
];
// Randomly return 1-3 arrivals
return mockArrivals.slice(0, Math.floor(Math.random() * 3) + 1);
},
/** Create a new bus stop (Admin) */
async createBusStop(data: import('@/types').BusStopCreate): Promise<BusStop> {
const response = await apiClient.post<BusStop>('/api/bus-stops', data)
return response.data
},
/** Update a bus stop (Admin) */
async updateBusStop(id: string, data: import('@/types').BusStopUpdate): Promise<BusStop> {
const response = await apiClient.put<BusStop>(`/api/bus-stops/${id}`, data)
return response.data
},
/** Delete a bus stop (Admin) */
async deleteBusStop(id: string): Promise<void> {
await apiClient.delete(`/api/bus-stops/${id}`)
}
}

View File

@ -0,0 +1,42 @@
/** Service for business-related API calls */
import { apiClient } from './apiClient'
import type { Business } from '@/types'
export const businessService = {
/** Get all businesses */
async getAllBusinesses(): Promise<Business[]> {
const response = await apiClient.get<Business[]>('/api/businesses')
return response.data
},
/** Get a single business by ID */
async getBusiness(id: string): Promise<Business> {
const response = await apiClient.get<Business>(`/api/businesses/${id}`)
return response.data
},
/** Create a new business */
async createBusiness(businessData: FormData): Promise<Business> {
const response = await apiClient.post<Business>('/api/businesses', businessData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
return response.data
},
/** Update an existing business */
async updateBusiness(id: string, businessData: FormData): Promise<Business> {
const response = await apiClient.patch<Business>(`/api/businesses/${id}`, businessData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
return response.data
},
/** Delete a business */
async deleteBusiness(id: string): Promise<void> {
await apiClient.delete(`/api/businesses/${id}`)
},
}

View File

@ -0,0 +1,61 @@
/** Service for coupon-related API calls */
import { apiClient } from './apiClient'
import type { Coupon } from '@/types'
export interface CouponFilters {
category?: string
is_active?: boolean
active_only?: boolean
}
export const couponsService = {
/** Get all coupons with optional filters */
async getAllCoupons(filters?: CouponFilters): Promise<Coupon[]> {
const response = await apiClient.get<Coupon[]>('/api/coupons', {
params: filters,
})
return response.data
},
/** Get a single coupon by ID */
async getCouponById(id: string): Promise<Coupon> {
const response = await apiClient.get<Coupon>(`/api/coupons/${id}`)
return response.data
},
/** Create a new coupon */
async createCoupon(coupon: Omit<Coupon, 'id' | 'created_at' | 'updated_at'>): Promise<Coupon> {
const response = await apiClient.post<Coupon>('/api/coupons', coupon)
return response.data
},
/** Update an existing coupon */
async updateCoupon(id: string, coupon: Partial<Coupon>): Promise<Coupon> {
const response = await apiClient.patch<Coupon>(`/api/coupons/${id}`, coupon)
return response.data
},
/** Delete a coupon */
async deleteCoupon(id: string): Promise<void> {
await apiClient.delete(`/api/coupons/${id}`)
},
/** Claim a coupon */
async claimCoupon(id: string): Promise<any> {
const response = await apiClient.post(`/api/coupons/${id}/claim`)
return response.data
},
/** Get current user's claimed coupons */
async getMyCoupons(): Promise<any[]> {
const response = await apiClient.get('/api/coupons/my-coupons')
return response.data
},
/** Validate a coupon by code (merchants/drivers only) */
async validateCoupon(code: string): Promise<any> {
const response = await apiClient.post(`/api/coupons/validate/${code}`)
return response.data
}
}

View File

@ -0,0 +1,74 @@
/** Service for favorite-related API calls */
import { apiClient } from './apiClient'
import type { Favorite } from '@/types'
export const favoritesService = {
/** Get all favorites for the current user */
async getMyFavorites(itemType?: string): Promise<Favorite[]> {
const params = itemType ? { item_type: itemType } : {}
const response = await apiClient.get<Favorite[]>('/api/favorites', { params })
return response.data
},
/** Add a new favorite */
async addFavorite(
itemType: 'route' | 'stop' | 'taxi' | 'coupon' | 'business',
itemId: string,
itemName?: string,
itemImage?: string
): Promise<Favorite> {
const response = await apiClient.post<Favorite>('/api/favorites', {
item_type: itemType,
item_id: itemId,
item_name: itemName,
item_image: itemImage
})
return response.data
},
/** Remove a favorite by type and ID */
async removeFavorite(itemType: string, itemId: string): Promise<void> {
await apiClient.delete(`/api/favorites/${itemType}/${itemId}`)
},
/** Remove a favorite by favorite ID (legacy support) */
async removeFavoriteById(favoriteId: string): Promise<void> {
// This requires finding the favorite first to get type and id
const favorites = await this.getMyFavorites()
const favorite = favorites.find(f => f.id === favoriteId)
if (favorite) {
await this.removeFavorite(favorite.item_type, favorite.item_id)
}
},
/** Check if an item is favorited */
async checkFavorite(itemType: string, itemId: string): Promise<boolean> {
try {
const response = await apiClient.get<{ is_favorite: boolean }>(
`/api/favorites/check/${itemType}/${itemId}`
)
return response.data.is_favorite
} catch (error) {
console.error('Error checking favorite:', error)
return false
}
},
/** Toggle favorite status */
async toggleFavorite(
itemType: 'route' | 'stop' | 'taxi' | 'coupon' | 'business',
itemId: string,
itemName?: string,
itemImage?: string
): Promise<boolean> {
const isFavorite = await this.checkFavorite(itemType, itemId)
if (isFavorite) {
await this.removeFavorite(itemType, itemId)
return false
} else {
await this.addFavorite(itemType, itemId, itemName, itemImage)
return true
}
}
}

View File

@ -0,0 +1,28 @@
import { apiClient } from './apiClient';
export interface Report {
id: string;
user_id?: string;
user_name?: string;
message: string;
status: 'pending' | 'resolved' | 'archived';
created_at: string;
}
export const reportsService = {
async sendReport(message: string) {
const response = await apiClient.post('/api/reports', { message });
return response.data;
},
async getReports() {
// This would be for the admin
const response = await apiClient.get('/api/reports');
return response.data;
},
async updateReportStatus(reportId: string, status: string) {
const response = await apiClient.patch(`/api/reports/${reportId}`, { status });
return response.data;
}
};

View File

@ -0,0 +1,56 @@
/** Service for route-related API calls */
import { apiClient } from './apiClient'
import type { Route, BusStop } from '@/types'
export const routesService = {
/** Get all routes with optional filtering */
async getAllRoutes(filters?: { originCity?: string, destinationCity?: string }): Promise<Route[]> {
const response = await apiClient.get<Route[]>('/api/routes', {
params: {
origin_city: filters?.originCity,
destination_city: filters?.destinationCity
}
})
return response.data
},
/** Get a single route by ID */
async getRouteById(id: string): Promise<Route> {
const response = await apiClient.get<Route>(`/api/routes/${id}`)
return response.data
},
/** Get all stops for a route */
async getRouteStops(routeId: string): Promise<BusStop[]> {
const response = await apiClient.get<BusStop[]>(`/api/routes/${routeId}/stops`)
return response.data
},
/** Create a new route (Admin) */
async createRoute(data: import('@/types').RouteCreate): Promise<Route> {
const response = await apiClient.post<Route>('/api/routes', data)
return response.data
},
/** Update a route (Admin) */
async updateRoute(id: string, data: import('@/types').RouteUpdate): Promise<Route> {
const response = await apiClient.put<Route>(`/api/routes/${id}`, data)
return response.data
},
/** Delete a route (Admin) */
async deleteRoute(id: string): Promise<void> {
await apiClient.delete(`/api/routes/${id}`)
},
/** Add a stop to a route (Admin) */
async addStopToRoute(routeId: string, data: import('@/types').RouteStopCreate): Promise<void> {
await apiClient.post(`/api/routes/${routeId}/stops`, data)
},
/** Update a stop on a route (Admin) - including reorder */
async updateRouteStop(routeId: string, stopId: string, data: import('@/types').RouteStopUpdate): Promise<void> {
await apiClient.put(`/api/routes/${routeId}/stops/${stopId}`, data)
}
}

View File

@ -0,0 +1,32 @@
import { apiClient } from './apiClient';
export const schedulesService = {
async getRouteSchedules(routeId: string, onlyPublished = true) {
const response = await apiClient.get('/api/schedules', {
params: { route_id: routeId, only_published: onlyPublished }
});
return response.data;
},
async getStopSchedules(stopId: string, onlyPublished = true) {
const response = await apiClient.get('/api/schedules', {
params: { stop_id: stopId, only_published: onlyPublished }
});
return response.data;
},
async createSchedule(scheduleData: any) {
const response = await apiClient.post('/api/schedules', scheduleData);
return response.data;
},
async updateSchedule(scheduleId: string, updateData: any) {
const response = await apiClient.put(`/api/schedules/${scheduleId}`, updateData);
return response.data;
},
async deleteSchedule(scheduleId: string) {
const response = await apiClient.delete(`/api/schedules/${scheduleId}`);
return response.data;
}
};

View File

@ -0,0 +1,27 @@
/** Service for shuttle-related API calls (Intercity/Tourism) */
import { apiClient } from './apiClient'
import type { Shuttle } from '@/types'
export interface ShuttleFilters {
origin?: string
destination?: string
company_name?: string
trip_type?: string
is_active?: boolean
}
export const shuttlesService = {
/** Get all shuttles with optional filters */
async getAllShuttles(filters?: ShuttleFilters): Promise<Shuttle[]> {
const response = await apiClient.get<Shuttle[]>('/api/shuttles', {
params: filters,
})
return response.data
},
/** Get a single shuttle by ID */
async getShuttleById(id: string): Promise<Shuttle> {
const response = await apiClient.get<Shuttle>(`/api/shuttles/${id}`)
return response.data
},
}

View File

@ -0,0 +1,27 @@
/** Service for taxi-related API calls */
import { apiClient } from './apiClient'
import type { Taxi } from '@/types'
export interface TaxiFilters {
corregimiento?: string
shift?: string
english_speaking?: boolean
is_active?: boolean
}
export const taxisService = {
/** Get all taxis with optional filters */
async getAllTaxis(filters?: TaxiFilters): Promise<Taxi[]> {
const response = await apiClient.get<Taxi[]>('/api/taxis', {
params: filters,
})
return response.data
},
/** Get a single taxi by ID */
async getTaxiById(id: string): Promise<Taxi> {
const response = await apiClient.get<Taxi>(`/api/taxis/${id}`)
return response.data
},
}

View File

@ -0,0 +1,32 @@
import { apiClient } from './apiClient'
export interface TelemetryData {
latitude: number
longitude: number
speed?: number
heading?: number
status?: 'active' | 'offline' | 'break'
}
export interface ActiveUnit {
user_id: string
full_name: string
latitude: number
longitude: number
speed?: number
heading?: number
timestamp: string
vehicle_type: string
license_plate: string
}
export const telemetryService = {
async sendTelemetry(data: TelemetryData) {
return await apiClient.post('/api/telemetry', data)
},
async getActiveUnits(): Promise<ActiveUnit[]> {
const response = await apiClient.get<ActiveUnit[]>('/api/telemetry/active')
return response.data
}
}

View File

@ -0,0 +1,27 @@
import { apiClient } from './apiClient';
export const usersService = {
async searchUsers(email: string) {
const response = await apiClient.get('/api/users/search', {
params: { email }
});
return response.data;
},
async getUserDetails(userId: string) {
const response = await apiClient.get(`/api/users/${userId}`);
return response.data;
},
async getPendingDrivers() {
const response = await apiClient.get('/api/users/pending-drivers');
return response.data;
},
async verifyUser(userId: string, isVerified: boolean) {
const response = await apiClient.post(`/api/users/${userId}/verify`, null, {
params: { is_verified: isVerified }
});
return response.data;
}
};