/// Hand-rolled to be tolerant of the actual server payload: only [token] is /// load-bearing. `expiresAt` may be `null` (server-issued tokens without an /// explicit expiry); every other field shape is also tolerated so a stray /// rename on the backend does not break login for everyone. class LoginResponse { final String token; final String? tokenId; /// `null` when the backend did not provide an expiry. In that case the /// token is treated as long-lived; callers should refresh on 401. final DateTime? expiresAt; final ConnectUserDto? user; LoginResponse({ required this.token, required this.tokenId, required this.expiresAt, required this.user, }); factory LoginResponse.fromJson(Map json) { final token = json['token']; if (token is! String || token.isEmpty) { throw const FormatException('login response missing "token" string'); } final expiresRaw = json['expiresAt']; final expires = expiresRaw is String ? DateTime.tryParse(expiresRaw) : null; final userJson = json['user']; return LoginResponse( token: token, tokenId: json['tokenId']?.toString(), expiresAt: expires, user: userJson is Map ? ConnectUserDto.fromJson(userJson) : null, ); } } class ConnectUserDto { final String? id; final String? username; final String? firstName; final String? lastName; final String? userType; final String? className; ConnectUserDto({ this.id, this.username, this.firstName, this.lastName, this.userType, this.className, }); factory ConnectUserDto.fromJson(Map json) => ConnectUserDto( id: json['id']?.toString(), username: json['username']?.toString(), firstName: json['firstName']?.toString(), lastName: json['lastName']?.toString(), userType: json['userType']?.toString(), className: json['className']?.toString(), ); }