first app design
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import '../models/analytics.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class AnalyticsService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
Future<DashboardMetrics> getDashboard() async {
|
||||
final res = await _client.get('/analytics/dashboard');
|
||||
return DashboardMetrics.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import '../constants/app_constants.dart';
|
||||
import '../storage/token_storage.dart';
|
||||
|
||||
class ApiClient {
|
||||
static final ApiClient _instance = ApiClient._();
|
||||
late final Dio _dio;
|
||||
|
||||
ApiClient._() {
|
||||
_dio = Dio(BaseOptions(
|
||||
baseUrl: AppConstants.baseUrl,
|
||||
connectTimeout: const Duration(seconds: 10),
|
||||
receiveTimeout: const Duration(seconds: 30),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
));
|
||||
|
||||
_dio.interceptors.add(InterceptorsWrapper(
|
||||
onRequest: (options, handler) async {
|
||||
final token = await TokenStorage.getToken();
|
||||
if (token != null) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
handler.next(options);
|
||||
},
|
||||
onResponse: (response, handler) {
|
||||
// Every successful response from this API is wrapped: { data: <payload> }
|
||||
// Unwrap it so services always receive the raw payload.
|
||||
final body = response.data;
|
||||
if (body is Map<String, dynamic> &&
|
||||
body.length == 1 &&
|
||||
body.containsKey('data')) {
|
||||
response.data = body['data'];
|
||||
}
|
||||
handler.next(response);
|
||||
},
|
||||
onError: (e, handler) {
|
||||
handler.next(e);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
static ApiClient get instance => _instance;
|
||||
|
||||
// baseUrl is dynamic (platform-aware), so recreate when it may have changed.
|
||||
// In practice this is called once and cached via the singleton.
|
||||
Dio get dio => _dio;
|
||||
|
||||
Future<Response> get(String path, {Map<String, dynamic>? params}) =>
|
||||
_dio.get(path, queryParameters: params);
|
||||
|
||||
Future<Response> post(String path, {dynamic data}) =>
|
||||
_dio.post(path, data: data);
|
||||
|
||||
Future<Response> patch(String path, {dynamic data}) =>
|
||||
_dio.patch(path, data: data);
|
||||
|
||||
Future<Response> delete(String path) => _dio.delete(path);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import '../models/auth_models.dart';
|
||||
import '../storage/token_storage.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class AuthService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
// Response after envelope unwrap: { token, employee: { id, firstName, ... } }
|
||||
Future<({String token, Employee employee})> loginEmployee({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
final res = await _client.post('/auth/employee/login', data: {
|
||||
'email': email,
|
||||
'password': password,
|
||||
});
|
||||
final data = res.data as Map<String, dynamic>;
|
||||
final token = data['token'] as String;
|
||||
final employee =
|
||||
Employee.fromJson(data['employee'] as Map<String, dynamic>);
|
||||
return (token: token, employee: employee);
|
||||
}
|
||||
|
||||
// Response after envelope unwrap: { employee: { id, firstName, ... } }
|
||||
Future<Employee> getMe() async {
|
||||
final res = await _client.get('/auth/employee/me');
|
||||
final data = res.data as Map<String, dynamic>;
|
||||
return Employee.fromJson(data['employee'] as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Renter> getRenterMe() async {
|
||||
final res = await _client.get('/auth/renter/me');
|
||||
final data = res.data as Map<String, dynamic>;
|
||||
return Renter.fromJson(data);
|
||||
}
|
||||
|
||||
Future<void> logout() => TokenStorage.clear();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import '../models/customer.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class CustomerService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
Future<CustomerListResponse> getCustomers({
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
String? search,
|
||||
}) async {
|
||||
final res = await _client.get('/customers', params: {
|
||||
'page': page,
|
||||
'pageSize': pageSize,
|
||||
if (search != null && search.isNotEmpty) 'search': search,
|
||||
});
|
||||
return CustomerListResponse.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Customer> getCustomer(String id) async {
|
||||
final res = await _client.get('/customers/$id');
|
||||
return Customer.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Customer> createCustomer(Map<String, dynamic> data) async {
|
||||
final res = await _client.post('/customers', data: data);
|
||||
return Customer.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import '../models/marketplace.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class MarketplaceService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
// After envelope unwrap: [ "City1", "City2", ... ]
|
||||
Future<List<String>> getCities() async {
|
||||
final res = await _client.get('/marketplace/cities');
|
||||
final data = res.data;
|
||||
if (data is List) {
|
||||
return data.whereType<String>().toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// After envelope unwrap: [ { id, title, ... }, ... ]
|
||||
Future<List<MarketplaceOffer>> getOffers() async {
|
||||
final res = await _client.get('/marketplace/offers');
|
||||
final data = res.data;
|
||||
if (data is List) {
|
||||
return data
|
||||
.map((e) => MarketplaceOffer.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// After envelope unwrap: [ { id, make, model, ... }, ... ]
|
||||
Future<MarketplaceSearchResult> search({
|
||||
String? city,
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
String? category,
|
||||
String? transmission,
|
||||
String? make,
|
||||
int? maxPriceCents,
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
final res = await _client.get('/marketplace/search', params: {
|
||||
'page': page,
|
||||
'pageSize': pageSize,
|
||||
if (city != null && city.isNotEmpty) 'city': city,
|
||||
if (startDate != null)
|
||||
'startDate': '${startDate.toIso8601String().substring(0, 10)}T00:00:00.000Z',
|
||||
if (endDate != null)
|
||||
'endDate': '${endDate.toIso8601String().substring(0, 10)}T00:00:00.000Z',
|
||||
if (category != null) 'category': category,
|
||||
if (transmission != null) 'transmission': transmission,
|
||||
if (make != null && make.isNotEmpty) 'make': make,
|
||||
if (maxPriceCents != null) 'maxPrice': maxPriceCents,
|
||||
});
|
||||
|
||||
final data = res.data;
|
||||
if (data is List) {
|
||||
final vehicles = data
|
||||
.map((e) => MarketplaceVehicle.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
return MarketplaceSearchResult(
|
||||
data: vehicles,
|
||||
total: vehicles.length,
|
||||
page: page,
|
||||
pageSize: pageSize,
|
||||
);
|
||||
}
|
||||
// Fallback: paginated object shape { data: [...], total: N }
|
||||
return MarketplaceSearchResult.fromJson(data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<String> createReservation({
|
||||
required String vehicleId,
|
||||
required String companySlug,
|
||||
required String firstName,
|
||||
required String lastName,
|
||||
required String email,
|
||||
String? phone,
|
||||
required DateTime startDate,
|
||||
required DateTime endDate,
|
||||
String? notes,
|
||||
String language = 'en',
|
||||
}) async {
|
||||
final res = await _client.post('/marketplace/reservations', data: {
|
||||
'vehicleId': vehicleId,
|
||||
'companySlug': companySlug,
|
||||
'firstName': firstName,
|
||||
'lastName': lastName,
|
||||
'email': email,
|
||||
if (phone != null && phone.isNotEmpty) 'phone': phone,
|
||||
'startDate': '${startDate.toIso8601String().substring(0, 10)}T00:00:00.000Z',
|
||||
'endDate': '${endDate.toIso8601String().substring(0, 10)}T00:00:00.000Z',
|
||||
if (notes != null && notes.isNotEmpty) 'notes': notes,
|
||||
'language': language,
|
||||
});
|
||||
final data = res.data as Map<String, dynamic>;
|
||||
return data['reservationId'] as String;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import '../models/reservation.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class ReservationService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
Future<ReservationListResponse> getReservations({
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
String? status,
|
||||
String? search,
|
||||
}) async {
|
||||
final res = await _client.get('/reservations', params: {
|
||||
'page': page,
|
||||
'pageSize': pageSize,
|
||||
if (status != null) 'status': status,
|
||||
if (search != null && search.isNotEmpty) 'search': search,
|
||||
});
|
||||
return ReservationListResponse.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Reservation> getReservation(String id) async {
|
||||
final res = await _client.get('/reservations/$id');
|
||||
return Reservation.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Reservation> createReservation(Map<String, dynamic> data) async {
|
||||
final res = await _client.post('/reservations', data: data);
|
||||
return Reservation.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> confirmReservation(String id) async {
|
||||
await _client.post('/reservations/$id/confirm');
|
||||
}
|
||||
|
||||
Future<void> cancelReservation(String id, String reason) async {
|
||||
await _client.post('/reservations/$id/cancel', data: {'reason': reason});
|
||||
}
|
||||
|
||||
Future<void> checkin(String id, int mileage) async {
|
||||
await _client.post('/reservations/$id/checkin', data: {'mileage': mileage});
|
||||
}
|
||||
|
||||
Future<void> checkout(String id, int mileage) async {
|
||||
await _client.post('/reservations/$id/checkout', data: {'mileage': mileage});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import '../models/vehicle.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class VehicleService {
|
||||
final _client = ApiClient.instance;
|
||||
|
||||
Future<VehicleListResponse> getVehicles({
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
String? status,
|
||||
String? search,
|
||||
}) async {
|
||||
final res = await _client.get('/vehicles', params: {
|
||||
'page': page,
|
||||
'pageSize': pageSize,
|
||||
if (status != null) 'status': status,
|
||||
if (search != null && search.isNotEmpty) 'search': search,
|
||||
});
|
||||
return VehicleListResponse.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Vehicle> getVehicle(String id) async {
|
||||
final res = await _client.get('/vehicles/$id');
|
||||
return Vehicle.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Vehicle> createVehicle(Map<String, dynamic> data) async {
|
||||
final res = await _client.post('/vehicles', data: data);
|
||||
return Vehicle.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Vehicle> updateVehicle(String id, Map<String, dynamic> data) async {
|
||||
final res = await _client.patch('/vehicles/$id', data: data);
|
||||
return Vehicle.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> togglePublish(String id, bool publish) async {
|
||||
await _client.patch('/vehicles/$id/publish', data: {'isPublished': publish});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user