/** * @page Customer Account & Orders (`app/account/page.tsx`) * @purpose TechShop-styled customer account: profile, addresses, orders, settings. * @dependencies useAuthStore, apiFetch */ 'use client'; import React, { useState, useEffect, Suspense } from 'react'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; import { User, Package, MapPin, Settings, LogOut, Plus, Edit2, Trash2, Check, ShieldCheck, ShoppingBag, Loader2, KeyRound, X, } from 'lucide-react'; import { useAuthStore } from '@/store/authStore'; import { apiFetch, clearTokens } from '@/services/api/client'; import { formatCurrency } from '@/lib/utils'; import GoogleAuthModal from '@/components/auth/GoogleAuthModal'; import { AnnouncementBar } from '@/layout/AnnouncementBar'; import { Header } from '@/layout/Header'; import { Navbar } from '@/layout/Navbar'; import { Footer } from '@/layout/Footer'; import { StickyHeaderSpacer } from '@/components/StickyHeaderSpacer'; import { PhoneInput, PincodeInput } from '@/components/PhoneInput'; import { validatePhone, validatePincode, validateAddress } from '@/lib/validation'; import { FloatingButtons } from '@/components/FloatingButtons'; type TabType = 'dashboard' | 'orders' | 'addresses' | 'settings'; const VALID_TABS: TabType[] = [ 'dashboard', 'orders', 'addresses', 'settings', ]; function parseTab(raw: string | null): TabType { if (raw && VALID_TABS.includes(raw as TabType)) return raw as TabType; return 'dashboard'; } interface Address { address_id?: string; address_type: string; full_name: string; phone: string; street_address: string; city: string; state: string; pincode: string; is_default: boolean; } interface Order { order_id: string; order_no: string; final_amount: number; status: string; payment_status: string; created_at: string; } const TABS: { id: TabType; label: string; icon: typeof User }[] = [ { id: 'dashboard', label: 'Dashboard', icon: User }, { id: 'orders', label: 'Order History', icon: Package }, { id: 'addresses', label: 'Address Book', icon: MapPin }, { id: 'settings', label: 'Settings', icon: Settings }, ]; const inputClass = 'w-full px-3 py-2.5 bg-white border border-[#e5e5e5] text-[13px] text-[#222] outline-none focus:border-primary transition-colors'; const labelClass = 'block text-[11px] font-semibold text-[#666] uppercase tracking-wide mb-1.5'; function AccountDashboardPage() { const router = useRouter(); const searchParams = useSearchParams(); const { isAuthenticated, clearAuth, setAuth, authReady, bootstrapAuth, } = useAuthStore(); const [authModalOpen, setAuthModalOpen] = useState(false); const [activeTab, setActiveTab] = useState(() => parseTab(searchParams.get('tab')) ); const [profile, setProfile] = useState(null); const [addresses, setAddresses] = useState([]); const [orders, setOrders] = useState([]); const [loading, setLoading] = useState(true); const [addressModalOpen, setAddressModalOpen] = useState(false); const [editingAddress, setEditingAddress] = useState
(null); const [addressForm, setAddressForm] = useState
({ address_type: 'SHIPPING', full_name: '', phone: '', street_address: '', city: '', state: '', pincode: '', is_default: false, }); const [settingsForm, setSettingsForm] = useState({ first_name: '', last_name: '', phone: '', }); const [actionLoading, setActionLoading] = useState(false); const [errorMsg, setErrorMsg] = useState(null); useEffect(() => { void bootstrapAuth(); }, [bootstrapAuth]); useEffect(() => { setActiveTab(parseTab(searchParams.get('tab'))); }, [searchParams]); const goToTab = (tab: TabType) => { setActiveTab(tab); setErrorMsg(null); const qs = tab === 'dashboard' ? '/account' : `/account?tab=${tab}`; router.replace(qs, { scroll: false }); }; useEffect(() => { if (!authReady) return; if (!isAuthenticated) { setAuthModalOpen(true); } else { setAuthModalOpen(false); fetchAccountData(); } }, [isAuthenticated, authReady]); const fetchAccountData = async () => { setLoading(true); setErrorMsg(null); try { const prof = await apiFetch('/api/v1/customer/profile'); setProfile(prof); setSettingsForm({ first_name: prof.first_name || '', last_name: prof.last_name || '', phone: prof.phone || '', }); const addrs = await apiFetch( '/api/v1/customer/profile/addresses' ); setAddresses(addrs); const ords = await apiFetch('/api/v1/orders'); setOrders(ords); } catch (err: any) { console.error(err); setErrorMsg(err.message || 'Failed to retrieve account records.'); } finally { setLoading(false); } }; const handleLogout = async () => { try { await apiFetch('/api/v1/customer/auth/logout', { method: 'POST', skipAuth: true, }); } catch (err) { console.error('Logout endpoint fail:', err); } finally { clearTokens(); clearAuth(); setProfile(null); setAddresses([]); setOrders([]); router.push('/'); } }; const handleProfileUpdate = async (e: React.FormEvent) => { e.preventDefault(); setErrorMsg(null); if (settingsForm.phone) { const phoneErr = validatePhone(settingsForm.phone); if (phoneErr) { setErrorMsg(phoneErr); return; } } setActionLoading(true); try { const updated = await apiFetch('/api/v1/customer/profile', { method: 'PUT', body: JSON.stringify(settingsForm), }); setProfile(updated); setAuth(useAuthStore.getState().accessToken || '', updated); alert('Profile updated successfully!'); } catch (err: any) { setErrorMsg(err.message || 'Failed to update profile.'); } finally { setActionLoading(false); } }; const handleAddressSubmit = async (e: React.FormEvent) => { e.preventDefault(); setErrorMsg(null); const phoneErr = validatePhone(addressForm.phone, true); if (phoneErr) { setErrorMsg(phoneErr); return; } const pincodeErr = validatePincode(addressForm.pincode, true); if (pincodeErr) { setErrorMsg(pincodeErr); return; } const addressErr = validateAddress(addressForm.street_address, true); if (addressErr) { setErrorMsg(addressErr); return; } setActionLoading(true); try { if (editingAddress?.address_id) { await apiFetch
( `/api/v1/customer/profile/addresses/${editingAddress.address_id}`, { method: 'PUT', body: JSON.stringify(addressForm), } ); } else { await apiFetch
('/api/v1/customer/profile/addresses', { method: 'POST', body: JSON.stringify(addressForm), }); } setAddressModalOpen(false); setEditingAddress(null); const addrs = await apiFetch( '/api/v1/customer/profile/addresses' ); setAddresses(addrs); } catch (err: any) { setErrorMsg(err.message || 'Failed to save address record.'); } finally { setActionLoading(false); } }; const handleEditAddressClick = (addr: Address) => { setEditingAddress(addr); setAddressForm({ address_type: addr.address_type, full_name: addr.full_name, phone: addr.phone, street_address: addr.street_address, city: addr.city, state: addr.state, pincode: addr.pincode, is_default: addr.is_default, }); setAddressModalOpen(true); }; const handleDeleteAddress = async (addressId: string) => { if (!confirm('Are you sure you want to delete this address?')) return; setActionLoading(true); try { await apiFetch(`/api/v1/customer/profile/addresses/${addressId}`, { method: 'DELETE', }); const addrs = await apiFetch( '/api/v1/customer/profile/addresses' ); setAddresses(addrs); } catch (err: any) { setErrorMsg(err.message || 'Failed to delete address.'); } finally { setActionLoading(false); } }; const handleCancelOrder = async (orderId: string) => { if (!confirm('Are you sure you want to cancel this order?')) return; setActionLoading(true); try { await apiFetch(`/api/v1/orders/${orderId}/cancel`, { method: 'POST' }); setOrders( orders.map((o) => o.order_id === orderId ? { ...o, status: 'CANCELLED' } : o ) ); alert('Order cancelled successfully.'); } catch (err: any) { setErrorMsg(err.message || 'Failed to cancel order.'); } finally { setActionLoading(false); } }; const openNewAddressModal = () => { setEditingAddress(null); setAddressForm({ address_type: 'SHIPPING', full_name: '', phone: '', street_address: '', city: '', state: '', pincode: '', is_default: false, }); setAddressModalOpen(true); }; const defaultAddress = addresses.find((a) => a.is_default); const activeTabMeta = TABS.find((t) => t.id === activeTab); const pageShell = (children: React.ReactNode) => (
{children}
); if (!authReady) { return (
); } if (!isAuthenticated) { return pageShell( <>
iFix Kart

Sign in to your account

View your profile, manage shipping addresses, and track order history in one place.

setAuthModalOpen(false)} onSuccess={() => { setAuthModalOpen(false); router.push('/'); }} /> ); } return pageShell( <>
{/* Profile strip */}
{profile?.first_name?.charAt(0).toUpperCase() || 'U'} {profile?.last_name?.charAt(0).toUpperCase() || ''}

{profile?.first_name} {profile?.last_name}

{profile?.email}

{errorMsg && (
{errorMsg}
)} {loading ? (
) : (
{/* Sidebar — matches category rail language */} {/* Content panel */}

{activeTabMeta?.label}

{activeTab === 'addresses' ? ( ) : null}
{/* Dashboard */} {activeTab === 'dashboard' && (

Hello, {profile?.first_name}! View recent activity and manage your account here.

Default Shipping Address {defaultAddress ? (

{defaultAddress.full_name} {defaultAddress.street_address}
{defaultAddress.city}, {defaultAddress.state} –{' '} {defaultAddress.pincode}
Phone: {defaultAddress.phone}

) : (

No default address saved in address book.

)}
Recent Order Activity {orders.length > 0 ? (

Order {orders[0].order_no}

Total:{' '} {formatCurrency(orders[0].final_amount)}

Status:{' '} {orders[0].status.replace('_', ' ')}

) : (

No purchase order history recorded.

)}
)} {/* Orders */} {activeTab === 'orders' && (
{orders.length === 0 ? (

No orders placed yet.

Continue Shopping
) : (
{orders.map((order) => (
{order.order_no} Placed:{' '} {new Date( order.created_at.endsWith('Z') || order.created_at.includes('+') ? order.created_at : order.created_at + 'Z' ).toLocaleDateString('en-IN', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', })}
{formatCurrency(order.final_amount)} {order.payment_status}
{order.status.replace('_', ' ')} {(order.status === 'ORDER_CREATED' || order.status === 'PAYMENT_PENDING') && ( )}
))}
)}
)} {/* Addresses */} {activeTab === 'addresses' && (
{addresses.length === 0 ? (

No addresses saved yet.

) : (
{addresses.map((addr) => (
{addr.address_type} {addr.is_default && ( Default )}

{addr.full_name}

{addr.street_address}
{addr.city}, {addr.state} – {addr.pincode}

Phone: {addr.phone}

))}
)}
)} {/* Settings */} {activeTab === 'settings' && (
setSettingsForm({ ...settingsForm, first_name: e.target.value, }) } required className={inputClass} />
setSettingsForm({ ...settingsForm, last_name: e.target.value, }) } required className={inputClass} />
Email is verified via Google login and cannot be changed.
setSettingsForm({ ...settingsForm, phone: val })} /> )}
)}
{addressModalOpen && (

{editingAddress ? 'Edit Address' : 'New Address'}

setAddressForm({ ...addressForm, full_name: e.target.value, }) } required placeholder="John Doe" className={inputClass} />
setAddressForm({ ...addressForm, phone: val })} />
setAddressForm({ ...addressForm, street_address: e.target.value, }) } required placeholder="123 Technology Park Drive" className={inputClass} />
setAddressForm({ ...addressForm, city: e.target.value }) } required placeholder="Chennai" className={inputClass} />
setAddressForm({ ...addressForm, state: e.target.value }) } required placeholder="Tamil Nadu" className={inputClass} />
setAddressForm({ ...addressForm, pincode: val })} />
)} ); } export default function AccountPage() { return ( } > ); }