From c2ffc9d9a7d2ad3ed489e16fdb0dcb213915adb8 Mon Sep 17 00:00:00 2001 From: mystery012728 Date: Thu, 5 Feb 2026 12:07:33 +0530 Subject: [PATCH] my Post Cards added with get api and more changes --- README.md | 2 +- assets/images/empty_postcard_drafts.png | Bin 0 -> 108428 bytes assets/images/empty_postcard_orders.png | Bin 0 -> 49647 bytes .../bloc/stripe_payment_bloc.dart | 41 + .../bloc/stripe_payment_event.dart | 12 + .../repository/stripe_service.dart | 2 +- lib/buy_a_pass/view/buy_pass_view.dart | 270 +++--- lib/buy_a_pass/widget/payment_card_view.dart | 18 +- .../blocs/myPassCart/my_pass_cart_bloc.dart | 73 ++ .../blocs/myPassCart/my_pass_cart_event.dart | 18 + .../blocs/myPassCart/my_pass_cart_state.dart | 43 + .../repository/my_pass_cart_repository.dart | 35 + lib/cart/views/my_cart_view_page.dart | 16 +- lib/cart/views/my_pass_cart_page_view.dart | 486 +++++++++++ lib/cart/views/my_pass_page_view.dart | 379 -------- .../bloc/allCoupons/all_coupons_bloc.dart} | 0 .../bloc/allCoupons/all_coupons_event.dart} | 0 .../bloc/allCoupons/all_coupons_state.dart | 0 lib/checkout/bloc/checkout/checkout_bloc.dart | 53 -- .../bloc/checkout/checkout_event.dart | 24 - .../bloc/checkout/checkout_state.dart | 52 -- lib/checkout/models/all_coupons_model.dart | 61 ++ .../repository/all_coupons_repository.dart | 0 lib/checkout/view/checkout_view.dart | 5 - lib/common_packages/app_bar.dart | 43 +- lib/contact_us/contact_us_view.dart | 237 ----- lib/core/app_router.dart | 2 +- .../bloc/create_account_bloc.dart | 1 + .../models/create_account_model.dart | 4 + .../view/create_account_view.dart | 9 +- lib/home/views/first_time_user_home_page.dart | 24 +- lib/home/views/home_page_view.dart | 3 +- lib/home/views/registered_user_home_page.dart | 26 + .../views/intro_screen_view.dart | 7 +- lib/localPreference/local_database.dart | 21 +- lib/localPreference/local_preference.dart | 228 +++++ lib/login/bloc/verify/verify_bloc.dart | 1 + lib/login/view/verify_otp_bottomsheet.dart | 7 + lib/main.dart | 7 + lib/networkApiServices/api_urls.dart | 3 + .../blocs/myPostCards/my_postcard_bloc.dart | 201 +++++ .../blocs/myPostCards/my_postcard_event.dart | 33 + .../blocs/myPostCards/my_postcard_state.dart | 76 ++ .../postcard_checkout_bloc.dart | 243 ++++++ .../postcard_checkout_event.dart | 97 +++ .../postcard_checkout_state.dart | 136 +++ .../blocs/postcard_creation_bloc.dart | 13 + .../blocs/postcard_creation_events.dart | 21 + .../blocs/postcard_creation_state.dart | 38 +- lib/postcard/models/my_postcard_model.dart | 173 ++++ .../repository/my_postcard_repository.dart | 20 + .../postcard_checkout_repository.dart | 205 +++++ lib/postcard/views/my_orders_page_view.dart | 817 ------------------ .../views/my_postcard_drafts_view.dart | 290 +++++++ .../views/my_postcard_orders_view.dart | 385 +++++++++ .../views/my_postcard_preview_view.dart | 438 ++++++++++ lib/postcard/views/my_postcards_view.dart | 491 +++++++++++ .../views/postcard_checkout_page_view.dart | 684 +++++++++++---- .../views/postcard_creation_page_view.dart | 30 +- .../postcard_purchase_form_page_view.dart | 372 +++++--- .../purchase_details_bottom_sheet.dart | 374 ++++---- .../bloc/contactUs/contact_us_bloc.dart | 42 + .../bloc/contactUs/contact_us_event.dart | 34 + .../bloc/contactUs/contact_us_state.dart | 34 + lib/profile/bloc/profile/profile_bloc.dart | 3 +- .../repository/contact_us_repository.dart | 32 + .../view/contact_us/contact_us_view.dart | 271 ++++++ .../view/edit_profile/edit_profile_view.dart | 21 +- lib/profile/view/profile_page_view.dart | 39 +- 69 files changed, 5658 insertions(+), 2168 deletions(-) create mode 100644 assets/images/empty_postcard_drafts.png create mode 100644 assets/images/empty_postcard_orders.png create mode 100644 lib/cart/blocs/myPassCart/my_pass_cart_bloc.dart create mode 100644 lib/cart/blocs/myPassCart/my_pass_cart_event.dart create mode 100644 lib/cart/blocs/myPassCart/my_pass_cart_state.dart create mode 100644 lib/cart/repository/my_pass_cart_repository.dart create mode 100644 lib/cart/views/my_pass_cart_page_view.dart delete mode 100644 lib/cart/views/my_pass_page_view.dart rename lib/{postcard/models/postcard_model.dart => checkout/bloc/allCoupons/all_coupons_bloc.dart} (100%) rename lib/{postcard/repository/postcard_repository.dart => checkout/bloc/allCoupons/all_coupons_event.dart} (100%) create mode 100644 lib/checkout/bloc/allCoupons/all_coupons_state.dart delete mode 100644 lib/checkout/bloc/checkout/checkout_bloc.dart delete mode 100644 lib/checkout/bloc/checkout/checkout_event.dart delete mode 100644 lib/checkout/bloc/checkout/checkout_state.dart create mode 100644 lib/checkout/models/all_coupons_model.dart create mode 100644 lib/checkout/repository/all_coupons_repository.dart delete mode 100644 lib/contact_us/contact_us_view.dart create mode 100644 lib/postcard/blocs/myPostCards/my_postcard_bloc.dart create mode 100644 lib/postcard/blocs/myPostCards/my_postcard_event.dart create mode 100644 lib/postcard/blocs/myPostCards/my_postcard_state.dart create mode 100644 lib/postcard/blocs/postcardCheckout/postcard_checkout_bloc.dart create mode 100644 lib/postcard/blocs/postcardCheckout/postcard_checkout_event.dart create mode 100644 lib/postcard/blocs/postcardCheckout/postcard_checkout_state.dart create mode 100644 lib/postcard/models/my_postcard_model.dart create mode 100644 lib/postcard/repository/my_postcard_repository.dart create mode 100644 lib/postcard/repository/postcard_checkout_repository.dart delete mode 100644 lib/postcard/views/my_orders_page_view.dart create mode 100644 lib/postcard/views/my_postcard_drafts_view.dart create mode 100644 lib/postcard/views/my_postcard_orders_view.dart create mode 100644 lib/postcard/views/my_postcard_preview_view.dart create mode 100644 lib/postcard/views/my_postcards_view.dart create mode 100644 lib/profile/bloc/contactUs/contact_us_bloc.dart create mode 100644 lib/profile/bloc/contactUs/contact_us_event.dart create mode 100644 lib/profile/bloc/contactUs/contact_us_state.dart create mode 100644 lib/profile/repository/contact_us_repository.dart create mode 100644 lib/profile/view/contact_us/contact_us_view.dart diff --git a/README.md b/README.md index 3f2cf54..fcb7f41 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A few resources to get you started if this is your first Flutter project: - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, +[online documentation](https://docs.flutter.dev/),which offers tutorials, samples, guidance on mobile development, and a full API reference.

Figma Link

diff --git a/assets/images/empty_postcard_drafts.png b/assets/images/empty_postcard_drafts.png new file mode 100644 index 0000000000000000000000000000000000000000..5f71aaeb1c8405ab422a20294bdb70d413bdde79 GIT binary patch literal 108428 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
E$yR0W82z8!0mO$;9CVc?7sL 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 literal 0 HcmV?d00001 diff --git a/lib/StripePayment/bloc/stripe_payment_bloc.dart b/lib/StripePayment/bloc/stripe_payment_bloc.dart index 39e336e..4b63321 100644 --- a/lib/StripePayment/bloc/stripe_payment_bloc.dart +++ b/lib/StripePayment/bloc/stripe_payment_bloc.dart @@ -14,6 +14,7 @@ class StripePaymentBloc extends Bloc { }) : _stripeService = stripeService ?? StripeService(), super(const StripePaymentInitial()) { on(_onInitiatePayment); + on(_onInitiatePaymentWithClientSecret); on(_onResetPaymentState); } @@ -66,6 +67,46 @@ class StripePaymentBloc extends Bloc { } } + /// 🆕 NEW: Handle payment with clientSecret directly from backend + Future _onInitiatePaymentWithClientSecret( + InitiatePaymentWithClientSecret event, + Emitter emit, + ) async { + try { + emit(const StripePaymentLoading()); + + // 1️⃣ Init Payment Sheet with clientSecret from backend + await Stripe.instance.initPaymentSheet( + paymentSheetParameters: SetupPaymentSheetParameters( + paymentIntentClientSecret: event.clientSecret, + merchantDisplayName: "CityCards", + style: ThemeMode.light, + ), + ); + + // 2️⃣ Show Payment Sheet + await Stripe.instance.presentPaymentSheet(); + + // ✅ SUCCESS + emit(const StripePaymentSuccess()); + } on StripeException catch (e) { + // Handle Stripe-specific errors + if (e.error.code == FailureCode.Canceled) { + emit(StripePaymentCancelled( + message: e.error.localizedMessage ?? 'Payment Cancelled', + )); + } else { + emit(StripePaymentFailure( + error: e.error.localizedMessage ?? 'Payment failed', + )); + } + } catch (e) { + emit(StripePaymentFailure( + error: e.toString(), + )); + } + } + void _onResetPaymentState( ResetPaymentState event, Emitter emit, diff --git a/lib/StripePayment/bloc/stripe_payment_event.dart b/lib/StripePayment/bloc/stripe_payment_event.dart index f356b54..470e359 100644 --- a/lib/StripePayment/bloc/stripe_payment_event.dart +++ b/lib/StripePayment/bloc/stripe_payment_event.dart @@ -20,6 +20,18 @@ class InitiatePayment extends StripePaymentEvent { List get props => [amount, currency]; } +/// 🆕 NEW: Event to initiate payment with clientSecret from backend +class InitiatePaymentWithClientSecret extends StripePaymentEvent { + final String clientSecret; + + const InitiatePaymentWithClientSecret({ + required this.clientSecret, + }); + + @override + List get props => [clientSecret]; +} + class ResetPaymentState extends StripePaymentEvent { const ResetPaymentState(); } \ No newline at end of file diff --git a/lib/StripePayment/repository/stripe_service.dart b/lib/StripePayment/repository/stripe_service.dart index dddd3d1..312daee 100644 --- a/lib/StripePayment/repository/stripe_service.dart +++ b/lib/StripePayment/repository/stripe_service.dart @@ -11,7 +11,7 @@ class StripeService { // ⚠️ TEMPORARY FALLBACK - Use secret key directly // TODO: Remove this and use backend when ready! - final String _stripeSecretKey = 'sk_test_51SrwZ7RtCkWyT4EmgS97odPlrKNj2TUxIkyu5L2i6qQyEpCivhYtEO6cW660UjBMoUsN1rUldvVhGx7RpGMarANp00Ntyi2Bp4'; // ← ADD YOUR SECRET KEY + final String _stripeSecretKey = ''; // ← ADD YOUR SECRET KEY Future createPaymentIntent({ required int amount, diff --git a/lib/buy_a_pass/view/buy_pass_view.dart b/lib/buy_a_pass/view/buy_pass_view.dart index 3ffd6e0..c01845a 100644 --- a/lib/buy_a_pass/view/buy_pass_view.dart +++ b/lib/buy_a_pass/view/buy_pass_view.dart @@ -240,102 +240,110 @@ class BuyPassContent extends StatelessWidget { itemBuilder: (context, index) { final offer = selectedCard.offers[index]; - return Container( - padding: EdgeInsets.symmetric( - horizontal: 6.w, - vertical: 6.h, - ), - decoration: BoxDecoration( - border: Border.all( - color: const Color(0xFFF95F62).withOpacity(.24), + return GestureDetector( + onTap: () { + Navigator.of(context).pushNamed( + RouteConstants.offerPassDetail, + arguments: offer.id, // ✅ pass offerId + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 6.w, + vertical: 6.h, ), - borderRadius: BorderRadius.circular(12.sp), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - /// Image - ClipRRect( - borderRadius: BorderRadius.circular(8.sp), - child: offer.mobileBannerImage != null && - offer.mobileBannerImage!.isNotEmpty - ? Image.network( - '${ApiUrls.baseUrl}/${offer.mobileBannerImage}', - width: double.infinity, - height: 120.5.h, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return Container( - width: double.infinity, - height: 120.5.h, - color: const Color(0xFFFEE7E7), - child: Icon( - Icons.local_offer, - size: 40.sp, - color: - const Color(0xFFF95F62).withOpacity(.6), - ), - ); - }, - loadingBuilder: - (context, child, loadingProgress) { - if (loadingProgress == null) return child; - - return Container( - width: double.infinity, - height: 120.5.h, - color: const Color(0xFFFEE7E7), - child: Center( - child: CircularProgressIndicator( - strokeWidth: 2, - color: const Color(0xFFF95F62), - value: loadingProgress - .expectedTotalBytes != - null - ? loadingProgress - .cumulativeBytesLoaded / - loadingProgress - .expectedTotalBytes! - : null, + decoration: BoxDecoration( + border: Border.all( + color: const Color(0xFFF95F62).withOpacity(.24), + ), + borderRadius: BorderRadius.circular(12.sp), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// Image + ClipRRect( + borderRadius: BorderRadius.circular(8.sp), + child: offer.mobileBannerImage != null && + offer.mobileBannerImage!.isNotEmpty + ? Image.network( + '${ApiUrls.baseUrl}/${offer.mobileBannerImage}', + width: double.infinity, + height: 120.5.h, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + width: double.infinity, + height: 120.5.h, + color: const Color(0xFFFEE7E7), + child: Icon( + Icons.local_offer, + size: 40.sp, + color: + const Color(0xFFF95F62).withOpacity(.6), ), - ), - ); - }, - ) - : Container( - width: double.infinity, - height: 120.5.h, - color: const Color(0xFFFEE7E7), - child: Icon( - Icons.local_offer, - size: 40.sp, - color: - const Color(0xFFF95F62).withOpacity(.6), + ); + }, + loadingBuilder: + (context, child, loadingProgress) { + if (loadingProgress == null) return child; + + return Container( + width: double.infinity, + height: 120.5.h, + color: const Color(0xFFFEE7E7), + child: Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: const Color(0xFFF95F62), + value: loadingProgress + .expectedTotalBytes != + null + ? loadingProgress + .cumulativeBytesLoaded / + loadingProgress + .expectedTotalBytes! + : null, + ), + ), + ); + }, + ) + : Container( + width: double.infinity, + height: 120.5.h, + color: const Color(0xFFFEE7E7), + child: Icon( + Icons.local_offer, + size: 40.sp, + color: + const Color(0xFFF95F62).withOpacity(.6), + ), ), ), - ), - SizedBox(height: 8.h), + SizedBox(height: 8.h), - /// Title - CustomText( - text: offer.title, - size: 18.sp, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + /// Title + CustomText( + text: offer.title, + size: 18.sp, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), - SizedBox(height: 8.h), + SizedBox(height: 8.h), - /// Offer Code - CustomText( - text: offer.description??"N/A", - color: Colors.black.withOpacity(.6), - size: 12.sp, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], + /// Offer Code + CustomText( + text: offer.description??"N/A", + color: Colors.black.withOpacity(.6), + size: 12.sp, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), ), ); }, @@ -386,35 +394,43 @@ class BuyPassContent extends StatelessWidget { color: Colors.grey[200], borderRadius: BorderRadius.circular(8.r), ), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.r), - child: attraction.thumbnail != null && - attraction.thumbnail!.isNotEmpty - ? Image.network( - attraction.thumbnail!, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return Icon( - Icons.location_on, - size: 40.sp, - color: Colors.grey[400], - ); - }, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) return child; - return Center( - child: SizedBox( - width: 20.w, - height: 20.w, - child: CircularProgressIndicator(strokeWidth: 2), - ), - ); - }, - ) - : Icon( - Icons.location_on, - size: 40.sp, - color: Colors.grey[400], + child: GestureDetector( + onTap: () { + // Navigator.of(context).pushNamed( + // RouteConstants.attractionDetails, + // arguments: attraction, + // ); + }, + child: ClipRRect( + borderRadius: BorderRadius.circular(8.r), + child: attraction.thumbnail != null && + attraction.thumbnail!.isNotEmpty + ? Image.network( + attraction.thumbnail!, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Icon( + Icons.location_on, + size: 40.sp, + color: Colors.grey[400], + ); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: SizedBox( + width: 20.w, + height: 20.w, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ); + }, + ) + : Icon( + Icons.location_on, + size: 40.sp, + color: Colors.grey[400], + ), ), ), ), @@ -447,12 +463,20 @@ class BuyPassContent extends StatelessWidget { ), SizedBox(height: 20.h), - Align( - alignment: Alignment.center, - child: CustomText( - text: "View All", - size: 12.sp, - color: Color(0xFFF95F62), + GestureDetector( + onTap: () { + Navigator.of(context).pushNamed( + RouteConstants.attractionsPage, + arguments: "home", + ); + }, + child: Align( + alignment: Alignment.center, + child: CustomText( + text: "View All", + size: 12.sp, + color: Color(0xFFF95F62), + ), ), ), SizedBox(height: 41.h), diff --git a/lib/buy_a_pass/widget/payment_card_view.dart b/lib/buy_a_pass/widget/payment_card_view.dart index f88b7f2..23d29c3 100644 --- a/lib/buy_a_pass/widget/payment_card_view.dart +++ b/lib/buy_a_pass/widget/payment_card_view.dart @@ -3,6 +3,7 @@ import 'package:citycards_customer/common_packages/custom_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../localPreference/local_preference.dart'; import '../models/checkout_model.dart'; import '../../checkout/view/checkout_view.dart'; // ✅ Import CheckoutView directly @@ -127,7 +128,7 @@ class PaymentCard extends StatelessWidget { ), SizedBox(height: 20.h), CustomFilledButton( - onTap: () { + onTap: () async { // ✅ Create checkout data final checkoutData = CheckoutData( cityName: city, @@ -144,6 +145,21 @@ class PaymentCard extends StatelessWidget { description: description, ); + await LocalPreference.setPassCart( + cityName: city, + heroImage: heroImage, + cardTypeName: cardType, + cardDisplayName: cardDisplayName, + themeColor: themeColor.value, // Convert Color to int + adultCount: adults, + childCount: children, + adultPrice: adultPrice, + childPrice: childPrice, + validityDuration: selectedValue, + totalPrice: totalPrice, + description: description, + ); + // ✅ DIRECT NAVIGATION - This fixes the route issue! Navigator.of(context).push( MaterialPageRoute( diff --git a/lib/cart/blocs/myPassCart/my_pass_cart_bloc.dart b/lib/cart/blocs/myPassCart/my_pass_cart_bloc.dart new file mode 100644 index 0000000..67607d4 --- /dev/null +++ b/lib/cart/blocs/myPassCart/my_pass_cart_bloc.dart @@ -0,0 +1,73 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../repository/my_pass_cart_repository.dart'; +import 'my_pass_cart_event.dart'; +import 'my_pass_cart_state.dart'; + +class MyPassCartBloc extends Bloc { + final MyPassCartRepository repository; + + MyPassCartBloc({required this.repository}) : super(const MyPassCartInitial()) { + on(_onFetchPassCart); + on(_onClearPassCart); + } + + /// Handle fetching pass cart data + Future _onFetchPassCart( + FetchPassCartEvent event, + Emitter emit, + ) async { + try { + if (kDebugMode) { + print('🔄 [BLOC] Fetching pass cart...'); + } + + emit(const MyPassCartLoading()); + + final cartData = await repository.fetchPassesCartByLocal(); + + if (cartData != null) { + if (kDebugMode) { + print('✅ [BLOC] Cart data loaded successfully'); + } + emit(MyPassCartLoaded(cartData: cartData)); + } else { + if (kDebugMode) { + print('ℹ️ [BLOC] Cart is empty'); + } + emit(const MyPassCartEmpty()); + } + } catch (e) { + if (kDebugMode) { + print('❌ [BLOC] Error fetching cart: $e'); + } + emit(MyPassCartError(message: e.toString())); + } + } + + /// Handle clearing pass cart + Future _onClearPassCart( + ClearPassCartEvent event, + Emitter emit, + ) async { + try { + if (kDebugMode) { + print('🔄 [BLOC] Clearing pass cart...'); + } + + // You can add clearPassCart method to repository if needed + // await repository.clearPassCartFromLocal(); + + emit(const MyPassCartEmpty()); + + if (kDebugMode) { + print('✅ [BLOC] Cart cleared successfully'); + } + } catch (e) { + if (kDebugMode) { + print('❌ [BLOC] Error clearing cart: $e'); + } + emit(MyPassCartError(message: e.toString())); + } + } +} \ No newline at end of file diff --git a/lib/cart/blocs/myPassCart/my_pass_cart_event.dart b/lib/cart/blocs/myPassCart/my_pass_cart_event.dart new file mode 100644 index 0000000..5bd32ad --- /dev/null +++ b/lib/cart/blocs/myPassCart/my_pass_cart_event.dart @@ -0,0 +1,18 @@ +import 'package:equatable/equatable.dart'; + +abstract class MyPassCartEvent extends Equatable { + const MyPassCartEvent(); + + @override + List get props => []; +} + +/// Event to fetch pass cart data from local database +class FetchPassCartEvent extends MyPassCartEvent { + const FetchPassCartEvent(); +} + +/// Event to clear pass cart +class ClearPassCartEvent extends MyPassCartEvent { + const ClearPassCartEvent(); +} \ No newline at end of file diff --git a/lib/cart/blocs/myPassCart/my_pass_cart_state.dart b/lib/cart/blocs/myPassCart/my_pass_cart_state.dart new file mode 100644 index 0000000..3d6ea24 --- /dev/null +++ b/lib/cart/blocs/myPassCart/my_pass_cart_state.dart @@ -0,0 +1,43 @@ +import 'package:equatable/equatable.dart'; + +abstract class MyPassCartState extends Equatable { + const MyPassCartState(); + + @override + List get props => []; +} + +/// Initial state +class MyPassCartInitial extends MyPassCartState { + const MyPassCartInitial(); +} + +/// Loading state when fetching cart data +class MyPassCartLoading extends MyPassCartState { + const MyPassCartLoading(); +} + +/// Loaded state with cart data +class MyPassCartLoaded extends MyPassCartState { + final Map cartData; + + const MyPassCartLoaded({required this.cartData}); + + @override + List get props => [cartData]; +} + +/// Empty state when no cart data exists +class MyPassCartEmpty extends MyPassCartState { + const MyPassCartEmpty(); +} + +/// Error state +class MyPassCartError extends MyPassCartState { + final String message; + + const MyPassCartError({required this.message}); + + @override + List get props => [message]; +} \ No newline at end of file diff --git a/lib/cart/repository/my_pass_cart_repository.dart b/lib/cart/repository/my_pass_cart_repository.dart new file mode 100644 index 0000000..c4b9be3 --- /dev/null +++ b/lib/cart/repository/my_pass_cart_repository.dart @@ -0,0 +1,35 @@ +import 'package:flutter/foundation.dart'; + +import '../../localPreference/local_preference.dart'; + +class MyPassCartRepository { + + /// Fetch pass cart data from local database + Future?> fetchPassesCartByLocal() async { + try { + if (kDebugMode) { + print('🔄 [REPO] Fetching pass cart from local database...'); + } + + final passCartData = await LocalPreference.getPassCart(); + + if (passCartData != null) { + if (kDebugMode) { + print('✅ [REPO] Pass cart retrieved successfully'); + print('📦 [REPO] Cart details: ${passCartData['card_display_name']} - ${passCartData['city_name']}'); + } + return passCartData; + } else { + if (kDebugMode) { + print('ℹ️ [REPO] No pass cart data found in local database'); + } + return null; + } + } catch (e) { + if (kDebugMode) { + print('❌ [REPO] Error fetching pass cart: $e'); + } + rethrow; + } + } +} \ No newline at end of file diff --git a/lib/cart/views/my_cart_view_page.dart b/lib/cart/views/my_cart_view_page.dart index 4005474..c1cead0 100644 --- a/lib/cart/views/my_cart_view_page.dart +++ b/lib/cart/views/my_cart_view_page.dart @@ -3,9 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../../common_packages/back_widget.dart'; -import '../blocs/pass_bloc.dart'; +import '../blocs/myPassCart/my_pass_cart_bloc.dart'; +import '../blocs/myPassCart/my_pass_cart_event.dart'; import '../blocs/postcard_bloc.dart'; -import 'my_pass_page_view.dart'; +import '../repository/my_pass_cart_repository.dart'; +import 'my_pass_cart_page_view.dart'; import 'my_postcard_page_view.dart'; class MyCartPage extends StatefulWidget { @@ -22,8 +24,14 @@ class _MyCartPageState extends State { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider(create: (_) => PassBloc()..add(LoadPasses())), - BlocProvider(create: (_) => PostCardBloc()..add(LoadPostCards())), + BlocProvider( + create: (_) => PostCardBloc()..add(LoadPostCards()), + ), + BlocProvider( + create: (_) => MyPassCartBloc( + repository: MyPassCartRepository(), + )..add(const FetchPassCartEvent()), + ), ], child: Scaffold( backgroundColor: Colors.white, diff --git a/lib/cart/views/my_pass_cart_page_view.dart b/lib/cart/views/my_pass_cart_page_view.dart new file mode 100644 index 0000000..6f8d349 --- /dev/null +++ b/lib/cart/views/my_pass_cart_page_view.dart @@ -0,0 +1,486 @@ +import 'package:citycards_customer/cart/views/view_pass_page_view.dart'; +import 'package:citycards_customer/checkout/widget/all_coupons_bottomsheet.dart'; +import 'package:citycards_customer/common_packages/custom_dashed_line.dart'; +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../login/view/login_email_bottomsheet.dart'; +import '../../common_packages/common_app_texts.dart'; +import '../../localPreference/local_preference.dart'; +import '../blocs/myPassCart/my_pass_cart_bloc.dart'; +import '../blocs/myPassCart/my_pass_cart_event.dart'; +import '../blocs/myPassCart/my_pass_cart_state.dart'; + +class MyPassesPage extends StatefulWidget { + const MyPassesPage({super.key}); + + @override + State createState() => _MyPassesPageState(); +} + +class _MyPassesPageState extends State { + // For coupon/discount management + String? appliedCouponCode; + double discountPercentage = 0.0; + + @override + void initState() { + super.initState(); + // Fetch cart data when page loads + context.read().add(const FetchPassCartEvent()); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is MyPassCartLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is MyPassCartLoaded) { + final cartData = state.cartData; + + // Extract data from cart + final String cityName = cartData['city_name'] as String? ?? ''; + final String heroImage = cartData['hero_image'] as String? ?? ''; + final String cardTypeName = cartData['card_type_name'] as String? ?? ''; + final String cardDisplayName = cartData['card_display_name'] as String? ?? ''; + final int themeColor = cartData['theme_color'] as int? ?? 0xFFF95FAF; + final int adultCount = cartData['adult_count'] as int? ?? 0; + final int childCount = cartData['child_count'] as int? ?? 0; + final double adultPrice = (cartData['adult_price'] as num?)?.toDouble() ?? 0.0; + final double childPrice = (cartData['child_price'] as num?)?.toDouble() ?? 0.0; + final int validityDuration = cartData['validity_duration'] as int? ?? 0; + final double totalPrice = (cartData['total_price'] as num?)?.toDouble() ?? 0.0; + final String? description = cartData['description'] as String?; + + // Calculate pricing + final double subtotal = totalPrice; + final double discountAmount = subtotal * (discountPercentage / 100); + final double taxRate = 0.05; // 5% tax + final double totalBeforeTax = subtotal - discountAmount; + final double taxAmount = totalBeforeTax * taxRate; + final double finalTotal = totalBeforeTax + taxAmount; + + // Determine if unlimited card + final bool isUnlimitedCard = cardTypeName == "unlimited_card"; + final String validityLabel = isUnlimitedCard + ? "$validityDuration Days" + : "$validityDuration Attractions"; + + return Column( + children: [ + SizedBox(height: 22.h), + Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all( + color: Color(themeColor).withOpacity(0.2), + ), + borderRadius: BorderRadius.circular(8.r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.r), + bottomLeft: Radius.circular(8.r), + ), + child: heroImage.isNotEmpty + ? Image.network( + heroImage, + width: 105.w, + height: 123.h, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Image.asset( + "assets/images/card_banner.png", + scale: 4, + width: 105.w, + height: 123.h, + fit: BoxFit.cover, + ); + }, + ) + : Image.asset( + "assets/images/card_banner.png", + scale: 4, + width: 105.w, + height: 123.h, + fit: BoxFit.cover, + ), + ), + SizedBox(width: 6.66.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText( + text: cityName, + weight: FontWeight.w500, + size: 16.sp, + ), + SizedBox(height: 5.h), + CustomText( + text: validityLabel, + color: Color(0xFF8E8E8E), + size: 12.sp, + ), + SizedBox(height: 5.h), + SizedBox( + width: MediaQuery.of(context).size.width * .5, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Image.asset( + 'assets/icons/adult.png', + scale: 4, + ), + SizedBox(width: 4.w), + CustomText( + text: "$adultCount ${adultCount == 1 ? 'adult' : 'adults'}", + color: Color(0xFF8E8E8E), + size: 12.sp, + ), + ], + ), + Row( + children: [ + Image.asset( + 'assets/icons/qty.png', + scale: 4, + ), + SizedBox(width: 4.w), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "Qty:", + style: TextStyle( + color: Color(0xFF8E8E8E), + fontSize: 12.sp, + ), + ), + TextSpan( + text: " ${adultCount + childCount}", + style: TextStyle( + color: Color(0xFF000000), + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + SizedBox(height: 5.h), + Row( + children: [ + Image.asset( + "assets/icons/kid.png", + scale: 4, + ), + SizedBox(width: 4.w), + CustomText( + text: "$childCount ${childCount == 1 ? 'Kid' : 'Kids'}", + color: Color(0xFF8E8E8E), + size: 12.sp, + ), + SizedBox(width: 53.w), + CustomText( + text: "\$${totalPrice.toStringAsFixed(2)}", + size: 24.sp, + weight: FontWeight.w500, + color: Color(0xFFF95F62), + ), + ], + ), + ], + ), + ], + ), + Container( + width: 35.w, + height: 123.h, + decoration: BoxDecoration( + color: Color(themeColor), + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(8.r), + topRight: Radius.circular(8.r), + ), + ), + child: RotatedBox( + quarterTurns: -1, + child: Center( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: "$cardDisplayName ", + style: TextStyle( + color: Colors.white, + fontSize: 16.sp, + ), + ), + // TextSpan( + // text: "Card", + // style: TextStyle( + // color: Colors.white, + // fontSize: 12.sp, + // ), + // ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + SizedBox(height: 15.h), + Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 12.h, + ), + decoration: BoxDecoration( + color: Color(0xFFFFF5F5), + borderRadius: BorderRadius.circular(8.r), + border: Border.all( + color: Color(0xFFBB474A).withOpacity(0.4), + width: 0.8, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText( + text: "Get 10% off on your first trip", + color: Color(0xFF262626), + size: 14.sp, + ), + SizedBox(height: 7.h), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (_) => AllCouponsBottomsheet(), + ); + }, + child: CustomText( + text: "View all coupons", + color: Color(0xFFF95F62), + size: 12, + ), + ), + SizedBox(width: 3.w), + Icon(Icons.arrow_right, color: Color(0xFFF95F62)), + ], + ), + ], + ), + const Spacer(), + GestureDetector( + onTap: () { + setState(() { + if (appliedCouponCode == null) { + appliedCouponCode = "FIRST10"; + discountPercentage = 10.0; + } else { + appliedCouponCode = null; + discountPercentage = 0.0; + } + }); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 20.w, + vertical: 10.h, + ), + decoration: BoxDecoration( + border: Border.all(color: Color(0xFFF95F62)), + borderRadius: BorderRadius.circular(8.r), + ), + child: CustomText( + text: appliedCouponCode != null ? "Remove" : "Apply", + color: Color(0xFFF95F62), + size: 14.sp, + ), + ), + ), + ], + ), + ), + SizedBox(height: 15.h), + DashedDivider( + color: Color(0xFFACACAC), + thickness: 1.h, + dashLength: 4, + dashSpace: 4, + ), + SizedBox(height: 10.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomText(text: "Subtotal", size: 14.sp), + CustomText( + text: "\$${subtotal.toStringAsFixed(2)}", + size: 14.sp, + weight: FontWeight.w500, + ), + ], + ), + SizedBox(height: 14.h), + if (discountPercentage > 0) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomText(text: "Discount", size: 14.sp), + CustomText( + text: "-\$${discountAmount.toStringAsFixed(2)} (${discountPercentage.toStringAsFixed(0)}%)", + size: 14.sp, + weight: FontWeight.w500, + color: Colors.green, + ), + ], + ), + SizedBox(height: 14.h), + ], + DashedDivider( + color: Color(0xFFACACAC), + thickness: 1.h, + dashLength: 4, + dashSpace: 4, + ), + SizedBox(height: 10.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText(text: 'Total', size: 14.sp), + SizedBox(height: 4.h), + CustomText( + text: "Including \$${taxAmount.toStringAsFixed(2)} in taxes", + size: 12.sp, + color: Colors.black.withOpacity(0.6), + ), + ], + ), + ), + CustomText( + text: "\$${finalTotal.toStringAsFixed(2)}", + size: 24.sp, + weight: FontWeight.w500, + ), + ], + ), + SizedBox(height: 150.h), + + // FutureBuilder for login check + FutureBuilder( + future: LocalPreference.getLogin(), + builder: (context, snapshot) { + final isLoggedIn = snapshot.data ?? false; + + return CustomFilledButton( + onTap: () { + if (!isLoggedIn) { + showModalBottomSheet( + backgroundColor: Colors.white, + context: context, + isScrollControlled: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (_) => const LoginEmailBottomsheet(), + ); + } else { + // Handle checkout logic for logged in user + // You can navigate to checkout or payment screen + print("✅ User is logged in, proceed to checkout"); + } + }, + width: double.infinity, + label: isLoggedIn ? "Checkout" : "Login to Checkout", + ); + }, + ), + SizedBox(height: 25.h), + ], + ); + } else if (state is MyPassCartEmpty) { + return Center( + child: Column( + children: [ + Image.asset("assets/gif/empty_cart.gif", width: 250.w), + CustomText( + text: "You do not have any passes", + size: 24.sp, + color: Color(0xFFF95F62), + ), + SizedBox(height: 4.h), + Text( + "Get a pass and get offers and discounts and more on your trip to your favourite city", + style: TextStyle(color: Color(0xFF656565), fontSize: 14.sp), + textAlign: TextAlign.center, + ), + ], + ), + ); + } else if (state is MyPassCartError) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error_outline, size: 60.sp, color: Colors.red), + SizedBox(height: 16.h), + CustomText( + text: "Error loading cart", + size: 16.sp, + color: Colors.red, + ), + SizedBox(height: 8.h), + CustomText( + text: state.message, + size: 12.sp, + color: Colors.grey, + ), + ], + ), + ); + } + + return const SizedBox.shrink(); + }, + ); + } +} \ No newline at end of file diff --git a/lib/cart/views/my_pass_page_view.dart b/lib/cart/views/my_pass_page_view.dart deleted file mode 100644 index ac3bf04..0000000 --- a/lib/cart/views/my_pass_page_view.dart +++ /dev/null @@ -1,379 +0,0 @@ -import 'package:citycards_customer/cart/views/view_pass_page_view.dart'; -import 'package:citycards_customer/checkout/widget/all_coupons_bottomsheet.dart'; -import 'package:citycards_customer/common_packages/custom_dashed_line.dart'; -import 'package:citycards_customer/common_packages/custom_filled_button.dart'; -import 'package:citycards_customer/common_packages/custom_text.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import '../../login/view/login_email_bottomsheet.dart'; -import '../../common_packages/common_app_texts.dart'; -import '../blocs/pass_bloc.dart'; - -class MyPassesPage extends StatelessWidget { - const MyPassesPage({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is PassLoading) { - return const Center(child: CircularProgressIndicator()); - } else if (state is PassLoaded) { - return - Column( - children: [ - SizedBox(height: 22.h), - Container( - decoration: BoxDecoration( - color: Colors.white, - border: Border.all( - color: Color(0xFFF95FAF).withOpacity(0.2), - ), - borderRadius: BorderRadius.circular(8.r), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(8.r), - bottomLeft: Radius.circular(8.r), - ), - child: Image.asset( - "assets/images/card_banner.png", - scale: 4, - width: 105.w, - height: 123.h, - fit: BoxFit.cover, - ), - ), - SizedBox(width: 6.66.w), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomText( - text: "Melbourne", - weight: FontWeight.w500, - size: 16.sp, - ), - SizedBox(height: 5.h), - CustomText( - text: "2 Days", - color: Color(0xFF8E8E8E), - size: 12.sp, - ), - SizedBox(height: 5.h), - - SizedBox( - width: MediaQuery.of(context).size.width * .5, - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Image.asset( - 'assets/icons/adult.png', - scale: 4, - ), - SizedBox(width: 4.w), - CustomText( - text: "3 adults", - color: Color(0xFF8E8E8E), - size: 12.sp, - ), - ], - ), - - Row( - children: [ - Image.asset( - 'assets/icons/qty.png', - scale: 4, - ), - SizedBox(width: 4.w), - Text.rich( - TextSpan( - children: [ - TextSpan( - text: "Qty:", - style: TextStyle( - color: Color(0xFF8E8E8E), - fontSize: 12.sp, - ), - ), - TextSpan( - text: " 2", - style: TextStyle( - color: Color(0xFF000000), - fontSize: 12.sp, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - ], - ), - ], - ), - ), - - SizedBox(height: 5.h), - Row( - children: [ - Image.asset( - "assets/icons/kid.png", - scale: 4, - ), - SizedBox(width: 4.w), - CustomText( - text: "3 Kids", - color: Color(0xFF8E8E8E), - size: 12.sp, - ), - - SizedBox(width: 53.w), - - CustomText( - text: "\$49.50", - size: 24.sp, - weight: FontWeight.w500, - color: Color(0xFFF95F62), - ), - ], - ), - ], - ), - ], - ), - - Container( - width: 35.w, - height: 123.h, - decoration: BoxDecoration( - color: Color(0xFFF97316), - borderRadius: BorderRadius.only( - bottomRight: Radius.circular(8.r), - topRight: Radius.circular(8.r), - ), - ), - child: RotatedBox( - quarterTurns: -1, - child: Center( - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: "${CommonAppText.selectiveCard} ", - style: TextStyle( - color: Colors.white, - fontSize: 16.sp, - ), - ), - TextSpan( - text: "Card", - style: TextStyle( - color: Colors.white, - fontSize: 12.sp, - ), - ), - ], - ), - ), - ), - ), - ), - ], - ), - ), - SizedBox(height: 15.h), - Container( - padding: EdgeInsets.symmetric( - horizontal: 12.w, - vertical: 12.h, - ), - decoration: BoxDecoration( - color: Color(0xFFFFF5F5), - borderRadius: BorderRadius.circular(8.r), - border: Border.all( - color: Color(0xFFBB474A).withOpacity(0.4), - width: 0.8, - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomText( - text: "Get 10% off on your first trip", - color: Color(0xFF262626), - size: 14.sp, - ), - SizedBox(height: 7.h), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - GestureDetector( - onTap: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(12.r), - ), - ), - builder: (_) => AllCouponsBottomsheet(), - ); - }, - child: CustomText( - text: "View all coupons", - color: Color(0xFFF95F62), - size: 12, - ), - ), - SizedBox(width: 3.w), - Icon(Icons.arrow_right, color: Color(0xFFF95F62)), - ], - ), - ], - ), - - const Spacer(), - Container( - padding: EdgeInsets.symmetric( - horizontal: 20.w, - vertical: 10.h, - ), - decoration: BoxDecoration( - border: Border.all(color: Color(0xFFF95F62)), - borderRadius: BorderRadius.circular(8.r), - ), - child: CustomText( - text: "Apply", - color: Color(0xFFF95F62), - size: 14.sp, - ), - ), - ], - ), - ), - - SizedBox(height: 15.h), - - DashedDivider( - color: Color(0xFFACACAC), - thickness: 1.h, - dashLength: 4, - dashSpace: 4, - ), - SizedBox(height: 10.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomText(text: "Subtotal", size: 14.sp), - CustomText( - text: "\$49.50", - size: 14.sp, - weight: FontWeight.w500, - ), - ], - ), - SizedBox(height: 14.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomText(text: "Discount", size: 14.sp), - CustomText( - text: "-7.20%", - size: 14.sp, - weight: FontWeight.w500, - ), - ], - ), - SizedBox(height: 10.h), - DashedDivider( - color: Color(0xFFACACAC), - thickness: 1.h, - dashLength: 4, - dashSpace: 4, - ), - SizedBox(height: 10.h), - - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomText(text: 'Total', size: 14.sp), - SizedBox(height: 4.h), - CustomText( - text: "Including \$2.24 in taxes", - size: 12.sp, - color: Colors.black.withOpacity(0.6), - ), - ], - ), - ), - CustomText( - text: "\$42.60", - size: 24.sp, - weight: FontWeight.w500, - ), - ], - ), - - SizedBox(height: 150.h,), - - CustomFilledButton( - onTap: () { - showModalBottomSheet( - backgroundColor: Colors.white, - context: context, - isScrollControlled: true, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(12.r), - ), - ), - builder: (_) => const LoginEmailBottomsheet(), - ); - }, - width: double.infinity, - label: "Login to Checkout", - ), - SizedBox(height: 25.h), - ], - ); - } - return Center( - child: Column( - children: [ - Image.asset("assets/gif/empty_cart.gif", width: 250.w), - CustomText( - text: "You do not have any passes", - size: 24.sp, - color: Color(0xFFF95F62), - ), - SizedBox(height: 4.h), - Text( - "Get a pass and get offers and discounts and more on your trip to your favourite city", - style: TextStyle(color: Color(0xFF656565), fontSize: 14.sp), - textAlign: TextAlign.center, - ), - ], - ), - ); - }, - ); - } -} diff --git a/lib/postcard/models/postcard_model.dart b/lib/checkout/bloc/allCoupons/all_coupons_bloc.dart similarity index 100% rename from lib/postcard/models/postcard_model.dart rename to lib/checkout/bloc/allCoupons/all_coupons_bloc.dart diff --git a/lib/postcard/repository/postcard_repository.dart b/lib/checkout/bloc/allCoupons/all_coupons_event.dart similarity index 100% rename from lib/postcard/repository/postcard_repository.dart rename to lib/checkout/bloc/allCoupons/all_coupons_event.dart diff --git a/lib/checkout/bloc/allCoupons/all_coupons_state.dart b/lib/checkout/bloc/allCoupons/all_coupons_state.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/checkout/bloc/checkout/checkout_bloc.dart b/lib/checkout/bloc/checkout/checkout_bloc.dart deleted file mode 100644 index 4cae026..0000000 --- a/lib/checkout/bloc/checkout/checkout_bloc.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; - -part 'checkout_event.dart'; -part 'checkout_state.dart'; - -/// BLoC for managing checkout screen state -class CheckoutBloc extends Bloc { - CheckoutBloc() : super(CheckoutState.initial()) { - // Handle apply coupon event - on(_onApplyCoupon); - - // Handle remove coupon event - on(_onRemoveCoupon); - - // Handle confirm purchase details event - on(_onConfirmPurchaseDetails); - - // Handle reset purchase details event - on(_onResetPurchaseDetails); - } - - /// Handle applying a coupon - void _onApplyCoupon(ApplyCouponEvent event, Emitter emit) { - emit(state.copyWith( - appliedCouponCode: event.couponCode, - discountPercentage: event.discountPercentage, - )); - } - - /// Handle removing a coupon - void _onRemoveCoupon(RemoveCouponEvent event, Emitter emit) { - emit(state.copyWith( - clearCoupon: true, - discountPercentage: 0.0, - )); - } - - /// Handle confirming purchase details - void _onConfirmPurchaseDetails( - ConfirmPurchaseDetailsEvent event, - Emitter emit, - ) { - emit(state.copyWith(isPurchaseDetailsConfirmed: true)); - } - - /// Handle resetting purchase details confirmation - void _onResetPurchaseDetails( - ResetPurchaseDetailsEvent event, - Emitter emit, - ) { - emit(state.copyWith(isPurchaseDetailsConfirmed: false)); - } -} \ No newline at end of file diff --git a/lib/checkout/bloc/checkout/checkout_event.dart b/lib/checkout/bloc/checkout/checkout_event.dart deleted file mode 100644 index 7ed9c86..0000000 --- a/lib/checkout/bloc/checkout/checkout_event.dart +++ /dev/null @@ -1,24 +0,0 @@ -part of 'checkout_bloc.dart'; - -/// Base class for all checkout events -abstract class CheckoutEvent {} - -/// Event to apply a coupon code -class ApplyCouponEvent extends CheckoutEvent { - final String couponCode; - final double discountPercentage; - - ApplyCouponEvent({ - required this.couponCode, - required this.discountPercentage, - }); -} - -/// Event to remove the applied coupon -class RemoveCouponEvent extends CheckoutEvent {} - -/// Event to confirm purchase details -class ConfirmPurchaseDetailsEvent extends CheckoutEvent {} - -/// Event to reset purchase details confirmation -class ResetPurchaseDetailsEvent extends CheckoutEvent {} \ No newline at end of file diff --git a/lib/checkout/bloc/checkout/checkout_state.dart b/lib/checkout/bloc/checkout/checkout_state.dart deleted file mode 100644 index 00db8c2..0000000 --- a/lib/checkout/bloc/checkout/checkout_state.dart +++ /dev/null @@ -1,52 +0,0 @@ -part of 'checkout_bloc.dart'; - -/// State class for checkout screen -class CheckoutState { - final String? appliedCouponCode; - final double discountPercentage; - final bool isPurchaseDetailsConfirmed; - - const CheckoutState({ - this.appliedCouponCode, - this.discountPercentage = 0.0, - this.isPurchaseDetailsConfirmed = false, - }); - - /// Initial state - factory CheckoutState.initial() { - return const CheckoutState(); - } - - /// Copy with method for immutable state updates - CheckoutState copyWith({ - String? appliedCouponCode, - double? discountPercentage, - bool? isPurchaseDetailsConfirmed, - bool clearCoupon = false, - }) { - return CheckoutState( - appliedCouponCode: clearCoupon ? null : appliedCouponCode ?? this.appliedCouponCode, - discountPercentage: discountPercentage ?? this.discountPercentage, - isPurchaseDetailsConfirmed: isPurchaseDetailsConfirmed ?? this.isPurchaseDetailsConfirmed, - ); - } - - /// Calculate discount amount based on subtotal - double calculateDiscountAmount(double subtotal) { - return subtotal * (discountPercentage / 100); - } - - /// Calculate tax amount - double calculateTaxAmount(double subtotal, {double taxRate = 0.05}) { - final totalBeforeTax = subtotal - calculateDiscountAmount(subtotal); - return totalBeforeTax * taxRate; - } - - /// Calculate final total - double calculateFinalTotal(double subtotal, {double taxRate = 0.05}) { - final discountAmount = calculateDiscountAmount(subtotal); - final totalBeforeTax = subtotal - discountAmount; - final taxAmount = totalBeforeTax * taxRate; - return totalBeforeTax + taxAmount; - } -} \ No newline at end of file diff --git a/lib/checkout/models/all_coupons_model.dart b/lib/checkout/models/all_coupons_model.dart new file mode 100644 index 0000000..60698de --- /dev/null +++ b/lib/checkout/models/all_coupons_model.dart @@ -0,0 +1,61 @@ +class AllCouponsModel { + final int id; + final String title; + final String? description; + final int cityXid; + final int discountPercent; + final String couponCode; + final DateTime startDateTime; + final DateTime endDateTime; + final bool showAtCheckout; + final String couponStatus; + final bool isActive; + + AllCouponsModel({ + required this.id, + required this.title, + this.description, + required this.cityXid, + required this.discountPercent, + required this.couponCode, + required this.startDateTime, + required this.endDateTime, + required this.showAtCheckout, + required this.couponStatus, + required this.isActive, + }); + + /// From JSON + factory AllCouponsModel.fromJson(Map json) { + return AllCouponsModel( + id: json['id'] as int, + title: json['title'] as String, + description: json['description'], + cityXid: json['cityXid'] as int, + discountPercent: json['discountPercent'] as int, + couponCode: json['couponCode'] as String, + startDateTime: DateTime.parse(json['startDateTime']), + endDateTime: DateTime.parse(json['endDateTime']), + showAtCheckout: json['showAtCheckout'] as bool, + couponStatus: json['couponStatus'] as String, + isActive: json['isActive'] as bool, + ); + } + + /// To JSON + Map toJson() { + return { + 'id': id, + 'title': title, + 'description': description, + 'cityXid': cityXid, + 'discountPercent': discountPercent, + 'couponCode': couponCode, + 'startDateTime': startDateTime.toIso8601String(), + 'endDateTime': endDateTime.toIso8601String(), + 'showAtCheckout': showAtCheckout, + 'couponStatus': couponStatus, + 'isActive': isActive, + }; + } +} diff --git a/lib/checkout/repository/all_coupons_repository.dart b/lib/checkout/repository/all_coupons_repository.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/checkout/view/checkout_view.dart b/lib/checkout/view/checkout_view.dart index 8813992..83e18be 100644 --- a/lib/checkout/view/checkout_view.dart +++ b/lib/checkout/view/checkout_view.dart @@ -5,15 +5,10 @@ import 'package:citycards_customer/common_packages/custom_filled_button.dart'; import 'package:citycards_customer/common_packages/custom_text.dart'; import 'package:citycards_customer/common_packages/custom_dashed_line.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; - import '../../StripePayment/view/stripe_payment.dart'; import '../../buy_a_pass/models/checkout_model.dart'; -import '../../common_packages/common_app_texts.dart'; import '../../localPreference/local_preference.dart'; -import '../../postcard/widgets/purchase_details_bottom_sheet.dart'; -import '../bloc/pass_purchase_details_bloc.dart'; import '../widget/pass_purchase_details_bottomsheet.dart'; class CheckoutView extends StatefulWidget { diff --git a/lib/common_packages/app_bar.dart b/lib/common_packages/app_bar.dart index 6180a3c..b53bbcc 100644 --- a/lib/common_packages/app_bar.dart +++ b/lib/common_packages/app_bar.dart @@ -1,8 +1,13 @@ +import 'package:citycards_customer/networkApiServices/api_urls.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../core/route_constants.dart'; import '../home/widgets/search_city_bottomsheet.dart'; +import '../localPreference/local_preference.dart'; +import '../profile/bloc/profile/profile_bloc.dart'; +import '../profile/bloc/profile/profile_state.dart'; class CommonAppBar extends StatelessWidget { const CommonAppBar({ @@ -115,11 +120,39 @@ class CommonAppBar extends StatelessWidget { rootNavigator: true, ).pushNamed(RouteConstants.profile); }, - child: CircleAvatar( - backgroundColor: const Color(0xffFFDFDF), - child: Image.asset( - "assets/images/profile_default_img.png", - ), + child: BlocBuilder( + builder: (context, state) { + String? imagePath; + + // ✅ Get image from profile state + if (state is ProfileLoaded) { + imagePath = state.profile.profileImage; + } + + // ✅ Build full image URL + final String? imageUrl = + (imagePath != null && imagePath.isNotEmpty) + ? "${ApiUrls.baseUrl}$imagePath" + : null; + + return CircleAvatar( + radius: 20.r, + backgroundColor: const Color(0xffFFDFDF), + + // ✅ Network image only if exists + backgroundImage: + (imageUrl != null && imageUrl.isNotEmpty) + ? NetworkImage(imageUrl) + : null, + + // ✅ Default fallback (unchanged) + child: (imageUrl == null || imageUrl.isEmpty) + ? Image.asset( + "assets/images/profile_default_img.png", + ) + : null, + ); + }, ), ), ], diff --git a/lib/contact_us/contact_us_view.dart b/lib/contact_us/contact_us_view.dart deleted file mode 100644 index ad56f7c..0000000 --- a/lib/contact_us/contact_us_view.dart +++ /dev/null @@ -1,237 +0,0 @@ -import 'package:citycards_customer/common_packages/app_bar.dart'; -import 'package:citycards_customer/common_packages/back_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:citycards_customer/common_packages/custom_text.dart'; -import 'package:citycards_customer/common_packages/custom_textfield.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; - -class ContactUsPage extends StatelessWidget { - const ContactUsPage({super.key}); - - @override - Widget build(BuildContext context) { - final TextEditingController firstNameController = TextEditingController(); - final TextEditingController lastNameController = TextEditingController(); - final TextEditingController emailController = TextEditingController(); - final TextEditingController phoneController = TextEditingController(); - final TextEditingController messageController = TextEditingController(); - - return Scaffold( - backgroundColor: Colors.white, - body: SafeArea( - child: SingleChildScrollView( - padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header bar - CommonAppBar(isWhiteLogo: false, isProfilePage: true, showDivider: true,), - - backWidget(context,"Contact Us", Colors.black), - SizedBox(height: 22.h), - - CustomText( - text: - "You can get in touch with us through the below platforms. Our team will contact you shortly", - size: 14.sp, - color: Colors.black.withOpacity(.6), - ), - SizedBox(height: 20.h), - - // Customer Support Section - Container( - padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 16.h), - decoration: BoxDecoration( - color: Color(0x00000005).withOpacity(.02), - borderRadius: BorderRadius.circular(12.r), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomText( - text: "Customer Support", - size: 18.sp, - weight: FontWeight.w500, - ), - SizedBox(height: 16.h), - _supportBox( - icon: Icons.phone, - title: "Contact Number", - subtitle: "+1012 3456 789", - action: "Tap to call", - ), - SizedBox(height: 12.h), - _supportBox( - icon: Icons.email_rounded, - title: "Email", - subtitle: "citycards24@gmail.com", - action: "Tap to email", - ), - SizedBox(height: 12.h), - _supportBox( - icon: Icons.location_on, - title: "Location", - subtitle: - "132 Dartmouth Street Boston, Massachusetts 02156 United States", - action: "View on map", - ), - ], - ), - ), - SizedBox(height: 24.h), - - // Text fields - CustomTextField( - label: "First Name", - hint: "Enter your first name", - controller: firstNameController, - ), - CustomTextField( - label: "Last Name", - hint: "Enter your last name", - controller: lastNameController, - ), - CustomTextField( - label: "Email", - hint: "Enter your email address", - controller: emailController, - ), - CustomTextField( - label: "Phone Number", - hint: "Enter your phone number", - controller: phoneController, - ), - - CustomTextField( - label: "Description", - hint: "Write your message here", - maxLines: 4, - controller: messageController, - ), - - // _descriptionField(messageController), - SizedBox(height: 24.h), - - // Submit Button - SizedBox( - width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFF95F62), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(38.r), - ), - padding: EdgeInsets.symmetric(vertical: 6.h), - ), - onPressed: () {}, - child: CustomText( - text: "Submit Ticket", - size: 16.sp, - weight: FontWeight.w500, - color: Colors.white, - ), - ), - ), - SizedBox(height: 20.h), - ], - ), - ), - ), - ); - } - - // --- Support Info Box --- - Widget _supportBox({ - required IconData icon, - required String title, - required String subtitle, - required String action, - }) { - return Container( - width: double.infinity, - padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), - border: Border.all(color: const Color(0xFFF95F62), width: 0.8), - color: Colors.white, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon(icon, color: const Color(0xFFF95F62), size: 32.sp), - SizedBox(width: 12.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomText( - text: title, - size: 11.sp, - weight: FontWeight.w600, - color: Color(0x00000000).withOpacity(.6), - ), - SizedBox(height: 6.h), - Text( - subtitle, - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w400, - color: Colors.black, - ), - ), - SizedBox(height: 2.h), - Text( - action, - style: TextStyle( - fontSize: 11.sp, - color: Color(0xFF000000).withOpacity(.4), - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ), - ], - ), - ); - } - - // --- Description Field --- - Widget _descriptionField(TextEditingController controller) { - return Padding( - padding: EdgeInsets.only(bottom: 12.h, left: 12.w, right: 12.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomText(text: "Description", size: 14.sp), - SizedBox(height: 6.h), - TextField( - controller: controller, - maxLines: 4, - decoration: InputDecoration( - hintText: "Write your message here", - hintStyle: TextStyle(fontSize: 12.sp, color: Color(0xFF8E8E8E)), - filled: true, - fillColor: const Color(0xFFFFF5F5), - contentPadding: EdgeInsets.symmetric( - horizontal: 24.w, - vertical: 12.h, - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.r), - borderSide: BorderSide( - color: const Color(0xBBC83B61).withOpacity(0.4), - width: .4.w, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.r), - borderSide: BorderSide(color: Color(0xFFF95F62), width: 1.w), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index f733105..47812b7 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -5,7 +5,6 @@ import 'package:citycards_customer/attractions/models/attraction_model.dart'; import 'package:citycards_customer/buy_a_pass/view/buy_pass_view.dart'; import 'package:citycards_customer/checkout/view/checkout_view.dart'; import 'package:citycards_customer/common_bloc/language_selection_bloc.dart'; -import 'package:citycards_customer/contact_us/contact_us_view.dart'; import 'package:citycards_customer/create_account/view/create_account_view.dart'; import 'package:citycards_customer/esim_offer/esim_offer_view.dart'; import 'package:citycards_customer/hotel_offer/hotel_offer_view.dart'; @@ -29,6 +28,7 @@ import '../cart/views/my_cart_view_page.dart'; import '../common_bloc/bottom_navigation_bloc.dart'; import '../home/views/home_page_view.dart'; import '../home/views/registered_user_home_page.dart'; +import '../profile/view/contact_us/contact_us_view.dart'; import '../profile/view/edit_profile/edit_profile_view.dart'; import '../profile/view/faq/faq_view.dart'; import '../profile/view/privacy/privacy_view.dart'; diff --git a/lib/create_account/bloc/create_account_bloc.dart b/lib/create_account/bloc/create_account_bloc.dart index 6c2abcd..cd62f97 100644 --- a/lib/create_account/bloc/create_account_bloc.dart +++ b/lib/create_account/bloc/create_account_bloc.dart @@ -45,6 +45,7 @@ class CreateAccountBloc extends Bloc { role: userModel.user.role, roleId: userModel.user.roleId, ); + await LocalPreference.setProfileImage(userModel.user.profileImage); emit(CreateAccountSuccess( message: response['message'] ?? 'Account created successfully', userData: response['data'] ?? {}, diff --git a/lib/create_account/models/create_account_model.dart b/lib/create_account/models/create_account_model.dart index 506d46e..7f28757 100644 --- a/lib/create_account/models/create_account_model.dart +++ b/lib/create_account/models/create_account_model.dart @@ -47,6 +47,7 @@ class User { final String lastName; final String fullName; final String emailAddress; + final String profileImage; // ✅ newly added final String role; final int roleId; @@ -56,6 +57,7 @@ class User { required this.lastName, required this.fullName, required this.emailAddress, + required this.profileImage, required this.role, required this.roleId, }); @@ -67,6 +69,7 @@ class User { lastName: json['lastName'] ?? '', fullName: json['fullName'] ?? '', emailAddress: json['emailAddress'] ?? '', + profileImage: json['profileImage'] ?? '', role: json['role'] ?? '', roleId: json['roleId'] ?? 0, ); @@ -79,6 +82,7 @@ class User { 'lastName': lastName, 'fullName': fullName, 'emailAddress': emailAddress, + 'profileImage': profileImage, 'role': role, 'roleId': roleId, }; diff --git a/lib/create_account/view/create_account_view.dart b/lib/create_account/view/create_account_view.dart index 3fa9f15..a0d3c5e 100644 --- a/lib/create_account/view/create_account_view.dart +++ b/lib/create_account/view/create_account_view.dart @@ -5,6 +5,9 @@ import 'package:citycards_customer/common_packages/custom_textfield.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../localPreference/local_preference.dart'; +import '../../profile/bloc/profile/profile_bloc.dart'; +import '../../profile/bloc/profile/profile_event.dart'; import '../bloc/create_account_bloc.dart'; import '../bloc/create_account_event.dart'; import '../bloc/create_account_state.dart'; @@ -52,11 +55,15 @@ class CreateAccountView extends StatelessWidget { repository: CreateAccountRepository(), ), child: BlocListener( - listener: (context, state) { + listener: (context, state) async { if (state is CreateAccountSuccess) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.message)), ); + await LocalPreference.setLogin(true); + final userId = await LocalPreference.getUserId(); + context.read().add(FetchProfileEvent(userId: userId!)); + context.read().add(CheckLoginStatusEvent()); Navigator.pop(context); Navigator.pop(context); } else if (state is CreateAccountFailure) { diff --git a/lib/home/views/first_time_user_home_page.dart b/lib/home/views/first_time_user_home_page.dart index af9859b..024b4a6 100644 --- a/lib/home/views/first_time_user_home_page.dart +++ b/lib/home/views/first_time_user_home_page.dart @@ -201,13 +201,23 @@ class _FirstTimeUserHomePageState extends State { // Determine if it's a network image or asset final isNetworkImage = imageUrl.startsWith('http'); - return ExploreCitiesCard( - name: city.cityName ?? 'N/A', - description: city.tagLine ?? 'N/A', - imageUrl: imageUrl, - individualPrice: '\$${city.indivisualTicketAmt ?? 0}+', - cityCardPrice: '\$${city.cityCardTicketAmt ?? 0}', - savingsText: city.saveLabel ?? 'Save \$0+', + return GestureDetector( + onTap: () async { + await LocalPreference.updateOnboardingPage(2); + await LocalPreference.setSelectedCityId(city.id!); + Navigator.pushReplacementNamed( + context, + RouteConstants.home, + ); + }, + child: ExploreCitiesCard( + name: city.cityName ?? 'N/A', + description: city.tagLine ?? 'N/A', + imageUrl: imageUrl, + individualPrice: '\$${city.indivisualTicketAmt ?? 0}+', + cityCardPrice: '\$${city.cityCardTicketAmt ?? 0}', + savingsText: city.saveLabel ?? 'Save \$0+', + ), ); }, ), diff --git a/lib/home/views/home_page_view.dart b/lib/home/views/home_page_view.dart index 07659a1..a7e4201 100644 --- a/lib/home/views/home_page_view.dart +++ b/lib/home/views/home_page_view.dart @@ -1,4 +1,5 @@ import 'package:citycards_customer/itinerary_creation/views/magic_itinerary_view.dart'; +import 'package:citycards_customer/postcard/views/my_postcards_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:citycards_customer/common_packages/custom_bottom_navbar.dart'; @@ -52,7 +53,7 @@ class _HomePageState extends State { buildOffstageNavigator( 3, currentIndex, - const PostcardPage(), + const MyPostCardsView(), _navigatorKeys[3], ), ], diff --git a/lib/home/views/registered_user_home_page.dart b/lib/home/views/registered_user_home_page.dart index 3521586..890fc2a 100644 --- a/lib/home/views/registered_user_home_page.dart +++ b/lib/home/views/registered_user_home_page.dart @@ -9,6 +9,10 @@ import '../../common_packages/app_bar.dart'; import '../../core/route_constants.dart'; import '../../localPreference/local_preference.dart'; import '../../networkApiServices/api_urls.dart'; +import '../../postcard/blocs/myPostCards/my_postcard_bloc.dart'; +import '../../postcard/blocs/myPostCards/my_postcard_event.dart'; +import '../../profile/bloc/profile/profile_bloc.dart'; +import '../../profile/bloc/profile/profile_event.dart'; import '../bloc/registeredHome/home_bloc.dart'; import '../bloc/registeredHome/home_event.dart'; import '../bloc/registeredHome/home_state.dart'; @@ -31,7 +35,29 @@ class _RegisteredUserHomePageState extends State { @override void initState() { super.initState(); + // _loadMyPostCards(); _checkAndShowCitySelection(); + _loadProfileIfLoggedIn(); + } + Future _loadProfileIfLoggedIn() async { + final userId = await LocalPreference.getUserId(); + + if (userId != null && mounted) { + context.read().add( + FetchProfileEvent(userId: userId), + ); + } + } + + Future _loadMyPostCards() async { + final userId = await LocalPreference.getUserId(); + + if (userId != null && mounted) { + context.read().add(FetchDraftPostCards()); + context.read().add(RefreshDraftPostCards()); + context.read().add(RefreshOrderPostCards()); + context.read().add(FetchOrderPostCards()); + } } Future _checkAndShowCitySelection() async { diff --git a/lib/intro_screens/views/intro_screen_view.dart b/lib/intro_screens/views/intro_screen_view.dart index c6c8158..84c8cc2 100644 --- a/lib/intro_screens/views/intro_screen_view.dart +++ b/lib/intro_screens/views/intro_screen_view.dart @@ -79,7 +79,12 @@ class _IntroScreensViewState extends State { right: 20, child: GestureDetector( onTap: (){ - Navigator.pushReplacementNamed(context,RouteConstants.home); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (_) => const FirstTimeUserHomePage(), + ), + ); }, child: Container( height: 48.h, diff --git a/lib/localPreference/local_database.dart b/lib/localPreference/local_database.dart index 764a184..4380d0f 100644 --- a/lib/localPreference/local_database.dart +++ b/lib/localPreference/local_database.dart @@ -67,10 +67,29 @@ class LocalDatabase { full_name TEXT NOT NULL, email_address TEXT NOT NULL, role TEXT NOT NULL, - role_id INTEGER NOT NULL + role_id INTEGER NOT NULL, + profile_image TEXT ) '''); + /// PASS CART TABLE + await db.execute(''' + CREATE TABLE pass_cart ( + id INTEGER PRIMARY KEY, + city_name TEXT NOT NULL, + hero_image TEXT NOT NULL, + card_type_name TEXT NOT NULL, + card_display_name TEXT NOT NULL, + theme_color INTEGER NOT NULL, + adult_count INTEGER NOT NULL, + child_count INTEGER NOT NULL, + adult_price REAL NOT NULL, + child_price REAL NOT NULL, + validity_duration INTEGER NOT NULL, + total_price REAL NOT NULL, + description TEXT + ) +'''); }, ); diff --git a/lib/localPreference/local_preference.dart b/lib/localPreference/local_preference.dart index 0414915..2e20eba 100644 --- a/lib/localPreference/local_preference.dart +++ b/lib/localPreference/local_preference.dart @@ -1,4 +1,5 @@ import 'package:sqflite/sqflite.dart'; +import 'package:flutter/foundation.dart'; import 'local_database.dart'; class LocalPreference { @@ -121,6 +122,18 @@ class LocalPreference { return false; } + static Future clearLogin() async { + final db = await LocalDatabase().database; + + await db.update( + 'login_state', + {'is_logged_in': 0}, + where: 'id = ?', + whereArgs: [1], + ); + } + + /// Set user tokens static Future setTokens({ required String accessToken, @@ -205,6 +218,7 @@ class LocalPreference { required String emailAddress, required String role, required int roleId, + String? profileImage, // Added optional profileImage parameter }) async { final db = await LocalDatabase().database; @@ -219,6 +233,7 @@ class LocalPreference { 'email_address': emailAddress, 'role': role, 'role_id': roleId, + 'profile_image': profileImage, // Include profile image }, conflictAlgorithm: ConflictAlgorithm.replace, ); @@ -240,5 +255,218 @@ class LocalPreference { return null; } + /// Set profile image with error handling + static Future setProfileImage(String imageUrl) async { + try { + final db = await LocalDatabase().database; + + final result = await db.update( + 'user_details', + {'profile_image': imageUrl}, + where: 'id = ?', + whereArgs: [1], + ); + + if (kDebugMode) { + print('✅ [LOCAL_PREF] Profile image saved: $imageUrl'); + print('📊 [LOCAL_PREF] Rows affected: $result'); + } + } catch (e) { + if (kDebugMode) { + print('❌ [LOCAL_PREF] Error saving profile image: $e'); + } + rethrow; + } + } + + /// Get profile image + static Future getProfileImage() async { + try { + final db = await LocalDatabase().database; + + final result = await db.query( + 'user_details', + columns: ['profile_image'], + where: 'id = ?', + whereArgs: [1], + ); + + if (result.isNotEmpty) { + final imageUrl = result.first['profile_image'] as String?; + if (kDebugMode && imageUrl != null) { + print('✅ [LOCAL_PREF] Retrieved profile image: $imageUrl'); + } + return imageUrl; + } + return null; + } catch (e) { + if (kDebugMode) { + print('❌ [LOCAL_PREF] Error getting profile image: $e'); + } + return null; + } + } + + /// Set pass cart data + static Future setPassCart({ + required String cityName, + required String heroImage, + required String cardTypeName, + required String cardDisplayName, + required int themeColor, + required int adultCount, + required int childCount, + required double adultPrice, + required double childPrice, + required int validityDuration, + required double totalPrice, + String? description, + }) async { + final db = await LocalDatabase().database; + + await db.insert( + 'pass_cart', + { + 'id': 1, + 'city_name': cityName, + 'hero_image': heroImage, + 'card_type_name': cardTypeName, + 'card_display_name': cardDisplayName, + 'theme_color': themeColor, + 'adult_count': adultCount, + 'child_count': childCount, + 'adult_price': adultPrice, + 'child_price': childPrice, + 'validity_duration': validityDuration, + 'total_price': totalPrice, + 'description': description, + }, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + + if (kDebugMode) { + print('✅ [LOCAL_PREF] Pass cart saved: $cardDisplayName for $cityName'); + } + } + + /// Get pass cart data + static Future?> getPassCart() async { + try { + final db = await LocalDatabase().database; + + final result = await db.query( + 'pass_cart', + where: 'id = ?', + whereArgs: [1], + ); + + if (result.isNotEmpty) { + if (kDebugMode) { + print('✅ [LOCAL_PREF] Retrieved pass cart data'); + } + return result.first; + } + return null; + } catch (e) { + if (kDebugMode) { + print('❌ [LOCAL_PREF] Error getting pass cart: $e'); + } + return null; + } + } + + static Future clearPassCart() async { + try { + final db = await LocalDatabase().database; + + await db.delete( + 'pass_cart', + where: 'id = ?', + whereArgs: [1], + ); + + if (kDebugMode) { + print('✅ [LOCAL_PREF] Pass cart cleared'); + } + } catch (e) { + if (kDebugMode) { + print('❌ [LOCAL_PREF] Error clearing pass cart: $e'); + } + } + } + + static Future clearUserDetails() async { + final db = await LocalDatabase().database; + + await db.update( + 'user_details', + { + 'user_id': null, + 'first_name': '', + 'last_name': '', + 'full_name': '', + 'email_address': '', + 'role': '', + 'role_id': 0, + 'profile_image': null, + }, + where: 'id = ?', + whereArgs: [1], + ); + } + + + static Future clearProfileImage() async { + try { + final db = await LocalDatabase().database; + + final result = await db.update( + 'user_details', + {'profile_image': null}, + where: 'id = ?', + whereArgs: [1], + ); + + if (kDebugMode) { + print('🧹 [LOCAL_PREF] Profile image cleared'); + print('📊 [LOCAL_PREF] Rows affected: $result'); + } + } catch (e) { + if (kDebugMode) { + print('❌ [LOCAL_PREF] Error clearing profile image: $e'); + } + rethrow; + } + } + + static Future resetAppData() async { + await clearLogin(); + await clearTokens(); + await clearUserDetails(); + await clearPassCart();// optional + await clearProfileImage();// optional + } + + static Future clearAllData() async { + try { + final db = await LocalDatabase().database; + + // Clear all tables + await db.delete('selected_city'); + await db.delete('login_state'); + await db.delete('user_tokens'); + await db.delete('user_details'); + await db.delete('pass_cart'); + + if (kDebugMode) { + print('🧹 [LOCAL_PREF] All local data cleared successfully'); + } + } catch (e) { + if (kDebugMode) { + print('❌ [LOCAL_PREF] Error clearing all local data: $e'); + } + rethrow; + } + } } \ No newline at end of file diff --git a/lib/login/bloc/verify/verify_bloc.dart b/lib/login/bloc/verify/verify_bloc.dart index 07b3378..0142249 100644 --- a/lib/login/bloc/verify/verify_bloc.dart +++ b/lib/login/bloc/verify/verify_bloc.dart @@ -41,6 +41,7 @@ class VerifyOtpBloc extends Bloc { role: userModel.user.role, roleId: userModel.user.roleId, ); + await LocalPreference.setProfileImage(userModel.user.profileImage); emit(VerifyOtpSuccess(response: userModel)); } catch (e) { emit(VerifyOtpError(errorMessage: e.toString())); diff --git a/lib/login/view/verify_otp_bottomsheet.dart b/lib/login/view/verify_otp_bottomsheet.dart index 595b01d..3c8146d 100644 --- a/lib/login/view/verify_otp_bottomsheet.dart +++ b/lib/login/view/verify_otp_bottomsheet.dart @@ -1,5 +1,6 @@ import 'package:citycards_customer/common_packages/custom_filled_button.dart'; import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/postcard/blocs/myPostCards/my_postcard_bloc.dart'; import 'package:citycards_customer/profile/bloc/profile/profile_bloc.dart'; import 'package:citycards_customer/profile/bloc/profile/profile_event.dart'; import 'package:flutter/material.dart'; @@ -8,6 +9,7 @@ import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../../core/route_constants.dart'; import '../../localPreference/local_preference.dart'; +import '../../postcard/blocs/myPostCards/my_postcard_event.dart'; import '../bloc/verify/verify_bloc.dart'; import '../bloc/verify/verify_event.dart'; import '../bloc/verify/verify_state.dart'; @@ -39,6 +41,11 @@ class _VerifyOtpBottomsheetState extends State { final userId = await LocalPreference.getUserId(); context.read().add(FetchProfileEvent(userId: userId!)); context.read().add(CheckLoginStatusEvent()); + context.read().add(CheckLoginStatus()); + context.read().add(FetchDraftPostCards()); + context.read().add(RefreshDraftPostCards()); + context.read().add(RefreshOrderPostCards()); + context.read().add(FetchOrderPostCards()); // User exists - navigate to home/dashboard // Navigator.of(context).pushReplacementNamed(RouteConstants.home); ScaffoldMessenger.of(context).showSnackBar( diff --git a/lib/main.dart b/lib/main.dart index 529fe33..0422f2e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,6 +17,8 @@ import 'home/repository/home_repository.dart'; import 'login/bloc/login/login_bloc.dart'; import 'login/repository/login_repository.dart'; import 'my_pass/blocs/my_pass_bloc.dart'; +import 'postcard/blocs/myPostCards/my_postcard_bloc.dart'; +import 'postcard/repository/my_postcard_repository.dart'; import 'profile/bloc/profile/profile_bloc.dart'; import 'search_offers/repository/offers_repository.dart'; import 'search_offers/view/search_offers_with_listing.dart'; @@ -74,6 +76,11 @@ class MyApp extends StatelessWidget { child: const OffersScreen(), ), BlocProvider(create: (context) => ProfileBloc()), + BlocProvider( + create: (context) => MyPostCardBloc( + repository: MyPostCardsRepository(), + ), + ), ], child: MaterialApp( onGenerateRoute: _appRouter.onGenerateRoute, diff --git a/lib/networkApiServices/api_urls.dart b/lib/networkApiServices/api_urls.dart index 07c1262..3e10e99 100644 --- a/lib/networkApiServices/api_urls.dart +++ b/lib/networkApiServices/api_urls.dart @@ -15,10 +15,13 @@ class ApiUrls { static const offers = "$baseUrl/mobile/list/offers"; static const buyAPass = "$baseUrl/mobile/pass"; static const offersDetails = "$baseUrl/mobile/list/offers"; + static const myPostCards = "$baseUrl/mobile/postcards/all"; //Post Apis static const createAccount = "$baseUrl/mobile/user/register"; static const sendOtp = "$baseUrl/mobile/send-otp"; static const verifyOtp = "$baseUrl/mobile/user/verify-otp"; + static const submitTicket = "$baseUrl/mobile/user/support"; + static const createPostCard = "$baseUrl/mobile/postcards"; } \ No newline at end of file diff --git a/lib/postcard/blocs/myPostCards/my_postcard_bloc.dart b/lib/postcard/blocs/myPostCards/my_postcard_bloc.dart new file mode 100644 index 0000000..fa9b4d5 --- /dev/null +++ b/lib/postcard/blocs/myPostCards/my_postcard_bloc.dart @@ -0,0 +1,201 @@ +import 'package:citycards_customer/localPreference/local_preference.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'dart:developer' as developer; +import '../../repository/my_postcard_repository.dart'; +import 'my_postcard_event.dart'; +import 'my_postcard_state.dart'; + +class MyPostCardBloc extends Bloc { + final MyPostCardsRepository repository; + + MyPostCardBloc({required this.repository}) : super(const MyPostCardInitial()) { + on(_onCheckLoginStatus); + on(_onFetchDraftPostCards); + on(_onFetchOrderPostCards); + on(_onRefreshDraftPostCards); + on(_onRefreshOrderPostCards); + } + + /// Handle checking login status + Future _onCheckLoginStatus( + CheckLoginStatus event, + Emitter emit, + ) async { + developer.log('🔍 Checking login status...', name: 'MyPostCardBloc'); + emit(const MyPostCardCheckingLogin()); + + try { + final isLogin = await LocalPreference.getLogin(); + developer.log('📊 Login status: $isLogin', name: 'MyPostCardBloc'); + + if (isLogin) { + developer.log('✅ User is logged in - initializing state', name: 'MyPostCardBloc'); + // User is logged in, initialize with empty lists and loading states + emit(const MyPostCardLoaded( + draftPostCards: [], + orderPostCards: [], + isDraftLoading: true, + isOrderLoading: true, + )); + + // Fetch both drafts and orders + add(const FetchDraftPostCards()); + add(const FetchOrderPostCards()); + } else { + developer.log('❌ User is NOT logged in - emitting MyPostCardNotLoggedIn', name: 'MyPostCardBloc'); + // User is not logged in + emit(const MyPostCardNotLoggedIn()); + } + } catch (error) { + developer.log('⚠️ Error checking login: $error', name: 'MyPostCardBloc'); + // If there's an error checking login, treat as not logged in + emit(const MyPostCardNotLoggedIn()); + } + } + + /// Handle fetching draft postcards + Future _onFetchDraftPostCards( + FetchDraftPostCards event, + Emitter emit, + ) async { + developer.log('📥 Fetching draft postcards...', name: 'MyPostCardBloc'); + // Get current state + final currentState = state; + + if (currentState is MyPostCardLoaded) { + // Set draft loading to true + emit(currentState.copyWith(isDraftLoading: true)); + } + + try { + final draftPostCards = await repository.fetchMyPostCards(type: 'draft'); + developer.log('✅ Draft postcards fetched: ${draftPostCards.length} items', name: 'MyPostCardBloc'); + + if (state is MyPostCardLoaded) { + // Update with fetched drafts + emit((state as MyPostCardLoaded).copyWith( + draftPostCards: draftPostCards, + isDraftLoading: false, + )); + } else { + developer.log('⚠️ State is not MyPostCardLoaded, creating new state', name: 'MyPostCardBloc'); + // Fallback: create new loaded state (shouldn't normally happen) + emit(MyPostCardLoaded( + draftPostCards: draftPostCards, + orderPostCards: const [], + isDraftLoading: false, + isOrderLoading: false, + )); + } + } catch (error) { + developer.log('❌ Error fetching drafts: $error', name: 'MyPostCardBloc'); + // Keep current lists but stop loading + if (state is MyPostCardLoaded) { + emit((state as MyPostCardLoaded).copyWith(isDraftLoading: false)); + } + + // Emit error state + emit(MyPostCardError( + errorMessage: error.toString(), + errorType: 'draft', + )); + } + } + + /// Handle fetching order postcards + Future _onFetchOrderPostCards( + FetchOrderPostCards event, + Emitter emit, + ) async { + developer.log('📥 Fetching order postcards...', name: 'MyPostCardBloc'); + // Get current state + final currentState = state; + + if (currentState is MyPostCardLoaded) { + // Set order loading to true + emit(currentState.copyWith(isOrderLoading: true)); + } + + try { + final orderPostCards = await repository.fetchMyPostCards(type: 'orders'); + developer.log('✅ Order postcards fetched: ${orderPostCards.length} items', name: 'MyPostCardBloc'); + + if (state is MyPostCardLoaded) { + // Update with fetched orders + emit((state as MyPostCardLoaded).copyWith( + orderPostCards: orderPostCards, + isOrderLoading: false, + )); + } else { + developer.log('⚠️ State is not MyPostCardLoaded, creating new state', name: 'MyPostCardBloc'); + // Fallback: create new loaded state (shouldn't normally happen) + emit(MyPostCardLoaded( + draftPostCards: const [], + orderPostCards: orderPostCards, + isDraftLoading: false, + isOrderLoading: false, + )); + } + } catch (error) { + developer.log('❌ Error fetching orders: $error', name: 'MyPostCardBloc'); + // Keep current lists but stop loading + if (state is MyPostCardLoaded) { + emit((state as MyPostCardLoaded).copyWith(isOrderLoading: false)); + } + + // Emit error state + emit(MyPostCardError( + errorMessage: error.toString(), + errorType: 'order', + )); + } + } + + /// Handle refreshing draft postcards + Future _onRefreshDraftPostCards( + RefreshDraftPostCards event, + Emitter emit, + ) async { + developer.log('🔄 Refreshing draft postcards...', name: 'MyPostCardBloc'); + try { + final draftPostCards = await repository.fetchMyPostCards(type: 'draft'); + developer.log('✅ Draft postcards refreshed: ${draftPostCards.length} items', name: 'MyPostCardBloc'); + + if (state is MyPostCardLoaded) { + emit((state as MyPostCardLoaded).copyWith( + draftPostCards: draftPostCards, + )); + } + } catch (error) { + developer.log('❌ Error refreshing drafts: $error', name: 'MyPostCardBloc'); + emit(MyPostCardError( + errorMessage: error.toString(), + errorType: 'draft', + )); + } + } + + /// Handle refreshing order postcards + Future _onRefreshOrderPostCards( + RefreshOrderPostCards event, + Emitter emit, + ) async { + developer.log('🔄 Refreshing order postcards...', name: 'MyPostCardBloc'); + try { + final orderPostCards = await repository.fetchMyPostCards(type: 'orders'); + developer.log('✅ Order postcards refreshed: ${orderPostCards.length} items', name: 'MyPostCardBloc'); + + if (state is MyPostCardLoaded) { + emit((state as MyPostCardLoaded).copyWith( + orderPostCards: orderPostCards, + )); + } + } catch (error) { + developer.log('❌ Error refreshing orders: $error', name: 'MyPostCardBloc'); + emit(MyPostCardError( + errorMessage: error.toString(), + errorType: 'order', + )); + } + } +} \ No newline at end of file diff --git a/lib/postcard/blocs/myPostCards/my_postcard_event.dart b/lib/postcard/blocs/myPostCards/my_postcard_event.dart new file mode 100644 index 0000000..7c25209 --- /dev/null +++ b/lib/postcard/blocs/myPostCards/my_postcard_event.dart @@ -0,0 +1,33 @@ +import 'package:equatable/equatable.dart'; + +abstract class MyPostCardEvent extends Equatable { + const MyPostCardEvent(); + + @override + List get props => []; +} + +/// Event to check login status +class CheckLoginStatus extends MyPostCardEvent { + const CheckLoginStatus(); +} + +/// Event to fetch draft postcards +class FetchDraftPostCards extends MyPostCardEvent { + const FetchDraftPostCards(); +} + +/// Event to fetch order postcards +class FetchOrderPostCards extends MyPostCardEvent { + const FetchOrderPostCards(); +} + +/// Event to refresh draft postcards +class RefreshDraftPostCards extends MyPostCardEvent { + const RefreshDraftPostCards(); +} + +/// Event to refresh order postcards +class RefreshOrderPostCards extends MyPostCardEvent { + const RefreshOrderPostCards(); +} \ No newline at end of file diff --git a/lib/postcard/blocs/myPostCards/my_postcard_state.dart b/lib/postcard/blocs/myPostCards/my_postcard_state.dart new file mode 100644 index 0000000..cef9dd7 --- /dev/null +++ b/lib/postcard/blocs/myPostCards/my_postcard_state.dart @@ -0,0 +1,76 @@ +import 'package:equatable/equatable.dart'; +import '../../models/my_postcard_model.dart'; + +abstract class MyPostCardState extends Equatable { + const MyPostCardState(); + + @override + List get props => []; +} + +/// Initial state +class MyPostCardInitial extends MyPostCardState { + const MyPostCardInitial(); +} + +/// State to check login status +class MyPostCardCheckingLogin extends MyPostCardState { + const MyPostCardCheckingLogin(); +} + +/// State when user is not logged in +class MyPostCardNotLoggedIn extends MyPostCardState { + const MyPostCardNotLoggedIn(); +} + +/// Combined state that holds both drafts and orders +class MyPostCardLoaded extends MyPostCardState { + final List draftPostCards; + final List orderPostCards; + final bool isDraftLoading; + final bool isOrderLoading; + + const MyPostCardLoaded({ + required this.draftPostCards, + required this.orderPostCards, + this.isDraftLoading = false, + this.isOrderLoading = false, + }); + + @override + List get props => [ + draftPostCards, + orderPostCards, + isDraftLoading, + isOrderLoading, + ]; + + /// Helper method to create a copy with updated values + MyPostCardLoaded copyWith({ + List? draftPostCards, + List? orderPostCards, + bool? isDraftLoading, + bool? isOrderLoading, + }) { + return MyPostCardLoaded( + draftPostCards: draftPostCards ?? this.draftPostCards, + orderPostCards: orderPostCards ?? this.orderPostCards, + isDraftLoading: isDraftLoading ?? this.isDraftLoading, + isOrderLoading: isOrderLoading ?? this.isOrderLoading, + ); + } +} + +/// Error state +class MyPostCardError extends MyPostCardState { + final String errorMessage; + final String errorType; // 'draft' or 'order' + + const MyPostCardError({ + required this.errorMessage, + required this.errorType, + }); + + @override + List get props => [errorMessage, errorType]; +} \ No newline at end of file diff --git a/lib/postcard/blocs/postcardCheckout/postcard_checkout_bloc.dart b/lib/postcard/blocs/postcardCheckout/postcard_checkout_bloc.dart new file mode 100644 index 0000000..016b511 --- /dev/null +++ b/lib/postcard/blocs/postcardCheckout/postcard_checkout_bloc.dart @@ -0,0 +1,243 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../repository/postcard_checkout_repository.dart'; +import 'postcard_checkout_event.dart'; +import 'postcard_checkout_state.dart'; + +class PostcardCheckoutBloc + extends Bloc { + final CreatePostCardRepository repository; + + PostcardCheckoutBloc({required this.repository}) + : super(const PostcardCheckoutState()) { + on(_onUpdateAddress); + on(_onUpdateContent); + on(_onUpdateCheckoutData); + on(_onSaveAsDraft); + on(_onSubmitPostcard); + on(_onConfirmPayment); // 🆕 NEW + } + + void _onUpdateAddress( + UpdateAddressEvent event, Emitter emit) { + emit(state.copyWith( + countryName: event.countryName, + cityName: event.cityName, + stateName: event.stateName, + zipCode: event.zipCode, + address1: event.address1, + address2: event.address2, + )); + } + + void _onUpdateContent( + UpdatePostcardContentEvent event, Emitter emit) { + emit(state.copyWith( + pcTitle: event.pcTitle, + pcContent: event.pcContent, + pcImageFile: event.pcImageFile, + )); + } + + void _onUpdateCheckoutData( + UpdateCheckoutDataEvent event, Emitter emit) { + emit(state.copyWith( + countryName: event.countryName, + cityName: event.cityName, + stateName: event.stateName, + zipCode: event.zipCode, + address1: event.address1, + address2: event.address2, + pcTitle: event.pcTitle, + pcContent: event.pcContent, + pcImageFile: event.pcImageFile, + pcNumber: event.pcNumber, + pcDatetime: event.pcDatetime, + fullname: event.fullname, + emailAddress: event.emailAddress, + mobileNumber: event.mobileNumber, + isdCode: event.isdCode, + isForSelf: event.isForSelf, + baseAmount: event.baseAmount, + totalTaxAmount: event.totalTaxAmount, + totalAmount: event.totalAmount, + )); + } + + Future _onSaveAsDraft( + SaveAsDraftEvent event, Emitter emit) async { + emit(state.copyWith(isLoading: true, error: null, isSuccess: false)); + + try { + // Validate that image file exists before submitting + if (state.pcImageFile == null) { + emit(state.copyWith( + isLoading: false, + error: 'Please select a postcard image', + isSuccess: false, + )); + return; + } + + final response = await repository.createPostCard( + countryName: state.countryName, + cityName: state.cityName, + stateName: state.stateName, + zipCode: state.zipCode, + address1: state.address1.isNotEmpty ? state.address1 : null, + address2: state.address2.isNotEmpty ? state.address2 : null, + pcTitle: state.pcTitle, + pcContent: state.pcContent, + pcImageFile: state.pcImageFile!, + pcNumber: state.pcNumber, + pcDatetime: state.pcDatetime, + fullname: state.fullname, + emailAddress: state.emailAddress, + mobileNumber: state.mobileNumber, + isdCode: state.isdCode, + isForSelf: state.isForSelf, + isDraft: true, // Save as draft + baseAmount: state.baseAmount, + totalTaxAmount: state.totalTaxAmount, + totalAmount: state.totalAmount, + ); + + // Extract order ID from response if available + final orderId = response['orderId']?.toString() ?? + response['order_id']?.toString() ?? + response['id']?.toString(); + + emit(state.copyWith( + isLoading: false, + isSuccess: true, + isDraft: true, + orderId: orderId, + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + error: e.toString(), + isSuccess: false, + )); + } + } + + Future _onSubmitPostcard( + SubmitPostcardEvent event, Emitter emit) async { + emit(state.copyWith(isLoading: true, error: null, isSuccess: false)); + + try { + // Validate that image file exists before submitting + if (state.pcImageFile == null) { + emit(state.copyWith( + isLoading: false, + error: 'Please select a postcard image', + isSuccess: false, + )); + return; + } + + final response = await repository.createPostCard( + countryName: state.countryName, + cityName: state.cityName, + stateName: state.stateName, + zipCode: state.zipCode, + address1: state.address1.isNotEmpty ? state.address1 : null, + address2: state.address2.isNotEmpty ? state.address2 : null, + pcTitle: state.pcTitle, + pcContent: state.pcContent, + pcImageFile: state.pcImageFile!, + pcNumber: state.pcNumber, + pcDatetime: state.pcDatetime, + fullname: state.fullname, + emailAddress: state.emailAddress, + mobileNumber: state.mobileNumber, + isdCode: state.isdCode, + isForSelf: state.isForSelf, + isDraft: false, // Final submission (payment) + baseAmount: state.baseAmount, + totalTaxAmount: state.totalTaxAmount, + totalAmount: state.totalAmount, + ); + + // 🆕 Parse response from backend + // Expected: {"postcardId": 16, "clientSecret": "pi_3Sx0yjRtCkWyT4Em1MKw1FeU_secret_S8M74wnEhTRC9lUz9RqJnuuqg"} + + final postcardId = response['postcardId'] as int?; + final clientSecret = response['clientSecret'] as String?; + + // Also try alternative key names in case backend uses different naming + final orderId = response['orderId']?.toString() ?? + response['order_id']?.toString() ?? + response['id']?.toString(); + + // Validate clientSecret is present + if (clientSecret == null || clientSecret.isEmpty) { + emit(state.copyWith( + isLoading: false, + error: 'Payment initialization failed - no client secret received from server', + isSuccess: false, + )); + return; + } + + // 🆕 Emit success with clientSecret for payment processing + emit(state.copyWith( + isLoading: false, + isSuccess: true, + isDraft: false, + postcardId: postcardId, + clientSecret: clientSecret, // This will trigger payment flow + orderId: orderId, + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + error: e.toString(), + isSuccess: false, + )); + } + } + + /// 🆕 Confirm payment after Stripe payment completes + /// This should be called after Stripe payment succeeds or fails + Future _onConfirmPayment( + ConfirmPaymentEvent event, Emitter emit) async { + + // Validate postcardId exists + if (state.postcardId == null) { + emit(state.copyWith( + confirmationError: 'Cannot confirm payment - postcard ID is missing', + isConfirmingPayment: false, + isPaymentConfirmed: false, + )); + return; + } + + emit(state.copyWith( + isConfirmingPayment: true, + confirmationError: null, + isPaymentConfirmed: false, + )); + + try { + final response = await repository.confirmPayment( + postcardId: state.postcardId!, + stripeStatus: event.stripeStatus, + paymentStatus: event.paymentStatus, + ); + + // Payment confirmation successful + emit(state.copyWith( + isConfirmingPayment: false, + isPaymentConfirmed: true, + confirmationError: null, + )); + } catch (e) { + emit(state.copyWith( + isConfirmingPayment: false, + isPaymentConfirmed: false, + confirmationError: e.toString(), + )); + } + } +} \ No newline at end of file diff --git a/lib/postcard/blocs/postcardCheckout/postcard_checkout_event.dart b/lib/postcard/blocs/postcardCheckout/postcard_checkout_event.dart new file mode 100644 index 0000000..765e6a7 --- /dev/null +++ b/lib/postcard/blocs/postcardCheckout/postcard_checkout_event.dart @@ -0,0 +1,97 @@ +import 'dart:io'; + +abstract class PostcardCheckoutEvent {} + +/// Page 1 – Address +class UpdateAddressEvent extends PostcardCheckoutEvent { + final String countryName; + final String cityName; + final String stateName; + final String zipCode; + final String address1; + final String address2; + + UpdateAddressEvent({ + required this.countryName, + required this.cityName, + required this.stateName, + required this.zipCode, + required this.address1, + required this.address2, + }); +} + +/// Page 2 – Postcard content +class UpdatePostcardContentEvent extends PostcardCheckoutEvent { + final String pcTitle; + final String pcContent; + final File? pcImageFile; // ⭐ CHANGED: File instead of String + + UpdatePostcardContentEvent({ + required this.pcTitle, + required this.pcContent, + this.pcImageFile, // ⭐ CHANGED: nullable File + }); +} + +/// Update all checkout data at once +class UpdateCheckoutDataEvent extends PostcardCheckoutEvent { + final String? countryName; + final String? cityName; + final String? stateName; + final String? zipCode; + final String? address1; + final String? address2; + final String? pcTitle; + final String? pcContent; + final File? pcImageFile; // ⭐ CHANGED: File instead of String + final String? pcNumber; + final String? pcDatetime; + final String? fullname; + final String? emailAddress; + final String? mobileNumber; + final String? isdCode; + final bool? isForSelf; + final double? baseAmount; + final double? totalTaxAmount; + final double? totalAmount; + + UpdateCheckoutDataEvent({ + this.countryName, + this.cityName, + this.stateName, + this.zipCode, + this.address1, + this.address2, + this.pcTitle, + this.pcContent, + this.pcImageFile, // ⭐ CHANGED + this.pcNumber, + this.pcDatetime, + this.fullname, + this.emailAddress, + this.mobileNumber, + this.isdCode, + this.isForSelf, + this.baseAmount, + this.totalTaxAmount, + this.totalAmount, + }); +} + +/// Save as draft +class SaveAsDraftEvent extends PostcardCheckoutEvent {} + +/// Page 3 – Checkout & submit (Pay button) +class SubmitPostcardEvent extends PostcardCheckoutEvent {} + +/// 🆕 Confirm payment after successful Stripe payment +class ConfirmPaymentEvent extends PostcardCheckoutEvent { + final String stripeStatus; // e.g., "succeeded", "requires_payment_method" + final String paymentStatus; // e.g., "success", "failed" + + ConfirmPaymentEvent({ + required this.stripeStatus, + required this.paymentStatus, + }); +} \ No newline at end of file diff --git a/lib/postcard/blocs/postcardCheckout/postcard_checkout_state.dart b/lib/postcard/blocs/postcardCheckout/postcard_checkout_state.dart new file mode 100644 index 0000000..ff165bb --- /dev/null +++ b/lib/postcard/blocs/postcardCheckout/postcard_checkout_state.dart @@ -0,0 +1,136 @@ +import 'dart:io'; + +class PostcardCheckoutState { + final String countryName; + final String cityName; + final String stateName; + final String zipCode; + final String address1; + final String address2; + + final String pcTitle; + final String pcContent; + final File? pcImageFile; + final String pcNumber; + final String pcDatetime; + + final String fullname; + final String emailAddress; + final String mobileNumber; + final String isdCode; + + final bool isForSelf; + final bool isDraft; + + final double baseAmount; + final double totalTaxAmount; + final double totalAmount; + + final bool isLoading; + final String? error; + final bool isSuccess; + final String? orderId; + final String? clientSecret; // 🆕 NEW: For Stripe payment + final int? postcardId; // 🆕 NEW: Postcard ID from API + + // 🆕 Payment confirmation tracking + final bool isConfirmingPayment; // Loading state for payment confirmation + final bool isPaymentConfirmed; // Whether payment was confirmed successfully + final String? confirmationError; // Error during payment confirmation + + const PostcardCheckoutState({ + this.countryName = '', + this.cityName = '', + this.stateName = '', + this.zipCode = '', + this.address1 = '', + this.address2 = '', + this.pcTitle = '', + this.pcContent = '', + this.pcImageFile, + this.pcNumber = '', + this.pcDatetime = '', + this.fullname = '', + this.emailAddress = '', + this.mobileNumber = '', + this.isdCode = '', + this.isForSelf = true, + this.isDraft = true, + this.baseAmount = 0, + this.totalTaxAmount = 0, + this.totalAmount = 0, + this.isLoading = false, + this.error, + this.isSuccess = false, + this.orderId, + this.clientSecret, // 🆕 NEW + this.postcardId, // 🆕 NEW + this.isConfirmingPayment = false, // 🆕 NEW + this.isPaymentConfirmed = false, // 🆕 NEW + this.confirmationError, // 🆕 NEW + }); + + PostcardCheckoutState copyWith({ + String? countryName, + String? cityName, + String? stateName, + String? zipCode, + String? address1, + String? address2, + String? pcTitle, + String? pcContent, + File? pcImageFile, + String? pcNumber, + String? pcDatetime, + String? fullname, + String? emailAddress, + String? mobileNumber, + String? isdCode, + bool? isForSelf, + bool? isDraft, + double? baseAmount, + double? totalTaxAmount, + double? totalAmount, + bool? isLoading, + String? error, + bool? isSuccess, + String? orderId, + String? clientSecret, // 🆕 NEW + int? postcardId, // 🆕 NEW + bool? isConfirmingPayment, // 🆕 NEW + bool? isPaymentConfirmed, // 🆕 NEW + String? confirmationError, // 🆕 NEW + }) { + return PostcardCheckoutState( + countryName: countryName ?? this.countryName, + cityName: cityName ?? this.cityName, + stateName: stateName ?? this.stateName, + zipCode: zipCode ?? this.zipCode, + address1: address1 ?? this.address1, + address2: address2 ?? this.address2, + pcTitle: pcTitle ?? this.pcTitle, + pcContent: pcContent ?? this.pcContent, + pcImageFile: pcImageFile ?? this.pcImageFile, + pcNumber: pcNumber ?? this.pcNumber, + pcDatetime: pcDatetime ?? this.pcDatetime, + fullname: fullname ?? this.fullname, + emailAddress: emailAddress ?? this.emailAddress, + mobileNumber: mobileNumber ?? this.mobileNumber, + isdCode: isdCode ?? this.isdCode, + isForSelf: isForSelf ?? this.isForSelf, + isDraft: isDraft ?? this.isDraft, + baseAmount: baseAmount ?? this.baseAmount, + totalTaxAmount: totalTaxAmount ?? this.totalTaxAmount, + totalAmount: totalAmount ?? this.totalAmount, + isLoading: isLoading ?? this.isLoading, + error: error, + isSuccess: isSuccess ?? this.isSuccess, + orderId: orderId ?? this.orderId, + clientSecret: clientSecret ?? this.clientSecret, // 🆕 NEW + postcardId: postcardId ?? this.postcardId, // 🆕 NEW + isConfirmingPayment: isConfirmingPayment ?? this.isConfirmingPayment, // 🆕 NEW + isPaymentConfirmed: isPaymentConfirmed ?? this.isPaymentConfirmed, // 🆕 NEW + confirmationError: confirmationError, // 🆕 NEW + ); + } +} \ No newline at end of file diff --git a/lib/postcard/blocs/postcard_creation_bloc.dart b/lib/postcard/blocs/postcard_creation_bloc.dart index 749dcaf..ac1d4d8 100644 --- a/lib/postcard/blocs/postcard_creation_bloc.dart +++ b/lib/postcard/blocs/postcard_creation_bloc.dart @@ -72,6 +72,19 @@ class PostcardCreationBloc } }); + on((event, emit) { + emit(state.copyWith( + pcTitle: event.pcTitle, + fullName: event.fullName, + emailId: event.emailId, + phoneNumber: event.phoneNumber, + city: event.city, + country: event.country, + state: event.state, + zipCode: event.zipCode, + )); + }); + /* Select filter */ on((event, emit) async { // 1️⃣ No image? Exit early. diff --git a/lib/postcard/blocs/postcard_creation_events.dart b/lib/postcard/blocs/postcard_creation_events.dart index 737885f..0439cab 100644 --- a/lib/postcard/blocs/postcard_creation_events.dart +++ b/lib/postcard/blocs/postcard_creation_events.dart @@ -36,4 +36,25 @@ class TogglePurchaseOption extends PostcardCreationEvent { final bool isGift; TogglePurchaseOption(this.isGift); +} +class UpdatePurchaseFormData extends PostcardCreationEvent { + final String? pcTitle; + final String? fullName; + final String? emailId; + final String? phoneNumber; + final String? city; + final String? country; + final String? state; + final String? zipCode; + + UpdatePurchaseFormData({ + this.pcTitle, + this.fullName, + this.emailId, + this.phoneNumber, + this.city, + this.country, + this.state, + this.zipCode, + }); } \ No newline at end of file diff --git a/lib/postcard/blocs/postcard_creation_state.dart b/lib/postcard/blocs/postcard_creation_state.dart index d8fec4a..44191e1 100644 --- a/lib/postcard/blocs/postcard_creation_state.dart +++ b/lib/postcard/blocs/postcard_creation_state.dart @@ -10,6 +10,16 @@ class PostcardCreationState { final bool isProcessing; final String? selectedFont; + // Add these new fields + final String? pcTitle; + final String? fullName; + final String? emailId; + final String? phoneNumber; + final String? city; + final String? country; + final String? state; + final String? zipCode; + const PostcardCreationState({ required this.currentStep, this.imagePath, @@ -18,7 +28,15 @@ class PostcardCreationState { this.message, this.isGift = false, this.isProcessing = false, - this.selectedFont + this.selectedFont, + this.pcTitle, + this.fullName, + this.emailId, + this.phoneNumber, + this.city, + this.country, + this.state, + this.zipCode, }); PostcardCreationState copyWith({ @@ -30,6 +48,14 @@ class PostcardCreationState { bool? isGift, bool? isProcessing, String? selectedFont, + String? pcTitle, + String? fullName, + String? emailId, + String? phoneNumber, + String? city, + String? country, + String? state, + String? zipCode, }) { return PostcardCreationState( currentStep: currentStep ?? this.currentStep, @@ -39,7 +65,15 @@ class PostcardCreationState { message: message ?? this.message, isGift: isGift ?? this.isGift, isProcessing: isProcessing ?? this.isProcessing, - selectedFont: selectedFont ?? this.selectedFont + selectedFont: selectedFont ?? this.selectedFont, + pcTitle: pcTitle ?? this.pcTitle, + fullName: fullName ?? this.fullName, + emailId: emailId ?? this.emailId, + phoneNumber: phoneNumber ?? this.phoneNumber, + city: city ?? this.city, + country: country ?? this.country, + state: state ?? this.state, + zipCode: zipCode ?? this.zipCode, ); } } \ No newline at end of file diff --git a/lib/postcard/models/my_postcard_model.dart b/lib/postcard/models/my_postcard_model.dart new file mode 100644 index 0000000..1ff827e --- /dev/null +++ b/lib/postcard/models/my_postcard_model.dart @@ -0,0 +1,173 @@ +class MyPostCard { + final int id; + final int userXid; + final String pcTitle; + final String pcNumber; + final String cityName; + final DateTime pcDatetime; + final String pcContent; + final String pcImagePath; + final bool isForSelf; + final String fullname; + final String emailAddress; + final String isdCode; + final String mobileNumber; + final String address1; + final String address2; + final String zipCode; + final String stateName; + final String countryName; + final String orderStatus; + final double baseAmount; + final int? couponXid; + final double? couponDiscountPercent; + final double? couponDiscountAmount; + final double totalTaxAmount; + final double totalAmount; + final bool isPaid; + final String paymentMode; + final String? paymentId; + final String paymentStatus; + final String? paymentIntentId; + final bool isDraft; + final DateTime? deliveredOn; + final bool isActive; + final DateTime createdAt; + final DateTime updatedAt; + + MyPostCard({ + required this.id, + required this.userXid, + required this.pcTitle, + required this.pcNumber, + required this.cityName, + required this.pcDatetime, + required this.pcContent, + required this.pcImagePath, + required this.isForSelf, + required this.fullname, + required this.emailAddress, + required this.isdCode, + required this.mobileNumber, + required this.address1, + required this.address2, + required this.zipCode, + required this.stateName, + required this.countryName, + required this.orderStatus, + required this.baseAmount, + this.couponXid, + this.couponDiscountPercent, + this.couponDiscountAmount, + required this.totalTaxAmount, + required this.totalAmount, + required this.isPaid, + required this.paymentMode, + this.paymentId, + required this.paymentStatus, + this.paymentIntentId, + required this.isDraft, + this.deliveredOn, + required this.isActive, + required this.createdAt, + required this.updatedAt, + }); + + factory MyPostCard.fromJson(Map json) { + return MyPostCard( + id: json['id'] ?? 0, + userXid: json['userXid'] ?? 0, + pcTitle: json['pcTitle'] ?? 'N/A', + pcNumber: json['pcNumber'] ?? 'N/A', + cityName: json['cityName'] ?? 'N/A', + pcDatetime: json['pcDatetime'] != null + ? DateTime.parse(json['pcDatetime']) + : DateTime.now(), + pcContent: json['pcContent'] ?? 'N/A', + pcImagePath: json['pcImagePath'] ?? '', + isForSelf: json['isForSelf'] ?? false, + fullname: json['fullname'] ?? 'N/A', + emailAddress: json['emailAddress'] ?? 'N/A', + isdCode: json['isdCode'] ?? '', + mobileNumber: json['mobileNumber'] ?? '', + address1: json['address1'] ?? 'N/A', + address2: json['address2'] ?? '', + zipCode: json['zipCode'] ?? '', + stateName: json['stateName'] ?? 'N/A', + countryName: json['countryName'] ?? 'N/A', + orderStatus: json['orderStatus'] ?? 'N/A', + baseAmount: json['baseAmount'] != null + ? (json['baseAmount'] as num).toDouble() + : 0.0, + couponXid: json['couponXid'], + couponDiscountPercent: json['couponDiscountPercent'] != null + ? (json['couponDiscountPercent'] as num).toDouble() + : null, + couponDiscountAmount: json['couponDiscountAmount'] != null + ? (json['couponDiscountAmount'] as num).toDouble() + : null, + totalTaxAmount: json['totalTaxAmount'] != null + ? (json['totalTaxAmount'] as num).toDouble() + : 0.0, + totalAmount: json['totalAmount'] != null + ? (json['totalAmount'] as num).toDouble() + : 0.0, + isPaid: json['isPaid'] ?? false, + paymentMode: json['paymentMode'] ?? 'N/A', + paymentId: json['paymentId'], + paymentStatus: json['paymentStatus'] ?? 'N/A', + paymentIntentId: json['paymentIntentId'], + isDraft: json['isDraft'] ?? false, + deliveredOn: json['deliveredOn'] != null + ? DateTime.parse(json['deliveredOn']) + : null, + isActive: json['isActive'] ?? false, + createdAt: json['createdAt'] != null + ? DateTime.parse(json['createdAt']) + : DateTime.now(), + updatedAt: json['updatedAt'] != null + ? DateTime.parse(json['updatedAt']) + : DateTime.now(), + ); + } + + Map toJson() { + return { + 'id': id, + 'userXid': userXid, + 'pcTitle': pcTitle, + 'pcNumber': pcNumber, + 'cityName': cityName, + 'pcDatetime': pcDatetime.toIso8601String(), + 'pcContent': pcContent, + 'pcImagePath': pcImagePath, + 'isForSelf': isForSelf, + 'fullname': fullname, + 'emailAddress': emailAddress, + 'isdCode': isdCode, + 'mobileNumber': mobileNumber, + 'address1': address1, + 'address2': address2, + 'zipCode': zipCode, + 'stateName': stateName, + 'countryName': countryName, + 'orderStatus': orderStatus, + 'baseAmount': baseAmount, + 'couponXid': couponXid, + 'couponDiscountPercent': couponDiscountPercent, + 'couponDiscountAmount': couponDiscountAmount, + 'totalTaxAmount': totalTaxAmount, + 'totalAmount': totalAmount, + 'isPaid': isPaid, + 'paymentMode': paymentMode, + 'paymentId': paymentId, + 'paymentStatus': paymentStatus, + 'paymentIntentId': paymentIntentId, + 'isDraft': isDraft, + 'deliveredOn': deliveredOn?.toIso8601String(), + 'isActive': isActive, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + }; + } +} diff --git a/lib/postcard/repository/my_postcard_repository.dart b/lib/postcard/repository/my_postcard_repository.dart new file mode 100644 index 0000000..2a5932b --- /dev/null +++ b/lib/postcard/repository/my_postcard_repository.dart @@ -0,0 +1,20 @@ +import '../../networkApiServices/network_api_services.dart'; +import '../../networkApiServices/api_urls.dart'; +import '../models/my_postcard_model.dart'; + +class MyPostCardsRepository { + final NetworkApiService _apiService = NetworkApiService(); + + /// Fetch My Postcards (draft / orders) + Future> fetchMyPostCards({ + required String type, // "draft" or "orders" + }) async { + final response = await _apiService.getApi( + url: '${ApiUrls.myPostCards}?type=$type', + ); + + return (response.data as List) + .map((e) => MyPostCard.fromJson(e)) + .toList(); + } +} diff --git a/lib/postcard/repository/postcard_checkout_repository.dart b/lib/postcard/repository/postcard_checkout_repository.dart new file mode 100644 index 0000000..3d82050 --- /dev/null +++ b/lib/postcard/repository/postcard_checkout_repository.dart @@ -0,0 +1,205 @@ +import 'dart:developer'; +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; + +import '../../networkApiServices/api_urls.dart'; +import '../../networkApiServices/network_api_services.dart'; + +class CreatePostCardRepository { + final NetworkApiService _apiServices = NetworkApiService(); + + /// Create / Save Postcard (Draft or Final) + /// ⭐ UPDATED: Now uses multipart/form-data for file upload + Future> createPostCard({ + required String countryName, + required String cityName, + required String stateName, + required String zipCode, + + String? address1, // NOT required + String? address2, // NOT required + + required String pcTitle, + required String pcContent, + required File pcImageFile, // ⭐ CHANGED: File instead of String + required String pcNumber, + required String pcDatetime, + + required String fullname, + required String emailAddress, + required String mobileNumber, + required String isdCode, + + required bool isForSelf, + required bool isDraft, + + required double baseAmount, + required double totalTaxAmount, + required double totalAmount, + }) async { + try { + log('🟡 createPostCard() called'); + + if (kDebugMode) { + print('📤 [CREATE POSTCARD] Country: $countryName'); + print('📤 [CREATE POSTCARD] City: $cityName'); + print('📤 [CREATE POSTCARD] State: $stateName'); + print('📤 [CREATE POSTCARD] Zip: $zipCode'); + print('📤 [CREATE POSTCARD] Title: $pcTitle'); + print('📤 [CREATE POSTCARD] Number: $pcNumber'); + print('📤 [CREATE POSTCARD] Image File: ${pcImageFile.path}'); + print('📤 [CREATE POSTCARD] Is Draft: $isDraft'); + } + + // ⭐ Create FormData for multipart/form-data upload + final formData = FormData(); + + // Add text fields + formData.fields.addAll([ + MapEntry('countryName', countryName), + MapEntry('cityName', cityName), + MapEntry('stateName', stateName), + MapEntry('zipCode', zipCode), + MapEntry('pcTitle', pcTitle), + MapEntry('pcContent', pcContent), + MapEntry('pcNumber', pcNumber), + MapEntry('pcDatetime', pcDatetime), + MapEntry('fullname', fullname), + MapEntry('emailAddress', emailAddress), + MapEntry('mobileNumber', mobileNumber), + MapEntry('isdCode', isdCode), + MapEntry('isForSelf', isForSelf.toString()), + MapEntry('isDraft', isDraft.toString()), + MapEntry('baseAmount', baseAmount.toString()), + MapEntry('totalTaxAmount', totalTaxAmount.toString()), + MapEntry('totalAmount', totalAmount.toString()), + ]); + + // Add optional address fields only if they are not null + if (address1 != null && address1.isNotEmpty) { + formData.fields.add(MapEntry('address1', address1)); + } + + if (address2 != null && address2.isNotEmpty) { + formData.fields.add(MapEntry('address2', address2)); + } + + // ⭐ Add postcard image file + final fileName = pcImageFile.path.split('/').last; + formData.files.add( + MapEntry( + 'pcImage', + await MultipartFile.fromFile( + pcImageFile.path, + filename: fileName, + ), + ), + ); + + if (kDebugMode) { + print('📤 [CREATE POSTCARD] ✅ Postcard Image File Added'); + print('📤 [CREATE POSTCARD] File Name: $fileName'); + print('📤 [CREATE POSTCARD] File Path: ${pcImageFile.path}'); + final fileSize = await pcImageFile.length(); + print('📤 [CREATE POSTCARD] File Size: ${(fileSize / 1024).toStringAsFixed(2)} KB'); + } + + // ⭐ Log complete payload details + log('📦 Request Payload Summary:'); + log('📦 Total Fields: ${formData.fields.length}'); + log('📦 Total Files: ${formData.files.length}'); + + log('📦 Field Details:'); + for (var field in formData.fields) { + log(' - ${field.key}: ${field.value}'); + } + + log('📦 File Details:'); + for (var file in formData.files) { + log(' - ${file.key}: ${file.value.filename} (${file.value.length} bytes)'); + } + + log('🌐 API URL: ${ApiUrls.createPostCard}'); + + // ⭐ Send as multipart/form-data + final response = await _apiServices.postApi( + url: ApiUrls.createPostCard, + data: formData, + ); + + log('✅ API Response Status: ${response.statusCode}'); + log('📥 API Response Data: ${response.data}'); + + if (kDebugMode) { + print('📤 [CREATE POSTCARD] ✅ Response Status: Success'); + print('📤 [CREATE POSTCARD] Full Response: ${response.data}'); + } + + return response.data as Map; + } catch (e, stackTrace) { + log( + '❌ createPostCard FAILED', + error: e, + stackTrace: stackTrace, + ); + throw Exception('Failed to create postcard: $e'); + } + } + + /// 🆕 Confirm Payment after successful Stripe payment + /// POST https://devapi.citycards.betadelivery.com/mobile/postcards/{postcardId}/confirm-payment + Future> confirmPayment({ + required int postcardId, + required String stripeStatus, + required String paymentStatus, + }) async { + try { + log('🟢 confirmPayment() called'); + log('📤 [CONFIRM PAYMENT] Postcard ID: $postcardId'); + log('📤 [CONFIRM PAYMENT] Stripe Status: $stripeStatus'); + log('📤 [CONFIRM PAYMENT] Payment Status: $paymentStatus'); + + // Construct URL with postcardId + final url = '${ApiUrls.baseUrl}/mobile/postcards/$postcardId/confirm-payment'; + + // Note: Update ApiUrls class if you want to use a constant instead + // final url = ApiUrls.confirmPayment(postcardId); + + if (kDebugMode) { + print('📤 [CONFIRM PAYMENT] API URL: $url'); + } + + // Request body + final requestBody = { + 'stripeStatus': stripeStatus, + 'paymentStatus': paymentStatus, + }; + + log('📦 Request Body: $requestBody'); + + // Send POST request + final response = await _apiServices.postApi( + url: url, + data: requestBody, + ); + + log('✅ [CONFIRM PAYMENT] Response Status: ${response.statusCode}'); + log('📥 [CONFIRM PAYMENT] Response Data: ${response.data}'); + + if (kDebugMode) { + print('📤 [CONFIRM PAYMENT] ✅ Payment confirmation successful'); + print('📤 [CONFIRM PAYMENT] Full Response: ${response.data}'); + } + + return response.data as Map; + } catch (e, stackTrace) { + log( + '❌ confirmPayment FAILED', + error: e, + stackTrace: stackTrace, + ); + throw Exception('Failed to confirm payment: $e'); + } + } +} \ No newline at end of file diff --git a/lib/postcard/views/my_orders_page_view.dart b/lib/postcard/views/my_orders_page_view.dart deleted file mode 100644 index f21470f..0000000 --- a/lib/postcard/views/my_orders_page_view.dart +++ /dev/null @@ -1,817 +0,0 @@ -import 'dart:io'; - -import 'package:citycards_customer/common_packages/app_bar.dart'; -import 'package:citycards_customer/postcard/blocs/postcard_creation_events.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:google_fonts/google_fonts.dart'; - -import '../blocs/postcard_creation_bloc.dart'; -import '../blocs/postcard_creation_state.dart'; - -class MyOrdersPageView extends StatefulWidget { - const MyOrdersPageView({super.key}); - - @override - State createState() => _MyOrdersPageViewState(); -} - -class _MyOrdersPageViewState extends State { - bool showDrafts = true; - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - final bloc = context.read(); - return SafeArea( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 🏙️ Header - CommonAppBar(isWhiteLogo: false, isProfilePage: false, showDivider: true,), - - Row( - children: [ - Expanded( - child: GestureDetector( - onTap: () => setState(() => showDrafts = true), - child: Container( - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: showDrafts - ? const Color(0xffF95F62).withOpacity(0.24) - : Colors.transparent, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: showDrafts - ? const Color(0xffF95F62).withOpacity(0.4) - : const Color(0xffE0E0E0), - ), - ), - child: Center( - child: Text( - "My drafts", - style: TextStyle( - fontWeight: FontWeight.w400, - fontSize: 14.sp, - color: showDrafts - ? Colors.black - : Colors.black.withOpacity(0.56), - ), - ), - ), - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: GestureDetector( - onTap: () => setState(() => showDrafts = false), - child: Container( - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: !showDrafts - ? const Color(0xffF95F62).withOpacity(0.24) - : Colors.transparent, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: !showDrafts - ? const Color(0xffF95F62).withOpacity(0.4) - : const Color(0xffE0E0E0), - ), - ), - child: Center( - child: Text( - "My orders", - style: TextStyle( - fontWeight: FontWeight.w400, - fontSize: 14.sp, - color: !showDrafts - ? Colors.black - : Colors.black.withOpacity(0.56), - ), - ), - ), - ), - ), - ), - ], - ), - const SizedBox(height: 24), - - // 📬 Postcard List - showDrafts - ? Expanded( - child: ListView.builder( - itemCount: 5, - itemBuilder: (context, index) { - return Container( - margin: const EdgeInsets.only(bottom: 16), - padding: const EdgeInsets.fromLTRB( - 10, - 10, - 10, - 10, - ), - decoration: BoxDecoration( - color: const Color(0xFFFFF5F5), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: const Color(0xffF1F5F7), - ), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Image.file( - File(state.imagePath ?? ""), - height: 90.h, - width: 90.w, - fit: BoxFit.cover, - ), - ), - const SizedBox(width: 20), - - Expanded( - child: SizedBox( - height: 90.h, - child: Stack( - children: [ - /// Centered texts - Align( - alignment: Alignment.centerLeft, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "#688574", - style: GoogleFonts.poppins( - fontSize: 14.sp, - fontWeight: FontWeight.w400, - color: Colors.black, - ), - ), - const SizedBox(height: 3), - Text( - "My postcard", - style: GoogleFonts.poppins( - fontSize: 16.sp, - fontWeight: FontWeight.w400, - color: Colors.black, - ), - ), - ], - ), - ), - - /// 🧭 Bottom-right icons - Align( - alignment: Alignment.bottomRight, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - InkWell( - onTap: () {}, - child: Image.asset( - "assets/icons/delete_icon.png", - scale: 4, - ), - ), - const SizedBox(width: 20), - InkWell( - onTap: () {}, - child: Image.asset( - "assets/icons/edit_icon.png", - scale: 4, - ), - ), - const SizedBox(width: 20), - InkWell( - onTap: () {}, - child: Image.asset( - "assets/icons/send_icon.png", - scale: 4, - ), - ), - const SizedBox(width: 10), - ], - ), - ), - ], - ), - ), - ), - ], - ), - ); - }, - ), - ) - : Expanded( - child: ListView.builder( - itemCount: 2, - itemBuilder: (context, index) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "#688574", - style: GoogleFonts.poppins( - fontSize: 14.sp, - fontWeight: FontWeight.w400, - color: Colors.black, - ), - ), - const SizedBox(height: 3), - Container( - margin: const EdgeInsets.only(bottom: 16), - padding: const EdgeInsets.fromLTRB( - 10, - 10, - 10, - 10, - ), - decoration: BoxDecoration( - color: const Color(0xFFFFF5F5), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: const Color(0xffF1F5F7), - ), - ), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Image.file( - File(state.imagePath ?? ""), - height: 70.h, - width: 70.w, - fit: BoxFit.cover, - ), - ), - const SizedBox(width: 20), - - Expanded( - child: SizedBox( - height: 60.h, - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisAlignment: - MainAxisAlignment.start, - children: [ - Text( - "My PostCard", - style: GoogleFonts.poppins( - color: Colors.black, - fontWeight: - FontWeight.w400, - fontSize: 16.sp, - ), - ), - const SizedBox(height: 6), - Text( - "5 Post cards", - style: GoogleFonts.poppins( - color: Colors.black, - fontWeight: - FontWeight.w400, - fontSize: 14.sp, - ), - ), - ], - ), - Column( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - crossAxisAlignment: - CrossAxisAlignment.end, - children: [ - Container( - padding: EdgeInsets.fromLTRB(13, 7, 13, 7), - decoration: BoxDecoration( - color: Color( - 0xff00FFA6, - ).withOpacity(0.16), - border: Border.all( - color: Color( - 0xff439F6E, - ), - ), - borderRadius: - BorderRadius.circular( - 16, - ), - ), - child: Text( - "In Progress", - style: TextStyle( - color: Colors.black, - fontWeight: - FontWeight.w400, - fontSize: 8.54.sp, - ), - ), - ), - InkWell( - onTap: () { - bloc.add(GoToNextStep()); - }, - child: Row( - children: [ - Icon( - Icons - .remove_red_eye_outlined, - size: 15, - color: Color( - 0xffF95F62, - ), - ), - SizedBox(width: 5.w), - Text( - "Preview", - style: TextStyle( - fontWeight: - FontWeight.w400, - color: Color( - 0xffF95F62, - ), - ), - ), - ], - ), - ), - ], - ), - ], - ), - ), - ), - ], - ), - ), - ], - ); - }, - ), - ), - - // ➕ Create postcard button - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () {}, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xffF95F62), - padding: EdgeInsets.symmetric(vertical: 16.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40), - ), - ), - child: Text( - "Create post card", - style: GoogleFonts.poppins( - color: Colors.white, - fontSize: 14.sp, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - ], - ), - ), - ); - }, - ); - } -} - -// import 'package:citycards_customer/common_packages/app_bar.dart'; -// import 'package:citycards_customer/core/route_constants.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter_screenutil/flutter_screenutil.dart'; -// import 'package:google_fonts/google_fonts.dart'; -// -// class MyOrdersPageView extends StatefulWidget { -// const MyOrdersPageView({super.key}); -// -// @override -// State createState() => _MyOrdersPageViewState(); -// } -// -// class _MyOrdersPageViewState extends State { -// bool showDrafts = true; -// -// @override -// Widget build(BuildContext context) { -// return SafeArea( -// child: Padding( -// padding: const EdgeInsets.all(16), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// // 🏙️ Header -// CommonAppBar(isWhiteLogo: false, isProfilePage: false, showDivider: true,), -// -// Row( -// children: [ -// Expanded( -// child: GestureDetector( -// onTap: () => setState(() => showDrafts = true), -// child: Container( -// padding: const EdgeInsets.symmetric(vertical: 12), -// decoration: BoxDecoration( -// color: showDrafts -// ? const Color(0xffF95F62).withOpacity(0.24) -// : Colors.transparent, -// borderRadius: BorderRadius.circular(12), -// border: Border.all( -// color: showDrafts -// ? const Color(0xffF95F62).withOpacity(0.4) -// : const Color(0xffE0E0E0), -// ), -// ), -// child: Center( -// child: Text( -// "My drafts", -// style: TextStyle( -// fontWeight: FontWeight.w400, -// fontSize: 14.sp, -// color: showDrafts -// ? Colors.black -// : Colors.black.withOpacity(0.56), -// ), -// ), -// ), -// ), -// ), -// ), -// const SizedBox(width: 12), -// Expanded( -// child: GestureDetector( -// onTap: () => setState(() => showDrafts = false), -// child: Container( -// padding: const EdgeInsets.symmetric(vertical: 12), -// decoration: BoxDecoration( -// color: !showDrafts -// ? const Color(0xffF95F62).withOpacity(0.24) -// : Colors.transparent, -// borderRadius: BorderRadius.circular(12), -// border: Border.all( -// color: !showDrafts -// ? const Color(0xffF95F62).withOpacity(0.4) -// : const Color(0xffE0E0E0), -// ), -// ), -// child: Center( -// child: Text( -// "My orders", -// style: TextStyle( -// fontWeight: FontWeight.w400, -// fontSize: 14.sp, -// color: !showDrafts -// ? Colors.black -// : Colors.black.withOpacity(0.56), -// ), -// ), -// ), -// ), -// ), -// ), -// ], -// ), -// const SizedBox(height: 24), -// -// // 📬 Postcard List -// showDrafts -// ? Expanded( -// child: ListView.builder( -// itemCount: 5, -// itemBuilder: (context, index) { -// return Container( -// margin: const EdgeInsets.only(bottom: 16), -// padding: const EdgeInsets.fromLTRB( -// 10, -// 10, -// 10, -// 10, -// ), -// decoration: BoxDecoration( -// color: const Color(0xFFFFF5F5), -// borderRadius: BorderRadius.circular(12), -// border: Border.all( -// color: const Color(0xffF1F5F7), -// ), -// ), -// child: Row( -// crossAxisAlignment: CrossAxisAlignment.center, -// children: [ -// ClipRRect( -// borderRadius: BorderRadius.circular(8), -// child: Container( -// height: 90.h, -// width: 90.w, -// color: const Color(0xffFEE7E7), -// child: const Icon( -// Icons.image_outlined, -// color: Color(0xffFDCDCE), -// size: 40, -// ), -// ), -// ), -// const SizedBox(width: 20), -// -// Expanded( -// child: SizedBox( -// height: 90.h, -// child: Stack( -// children: [ -// /// Centered texts -// Align( -// alignment: Alignment.centerLeft, -// child: Column( -// mainAxisSize: MainAxisSize.min, -// crossAxisAlignment: -// CrossAxisAlignment.start, -// children: [ -// Text( -// "#688574", -// style: GoogleFonts.poppins( -// fontSize: 16.sp, -// fontWeight: FontWeight.w400, -// color: Colors.black, -// ), -// ), -// const SizedBox(height: 4), -// Text( -// "Created 24 Jan 2025", -// style: GoogleFonts.poppins( -// fontSize: 12.sp, -// fontWeight: FontWeight.w400, -// color: -// const Color(0xff999999), -// ), -// ), -// ], -// ), -// ), -// -// /// Top-right buttons -// Positioned( -// top: 0, -// right: 0, -// child: Row( -// mainAxisSize: MainAxisSize.min, -// children: [ -// InkWell( -// onTap: () {}, -// child: Image.asset( -// "assets/icons/delete_icon.png", -// scale: 3.5, -// ), -// ), -// const SizedBox(width: 20), -// InkWell( -// onTap: () {}, -// child: Image.asset( -// "assets/icons/edit_icon.png", -// scale: 3.5, -// ), -// ), -// ], -// ), -// ), -// -// /// Bottom-right "Preview" link -// Positioned( -// bottom: 0, -// right: 0, -// child: InkWell( -// onTap: () { -// // Navigate to preview -// // You can use Navigator or your routing solution -// }, -// child: Row( -// children: [ -// const Icon( -// Icons.remove_red_eye_outlined, -// size: 15, -// color: Color(0xffF95F62), -// ), -// SizedBox(width: 5.w), -// Text( -// "Preview", -// style: TextStyle( -// fontWeight: FontWeight.w400, -// color: -// const Color(0xffF95F62), -// ), -// ), -// ], -// ), -// ), -// ), -// ], -// ), -// ), -// ), -// ], -// ), -// ); -// }, -// ), -// ) -// : Expanded( -// child: ListView.builder( -// itemCount: 3, -// itemBuilder: (context, index) { -// return Container( -// margin: const EdgeInsets.only(bottom: 16), -// padding: const EdgeInsets.fromLTRB( -// 16, -// 16, -// 16, -// 16, -// ), -// decoration: BoxDecoration( -// color: const Color(0xFFFFF5F5), -// borderRadius: BorderRadius.circular(12), -// border: Border.all( -// color: const Color(0xffF1F5F7), -// ), -// ), -// child: Row( -// crossAxisAlignment: -// CrossAxisAlignment.center, -// children: [ -// ClipRRect( -// borderRadius: BorderRadius.circular(8), -// child: Container( -// height: 70.h, -// width: 70.w, -// color: const Color(0xffFEE7E7), -// child: const Icon( -// Icons.image_outlined, -// color: Color(0xffFDCDCE), -// size: 30, -// ), -// ), -// ), -// const SizedBox(width: 20), -// -// Expanded( -// child: SizedBox( -// height: 60.h, -// child: Row( -// mainAxisAlignment: -// MainAxisAlignment.spaceBetween, -// children: [ -// Column( -// crossAxisAlignment: -// CrossAxisAlignment.start, -// mainAxisAlignment: -// MainAxisAlignment.start, -// children: [ -// Text( -// "My PostCard", -// style: GoogleFonts.poppins( -// color: Colors.black, -// fontWeight: -// FontWeight.w400, -// fontSize: 16.sp, -// ), -// ), -// const SizedBox(height: 6), -// Text( -// "5 Post cards", -// style: GoogleFonts.poppins( -// color: Colors.black, -// fontWeight: -// FontWeight.w400, -// fontSize: 14.sp, -// ), -// ), -// ], -// ), -// Column( -// mainAxisAlignment: -// MainAxisAlignment -// .spaceBetween, -// crossAxisAlignment: -// CrossAxisAlignment.end, -// children: [ -// Container( -// padding: EdgeInsets.fromLTRB(13, 7, 13, 7), -// decoration: BoxDecoration( -// color: Color( -// 0xff00FFA6, -// ).withOpacity(0.16), -// border: Border.all( -// color: Color( -// 0xff439F6E, -// ), -// ), -// borderRadius: -// BorderRadius.circular( -// 16, -// ), -// ), -// child: Text( -// "In Progress", -// style: TextStyle( -// color: Colors.black, -// fontWeight: -// FontWeight.w400, -// fontSize: 8.54.sp, -// ), -// ), -// ), -// InkWell( -// onTap: () { -// // Navigate to preview -// // You can use Navigator or your routing solution -// }, -// child: Row( -// children: [ -// Icon( -// Icons -// .remove_red_eye_outlined, -// size: 15, -// color: Color( -// 0xffF95F62, -// ), -// ), -// SizedBox(width: 5.w), -// Text( -// "Preview", -// style: TextStyle( -// fontWeight: -// FontWeight.w400, -// color: Color( -// 0xffF95F62, -// ), -// ), -// ), -// ], -// ), -// ), -// ], -// ), -// ], -// ), -// ), -// ), -// ], -// ), -// ); -// }, -// ), -// ), -// -// // ➕ Create postcard button -// SizedBox( -// width: double.infinity, -// child: ElevatedButton( -// onPressed: () { -// // Navigate to postcard creation flow (starts at upload photo step) -// Navigator.of(context).pushNamed(RouteConstants.uploadPhotoPage); -// }, -// style: ElevatedButton.styleFrom( -// backgroundColor: const Color(0xffF95F62), -// padding: EdgeInsets.symmetric(vertical: 16.h), -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular(40), -// ), -// ), -// child: Text( -// "Create post card", -// style: GoogleFonts.poppins( -// color: Colors.white, -// fontSize: 14.sp, -// fontWeight: FontWeight.w600, -// ), -// ), -// ), -// ), -// ], -// ), -// ), -// ); -// } -// } diff --git a/lib/postcard/views/my_postcard_drafts_view.dart b/lib/postcard/views/my_postcard_drafts_view.dart new file mode 100644 index 0000000..a9a5502 --- /dev/null +++ b/lib/postcard/views/my_postcard_drafts_view.dart @@ -0,0 +1,290 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; +import '../../core/route_constants.dart'; +import '../../networkApiServices/api_urls.dart'; +import '../blocs/myPostCards/my_postcard_bloc.dart'; +import '../blocs/myPostCards/my_postcard_event.dart'; +import '../blocs/myPostCards/my_postcard_state.dart'; +import '../models/my_postcard_model.dart'; + +class MyPostCardDraftView extends StatelessWidget { + const MyPostCardDraftView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + // Handle the new combined MyPostCardLoaded state + if (state is MyPostCardLoaded) { + // Show loading indicator if drafts are loading + if (state.isDraftLoading && state.draftPostCards.isEmpty) { + return const Center( + child: CircularProgressIndicator( + color: Color(0xffF95F62), + ), + ); + } + + // Show empty state if no drafts + if (state.draftPostCards.isEmpty) { + return Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Empty state image + Image.asset( + "assets/images/empty_postcard_drafts.png", + width: 260.w, + fit: BoxFit.contain, + ), + + SizedBox(height: 32.h), + + // Title + Text( + "Looks like you haven't created\nany postcards yet!", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: const Color(0xffF95F62), + height: 1.4, + ), + ), + + SizedBox(height: 12.h), + + // Subtitle + Text( + "Why not whip up a postcard and send it to someone special who's far away?", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + color: Colors.black54, + height: 1.5, + ), + ), + + SizedBox(height: 32.h), + ], + ), + ), + ); + } + + // Show the list of drafts + return RefreshIndicator( + onRefresh: () async { + context.read().add(const RefreshDraftPostCards()); + }, + color: const Color(0xffF95F62), + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + itemCount: state.draftPostCards.length, + itemBuilder: (context, index) { + final postcard = state.draftPostCards[index]; + return _buildDraftCard(context, postcard); + }, + ), + ); + } + + // Handle error state + if (state is MyPostCardError && state.errorType == 'draft') { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 60, + color: Colors.red.withOpacity(0.6), + ), + const SizedBox(height: 16), + Text( + 'Error loading drafts', + style: GoogleFonts.poppins( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Text( + state.errorMessage, + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 14.sp, + color: Colors.black54, + ), + ), + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: () { + context.read().add(const FetchDraftPostCards()); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + ), + child: Text( + 'Retry', + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ); + } + + return const SizedBox.shrink(); + }, + ); + } + + Widget _buildDraftCard(BuildContext context, MyPostCard postcard) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xffF95F62).withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: const Color(0xffF1F5F7)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// LEFT IMAGE + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.network( + '${ApiUrls.baseUrl}${postcard.pcImagePath}', + height: 72, + width: 72, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + height: 72, + width: 72, + color: Colors.grey[300], + child: const Center( + child: CircularProgressIndicator( + color: Color(0xffF95F62), + strokeWidth: 2, + ), + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Container( + height: 72, + width: 72, + color: Colors.grey[300], + child: const Icon( + Icons.image_not_supported, + color: Colors.grey, + ), + ); + }, + ), + ), + + const SizedBox(width: 14), + + /// RIGHT CONTENT + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// NUMBER + Text( + "#${postcard.pcNumber}", + style: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + + const SizedBox(height: 4), + + /// TITLE + Text( + postcard.pcTitle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.poppins( + fontSize: 15, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), + ), + + const SizedBox(height: 10), + + /// ICONS – BOTTOM RIGHT (UNDER TITLE) + Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () { + // delete + }, + child: Image.asset( + 'assets/icons/delete_icon.png', + width: 20, + height: 20, + ), + ), + const SizedBox(width: 16), + GestureDetector( + onTap: () { + // edit + }, + child: Image.asset( + 'assets/icons/edit_icon.png', + width: 20, + height: 20, + ), + ), + const SizedBox(width: 16), + GestureDetector( + onTap: () { + // send + }, + child: Image.asset( + 'assets/icons/send_icon.png', + width: 20, + height: 20, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/postcard/views/my_postcard_orders_view.dart b/lib/postcard/views/my_postcard_orders_view.dart new file mode 100644 index 0000000..74811ad --- /dev/null +++ b/lib/postcard/views/my_postcard_orders_view.dart @@ -0,0 +1,385 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; +import '../../core/route_constants.dart'; +import '../blocs/myPostCards/my_postcard_bloc.dart'; +import '../blocs/myPostCards/my_postcard_event.dart'; +import '../blocs/myPostCards/my_postcard_state.dart'; +import '../models/my_postcard_model.dart'; +import '../../networkApiServices/api_urls.dart'; +import 'my_postcard_preview_view.dart'; + +class MyPostCardOrdersView extends StatelessWidget { + const MyPostCardOrdersView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + // Handle the new combined MyPostCardLoaded state + if (state is MyPostCardLoaded) { + // Show loading indicator if orders are loading + if (state.isOrderLoading && state.orderPostCards.isEmpty) { + return const Center( + child: CircularProgressIndicator( + color: Color(0xffF95F62), + ), + ); + } + + // Show empty state if no orders + if (state.orderPostCards.isEmpty) { + return Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Empty state image + Image.asset( + "assets/images/empty_postcard_orders.png", + width: 260.w, + fit: BoxFit.contain, + ), + + SizedBox(height: 32.h), + + // Title + Text( + "It looks like you haven't ordered\na postcards yet!", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: const Color(0xffF95F62), + height: 1.4, + ), + ), + + SizedBox(height: 12.h), + + // Subtitle + Text( + "How about we whip up a fun postcard to send to your loved ones? Lets get started on that!", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + color: Colors.black54, + height: 1.5, + ), + ), + + SizedBox(height: 32.h), + ], + ), + ), + ); + } + + // Show the list of orders + return RefreshIndicator( + onRefresh: () async { + context.read().add(const RefreshOrderPostCards()); + }, + color: const Color(0xffF95F62), + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + itemCount: state.orderPostCards.length, + itemBuilder: (context, index) { + final postcard = state.orderPostCards[index]; + return _buildOrderCard(context, postcard); + }, + ), + ); + } + + // Handle error state + if (state is MyPostCardError && state.errorType == 'order') { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 60, + color: Colors.red.withOpacity(0.6), + ), + const SizedBox(height: 16), + Text( + 'Error loading orders', + style: GoogleFonts.poppins( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Text( + state.errorMessage, + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 14.sp, + color: Colors.black54, + ), + ), + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: () { + context.read().add(const FetchOrderPostCards()); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 12.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + ), + child: Text( + 'Retry', + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ); + } + + return const SizedBox.shrink(); + }, + ); + } + + Widget _buildOrderCard(BuildContext context, MyPostCard postcard) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Postcard Number above the card + Padding( + padding: const EdgeInsets.only(left: 4, bottom: 8), + child: Text( + "#${postcard.pcNumber}", + style: GoogleFonts.poppins( + color: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 15.sp, + ), + ), + ), + + // Order Card + Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: const Color(0xffF95F62).withValues(alpha:0.08), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0xffF1F5F7), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Postcard Image + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image( + image: NetworkImage('${ApiUrls.baseUrl}${postcard.pcImagePath}'), + height: 70.h, + width: 70.w, + fit: BoxFit.cover, + + // Loading indicator + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + height: 70.h, + width: 70.w, + color: Colors.grey[300], + child: const Center( + child: CircularProgressIndicator( + color: Color(0xffF95F62), + strokeWidth: 2, + ), + ), + ); + }, + + // Error UI + errorBuilder: (context, error, stackTrace) { + return Container( + height: 70.h, + width: 70.w, + color: Colors.grey[300], + child: const Icon( + Icons.image_not_supported, + color: Colors.grey, + ), + ); + }, + ), + ), + const SizedBox(width: 20), + + // Postcard Details + Expanded( + child: SizedBox( + height: 60.h, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + postcard.pcTitle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.poppins( + color: Colors.black, + fontWeight: FontWeight.w400, + fontSize: 16.sp, + ), + ), + const SizedBox(height: 6), + Text( + "5 Post cards", + style: GoogleFonts.poppins( + color: Colors.black, + fontWeight: FontWeight.w400, + fontSize: 14.sp, + ), + ), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(13, 7, 13, 7), + decoration: BoxDecoration( + color: _getStatusColor(postcard.orderStatus).withOpacity(0.16), + border: Border.all( + color: _getStatusBorderColor(postcard.orderStatus), + ), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + _getStatusText(postcard.orderStatus), + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.w400, + fontSize: 8.54.sp, + ), + ), + ), + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MyPostcardPreviewView( + postcard: postcard, + ), + ), + ); + }, + child: Row( + children: [ + Icon( + Icons.remove_red_eye_outlined, + size: 15, + color: const Color(0xffF95F62), + ), + SizedBox(width: 5.w), + Text( + "Preview", + style: TextStyle( + fontWeight: FontWeight.w400, + color: const Color(0xffF95F62), + fontSize: 13.sp, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ], + ); + } + + Color _getStatusColor(String status) { + switch (status.toLowerCase()) { + case 'pending': + return const Color(0xffFFA500); + case 'processing': + case 'in progress': + return const Color(0xff00FFA6); + case 'shipped': + return const Color(0xff0096FF); + case 'delivered': + return const Color(0xff00C851); + case 'cancelled': + return const Color(0xffFF4444); + default: + return const Color(0xff00FFA6); + } + } + + Color _getStatusBorderColor(String status) { + switch (status.toLowerCase()) { + case 'pending': + return const Color(0xffCC8400); + case 'processing': + case 'in progress': + return const Color(0xff439F6E); + case 'shipped': + return const Color(0xff0078CC); + case 'delivered': + return const Color(0xff00A041); + case 'cancelled': + return const Color(0xffCC0000); + default: + return const Color(0xff439F6E); + } + } + + String _getStatusText(String status) { + switch (status.toLowerCase()) { + case 'pending': + return 'Pending'; + case 'processing': + return 'Processing'; + case 'in progress': + return 'In Progress'; + case 'shipped': + return 'Shipped'; + case 'delivered': + return 'Delivered'; + case 'cancelled': + return 'Cancelled'; + default: + return status; + } + } +} \ No newline at end of file diff --git a/lib/postcard/views/my_postcard_preview_view.dart b/lib/postcard/views/my_postcard_preview_view.dart new file mode 100644 index 0000000..eedc6ca --- /dev/null +++ b/lib/postcard/views/my_postcard_preview_view.dart @@ -0,0 +1,438 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../../common_packages/app_bar.dart'; +import '../../common_packages/back_widget.dart'; +import '../models/my_postcard_model.dart'; +import '../../networkApiServices/api_urls.dart'; + +class MyPostcardPreviewView extends StatefulWidget { + final MyPostCard postcard; + + const MyPostcardPreviewView({ + super.key, + required this.postcard, + }); + + @override + State createState() => _MyPostcardPreviewViewState(); +} + +class _MyPostcardPreviewViewState extends State { + bool showBack = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, + ), + backWidget(context, "Preview", Colors.black), + + SizedBox(height: 29.h), + // Postcard Number with Action Icons + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "#${widget.postcard.pcNumber}", + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 18.sp, + fontWeight: FontWeight.w400, + ), + ), + Row( + children: [ + GestureDetector( + onTap: () { + // Delete functionality + }, + child: Image.asset( + 'assets/icons/delete_icon.png', + width: 24, + height: 24, + ), + ), + SizedBox(width: 16.w), + GestureDetector( + onTap: () { + // Edit functionality + }, + child: Image.asset( + 'assets/icons/edit_icon.png', + width: 24, + height: 24, + ), + ), + SizedBox(width: 16.w), + GestureDetector( + onTap: () { + // Send functionality + }, + child: Image.asset( + 'assets/icons/send_icon.png', + width: 24, + height: 24, + ), + ), + ], + ), + ], + ), + ), + SizedBox(height: 20.h), + + // Flip buttons + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + setState(() { + showBack = false; + }); + }, + child: Row( + children: [ + Icon( + Icons.arrow_back, + color: !showBack ? const Color(0xffF95F62) : Colors.grey[400], + size: 20, + ), + SizedBox(width: 6.w), + Text( + 'Flip', + style: GoogleFonts.poppins( + color: !showBack ? const Color(0xffF95F62) : Colors.grey[400], + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + GestureDetector( + onTap: () { + setState(() { + showBack = true; + }); + }, + child: Row( + children: [ + Text( + 'Flip', + style: GoogleFonts.poppins( + color: showBack ? const Color(0xffF95F62) : Colors.grey[400], + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 6.w), + Icon( + Icons.arrow_forward, + color: showBack ? const Color(0xffF95F62) : Colors.grey[400], + size: 20, + ), + ], + ), + ), + ], + ), + ), + + // Postcard Display + Expanded( + child: Center( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 400), + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: showBack ? _buildBackSide() : _buildFrontSide(), + ), + ), + ), + SizedBox(height: 40.h), + ], + ), + ), + ), + ); + } + + Widget _buildFrontSide() { + return Container( + key: const ValueKey('front'), + margin: EdgeInsets.symmetric(horizontal: 20.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: AspectRatio( + aspectRatio: 1.5, // Standard postcard ratio + child: Image.network( + '${ApiUrls.baseUrl}${widget.postcard.pcImagePath}', + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + color: Colors.grey[300], + child: Center( + child: CircularProgressIndicator( + color: const Color(0xffF95F62), + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Container( + color: Colors.grey[300], + child: const Center( + child: Icon( + Icons.image_not_supported, + size: 60, + color: Colors.grey, + ), + ), + ); + }, + ), + ), + ), + ); + } + + Widget _buildBackSide() { + return Container( + key: const ValueKey('back'), + margin: EdgeInsets.symmetric(horizontal: 20.w), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [ + Color(0xffE2D6C2), + Color(0xffFFF5E6), + Color(0xffFFF5E6), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: const Color(0xff000000).withOpacity(0.12), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: AspectRatio( + aspectRatio: 1.5, + child: Row( + children: [ + // ================= LEFT SIDE ================= + Expanded( + flex: 55, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Logo + Image.asset( + 'assets/logo/logo_city_cards.png', + height: 24.h, // adjust as needed + fit: BoxFit.contain, + ), + SizedBox(height: 2.h), + Text( + 'POSTCARD', + style: TextStyle( + color: Colors.black45, + fontSize: 6.sp, + letterSpacing: 1.4, + fontWeight: FontWeight.w500, + ), + ), + + SizedBox(height: 14.h), + + // Message label + Text( + 'MESSAGE PREVIEW', + style: TextStyle( + color: Colors.black, + fontSize: 6.sp, + letterSpacing: 1.4, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 8.h), + + // Message text + Expanded( + child: SingleChildScrollView( + child: Text( + widget.postcard.pcContent, + style: TextStyle( + color: Colors.black87, + fontSize: 13.sp, + height: 1.45, + ), + ), + ), + ), + + SizedBox(height: 10.h), + + // Footer + Text( + 'CityCards.co', + style: TextStyle( + color: const Color(0xffF95F62), + fontSize: 12.sp, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + + // ================= DIVIDER ================= + Container( + width: 4, + margin: EdgeInsets.symmetric(vertical: 14.h), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.black.withOpacity(0.05), + Colors.black.withOpacity(0.30), + Colors.black.withOpacity(0.05), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + ), + + // ================= RIGHT SIDE ================= + Expanded( + flex: 45, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 14.h), + child: Column( + children: [ + const Spacer(), + + // Address with BORDER + Container( + width: double.infinity, + padding: EdgeInsets.all(4.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: Colors.black.withOpacity(0.15), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // ADDRESS label + Align( + alignment: Alignment.centerLeft, + child: Text( + 'ADDRESS', + style: TextStyle( + color: Colors.black45, + fontSize: 7.5.sp, + letterSpacing: 1.6, + fontWeight: FontWeight.w600, + ), + ), + ), + + SizedBox(height: 6.h), + + // Address line 1 + Text( + '${widget.postcard.cityName},', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black87, + fontSize: 13.sp, + height: 1.5, + ), + ), + + SizedBox(height: 6.h), + + // State + Text( + '${widget.postcard.stateName},', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black87, + fontSize: 13.sp, + height: 1.5, + ), + ), + SizedBox(height: 6.h), + // Country + Text( + widget.postcard.countryName, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black87, + fontSize: 13.sp, + height: 1.5, + ), + ), + ], + ), + ), + + const Spacer(), + ], + ), + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/postcard/views/my_postcards_view.dart b/lib/postcard/views/my_postcards_view.dart new file mode 100644 index 0000000..059ca67 --- /dev/null +++ b/lib/postcard/views/my_postcards_view.dart @@ -0,0 +1,491 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:citycards_customer/common_packages/app_bar.dart'; +import 'dart:developer' as developer; +import '../../core/route_constants.dart'; +import '../../login/view/login_email_bottomsheet.dart'; +import '../blocs/myPostCards/my_postcard_bloc.dart'; +import '../blocs/myPostCards/my_postcard_event.dart'; +import '../blocs/myPostCards/my_postcard_state.dart'; +import 'my_postcard_drafts_view.dart'; +import 'my_postcard_orders_view.dart'; + +class MyPostCardsView extends StatefulWidget { + const MyPostCardsView({super.key}); + + @override + State createState() => _MyPostCardsViewState(); +} + +class _MyPostCardsViewState extends State { + bool showDrafts = true; + + @override + void initState() { + super.initState(); + developer.log('🚀 MyPostCardsView initialized', name: 'MyPostCardsView'); + context.read().add(const CheckLoginStatus()); + } + + void _switchTab(bool isDrafts) { + setState(() { + showDrafts = isDrafts; + }); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: BlocBuilder( + builder: (context, state) { + developer.log('📊 Current state: ${state.runtimeType}', name: 'MyPostCardsView'); + + // Handle not logged in state + if (state is MyPostCardNotLoggedIn) { + developer.log('❌ Showing login page - user not logged in', name: 'MyPostCardsView'); + return _buildPleaseLoginPageUI(); + } + + // Handle checking login state + if (state is MyPostCardCheckingLogin) { + developer.log('🔍 Checking login...', name: 'MyPostCardsView'); + return const Center(child: CircularProgressIndicator()); + } + + // Handle loaded state + if (state is MyPostCardLoaded) { + final isDraftsEmpty = state.draftPostCards.isEmpty; + final isOrdersEmpty = state.orderPostCards.isEmpty; + + developer.log('📊 Loaded - Drafts: ${state.draftPostCards.length}, Orders: ${state.orderPostCards.length}', name: 'MyPostCardsView'); + developer.log('🔄 Loading - Drafts: ${state.isDraftLoading}, Orders: ${state.isOrderLoading}', name: 'MyPostCardsView'); + + // Show initial UI only when both are empty AND not loading + final shouldShowInitialUI = isDraftsEmpty && + isOrdersEmpty && + !state.isDraftLoading && + !state.isOrderLoading; + + if (shouldShowInitialUI) { + developer.log('🎨 Showing initial UI - both lists empty', name: 'MyPostCardsView'); + return _buildInitialPageUI(); + } + + // Show loading state while initial data is being fetched + if (state.isDraftLoading && state.isOrderLoading && + isDraftsEmpty && isOrdersEmpty) { + developer.log('⏳ Showing loading - fetching initial data', name: 'MyPostCardsView'); + return Column( + children: [ + const CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, + ), + const Expanded( + child: Center(child: CircularProgressIndicator()), + ), + ], + ); + } + + developer.log('📱 Showing main content UI', name: 'MyPostCardsView'); + return _buildMainContentUI(state); + } + + // Handle error state + if (state is MyPostCardError) { + developer.log('❌ Error state: ${state.errorMessage}', name: 'MyPostCardsView'); + return _buildErrorUI(state.errorMessage); + } + + // Default fallback + developer.log('⚠️ Unknown state - showing loading', name: 'MyPostCardsView'); + return const Center(child: CircularProgressIndicator()); + }, + ), + ), + ); + } + + Widget _buildMainContentUI(MyPostCardLoaded state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + const CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, + ), + + // Tab Buttons + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () => _switchTab(true), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: showDrafts + ? const Color(0xffF95F62).withOpacity(0.24) + : Colors.transparent, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: showDrafts + ? const Color(0xffF95F62).withOpacity(0.4) + : const Color(0xffE0E0E0), + ), + ), + child: Center( + child: Text( + "My drafts", + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 14.sp, + color: showDrafts + ? Colors.black + : Colors.black.withOpacity(0.56), + ), + ), + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: GestureDetector( + onTap: () => _switchTab(false), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: !showDrafts + ? const Color(0xffF95F62).withOpacity(0.24) + : Colors.transparent, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: !showDrafts + ? const Color(0xffF95F62).withOpacity(0.4) + : const Color(0xffE0E0E0), + ), + ), + child: Center( + child: Text( + "My orders", + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 14.sp, + color: !showDrafts + ? Colors.black + : Colors.black.withOpacity(0.56), + ), + ), + ), + ), + ), + ), + ], + ), + const SizedBox(height: 24), + + // Content based on selected tab + Expanded( + child: showDrafts + ? (state.isDraftLoading && state.draftPostCards.isEmpty + ? const Center(child: CircularProgressIndicator()) + : const MyPostCardDraftView()) + : (state.isOrderLoading && state.orderPostCards.isEmpty + ? const Center(child: CircularProgressIndicator()) + : const MyPostCardOrdersView()), + ), + + // Create postcard button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.of(context) + .pushNamed(RouteConstants.uploadPhotoPage); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: EdgeInsets.symmetric(vertical: 16.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + ), + child: Text( + "Create post card", + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ); + } + + // Initial page UI when both lists are empty + Widget _buildInitialPageUI() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + const CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, + ), + + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 30.h), + + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.asset( + "assets/images/post_card_intro.png", + width: double.infinity, + fit: BoxFit.cover, + ), + ), + + SizedBox(height: 50.h), + + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("🌴", style: TextStyle(fontSize: 16)), + SizedBox(width: 4), + Text("📮", style: TextStyle(fontSize: 16)), + SizedBox(width: 4), + Text("💌", style: TextStyle(fontSize: 16)), + ], + ), + + SizedBox(height: 24.h), + + Text( + "Make the most of your trip", + style: TextStyle( + fontSize: 20.sp, + fontWeight: FontWeight.w800, + color: const Color(0xffF95F62), + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 8.h), + Text( + "Design your own unique postcards to\ncherish your unforgettable moments.", + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + color: const Color(0xff707070), + height: 1.5, + ), + textAlign: TextAlign.center, + ), + + SizedBox(height: 36.h), + ], + ), + ), + ), + + // Create postcard button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pushNamed(RouteConstants.uploadPhotoPage); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: EdgeInsets.symmetric(vertical: 16.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + ), + child: Text( + "Lets Create", + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ); + } + + // Please login page UI when user is not logged in + Widget _buildPleaseLoginPageUI() { + developer.log('🔐 Building login page UI', name: 'MyPostCardsView'); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + const CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, + ), + + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 50.h), + + // Postcard Image with opacity + Opacity( + opacity: 0.3, + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.asset( + "assets/images/post_card_intro.png", + width: double.infinity, + fit: BoxFit.cover, + ), + ), + ), + + SizedBox(height: 60.h), + + // Error Message + Text( + "You are not logged in yet!", + style: TextStyle( + fontSize: 20.sp, + fontWeight: FontWeight.w800, + color: const Color(0xffF95F62), + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 12.h), + Text( + "To design your own unique postcards, log\nin and purchase an unlimited pass", + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + color: const Color(0xff707070), + height: 1.5, + ), + textAlign: TextAlign.center, + ), + + SizedBox(height: 36.h), + ], + ), + ), + ), + + // Login button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + showModalBottomSheet( + backgroundColor: Colors.white, + context: context, + isScrollControlled: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (_) => const LoginEmailBottomsheet(), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: EdgeInsets.symmetric(vertical: 16.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + ), + child: Text( + "Login", + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ); + } + + // Error UI + Widget _buildErrorUI(String errorMessage) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, + ), + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error_outline, + size: 64, + color: Color(0xffF95F62), + ), + SizedBox(height: 16.h), + Text( + "Something went wrong", + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 8.h), + Text( + errorMessage, + style: TextStyle( + fontSize: 14.sp, + color: Colors.grey, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 24.h), + ElevatedButton( + onPressed: () { + context.read().add(const CheckLoginStatus()); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + ), + child: const Text("Retry"), + ), + ], + ), + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/postcard/views/postcard_checkout_page_view.dart b/lib/postcard/views/postcard_checkout_page_view.dart index c199a3e..04242fc 100644 --- a/lib/postcard/views/postcard_checkout_page_view.dart +++ b/lib/postcard/views/postcard_checkout_page_view.dart @@ -1,153 +1,547 @@ +import 'dart:io'; +import 'package:citycards_customer/postcard/views/my_postcards_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:google_fonts/google_fonts.dart'; +import '../../StripePayment/bloc/stripe_payment_bloc.dart'; +import '../../StripePayment/bloc/stripe_payment_event.dart'; +import '../../StripePayment/bloc/stripe_payment_state.dart'; +import '../../StripePayment/repository/stripe_service.dart'; import '../../common_packages/app_bar.dart'; +import '../../core/route_constants.dart'; +import '../blocs/myPostCards/my_postcard_bloc.dart'; +import '../blocs/myPostCards/my_postcard_event.dart'; +import '../blocs/postcardCheckout/postcard_checkout_bloc.dart'; +import '../blocs/postcardCheckout/postcard_checkout_event.dart'; +import '../blocs/postcardCheckout/postcard_checkout_state.dart'; import '../blocs/postcard_creation_bloc.dart'; import '../blocs/postcard_creation_events.dart'; import '../blocs/postcard_creation_state.dart'; import '../widgets/message_card_widget.dart'; import '../widgets/postcard_preview_widget.dart'; -class PostcardCheckoutPageView extends StatelessWidget { - const PostcardCheckoutPageView({super.key}); +class PostcardCheckoutPageView extends StatefulWidget { + final String countryName; + final String cityName; + final String stateName; + final String zipCode; + final String address1; + final String address2; + final String pcTitle; + final String pcNumber; + final String pcDatetime; + final String fullname; + final String emailAddress; + final String mobileNumber; + final String isdCode; + final bool isForSelf; + final double baseAmount; + final double totalTaxAmount; + final double totalAmount; + + const PostcardCheckoutPageView({ + super.key, + required this.countryName, + required this.cityName, + required this.stateName, + required this.zipCode, + this.address1 = '', + this.address2 = '', + required this.pcTitle, + required this.pcNumber, + required this.pcDatetime, + required this.fullname, + required this.emailAddress, + required this.mobileNumber, + required this.isdCode, + required this.isForSelf, + required this.baseAmount, + required this.totalTaxAmount, + required this.totalAmount, + }); + + @override + State createState() => _PostcardCheckoutPageViewState(); +} + +class _PostcardCheckoutPageViewState extends State { + @override + void initState() { + super.initState(); + // Initialize checkout bloc with data from widget + WidgetsBinding.instance.addPostFrameCallback((_) { + final creationState = context.read().state; + + // ⭐ Convert image path to File object + File? imageFile; + if (creationState.imagePath != null && creationState.imagePath!.isNotEmpty) { + imageFile = File(creationState.imagePath!); + } + + context.read().add( + UpdateCheckoutDataEvent( + countryName: widget.countryName, + cityName: widget.cityName, + stateName: widget.stateName, + zipCode: widget.zipCode, + address1: widget.address1, + address2: widget.address2, + pcTitle: widget.pcTitle, + pcContent: creationState.message ?? '', + pcImageFile: imageFile, + pcNumber: widget.pcNumber, + pcDatetime: widget.pcDatetime, + fullname: widget.fullname, + emailAddress: widget.emailAddress, + mobileNumber: widget.mobileNumber, + isdCode: widget.isdCode, + isForSelf: widget.isForSelf, + baseAmount: widget.baseAmount, + totalTaxAmount: widget.totalTaxAmount, + totalAmount: widget.totalAmount, + ), + ); + }); + } + + /// 🆕 Handle payment flow with client secret + Future _handlePaymentFlow(BuildContext context, String clientSecret) async { + // Show payment bottom sheet with BLoC + final paymentSuccess = await showModalBottomSheet( + context: context, + isDismissible: false, + enableDrag: false, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (bottomSheetContext) { + return BlocProvider( + create: (_) => StripePaymentBloc(stripeService: StripeService()) + ..add(InitiatePaymentWithClientSecret(clientSecret: clientSecret)), + child: BlocConsumer( + listener: (context, state) { + if (state is StripePaymentSuccess) { + Navigator.of(bottomSheetContext).pop(true); + } else if (state is StripePaymentFailure || state is StripePaymentCancelled) { + Navigator.of(bottomSheetContext).pop(false); + } + }, + builder: (context, state) { + return Container( + height: MediaQuery.of(context).size.height * 0.5, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Center( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (state is StripePaymentLoading) ...[ + const CircularProgressIndicator( + strokeWidth: 3, + valueColor: AlwaysStoppedAnimation( + Color(0xFFF95F62), + ), + ), + const SizedBox(height: 24), + const Text( + "Processing payment...", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Color(0xFF333333), + ), + ), + ] else if (state is StripePaymentSuccess) ...[ + const Icon( + Icons.check_circle, + color: Colors.green, + size: 64, + ), + const SizedBox(height: 16), + const Text( + "Payment Successful!", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color(0xFF333333), + ), + ), + ] else if (state is StripePaymentFailure) ...[ + const Icon( + Icons.error, + color: Colors.red, + size: 64, + ), + const SizedBox(height: 16), + const Text( + "Payment Failed", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color(0xFF333333), + ), + ), + const SizedBox(height: 8), + Text( + state.error, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ] else if (state is StripePaymentCancelled) ...[ + const Icon( + Icons.cancel, + color: Colors.orange, + size: 64, + ), + const SizedBox(height: 16), + const Text( + "Payment Cancelled", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Color(0xFF333333), + ), + ), + ], + const SizedBox(height: 32), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 16, + ), + decoration: BoxDecoration( + color: const Color(0xFFF5F5F5), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0xFFE0E0E0), + ), + ), + child: Column( + children: [ + Text( + "Payment Amount", + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + const SizedBox(height: 8), + Text( + "\$${widget.totalAmount.toStringAsFixed(2)}", + style: const TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Color(0xFF333333), + ), + ), + ], + ), + ), + const SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.lock_outline, + size: 16, + color: Colors.grey[600], + ), + const SizedBox(width: 6), + Text( + "Secured by Stripe", + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ), + ); + }, + ); + + // Handle payment result + if (!mounted) return; + + if (paymentSuccess == true) { + // Payment successful - continue to next step + context.read().add(GoToNextStep()); + final bloc = context.read(); + bloc.add( + ConfirmPaymentEvent( + stripeStatus: 'succeeded', + paymentStatus: 'success', + ), + ); + } else { + + // Payment failed or cancelled - go to MyPostCardsView + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const MyPostCardsView(), + ), + ); + + final bloc = context.read(); + bloc.add( + ConfirmPaymentEvent( + stripeStatus: 'requires_payment_method', + paymentStatus: 'failed', + ), + ); + } + } @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - final bloc = context.read(); - return SafeArea( - child: SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CommonAppBar(isWhiteLogo: false, isProfilePage: false, showDivider: true,), - // Header - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Checkout", - style: GoogleFonts.poppins( - fontSize: 20.sp, - fontWeight: FontWeight.w600, - color: const Color(0xff1A1A1A), - ), - ), - TextButton( - onPressed: () { - // TODO: Save as draft - }, - child: Text( - "Save as draft", - style: GoogleFonts.poppins( - fontSize: 14.sp, - fontWeight: FontWeight.w500, - color: const Color(0xffF95F62), - ), - ), - ), - ], - ), - - const SizedBox(height: 16), - - MessageCardWidget( - message: state.message ?? "", - selectedFont: state.selectedFont, - ), - SizedBox(height: 10.h), - PostCardPreviewWidget( - imagePath: state.imagePath ?? "", - message: state.message ?? "", - selectedFont: state.selectedFont, - ), - - SizedBox(height: 60.h), - - // 💰 Payment Summary - Text( - "Payment summary", - style: TextStyle( - fontSize: 12.sp, - fontWeight: FontWeight.w400, - color: const Color(0xff999999), - ), - ), - Divider(color: Color(0xffEDEDED)), - const SizedBox(height: 5), - - _buildPaymentRow("Subtotal", "\$ 50"), - const SizedBox(height: 20), - _buildPaymentRow("Discount", "\$ 20", highlight: true), - const SizedBox(height: 8), - Divider(color: Colors.black), - _buildPaymentRow("Grand Total", "\$ 30", size: 20.sp), - const SizedBox(height: 28), - Container(color: Color(0xffFAFAFA), height: 10), - const SizedBox(height: 10), - Row( - children: [ - const Icon( - Icons.home_outlined, - color: Color(0xffF95F62), - size: 20, - ), - const SizedBox(width: 10), - Expanded( - child: Text( - "Unit 7, Level 3, Dummy Towers 33.......", - style: GoogleFonts.poppins( - fontSize: 13.sp, - color: const Color(0xff2D3134), - ), - overflow: TextOverflow.ellipsis, - ), - ), - IconButton( - onPressed: () {}, - icon: const Icon( - Icons.edit_outlined, - color: Color(0xffF95F62), - size: 18, - ), - ), - ], - ), - const SizedBox(height: 10), - Container(color: Color(0xffFAFAFA), height: 10), - - const SizedBox(height: 40), - - // 🧾 Pay Button - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - bloc.add(GoToNextStep()); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xffF95F62), - padding: EdgeInsets.symmetric(vertical: 16.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40), - ), - ), - child: Text( - "Pay \$30", - style: TextStyle( - color: Colors.white, - fontSize: 14.sp, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - ], + return BlocConsumer( + listener: (context, checkoutState) { + if (checkoutState.isSuccess && !checkoutState.isDraft) { + // 🆕 Payment flow: Check if we have clientSecret + if (checkoutState.clientSecret != null && checkoutState.clientSecret!.isNotEmpty) { + // Initiate Stripe payment with clientSecret + _handlePaymentFlow(context, checkoutState.clientSecret!); + } else { + // No clientSecret - show error + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Error: Payment initialization failed'), + backgroundColor: Colors.red, + ), + ); + // Navigate to MyPostCardsView on error + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const MyPostCardsView(), + ), + ); + } + } else if (checkoutState.isSuccess && checkoutState.isDraft) { + // Draft saved successfully + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Draft saved successfully!'), + backgroundColor: Colors.green, ), - ), + ); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const MyPostCardsView(), + ), + ); + } else if (checkoutState.error != null) { + // Show error message + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error: ${checkoutState.error}'), + backgroundColor: Colors.red, + ), + ); + } + }, + builder: (context, checkoutState) { + return BlocBuilder( + builder: (context, creationState) { + return Stack( + children: [ + SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, + ), + // Header + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Checkout", + style: GoogleFonts.poppins( + fontSize: 20.sp, + fontWeight: FontWeight.w600, + color: const Color(0xff1A1A1A), + ), + ), + TextButton( + onPressed: checkoutState.isLoading + ? null + : () { + context + .read() + .add(SaveAsDraftEvent()); + }, + child: Text( + "Save as draft", + style: GoogleFonts.poppins( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: checkoutState.isLoading + ? Colors.grey + : const Color(0xffF95F62), + ), + ), + ), + ], + ), + + const SizedBox(height: 16), + + MessageCardWidget( + message: creationState.message ?? "", + selectedFont: creationState.selectedFont, + ), + SizedBox(height: 10.h), + PostCardPreviewWidget( + imagePath: creationState.imagePath ?? "", + message: creationState.message ?? "", + selectedFont: creationState.selectedFont, + ), + + SizedBox(height: 60.h), + + // 💰 Payment Summary + Text( + "Payment summary", + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w400, + color: const Color(0xff999999), + ), + ), + Divider(color: Color(0xffEDEDED)), + const SizedBox(height: 5), + + _buildPaymentRow( + "Subtotal", "\$ ${widget.baseAmount.toStringAsFixed(2)}"), + const SizedBox(height: 20), + _buildPaymentRow( + "Tax", "\$ ${widget.totalTaxAmount.toStringAsFixed(2)}", + highlight: true), + const SizedBox(height: 8), + Divider(color: Colors.black), + _buildPaymentRow( + "Grand Total", "\$ ${widget.totalAmount.toStringAsFixed(2)}", + size: 20.sp), + const SizedBox(height: 28), + Container(color: Color(0xffFAFAFA), height: 10), + const SizedBox(height: 10), + Row( + children: [ + const Icon( + Icons.home_outlined, + color: Color(0xffF95F62), + size: 20, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + "${widget.cityName}, ${widget.stateName}, ${widget.countryName} ${widget.zipCode}", + style: GoogleFonts.poppins( + fontSize: 13.sp, + color: const Color(0xff2D3134), + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + IconButton( + onPressed: () { + + }, + icon: const Icon( + Icons.edit_outlined, + color: Color(0xffF95F62), + size: 18, + ), + ), + ], + ), + const SizedBox(height: 10), + Container(color: Color(0xffFAFAFA), height: 10), + + const SizedBox(height: 40), + + // 🧾 Pay Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: checkoutState.isLoading + ? null + : () { + context + .read() + .add(SubmitPostcardEvent()); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: EdgeInsets.symmetric(vertical: 16.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + ), + child: checkoutState.isLoading + ? SizedBox( + height: 20.h, + width: 20.h, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Colors.white), + ), + ) + : Text( + "Pay \$${widget.totalAmount.toStringAsFixed(2)}", + style: TextStyle( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + ), + ), + // Loading overlay + if (checkoutState.isLoading) + Container( + color: Colors.black.withOpacity(0.3), + child: Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + Color(0xffF95F62)), + ), + ), + ), + ], + ); + }, ); }, ); @@ -155,11 +549,11 @@ class PostcardCheckoutPageView extends StatelessWidget { /// 💵 Helper for payment summary row Widget _buildPaymentRow( - String label, - String value, { - bool highlight = false, - double? size, - }) { + String label, + String value, { + bool highlight = false, + double? size, + }) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -174,10 +568,10 @@ class PostcardCheckoutPageView extends StatelessWidget { Container( decoration: highlight ? BoxDecoration( - color: const Color(0xffFDCDCE), - borderRadius: BorderRadius.circular(6), - border: Border.all(color: Color(0xffEDEDED)), - ) + color: const Color(0xffFDCDCE), + borderRadius: BorderRadius.circular(6), + border: Border.all(color: Color(0xffEDEDED)), + ) : null, padding: EdgeInsets.symmetric( horizontal: highlight ? 6 : 0, @@ -195,4 +589,4 @@ class PostcardCheckoutPageView extends StatelessWidget { ], ); } -} +} \ No newline at end of file diff --git a/lib/postcard/views/postcard_creation_page_view.dart b/lib/postcard/views/postcard_creation_page_view.dart index 8f3ffc5..cfcb88c 100644 --- a/lib/postcard/views/postcard_creation_page_view.dart +++ b/lib/postcard/views/postcard_creation_page_view.dart @@ -8,9 +8,11 @@ import 'package:citycards_customer/postcard/views/write_message_step_page_view.d import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import '../blocs/postcardCheckout/postcard_checkout_bloc.dart'; import '../blocs/postcard_creation_bloc.dart'; import '../blocs/postcard_creation_state.dart'; -import 'my_orders_page_view.dart'; +import '../repository/postcard_checkout_repository.dart'; +import 'my_postcards_view.dart'; import 'order_success_page_view.dart'; class PostcardCreationPage extends StatelessWidget { @@ -40,13 +42,35 @@ class PostcardCreationPage extends StatelessWidget { stepWidget = const PostcardPurchaseFormPageView(); break; case PostcardStep.checkout: - stepWidget = const PostcardCheckoutPageView(); + stepWidget = BlocProvider( + create: (_) => PostcardCheckoutBloc( + repository: CreatePostCardRepository(), + ), + child: PostcardCheckoutPageView( + countryName: state.country ?? 'N/A', + cityName: state.city ?? 'N/A', + stateName: state.state ?? 'N/A', + zipCode: state.zipCode ?? 'N/A', + pcTitle: state.pcTitle ?? 'N/A', + pcNumber: '12', + pcDatetime: '2008-11-20', + fullname: state.fullName ?? 'N/A', + emailAddress: state.emailId ?? 'N/A', + mobileNumber: state.phoneNumber ?? 'N/A', + isdCode: '+91', + isForSelf: !state.isGift, + totalTaxAmount: 0.5, + baseAmount: 10, + totalAmount: 10.5, + ), + ); break; + case PostcardStep.orderSuccess: stepWidget = const OrderSuccessPageView(); break; case PostcardStep.myOrders: - stepWidget = const MyOrdersPageView(); + stepWidget = const MyPostCardsView(); break; case PostcardStep.myOrderPostcardPreview: stepWidget = const OrderPostcardPreviewPageView(); diff --git a/lib/postcard/views/postcard_purchase_form_page_view.dart b/lib/postcard/views/postcard_purchase_form_page_view.dart index 92d77e2..88e67d0 100644 --- a/lib/postcard/views/postcard_purchase_form_page_view.dart +++ b/lib/postcard/views/postcard_purchase_form_page_view.dart @@ -8,9 +8,38 @@ import '../blocs/postcard_creation_bloc.dart'; import '../blocs/postcard_creation_events.dart'; import '../blocs/postcard_creation_state.dart'; -class PostcardPurchaseFormPageView extends StatelessWidget { +class PostcardPurchaseFormPageView extends StatefulWidget { const PostcardPurchaseFormPageView({super.key}); + @override + State createState() => _PostcardPurchaseFormPageViewState(); +} + +class _PostcardPurchaseFormPageViewState extends State { + final _formKey = GlobalKey(); + + // Controllers + final _titleController = TextEditingController(); + final _fullNameController = TextEditingController(); + final _emailController = TextEditingController(); + final _phoneController = TextEditingController(); + final _cityController = TextEditingController(); + final _zipCodeController = TextEditingController(); + + String? _selectedCountry; + String? _selectedState; + + @override + void dispose() { + _titleController.dispose(); + _fullNameController.dispose(); + _emailController.dispose(); + _phoneController.dispose(); + _cityController.dispose(); + _zipCodeController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return BlocBuilder( @@ -20,140 +49,198 @@ class PostcardPurchaseFormPageView extends StatelessWidget { return SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CommonAppBar(isWhiteLogo: false, isProfilePage: false, showDivider: true,), - - // Order ID - Text( - "#78895436", - style: TextStyle( - fontSize: 20.sp, - fontWeight: FontWeight.w600, - color: const Color(0xff1A1A1A), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, ), - ), - const SizedBox(height: 20), - // Postcard image + title - Row( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: state.imagePath != null - ? Image.file( - File(state.imagePath!), - height: 70, - width: 70, - fit: BoxFit.cover, - ) - : Container( - height: 70, - width: 70, - color: const Color(0xffFEE7E7), - child: const Icon(Icons.image_outlined, - color: Color(0xffFDCDCE)), - ), + // Order ID + Text( + "#78895436", + style: TextStyle( + fontSize: 20.sp, + fontWeight: FontWeight.w600, + color: const Color(0xff1A1A1A), ), - const SizedBox(width: 16), - Expanded( - child: TextField( - decoration: InputDecoration( - hintText: "Add title", - hintStyle: GoogleFonts.poppins( - color: const Color(0xff999999), fontSize: 14.sp), - enabledBorder: const UnderlineInputBorder( - borderSide: - BorderSide(color: Color(0xffFDCDCE), width: 1), - ), - focusedBorder: const UnderlineInputBorder( - borderSide: - BorderSide(color: Color(0xffFDCDCE), width: 1), - ), + ), + const SizedBox(height: 20), + + // Postcard image + title + Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: state.imagePath != null + ? Image.file( + File(state.imagePath!), + height: 70, + width: 70, + fit: BoxFit.cover, + ) + : Container( + height: 70, + width: 70, + color: const Color(0xffFEE7E7), + child: const Icon(Icons.image_outlined, + color: Color(0xffFDCDCE)), ), - style: GoogleFonts.poppins(fontSize: 14.sp), - onChanged: (val) { - // You can dispatch event here: bloc.add(UpdateTitle(val)); - }, ), + const SizedBox(width: 16), + Expanded( + child: TextFormField( + controller: _titleController, + decoration: InputDecoration( + hintText: "Add title", + hintStyle: GoogleFonts.poppins( + color: const Color(0xff999999), fontSize: 14.sp), + enabledBorder: const UnderlineInputBorder( + borderSide: + BorderSide(color: Color(0xffFDCDCE), width: 1), + ), + focusedBorder: const UnderlineInputBorder( + borderSide: + BorderSide(color: Color(0xffFDCDCE), width: 1), + ), + ), + style: GoogleFonts.poppins(fontSize: 14.sp), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a title'; + } + return null; + }, + ), + ), + ], + ), + + const SizedBox(height: 28), + + // Personal details section + Text( + "Add personal details", + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: const Color(0xff1A1A1A), ), - ], - ), - - const SizedBox(height: 28), - - // Personal details section - Text( - "Add personal details", - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: const Color(0xff1A1A1A), ), - ), - const SizedBox(height: 16), + const SizedBox(height: 16), - _buildInputField( - label: "Full Name", - hint: "Lorem Ipsum", - ), - _buildInputField( - label: "Email ID", - hint: "Lorem@gmail.com", - icon: Icons.email_outlined, - ), - _buildInputField( - label: "Phone number", - hint: "+91 9999 999 999", - icon: Icons.phone_outlined, - ), - - const SizedBox(height: 28), - - // Address details section - Text( - "Add address details", - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: const Color(0xff1A1A1A), + _buildInputField( + label: "Full Name", + hint: "Lorem Ipsum", + controller: _fullNameController, + ), + _buildInputField( + label: "Email ID", + hint: "Lorem@gmail.com", + icon: Icons.email_outlined, + controller: _emailController, + keyboardType: TextInputType.emailAddress, + ), + _buildInputField( + label: "Phone number", + hint: "+91 9999 999 999", + icon: Icons.phone_outlined, + controller: _phoneController, + keyboardType: TextInputType.phone, ), - ), - const SizedBox(height: 16), - _buildInputField(label: "City", hint: "Lorem Ipsum"), - _buildDropdownField(label: "Country", hint: "Lorem Ipsum"), - _buildDropdownField(label: "State", hint: "Lorem Ipsum"), - _buildInputField(label: "Zip Code", hint: "000000"), + const SizedBox(height: 28), - const SizedBox(height: 30), + // Address details section + Text( + "Add address details", + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: const Color(0xff1A1A1A), + ), + ), + const SizedBox(height: 16), - // Next Button - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - bloc.add(GoToNextStep()); + _buildInputField( + label: "City", + hint: "Lorem Ipsum", + controller: _cityController, + ), + _buildDropdownField( + label: "Country", + hint: "Lorem Ipsum", + value: _selectedCountry, + onChanged: (val) { + setState(() { + _selectedCountry = val; + }); }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xffF95F62), - padding: EdgeInsets.symmetric(vertical: 16.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40), + ), + _buildDropdownField( + label: "State", + hint: "Lorem Ipsum", + value: _selectedState, + onChanged: (val) { + setState(() { + _selectedState = val; + }); + }, + ), + _buildInputField( + label: "Zip Code", + hint: "000000", + controller: _zipCodeController, + keyboardType: TextInputType.number, + ), + + const SizedBox(height: 30), + + // Next Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + // Update the bloc with form data + bloc.add(UpdatePurchaseFormData( + pcTitle: _titleController.text, + fullName: _fullNameController.text, + emailId: _emailController.text, + phoneNumber: _phoneController.text, + city: _cityController.text, + country: _selectedCountry ?? '', + state: _selectedState ?? '', + zipCode: _zipCodeController.text, + )); + + // Navigate to next step + bloc.add(GoToNextStep()); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: EdgeInsets.symmetric(vertical: 16.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), ), - ), - child: Text( - "Next", - style: TextStyle( - color: Colors.white, - fontSize: 14.sp, - fontWeight: FontWeight.w600, + child: Text( + "Next", + style: TextStyle( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), ), ), ), - ), - ], + ], + ), ), ), ); @@ -165,7 +252,9 @@ class PostcardPurchaseFormPageView extends StatelessWidget { Widget _buildInputField({ required String label, required String hint, + required TextEditingController controller, IconData? icon, + TextInputType? keyboardType, }) { return Padding( padding: const EdgeInsets.only(bottom: 18), @@ -181,7 +270,9 @@ class PostcardPurchaseFormPageView extends StatelessWidget { ), ), const SizedBox(height: 6), - TextField( + TextFormField( + controller: controller, + keyboardType: keyboardType, decoration: InputDecoration( hintText: hint, hintStyle: GoogleFonts.poppins( @@ -201,7 +292,24 @@ class PostcardPurchaseFormPageView extends StatelessWidget { borderSide: const BorderSide(color: Color(0xffFDCDCE)), borderRadius: BorderRadius.circular(8), ), + errorBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.red), + borderRadius: BorderRadius.circular(8), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.red), + borderRadius: BorderRadius.circular(8), + ), ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter $label'; + } + if (label == "Email ID" && !value.contains('@')) { + return 'Please enter a valid email'; + } + return null; + }, ), ], ), @@ -212,6 +320,8 @@ class PostcardPurchaseFormPageView extends StatelessWidget { Widget _buildDropdownField({ required String label, required String hint, + required String? value, + required Function(String?) onChanged, }) { return Padding( padding: const EdgeInsets.only(bottom: 18), @@ -228,7 +338,7 @@ class PostcardPurchaseFormPageView extends StatelessWidget { ), const SizedBox(height: 6), DropdownButtonFormField( - value: null, + value: value, decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 12), @@ -240,6 +350,14 @@ class PostcardPurchaseFormPageView extends StatelessWidget { borderSide: const BorderSide(color: Color(0xffFDCDCE)), borderRadius: BorderRadius.circular(8), ), + errorBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.red), + borderRadius: BorderRadius.circular(8), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Colors.red), + borderRadius: BorderRadius.circular(8), + ), ), icon: const Icon(Icons.keyboard_arrow_down, color: Color(0xffFDCDCE)), @@ -251,12 +369,20 @@ class PostcardPurchaseFormPageView extends StatelessWidget { ), ), items: const [ - DropdownMenuItem(value: "Lorem Ipsum", child: Text("Lorem Ipsum")), + DropdownMenuItem(value: "India", child: Text("India")), + DropdownMenuItem(value: "USA", child: Text("USA")), + // Add more items as needed ], - onChanged: (val) {}, + onChanged: onChanged, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please select $label'; + } + return null; + }, ), ], ), ); } -} +} \ No newline at end of file diff --git a/lib/postcard/widgets/purchase_details_bottom_sheet.dart b/lib/postcard/widgets/purchase_details_bottom_sheet.dart index 39f41da..9f3f696 100644 --- a/lib/postcard/widgets/purchase_details_bottom_sheet.dart +++ b/lib/postcard/widgets/purchase_details_bottom_sheet.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../checkout/bloc/pass_purchase_details_bloc.dart'; +import '../../checkout/bloc/pass_purchase_details_event.dart'; +import '../../checkout/bloc/pass_purchase_details_state.dart'; +import '../../profile/view/edit_profile/edit_profile_view.dart'; import '../blocs/postcard_creation_bloc.dart'; import '../blocs/postcard_creation_events.dart'; import '../blocs/postcard_creation_state.dart'; - class PurchaseDetailsBottomSheet { static void show(BuildContext context) { final existingBloc = BlocProvider.of(context); @@ -17,189 +20,230 @@ class PurchaseDetailsBottomSheet { borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (BuildContext modalContext) { - return BlocProvider.value( - value: existingBloc, + return MultiBlocProvider( + providers: [ + BlocProvider.value(value: existingBloc), + BlocProvider( + create: (_) => PurchaseDetailsBloc()..add(LoadProfileEvent()), + ), + ], child: BlocBuilder( - builder: (context, state) { - final bloc = context.read(); + builder: (context, postcardState) { + final postcardBloc = context.read(); - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - top: 16, - left: 16, - right: 16, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 45, - height: 5, - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(10), - ), + return BlocBuilder( + builder: (context, purchaseState) { + final purchaseBloc = context.read(); + + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + top: 16, + left: 16, + right: 16, ), - const SizedBox(height: 12), - - Text( - "Purchase Details", - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 24), - - // 🟥 Option 1: Buy Postcard for Myself - GestureDetector( - onTap: () => bloc.add(TogglePurchaseOption(false)), - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: !state.isGift - ? const Color(0xffF95F62) - : const Color(0xffE0E0E0), - width: 1.5, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 45, + height: 5, + decoration: BoxDecoration( + color: Colors.grey[400], + borderRadius: BorderRadius.circular(10), ), ), - child: Row( - children: [ - Radio( - value: false, - groupValue: state.isGift, - onChanged: (_) => - bloc.add(TogglePurchaseOption(false)), - activeColor: const Color(0xffF95F62), + const SizedBox(height: 12), + + Text( + "Purchase Details", + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 24), + + // 🟥 Option 1: Buy Postcard for Myself + GestureDetector( + onTap: () { + postcardBloc.add(TogglePurchaseOption(false)); + purchaseBloc.add(ToggleGiftModeEvent(false)); + }, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: !postcardState.isGift + ? const Color(0xffF95F62) + : const Color(0xffE0E0E0), + width: 1.5, + ), ), - const SizedBox(width: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Text( - "Buy Postcard for Myself", + child: Row( + children: [ + Radio( + value: false, + groupValue: postcardState.isGift, + onChanged: (_) { + postcardBloc.add(TogglePurchaseOption(false)); + purchaseBloc.add(ToggleGiftModeEvent(false)); + }, + activeColor: const Color(0xffF95F62), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Buy Postcard for Myself", + style: TextStyle( + fontWeight: FontWeight.w600, + color: !postcardState.isGift + ? const Color(0xffF95F62) + : const Color(0xff9E9E9E), + ), + ), + if (!postcardState.isGift && purchaseState.profile != null) ...[ + const SizedBox(height: 8), + Text( + "${purchaseState.profile!.firstName} ${purchaseState.profile!.lastName}", + style: const TextStyle( + fontWeight: FontWeight.w500, + color: Color(0xff1A1A1A), + ), + ), + Text( + "${purchaseState.profile!.address1 ?? ""}\n${purchaseState.profile!.address2 ?? ""}", + style: const TextStyle( + fontSize: 13, + color: Color(0xff5E5E5E), + ), + ), + ], + if (!postcardState.isGift && purchaseState.isLoadingProfile) ...[ + const SizedBox(height: 8), + const SizedBox( + height: 16, + width: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Color(0xffF95F62), + ), + ), + ], + ], + ), + ), + if (!postcardState.isGift) + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const EditProfilePage(), + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: const EdgeInsets.symmetric( + horizontal: 14, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text( + "Edit Details", + style: TextStyle( + fontSize: 12, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 20), + + // 🩶 Option 2: Gift the Postcard + GestureDetector( + onTap: () { + postcardBloc.add(TogglePurchaseOption(true)); + purchaseBloc.add(ToggleGiftModeEvent(true)); + }, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: postcardState.isGift + ? const Color(0xffF95F62) + : const Color(0xffE0E0E0), + width: 1.5, + ), + ), + child: Row( + children: [ + Radio( + value: true, + groupValue: postcardState.isGift, + onChanged: (_) { + postcardBloc.add(TogglePurchaseOption(true)); + purchaseBloc.add(ToggleGiftModeEvent(true)); + }, + activeColor: const Color(0xffF95F62), + ), + const SizedBox(width: 8), + const Expanded( + child: Text( + "Gift the Postcard for someone else", style: TextStyle( fontWeight: FontWeight.w600, color: Color(0xffF95F62), ), ), - SizedBox(height: 8), - Text( - "Frank Adam", - style: TextStyle( - fontWeight: FontWeight.w500, - color: Color(0xff1A1A1A), - ), - ), - Text( - "132 My Street, Kingston, NY\n12401", - style: TextStyle( - fontSize: 13, - color: Color(0xff5E5E5E), - ), - ), - ], - ), - ), - ElevatedButton( - onPressed: () { - PurchaseDetailsBottomSheet.close(context); - bloc.add(GoToNextStep()); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xffF95F62), - padding: const EdgeInsets.symmetric( - horizontal: 14, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), ), - ), - child: const Text( - "Edit Details", - style: TextStyle( - fontSize: 12, - color: Colors.white, - fontWeight: FontWeight.w500, - ), - ), + ], ), - ], - ), - ), - ), - - const SizedBox(height: 20), - - // 🩶 Option 2: Gift the Postcard - GestureDetector( - onTap: () => bloc.add(TogglePurchaseOption(true)), - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: state.isGift - ? const Color(0xffF95F62) - : const Color(0xffE0E0E0), - width: 1.5, ), ), - child: Row( - children: [ - Radio( - value: true, - groupValue: state.isGift, - onChanged: (_) => - bloc.add(TogglePurchaseOption(true)), - activeColor: const Color(0xffF95F62), - ), - const SizedBox(width: 8), - const Expanded( - child: Text( - "Gift the Postcard for someone else", - style: TextStyle( - fontWeight: FontWeight.w600, - color: Color(0xffF95F62), - ), + + const SizedBox(height: 15), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + PurchaseDetailsBottomSheet.close(context); + postcardBloc.add(GoToNextStep()); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: EdgeInsets.symmetric(vertical: 16.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + ), + child: Text( + "Proceed", + style: TextStyle( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, ), ), - ], - ), - ), - ), - - const SizedBox(height: 15), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - PurchaseDetailsBottomSheet.close(context); - bloc.add(GoToNextStep()); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xffF95F62), - padding: EdgeInsets.symmetric(vertical: 16.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(40), ), ), - child: Text( - "Proceed", - style: TextStyle( - color: Colors.white, - fontSize: 14.sp, - fontWeight: FontWeight.w600, - ), - ), - ), + const SizedBox(height: 15), + ], ), - const SizedBox(height: 15), - ], - ), + ); + }, ); }, ), @@ -211,4 +255,4 @@ class PurchaseDetailsBottomSheet { static void close(BuildContext context) { Navigator.of(context).pop(); } -} +} \ No newline at end of file diff --git a/lib/profile/bloc/contactUs/contact_us_bloc.dart b/lib/profile/bloc/contactUs/contact_us_bloc.dart new file mode 100644 index 0000000..caca266 --- /dev/null +++ b/lib/profile/bloc/contactUs/contact_us_bloc.dart @@ -0,0 +1,42 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../repository/contact_us_repository.dart'; +import 'contact_us_event.dart'; +import 'contact_us_state.dart'; + +class ContactUsBloc extends Bloc { + final ContactUsRepository repository; + + ContactUsBloc({required this.repository}) + : super(ContactUsInitial()) { + on(_onSubmitContactUs); + } + + Future _onSubmitContactUs( + SubmitContactUsEvent event, + Emitter emit, + ) async { + emit(ContactUsLoading()); + + try { + final response = await repository.submitTicket( + firstName: event.firstName, + lastName: event.lastName, + emailAddress: event.emailAddress, + mobileNumber: event.mobileNumber, + description: event.description, + ); + + emit( + ContactUsSuccess( + message: response['message'] ?? 'Ticket submitted successfully', + ), + ); + } catch (e) { + emit( + ContactUsFailure( + error: e.toString().replaceAll('Exception:', '').trim(), + ), + ); + } + } +} diff --git a/lib/profile/bloc/contactUs/contact_us_event.dart b/lib/profile/bloc/contactUs/contact_us_event.dart new file mode 100644 index 0000000..4d31a82 --- /dev/null +++ b/lib/profile/bloc/contactUs/contact_us_event.dart @@ -0,0 +1,34 @@ +import 'package:equatable/equatable.dart'; + +abstract class ContactUsEvent extends Equatable { + const ContactUsEvent(); + + @override + List get props => []; +} + +/// Event to submit contact us / support ticket +class SubmitContactUsEvent extends ContactUsEvent { + final String firstName; + final String lastName; + final String emailAddress; + final String mobileNumber; + final String description; + + const SubmitContactUsEvent({ + required this.firstName, + required this.lastName, + required this.emailAddress, + required this.mobileNumber, + required this.description, + }); + + @override + List get props => [ + firstName, + lastName, + emailAddress, + mobileNumber, + description, + ]; +} diff --git a/lib/profile/bloc/contactUs/contact_us_state.dart b/lib/profile/bloc/contactUs/contact_us_state.dart new file mode 100644 index 0000000..ebfadb0 --- /dev/null +++ b/lib/profile/bloc/contactUs/contact_us_state.dart @@ -0,0 +1,34 @@ +import 'package:equatable/equatable.dart'; + +abstract class ContactUsState extends Equatable { + const ContactUsState(); + + @override + List get props => []; +} + +/// Initial state +class ContactUsInitial extends ContactUsState {} + +/// Loading state while submitting ticket +class ContactUsLoading extends ContactUsState {} + +/// Success state +class ContactUsSuccess extends ContactUsState { + final String message; + + const ContactUsSuccess({required this.message}); + + @override + List get props => [message]; +} + +/// Error state +class ContactUsFailure extends ContactUsState { + final String error; + + const ContactUsFailure({required this.error}); + + @override + List get props => [error]; +} diff --git a/lib/profile/bloc/profile/profile_bloc.dart b/lib/profile/bloc/profile/profile_bloc.dart index 97fa946..4a3a6e3 100644 --- a/lib/profile/bloc/profile/profile_bloc.dart +++ b/lib/profile/bloc/profile/profile_bloc.dart @@ -195,8 +195,7 @@ class ProfileBloc extends Bloc { print('📄 [BLOC] LogoutEvent received'); } - // Clear local preferences (uncomment when ready) - // await LocalPreference.clearPreference(); + await LocalPreference.resetAppData(); emit(const ProfileLoggedOut()); emit(const ProfileInitial()); diff --git a/lib/profile/repository/contact_us_repository.dart b/lib/profile/repository/contact_us_repository.dart new file mode 100644 index 0000000..6c5a756 --- /dev/null +++ b/lib/profile/repository/contact_us_repository.dart @@ -0,0 +1,32 @@ +import '../../networkApiServices/api_urls.dart'; +import '../../networkApiServices/network_api_services.dart'; + +class ContactUsRepository { + final NetworkApiService _apiServices = NetworkApiService(); + + /// Submit support ticket + Future> submitTicket({ + required String firstName, + required String lastName, + required String emailAddress, + required String mobileNumber, + required String description, + }) async { + try { + final response = await _apiServices.postApi( + url: ApiUrls.submitTicket, // add this key in ApiUrls + data: { + "firstName": firstName, + "lastName": lastName, + "emailAddress": emailAddress, + "mobileNumber": mobileNumber, + "description": description, + }, + ); + + return response.data as Map; + } catch (e) { + throw Exception('Failed to submit ticket: $e'); + } + } +} diff --git a/lib/profile/view/contact_us/contact_us_view.dart b/lib/profile/view/contact_us/contact_us_view.dart new file mode 100644 index 0000000..5aa6e41 --- /dev/null +++ b/lib/profile/view/contact_us/contact_us_view.dart @@ -0,0 +1,271 @@ +import 'package:citycards_customer/common_packages/app_bar.dart'; +import 'package:citycards_customer/common_packages/back_widget.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/common_packages/custom_textfield.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../../bloc/contactUs/contact_us_bloc.dart'; +import '../../bloc/contactUs/contact_us_event.dart'; +import '../../bloc/contactUs/contact_us_state.dart'; +import '../../repository/contact_us_repository.dart'; + +class ContactUsPage extends StatelessWidget { + const ContactUsPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => ContactUsBloc(repository: ContactUsRepository()), + child: const _ContactUsView(), + ); + } +} + +class _ContactUsView extends StatelessWidget { + const _ContactUsView(); + + @override + Widget build(BuildContext context) { + final firstNameController = TextEditingController(); + final lastNameController = TextEditingController(); + final emailController = TextEditingController(); + final phoneController = TextEditingController(); + final messageController = TextEditingController(); + + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: BlocListener( + listener: (context, state) { + if (state is ContactUsSuccess) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.message), + backgroundColor: Colors.green, + ), + ); + + firstNameController.clear(); + lastNameController.clear(); + emailController.clear(); + phoneController.clear(); + messageController.clear(); + } + + if (state is ContactUsFailure) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.error), + backgroundColor: Colors.red, + ), + ); + } + }, + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CommonAppBar( + isWhiteLogo: false, + isProfilePage: true, + showDivider: true, + ), + + backWidget(context, "Contact Us", Colors.black), + SizedBox(height: 22.h), + + CustomText( + text: + "You can get in touch with us through the below platforms. Our team will contact you shortly", + size: 14.sp, + color: Colors.black.withOpacity(.6), + ), + SizedBox(height: 20.h), + + /// Customer Support Section + Container( + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 16.h, + ), + decoration: BoxDecoration( + color: const Color(0x00000005).withOpacity(.02), + borderRadius: BorderRadius.circular(12.r), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText( + text: "Customer Support", + size: 18.sp, + weight: FontWeight.w500, + ), + SizedBox(height: 16.h), + _supportBox( + icon: Icons.phone, + title: "Contact Number", + subtitle: "+1012 3456 789", + action: "Tap to call", + ), + SizedBox(height: 12.h), + _supportBox( + icon: Icons.email_rounded, + title: "Email", + subtitle: "citycards24@gmail.com", + action: "Tap to email", + ), + SizedBox(height: 12.h), + _supportBox( + icon: Icons.location_on, + title: "Location", + subtitle: + "132 Dartmouth Street Boston, Massachusetts 02156 United States", + action: "View on map", + ), + ], + ), + ), + SizedBox(height: 24.h), + + /// Form Fields + CustomTextField( + label: "First Name", + hint: "Enter your first name", + controller: firstNameController, + ), + CustomTextField( + label: "Last Name", + hint: "Enter your last name", + controller: lastNameController, + ), + CustomTextField( + label: "Email", + hint: "Enter your email address", + controller: emailController, + ), + CustomTextField( + label: "Phone Number", + hint: "Enter your phone number", + controller: phoneController, + ), + CustomTextField( + label: "Description", + hint: "Write your message here", + maxLines: 4, + controller: messageController, + ), + + SizedBox(height: 24.h), + + /// Submit Button with Loading + BlocBuilder( + builder: (context, state) { + final isLoading = state is ContactUsLoading; + + return SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFF95F62), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(38.r), + ), + padding: EdgeInsets.symmetric(vertical: 6.h), + ), + onPressed: isLoading + ? null + : () { + context.read().add( + SubmitContactUsEvent( + firstName: firstNameController.text.trim(), + lastName: lastNameController.text.trim(), + emailAddress: emailController.text.trim(), + mobileNumber: phoneController.text.trim(), + description: messageController.text.trim(), + ), + ); + }, + child: isLoading + ? SizedBox( + height: 22.h, + width: 22.h, + child: const CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : CustomText( + text: "Submit Ticket", + size: 16.sp, + weight: FontWeight.w500, + color: Colors.white, + ), + ), + ); + }, + ), + SizedBox(height: 20.h), + ], + ), + ), + ), + ), + ); + } + + /// Support Box Widget + static Widget _supportBox({ + required IconData icon, + required String title, + required String subtitle, + required String action, + }) { + return Container( + width: double.infinity, + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + border: Border.all(color: const Color(0xFFF95F62), width: 0.8), + color: Colors.white, + ), + child: Row( + children: [ + Icon(icon, color: const Color(0xFFF95F62), size: 32.sp), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText( + text: title, + size: 11.sp, + weight: FontWeight.w600, + color: Colors.black.withOpacity(.6), + ), + SizedBox(height: 6.h), + Text( + subtitle, + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 2.h), + Text( + action, + style: TextStyle( + fontSize: 11.sp, + color: Colors.black.withOpacity(.4), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/profile/view/edit_profile/edit_profile_view.dart b/lib/profile/view/edit_profile/edit_profile_view.dart index 12b8b56..b58ca4c 100644 --- a/lib/profile/view/edit_profile/edit_profile_view.dart +++ b/lib/profile/view/edit_profile/edit_profile_view.dart @@ -46,6 +46,8 @@ class _EditProfilePageState extends State { } final userId = await LocalPreference.getUserId(); + if (!mounted) return; + if (userId != null) { context.read().add(FetchProfileEvent(userId: userId)); } @@ -174,6 +176,8 @@ class _EditProfilePageState extends State { print('🔵 [EDIT PROFILE] File size: ${(fileSize / 1024).toStringAsFixed(2)} KB'); } + if (!mounted) return; + // ⭐ REPLACED setState with BLoC event context.read().add( ProfileImageSelectedEvent(imageFile: imageFile), @@ -184,6 +188,8 @@ class _EditProfilePageState extends State { print('❌ [EDIT PROFILE] Error picking image: $e'); } + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Failed to pick image: $e'), @@ -279,6 +285,7 @@ class _EditProfilePageState extends State { final userId = await LocalPreference.getUserId(); if (userId == null) { + if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('User ID not found'), @@ -288,6 +295,8 @@ class _EditProfilePageState extends State { return; } + if (!mounted) return; + // ⭐ Get selectedImageFile from current BLoC state File? imageFileToSend; final currentState = context.read().state; @@ -333,8 +342,18 @@ class _EditProfilePageState extends State { backgroundColor: Colors.white, body: SafeArea( child: BlocConsumer( - listener: (context, state) { + listener: (context, state) async { if (state is ProfileUpdated) { + if (state.profile.profileImage != null && + state.profile.profileImage!.isNotEmpty) { + await LocalPreference.setProfileImage( + state.profile.profileImage!, + ); + } + + // Check if widget is still mounted before using context + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), diff --git a/lib/profile/view/profile_page_view.dart b/lib/profile/view/profile_page_view.dart index 0fafa63..fc0efa3 100644 --- a/lib/profile/view/profile_page_view.dart +++ b/lib/profile/view/profile_page_view.dart @@ -35,7 +35,20 @@ class _ProfilePageState extends State { return Scaffold( backgroundColor: Colors.white, body: SafeArea( - child: BlocBuilder( + child: BlocConsumer( + listener: (context, state) { + // ⭐ SOLUTION: Auto-refresh when profile is updated (from edit page) + // This prevents race conditions with manual navigation callbacks + if (state is ProfileUpdated) { + // Profile was just updated, fetch fresh data + final userId = state.profile.id; + if (mounted) { + context.read().add( + FetchProfileEvent(userId: userId), + ); + } + } + }, builder: (context, state) { // ⭐ Show loading during initial checks and profile loading if (state is ProfileInitial || @@ -126,6 +139,10 @@ class _ProfilePageState extends State { ElevatedButton( onPressed: () async { final userId = await LocalPreference.getUserId(); + + // ⭐ Check if widget is still mounted after async call + if (!mounted) return; + if (userId != null) { context.read().add( FetchProfileEvent(userId: userId), @@ -213,8 +230,11 @@ class _ProfilePageState extends State { padding: EdgeInsets.symmetric(vertical: 6.h), ), onPressed: () { - // ⭐ REPLACED setState with BLoC event context.read().add(const LogoutEvent()); + Navigator.pushReplacementNamed( + context, + RouteConstants.home, + ); }, child: Text( 'Log out', @@ -451,20 +471,13 @@ class _ProfilePageState extends State { _buildListTile( icon: "assets/icons/user_profile.png", title: 'Edit profile', - onTap: () async { - final result = await Navigator.pushNamed( + onTap: () { + // ⭐ SOLUTION: Just navigate - BlocListener will auto-refresh on ProfileUpdated + // This prevents race conditions from manual refresh callbacks + Navigator.pushNamed( context, RouteConstants.editProfile, ); - - if (result == true) { - final userId = await LocalPreference.getUserId(); - if (userId != null) { - context.read().add( - FetchProfileEvent(userId: userId), - ); - } - } }, ),