@@ -0,0 +1,3 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
@@ -1,7 +1,7 @@
|
||||
/// User model
|
||||
class User {
|
||||
final int id;
|
||||
final String userCode;
|
||||
final int userCode;
|
||||
final String? job;
|
||||
|
||||
User({
|
||||
@@ -11,9 +11,20 @@ class User {
|
||||
});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
// userCode may be provided as 'userCode' or 'usercode' and may be int or string
|
||||
final dynamic rawUserCode = json['userCode'] ?? json['usercode'] ?? json['user_code'];
|
||||
int parsedUserCode;
|
||||
if (rawUserCode is int) {
|
||||
parsedUserCode = rawUserCode;
|
||||
} else if (rawUserCode is String) {
|
||||
parsedUserCode = int.tryParse(rawUserCode) ?? (throw FormatException('Invalid userCode'));
|
||||
} else {
|
||||
throw FormatException('Missing userCode');
|
||||
}
|
||||
|
||||
return User(
|
||||
id: json['id'] as int,
|
||||
userCode: json['userCode'] as String,
|
||||
userCode: parsedUserCode,
|
||||
job: json['job'] as String?,
|
||||
);
|
||||
}
|
||||
@@ -30,17 +41,44 @@ class User {
|
||||
/// Login response model
|
||||
class AuthResponse {
|
||||
final String token;
|
||||
final User user;
|
||||
final User? user;
|
||||
|
||||
AuthResponse({
|
||||
required this.token,
|
||||
required this.user,
|
||||
this.user,
|
||||
});
|
||||
|
||||
factory AuthResponse.fromJson(Map<String, dynamic> json) {
|
||||
final dynamic rawToken = json['token'] ?? json['Token'] ?? json['authToken'];
|
||||
final token = rawToken?.toString();
|
||||
if (token == null || token.isEmpty) throw FormatException('Missing token in auth response');
|
||||
|
||||
// Determine if a user object exists and appears valid
|
||||
Map<String, dynamic>? userMap;
|
||||
if (json.containsKey('user') && json['user'] is Map<String, dynamic>) {
|
||||
userMap = json['user'] as Map<String, dynamic>;
|
||||
} else {
|
||||
// maybe user fields are top-level (id, userCode, job)
|
||||
final hasUserFields = json.containsKey('id') || json.containsKey('userCode') || json.containsKey('usercode') || json.containsKey('user_code');
|
||||
if (hasUserFields) {
|
||||
// treat whole json as user map
|
||||
userMap = json.cast<String, dynamic>();
|
||||
}
|
||||
}
|
||||
|
||||
User? parsedUser;
|
||||
if (userMap != null) {
|
||||
try {
|
||||
parsedUser = User.fromJson(userMap);
|
||||
} catch (e) {
|
||||
// If parsing user fails, ignore user but keep token
|
||||
parsedUser = null;
|
||||
}
|
||||
}
|
||||
|
||||
return AuthResponse(
|
||||
token: json['token'] as String,
|
||||
user: User.fromJson(json['user'] as Map<String, dynamic>),
|
||||
token: token,
|
||||
user: parsedUser,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,14 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
});
|
||||
|
||||
try {
|
||||
final input = _userCodeController.text.trim();
|
||||
final parsedUserCode = int.tryParse(input);
|
||||
if (parsedUserCode == null) {
|
||||
throw Exception('User code must be a number');
|
||||
}
|
||||
|
||||
final response = await _authService.login(
|
||||
userCode: _userCodeController.text.trim(),
|
||||
userCode: parsedUserCode,
|
||||
password: _passwordController.text,
|
||||
);
|
||||
|
||||
@@ -57,9 +63,13 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
}
|
||||
|
||||
String? _validateUserCode(String? value) {
|
||||
if (value?.isEmpty ?? true) {
|
||||
final v = value?.trim();
|
||||
if (v?.isEmpty ?? true) {
|
||||
return 'User code is required';
|
||||
}
|
||||
if (int.tryParse(v!) == null) {
|
||||
return 'User code must be a number';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/user.dart';
|
||||
@@ -6,7 +7,7 @@ import '../models/user.dart';
|
||||
/// Authentication Service
|
||||
class AuthService {
|
||||
// TODO: Implement API connector - configure base URL from env/config
|
||||
static const String _apiBaseUrl = 'http://localhost:5000';
|
||||
static const String _apiBaseUrl = 'http://192.168.2.66:5000';
|
||||
static const String _tokenKey = 'auth_token';
|
||||
|
||||
late SharedPreferences _prefs;
|
||||
@@ -17,7 +18,7 @@ class AuthService {
|
||||
|
||||
/// Login with userCode and password
|
||||
Future<AuthResponse> login({
|
||||
required String userCode,
|
||||
required int userCode,
|
||||
required String password,
|
||||
}) async {
|
||||
await _initPrefs();
|
||||
@@ -33,13 +34,19 @@ class AuthService {
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final authResponse = AuthResponse.fromJson(data);
|
||||
final body = response.body;
|
||||
try {
|
||||
final data = jsonDecode(body) as Map<String, dynamic>;
|
||||
final authResponse = AuthResponse.fromJson(data);
|
||||
|
||||
// Store token
|
||||
await _prefs.setString(_tokenKey, authResponse.token);
|
||||
// Store token
|
||||
await _prefs.setString(_tokenKey, authResponse.token);
|
||||
|
||||
return authResponse;
|
||||
return authResponse;
|
||||
} catch (e) {
|
||||
// Surface the response body to help debug backend field names / formats
|
||||
throw Exception('Login parse error: $e -- response body: $body');
|
||||
}
|
||||
} else if (response.statusCode == 401) {
|
||||
throw Exception('Invalid credentials');
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,8 @@ import 'auth_service.dart';
|
||||
/// Schedule/Route Service
|
||||
class ScheduleService {
|
||||
// TODO: Implement API connector - configure base URL from env/config
|
||||
static const String _apiBaseUrl = 'http://localhost:5000';
|
||||
// Transit API runs on port 5001
|
||||
static const String _apiBaseUrl = 'http://192.168.2.66:5001';
|
||||
final _authService = AuthService();
|
||||
|
||||
/// Get daily schedule for a specific date
|
||||
@@ -43,8 +44,9 @@ class ScheduleService {
|
||||
try {
|
||||
final headers = await _authService.getAuthHeaders();
|
||||
|
||||
// Transit API exposes route details at /routes/:routeId
|
||||
final response = await http.get(
|
||||
Uri.parse('$_apiBaseUrl/schedule/route/$routeId'),
|
||||
Uri.parse('$_apiBaseUrl/routes/$routeId'),
|
||||
headers: headers,
|
||||
);
|
||||
|
||||
@@ -66,8 +68,9 @@ class ScheduleService {
|
||||
try {
|
||||
final headers = await _authService.getAuthHeaders();
|
||||
|
||||
// Transit API updates status at /routes/:routeId/status
|
||||
final response = await http.patch(
|
||||
Uri.parse('$_apiBaseUrl/schedule/route/$routeId/status'),
|
||||
Uri.parse('$_apiBaseUrl/routes/$routeId/status'),
|
||||
headers: headers,
|
||||
body: jsonEncode({'status': status}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user