Commit b32c988a authored by Emily Carroll's avatar Emily Carroll

Feat: Add driver and admin login for webpage

parent c2c69eec
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "ac4e799d237041cf905519190471f657b657155a" revision: "a402d9a4376add5bc2d6b1e33e53edaae58c07f8"
channel: "stable" channel: "stable"
project_type: app project_type: app
...@@ -13,26 +13,26 @@ project_type: app ...@@ -13,26 +13,26 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: ac4e799d237041cf905519190471f657b657155a create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: ac4e799d237041cf905519190471f657b657155a base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: android - platform: android
create_revision: ac4e799d237041cf905519190471f657b657155a create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: ac4e799d237041cf905519190471f657b657155a base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: ios - platform: ios
create_revision: ac4e799d237041cf905519190471f657b657155a create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: ac4e799d237041cf905519190471f657b657155a base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: linux - platform: linux
create_revision: ac4e799d237041cf905519190471f657b657155a create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: ac4e799d237041cf905519190471f657b657155a base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: macos - platform: macos
create_revision: ac4e799d237041cf905519190471f657b657155a create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: ac4e799d237041cf905519190471f657b657155a base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: web - platform: web
create_revision: ac4e799d237041cf905519190471f657b657155a create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: ac4e799d237041cf905519190471f657b657155a base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- platform: windows - platform: windows
create_revision: ac4e799d237041cf905519190471f657b657155a create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
base_revision: ac4e799d237041cf905519190471f657b657155a base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
# User provided section # User provided section
......
...@@ -79,7 +79,7 @@ class MyApp extends StatelessWidget { ...@@ -79,7 +79,7 @@ class MyApp extends StatelessWidget {
), ),
useMaterial3: true, useMaterial3: true,
), ),
home: kIsWeb ? const AdminDashboardScreen() : const HomeScreen(), home: kIsWeb ? const LoginPage() : const HomeScreen(),
routes: { routes: {
"/login": (context) => const LoginPage(), "/login": (context) => const LoginPage(),
"/signup": (context) => const SignupPage(), "/signup": (context) => const SignupPage(),
...@@ -87,6 +87,7 @@ class MyApp extends StatelessWidget { ...@@ -87,6 +87,7 @@ class MyApp extends StatelessWidget {
"/map": (context) => const MapScreen(), "/map": (context) => const MapScreen(),
"/settings": (context) => const SettingsScreen(), "/settings": (context) => const SettingsScreen(),
"/profile": (context) => const ProfileScreen(), "/profile": (context) => const ProfileScreen(),
"/adminDashboard": (context) => const AdminDashboardScreen(),
}, },
); );
}, },
......
...@@ -35,6 +35,10 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> { ...@@ -35,6 +35,10 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
setState(() { setState(() {
_user = user; _user = user;
}); });
if (user == null) {
Navigator.of(context).pushReplacementNamed('/login');
}
} }
}); });
} }
......
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../providers/settings_provider.dart'; import '../providers/settings_provider.dart';
import '../services/google_auth_service.dart'; import '../services/google_auth_service.dart';
import '../colors.dart';
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
const LoginPage({super.key}); const LoginPage({super.key});
...@@ -18,50 +19,53 @@ class _LoginPageState extends State<LoginPage> { ...@@ -18,50 +19,53 @@ class _LoginPageState extends State<LoginPage> {
final TextEditingController _passwordController = TextEditingController(); final TextEditingController _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
bool _isLoading = false; bool _isLoading = false;
bool _rememberMe = false; String _selectedRole = 'Admin'; // Default role
void _navigateBasedOnRole(String role) {
if (!mounted) return;
switch (role) {
case 'Admin':
Navigator.of(context).pushReplacementNamed('/adminDashboard');
break;
case 'Driver':
default:
Navigator.of(context).pushReplacementNamed('/map');
break;
}
}
Future<void> _login() async { Future<void> _login() async {
if (!_formKey.currentState!.validate()) return; if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true); setState(() => _isLoading = true);
final settingsProvider = Provider.of<SettingsProvider>(context, listen: false);
try { try {
await FirebaseAuth.instance.signInWithEmailAndPassword( await FirebaseAuth.instance.signInWithEmailAndPassword(
email: _emailController.text.trim(), email: _emailController.text.trim(),
password: _passwordController.text.trim(), password: _passwordController.text.trim(),
); );
_navigateBasedOnRole(_selectedRole);
if (_rememberMe) { } on FirebaseAuthException catch (e) {
settingsProvider.login(); ScaffoldMessenger.of(context).showSnackBar(
} SnackBar(content: Text("Login Failed: ${e.message}")),
);
if (mounted) {
Navigator.of(context).pushReplacementNamed('/map');
}
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Login Failed: ${e.toString()}")), SnackBar(content: Text("Login Failed: ${e.toString()}")),
); );
} finally { } finally {
if(mounted) { if (mounted) setState(() => _isLoading = false);
setState(() => _isLoading = false);
}
} }
} }
Future<void> _loginWithGoogle() async { Future<void> _loginWithGoogle() async {
setState(() => _isLoading = true); setState(() => _isLoading = true);
final settingsProvider = Provider.of<SettingsProvider>(context, listen: false);
try { try {
final UserCredential? userCredential = await GoogleAuthService.signInWithGoogle(); final UserCredential? userCredential = await GoogleAuthService.signInWithGoogle();
if (FirebaseAuth.instance.currentUser != null) { if (FirebaseAuth.instance.currentUser != null) {
settingsProvider.login(); _navigateBasedOnRole(_selectedRole);
if (mounted) {
Navigator.of(context).pushReplacementNamed('/map');
}
} else if (userCredential == null && mounted) { } else if (userCredential == null && mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Google Sign-In was cancelled or failed.")), const SnackBar(content: Text("Google Sign-In was cancelled or failed.")),
...@@ -74,100 +78,157 @@ class _LoginPageState extends State<LoginPage> { ...@@ -74,100 +78,157 @@ class _LoginPageState extends State<LoginPage> {
); );
} }
} finally { } finally {
if(mounted) { if (mounted) setState(() => _isLoading = false);
setState(() => _isLoading = false);
}
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData currentTheme = Theme.of(context);
final bool darkMode = currentTheme.brightness == Brightness.dark;
final Color welcomeTextColor = darkMode ? Colors.white : Colors.black87;
final Color sloganTextColor = darkMode ? Colors.grey[300]! : Colors.black54;
final Color iconColor = darkMode ? Colors.white : const Color(0xFF0D2B0D);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('GraphGo Login'),
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.settings, color: Colors.white),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pushNamed('/settings'),
tooltip: 'Back to Home',
), ),
title: const Text('GraphGo', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white)),
centerTitle: true,
), ),
body: SingleChildScrollView( body: Center(
padding: const EdgeInsets.all(16.0), child: SingleChildScrollView(
child: Form( padding: const EdgeInsets.all(32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.account_tree,
size: 100,
color: iconColor,
),
const SizedBox(height: 20),
Text(
'Welcome to GraphGo',
style: currentTheme.textTheme.headlineMedium?.copyWith(
fontSize: (currentTheme.textTheme.headlineMedium?.fontSize ?? 28) * 1.15,
fontWeight: FontWeight.bold,
color: welcomeTextColor,
),
),
const SizedBox(height: 10),
Text(
'Log in as Admin or Driver to start exploring',
style: currentTheme.textTheme.bodyLarge?.copyWith(
fontSize: 16,
color: sloganTextColor,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 50),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Column(
children: [
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: _isLoading ? null : _loginWithGoogle,
icon: SvgPicture.asset('assets/icons/google_icon.svg', width: 20, height: 20),
label: const Text('Sign in with Google'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
const SizedBox(height: 20),
const Row(
children: [
Expanded(child: Divider()),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text('OR'),
),
Expanded(child: Divider()),
],
),
const SizedBox(height: 20),
Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
children: [ children: [
DropdownButtonFormField<String>(
value: _selectedRole,
decoration: const InputDecoration(
labelText: 'Role',
border: OutlineInputBorder(),
),
items: ['Admin', 'Driver']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
setState(() => _selectedRole = newValue);
}
},
),
const SizedBox(height: 16),
TextFormField( TextFormField(
controller: _emailController, controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'), decoration: const InputDecoration(labelText: 'Email', border: OutlineInputBorder()),
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
validator: (value) => value!.isEmpty ? "Enter your email" : null, validator: (value) => value!.isEmpty ? "Enter your email" : null,
), ),
const SizedBox(height: 16),
TextFormField( TextFormField(
controller: _passwordController, controller: _passwordController,
decoration: const InputDecoration(labelText: 'Password'), decoration: const InputDecoration(labelText: 'Password', border: OutlineInputBorder()),
obscureText: true, obscureText: true,
validator: (value) => value!.isEmpty ? "Enter your password" : null, validator: (value) => value!.isEmpty ? "Enter your password" : null,
), ),
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerRight,
child: Row( child: TextButton(
children: [
Checkbox(
value: _rememberMe,
onChanged: (val) {
setState(() => _rememberMe = val ?? false);
},
),
const Text("Remember Me"),
],
),
),
TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pushNamed('/forgot'); Navigator.of(context).pushNamed('/forgot');
}, },
child: const Text("Forgot Password?"), child: const Text('Forgot Password?'),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: _isLoading ? null : _loginWithGoogle,
icon: const Icon(Icons.login, size: 20, color: Colors.blue),
label: const Text('Sign in with Google'),
),
), ),
const SizedBox(height: 16),
Row(
children: [
const Expanded(child: Divider()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text('OR'),
), ),
const Expanded(child: Divider()), const SizedBox(height: 10),
],
),
const SizedBox(height: 16),
_isLoading _isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: SizedBox( : SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: _login, onPressed: _login,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 20),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text('Login with Email'), child: const Text('Login with Email'),
), ),
), ),
],
),
),
const SizedBox(height: 20),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pushNamed('/signup'), onPressed: () {
Navigator.of(context).pushNamed('/signup');
},
child: const Text("Don't have an account? Sign Up"), child: const Text("Don't have an account? Sign Up"),
), ),
],
// The debug override button has been removed. ),
),
], ],
), ),
), ),
......
...@@ -3,6 +3,11 @@ import 'package:flutter/material.dart'; ...@@ -3,6 +3,11 @@ import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart'; import 'package:location/location.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:geocoding/geocoding.dart' as geocoding;
import '../models/delivery_address.dart';
import '../services/firestore_service.dart';
class MapScreen extends StatefulWidget { class MapScreen extends StatefulWidget {
const MapScreen({super.key}); const MapScreen({super.key});
...@@ -13,8 +18,10 @@ class MapScreen extends StatefulWidget { ...@@ -13,8 +18,10 @@ class MapScreen extends StatefulWidget {
class _MapScreenState extends State<MapScreen> { class _MapScreenState extends State<MapScreen> {
final Completer<GoogleMapController> _controller = Completer(); final Completer<GoogleMapController> _controller = Completer();
final FirestoreService _firestoreService = FirestoreService();
LocationData? _currentLocation; LocationData? _currentLocation;
StreamSubscription<LocationData>? _locationSubscription; StreamSubscription<LocationData>? _locationSubscription;
Set<Marker> _markers = {};
final User? user = FirebaseAuth.instance.currentUser; final User? user = FirebaseAuth.instance.currentUser;
...@@ -26,29 +33,28 @@ class _MapScreenState extends State<MapScreen> { ...@@ -26,29 +33,28 @@ class _MapScreenState extends State<MapScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initializeLocation(); _initializeLocationAndMarkers();
}
Future<void> _initializeLocationAndMarkers() async {
await _initializeLocation();
if (user != null) {
_loadAddressMarkers(user!.uid);
}
} }
Future<void> _initializeLocation() async { Future<void> _initializeLocation() async {
Location location = Location(); Location location = Location();
bool serviceEnabled = await location.serviceEnabled();
bool serviceEnabled;
PermissionStatus permissionGranted;
serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) { if (!serviceEnabled) {
serviceEnabled = await location.requestService(); serviceEnabled = await location.requestService();
if (!serviceEnabled) { if (!serviceEnabled) return;
return;
}
} }
permissionGranted = await location.hasPermission(); PermissionStatus permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) { if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await location.requestPermission(); permissionGranted = await location.requestPermission();
if (permissionGranted != PermissionStatus.granted) { if (permissionGranted != PermissionStatus.granted) return;
return;
}
} }
_currentLocation = await location.getLocation(); _currentLocation = await location.getLocation();
...@@ -57,15 +63,49 @@ class _MapScreenState extends State<MapScreen> { ...@@ -57,15 +63,49 @@ class _MapScreenState extends State<MapScreen> {
} }
_locationSubscription = location.onLocationChanged.listen((LocationData newLocation) { _locationSubscription = location.onLocationChanged.listen((LocationData newLocation) {
if(mounted) { if (mounted) {
setState(() { setState(() => _currentLocation = newLocation);
_currentLocation = newLocation;
});
_moveCameraToLocation(newLocation); _moveCameraToLocation(newLocation);
} }
}); });
} }
Future<void> _loadAddressMarkers(String userId) async {
_firestoreService.getAddresses(userId).listen((addresses) async {
Set<Marker> newMarkers = {};
for (var address in addresses) {
// Check for null or empty required fields before geocoding
if (address.streetAddress.isNotEmpty &&
address.city.isNotEmpty &&
address.state.isNotEmpty &&
address.zipCode.isNotEmpty) {
try {
List<geocoding.Location> locations = await geocoding.locationFromAddress(
'${address.streetAddress}, ${address.city}, ${address.state} ${address.zipCode}'
);
if (locations.isNotEmpty) {
final loc = locations.first;
newMarkers.add(
Marker(
markerId: MarkerId(address.id),
position: LatLng(loc.latitude, loc.longitude),
infoWindow: InfoWindow(title: address.streetAddress, snippet: address.notes),
),
);
}
} catch (e) {
print("Error geocoding address: ${e}");
}
} else {
print("Skipping address due to missing fields: ${address.id}");
}
}
if (mounted) {
setState(() => _markers = newMarkers);
}
});
}
Future<void> _moveCameraToLocation(LocationData locationData) async { Future<void> _moveCameraToLocation(LocationData locationData) async {
final GoogleMapController controller = await _controller.future; final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newCameraPosition( controller.animateCamera(CameraUpdate.newCameraPosition(
...@@ -76,6 +116,13 @@ class _MapScreenState extends State<MapScreen> { ...@@ -76,6 +116,13 @@ class _MapScreenState extends State<MapScreen> {
)); ));
} }
Future<void> _logout() async {
await FirebaseAuth.instance.signOut();
if (mounted) {
Navigator.of(context).pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false);
}
}
@override @override
void dispose() { void dispose() {
_locationSubscription?.cancel(); _locationSubscription?.cancel();
...@@ -86,23 +133,17 @@ class _MapScreenState extends State<MapScreen> { ...@@ -86,23 +133,17 @@ class _MapScreenState extends State<MapScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Your Location'), title: const Text("Driver's View"),
leading: IconButton( automaticallyImplyLeading: false,
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
actions: [ actions: [
if (user?.email != null) if (user?.email != null)
Padding( Padding(
padding: const EdgeInsets.only(right: 16.0), padding: const EdgeInsets.only(right: 16.0),
child: Center( child: Center(child: Text(user!.email!, style: const TextStyle(fontSize: 12))),
child: Text(
user!.email!,
style: const TextStyle(
fontSize: 12,
),
),
), ),
IconButton(
icon: const Icon(Icons.logout),
onPressed: _logout,
), ),
], ],
), ),
...@@ -116,14 +157,8 @@ class _MapScreenState extends State<MapScreen> { ...@@ -116,14 +157,8 @@ class _MapScreenState extends State<MapScreen> {
}, },
myLocationEnabled: true, myLocationEnabled: true,
myLocationButtonEnabled: true, myLocationButtonEnabled: true,
markers: {
if (_currentLocation != null) markers: _markers,
Marker(
markerId: const MarkerId('currentLocation'),
position: LatLng(_currentLocation!.latitude!, _currentLocation!.longitude!),
infoWindow: const InfoWindow(title: 'My Location'),
),
},
), ),
); );
} }
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import '../services/google_auth_service.dart';
import '../colors.dart';
class SignupPage extends StatefulWidget { class SignupPage extends StatefulWidget {
const SignupPage({super.key}); const SignupPage({super.key});
...@@ -13,13 +11,21 @@ class SignupPage extends StatefulWidget { ...@@ -13,13 +11,21 @@ class SignupPage extends StatefulWidget {
class _SignupPageState extends State<SignupPage> { class _SignupPageState extends State<SignupPage> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final TextEditingController _firstNameController = TextEditingController();
final TextEditingController _lastNameController = TextEditingController();
final TextEditingController _emailController = TextEditingController(); final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController(); final TextEditingController _passwordController = TextEditingController();
final TextEditingController _confirmPasswordController = TextEditingController();
bool _isLoading = false; bool _isLoading = false;
String? _validateEmail(String? value) {
if (value == null || value.isEmpty) {
return "Email is required.";
}
final emailRegex = RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$');
if (!emailRegex.hasMatch(value)) {
return "Please enter a valid email address.";
}
return null;
}
String? _validatePassword(String? value) { String? _validatePassword(String? value) {
if (value == null || value.isEmpty) return "Password is required."; if (value == null || value.isEmpty) return "Password is required.";
if (value.length < 12) return "Password must be at least 12 characters."; if (value.length < 12) return "Password must be at least 12 characters.";
...@@ -41,45 +47,30 @@ class _SignupPageState extends State<SignupPage> { ...@@ -41,45 +47,30 @@ class _SignupPageState extends State<SignupPage> {
); );
await FirebaseFirestore.instance.collection('users').doc(userCredential.user!.uid).set({ await FirebaseFirestore.instance.collection('users').doc(userCredential.user!.uid).set({
'first_name': _firstNameController.text.trim(),
'last_name': _lastNameController.text.trim(),
'email': _emailController.text.trim(), 'email': _emailController.text.trim(),
'provider': 'email', 'provider': 'email',
'created_at': Timestamp.now(), 'created_at': Timestamp.now(),
}); });
if (mounted) { if (mounted) {
// After signing up, go to the map screen
Navigator.of(context).pushReplacementNamed('/map'); Navigator.of(context).pushReplacementNamed('/map');
} }
} catch (e) { } on FirebaseAuthException catch (e) {
String errorMessage;
if (e.code == 'email-already-in-use') {
errorMessage = "This email is already registered. Please log in or use a different email.";
} else {
errorMessage = "Signup Failed: ${e.message}";
}
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Signup Failed: ${e.toString()}")), SnackBar(content: Text(errorMessage)),
); );
} finally {
if(mounted) {
setState(() => _isLoading = false);
}
}
}
Future<void> _signUpWithGoogle() async {
setState(() => _isLoading = true);
try {
await GoogleAuthService.signInWithGoogle();
final user = FirebaseAuth.instance.currentUser;
if (user != null && mounted) {
Navigator.of(context).pushReplacementNamed('/map');
}
} catch (e) { } catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Google Sign-Up Failed: ${e.toString()}")), SnackBar(content: Text("An unexpected error occurred: ${e.toString()}")),
); );
}
} finally { } finally {
if(mounted) { if (mounted) {
setState(() => _isLoading = false); setState(() => _isLoading = false);
} }
} }
...@@ -87,95 +78,71 @@ class _SignupPageState extends State<SignupPage> { ...@@ -87,95 +78,71 @@ class _SignupPageState extends State<SignupPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('GraphGo Sign Up'), title: const Text('Create Account'),
// The back button is now handled correctly by the Navigator
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(), // Corrected line onPressed: () => Navigator.of(context).pop(),
tooltip: 'Back to Login', tooltip: 'Back to Login',
), ),
), ),
body: SingleChildScrollView( body: Center(
padding: const EdgeInsets.all(16.0), child: SingleChildScrollView(
padding: const EdgeInsets.all(32.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
TextFormField(
controller: _firstNameController,
decoration: const InputDecoration(labelText: 'First Name'),
validator: (value) => value!.isEmpty ? "Enter your first name" : null,
),
TextFormField(
controller: _lastNameController,
decoration: const InputDecoration(labelText: 'Last Name'),
validator: (value) => value!.isEmpty ? "Enter your last name" : null,
),
TextFormField( TextFormField(
controller: _emailController, controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'), decoration: const InputDecoration(
labelText: 'Username (Email)',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
validator: (value) => value!.isEmpty ? "Enter your email" : null, validator: _validateEmail,
), ),
const SizedBox(height: 16),
TextFormField( TextFormField(
controller: _passwordController, controller: _passwordController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password', labelText: 'Password',
border: const OutlineInputBorder(),
suffixIcon: Tooltip( suffixIcon: Tooltip(
message: 'Password must be at least 12 characters long and include:\n' message: 'Password must be at least 12 characters long and include:\n'
'- 1 uppercase letter\n' '- 1 uppercase letter\n'
'- 1 number\n' '- 1 number\n'
'- 1 special character (!@#\$%^&*(),.?":{}|<>)', '- 1 special character (!@#\$%^&*(),.?":{}|<>)',
child: Icon(Icons.help_outline), child: const Icon(Icons.help_outline),
), ),
), ),
obscureText: true, obscureText: true,
validator: _validatePassword, validator: _validatePassword,
), ),
TextFormField( const SizedBox(height: 30),
controller: _confirmPasswordController,
decoration: const InputDecoration(labelText: 'Confirm Password'),
obscureText: true,
validator: (value) => value != _passwordController.text ? "Passwords do not match" : null,
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: _isLoading ? null : _signUpWithGoogle,
icon: const Icon(Icons.login, size: 20, color: Colors.blue),
label: const Text('Sign up with Google'),
),
),
const SizedBox(height: 16),
Row(
children: [
const Expanded(child: Divider()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text('OR'),
),
const Expanded(child: Divider()),
],
),
const SizedBox(height: 16),
_isLoading _isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: SizedBox( : SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: _signUp, onPressed: _signUp,
child: const Text('Sign Up with Email'), style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 20),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text('Create Account'),
), ),
), ),
], ],
), ),
), ),
), ),
),
),
); );
} }
} }
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
<title>graph_go</title> <title>graph_go</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCFx_8PW_R6rGq-julkwV4JJGixbzmnP74"></script>
</head> </head>
<body> <body>
<script src="flutter_bootstrap.js" async></script> <script src="flutter_bootstrap.js" async></script>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment