Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
6 changes: 3 additions & 3 deletions app/lib/presentation/navigation/routers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ enum Routes {
String get path => '/$name';
String get subPath => name;

void nav(BuildContext context, {Object? extra}) {
void go(BuildContext context, {Object? extra}) {
context.router.goNamed(
name,
extra: extra,
Expand Down Expand Up @@ -63,7 +63,7 @@ class Routers {
return;
}
debugPrint('Navigating to app route');
Routes.app.nav(context);
Routes.app.go(context);
break;
case AuthStateUnauthenticated _:
debugPrint(
Expand All @@ -74,7 +74,7 @@ class Routers {
return;
}
debugPrint('Navigating to auth route');
Routes.auth.nav(context);
Routes.auth.go(context);
break;
case _:
}
Expand Down
2 changes: 1 addition & 1 deletion app/lib/presentation/resources/dim.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Dimen {
static const loadingSpinnerSize = 32.0;
static const loadingSpinnerSizeS = 16.0;

static const double loginFormMaxWidth = 400.0;
static const double authFormMaxWidth = 400.0;

static const spacingXxs = 2.0;
static const spacingXs = 4.0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class MessageLookup extends MessageLookupByLibrary {
"This website uses cookies",
),
"ctaLogin": MessageLookupByLibrary.simpleMessage("Login"),
"ctaSignUp": MessageLookupByLibrary.simpleMessage("Sign Up"),
"errorEmailInvalid": MessageLookupByLibrary.simpleMessage(
"Please enter a valid email address.",
),
Expand All @@ -43,9 +44,15 @@ class MessageLookup extends MessageLookupByLibrary {
"errorPasswordWeak": MessageLookupByLibrary.simpleMessage(
"Password is too weak.",
),
"errorPasswordsDoNotMatch": MessageLookupByLibrary.simpleMessage(
"Passwords do not match.",
),
"labelAgreeToTerms": MessageLookupByLibrary.simpleMessage(
"I agree to the Terms and Conditions",
),
"labelConfirmPassword": MessageLookupByLibrary.simpleMessage(
"Confirm Password",
),
"labelEmail": MessageLookupByLibrary.simpleMessage("Email"),
"labelPassword": MessageLookupByLibrary.simpleMessage("Password"),
"loginErrorInvalidCredentials": MessageLookupByLibrary.simpleMessage(
Expand All @@ -67,5 +74,9 @@ class MessageLookup extends MessageLookupByLibrary {
"titleLoginSubtitle": MessageLookupByLibrary.simpleMessage(
"Use your email and password to login to your account.",
),
"titleSignUp": MessageLookupByLibrary.simpleMessage("Sign Up"),
"titleSignUpSubtitle": MessageLookupByLibrary.simpleMessage(
"Create an account using your email and password.",
),
};
}
40 changes: 40 additions & 0 deletions app/lib/presentation/resources/locale/generated/l10n.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions app/lib/presentation/resources/locale/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@
"pleaseTryAgainLaterWeArenworkingToFixTheIssue": "Please try again later, we are\nworking to fix the issue.",
"sorryWeDidntFindAnyProduct": "Sorry we didn't find any product",
"ctaLogin": "Login",
"ctaSignUp": "Sign Up",
"labelEmail": "Email",
"labelPassword": "Password",
"labelConfirmPassword": "Confirm Password",
"errorPasswordsDoNotMatch": "Passwords do not match.",
"passwordInstructions": "Min 8 characters long: 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character.",
"labelAgreeToTerms": "I agree to the Terms and Conditions",
"errorEmailRequired": "Email is required.",
"errorPasswordRequired": "Password is required.",
"titleLogin": "Login",
"titleSignUp": "Sign Up",
"titleLoginSubtitle": "Use your email and password to login to your account.",
"titleSignUpSubtitle": "Create an account using your email and password.",
"errorEmailInvalid": "Please enter a valid email address.",
"errorPasswordWeak": "Password is too weak.",
"loginErrorInvalidCredentials": "Invalid email or password."
Expand Down
22 changes: 22 additions & 0 deletions app/lib/presentation/ui/pages/auth/login/login_form.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:app/presentation/navigation/routers.dart';
import 'package:app/presentation/resources/locale/generated/l10n.dart';
import 'package:app/presentation/resources/resources.dart';
import 'package:app/presentation/ui/components/primary_button.dart';
Expand Down Expand Up @@ -52,6 +53,7 @@ class _LoginFormState extends State<LoginForm> {
labelText: S.of(context).labelEmail,
),
keyboardType: TextInputType.emailAddress,
autofillHints: const [AutofillHints.username, AutofillHints.email],
controller: emailController,
validator: (value) {
if (!FormValidator.isEmail(value)) {
Expand Down Expand Up @@ -147,6 +149,26 @@ class _LoginFormState extends State<LoginForm> {
);
},
),
const Gap(Dimen.spacingL),
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(child: Divider()),
Padding(
padding: EdgeInsets.symmetric(horizontal: Dimen.spacingS),
child: Text("OR"),
),
Expanded(child: Divider()),
],
),
const Gap(Dimen.spacingM),
SizedBox(
width: double.infinity,
child: TextButton(
onPressed: () => Routes.signup.go(context),
child: Text(S.of(context).ctaSignUp.toUpperCase()),
),
),
],
),
);
Expand Down
3 changes: 2 additions & 1 deletion app/lib/presentation/ui/pages/auth/login/login_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:app/presentation/resources/resources.dart';
import 'package:app/presentation/ui/pages/auth/login/login_form.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class LoginPage extends StatelessWidget {
Expand All @@ -11,7 +12,7 @@ class LoginPage extends StatelessWidget {
body: Center(
child: Container(
constraints: const BoxConstraints(
maxWidth: Dimen.loginFormMaxWidth,
maxWidth: kIsWeb ? Dimen.authFormMaxWidth : double.infinity,
),
child: const Card(
child: Padding(
Expand Down
171 changes: 171 additions & 0 deletions app/lib/presentation/ui/pages/auth/sign_up/sign_up_form.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import 'package:app/presentation/resources/locale/generated/l10n.dart';
import 'package:app/presentation/resources/resources.dart';
import 'package:app/presentation/ui/components/primary_button.dart';
import 'package:common/core/resource.dart';
import 'package:common/validators/form_validator.dart';
import 'package:domain/bloc/auth/auth_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart';

class SignUpForm extends StatefulWidget {
const SignUpForm({super.key});

@override
State<SignUpForm> createState() => _SignUpFormState();
}

class _SignUpFormState extends State<SignUpForm> {
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController confirmPasswordController = TextEditingController();
bool agreeToTerms = false;

final _formKey = GlobalKey<FormState>();

@override
void dispose() {
emailController.dispose();
passwordController.dispose();
confirmPasswordController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).titleSignUp,
style: Theme.of(context).textTheme.titleMedium,
),
const Gap(Dimen.spacingL),
Text(
S.of(context).titleSignUpSubtitle,
style: Theme.of(context).textTheme.titleSmall,
),
const Gap(Dimen.spacingL),
TextFormField(
decoration: InputDecoration(
labelText: S.of(context).labelEmail,
),
keyboardType: TextInputType.emailAddress,
autofillHints: const [AutofillHints.username, AutofillHints.email],
controller: emailController,
validator: (value) {
if (!FormValidator.isEmail(value)) {
return S.of(context).errorEmailInvalid;
}

return null;
},
),
const Gap(Dimen.spacingM),
TextFormField(
decoration: InputDecoration(
labelText: S.of(context).labelPassword,
),
obscureText: true,
controller: passwordController,
validator: (value) {
if (!FormValidator.isStrongPassword(value)) {
return S.of(context).errorPasswordWeak;
}
return null;
},
),
const Gap(Dimen.spacingM),
Text(
S.of(context).passwordInstructions,
style: Theme.of(context).textTheme.bodySmall,
),
const Gap(Dimen.spacingM),
TextFormField(
decoration: InputDecoration(
labelText: S.of(context).labelConfirmPassword,
),
obscureText: true,
controller: confirmPasswordController,
validator: (value) {
if (value != passwordController.text) {
return S.of(context).errorPasswordsDoNotMatch;
}
return null;
},
),
const Gap(Dimen.spacingM),
TextButton(
onPressed: () => setState(() {
agreeToTerms = !agreeToTerms;
}),
child: Row(
children: [
Checkbox(
value: agreeToTerms,
onChanged: (value) => setState(() {
agreeToTerms = value ?? false;
}),
),
Text(
S.of(context).labelAgreeToTerms,
style: Theme.of(context).textTheme.titleSmall,
),
const Gap(Dimen.spacingS),
IconButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
"This should open the terms and conditions URL."),
),
);
},
icon: const Icon(Icons.info),
)
],
),
),
const Gap(Dimen.spacingM),
BlocBuilder<AuthCubit, Resource>(
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (state is RError) ...[
Text(
S.of(context).loginErrorInvalidCredentials,
style:
Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
const Gap(Dimen.spacingM),
],
PrimaryButton(
label: S.of(context).ctaSignUp,
onPressed: () {
if ((_formKey.currentState?.validate() ?? false) &&
agreeToTerms) {
context.read<AuthCubit>().signUp(
email: emailController.text,
password: passwordController.text,
);
}
},
isEnabled: agreeToTerms,
isLoading: state is RLoading,
trailingIcon: const Icon(Icons.login),
)
],
);
},
),
],
),
);
}
}
Loading