Skip to content

Commit

Permalink
signup optimize (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ida631 authored Nov 9, 2024
1 parent ea0afbd commit 2a88ed6
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 14 deletions.
38 changes: 36 additions & 2 deletions data/lib/repositories/auth_repository_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ class AuthRepositoryImpl implements AuthRepository {
AuthRepositoryImpl({required this.client, required this.authClient});

@override
Future<User> signup(String email, String password, {String? name}) async {
Future<User> signup(String email, String password, String verificationCode, {String? name}) async {
final url = Uri.parse('http://localhost:3000/api/users/');
final response = await client.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode(
{'email': email, 'password': password,
if (name != null) 'name': name,}),
'verificationCode': verificationCode, if (name != null) 'name': name,},),
);

if (response.statusCode == 200 || response.statusCode == 201) {
Expand Down Expand Up @@ -112,4 +112,38 @@ class AuthRepositoryImpl implements AuthRepository {
'Password reset failed with status code: ${response.statusCode}');
}
}

Future<void> sendSignupVerificationCode(String email) async {
final url =
Uri.parse('http://localhost:3000/api/auth/send-verification');
try {
final response = await client.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'email': email}),
);

if (response.statusCode == 200) {
// Request was successful, exit the function
return;
}

// Decode the server response to retrieve the error message
final responseData = jsonDecode(response.body);
final errorMessage = responseData['message'] ?? 'Failed to send verification email';

// Throw specific error based on the status code
if (response.statusCode == 400) {
throw ServerException('Invalid email address');
} else if (response.statusCode == 409) {
throw ServerException('Email is already registered');
} else {
throw ServerException(errorMessage);
}
// If statusCode is 200, assume the request was successful
} catch (error) {
// Re-throw network or parsing errors as custom exceptions
throw Exception('An error occurred. Please try again.');
}
}
}
3 changes: 2 additions & 1 deletion domain/lib/repositories_abstract/auth_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import '../entities/user.dart';

abstract class AuthRepository {
Future<User> signup(String email, String password, {String? name});
Future<User> signup(String email, String password, String verificationCode,{String? name});
Future<String> login(String email, String password);
Future<void> logout();
Future<void> forgetPassword(String email);
Future<String> resetPassword(String email, String newPassword, String confirmationCode);
Future<void> sendSignupVerificationCode(String email);
}
8 changes: 6 additions & 2 deletions domain/lib/usecases/auth_usecase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class AuthUseCase {

AuthUseCase({required this.repository, required this.tokenProvider});

Future<User> signup(String email, String password, {String? name}) async {
return await repository.signup(email, password, name: name);
Future<User> signup(String email, String password,String verificationCode,{String? name}) async {
return await repository.signup(email, password, verificationCode, name: name);
}

Future<String> login(String email, String password) async {
Expand Down Expand Up @@ -40,6 +40,10 @@ class AuthUseCase {
Future<String> resetPassword(email, newPassword, confirmationCode) async {
return await repository.resetPassword(email, newPassword, confirmationCode);
}

Future<void> sendSignupVerificationCode(String email) async {
return await repository.sendSignupVerificationCode(email);
}
}


35 changes: 33 additions & 2 deletions lib/presentation/settings/viewModels/signup_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class SignupViewModel extends ChangeNotifier {

bool obscureTextNewPassword = true;
bool obscureTextConfirmPassword = true;
bool isSignUp = false;


void toggleNewPasswordVisibility() {
obscureTextNewPassword = !obscureTextNewPassword;
Expand All @@ -30,13 +32,13 @@ class SignupViewModel extends ChangeNotifier {
String? _errorMessage;
String? get errorMessage => _errorMessage;

Future<User?> signup(String email, String password, {String? name}) async {
Future<User?> signup(String email, String password,String verificationCode, {String? name}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();

try {
User user = await authUseCase.signup(email, password, name: name);
User user = await authUseCase.signup(email, password, verificationCode, name: name);
return user;
} catch (e) {
_errorMessage = 'Signup failed: ${e.toString()}';
Expand All @@ -46,4 +48,33 @@ class SignupViewModel extends ChangeNotifier {
notifyListeners();
}
}

Future<void> continueSignUp(String email) async {
_setLoadingState(true);
_errorMessage = '';

try {
// Call the auth use case to send the confirmation code to the email
await authUseCase.sendSignupVerificationCode(email);
isSignUp = true;
} catch (error) {
final errorMessage = error.toString();
print("Error Message: $errorMessage");

if (errorMessage.contains("already registered")) {
_errorMessage = 'Email is already registered';
} else if (errorMessage.contains("Invalid email")) {
_errorMessage = 'Invalid email address';
} else {
_errorMessage = 'Failed to send verification code.';
}
} finally {
_setLoadingState(false);
}
}

void _setLoadingState(bool value) {
_isLoading = value;
notifyListeners();
}
}
4 changes: 2 additions & 2 deletions lib/presentation/settings/views/forget_password_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ class _ForgetPasswordPageState extends State<ForgetPasswordPage> {
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email address';
return 'Please enter your email address to receive a confirmation code to reset your password.';
}
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
return 'Please enter a valid email address';
return 'Please enter a valid email address to receive a confirmation code to reset your password.';
}
return null;
},
Expand Down
112 changes: 107 additions & 5 deletions lib/presentation/settings/views/sigup_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,62 @@ class _SignupFormState extends State<SignupForm> {
String email = '';
String password = '';
String confirmPassword = '';
String? verificationCode;
bool isLoading = false;
String nickname = '';
final TextEditingController _nicknameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _verificationCodeController = TextEditingController();
bool isButtonEnabled = false;
bool isPasswordValid = false;
bool isEmailValid = false;

void _checkFields() {
setState(() {
isButtonEnabled = email.isNotEmpty &&
RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(email) &&
password.isNotEmpty &&
password.length >= 6 &&
confirmPassword == password;
confirmPassword == password &&
_verificationCodeController.text.isNotEmpty &&
_verificationCodeController.text.length == 6;
});
}

@override
void initState() {
super.initState();
_emailController.addListener(_validateEmail);
_verificationCodeController.addListener(_checkFields);
}

void _validateEmail() {
email = _emailController.text;
final isValid = RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(email);
if (isValid != isEmailValid) {
setState(() {
isEmailValid = isValid;
});
}
}

void _signup(SignupViewModel viewModel) async {
nickname = _nicknameController.text.trim();
if (_formKey.currentState!.validate()) {
setState(() => isLoading = true);
User? user = await viewModel.signup(
email,
password,
_verificationCodeController.text,
name: nickname.isNotEmpty ? nickname : null,
);
setState(() => isLoading = false);

if (user != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Signup successful!"),
duration: Duration(seconds: 2),
content: Text("Sign-up complete! Welcome aboard!"),
duration: Duration(seconds: 5),
),
);
Navigator.pop(context, user);
Expand All @@ -77,7 +101,6 @@ class _SignupFormState extends State<SignupForm> {
@override
Widget build(BuildContext context) {
final viewModel = Provider.of<SignupViewModel>(context);

return Scaffold(
appBar: AppBar(title: Text('Signup')),
body: SingleChildScrollView(
Expand All @@ -89,8 +112,10 @@ class _SignupFormState extends State<SignupForm> {
children: [
// Username Field
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
hintText: "Input Email",
errorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color(0xFFB71C1C)),
),
Expand All @@ -103,6 +128,7 @@ class _SignupFormState extends State<SignupForm> {
email = value.trim();
_checkFields();
},
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) return 'Please enter your email';
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) return 'Enter a valid email';
Expand All @@ -112,6 +138,7 @@ class _SignupFormState extends State<SignupForm> {
SizedBox(height: 16.0),

// Nickname Field (Optional)
if(viewModel.isSignUp)
TextFormField(
controller: _nicknameController,
decoration: InputDecoration(
Expand All @@ -126,6 +153,7 @@ class _SignupFormState extends State<SignupForm> {
SizedBox(height: 16.0),

// Password Field
if(viewModel.isSignUp)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expand Down Expand Up @@ -169,6 +197,7 @@ class _SignupFormState extends State<SignupForm> {
SizedBox(height: 16.0),

// Confirm Password Field
if(viewModel.isSignUp)
TextFormField(
decoration: InputDecoration(
labelText: 'Confirm Password',
Expand Down Expand Up @@ -197,9 +226,38 @@ class _SignupFormState extends State<SignupForm> {
_checkFields();
},
),
SizedBox(height: 30.0),
SizedBox(height: 16.0),

if(viewModel.isSignUp)
TextFormField(
keyboardType: TextInputType.number,
controller: _verificationCodeController,
decoration: InputDecoration(
labelText: "Verification Code",
border: UnderlineInputBorder(),
errorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color(0xFFB71C1C)),
),
focusedErrorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color(0xFFB71C1C)),
),
errorStyle: TextStyle(color: Color(0xFFB71C1C)),
),
validator: (value) {
if (value == null || value.length != 6) {
return "Please enter a valid verification code to proceed";
}
return null;
},
onChanged: (value) {
verificationCode = value;
_checkFields();
},
),
SizedBox(height: 20),

// Signup Button
if(viewModel.isSignUp)
viewModel.isLoading || isLoading
? CircularProgressIndicator()
: MaterialButton(
Expand All @@ -215,6 +273,50 @@ class _SignupFormState extends State<SignupForm> {
),
child: Text('Signup', style: TextStyle(color: Colors.white, fontSize: 16)),
),

if (!viewModel.isSignUp)
Padding(
padding: const EdgeInsets.only(top: 1.0), // Adjust top padding here
child: MaterialButton(
onPressed: viewModel.isLoading
? null
: () async {
if (_formKey.currentState!.validate()) {
await viewModel.continueSignUp(email);

if (viewModel.errorMessage != null && viewModel.errorMessage!.isNotEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(viewModel.errorMessage!),
),
);
} else if (viewModel.isSignUp) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Verification code sent successfully.'),
),
);
}
}
},
height: 45,
minWidth: double.infinity,
color: isEmailValid ? const Color(0xFF33424E) : const Color(0xFF8C9699),
disabledColor: const Color(0xFF8C9699),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: viewModel.isLoading
? const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
)
: const Text(
"Send Verification Code",
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),

],
),
),
Expand Down

0 comments on commit 2a88ed6

Please sign in to comment.