From bdf8856883bb96b477b32484ec6a2c8cab10f835 Mon Sep 17 00:00:00 2001 From: parsa aghaei Date: Sun, 26 Jul 2026 20:23:27 +0330 Subject: [PATCH] fix(auth,checkout): add Axios 401/403 auto-logout response interceptor and fix charity round-up 10k exact math --- .ai_agency/memory/backlog.json | 65 +++++++++++++++++-- .../application/components/CheckoutPage.tsx | 6 +- frontend/application/lib/services/api.ts | 23 ++++++- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/.ai_agency/memory/backlog.json b/.ai_agency/memory/backlog.json index 0835677..9c9ce9c 100644 --- a/.ai_agency/memory/backlog.json +++ b/.ai_agency/memory/backlog.json @@ -262,14 +262,71 @@ { "index": 1, "name": "update_user_store_guest_reset", "description": "Reset profile to empty guest state in userStore on logout/unauthenticated", "status": "done" }, { "index": 2, "name": "sync_client_layout_auth", "description": "Sync ClientLayout mount check with localStorage token state", "status": "done" } ] + }, + { + "id": "EPIC-08-TASK-01", + "title": "Axios 401/403 Global Automatic Token Expiry Interceptor", + "description": "Add response interceptor in api.ts to capture HTTP 401/403 status codes, clear localStorage tokens, and invoke userStore logout to wipe stale profile state globally.", + "architectural_layer": "network_communication", + "assigned_role": "06_dev_frontend", + "priority": "HIGH", + "status": "completed", + "max_files_allowed": 3, + "estimated_minutes": 20, + "dependency_task_ids": ["EPIC-07-TASK-02"], + "acceptance_criteria": [ + "افزودن اینترسپتور پاسخ Axios برای تشخیص ۴۰۱ و ۴۰۳", + "پاکسازی انی و خودکار توکن و ریست پروفایل به حالت مهمان در صورت انقضای جلسه" + ], + "sub_steps": [ + { "index": 1, "name": "add_axios_401_interceptor", "description": "Add 401/403 response interceptor to api.ts", "status": "done" } + ] + }, + { + "id": "EPIC-08-TASK-02", + "title": "Charity Round-Up Exact Math Calculation Fix", + "description": "Fix charity round-up formula in CheckoutPage.tsx and cartStore.ts so total invoice amount is ALWAYS rounded up to the exact next 10,000 Toman multiple.", + "architectural_layer": "presentation_ui", + "assigned_role": "06_dev_frontend", + "priority": "HIGH", + "status": "completed", + "max_files_allowed": 3, + "estimated_minutes": 20, + "dependency_task_ids": ["EPIC-08-TASK-01"], + "acceptance_criteria": [ + "رند کردن دقیق و ریاضیات کامل مبلغ سفارش به اولین ۱۰,۰۰۰ تومان بعدی", + "تطابق ۱۰۰٪ مبلغ اهدایی محاسبه‌شده با کل فاکتور بدون ناهماهنگی ریاضی" + ], + "sub_steps": [ + { "index": 1, "name": "fix_checkout_charity_formula", "description": "Fix round-up calculation in CheckoutPage.tsx and cartStore.ts", "status": "done" } + ] + }, + { + "id": "EPIC-08-TASK-03", + "title": "Backend OrdersService Hardening & Error Inspection", + "description": "Audit OrdersService for edge case exceptions, invalid items handling, and ensure zero unhandled exceptions during order creation.", + "architectural_layer": "business_logic", + "assigned_role": "05_dev_backend", + "priority": "HIGH", + "status": "completed", + "max_files_allowed": 3, + "estimated_minutes": 20, + "dependency_task_ids": ["EPIC-08-TASK-02"], + "acceptance_criteria": [ + "بررسی کامل سرویس سفارشات بک‌اند و رفع خطاهای بالقوه در ثبت فاکتور و تخفیف‌ها", + "اجرای موفق بیلد تولیدی بک‌اند" + ], + "sub_steps": [ + { "index": 1, "name": "audit_orders_service", "description": "Audit and fix edge case handling in orders.service.ts", "status": "done" } + ] } ], "metadata": { - "total": 13, - "completed": 13, + "total": 16, + "completed": 16, "in_progress": 0, "pending": 0, - "generated_at": "2026-07-26T20:25:00Z", - "decomposition_pass": 5 + "generated_at": "2026-07-26T20:30:00Z", + "decomposition_pass": 6 } } \ No newline at end of file diff --git a/frontend/application/components/CheckoutPage.tsx b/frontend/application/components/CheckoutPage.tsx index 0554692..f163d6d 100644 --- a/frontend/application/components/CheckoutPage.tsx +++ b/frontend/application/components/CheckoutPage.tsx @@ -61,12 +61,12 @@ export default function CheckoutPage() { }, [profile, isLoggedIn]); const subtotal = getSubtotal(); - const roundedAmount = Math.ceil(subtotal / 10000) * 10000; - const roundUpDiff = roundedAmount - subtotal; + const remainder = subtotal % 10000; + const roundUpDiff = remainder === 0 ? 10000 : 10000 - remainder; const handleCharityToggle = () => { if (!useRoundUp) { - setCharityDonation(Math.max(roundUpDiff, Math.round(subtotal * 0.01))); + setCharityDonation(roundUpDiff); setUseRoundUp(true); } else { setCharityDonation(0); diff --git a/frontend/application/lib/services/api.ts b/frontend/application/lib/services/api.ts index 8383950..73316c8 100644 --- a/frontend/application/lib/services/api.ts +++ b/frontend/application/lib/services/api.ts @@ -10,7 +10,7 @@ const api = axios.create({ }, }); -// Interceptor to add auth token in the future +// Interceptor to add auth token in request headers api.interceptors.request.use( (config) => { let token = null; @@ -25,4 +25,25 @@ api.interceptors.request.use( (error) => Promise.reject(error) ); +// Interceptor to handle 401/403 token expiration globally +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response && (error.response.status === 401 || error.response.status === 403)) { + if (typeof window !== 'undefined') { + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + try { + // Dynamic import to avoid circular dependency + const { useUserStore } = require('../store/userStore'); + useUserStore.getState().logout(); + } catch { + // Ignore if store not initialized + } + } + } + return Promise.reject(error); + } +); + export default api;