fix cart, fix log cart

This commit is contained in:
Phuoc Nguyen
2025-12-11 16:39:25 +07:00
parent e3632d4445
commit fc6a4f038e
4 changed files with 65 additions and 44 deletions

View File

@@ -79,7 +79,8 @@ class SentryService {
..beforeSend = (event, hint) { ..beforeSend = (event, hint) {
// Filter out certain errors if needed // Filter out certain errors if needed
// Return null to drop the event // Return null to drop the event
return event; // return event;
return null;
}; };
}, },
appRunner: appRunner, appRunner: appRunner,

View File

@@ -36,6 +36,7 @@ class CartPage extends ConsumerStatefulWidget {
class _CartPageState extends ConsumerState<CartPage> { class _CartPageState extends ConsumerState<CartPage> {
bool _isSyncing = false; bool _isSyncing = false;
bool _hasLoggedViewCart = false;
// Cart is initialized once in home_page.dart at app startup // Cart is initialized once in home_page.dart at app startup
// Provider has keepAlive: true, so no need to reload here // Provider has keepAlive: true, so no need to reload here
@@ -44,20 +45,12 @@ class _CartPageState extends ConsumerState<CartPage> {
// and in checkout button handler for checkout flow. // and in checkout button handler for checkout flow.
// No dispose() method needed - using ref.read() in dispose() is unsafe. // No dispose() method needed - using ref.read() in dispose() is unsafe.
@override void _logViewCartOnce(CartState cartState) {
Widget build(BuildContext context) { if (_hasLoggedViewCart || cartState.isEmpty) return;
final colorScheme = Theme.of(context).colorScheme; _hasLoggedViewCart = true;
final cartState = ref.watch(cartProvider);
final itemCount = cartState.itemCount;
final hasSelection = cartState.selectedCount > 0;
// Log view cart analytics event when cart has items
if (cartState.isNotEmpty) {
AnalyticsService.logViewCart( AnalyticsService.logViewCart(
cartValue: cartState.selectedTotal, cartValue: cartState.totalPrice,
items: cartState.items.map((item) => AnalyticsEventItem( items: cartState.items.map((item) => AnalyticsEventItem(
itemId: item.product.productId, itemId: item.product.productId,
itemName: item.product.name, itemName: item.product.name,
@@ -67,6 +60,17 @@ class _CartPageState extends ConsumerState<CartPage> {
); );
} }
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final cartState = ref.watch(cartProvider);
// Log view cart analytics event only once when page opens
_logViewCartOnce(cartState);
final itemCount = cartState.itemCount;
final hasSelection = cartState.selectedCount > 0;
return PopScope( return PopScope(
// Intercept back navigation to sync pending updates // Intercept back navigation to sync pending updates
onPopInvokedWithResult: (didPop, result) async { onPopInvokedWithResult: (didPop, result) async {
@@ -346,6 +350,7 @@ class _CartPageState extends ConsumerState<CartPage> {
/// Build error banner (shown at top when there's an error but cart has items) /// Build error banner (shown at top when there's an error but cart has items)
Widget _buildErrorBanner(String errorMessage) { Widget _buildErrorBanner(String errorMessage) {
print(errorMessage);
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),

View File

@@ -7,7 +7,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart'; import 'package:go_router/go_router.dart';
import 'package:worker/core/utils/extensions.dart'; import 'package:worker/core/utils/extensions.dart';
import 'package:worker/core/widgets/loading_indicator.dart'; import 'package:worker/core/widgets/loading_indicator.dart';
import 'package:worker/core/theme/typography.dart'; import 'package:worker/core/theme/typography.dart';
@@ -113,8 +113,13 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
const SizedBox(width: 12), const SizedBox(width: 12),
// Product Image (bigger: 100x100) // Product Image (bigger: 100x100) - tap to navigate to product detail
ClipRRect( GestureDetector(
onTap: () {
// Navigate to product detail with product ID in path
context.push('/products/${widget.item.product.productId}');
},
child: ClipRRect(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: widget.item.product.thumbnail.isNotEmpty imageUrl: widget.item.product.thumbnail.isNotEmpty
@@ -145,6 +150,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
), ),
), ),
), ),
),
const SizedBox(width: 12), const SizedBox(width: 12),
@@ -193,14 +199,15 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
const SizedBox(width: 8), const SizedBox(width: 8),
// Quantity TextField // Quantity TextField - uses text keyboard for Done button on iOS/Android
SizedBox( SizedBox(
width: 50, width: 50,
height: 32, height: 32,
child: TextField( child: TextField(
controller: _quantityController, controller: _quantityController,
focusNode: _quantityFocusNode, focusNode: _quantityFocusNode,
keyboardType: TextInputType.number, keyboardType: TextInputType.text,
textInputAction: TextInputAction.done,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: AppTypography.titleMedium.copyWith( style: AppTypography.titleMedium.copyWith(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,

View File

@@ -138,7 +138,15 @@ class ProductsRemoteDataSource {
throw Exception('Product not found: $itemCode'); throw Exception('Product not found: $itemCode');
} }
return ProductModel.fromJson(message as Map<String, dynamic>); // Handle API error response: {success: false, message: "Item not found"}
if (message is Map<String, dynamic>) {
if (message['success'] == false) {
throw Exception(message['message'] ?? 'Product not found: $itemCode');
}
return ProductModel.fromJson(message);
}
throw Exception('Invalid response format for product: $itemCode');
} on DioException catch (e) { } on DioException catch (e) {
if (e.response?.statusCode == 404) { if (e.response?.statusCode == 404) {
throw Exception('Product not found: $itemCode'); throw Exception('Product not found: $itemCode');