>({
proformaNumber: "",
@@ -65,6 +67,7 @@ export default function ProformaPage() {
items: [] as InvoiceItem[],
});
+
const { data: proformaData, isLoading } = useQuery({
queryKey: ["admin", "proforma", page, search],
queryFn: () =>
@@ -75,6 +78,7 @@ export default function ProformaPage() {
}),
});
+
const createMutation = useMutation({
mutationFn: (data: any) => invoiceService.createProforma(data),
onSuccess: () => {
@@ -89,6 +93,7 @@ export default function ProformaPage() {
},
});
+
const updateMutation = useMutation({
mutationFn: ({ id, data }: { id: string; data: any }) =>
invoiceService.updateProforma(id, data),
@@ -104,6 +109,7 @@ export default function ProformaPage() {
},
});
+
const deleteMutation = useMutation({
mutationFn: (id: string) => invoiceService.deleteProforma(id),
onSuccess: () => {
@@ -116,6 +122,7 @@ export default function ProformaPage() {
},
});
+
const handleOpenCreate = () => {
setEditingProforma(null);
setFormData({
@@ -136,16 +143,26 @@ export default function ProformaPage() {
setIsModalOpen(true);
};
+
+ // ✅ FIXED: Convert string values to numbers for items
const handleOpenEdit = (item: Proforma) => {
setEditingProforma(item);
setFormData({
...item,
issueDate: new Date(item.issueDate).toISOString().split("T")[0],
dueDate: new Date(item.dueDate).toISOString().split("T")[0],
+ // Convert all item string values to numbers
+ items: item.items.map(i => ({
+ ...i,
+ quantity: Number(i.quantity),
+ unitPrice: Number(i.unitPrice),
+ total: Number(i.total),
+ })),
});
setIsModalOpen(true);
};
+
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (editingProforma) {
@@ -155,18 +172,21 @@ export default function ProformaPage() {
}
};
+
+ // ✅ FIXED: Convert item.total to Number when calculating subtotal
const calculateTotals = (
items: InvoiceItem[],
tax: number,
discount: number,
) => {
const subtotal = items.reduce(
- (acc: number, item: InvoiceItem) => acc + item.total,
+ (acc: number, item: InvoiceItem) => acc + Number(item.total),
0,
);
return subtotal + tax - discount;
};
+
const handleAddItem = () => {
const newItem: InvoiceItem = {
id: Math.random().toString(36).substring(7),
@@ -181,6 +201,7 @@ export default function ProformaPage() {
});
};
+
const handleUpdateItem = (
index: number,
field: keyof InvoiceItem,
@@ -189,11 +210,13 @@ export default function ProformaPage() {
const newItems = [...(formData.items || [])];
newItems[index] = { ...newItems[index], [field]: value } as InvoiceItem;
+
if (field === "quantity" || field === "unitPrice") {
newItems[index].total =
Number(newItems[index].quantity) * Number(newItems[index].unitPrice);
}
+
const newAmount = calculateTotals(
newItems,
formData.taxAmount || 0,
@@ -202,6 +225,7 @@ export default function ProformaPage() {
setFormData({ ...formData, items: newItems, amount: newAmount });
};
+
const handleRemoveItem = (index: number) => {
const newItems = (formData.items || []).filter(
(_, i: number) => i !== index,
@@ -214,6 +238,7 @@ export default function ProformaPage() {
setFormData({ ...formData, items: newItems, amount: newAmount });
};
+
const formatCurrency = (amount: number | any) => {
const val = typeof amount === "number" ? amount : 0;
return new Intl.NumberFormat("en-US", {
@@ -222,6 +247,7 @@ export default function ProformaPage() {
}).format(val);
};
+
return (
@@ -246,6 +272,7 @@ export default function ProformaPage() {
)}
+
@@ -323,7 +350,7 @@ export default function ProformaPage() {
- {formatCurrency(item.amount)}
+ {formatCurrency(Number(item.amount))}
|
{new Date(item.issueDate).toLocaleDateString()}
@@ -405,6 +432,7 @@ export default function ProformaPage() {
)}
+
{/* Create/Edit Modal */}
|
- {formatMoney(row.amount, row.currency)}
+ {formatMoney(Number(row.totalAmount), row.currency)}
|
- {row.provider}
+ { "Default"}
- {row.providerRef && (
-
- {row.providerRef}
-
- )}
|
@@ -227,13 +219,7 @@ export default function SubscriptionTransactionsPage() {
|
- {tab === "failed" && (
-
-
- {row.failureReason ?? "Unknown Error Logic"}
-
- |
- )}
+
{/* Pagination Controls */}
- {data && data.totalPages > 1 && (
+ {data?.meta && data.meta.totalPages > 1 && (
Showing{" "}
- {data.data.length} of{" "}
- {data.total} entries
+
+ {data.data.length}
+ {" "}
+ of{" "}
+
+ {data.meta.total}
+ {" "}
+ entries
+
+
- {data.page} / {data.totalPages}
+ {data.meta.page} / {data.meta.totalPages}
+
diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx
index 6839645..2979840 100644
--- a/src/pages/login/index.tsx
+++ b/src/pages/login/index.tsx
@@ -82,7 +82,7 @@ export default function LoginPage() {
setEmail(e.target.value)}
required
diff --git a/src/services/api/client.ts b/src/services/api/client.ts
index a47b7d5..7d4f146 100644
--- a/src/services/api/client.ts
+++ b/src/services/api/client.ts
@@ -62,25 +62,32 @@ apiClient.interceptors.response.use(
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
+ const refreshToken = localStorage.getItem("refresh_token");
+
+ if (!refreshToken) {
+ // No refresh token available — clear session and redirect
+ localStorage.removeItem("access_token");
+ localStorage.removeItem("refresh_token");
+ localStorage.removeItem("user");
+ window.location.href = "/login";
+ return Promise.reject(error);
+ }
+
try {
- // Try to refresh token
- const refreshToken = localStorage.getItem("refresh_token");
- if (refreshToken) {
- const response = await axios.post(
- `${API_BASE_URL}/auth/refresh`,
- { refreshToken },
- { withCredentials: true },
- );
+ const response = await axios.post(
+ `${API_BASE_URL}/auth/refresh`,
+ { refreshToken },
+ { withCredentials: true },
+ );
- const { accessToken } = response.data;
- localStorage.setItem("access_token", accessToken);
+ const { accessToken } = response.data;
+ localStorage.setItem("access_token", accessToken);
- // Retry original request with new token
- originalRequest.headers.Authorization = `Bearer ${accessToken}`;
- return apiClient(originalRequest);
- }
+ // Retry original request with new token
+ originalRequest.headers.Authorization = `Bearer ${accessToken}`;
+ return apiClient(originalRequest);
} catch (refreshError) {
- // Refresh failed - logout user
+ // Refresh failed — clear session and redirect
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
localStorage.removeItem("user");
diff --git a/src/services/faq.service.ts b/src/services/faq.service.ts
index 050047e..3ce5a72 100644
--- a/src/services/faq.service.ts
+++ b/src/services/faq.service.ts
@@ -29,7 +29,7 @@ class FaqService {
audience?: FaqAudience
search?: string
}): Promise {
- const response = await apiClient.get("/admin/faq", {
+ const response = await apiClient.get("/faq", {
params,
})
return response.data
@@ -42,7 +42,7 @@ class FaqService {
sortOrder?: number
isPublished?: boolean
}): Promise {
- const response = await apiClient.post("/admin/faq", data)
+ const response = await apiClient.post("/faq", data)
return response.data
}
@@ -55,12 +55,12 @@ class FaqService {
>
>,
): Promise {
- const response = await apiClient.patch(`/admin/faq/${id}`, data)
+ const response = await apiClient.patch(`/faq/${id}`, data)
return response.data
}
async remove(id: string): Promise {
- await apiClient.delete(`/admin/faq/${id}`)
+ await apiClient.delete(`/faq/${id}`)
}
}
diff --git a/src/services/issue.service.ts b/src/services/issue.service.ts
index 5e70a8d..3eb7db3 100644
--- a/src/services/issue.service.ts
+++ b/src/services/issue.service.ts
@@ -33,7 +33,7 @@ export interface PaginatedIssues {
class IssueService {
async list(filters: IssueFilters = {}): Promise {
- const response = await apiClient.get("/admin/issues", {
+ const response = await apiClient.get("/issues", {
params: filters,
})
return response.data
@@ -44,7 +44,7 @@ class IssueService {
description: string
priority?: SupportIssue["priority"]
}): Promise {
- const response = await apiClient.post("/admin/issues", data)
+ const response = await apiClient.post("/issues", data)
return response.data
}
@@ -53,7 +53,7 @@ class IssueService {
status: IssueStatus,
): Promise {
const response = await apiClient.patch(
- `/admin/issues/${id}`,
+ `/issues/${id}`,
{ status },
)
return response.data
diff --git a/src/services/notification.service.ts b/src/services/notification.service.ts
index 8a1252d..0086215 100644
--- a/src/services/notification.service.ts
+++ b/src/services/notification.service.ts
@@ -52,7 +52,10 @@ export interface SendBroadcastRequest {
audience: "all_end_users" | "system_users_only" | "everyone_with_access";
channels: ("push" | "sms" | "email")[];
}
-
+export interface NotificationListResponse {
+ data: Notification[];
+ total: number;
+}
class NotificationService {
/**
* Get all notifications for current user (Paginated)
@@ -64,20 +67,20 @@ class NotificationService {
status?: string;
search?: string;
}): Promise {
- const response = await apiClient.get("/notifications", {
+ const response = await apiClient.get("/notifications", {
params,
});
- return response.data;
+ return response.data.data;
}
/**
* Get unread notification count
*/
async getUnreadCount(): Promise {
- const response = await apiClient.get<{ count: number }>(
+ const response = await apiClient.get<{ unreadCount: number }>(
"/notifications/unread-count",
);
- return response.data.count;
+ return response.data.unreadCount;
}
/**
@@ -91,7 +94,7 @@ class NotificationService {
* Mark all notifications as read
*/
async markAllAsRead(): Promise {
- await apiClient.post("/notifications/read-all");
+ await apiClient.put("/notifications/mark-all-read");
}
/**
@@ -101,7 +104,7 @@ class NotificationService {
data: SendBroadcastRequest,
): Promise<{ success: boolean }> {
const response = await apiClient.post<{ success: boolean }>(
- "/admin/notifications/broadcast",
+ "/notifications/broadcast",
data,
);
return response.data;
diff --git a/src/services/subscription-transaction.service.ts b/src/services/subscription-transaction.service.ts
index ef9a382..75e7657 100644
--- a/src/services/subscription-transaction.service.ts
+++ b/src/services/subscription-transaction.service.ts
@@ -3,34 +3,64 @@ import apiClient from "./api/client"
export type SubscriptionPaymentStatus = "COMPLETED" | "FAILED" | "PENDING"
export interface SubscriptionTransaction {
- id: string
- userId: string
- userEmail: string
- planName: string
- amount: number
- currency: string
- status: SubscriptionPaymentStatus
- provider: string
- providerRef?: string
- failureReason?: string
- createdAt: string
+ id: string;
+ txRef: string;
+ checkoutUrl: string;
+
+ totalAmount: string;
+ currency: string;
+
+ status: SubscriptionPaymentStatus;
+ purpose: string;
+
+ chapaTransactionId: string | null;
+ chapaData: Record | null;
+
+ returnUrl: string;
+
+ userId: string;
+ invoiceId: string;
+
+ planId: string | null;
+ billingInterval: string | null;
+
+ completedAt: string | null;
+ createdAt: string;
+ updatedAt: string;
+
+ user: {
+ id: string;
+ email: string;
+ firstName: string | null;
+ lastName: string | null;
+ };
+
+ plan: {
+ id: string;
+ name: string;
+ displayName: string;
+ } | null;
}
export interface SubscriptionTransactionFilters {
- page?: number
- limit?: number
- status?: SubscriptionPaymentStatus
- search?: string
+ page?: number;
+ limit?: number;
+ status?: SubscriptionPaymentStatus;
+ search?: string;
}
export interface PaginatedSubscriptionTx {
- data: SubscriptionTransaction[]
- total: number
- page: number
- limit: number
- totalPages: number
-}
+ data: SubscriptionTransaction[];
+ meta: {
+ total: number;
+ page: number;
+ limit: number;
+ totalPages: number;
+ hasNextPage: boolean;
+ hasPreviousPage: boolean;
+ };
+}
class SubscriptionTransactionService {
async getTransactions(
filters: SubscriptionTransactionFilters = {},
|