Skip to content

Commit

Permalink
feat: Add authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
gausoft committed Jul 19, 2022
1 parent fe81696 commit 462a886
Show file tree
Hide file tree
Showing 24 changed files with 1,120 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .metadata
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ migration:
- platform: root
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
- platform: android
- platform: linux
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236

Expand Down
32 changes: 32 additions & 0 deletions lib/common/form_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,36 @@ class FormValidator {
static String? validateProductQuantity(String? value) {
return value!.isEmpty ? 'Please enter product quantity' : null;
}

//Authentication
static String? validateEmail(String? value) {
String emailPattern =
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = RegExp(emailPattern);

if (value!.isEmpty) {
return 'Please enter your email address';
} else if (!regExp.hasMatch(value)) {
return 'Please enter a valid email';
}
return null;
}

static String? validatePassword(String? value) {
return value!.isEmpty ? 'Please enter your password' : null;
}

static String? validateFullName(String? value) {
return value!.isEmpty ? 'Please enter your fullname' : null;
}

//validatePasswordConfirmation
static String? validatePasswordConfirmation(String? value, String password) {
if (value!.isEmpty) {
return 'Please enter your password confirmation';
} else if (value != password) {
return 'Password confirmation does not match';
}
return null;
}
}
13 changes: 13 additions & 0 deletions lib/common/providers.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../providers/auth/auth_notifier.dart';
import '../providers/auth/auth_state.dart';
import '../providers/quotes_list_notifier.dart';
import '../providers/quotes_list_state.dart';
import '../providers/submit_quote_notifier.dart';
import '../providers/submit_quote_state.dart';
import '../repositories/auth_repository.dart';
import '../repositories/quote_repository.dart';
import 'constants.dart';

Expand All @@ -20,10 +23,16 @@ final dioProvider = Provider<Dio>(
),
);

//Repositories
final repositoryProvider = Provider<QuoteRepository>(
(ref) => QuoteRepositoryImpl(ref.read(dioProvider)),
);

final authRepositoryProvider = Provider<AuthRepository>(
(ref) => AuthRepositoryImpl(ref.read(dioProvider)),
);

//Notifiers
final quotesListNotifier =
StateNotifierProvider<QuotesListNotifier, QuotesListState>(
(ref) => QuotesListNotifier(ref.read(repositoryProvider)),
Expand All @@ -36,3 +45,7 @@ final submitQuoteNotifier =
return SubmitQuoteNotifier(ref.read(repositoryProvider), notifier);
},
);

final authNotifier = StateNotifierProvider<AuthNotifier, AuthState>(
(ref) => AuthNotifier(ref.read(authRepositoryProvider)),
);
24 changes: 19 additions & 5 deletions lib/common/routes.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
import 'package:flutter/cupertino.dart';

import '../screens/home_screen.dart';
import '../screens/submit_request_screen.dart';
import '../screens/auth/login_screen.dart';
import '../screens/auth/register_screen.dart';
import '../screens/home/request_list_screen.dart';
import '../screens/home/submit_request_screen.dart';

Route<dynamic> onGenerate(RouteSettings settings) {
switch (settings.name) {
case '/':
case '/request-list':
return CupertinoPageRoute(
builder: (_) => const RequestListScreen(),
settings: settings,
builder: (_) => const HomeScreen(),
);
case '/request-quote':
return CupertinoPageRoute(
settings: settings,
builder: (BuildContext context) => const RequestQuoteScreen(),
);
case '/login':
return CupertinoPageRoute(
settings: settings,
builder: (BuildContext context) => const LoginScreen(),
);
case '/register':
return CupertinoPageRoute(
settings: settings,
builder: (BuildContext context) => const RegisterScreen(),
);
default:
return CupertinoPageRoute(
settings: settings,
builder: (_) => const CupertinoPageScaffold(child: SizedBox()),
builder: (_) => const CupertinoPageScaffold(
child: Center(child: Text('Not Found')),
),
);
}
}
5 changes: 2 additions & 3 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:quote_request_app/screens/auth/login_screen.dart';

import 'common/routes.dart' as routes;

import 'screens/home_screen.dart';

void main() {
runApp(const QuoteRequestApp());
}
Expand All @@ -24,7 +23,7 @@ class QuoteRequestApp extends StatelessWidget {
textTheme: GoogleFonts.dosisTextTheme(),
),
onGenerateRoute: routes.onGenerate,
home: const HomeScreen(),
home: const LoginScreen(),
),
);
}
Expand Down
26 changes: 26 additions & 0 deletions lib/models/user_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:flutter/foundation.dart';

class UserModel {
final String? uuid;
final String email;
final String? password;
final String fullname;
final String? address;

UserModel({
this.uuid,
this.password,
this.address,
required this.email,
required this.fullname,
});

factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
uuid: json['id'],
email: json['email'],
fullname: json['user_metadata']['fullname'],
address: json['user_metadata']['address'],
);
}
}
32 changes: 32 additions & 0 deletions lib/providers/auth/auth_notifier.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:quote_request_app/providers/auth/auth_state.dart';
import 'package:quote_request_app/repositories/auth_repository.dart';
import 'package:riverpod/riverpod.dart';

class AuthNotifier extends StateNotifier<AuthState> {
AuthNotifier(this._authRepository) : super(AuthInitial());

final AuthRepository _authRepository;

Future<void> login(Map<String, dynamic> credentials) async {
state = AuthLoading();
try {
final user = await _authRepository.login(
credentials['email'],
credentials['password'],
);
state = AuthSuccess(user);
} catch (e) {
state = AuthError(e.toString());
}
}

Future<void> register(Map<String, dynamic> userData) async {
state = AuthLoading();
try {
final user = await _authRepository.register(userData);
state = AuthSuccess(user);
} catch (e) {
state = AuthError(e.toString());
}
}
}
35 changes: 35 additions & 0 deletions lib/providers/auth/auth_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:equatable/equatable.dart';

import '../../models/user_model.dart';

abstract class AuthState extends Equatable {
const AuthState();
}

class AuthInitial extends AuthState {
@override
List<Object> get props => [];
}

class AuthLoading extends AuthState {
@override
List<Object> get props => [];
}

class AuthSuccess extends AuthState {
const AuthSuccess(this.user);

final UserModel user;

@override
List<Object> get props => [user];
}

class AuthError extends AuthState {
final String message;

const AuthError(this.message);

@override
List<Object> get props => [message];
}
54 changes: 54 additions & 0 deletions lib/repositories/auth_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';

import '../models/user_model.dart';

abstract class AuthRepository {
Future<UserModel> login(String email, String password);
Future<UserModel> register(Map<String, dynamic> userData);
}

class AuthRepositoryImpl implements AuthRepository {
final Dio _dio;

AuthRepositoryImpl(this._dio);

@override
Future<UserModel> login(String email, String password) async {
try {
final response = await _dio.post(
'/auth/v1/token',
queryParameters: {'grant_type': 'password'},
data: {'email': email, 'password': password},
);

if (response.statusCode == 200) {
return UserModel.fromJson(response.data['user']);
} else {
throw Exception('An error occurred');
}
} on DioError catch (e) {
throw await Future.error(e.response!.data['error_description']);
} catch (e) {
debugPrint('AuthRepositoryImpl.login: $e');
throw await Future.error("Couldn't login. Is the device online?");
}
}

@override
Future<UserModel> register(Map<String, dynamic> userData) async {
try {
final response = await _dio.post('/auth/v1/signup', data: userData);
return UserModel.fromJson(response.data);
} on DioError catch (e) {
if (e.response!.statusCode == 400) {
throw await Future.error(e.response!.data['error_description']);
} else {
throw await Future.error(e.message);
}
} catch (e, stack) {
debugPrint('Error: $e\nStack : $stack');
throw await Future.error("Couldn't register. Is the device online?");
}
}
}
30 changes: 15 additions & 15 deletions lib/repositories/quote_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,10 @@ class QuoteRepositoryImpl implements QuoteRepository {

QuoteRepositoryImpl(this._dio);

@override
Future<void> deleteRequestedQuote(int requestedQuoteId) async {
try {
await _dio.delete(
'/requests',
queryParameters: {'id': 'eq.$requestedQuoteId'}, //check PostgREST docs
);
} catch (_) {
throw Future.error("Couldn't delete quote. Is the device online?");
}
}

@override
Future<List<Quote>> getQuotes() async {
try {
final response = await _dio.get('/requests', queryParameters: {
final response = await _dio.get('/rest/v1/requests', queryParameters: {
'order': 'created_at.desc',
});

Expand All @@ -43,15 +31,27 @@ class QuoteRepositoryImpl implements QuoteRepository {
@override
Future<Quote> submitQuote(Map<String, dynamic> quote) async {
try {
final response = await _dio.post('/requests', data: quote);
final response = await _dio.post('/rest/v1/requests', data: quote);

if (response.statusCode == 201) {
return Quote.fromMap(response.data[0]);
return Quote.fromMap(response.data.first);
} else {
throw Exception('Error submitting request quote');
}
} catch (_) {
throw Future.error("Couldn't submit quote. Is the device online?");
}
}

@override
Future<void> deleteRequestedQuote(int requestedQuoteId) async {
try {
await _dio.delete(
'/rest/v1/requests',
queryParameters: {'id': 'eq.$requestedQuoteId'}, //check PostgREST docs
);
} catch (_) {
throw Future.error("Couldn't delete quote. Is the device online?");
}
}
}
Loading

0 comments on commit 462a886

Please sign in to comment.