import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../auth/auth_state.dart'; const _baseUrl = 'http://localhost:8080/api/v1'; /// Public Dio instance for unauthenticated calls (login / refresh) final _publicDio = Dio(BaseOptions( baseUrl: _baseUrl, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 30), headers: {'Content-Type': 'application/json'}, )); final apiClientProvider = Provider((ref) { final authState = ref.watch(authStateProvider); return ApiClient( token: authState.user?.accessToken, refreshToken: authState.user?.refreshToken, onTokenRefreshed: (newToken) => ref.read(authStateProvider.notifier).updateAccessToken(newToken), onAuthFailed: () => ref.read(authStateProvider.notifier).logout(), ); }); class ApiClient { late final Dio _dio; ApiClient({ String? token, String? refreshToken, void Function(String newToken)? onTokenRefreshed, void Function()? onAuthFailed, }) { _dio = Dio(BaseOptions( baseUrl: _baseUrl, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 30), headers: { 'Content-Type': 'application/json', if (token != null) 'Authorization': 'Bearer $token', }, )); // 401 auto-refresh interceptor if (refreshToken != null) { _dio.interceptors.add( InterceptorsWrapper( onError: (DioException e, ErrorInterceptorHandler handler) async { if (e.response?.statusCode == 401 && refreshToken.isNotEmpty) { try { final resp = await _publicDio.post('/auth/refresh', data: { 'refresh_token': refreshToken, }); final newToken = resp.data['data']['access_token'] as String; onTokenRefreshed?.call(newToken); // Retry original request with new token final opts = e.requestOptions; opts.headers['Authorization'] = 'Bearer $newToken'; final retryResp = await _dio.fetch(opts); return handler.resolve(retryResp); } catch (_) { onAuthFailed?.call(); } } return handler.next(e); }, ), ); } } Future get(String path, {Map? params}) => _dio.get(path, queryParameters: params); Future post(String path, {dynamic data}) => _dio.post(path, data: data); Future put(String path, {dynamic data}) => _dio.put(path, data: data); Future delete(String path) => _dio.delete(path); } /// Unauthenticated client for login/refresh class PublicApiClient { static Future post(String path, {dynamic data}) => _publicDio.post(path, data: data); }