mirror of
https://github.com/we-promise/sure.git
synced 2026-04-10 07:44:48 +00:00
* feat: Add API key login option to mobile app Add a "Via API Key Login" button on the login screen that opens a dialog for entering an API key. The API key is validated by making a test request to /api/v1/accounts with the X-Api-Key header, and on success is persisted in secure storage. All HTTP services now use a centralized ApiConfig.getAuthHeaders() helper that returns the correct auth header (X-Api-Key or Bearer) based on the current auth mode. https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH * fix: Improve API key dialog context handling and controller disposal - Use outer context for SnackBar so it displays on the main screen instead of behind the dialog - Explicitly dispose TextEditingController to prevent memory leaks - Close dialog on failure before showing error SnackBar for better UX - Avoid StatefulBuilder context parameter shadowing https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH * fix: Use user-friendly error message in API key login catch block Log the technical exception details via LogService.instance.error and show a generic "Unable to connect" message to the user instead of exposing the raw exception string. https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH --------- Co-authored-by: Claude <noreply@anthropic.com>
55 lines
1.4 KiB
Dart
55 lines
1.4 KiB
Dart
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
import '../models/account.dart';
|
|
import 'api_config.dart';
|
|
|
|
class AccountsService {
|
|
Future<Map<String, dynamic>> getAccounts({
|
|
required String accessToken,
|
|
int page = 1,
|
|
int perPage = 25,
|
|
}) async {
|
|
try {
|
|
final url = Uri.parse(
|
|
'${ApiConfig.baseUrl}/api/v1/accounts?page=$page&per_page=$perPage',
|
|
);
|
|
|
|
final response = await http.get(
|
|
url,
|
|
headers: ApiConfig.getAuthHeaders(accessToken),
|
|
).timeout(const Duration(seconds: 30));
|
|
|
|
if (response.statusCode == 200) {
|
|
final responseData = jsonDecode(response.body);
|
|
|
|
final accountsList = (responseData['accounts'] as List)
|
|
.map((json) => Account.fromJson(json))
|
|
.toList();
|
|
|
|
return {
|
|
'success': true,
|
|
'accounts': accountsList,
|
|
'pagination': responseData['pagination'],
|
|
};
|
|
} else if (response.statusCode == 401) {
|
|
return {
|
|
'success': false,
|
|
'error': 'unauthorized',
|
|
'message': 'Session expired. Please login again.',
|
|
};
|
|
} else {
|
|
final responseData = jsonDecode(response.body);
|
|
return {
|
|
'success': false,
|
|
'error': responseData['error'] ?? 'Failed to fetch accounts',
|
|
};
|
|
}
|
|
} catch (e) {
|
|
return {
|
|
'success': false,
|
|
'error': 'Network error: ${e.toString()}',
|
|
};
|
|
}
|
|
}
|
|
}
|