import 'package:awesome_dio_interceptor/awesome_dio_interceptor.dart'; import 'package:dio/dio.dart'; import 'package:enaklo_pos/core/constants/variables.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/data/models/response/auth_response_model.dart'; import 'package:enaklo_pos/presentation/auth/login_page.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; class DioClient { static final Dio _dio = Dio(BaseOptions( connectTimeout: const Duration(minutes: 1), receiveTimeout: const Duration(minutes: 1), headers: { 'Accept': 'application/json', }, )) ..interceptors.add(AuthInterceptor()) ..interceptors.add( AwesomeDioInterceptor( logRequestTimeout: true, logRequestHeaders: true, logResponseHeaders: true, ), ); static Dio get instance => _dio; } class AuthInterceptor extends Interceptor { static final GlobalKey navigatorKey = GlobalKey(); @override void onRequest( RequestOptions options, RequestInterceptorHandler handler) async { // Add token to request headers final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('auth_token'); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; } handler.next(options); } @override void onResponse(Response response, ResponseInterceptorHandler handler) { handler.next(response); } @override void onError(DioException err, ErrorInterceptorHandler handler) async { // Check if error is 401 (Unauthorized) - token expired if (err.response?.statusCode == 401) { await _handleTokenExpired(); } handler.next(err); } Future _handleTokenExpired() async { // Clear stored token final prefs = await SharedPreferences.getInstance(); await prefs.remove('auth_token'); await prefs.remove('refresh_token'); await prefs.clear(); // Optional: clear all user data // Navigate to login page final context = navigatorKey.currentContext; if (context != null) { // Option 1: Navigate and remove all previous routes context.pushReplacement(LoginPage()); // Option 2: If using GoRouter, uncomment below: // GoRouter.of(context).go('/login'); // Option 3: If using custom routing, uncomment below: // Navigator.of(context).pushAndRemoveUntil( // MaterialPageRoute(builder: (context) => LoginPage()), // (route) => false, // ); } } } // Alternative: Dengan Refresh Token Logic class AuthInterceptorWithRefresh extends Interceptor { static final GlobalKey navigatorKey = GlobalKey(); @override void onRequest( RequestOptions options, RequestInterceptorHandler handler) async { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('auth_token'); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; } handler.next(options); } @override void onError(DioException err, ErrorInterceptorHandler handler) async { if (err.response?.statusCode == 401) { // Try refresh token first final success = await _tryRefreshToken(); if (success) { // Retry the original request final response = await _retryRequest(err.requestOptions); handler.resolve(response); return; } else { // Refresh failed, redirect to login await _handleTokenExpired(); } } handler.next(err); } Future _tryRefreshToken() async { try { final prefs = await SharedPreferences.getInstance(); final authData = await AuthLocalDataSource().getAuthData(); final url = '${Variables.baseUrl}/api/v1/auth/refresh'; final response = await Dio().post( url, options: Options( headers: { 'Authorization': 'Bearer ${authData.refreshToken}', }, ), ); if (response.statusCode == 200) { final newToken = response.data['access_token']; final newRefreshToken = response.data['refresh_token']; await prefs.setString('auth_token', newToken); await prefs.setString('refresh_token', newRefreshToken); AuthResponseModel authResponseModel = AuthResponseModel.fromMap(response.data['data']); AuthLocalDataSource().saveAuthData(authResponseModel); return true; } } catch (e) { print('Refresh token failed: $e'); } return false; } Future _retryRequest(RequestOptions options) async { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('auth_token'); options.headers['Authorization'] = 'Bearer $token'; return await DioClient.instance.request( options.path, options: Options( method: options.method, headers: options.headers, ), data: options.data, queryParameters: options.queryParameters, ); } Future _handleTokenExpired() async { final prefs = await SharedPreferences.getInstance(); await prefs.clear(); final context = navigatorKey.currentContext; if (context != null) { Navigator.of(context).pushNamedAndRemoveUntil( '/login', (route) => false, ); } } }