fix(auth,checkout): add Axios 401/403 auto-logout response interceptor and fix charity round-up 10k exact math

This commit is contained in:
parsa aghaei 2026-07-26 20:23:27 +03:30
parent 72badb1a7b
commit bdf8856883
3 changed files with 86 additions and 8 deletions

View File

@ -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
}
}

View File

@ -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);

View File

@ -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;