Skip to content

Commit 4b84337

Browse files
authored
feat(auth): implement secure registration flow and authentication fixes (#13)
* feat(core): add auth layout template, custom textfield and colors * feat(auth): implement viewmodels for auth flow (MVVM) * feat(auth): build complete auth UI screens (Login, Register, OTP, Passwords) * chore(main): set LoginView as initial route * refactor(auth) : delete .gitkeep * chore: update dependencies and pubspec.lock * refactor(auth): optimize registration logic, timezone handling, and form validation * feat(auth): update UI for login, registration, and forgot password screens * feat(tasks): update task management UI and statistics screen * chore: update main entry point and fix widget tests * chore: ignore devtools_options.yaml * chore: ignore devtools_options.yaml * style(login) : rewrite title for login view
1 parent a82c86a commit 4b84337

23 files changed

Lines changed: 1336 additions & 625 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,5 @@ app.*.map.json
5050
**/supabase/.env
5151
**/supabase/pooler/
5252

53+
# DevTools config
54+
devtools_options.yaml

src/devtools_options.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
description: This file stores settings for Dart & Flutter DevTools.
2+
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3+
extensions:
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// data/helpers/auth_helper.dart
2+
3+
import 'package:supabase_flutter/supabase_flutter.dart';
4+
import 'package:flutter_timezone/flutter_timezone.dart';
5+
import '../models/user_model.dart';
6+
import 'package:task_management_app/main.dart';
7+
8+
class AuthHelper {
9+
10+
11+
Future<UserModel?> login(String email, String password) async {
12+
try {
13+
final response = await supabase.auth.signInWithPassword(
14+
email: email,
15+
password: password,
16+
);
17+
18+
if (response.user != null) {
19+
final userId = response.user!.id;
20+
21+
final timezoneObj = await FlutterTimezone.getLocalTimezone();
22+
final String currentTimezone = timezoneObj.toString();
23+
24+
final profileData = await supabase
25+
.from('profile')
26+
.update({'timezone': currentTimezone})
27+
.eq('id', userId)
28+
.select()
29+
.single();
30+
31+
return UserModel.fromJson(profileData, email);
32+
}
33+
return null;
34+
} on AuthException catch (e) {
35+
print('Supabase Auth Error: ${e.message}');
36+
rethrow;
37+
} catch (e) {
38+
print('Unknown Error: $e');
39+
rethrow;
40+
}
41+
}
42+
43+
Future<UserModel?> register(String email, String password, String username) async {
44+
try {
45+
final timezoneObj = await FlutterTimezone.getLocalTimezone();
46+
final String currentTimezone = timezoneObj.toString();
47+
final response = await supabase.auth.signUp(
48+
email: email,
49+
password: password,
50+
data: {
51+
'username': username,
52+
'timezone': currentTimezone, // Data is already a safe String
53+
}
54+
);
55+
56+
if (response.user != null) {
57+
final userId = response.user!.id;
58+
59+
// Step 2: Insert directly into the 'profile' table
60+
final profileData = await supabase
61+
.from('profile')
62+
.select()
63+
.eq('id', userId)
64+
.single();
65+
66+
return UserModel.fromJson(profileData, email);
67+
}
68+
return null;
69+
} on AuthException catch (e) {
70+
print('Supabase Sign Up Error: ${e.message}');
71+
rethrow;
72+
}
73+
}
74+
75+
Future<void> sendPasswordReset(String email) async {
76+
try {
77+
await supabase.auth.resetPasswordForEmail(email);
78+
} on AuthException catch (e) {
79+
print('Lỗi Gửi OTP: ${e.message}');
80+
rethrow;
81+
}
82+
}
83+
84+
Future<bool> verifyOTP(String email, String otpCode) async {
85+
try {
86+
// Authenticate OTP for recovery type
87+
final response = await supabase.auth.verifyOTP(
88+
email: email,
89+
token: otpCode,
90+
type: OtpType.recovery,
91+
);
92+
// Returns true if authentication is successful and session is created
93+
return response.session != null;
94+
} on AuthException catch (e) {
95+
print('Lỗi Xác thực OTP: ${e.message}');
96+
return false;
97+
}
98+
}
99+
Future<void> updatePassword(String newPassword) async {
100+
try {
101+
// After successful verifyOTP, user is automatically logged in
102+
// At this point, just call updateUser to change the password
103+
await supabase.auth.updateUser(UserAttributes(password: newPassword));
104+
} on AuthException catch (e) {
105+
print('Lỗi Đổi Pass: ${e.message}');
106+
rethrow;
107+
}
108+
}
109+
}

src/lib/features/auth/forgot_password_view.dart

Lines changed: 0 additions & 86 deletions
This file was deleted.

src/lib/features/auth/login_view.dart

Lines changed: 0 additions & 124 deletions
This file was deleted.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
class UserModel {
2+
final String id;
3+
final String email;
4+
final String username;
5+
final int? age;
6+
final String? avatar;
7+
final String? timezone;
8+
9+
UserModel({
10+
required this.id,
11+
required this.email,
12+
required this.username,
13+
this.age,
14+
this.avatar,
15+
this.timezone,
16+
});
17+
18+
factory UserModel.fromJson(Map<String, dynamic> json, String email) {
19+
return UserModel(
20+
id: json['id'] ?? '',
21+
email: email,
22+
username: json['username'] ?? 'No Name',
23+
age: json['age'],
24+
avatar: json['avatar'],
25+
timezone: json['timezone'],
26+
);
27+
}
28+
}

0 commit comments

Comments
 (0)