diff --git a/firebase/functions/src/types.ts b/firebase/functions/src/types.ts index 89b6132..84d49eb 100644 --- a/firebase/functions/src/types.ts +++ b/firebase/functions/src/types.ts @@ -1,9 +1,9 @@ -export const messageTypes = ['text', 'system', 'timeProposal'] as const; +export const messageTypes = ["text", "system", "timeProposal"] as const; export type MessageType = (typeof messageTypes)[number]; // TimeOfDay string type matching Flutter's TimeFormatter.productionToString format // Format: "TimeOfDay(HH:MM)" where HH and MM are zero-padded -type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; +type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"; export type TimeOfDayString = `TimeOfDay(${Digit}${Digit}:${Digit}${Digit})`; // Base message fields shared by all message types @@ -15,19 +15,19 @@ type BaseMessage = { }; export type TextMessage = BaseMessage & { - messageType: 'text'; + messageType: "text"; content: string; }; export type SystemMessage = BaseMessage & { - messageType: 'system'; + messageType: "system"; content: string; }; export type TimeProposal = BaseMessage & { - messageType: 'timeProposal'; + messageType: "timeProposal"; proposedTime: TimeOfDayString; - status: 'pending' | 'accepted' | 'rejected'; + status: "pending" | "accepted" | "rejected"; }; export type Message = TextMessage | SystemMessage | TimeProposal; @@ -67,4 +67,4 @@ export type User = { fcmToken?: string; isEmailVerified: boolean; // other fields aren't relevant -}; \ No newline at end of file +}; diff --git a/swipeshare_app/lib/pages/buy/buy_swipes.dart b/swipeshare_app/lib/pages/buy/buy_swipes.dart index 301b24a..067a87f 100644 --- a/swipeshare_app/lib/pages/buy/buy_swipes.dart +++ b/swipeshare_app/lib/pages/buy/buy_swipes.dart @@ -8,7 +8,6 @@ import 'package:swipeshare_app/components/buy_and_sell_screens/time_picker.dart' import 'package:swipeshare_app/components/buy_and_sell_screens/time_picker_validation.dart'; import 'package:swipeshare_app/components/colors.dart'; import 'package:swipeshare_app/components/text_styles.dart'; -import 'package:swipeshare_app/models/user.dart'; import 'package:swipeshare_app/pages/buy/listing_selection_page.dart'; import 'package:swipeshare_app/utils/haptics.dart'; @@ -128,7 +127,7 @@ class _BuySwipeScreenState extends State { return SizedBox( width: double.infinity, child: ElevatedButton( - onPressed: _canProceedToNextScreen() + onPressed: _canProceedToSelection() ? _navigateToListingSelection : null, style: ElevatedButton.styleFrom( @@ -157,10 +156,11 @@ class _BuySwipeScreenState extends State { } /// Validates if user can proceed to next screen - bool _canProceedToNextScreen() { + bool _canProceedToSelection() { return startTime != null && endTime != null && selectedLocations.isNotEmpty && + selectedPaymentOptions.isNotEmpty && !_isEndTimeBeforeStartTime(); } @@ -176,9 +176,7 @@ class _BuySwipeScreenState extends State { date: selectedDate, startTime: startTime!, endTime: endTime!, - paymentTypes: selectedPaymentOptions.isEmpty - ? PaymentOption.allPaymentTypeNames - : selectedPaymentOptions, + paymentTypes: selectedPaymentOptions, ), ), ); diff --git a/swipeshare_app/lib/pages/onboarding/login_page.dart b/swipeshare_app/lib/pages/onboarding/login_page.dart index eef5b07..8b8ec71 100644 --- a/swipeshare_app/lib/pages/onboarding/login_page.dart +++ b/swipeshare_app/lib/pages/onboarding/login_page.dart @@ -24,6 +24,18 @@ class _LoginPageState extends State { //sign in user void signIn() async { + // Validate that email and password are not empty + if (emailController.text.trim().isEmpty || + passwordController.text.isEmpty) { + await safeVibrate(HapticsType.error); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please enter both email and password')), + ); + } + return; + } + //get the auth service final authService = Provider.of(context, listen: false); @@ -36,9 +48,10 @@ class _LoginPageState extends State { } catch (e) { await safeVibrate(HapticsType.error); if (mounted) { + final errorMessage = e.toString().replaceFirst('Exception: ', ''); ScaffoldMessenger.of( context, - ).showSnackBar(SnackBar(content: Text(e.toString()))); + ).showSnackBar(SnackBar(content: Text(errorMessage))); } } } diff --git a/swipeshare_app/lib/pages/onboarding/signup_page.dart b/swipeshare_app/lib/pages/onboarding/signup_page.dart index ff899fa..e1c524a 100644 --- a/swipeshare_app/lib/pages/onboarding/signup_page.dart +++ b/swipeshare_app/lib/pages/onboarding/signup_page.dart @@ -29,6 +29,18 @@ class _RegisterPageState extends State { //sign up user void signUp() async { + // Validate that all required fields are filled out + if (nameController.text.trim().isEmpty || + emailController.text.trim().isEmpty || + passwordController.text.isEmpty || + confirmPasswordController.text.isEmpty) { + await safeVibrate(HapticsType.error); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please fill out all required fields')), + ); + return; + } + // make it unc email only if (!emailController.text.trim().toLowerCase().endsWith('unc.edu') || (!referralController.text.trim().toLowerCase().endsWith('unc.edu') && @@ -83,18 +95,13 @@ class _RegisterPageState extends State { nameController.text, referralController.text, ); - if (mounted) { - await safeVibrate(HapticsType.medium); - // Navigator.pushReplacement( - // context, - // MaterialPageRoute(builder: (context) => OnboardingCarousel()), - // ); - } + await safeVibrate(HapticsType.medium); } catch (e) { + final errorMessage = e.toString().replaceFirst('Exception: ', ''); if (mounted) { ScaffoldMessenger.of( context, - ).showSnackBar(SnackBar(content: Text(e.toString()))); + ).showSnackBar(SnackBar(content: Text(errorMessage))); } } } diff --git a/swipeshare_app/lib/services/auth/auth_services.dart b/swipeshare_app/lib/services/auth/auth_services.dart index 34f69b6..22e9f8c 100644 --- a/swipeshare_app/lib/services/auth/auth_services.dart +++ b/swipeshare_app/lib/services/auth/auth_services.dart @@ -27,7 +27,7 @@ class AuthServices extends ChangeNotifier { return userCredential; } on FirebaseAuthException catch (e) { - throw Exception(e.code); + throw Exception(_getAuthErrorMessage(e.code)); } } @@ -56,12 +56,7 @@ class AuthServices extends ChangeNotifier { return userCredential; } on FirebaseAuthException catch (e) { - //catch errors - // debugPrint('Error code: ${e.code}'); - // debugPrint('Error message: ${e.message}'); - // debugPrint('Error details: ${e.stackTrace}'); - - throw Exception(e.code); + throw Exception(_getAuthErrorMessage(e.code)); } } @@ -72,4 +67,30 @@ class AuthServices extends ChangeNotifier { await FirebaseAuth.instance.signOut(); debugPrint('User signed out successfully.'); } + + //convert Firebase auth error codes to user-friendly messages + String _getAuthErrorMessage(String code) { + switch (code) { + case 'invalid-credential': + return 'Invalid email or password. Please try again.'; + case 'user-not-found': + return 'No account found with this email.'; + case 'wrong-password': + return 'Incorrect password. Please try again.'; + case 'email-already-in-use': + return 'This email is already registered.'; + case 'weak-password': + return 'Password is too weak. Please use a stronger password.'; + case 'invalid-email': + return 'Invalid email address format.'; + case 'user-disabled': + return 'This account has been disabled.'; + case 'too-many-requests': + return 'Too many failed attempts. Please try again later.'; + case 'network-request-failed': + return 'Network error. Please check your connection.'; + default: + return 'An error occurred. Please try again.'; + } + } } diff --git a/swipeshare_app/lib/services/order_service.dart b/swipeshare_app/lib/services/order_service.dart index 31656e3..560533b 100644 --- a/swipeshare_app/lib/services/order_service.dart +++ b/swipeshare_app/lib/services/order_service.dart @@ -14,9 +14,9 @@ class OrderService extends ChangeNotifier { //POST ORDER Future postOrder(Listing listing) async { try { - final result = await _functions.httpsCallable('createOrderFromListing').call({ - 'listingId': listing.id, - }); + final result = await _functions + .httpsCallable('createOrderFromListing') + .call({'listingId': listing.id}); final orderData = result.data as Map; final newOrder = MealOrder.fromMap(orderData);