Skip to content

Commit a29b11d

Browse files
author
Dang
committed
fix jwt logic
1 parent 35cefdd commit a29b11d

File tree

9 files changed

+220
-145
lines changed

9 files changed

+220
-145
lines changed

e_shoppe/lib/features/cart/cart_page.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,15 @@ class _CartListTile extends StatelessWidget {
6666
@override
6767
Widget build(BuildContext context) {
6868
return ListTile(
69-
leading: Image.network(item.product.imageUrl,
70-
width: 56, height: 56, fit: BoxFit.cover),
69+
leading: item.product.imageUrl.isNotEmpty
70+
? Image.network(item.product.imageUrl,
71+
width: 56, height: 56, fit: BoxFit.cover)
72+
: Container(
73+
width: 56,
74+
height: 56,
75+
color: Colors.grey.shade300,
76+
child: const Icon(Icons.image_not_supported),
77+
),
7178
title: Text(item.product.title,
7279
maxLines: 1, overflow: TextOverflow.ellipsis),
7380
subtitle: Text('Subtotal: \$${item.subtotal.toStringAsFixed(2)}'),

e_shoppe/lib/features/checkout/checkout_page.dart

Lines changed: 119 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,17 @@ class _CheckoutPageState extends ConsumerState<CheckoutPage> {
4343
}
4444
}
4545
});
46+
}
47+
48+
@override
49+
void dispose() {
50+
_addressController.dispose();
51+
super.dispose();
52+
}
4653

47-
// Listen for draft changes and keep CartBloc in sync
54+
@override
55+
Widget build(BuildContext context) {
56+
// Ref listener moved to build method to comply with Riverpod rules.
4857
ref.listen<OrderDraft>(orderDraftProvider, (prev, next) {
4958
final cartBloc = context.read<CartBloc>();
5059
// Rebuild cart state to mirror draft exactly.
@@ -59,16 +68,6 @@ class _CheckoutPageState extends ConsumerState<CheckoutPage> {
5968
}
6069
}
6170
});
62-
}
63-
64-
@override
65-
void dispose() {
66-
_addressController.dispose();
67-
super.dispose();
68-
}
69-
70-
@override
71-
Widget build(BuildContext context) {
7271
final authState = context.watch<AuthBloc>().state;
7372
if (authState.status == AuthStatus.authenticated &&
7473
authState.user?.address != null &&
@@ -106,12 +105,15 @@ class _CheckoutPageState extends ConsumerState<CheckoutPage> {
106105
child: GestureDetector(
107106
onTap: () async {
108107
final selected = await Navigator.of(context).push(
109-
MaterialPageRoute(builder: (_) => const CustomerSearchPage()),
108+
MaterialPageRoute(
109+
builder: (_) => const CustomerSearchPage()),
110110
);
111111
if (selected != null && selected is User) {
112112
// For now just show snackbar; integration can follow
113113
ScaffoldMessenger.of(context).showSnackBar(
114-
SnackBar(content: Text('Đã chọn khách: ${selected.name}')),
114+
SnackBar(
115+
content: Text(
116+
'Đã chọn khách: ${selected.name}')),
115117
);
116118
}
117119
},
@@ -148,7 +150,15 @@ class _CheckoutPageState extends ConsumerState<CheckoutPage> {
148150
padding: const EdgeInsets.symmetric(horizontal: 16),
149151
child: SectionCard(
150152
title: 'Danh sách sản phẩm',
151-
trailing: const SizedBox.shrink(),
153+
trailing: TextButton.icon(
154+
onPressed: () {
155+
Navigator.of(context)
156+
.pushNamed('/order/select-product');
157+
},
158+
icon: const Icon(Icons.add_circle_outline,
159+
color: Colors.green, size: 18),
160+
label: const Text('Thêm mới'),
161+
),
152162
child: _ProductSection(items: state.items),
153163
),
154164
),
@@ -183,6 +193,11 @@ class _CheckoutPageState extends ConsumerState<CheckoutPage> {
183193
onTap: state.items.isEmpty
184194
? null
185195
: () {
196+
// Sync latest cart items into OrderDraft before proceeding
197+
ref
198+
.read(orderDraftProvider.notifier)
199+
.setItems(state.items);
200+
186201
Navigator.of(context).push(
187202
MaterialPageRoute(
188203
builder: (_) => const PaymentPage()),
@@ -262,8 +277,7 @@ class _CustomerInfoContent extends StatelessWidget {
262277
decoration: const InputDecoration(
263278
hintText: 'Địa chỉ giao hàng',
264279
isDense: true,
265-
border: OutlineInputBorder(
266-
borderSide: BorderSide.none),
280+
border: OutlineInputBorder(borderSide: BorderSide.none),
267281
),
268282
),
269283
),
@@ -295,17 +309,18 @@ class _ProductSection extends StatelessWidget {
295309

296310
@override
297311
Widget build(BuildContext context) {
298-
final cols = Responsive.gridColumnCount(context);
299-
return GridView.builder(
312+
if (items.isEmpty) {
313+
return const Padding(
314+
padding: EdgeInsets.all(12),
315+
child: Text('Chưa có sản phẩm'),
316+
);
317+
}
318+
319+
return ListView.separated(
300320
shrinkWrap: true,
301321
physics: const NeverScrollableScrollPhysics(),
302-
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
303-
crossAxisCount: cols,
304-
mainAxisSpacing: 8,
305-
crossAxisSpacing: 8,
306-
childAspectRatio: cols == 1 ? 5 : 3,
307-
),
308322
itemCount: items.length,
323+
separatorBuilder: (_, __) => const Divider(height: 1),
309324
itemBuilder: (_, i) => _ProductRow(item: items[i]),
310325
);
311326
}
@@ -320,70 +335,88 @@ class _ProductRow extends StatelessWidget {
320335
Widget build(BuildContext context) {
321336
return Container(
322337
padding: const EdgeInsets.all(8),
323-
child: SizedBox(
324-
width: double.infinity,
325-
child: Row(
326-
children: [
327-
Container(width: 60, height: 60, color: Colors.green.shade200),
328-
const SizedBox(width: 8),
329-
Expanded(
330-
child: Column(
331-
crossAxisAlignment: CrossAxisAlignment.start,
332-
children: [
333-
Text(item.product.title,
334-
maxLines: 1,
335-
overflow: TextOverflow.ellipsis,
336-
style: const TextStyle(
337-
color: Colors.orange, fontWeight: FontWeight.bold)),
338-
Text(_currency(item.product.price),
339-
style: const TextStyle(
340-
color: Colors.red, fontWeight: FontWeight.bold)),
341-
],
342-
),
343-
),
344-
InkWell(
345-
onTap: () {
346-
context.read<CartBloc>().add(CartItemQuantityChanged(
347-
productId: item.product.id, delta: -1));
348-
},
349-
child: const Icon(Icons.remove_circle_outline, size: 20),
350-
),
351-
Text(item.quantity.toString()),
352-
InkWell(
353-
onTap: () {
354-
context.read<CartBloc>().add(CartItemQuantityChanged(
355-
productId: item.product.id, delta: 1));
356-
},
357-
child: const Icon(Icons.add_circle_outline, size: 20),
358-
),
359-
// discount row
360-
Padding(
361-
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
362-
child: Row(
363-
children: [
364-
const Text('Giảm giá:'),
365-
const SizedBox(width: 8),
366-
Expanded(
367-
child: TextField(
368-
keyboardType: TextInputType.number,
369-
decoration: const InputDecoration(
370-
isDense: true,
371-
contentPadding: EdgeInsets.all(6),
372-
hintText: '0'),
373-
onSubmitted: (value) {
374-
final discount = double.tryParse(value) ?? 0;
375-
context.read<CartBloc>().add(CartItemDiscountChanged(
376-
productId: item.product.id, discount: discount));
377-
},
338+
child: Column(
339+
crossAxisAlignment: CrossAxisAlignment.start,
340+
children: [
341+
Row(
342+
children: [
343+
item.product.imageUrl.isNotEmpty
344+
? Image.network(item.product.imageUrl,
345+
width: 60, height: 60, fit: BoxFit.cover)
346+
: Container(
347+
width: 60,
348+
height: 60,
349+
color: Colors.grey.shade300,
350+
child: const Icon(Icons.image_not_supported),
378351
),
379-
),
380-
const SizedBox(width: 4),
381-
const Text('vnd'),
382-
],
352+
const SizedBox(width: 8),
353+
Expanded(
354+
child: Column(
355+
crossAxisAlignment: CrossAxisAlignment.start,
356+
children: [
357+
Text(item.product.title,
358+
maxLines: 1,
359+
overflow: TextOverflow.ellipsis,
360+
style: const TextStyle(
361+
color: Colors.orange, fontWeight: FontWeight.bold)),
362+
Text(_currency(item.product.price),
363+
style: const TextStyle(
364+
color: Colors.red, fontWeight: FontWeight.bold)),
365+
],
366+
),
383367
),
384-
),
385-
],
386-
),
368+
InkWell(
369+
onTap: () {
370+
context.read<CartBloc>().add(CartItemQuantityChanged(
371+
productId: item.product.id, delta: -1));
372+
},
373+
child: const Icon(Icons.remove_circle_outline, size: 20),
374+
),
375+
const SizedBox(width: 4),
376+
Text(item.quantity.toString()),
377+
const SizedBox(width: 4),
378+
InkWell(
379+
onTap: () {
380+
context.read<CartBloc>().add(CartItemQuantityChanged(
381+
productId: item.product.id, delta: 1));
382+
},
383+
child: const Icon(Icons.add_circle_outline, size: 20),
384+
),
385+
],
386+
),
387+
const SizedBox(height: 6),
388+
Row(
389+
children: [
390+
const Text('Giảm giá:'),
391+
const SizedBox(width: 8),
392+
Expanded(
393+
child: TextFormField(
394+
initialValue: item.discountValue > 0
395+
? item.discountValue.toStringAsFixed(0)
396+
: '',
397+
keyboardType: TextInputType.number,
398+
decoration: const InputDecoration(
399+
isDense: true,
400+
contentPadding: EdgeInsets.all(6),
401+
hintText: '0'),
402+
onChanged: (value) {
403+
final discount = double.tryParse(value) ?? 0;
404+
// apply immediately as user types
405+
context.read<CartBloc>().add(CartItemDiscountChanged(
406+
productId: item.product.id, discount: discount));
407+
},
408+
onFieldSubmitted: (value) {
409+
final discount = double.tryParse(value) ?? 0;
410+
context.read<CartBloc>().add(CartItemDiscountChanged(
411+
productId: item.product.id, discount: discount));
412+
},
413+
),
414+
),
415+
const SizedBox(width: 4),
416+
const Text('vnd'),
417+
],
418+
),
419+
],
387420
),
388421
);
389422
}

e_shoppe/lib/features/checkout/payment_page.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,14 @@ class _PaymentPageState extends ConsumerState<PaymentPage> {
108108
});
109109

110110
// Clear local state
111-
context.read<CartBloc>().add(const CartCleared());
111+
context.read<CartBloc>().add(const CartCleared());
112112
ref.read(orderDraftProvider.notifier).clear();
113113

114-
Navigator.of(context).pushAndRemoveUntil(
115-
MaterialPageRoute(
116-
builder: (_) => const OrderSuccessPage()),
117-
(route) => route.isFirst,
118-
);
114+
Navigator.of(context).pushAndRemoveUntil(
115+
MaterialPageRoute(
116+
builder: (_) => const OrderSuccessPage()),
117+
(route) => route.isFirst,
118+
);
119119
} catch (e) {
120120
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
121121
content: Text('Gửi đơn thất bại: $e')));

e_shoppe/lib/features/order/order_confirm_page.dart

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,15 @@ class OrderConfirmPage extends ConsumerWidget {
4242
},
4343
children: [
4444
...draft.items.map((e) => TableRow(children: [
45-
Image.network(e.product.imageUrl, width: 56, height: 56),
45+
e.product.imageUrl.isNotEmpty
46+
? Image.network(e.product.imageUrl,
47+
width: 56, height: 56, fit: BoxFit.cover)
48+
: Container(
49+
width: 56,
50+
height: 56,
51+
color: Colors.grey.shade300,
52+
child: const Icon(Icons.image_not_supported),
53+
),
4654
Padding(
4755
padding: const EdgeInsets.symmetric(
4856
horizontal: 4, vertical: 6),
@@ -78,11 +86,20 @@ class OrderConfirmPage extends ConsumerWidget {
7886
builder: (_) =>
7987
const Center(child: CircularProgressIndicator()));
8088
await repo.createOrder(draft.customer!, draft.items);
89+
// dismiss loading dialog before navigation
90+
if (context.mounted) Navigator.of(context).pop();
91+
8192
ref.read(orderDraftProvider.notifier).clear();
82-
Navigator.of(context).pushNamedAndRemoveUntil(
83-
'/order/success', ModalRoute.withName('/orders'));
93+
94+
if (context.mounted) {
95+
Navigator.of(context).pushNamedAndRemoveUntil(
96+
'/order/success',
97+
ModalRoute.withName('/orders'),
98+
);
99+
}
84100
} catch (e) {
85-
Navigator.of(context).pop();
101+
// ensure loading dialog is closed
102+
if (context.mounted) Navigator.of(context).pop();
86103
Navigator.of(context).pushNamed('/order/failure');
87104
}
88105
},

e_shoppe/lib/features/order/product_select_page.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,15 @@ class _ProductSelectPageState extends ConsumerState<ProductSelectPage> {
8383
itemBuilder: (context, index) {
8484
final p = products[index];
8585
return ListTile(
86-
leading: Image.network(p.imageUrl, width: 56, height: 56),
86+
leading: p.imageUrl.isNotEmpty
87+
? Image.network(p.imageUrl,
88+
width: 56, height: 56, fit: BoxFit.cover)
89+
: Container(
90+
width: 56,
91+
height: 56,
92+
color: Colors.grey.shade300,
93+
child: const Icon(Icons.image_not_supported),
94+
),
8795
title: Text(p.title,
8896
maxLines: 2, overflow: TextOverflow.ellipsis),
8997
subtitle: Text('${p.price.toStringAsFixed(0)} đ'),

e_shoppe/lib/features/order/riverpod/order_draft_provider.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ class OrderDraftNotifier extends StateNotifier<OrderDraft> {
9797
items: state.items.where((e) => e.product.id != productId).toList());
9898
}
9999

100+
/// Replace entire items list, e.g. when syncing from CartBloc.
101+
void setItems(List<CartItem> items) {
102+
final discountTotal = items.fold(0.0, (sum, e) => sum + e.discountValue);
103+
state = state.copyWith(items: items, discount: discountTotal);
104+
}
105+
100106
void clear() {
101107
state = const OrderDraft();
102108
}

0 commit comments

Comments
 (0)