diff --git a/actions/payment.ts b/actions/payment.ts
index 33143e5..1f07ba0 100644
--- a/actions/payment.ts
+++ b/actions/payment.ts
@@ -24,8 +24,7 @@ export async function createPayment(data: NewPayment) {
const session = await getServerSession(authOptions);
console.log("data", data);
const response = await fetch(
- `${
- process.env.SARLINK_API_BASE_URL // });
+ `${process.env.SARLINK_API_BASE_URL // });
}/api/billing/payment/`,
{
method: "POST",
diff --git a/app/(dashboard)/wallet/page.tsx b/app/(dashboard)/wallet/page.tsx
index 1fb3473..2f4c9ee 100644
--- a/app/(dashboard)/wallet/page.tsx
+++ b/app/(dashboard)/wallet/page.tsx
@@ -1,11 +1,63 @@
-import React from "react";
+import { Suspense } from "react";
+import DynamicFilter from "@/components/generic-filter";
+import { WalletTransactionsTable } from "@/components/wallet-transactions-table";
+
+export default async function Wallet({
+ searchParams,
+}: {
+ searchParams: Promise<{
+ query: string;
+ page: number;
+ sortBy: string;
+ status: string;
+ }>;
+}) {
+ const query = (await searchParams)?.query || "";
-export default function UserWallet() {
return (
);
}
diff --git a/app/globals.css b/app/globals.css
index 49fec38..ea1be82 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -76,6 +76,14 @@
}
}
+.credit-bg {
+ background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%2340b02f' fill-opacity='0.29' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E");
+}
+
+.debit-bg {
+ background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23d35c5c' fill-opacity='0.2' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E");
+}
+
.error-bg {
background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23e06f10' fill-opacity='0.35' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E");
}
diff --git a/components/ui/app-sidebar.tsx b/components/ui/app-sidebar.tsx
index 09acee5..1a62557 100644
--- a/components/ui/app-sidebar.tsx
+++ b/components/ui/app-sidebar.tsx
@@ -40,17 +40,17 @@ type Categories = {
id: string;
children: (
| {
- title: string;
- link: string;
- perm_identifier: string;
- icon: React.JSX.Element;
- }
+ title: string;
+ link: string;
+ perm_identifier: string;
+ icon: React.JSX.Element;
+ }
| {
- title: string;
- link: string;
- icon: React.JSX.Element;
- perm_identifier?: undefined;
- }
+ title: string;
+ link: string;
+ icon: React.JSX.Element;
+ perm_identifier?: undefined;
+ }
)[];
}[];
@@ -96,7 +96,7 @@ export async function AppSidebar({
title: "Wallet",
link: "/wallet",
icon: ,
- perm_identifier: "wallet",
+ perm_identifier: "wallet transaction",
},
],
},
diff --git a/components/wallet-transactions-table.tsx b/components/wallet-transactions-table.tsx
new file mode 100644
index 0000000..4cf6314
--- /dev/null
+++ b/components/wallet-transactions-table.tsx
@@ -0,0 +1,225 @@
+import { Calendar } from "lucide-react";
+import Link from "next/link";
+import { redirect } from "next/navigation";
+import {
+ Table,
+ TableBody,
+ TableCaption,
+ TableCell,
+ TableFooter,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { WalletTransaction } from "@/lib/backend-types";
+import { cn } from "@/lib/utils";
+import { getWaleltTransactions } from "@/queries/wallet";
+import { tryCatch } from "@/utils/tryCatch";
+import Pagination from "./pagination";
+import { Badge } from "./ui/badge";
+import { Button } from "./ui/button";
+
+export async function WalletTransactionsTable({
+ searchParams,
+}: {
+ searchParams: Promise<{
+ [key: string]: unknown;
+ }>;
+}) {
+ const resolvedParams = await searchParams;
+ const page = Number.parseInt(resolvedParams.page as string) || 1;
+ const limit = 10;
+ const offset = (page - 1) * limit;
+ // Build params object
+ const apiParams: Record = {};
+ for (const [key, value] of Object.entries(resolvedParams)) {
+ if (value !== undefined && value !== "") {
+ apiParams[key] = typeof value === "number" ? value : String(value);
+ }
+ }
+ apiParams.limit = limit;
+ apiParams.offset = offset;
+ const [error, transactions] = await tryCatch(
+ getWaleltTransactions(apiParams),
+ );
+
+ if (error) {
+ if (error.message.includes("Unauthorized")) {
+ redirect("/auth/signin");
+ } else {
+ return {JSON.stringify(error, null, 2)}
;
+ }
+ }
+ const { data, meta } = transactions;
+ return (
+
+ {data?.length === 0 ? (
+
+
No transactions yet.
+
+ ) : (
+ <>
+
+
+ Table of all transactions.
+
+
+ Description
+ Amount
+ Transaction Type
+ View Details
+ Created at
+
+
+
+ {transactions?.data?.map((trx) => (
+
+
+
+ {trx.description}
+
+
+ {trx.amount.toFixed(2)} MVR
+
+
+
+ {trx.transaction_type === "CREDIT" ? (
+
+ {trx.transaction_type}
+
+ ) : (
+
+ {trx.transaction_type}
+
+ )}
+
+
+
+
+
+ {new Date(trx.created_at).toLocaleDateString("en-US", {
+ month: "short",
+ day: "2-digit",
+ year: "numeric",
+ minute: "2-digit",
+ hour: "2-digit",
+ })}
+
+
+
+
+
+
+ ))}
+
+
+
+
+ {meta?.total === 1 ? (
+
+ Total {meta?.total} transaction.
+
+ ) : (
+
+ Total {meta?.total} transactions.
+
+ )}
+
+
+
+
+
+
+ {data.map((trx) => (
+
+ ))}
+
+
+ >
+ )}
+
+ );
+}
+
+function MobileTransactionDetails({ trx }: { trx: WalletTransaction }) {
+ return (
+
+
+
+
+
+ {new Date(trx.created_at).toLocaleDateString("en-US", {
+ month: "short",
+ day: "2-digit",
+ year: "numeric",
+ minute: "2-digit",
+ hour: "2-digit",
+ })}
+
+
+
{trx.description}
+
+
+
+
+
Amount
+
+ {trx.amount.toFixed(2)} MVR
+
+
+
+ {trx.transaction_type === "CREDIT" ? (
+
+ {trx.transaction_type}
+
+ ) : (
+
+ {trx.transaction_type}
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/lib/backend-types.ts b/lib/backend-types.ts
index e40b441..3f74075 100644
--- a/lib/backend-types.ts
+++ b/lib/backend-types.ts
@@ -97,3 +97,16 @@ export interface NewPayment {
number_of_months: number;
amount: number;
}
+
+
+export interface WalletTransaction {
+ id: string;
+ user: Pick & {
+ name: string;
+ };
+ amount: number;
+ transaction_type: "DEBIT" | "CREDIT";
+ description: string;
+ reference_id: string;
+ created_at: string;
+}
\ No newline at end of file
diff --git a/queries/wallet.ts b/queries/wallet.ts
new file mode 100644
index 0000000..b224dd7
--- /dev/null
+++ b/queries/wallet.ts
@@ -0,0 +1,44 @@
+import { getServerSession } from "next-auth";
+import { authOptions } from "@/app/auth";
+import type { ApiError, ApiResponse, WalletTransaction } from "@/lib/backend-types";
+
+type GenericGetResponseProps = {
+ offset?: number;
+ limit?: number;
+ page?: number;
+ [key: string]: string | number | undefined;
+};
+export async function getWaleltTransactions(
+ params: GenericGetResponseProps,
+ allTransactions = false,
+) {
+ // Build query string from all defined params
+ const query = Object.entries(params)
+ .filter(([_, value]) => value !== undefined && value !== "")
+ .map(
+ ([key, value]) =>
+ `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`,
+ )
+ .join("&");
+ const session = await getServerSession(authOptions);
+ const response = await fetch(
+ `${process.env.SARLINK_API_BASE_URL}/api/billing/wallet-transactions/?${query}&all_transactions=${allTransactions}`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Token ${session?.apiToken}`,
+ },
+ },
+ );
+ if (!response.ok) {
+ const errorData = (await response.json()) as ApiError;
+ const errorMessage =
+ errorData.message || errorData.detail || "An error occurred.";
+ const error = new Error(errorMessage);
+ (error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object
+ throw error;
+ }
+ const data = (await response.json()) as ApiResponse;
+ return data;
+}
\ No newline at end of file