diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx
index d5118d0..942a57a 100644
--- a/src/components/ui/Input.tsx
+++ b/src/components/ui/Input.tsx
@@ -13,6 +13,8 @@ export interface InputProps extends TextInputProps {
onChangeText?: (text: string) => void;
style?: any;
labelStyle?: any;
+ multiline?: boolean;
+ numberOfLines?: number;
}
export function Input({ label, error, style, labelStyle, ...props }: InputProps) {
diff --git a/src/constants/theme.ts b/src/constants/theme.ts
index f030e84..63c7604 100644
--- a/src/constants/theme.ts
+++ b/src/constants/theme.ts
@@ -1,6 +1,7 @@
export const THEME = {
colors: {
primary: '#1E3A8A',
+ primaryDark: '#172554',
primaryLight: '#E0E7FF',
primaryLighter: '#EEF2FF',
background: '#F9FAFB',
@@ -12,6 +13,7 @@ export const THEME = {
},
border: '#F3F4F6',
warning: '#EAB308',
+ error: '#EF4444',
},
spacing: {
xs: 4,
diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx
index e365565..fdd55ee 100644
--- a/src/navigation/AppNavigator.tsx
+++ b/src/navigation/AppNavigator.tsx
@@ -16,6 +16,9 @@ import { AvailabilityListScreen } from '../screens/AvailabilityListScreen';
import { AddAvailabilityScreen } from '../screens/AddAvailabilityScreen';
import { EditAvailabilityScreen } from '../screens/EditAvailabilityScreen';
import { PaymentProfileScreen } from '../screens/PaymentProfileScreen';
+import { ManageServicesScreen } from '../screens/ManageServicesScreen';
+import { AddEditServiceScreen } from '../screens/AddEditServiceScreen';
+import { Service } from '../services/serviceService';
export type RootStackParamList = {
Login: undefined;
@@ -32,6 +35,8 @@ export type RootStackParamList = {
end_time: string;
};
PaymentProfile: undefined;
+ ManageServices: undefined;
+ AddEditService: { service?: Service };
};
export type TabParamList = {
@@ -120,6 +125,8 @@ export const AppNavigator = () => {
+
+
);
diff --git a/src/screens/AddEditServiceScreen.tsx b/src/screens/AddEditServiceScreen.tsx
new file mode 100644
index 0000000..60a9a9b
--- /dev/null
+++ b/src/screens/AddEditServiceScreen.tsx
@@ -0,0 +1,472 @@
+import React, { useState, useEffect } from 'react';
+import { View, StyleSheet, ScrollView, Alert, KeyboardAvoidingView, Platform, Switch, TouchableOpacity } from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+import { NativeStackNavigationProp } from '@react-navigation/native-stack';
+import { RouteProp } from '@react-navigation/native';
+import { RootStackParamList } from '../navigation/AppNavigator';
+import { Header } from '../components/ui/Header';
+import { Input } from '../components/ui/Input';
+import { Button } from '../components/ui/Button';
+import { THEME } from '../constants/theme';
+import { serviceService, Service } from '../services/serviceService';
+import { profileService } from '../services/profileService';
+import { Typography } from '../components/ui/Typography';
+import { ChevronDown, Check } from 'lucide-react-native';
+
+const UNIT_OPTIONS = [
+ 'Visit',
+ 'Hour',
+ 'Day',
+ 'Week',
+ 'Month'
+];
+
+interface AddEditServiceScreenProps {
+ navigation: NativeStackNavigationProp;
+ route: RouteProp;
+}
+
+export const AddEditServiceScreen: React.FC = ({ navigation, route }) => {
+ const existingService = route.params?.service;
+ const isEditing = !!existingService;
+
+ const [form, setForm] = useState>({
+ name: existingService?.name || '',
+ description: existingService?.description || '',
+ care_type_slug: existingService?.care_type_slug || '',
+ care_sub_type_slug: existingService?.care_sub_type_slug || '',
+ category: existingService?.category || '',
+ unit: existingService?.unit || 'Visit',
+ customer_price: existingService?.customer_price?.toString() || '',
+ caregiver_rate: existingService?.caregiver_rate?.toString() || '',
+ tax: existingService?.tax?.toString() || '0',
+ duration_minutes: existingService?.duration_minutes?.toString() || '',
+ is_active: existingService?.is_active ?? true,
+ });
+
+ const [errors, setErrors] = useState>({});
+ const [isSaving, setIsSaving] = useState(false);
+
+ const [careTypes, setCareTypes] = useState([]);
+ const [careSubTypes, setCareSubTypes] = useState([]);
+ const [taxes, setTaxes] = useState([]);
+ const [showCareTypePicker, setShowCareTypePicker] = useState(false);
+ const [showCareSubTypePicker, setShowCareSubTypePicker] = useState(false);
+ const [showUnitPicker, setShowUnitPicker] = useState(false);
+ const [showTaxPicker, setShowTaxPicker] = useState(false);
+
+ useEffect(() => {
+ fetchCareTypes();
+ }, []);
+
+ useEffect(() => {
+ if (form.care_type_slug) {
+ fetchCareSubTypes(form.care_type_slug);
+ }
+ }, [form.care_type_slug]);
+
+ const fetchCareTypes = async () => {
+ try {
+ const [ownSkillsResponse, data, taxesData] = await Promise.all([
+ profileService.getOwnSkills(),
+ profileService.getCareTypes(),
+ serviceService.getTaxes()
+ ]);
+
+ setCareTypes(data);
+ setTaxes(taxesData);
+ } catch (error) {
+ console.error('Error fetching master types', error);
+ }
+ };
+
+ const fetchCareSubTypes = async (slug: string) => {
+ try {
+ const [ownSkillsResponse, data] = await Promise.all([
+ profileService.getOwnSkills(),
+ profileService.getCareSubTypes(slug)
+ ]);
+
+ const ownSkills = ownSkillsResponse || [];
+ const mySkillSlugs = ownSkills.map((s: any) => s.slug);
+
+ const allowedSubTypes = data.filter((st: any) => mySkillSlugs.includes(st.slug));
+ setCareSubTypes(allowedSubTypes.length > 0 ? allowedSubTypes : data);
+ } catch (error) {
+ console.error('Error fetching care sub types', error);
+ }
+ };
+
+ const handleChange = (field: keyof Service, value: string | number) => {
+ setForm(prev => ({ ...prev, [field]: value }));
+ if (errors[field]) {
+ setErrors(prev => ({ ...prev, [field]: '' }));
+ }
+ };
+
+ const toggleIsActive = () => {
+ setForm(prev => ({ ...prev, is_active: !prev.is_active }));
+ };
+
+ const validate = () => {
+ const newErrors: Record = {};
+ if (!form.name?.trim()) newErrors.name = 'Service name is required';
+ if (!form.description?.trim()) newErrors.description = 'Description is required';
+ if (!form.customer_price) newErrors.customer_price = 'Customer Price is required';
+ if (!form.caregiver_rate) newErrors.caregiver_rate = 'Caregiver Rate is required';
+
+ setErrors(newErrors);
+ return Object.keys(newErrors).length === 0;
+ };
+
+ const handleSave = async () => {
+ if (!validate()) {
+ return;
+ }
+
+ try {
+ setIsSaving(true);
+ const payload = {
+ ...form,
+ customer_price: parseFloat(form.customer_price as string),
+ caregiver_rate: parseFloat(form.caregiver_rate as string),
+ tax: form.tax ? parseFloat(form.tax as string) : 0,
+ duration_minutes: form.duration_minutes ? parseInt(form.duration_minutes as string, 10) : undefined,
+ } as Service;
+
+ if (isEditing && existingService.id) {
+ await serviceService.updateService(existingService.id, payload);
+ Alert.alert('Success', 'Service updated successfully.');
+ } else {
+ await serviceService.addService(payload);
+ Alert.alert('Success', 'Service added successfully.');
+ }
+ navigation.goBack();
+ } catch (error: any) {
+ console.error('Error saving service:', error);
+ Alert.alert('Error', error?.response?.data?.message || 'Failed to save service.');
+ } finally {
+ setIsSaving(false);
+ }
+ };
+
+ const insets = useSafeAreaInsets();
+
+ return (
+
+
+
+
+
+
+ {isEditing ? 'Update the details of your service below.' : 'Add a new service that you offer to clients.'}
+
+
+
+ handleChange('name', val)}
+ error={errors.name}
+ />
+ handleChange('description', val)}
+ error={errors.description}
+ style={styles.textArea}
+ />
+
+ Care Type
+ setShowCareTypePicker(!showCareTypePicker)}
+ >
+
+ {careTypes.find(c => c.slug === form.care_type_slug)?.name || form.care_type_slug || 'Select Care Type'}
+
+
+
+
+ {showCareTypePicker && (
+
+ {careTypes.map(type => (
+ {
+ handleChange('care_type_slug', type.slug);
+ handleChange('category', type.name);
+ handleChange('care_sub_type_slug', '');
+ setShowCareTypePicker(false);
+ }}
+ >
+
+ {type.name}
+
+ {form.care_type_slug === type.slug && }
+
+ ))}
+
+ )}
+
+
+
+ Care Sub Type
+ setShowCareSubTypePicker(!showCareSubTypePicker)}
+ >
+
+ {careSubTypes.find(c => c.slug === form.care_sub_type_slug)?.name || form.care_sub_type_slug || 'Select Care Sub Type'}
+
+
+
+
+ {showCareSubTypePicker && (
+
+ {careSubTypes.length === 0 ? (
+
+ Please select a Care Type first
+
+ ) : (
+ careSubTypes.map(type => (
+ {
+ handleChange('care_sub_type_slug', type.slug);
+ setShowCareSubTypePicker(false);
+ }}
+ >
+
+ {type.name}
+
+ {form.care_sub_type_slug === type.slug && }
+
+ ))
+ )}
+
+ )}
+
+ handleChange('category', val)}
+ />
+
+
+ Pricing Unit
+ setShowUnitPicker(!showUnitPicker)}
+ >
+
+ {form.unit || 'Select Unit (e.g. Visit, Hour)'}
+
+
+
+
+ {showUnitPicker && (
+
+ {UNIT_OPTIONS.map(unitOption => (
+ {
+ handleChange('unit', unitOption);
+ setShowUnitPicker(false);
+ }}
+ >
+
+ {unitOption}
+
+ {form.unit === unitOption && }
+
+ ))}
+
+ )}
+
+
+ handleChange('customer_price', val)}
+ error={errors.customer_price}
+ />
+ handleChange('caregiver_rate', parseFloat(val) || 0)}
+ error={errors.caregiver_rate}
+ />
+
+
+ Tax (%)
+ setShowTaxPicker(!showTaxPicker)}
+ >
+
+ {taxes.find(t => t.rate_percent?.toString() === form.tax?.toString())?.name ? `${taxes.find(t => t.rate_percent?.toString() === form.tax?.toString())?.name} (${form.tax}%)` : form.tax ? `${form.tax}%` : 'Select Tax Rate'}
+
+
+
+
+ {showTaxPicker && (
+
+ {taxes.length === 0 ? (
+
+ Loading taxes...
+
+ ) : (
+ taxes.map(taxOption => (
+ {
+ handleChange('tax', taxOption.rate_percent);
+ setShowTaxPicker(false);
+ }}
+ >
+
+
+ {taxOption.name} ({taxOption.rate_percent}%)
+
+
+ {taxOption.description}
+
+
+ {form.tax?.toString() === taxOption.rate_percent?.toString() && }
+
+ ))
+ )}
+
+ )}
+
+
+ handleChange('duration_minutes', val)}
+ />
+
+ Is Active
+
+
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: THEME.colors.background,
+ },
+ content: {
+ flex: 1,
+ },
+ scrollContent: {
+ padding: 24,
+ paddingBottom: 40,
+ },
+ helperText: {
+ color: THEME.colors.text.secondary,
+ marginBottom: 24,
+ },
+ formContainer: {
+ marginBottom: 32,
+ },
+ textArea: {
+ minHeight: 100,
+ textAlignVertical: 'top', // for Android
+ },
+ switchContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingVertical: 12,
+ borderTopWidth: 1,
+ borderTopColor: '#E5E7EB',
+ marginTop: 8,
+ },
+ dropdownContainer: {
+ marginBottom: 20,
+ },
+ dropdownLabel: {
+ color: '#374151',
+ marginBottom: 6,
+ },
+ dropdownBox: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ borderWidth: 1,
+ borderColor: '#D1D5DB',
+ borderRadius: 8,
+ height: 50,
+ paddingHorizontal: 16,
+ backgroundColor: '#F9FAFB',
+ },
+ dropdownText: {
+ color: '#111827',
+ },
+ dropdownList: {
+ backgroundColor: '#FFF',
+ borderWidth: 1,
+ borderColor: '#E5E7EB',
+ borderBottomLeftRadius: 8,
+ borderBottomRightRadius: 8,
+ borderTopWidth: 0,
+ marginTop: 0,
+ overflow: 'hidden',
+ },
+ dropdownOption: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ padding: 14,
+ borderBottomWidth: 1,
+ borderBottomColor: '#F3F4F6',
+ },
+ dropdownOptionSelected: {
+ backgroundColor: '#EEF2FF',
+ },
+ dropdownOptionText: {
+ color: '#374151',
+ },
+ dropdownOptionTextSelected: {
+ color: THEME.colors.primary,
+ fontWeight: 'bold',
+ },
+ saveButton: {
+ backgroundColor: THEME.colors.primary,
+ },
+});
diff --git a/src/screens/LoginScreen.tsx b/src/screens/LoginScreen.tsx
index 955fa1b..42d146e 100644
--- a/src/screens/LoginScreen.tsx
+++ b/src/screens/LoginScreen.tsx
@@ -1,5 +1,5 @@
-import React, { useState } from 'react';
-import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, TouchableOpacity } from 'react-native';
+import React, { useState, useEffect } from 'react';
+import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, TouchableOpacity, Alert } from 'react-native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '../navigation/AppNavigator';
import { Typography } from '../components/ui/Typography';
@@ -23,6 +23,20 @@ export const LoginScreen: React.FC = ({ navigation }) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
+ useEffect(() => {
+ const checkAuthStatus = async () => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ if (token) {
+ navigation.replace('MainTabs');
+ }
+ } catch (e) {
+ console.error('Error checking auth status:', e);
+ }
+ };
+ checkAuthStatus();
+ }, [navigation]);
+
const handleLogin = async () => {
if (step === 'PHONE') {
if (!phone || phone.length < 10) {
@@ -34,9 +48,14 @@ export const LoginScreen: React.FC = ({ navigation }) => {
try {
const response = await authService.login(phone);
console.log('OTP sent:', response);
+ if (response.success === false) {
+ Alert.alert('API Error', response.message || 'Unknown error');
+ return;
+ }
setStep('OTP');
} catch (err: any) {
console.error('Login failed:', err);
+ Alert.alert('Frontend Error', err?.response?.data?.message || err.message || 'Failed to connect');
setError('Failed to send OTP. Please check your number and try again.');
} finally {
setIsLoading(false);
@@ -51,9 +70,14 @@ export const LoginScreen: React.FC = ({ navigation }) => {
try {
const response = await authService.verifyOtp(phone, otp);
console.log('OTP verified successfully:', response);
- // Save the token
- if (response.data && response.data.accessToken) {
- await AsyncStorage.setItem('token', response.data.accessToken);
+ // Save the token and refresh token
+ if (response.data) {
+ if (response.data.accessToken) {
+ await AsyncStorage.setItem('token', response.data.accessToken);
+ }
+ if (response.data.refreshToken) {
+ await AsyncStorage.setItem('refresh_token', response.data.refreshToken);
+ }
} else if (response.token) {
await AsyncStorage.setItem('token', response.token);
}
diff --git a/src/screens/ManageServicesScreen.tsx b/src/screens/ManageServicesScreen.tsx
new file mode 100644
index 0000000..ead2d22
--- /dev/null
+++ b/src/screens/ManageServicesScreen.tsx
@@ -0,0 +1,249 @@
+import React, { useState, useEffect } from 'react';
+import { View, StyleSheet, FlatList, Alert, TouchableOpacity, ActivityIndicator } from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { NativeStackNavigationProp } from '@react-navigation/native-stack';
+import { RootStackParamList } from '../navigation/AppNavigator';
+import { Header } from '../components/ui/Header';
+import { Typography } from '../components/ui/Typography';
+import { THEME } from '../constants/theme';
+import { serviceService, Service } from '../services/serviceService';
+import { Edit2, Trash2, Plus } from 'lucide-react-native';
+import { useIsFocused } from '@react-navigation/native';
+
+interface ManageServicesScreenProps {
+ navigation: NativeStackNavigationProp;
+}
+
+export const ManageServicesScreen: React.FC = ({ navigation }) => {
+ const [services, setServices] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const isFocused = useIsFocused();
+
+ useEffect(() => {
+ if (isFocused) {
+ fetchServices();
+ }
+ }, [isFocused]);
+
+ const fetchServices = async () => {
+ try {
+ setIsLoading(true);
+ const data = await serviceService.getServices();
+ // Ensure data is array
+ setServices(Array.isArray(data) ? data : []);
+ } catch (error) {
+ console.error('Error fetching services:', error);
+ Alert.alert('Error', 'Failed to load services. Please try again.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleDelete = (id?: number) => {
+ if (!id) return;
+
+ Alert.alert(
+ 'Delete Service',
+ 'Are you sure you want to delete this service?',
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Delete',
+ style: 'destructive',
+ onPress: async () => {
+ try {
+ await serviceService.deleteService(id);
+ Alert.alert('Success', 'Service deleted successfully.');
+ fetchServices();
+ } catch (error) {
+ console.error('Error deleting service:', error);
+ Alert.alert('Error', 'Failed to delete service.');
+ }
+ }
+ }
+ ]
+ );
+ };
+
+ const renderItem = ({ item }: { item: Service }) => (
+
+
+ {item.name}
+ {item.description}
+
+
+
+ Price: ${item.customer_price}
+
+ {item.duration_minutes && (
+
+ • Duration: {item.duration_minutes} mins
+
+ )}
+
+
+
+ {item.category && (
+
+ {item.category}
+
+ )}
+ {item.unit && (
+
+ {item.unit}
+
+ )}
+
+
+ {item.is_active ? 'Active' : 'Inactive'}
+
+
+
+
+
+
+ navigation.navigate('AddEditService', { service: item })}
+ >
+
+
+ handleDelete(item.id)}
+ >
+
+
+
+
+ );
+
+ return (
+ // @ts-ignore - TS types for SafeAreaView in this version incorrectly omit the style prop
+
+
+
+ {isLoading ? (
+
+
+
+ ) : services.length === 0 ? (
+
+ No services found. Add one to get started.
+
+ ) : (
+ item.id?.toString() || index.toString()}
+ renderItem={renderItem}
+ contentContainerStyle={styles.listContent}
+ />
+ )}
+
+ navigation.navigate('AddEditService', {})}
+ >
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: THEME.colors.background,
+ },
+ centerContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 24,
+ },
+ emptyText: {
+ color: THEME.colors.text.secondary,
+ textAlign: 'center',
+ },
+ listContent: {
+ padding: 16,
+ paddingBottom: 80, // for FAB
+ },
+ serviceCard: {
+ backgroundColor: '#FFFFFF',
+ borderRadius: 12,
+ padding: 16,
+ marginBottom: 12,
+ flexDirection: 'row',
+ alignItems: 'flex-start',
+ borderWidth: 1,
+ borderColor: '#E5E7EB',
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 1 },
+ shadowOpacity: 0.05,
+ shadowRadius: 2,
+ elevation: 2,
+ },
+ serviceInfo: {
+ flex: 1,
+ },
+ serviceName: {
+ marginBottom: 4,
+ },
+ serviceDescription: {
+ color: THEME.colors.text.secondary,
+ marginBottom: 8,
+ },
+ metaContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: 8,
+ },
+ price: {
+ color: THEME.colors.primary,
+ },
+ duration: {
+ color: THEME.colors.text.secondary,
+ marginLeft: 8,
+ },
+ skillsContainer: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ gap: 8,
+ },
+ skillBadge: {
+ backgroundColor: THEME.colors.primaryLighter,
+ paddingHorizontal: 8,
+ paddingVertical: 4,
+ borderRadius: 16,
+ },
+ skillText: {
+ color: THEME.colors.primaryDark,
+ },
+ actionButtons: {
+ flexDirection: 'row',
+ marginLeft: 16,
+ },
+ actionBtn: {
+ padding: 8,
+ marginLeft: 4,
+ backgroundColor: '#F3F4F6',
+ borderRadius: 8,
+ },
+ fab: {
+ position: 'absolute',
+ bottom: 24,
+ right: 24,
+ width: 56,
+ height: 56,
+ borderRadius: 28,
+ backgroundColor: THEME.colors.primary,
+ justifyContent: 'center',
+ alignItems: 'center',
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 4 },
+ shadowOpacity: 0.3,
+ shadowRadius: 4,
+ elevation: 6,
+ },
+});
diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx
index 6e00888..314e907 100644
--- a/src/screens/SettingsScreen.tsx
+++ b/src/screens/SettingsScreen.tsx
@@ -4,7 +4,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { Typography } from '../components/ui/Typography';
import { Header } from '../components/ui/Header';
import { Card } from '../components/ui/Card';
-import { User, ChevronRight, LogOut, CreditCard } from 'lucide-react-native';
+import { User, ChevronRight, LogOut, CreditCard, Briefcase } from 'lucide-react-native';
import { CompositeNavigationProp } from '@react-navigation/native';
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
@@ -85,6 +85,23 @@ export const SettingsScreen: React.FC = ({ navigation }) =>
+ navigation.navigate('ManageServices')}>
+
+
+
+
+
+
+ Manage Services
+
+
+ Add or edit your offered services
+
+
+
+
+
+
diff --git a/src/services/profileService.ts b/src/services/profileService.ts
index fb356b6..8e16439 100644
--- a/src/services/profileService.ts
+++ b/src/services/profileService.ts
@@ -137,6 +137,8 @@ export const profileService = {
}
},
+
+
addSkills: async (skills: string[]): Promise => {
try {
const response = await apiClient.post('/v1/resource/caregiver/skills/add', { skills });
diff --git a/src/services/serviceService.ts b/src/services/serviceService.ts
new file mode 100644
index 0000000..21b17c5
--- /dev/null
+++ b/src/services/serviceService.ts
@@ -0,0 +1,83 @@
+import { apiClient } from './apiClient';
+
+export interface Service {
+ id?: number;
+ name: string;
+ care_type_slug?: string;
+ care_sub_type_slug?: string;
+ category?: string;
+ description: string;
+ unit?: string;
+ customer_price?: number | string;
+ caregiver_rate?: number | string;
+ tax?: number | string;
+ duration_minutes?: number | string;
+ is_active?: boolean;
+}
+
+export const serviceService = {
+ getServices: async (filters?: { skills?: string }): Promise => {
+ try {
+ const params = new URLSearchParams();
+ if (filters?.skills) {
+ params.append('skills', filters.skills);
+ }
+ const queryString = params.toString() ? `?${params.toString()}` : '';
+ const response = await apiClient.get(`/v1/caregiver/services${queryString}`);
+ return response.data?.data || response.data || [];
+ } catch (error) {
+ console.error('Error fetching services:', error);
+ throw error;
+ }
+ },
+
+ getTaxes: async (): Promise => {
+ try {
+ const response = await apiClient.get('/v1/resource/taxes');
+ return response.data?.data?.standard_tax_rates || [];
+ } catch (error) {
+ console.error('Error fetching taxes:', error);
+ throw error;
+ }
+ },
+
+ getServiceById: async (id: number): Promise => {
+ try {
+ const response = await apiClient.get(`/v1/caregiver/services/${id}`);
+ return response.data?.data || response.data;
+ } catch (error) {
+ console.error(`Error fetching service with id ${id}:`, error);
+ throw error;
+ }
+ },
+
+ addService: async (data: Service): Promise => {
+ try {
+ const response = await apiClient.post('/v1/caregiver/services', data);
+ return response.data;
+ } catch (error) {
+ console.error('Error adding service:', error);
+ throw error;
+ }
+ },
+
+ updateService: async (id: number, data: Partial): Promise => {
+ try {
+ const response = await apiClient.patch(`/v1/caregiver/services/${id}`, data);
+ return response.data;
+ } catch (error) {
+ console.error(`Error updating service with id ${id}:`, error);
+ throw error;
+ }
+ },
+
+ deleteService: async (id: number): Promise => {
+ try {
+ const response = await apiClient.delete(`/v1/caregiver/services/${id}`);
+ return response.data;
+ } catch (error) {
+ console.error(`Error deleting service with id ${id}:`, error);
+ throw error;
+ }
+ }
+};