Initial commit: Eventify frontend

This commit is contained in:
Rishad7594
2026-01-31 15:23:18 +05:30
commit b41cf6cc58
193 changed files with 12401 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
class UserModel {
final String username;
final String email;
final String role;
final String token;
final String? phoneNumber;
UserModel({
required this.username,
required this.email,
required this.role,
required this.token,
this.phoneNumber,
});
/// Defensive factory: uses defaults when keys are missing
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
username: (json['username'] ?? json['email'] ?? '').toString(),
email: (json['email'] ?? '').toString(),
role: (json['role'] ?? 'user').toString(),
token: (json['token'] ?? '').toString(),
phoneNumber: json['phone_number'] != null ? json['phone_number'].toString() : null,
);
}
Map<String, dynamic> toJson() {
return {
'username': username,
'email': email,
'role': role,
'token': token,
if (phoneNumber != null) 'phone_number': phoneNumber,
};
}
}

View File

@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import '../models/user_model.dart';
import '../services/auth_service.dart';
class AuthProvider extends ChangeNotifier {
final AuthService _authService = AuthService();
UserModel? _user;
bool _loading = false;
UserModel? get user => _user;
bool get loading => _loading;
/// Login with username/email and password.
Future<void> login(String username, String password) async {
_loading = true;
notifyListeners();
try {
final user = await _authService.login(username, password);
_user = user;
} finally {
_loading = false;
notifyListeners();
}
}
/// Register wrapper that is compatible with both old and new callers.
///
/// Preferred: provide [email], [phoneNumber], [password].
/// Older callers that pass [username] will still work — username is ignored
/// for the backend call (we use email/phone/password as per API spec).
Future<void> register({
String? username, // kept for backward compatibility; not sent to backend
String? email,
required String password,
String? phoneNumber,
}) async {
_loading = true;
notifyListeners();
try {
// Prefer explicit email param; if not provided try to use username (some screens passed email as username)
final resolvedEmail = (email != null && email.isNotEmpty) ? email : (username ?? '');
// Ensure we always pass a string phoneNumber to the AuthService (it expects a non-null value)
final resolvedPhone = phoneNumber ?? '';
final user = await _authService.register(
email: resolvedEmail,
phoneNumber: resolvedPhone,
password: password,
);
_user = user;
} finally {
_loading = false;
notifyListeners();
}
}
Future<void> logout() async {
await _authService.logout();
_user = null;
notifyListeners();
}
}

View File

@@ -0,0 +1,141 @@
// lib/features/auth/services/auth_service.dart
import 'package:flutter/foundation.dart' show kDebugMode, debugPrint;
import 'package:shared_preferences/shared_preferences.dart';
import '../../../core/api/api_client.dart';
import '../../../core/api/api_endpoints.dart';
import '../../../core/storage/token_storage.dart';
import '../models/user_model.dart';
class AuthService {
final ApiClient _api = ApiClient();
/// LOGIN → returns UserModel
Future<UserModel> login(String username, String password) async {
try {
final res = await _api.post(
ApiEndpoints.login,
body: {
"username": username,
"password": password,
},
requiresAuth: false,
);
final token = res['token'];
if (token == null) {
throw Exception('Token missing from response');
}
final serverUsername = (res['username'] is String && (res['username'] as String).isNotEmpty) ? res['username'] as String : null;
final serverEmail = (res['email'] is String && (res['email'] as String).isNotEmpty) ? res['email'] as String : null;
// savedEmail is the canonical identifier for this account
final savedEmail = serverEmail ?? username;
// candidate display name (server username or email fallback)
final displayCandidate = serverUsername ?? savedEmail;
// save token (TokenStorage stays responsible for token)
await TokenStorage.saveToken(token.toString(), savedEmail);
// persist per-account fields in SharedPreferences
final prefs = await SharedPreferences.getInstance();
// mark current logged-in account
await prefs.setString('current_email', savedEmail);
// always keep a canonical 'email' pointing to current user's email for old callers
await prefs.setString('email', savedEmail);
// save per-account display name (do not overwrite an explicit per-account display name if already set)
final perKey = 'display_name_$savedEmail';
final existingPer = prefs.getString(perKey);
if (existingPer == null || existingPer.trim().isEmpty) {
await prefs.setString(perKey, displayCandidate);
}
// save server-provided email (if any) separately too
if (serverEmail != null) await prefs.setString('server_email', serverEmail);
// Save phone if provided (optional)
if (res['phone_number'] != null) await prefs.setString('phone_number', res['phone_number'].toString());
return UserModel.fromJson(res);
} catch (e) {
if (kDebugMode) debugPrint('AuthService.login error: $e');
rethrow;
}
}
/// REGISTER → returns UserModel
Future<UserModel> register({
required String email,
required String phoneNumber,
required String password,
}) async {
try {
final res = await _api.post(
ApiEndpoints.register,
body: {
"email": email,
"phone_number": phoneNumber,
"password": password,
},
requiresAuth: false,
);
final token = res['token'];
if (token == null) {
throw Exception('Token missing from response');
}
final serverUsername = (res['username'] is String && (res['username'] as String).isNotEmpty) ? res['username'] as String : null;
final serverEmail = (res['email'] is String && (res['email'] as String).isNotEmpty) ? res['email'] as String : null;
final savedEmail = serverEmail ?? email;
final savedUsername = serverUsername ?? savedEmail;
final savedRole = (res['role'] ?? 'user').toString();
final savedPhone = (res['phone_number'] ?? phoneNumber)?.toString();
// Save token + canonical user id for token storage
await TokenStorage.saveToken(token.toString(), savedEmail);
// Persist per-account keys
final prefs = await SharedPreferences.getInstance();
await prefs.setString('current_email', savedEmail);
await prefs.setString('email', savedEmail);
// Set display_name_<email> if not present yet
final perKey = 'display_name_$savedEmail';
final existing = prefs.getString(perKey);
if (existing == null || existing.trim().isEmpty) {
await prefs.setString(perKey, savedUsername);
}
// Optionally save phone/email in prefs
await prefs.setString('server_email', savedEmail);
if (savedPhone != null) await prefs.setString('phone_number', savedPhone);
// Return a simple UserModel (defensive)
return UserModel(
username: savedUsername,
email: savedEmail,
role: savedRole,
token: token.toString(),
phoneNumber: savedPhone,
);
} catch (e) {
if (kDebugMode) debugPrint('AuthService.register error: $e');
rethrow;
}
}
/// Logout clear auth token and current_email (keep per-account display_name entries so they persist)
Future<void> logout() async {
try {
final prefs = await SharedPreferences.getInstance();
// clear token storage (should delete token + auth headers)
await TokenStorage.clear();
// remove current_email marker so no previous account remains current
await prefs.remove('current_email');
// Also remove canonical 'email' pointing to current user
await prefs.remove('email');
// Do not delete display_name_<email> entries — they are per-account and should remain on device.
} catch (e) {
if (kDebugMode) debugPrint('AuthService.logout warning: $e');
}
}
}