diff --git a/app/dashboard/approve/page.tsx b/app/dashboard/approve/page.tsx
index 0c1f92a..07d1a0c 100644
--- a/app/dashboard/approve/page.tsx
+++ b/app/dashboard/approve/page.tsx
@@ -1,7 +1,8 @@
// This ensures this component is rendered only on the client side
"use client";
-import { Approve } from "@/app/components/pages/Approve/Approve";
+import { Approve } from "@/app/features/pages/Approve/Approve";
+
export default function ApprovePage() {
return (
diff --git a/app/dashboard/audits/page.tsx b/app/dashboard/audits/page.tsx
new file mode 100644
index 0000000..9bec2b5
--- /dev/null
+++ b/app/dashboard/audits/page.tsx
@@ -0,0 +1,22 @@
+import DataTable from "@/app/features/DataTable/DataTable";
+import { getAudits } from "@/app/services/audits";
+
+export default async function AuditPage({
+ searchParams,
+}: {
+ searchParams: Promise
>;
+}) {
+ // Await searchParams before processing
+ const params = await searchParams;
+ // Create a safe query string by filtering only string values
+ const safeParams: Record = {};
+ for (const [key, value] of Object.entries(params)) {
+ if (typeof value === "string") {
+ safeParams[key] = value;
+ }
+ }
+ const query = new URLSearchParams(safeParams).toString();
+ const data = await getAudits({ query });
+
+ return ;
+}
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
index df3f502..aad0b66 100644
--- a/app/dashboard/page.tsx
+++ b/app/dashboard/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { DashboardHomePage } from "../features/pages/dashboardhomepage/dashboardhomepage";
+import { DashboardHomePage } from "../features/pages/DashboardHomePage/DashboardHomePage";
const DashboardPage = () => {
return ;
diff --git a/app/dashboard/transactions/deposits/page.tsx b/app/dashboard/transactions/deposits/page.tsx
index d496729..aa589d0 100644
--- a/app/dashboard/transactions/deposits/page.tsx
+++ b/app/dashboard/transactions/deposits/page.tsx
@@ -1,5 +1,4 @@
-
-import TransactionsTable from "@/app/features/pages/transactions/transactionstable";
+import DataTable from "@/app/features/DataTable/DataTable";
import { getTransactions } from "@/app/services/transactions";
export default async function DepositTransactionPage({
@@ -17,8 +16,8 @@ export default async function DepositTransactionPage({
}
}
const query = new URLSearchParams(safeParams).toString();
- const transactionType = 'deposit';
+ const transactionType = "deposits";
const data = await getTransactions({ transactionType, query });
- return ;
+ return ;
}
diff --git a/app/dashboard/transactions/history/page.tsx b/app/dashboard/transactions/history/page.tsx
index bf93fd7..78e01bc 100644
--- a/app/dashboard/transactions/history/page.tsx
+++ b/app/dashboard/transactions/history/page.tsx
@@ -1,23 +1,3 @@
-import TransactionsTable from "@/app/features/pages/transactions/transactionstable";
-import { getTransactions } from "@/app/services/transactions";
-
-export default async function DepositTransactionPage({
- searchParams,
-}: {
- searchParams: Promise>;
-}) {
- // Await searchParams before processing
- const params = await searchParams;
- // Create a safe query string by filtering only string values
- const safeParams: Record = {};
- for (const [key, value] of Object.entries(params)) {
- if (typeof value === "string") {
- safeParams[key] = value;
- }
- }
- const query = new URLSearchParams(safeParams).toString();
- const transactionType = 'deposit';
- const data = await getTransactions({ transactionType, query });
-
- return ;
+export default async function HistoryTransactionPage() {
+ return History Transactions Page
;
}
diff --git a/app/dashboard/transactions/withdrawals/page.tsx b/app/dashboard/transactions/withdrawals/page.tsx
index e48babe..e61e9bd 100644
--- a/app/dashboard/transactions/withdrawals/page.tsx
+++ b/app/dashboard/transactions/withdrawals/page.tsx
@@ -1,4 +1,4 @@
-import TransactionsTable from "@/app/features/pages/transactions/transactionstable";
+import DataTable from "@/app/features/DataTable/DataTable";
import { getTransactions } from "@/app/services/transactions";
export default async function WithdrawalTransactionPage({
@@ -16,8 +16,8 @@ export default async function WithdrawalTransactionPage({
}
}
const query = new URLSearchParams(safeParams).toString();
-const transactionType = 'withdrawal';
-const data = await getTransactions({ transactionType, query });
+ const transactionType = "withdrawal";
+ const data = await getTransactions({ transactionType, query });
- return ;
+ return ;
}
diff --git a/app/features/AdvancedSearch/AdvancedSearch.tsx b/app/features/AdvancedSearch/AdvancedSearch.tsx
index 79db3d2..78948a7 100644
--- a/app/features/AdvancedSearch/AdvancedSearch.tsx
+++ b/app/features/AdvancedSearch/AdvancedSearch.tsx
@@ -17,16 +17,9 @@ import SearchIcon from "@mui/icons-material/Search";
import RefreshIcon from "@mui/icons-material/Refresh";
import { useSearchParams, useRouter } from "next/navigation";
import { useState, useEffect, useMemo } from "react";
+import { ISearchLabel } from "../pages/transactions/types";
-
-interface ILabel {
- label: string;
- field: string;
- type: string;
- options?: string[];
-}
-
-export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
+export default function AdvancedSearch({ labels }: { labels: ISearchLabel[] }) {
const searchParams = useSearchParams();
const router = useRouter();
const [open, setOpen] = useState(false);
@@ -37,7 +30,6 @@ export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
setFormValues(initialParams);
}, [searchParams]);
-
const updateURL = useMemo(
() =>
debounce((newValues: Record) => {
@@ -47,7 +39,7 @@ export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
});
router.push(`?${updatedParams.toString()}`);
}, 500),
- [router]
+ [router],
);
const handleFieldChange = (field: string, value: string) => {
@@ -61,19 +53,20 @@ export default function AdvancedSearch({ labels }: { labels: ILabel[] }) {
router.push("?");
};
- const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
- if (
- event.type === "keydown" &&
- ((event as React.KeyboardEvent).key === "Tab" ||
- (event as React.KeyboardEvent).key === "Shift")
- ) {
- return;
- }
- setOpen(open);
- };
+ const toggleDrawer =
+ (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
+ if (
+ event.type === "keydown" &&
+ ((event as React.KeyboardEvent).key === "Tab" ||
+ (event as React.KeyboardEvent).key === "Shift")
+ ) {
+ return;
+ }
+ setOpen(open);
+ };
return (
-
+
+
+
+ {
+ if (params.field !== "actions") {
+ handleClickField(params.field, params.value as string);
+ }
+ }}
+ />
+
+ {/* Export Dialog */}
+
+
+ );
+};
+
+export default DataTable;
diff --git a/app/features/DataTable/types.ts b/app/features/DataTable/types.ts
new file mode 100644
index 0000000..0327000
--- /dev/null
+++ b/app/features/DataTable/types.ts
@@ -0,0 +1,12 @@
+interface ISearchLabel {
+ label: string;
+ field: string;
+ type: string;
+ options?: string[];
+}
+
+export interface IDataTable {
+ tableRows: TRow[];
+ tableColumns: TColumn[];
+ tableSearchLabels: ISearchLabel[];
+}
\ No newline at end of file
diff --git a/app/features/Pages/DashboardHomePage/DashboardHomePage.tsx b/app/features/Pages/DashboardHomePage/DashboardHomePage.tsx
index d36e1ad..cea093d 100644
--- a/app/features/Pages/DashboardHomePage/DashboardHomePage.tsx
+++ b/app/features/Pages/DashboardHomePage/DashboardHomePage.tsx
@@ -11,6 +11,7 @@ import { TransactionsOverView } from "../../TransactionsOverview/TransactionsOve
export const DashboardHomePage = () => {
return (
<>
+ {/* Conditional rendering of the Generic Modal, passing LoginModal as children */}
diff --git a/app/features/TransactionsWaitingApproval/TransactionsWaitingApproval.tsx b/app/features/TransactionsWaitingApproval/TransactionsWaitingApproval.tsx
index 93d5fbb..34ddc01 100644
--- a/app/features/TransactionsWaitingApproval/TransactionsWaitingApproval.tsx
+++ b/app/features/TransactionsWaitingApproval/TransactionsWaitingApproval.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
import {
Box,
Button,
diff --git a/app/features/dashboard/header/Header.tsx b/app/features/dashboard/header/Header.tsx
index fa7643d..beccad7 100644
--- a/app/features/dashboard/header/Header.tsx
+++ b/app/features/dashboard/header/Header.tsx
@@ -6,6 +6,17 @@ import AccountMenu from "./accountMenu/AccountMenu";
import "./Header.scss";
const Header = () => {
+ // const [anchorEl, setAnchorEl] = useState(null);
+
+ // // Handle menu open
+ // const handleMenuClick = (event: React.MouseEvent) => {
+ // setAnchorEl(event.currentTarget);
+ // };
+
+ // // Handle menu close
+ // const handleMenuClose = () => {
+ // setAnchorEl(null);
+ // };
const handleChange = () => {};
diff --git a/app/features/dashboard/sidebar/SidebarLink.constants.ts b/app/features/dashboard/sidebar/SidebarLink.constants.ts
index f7bb406..c0420c5 100644
--- a/app/features/dashboard/sidebar/SidebarLink.constants.ts
+++ b/app/features/dashboard/sidebar/SidebarLink.constants.ts
@@ -14,6 +14,8 @@ import SettingsIcon from "@mui/icons-material/Settings";
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import HistoryIcon from '@mui/icons-material/History';
+import FactCheckIcon from "@mui/icons-material/FactCheck";
+
import { ISidebarLink } from "@/app/features/dashboard/sidebar/SidebarLink.interfaces";
@@ -75,6 +77,7 @@ export const PAGE_LINKS: ISidebarLink[] = [
],
},
{ title: "Account IQ", path: "/dashboard/account-iq", icon: InsightsIcon },
+ { title: "Audits", path: "/dashboard/audits", icon: FactCheckIcon },
// { title: 'Documentation', path: '/documentation', icon: DescriptionIcon },
// { title: 'Support', path: '/support', icon: SupportAgentIcon },
// { title: 'System Status', path: '/system-status', icon: WarningAmberIcon },
diff --git a/app/login/page.scss b/app/login/page.scss
new file mode 100644
index 0000000..2e32ae3
--- /dev/null
+++ b/app/login/page.scss
@@ -0,0 +1,64 @@
+// Variables for consistent styling
+$primary-color: #2563eb; // Blue-600 equivalent
+$primary-hover-color: #1d4ed8; // Blue-700 equivalent
+$success-color: #16a34a; // Green-600 equivalent
+$error-color: #dc2626; // Red-600 equivalent
+$text-color-dark: #1f2937; // Gray-800 equivalent
+$text-color-medium: #4b5563; // Gray-700 equivalent
+$text-color-light: #6b7280; // Gray-600 equivalent
+$border-color: #d1d5db; // Gray-300 equivalent
+$bg-color-light: #f3f4f6; // Gray-100 equivalent
+$bg-color-white: #ffffff;
+
+.page-container {
+ min-height: 100vh;
+ background-color: $bg-color-light;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-family: "Inter", sans-serif; // Assuming Inter font is used
+ padding: 1rem;
+}
+
+.page-container__content {
+ width: 100%;
+ max-width: 56rem; // max-w-4xl
+ background-color: $bg-color-white;
+ border-radius: 0.75rem; // rounded-xl
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
+ 0 4px 6px -2px rgba(0, 0, 0, 0.05); // shadow-lg
+ padding: 2rem; // p-8
+ text-align: center;
+}
+
+.page-container__title {
+ font-size: 2.25rem; // text-4xl
+ font-weight: 700; // font-bold
+ color: $text-color-dark;
+ margin-bottom: 1.5rem; // mb-6
+}
+
+.page-container__message--logged-in {
+ font-size: 1.25rem; // text-xl
+ color: $success-color;
+ margin-bottom: 1rem; // mb-4
+}
+
+.page-container__text {
+ color: $text-color-medium;
+ margin-bottom: 1.5rem; // mb-6
+}
+
+.page-container__button--logout {
+ padding: 0.75rem 1.5rem; // px-6 py-3
+ background-color: $error-color;
+ color: $bg-color-white;
+ font-weight: 600; // font-semibold
+ border-radius: 0.5rem; // rounded-lg
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); // shadow-md
+ transition: background-color 0.3s ease-in-out;
+ &:hover {
+ background-color: darken($error-color, 5%); // red-700 equivalent
+ }
+}
diff --git a/app/login/page.tsx b/app/login/page.tsx
new file mode 100644
index 0000000..c185f15
--- /dev/null
+++ b/app/login/page.tsx
@@ -0,0 +1,103 @@
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+import LoginModal from "../features/Auth/LoginModal"; // Your LoginModal component
+
+import "./page.scss"; // Global styles for LoginModal and page
+import Modal from "../components/Modal/Modal";
+
+export default function LoginPage() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const redirectPath = searchParams.get("redirect") || "/dashboard";
+
+ const [authMessage, setAuthMessage] = useState("");
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+
+ useEffect(() => {
+ // Check if already logged in by trying to fetch a protected resource or checking a client-accessible flag
+ // For HTTP-only cookies, you can't directly read the token here.
+ // Instead, you'd rely on a server-side check (e.g., in middleware or a Server Component)
+ // or a simple client-side flag if your backend also sets one (less secure for token itself).
+ // For this example, we'll assume if they land here, they need to log in.
+ // A more robust check might involve a quick API call to /api/auth/status
+ // if the token is in an HTTP-only cookie.
+ const checkAuthStatus = async () => {
+ // In a real app, this might be a call to a /api/auth/status endpoint
+ // that checks the HTTP-only cookie on the server and returns a boolean.
+ // For now, we'll rely on the middleware to redirect if unauthenticated.
+ // If the user somehow lands on /login with a valid cookie, the middleware
+ // should have redirected them already.
+ };
+ checkAuthStatus();
+ }, []);
+
+ const handleLogin = async (email: string, password: string) => {
+ setAuthMessage("Attempting login...");
+ try {
+ const response = await fetch("/api/auth/login", {
+ // <--- CALLING YOUR INTERNAL ROUTE HANDLER
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ email, password }),
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ // Check if the response status is 2xx
+ // Backend has successfully set the HTTP-only cookie
+ setAuthMessage("Login successful!");
+ setIsLoggedIn(true);
+ // Redirect to the intended path after successful login
+ router.replace(redirectPath);
+ } else {
+ // Handle login errors (e.g., invalid credentials)
+ setAuthMessage(data.message || "Login failed. Please try again.");
+ setIsLoggedIn(false);
+ }
+ } catch (error) {
+ console.error("Login failed:", error);
+ setAuthMessage("An error occurred during login. Please try again later.");
+ setIsLoggedIn(false);
+ }
+ return isLoggedIn; // Return the current login status
+ };
+
+ const clearAuthMessage = () => setAuthMessage("");
+
+ // If user is already logged in (e.g., redirected by middleware to dashboard),
+ // this page shouldn't be visible. The middleware should handle the primary redirect.
+ // This `isLoggedIn` state here is more for internal page logic if the user somehow
+ // bypasses middleware or lands on /login with a valid session.
+ // For a robust setup, the middleware is key.
+
+ return (
+
+
+
Payment Backoffice
+
+ Please log in to access the backoffice.
+
+
+
+ {/* Always show the modal on the login page */}
+
{
+ /* No direct close for login modal, user must log in */
+ }}
+ title="Login to Backoffice"
+ >
+
+
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
index a739b02..bb91a4b 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { DashboardHomePage } from "./features/pages/dashboardhomepage/dashboardhomepage";
+import { DashboardHomePage } from "./features/Pages/DashboardHomePage/DashboardHomePage";
const DashboardPage = () => {
return ;
diff --git a/app/redux/store.ts b/app/redux/store.ts
index 59a6f77..9609be3 100644
--- a/app/redux/store.ts
+++ b/app/redux/store.ts
@@ -1,11 +1,8 @@
-import { configureStore } from '@reduxjs/toolkit';
-import advancedSearchReducer from './advanedSearch/advancedSearchSlice';
-import transactionsReducer from './transactions/transactionsSlice';
+import { configureStore } from "@reduxjs/toolkit";
+import advancedSearchReducer from "./advanedSearch/advancedSearchSlice";
export const store = configureStore({
reducer: {
advancedSearch: advancedSearchReducer,
- transactions: transactionsReducer,
},
});
-
diff --git a/app/services/audits.ts b/app/services/audits.ts
new file mode 100644
index 0000000..ac5e700
--- /dev/null
+++ b/app/services/audits.ts
@@ -0,0 +1,22 @@
+export async function getAudits({
+ query,
+}: {
+ query: string;
+}) {
+ const res = await fetch(
+ `http://localhost:3000/api/dashboard/audits?${query}`,
+ {
+ cache: "no-store",
+ },
+ );
+
+ if (!res.ok) {
+ // Handle error from the API
+ const errorData = await res
+ .json()
+ .catch(() => ({ message: "Unknown error" }));
+ throw new Error(errorData.message || `HTTP error! status: ${res.status}`);
+ }
+
+ return res.json();
+}
diff --git a/app/services/transactions.ts b/app/services/transactions.ts
index 08d73a4..0d7f83e 100644
--- a/app/services/transactions.ts
+++ b/app/services/transactions.ts
@@ -1,11 +1,23 @@
-export async function getTransactions({ transactionType, query }: { transactionType: string, query: string }) {
- const res = await fetch(`http://localhost:3000/api/transactions/${transactionType}?${query}`, {
- cache: "no-store",
- });
+export async function getTransactions({
+ transactionType,
+ query,
+}: {
+ transactionType: string;
+ query: string;
+}) {
+
+ const res = await fetch(
+ `http://localhost:3000/api/dashboard/transactions/${transactionType}?${query}`,
+ {
+ cache: "no-store",
+ },
+ );
if (!res.ok) {
// Handle error from the API
- const errorData = await res.json().catch(() => ({ message: 'Unknown error' }));
+ const errorData = await res
+ .json()
+ .catch(() => ({ message: "Unknown error" }));
throw new Error(errorData.message || `HTTP error! status: ${res.status}`);
}
diff --git a/app/utils/exportData.ts b/app/utils/exportData.ts
index 1f2b3f0..0bf1654 100644
--- a/app/utils/exportData.ts
+++ b/app/utils/exportData.ts
@@ -1,39 +1,34 @@
-/* eslint-disable @typescript-eslint/ban-ts-comment */
-// @ts-nocheck
import * as XLSX from "xlsx";
-import { GridColDef } from "@mui/x-data-grid";
export type FileType = "csv" | "xls" | "xlsx";
import { saveAs } from "file-saver";
+import { GridColDef } from "@mui/x-data-grid";
-import type { ITransaction } from "../features/pages/transactions/types";
-
-
-export const exportData = (
- transactions: ITransaction[],
- columns: GridColDef[],
+export const exportData = (
+ rows: TRow[],
+ columns: TColumn[],
fileType: FileType = "csv",
onlyCurrentTable = false,
- setOpen: (open: boolean) => void
+ setOpen: (open: boolean) => void,
) => {
- const exportRows = onlyCurrentTable ? transactions.slice(0, 5) : transactions;
- const exportData = [
- columns.map((col) => col.headerName),
- ...exportRows.map((row) => columns.map((col) => row[col.field] ?? "")),
- ];
+ const exportRows = onlyCurrentTable ? rows.slice(0, 5) : rows;
+ const exportData = [
+ columns.map((col) => col.headerName),
+ ...exportRows.map((row) => columns.map((col) => (row as Record)[col.field] ?? "")),
+ ];
- const worksheet = XLSX.utils.aoa_to_sheet(exportData);
- const workbook = XLSX.utils.book_new();
- XLSX.utils.book_append_sheet(workbook, worksheet, "Transactions");
+ const worksheet = XLSX.utils.aoa_to_sheet(exportData);
+ const workbook = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(workbook, worksheet, "Transactions");
- if (fileType === "csv") {
- const csv = XLSX.utils.sheet_to_csv(worksheet);
- const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
- saveAs(blob, "transactions.csv");
- } else {
- XLSX.writeFile(workbook, `transactions.${fileType}`, {
- bookType: fileType,
- });
- }
+ if (fileType === "csv") {
+ const csv = XLSX.utils.sheet_to_csv(worksheet);
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
+ saveAs(blob, "transactions.csv");
+ } else {
+ XLSX.writeFile(workbook, `transactions.${fileType}`, {
+ bookType: fileType,
+ });
+ }
- setOpen(false);
- };
+ setOpen(false);
+};
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 0000000..99ed90d
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,30 @@
+// middleware.ts
+import { NextResponse } from "next/server";
+import type { NextRequest } from "next/server";
+
+export function middleware(request: NextRequest) {
+ const token = request.cookies.get("auth_token")?.value; // Get token from cookie
+
+ // Define protected paths
+ const protectedPaths = ["/dashboard", "/settings", "/admin"];
+ const isProtected = protectedPaths.some((path) =>
+ request.nextUrl.pathname.startsWith(path)
+ );
+
+ // If accessing a protected path and no token
+ if (isProtected && !token) {
+ // Redirect to login page
+ const loginUrl = new URL("/login", request.url);
+ // Optional: Add a redirect query param to return to original page after login
+ loginUrl.searchParams.set("redirect", request.nextUrl.pathname);
+ return NextResponse.redirect(loginUrl);
+ }
+
+ // Allow the request to proceed if not protected or token exists
+ return NextResponse.next();
+}
+
+// Configure matcher to run middleware on specific paths
+export const config = {
+ matcher: ["/dashboard/:path*", "/settings/:path*", "/admin/:path*"], // Apply to dashboard and its sub-paths
+};