mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-04-19 20:56:52 +00:00
refactor: update payment types and user interface, enhance error handling, and adjust API base URL
Some checks failed
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 3m14s
Some checks failed
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 3m14s
This commit is contained in:
parent
aa18484475
commit
9e2a2f430e
@ -1,29 +1,146 @@
|
||||
"use server";
|
||||
|
||||
import type { PaymentType } from "@/lib/types";
|
||||
import { formatMacAddress } from "@/lib/utils";
|
||||
import { authOptions } from "@/app/auth";
|
||||
import type { ApiResponse, NewPayment, Payment } from "@/lib/backend-types";
|
||||
import type { User } from "@/lib/types/user";
|
||||
import { tryCatch } from "@/utils/tryCatch";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { addDevicesToGroup } from "./omada-actions";
|
||||
|
||||
export async function createPayment(data: PaymentType) {
|
||||
export async function createPayment(data: NewPayment) {
|
||||
const session = await getServerSession(authOptions);
|
||||
console.log("data", data);
|
||||
// const payment = await prisma.payment.create({
|
||||
// data: {
|
||||
// amount: data.amount,
|
||||
// numberOfMonths: data.numberOfMonths,
|
||||
// paid: data.paid,
|
||||
// userId: data.userId,
|
||||
// devices: {
|
||||
// connect: data.deviceIds.map((id) => {
|
||||
// return {
|
||||
// id,
|
||||
// };
|
||||
// }),
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// redirect(`/payments/${payment.id}`);
|
||||
const response = await fetch(
|
||||
`${
|
||||
process.env.SARLINK_API_BASE_URL // });
|
||||
}/api/billing/payment/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${session?.apiToken}`,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
// Throw an error with the message from the API
|
||||
throw new Error(errorData.message || "Something went wrong.");
|
||||
}
|
||||
const payment = (await response.json()) as Payment;
|
||||
revalidatePath("/devices");
|
||||
redirect(`/payments/${payment.id}`);
|
||||
}
|
||||
|
||||
export async function getPayment({ id }: { id: string }) {
|
||||
const session = await getServerSession(authOptions);
|
||||
const response = await fetch(
|
||||
`${process.env.SARLINK_API_BASE_URL}/api/billing/payment/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${session?.apiToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
// Throw an error with the message from the API
|
||||
throw new Error(errorData.message || "Something went wrong.");
|
||||
}
|
||||
const payment = (await response.json()) as Payment;
|
||||
return payment;
|
||||
}
|
||||
|
||||
export async function getPayments() {
|
||||
const session = await getServerSession(authOptions);
|
||||
const respose = await fetch(
|
||||
`${process.env.SARLINK_API_BASE_URL}/api/billing/payment/`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${session?.apiToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const data = (await respose.json()) as ApiResponse<Payment>;
|
||||
return data;
|
||||
}
|
||||
|
||||
type UpdatePayment = Pick<
|
||||
Payment,
|
||||
"id" | "paid" | "paid_at" | "method" | "number_of_months"
|
||||
>;
|
||||
export async function updatePayment({
|
||||
id,
|
||||
method,
|
||||
paid,
|
||||
paid_at,
|
||||
number_of_months,
|
||||
}: UpdatePayment) {
|
||||
const session = await getServerSession(authOptions);
|
||||
const response = await fetch(
|
||||
`${process.env.SARLINK_API_BASE_URL}/api/billing/payment/${id}/update/`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${session?.apiToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
method,
|
||||
paid,
|
||||
paid_at,
|
||||
number_of_months,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
// Throw an error with the message from the API
|
||||
throw new Error(errorData.message || "Something went wrong.");
|
||||
}
|
||||
const payment = (await response.json()) as Payment;
|
||||
return payment;
|
||||
}
|
||||
|
||||
type TUpdateWalletBalance = Pick<User, "id" | "wallet_balance">;
|
||||
export async function updateWalletBalance({
|
||||
id,
|
||||
wallet_balance,
|
||||
}: TUpdateWalletBalance) {
|
||||
const session = await getServerSession(authOptions);
|
||||
console.log("wallet bal in server action", wallet_balance);
|
||||
const response = await fetch(
|
||||
`${process.env.SARLINK_API_BASE_URL}/api/auth/update-wallet/${id}/`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Token ${session?.apiToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
wallet_balance: Number.parseFloat(wallet_balance?.toFixed(2) ?? "0"),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
// Throw an error with the message from the API
|
||||
throw new Error(errorData.message || "Something went wrong.");
|
||||
}
|
||||
const message = (await response.json()) as {
|
||||
message: "Wallet balance updated successfully.";
|
||||
};
|
||||
return message;
|
||||
}
|
||||
|
||||
type VerifyPaymentType = {
|
||||
@ -43,45 +160,45 @@ class InsufficientFundsError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
async function processWalletPayment(
|
||||
user: { id: string; walletBalance: number } | null,
|
||||
payment: PaymentWithDevices | null,
|
||||
amount: number,
|
||||
) {
|
||||
if (!user || !payment) {
|
||||
export async function processWalletPayment({
|
||||
payment,
|
||||
amount,
|
||||
}: { payment: Payment | undefined; amount: number }) {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user || !payment) {
|
||||
throw new Error("User or payment not found");
|
||||
}
|
||||
|
||||
const walletBalance = user.walletBalance ?? 0;
|
||||
const walletBalance = session.user.wallet_balance ?? 0;
|
||||
if (walletBalance < amount) {
|
||||
throw new InsufficientFundsError();
|
||||
}
|
||||
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setMonth(expiryDate.getMonth() + payment.numberOfMonths);
|
||||
await prisma.$transaction([
|
||||
prisma.payment.update({
|
||||
where: { id: payment.id },
|
||||
data: {
|
||||
paid: true,
|
||||
paidAt: new Date(),
|
||||
method: "WALLET",
|
||||
devices: {
|
||||
updateMany: payment.devices.map((device) => ({
|
||||
where: { id: device.id },
|
||||
data: {
|
||||
isActive: true,
|
||||
expiryDate: expiryDate,
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
const [updatePaymentError, _] = await tryCatch(
|
||||
updatePayment({
|
||||
id: payment.id,
|
||||
method: "WALLET",
|
||||
paid: true,
|
||||
paid_at: new Date().toISOString(),
|
||||
number_of_months: payment.number_of_months,
|
||||
}),
|
||||
prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { walletBalance: walletBalance - amount },
|
||||
);
|
||||
if (updatePaymentError) {
|
||||
throw new Error(updatePaymentError.message);
|
||||
}
|
||||
console.log("Wallet balance before update:", walletBalance);
|
||||
const updated_balance = walletBalance - amount;
|
||||
const [walletUpdateError, response] = await tryCatch(
|
||||
updateWalletBalance({
|
||||
id: session.user.id,
|
||||
wallet_balance: Number.parseFloat(updated_balance?.toFixed(2) ?? "0"),
|
||||
}),
|
||||
]);
|
||||
);
|
||||
if (walletUpdateError) {
|
||||
throw new Error(walletUpdateError.message);
|
||||
}
|
||||
revalidatePath("/payments/[paymentsId]", "page");
|
||||
return response;
|
||||
}
|
||||
|
||||
type VerifyPaymentResponse =
|
||||
@ -99,98 +216,98 @@ type VerifyPaymentResponse =
|
||||
};
|
||||
};
|
||||
|
||||
async function verifyExternalPayment(
|
||||
data: VerifyPaymentType,
|
||||
payment: PaymentWithDevices | null,
|
||||
): Promise<VerifyPaymentResponse> {
|
||||
console.log("payment verify data ->", data);
|
||||
const response = await fetch(
|
||||
"https://verifypaymentsapi.baraveli.dev/verify-payment",
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
},
|
||||
);
|
||||
// async function verifyExternalPayment(
|
||||
// data: VerifyPaymentType,
|
||||
// payment: PaymentWithDevices | null,
|
||||
// ): Promise<VerifyPaymentResponse> {
|
||||
// console.log("payment verify data ->", data);
|
||||
// const response = await fetch(
|
||||
// "https://verifypaymentsapi.baraveli.dev/verify-payment",
|
||||
// {
|
||||
// method: "POST",
|
||||
// headers: { "Content-Type": "application/json" },
|
||||
// body: JSON.stringify(data),
|
||||
// },
|
||||
// );
|
||||
|
||||
const json = await response.json();
|
||||
console.log(json);
|
||||
if (!payment) {
|
||||
throw new Error("Payment verification failed or payment not found");
|
||||
}
|
||||
// const json = await response.json();
|
||||
// console.log(json);
|
||||
// if (!payment) {
|
||||
// throw new Error("Payment verification failed or payment not found");
|
||||
// }
|
||||
|
||||
if (json.success) {
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setMonth(expiryDate.getMonth() + payment.numberOfMonths);
|
||||
await prisma.payment.update({
|
||||
where: { id: payment.id },
|
||||
data: {
|
||||
paid: true,
|
||||
paidAt: new Date(),
|
||||
method: "TRANSFER",
|
||||
devices: {
|
||||
updateMany: payment.devices.map((device) => ({
|
||||
where: { id: device.id },
|
||||
data: {
|
||||
isActive: true,
|
||||
expiryDate: expiryDate,
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
// if (json.success) {
|
||||
// const expiryDate = new Date();
|
||||
// expiryDate.setMonth(expiryDate.getMonth() + payment.numberOfMonths);
|
||||
// await prisma.payment.update({
|
||||
// where: { id: payment.id },
|
||||
// data: {
|
||||
// paid: true,
|
||||
// paidAt: new Date(),
|
||||
// method: "TRANSFER",
|
||||
// devices: {
|
||||
// updateMany: payment.devices.map((device) => ({
|
||||
// where: { id: device.id },
|
||||
// data: {
|
||||
// isActive: true,
|
||||
// expiryDate: expiryDate,
|
||||
// },
|
||||
// })),
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
return json;
|
||||
}
|
||||
// return json;
|
||||
// }
|
||||
|
||||
async function updateDevices(payment: PaymentWithDevices | null) {
|
||||
if (!payment) return;
|
||||
// async function updateDevices(payment: PaymentWithDevices | null) {
|
||||
// if (!payment) return;
|
||||
|
||||
const newDevices = payment.devices.map((d) => ({
|
||||
name: d.name,
|
||||
macAddress: formatMacAddress(d.mac),
|
||||
}));
|
||||
// const newDevices = payment.devices.map((d) => ({
|
||||
// name: d.name,
|
||||
// macAddress: formatMacAddress(d.mac),
|
||||
// }));
|
||||
|
||||
return await addDevicesToGroup({
|
||||
groupId: process.env.OMADA_GROUP_ID,
|
||||
siteId: process.env.OMADA_SITE_ID,
|
||||
newDevices,
|
||||
});
|
||||
}
|
||||
// return await addDevicesToGroup({
|
||||
// groupId: process.env.OMADA_GROUP_ID,
|
||||
// siteId: process.env.OMADA_SITE_ID,
|
||||
// newDevices,
|
||||
// });
|
||||
// }
|
||||
|
||||
export async function verifyPayment(data: VerifyPaymentType) {
|
||||
try {
|
||||
const [payment, user] = await Promise.all([
|
||||
prisma.payment.findUnique({
|
||||
where: { id: data.paymentId },
|
||||
include: { devices: true },
|
||||
}),
|
||||
prisma.user.findUnique({
|
||||
where: { id: data.userId },
|
||||
}),
|
||||
]);
|
||||
// export async function verifyPayment(data: VerifyPaymentType) {
|
||||
// try {
|
||||
// const [payment, user] = await Promise.all([
|
||||
// prisma.payment.findUnique({
|
||||
// where: { id: data.paymentId },
|
||||
// include: { devices: true },
|
||||
// }),
|
||||
// prisma.user.findUnique({
|
||||
// where: { id: data.userId },
|
||||
// }),
|
||||
// ]);
|
||||
|
||||
if (data.type === "WALLET") {
|
||||
console.log("WALLET");
|
||||
await processWalletPayment(user, payment, Number(data.absAmount));
|
||||
redirect("/payments");
|
||||
}
|
||||
if (data.type === "TRANSFER") {
|
||||
console.log({ data, payment });
|
||||
const verificationResult = await verifyExternalPayment(data, payment);
|
||||
await updateDevices(payment);
|
||||
// if (data.type === "WALLET") {
|
||||
// console.log("WALLET");
|
||||
// await processWalletPayment(user, payment, Number(data.absAmount));
|
||||
// redirect("/payments");
|
||||
// }
|
||||
// if (data.type === "TRANSFER") {
|
||||
// console.log({ data, payment });
|
||||
// const verificationResult = await verifyExternalPayment(data, payment);
|
||||
// await updateDevices(payment);
|
||||
|
||||
revalidatePath("/payment[paymentId]");
|
||||
// revalidatePath("/payment[paymentId]");
|
||||
|
||||
return verificationResult;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Payment verification failed:", error);
|
||||
throw error; // Re-throw to handle at a higher level
|
||||
}
|
||||
}
|
||||
// return verificationResult;
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error("Payment verification failed:", error);
|
||||
// throw error; // Re-throw to handle at a higher level
|
||||
// }
|
||||
// }
|
||||
|
||||
export async function addDevicesToOmada() {
|
||||
console.log("hi");
|
||||
}
|
||||
// export async function addDevicesToOmada() {
|
||||
// console.log("hi");
|
||||
// }
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { getPayment } from "@/actions/payment";
|
||||
import { authOptions } from "@/app/auth";
|
||||
import DevicesToPay from "@/components/devices-to-pay";
|
||||
import { auth } from "@/app/auth";
|
||||
import prisma from "@/lib/db";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { tryCatch } from "@/utils/tryCatch";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { headers } from "next/headers";
|
||||
import React from "react";
|
||||
export default async function PaymentPage({
|
||||
@ -9,23 +11,13 @@ export default async function PaymentPage({
|
||||
}: {
|
||||
params: Promise<{ paymentId: string }>;
|
||||
}) {
|
||||
const session = await auth.api.getSession({
|
||||
headers: await headers(),
|
||||
});
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: session?.session.userId,
|
||||
},
|
||||
});
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
const paymentId = (await params).paymentId;
|
||||
const payment = await prisma.payment.findUnique({
|
||||
where: {
|
||||
id: paymentId,
|
||||
},
|
||||
include: {
|
||||
devices: true,
|
||||
},
|
||||
});
|
||||
const [error, payment] = await tryCatch(getPayment({ id: paymentId }));
|
||||
if (error) {
|
||||
return <div>Error getting payment: {error.message}</div>;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border-[1px] rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
|
||||
@ -46,7 +38,10 @@ export default async function PaymentPage({
|
||||
id="user-filters"
|
||||
className="pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
|
||||
>
|
||||
<DevicesToPay user={user || undefined} payment={payment || undefined} />
|
||||
<DevicesToPay
|
||||
user={session?.user || undefined}
|
||||
payment={payment || undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
2
app/next-auth.d.ts
vendored
2
app/next-auth.d.ts
vendored
@ -16,6 +16,8 @@ declare module "next-auth" {
|
||||
username?: string;
|
||||
user_permissions?: { id: number; name: string }[];
|
||||
id_card?: string;
|
||||
mobile?: string;
|
||||
wallet_balance?: number;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
last_login?: string;
|
||||
|
@ -27,15 +27,16 @@ export function AccountPopover() {
|
||||
<UserIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-fit">
|
||||
<PopoverContent className="w-fit mr-4">
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">
|
||||
{session.data?.user?.name}
|
||||
{session.data?.user?.first_name} {session.data?.user?.last_name}
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{session.data?.user?.id_card}
|
||||
</p>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<p className="font-semibold">{session.data?.user?.id_card}</p>
|
||||
<p>{session.data?.user?.mobile}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
disabled={loading}
|
||||
|
@ -31,7 +31,7 @@ export async function ApplicationLayout({
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<div className="text-sm font-mono px-2 p-1 rounded-md bg-green-500/10 text-green-900 dark:text-green-400">
|
||||
Welcome back,{" "}
|
||||
Welcome,{" "}
|
||||
<span className="font-semibold">
|
||||
{session?.user?.first_name} {session?.user?.last_name}
|
||||
</span>
|
||||
@ -39,7 +39,7 @@ export async function ApplicationLayout({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* <Wallet walletBalance={user?.walletBalance || 0} /> */}
|
||||
<Wallet walletBalance={session?.user?.wallet_balance || 0} />
|
||||
<ModeToggle />
|
||||
<AccountPopover />
|
||||
</div>
|
||||
|
@ -1,68 +1,77 @@
|
||||
'use client'
|
||||
import {
|
||||
TableCell,
|
||||
TableRow
|
||||
} from "@/components/ui/table";
|
||||
"use client";
|
||||
import { TableCell, TableRow } from "@/components/ui/table";
|
||||
import { deviceCartAtom } from "@/lib/atoms";
|
||||
import type { Device } from "@/lib/backend-types";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Device } from "@prisma/client";
|
||||
import { pl } from "date-fns/locale";
|
||||
import { useAtom } from "jotai";
|
||||
import Link from 'next/link';
|
||||
import Link from "next/link";
|
||||
import AddDevicesToCartButton from "./add-devices-to-cart-button";
|
||||
import BlockDeviceDialog from "./block-device-dialog";
|
||||
export default function ClickableRow({ device, parentalControl, admin = false }: { device: Device, parentalControl?: boolean, admin?: boolean }) {
|
||||
const [devices, setDeviceCart] = useAtom(deviceCartAtom)
|
||||
export default function ClickableRow({
|
||||
device,
|
||||
parentalControl,
|
||||
admin = false,
|
||||
}: { device: Device; parentalControl?: boolean; admin?: boolean }) {
|
||||
const [devices, setDeviceCart] = useAtom(deviceCartAtom);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={device.id}
|
||||
className={cn(
|
||||
parentalControl === false && "cursor-pointer hover:bg-muted",
|
||||
)}
|
||||
onClick={() => {
|
||||
if (parentalControl === true) return;
|
||||
setDeviceCart((prev) =>
|
||||
devices.some((d) => d.id === device.id)
|
||||
? prev.filter((d) => d.id !== device.id)
|
||||
: [...prev, device],
|
||||
);
|
||||
}}
|
||||
>
|
||||
<TableCell>
|
||||
<div className="flex flex-col items-start">
|
||||
<Link
|
||||
className="font-medium hover:underline"
|
||||
href={`/devices/${device.id}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{device.name}
|
||||
</Link>
|
||||
{device.is_active ? (
|
||||
<span className="text-muted-foreground">
|
||||
Active until{" "}
|
||||
{new Date(device.expiry_date || "").toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "2-digit",
|
||||
year: "numeric",
|
||||
})}
|
||||
</span>
|
||||
) : (
|
||||
<p>Inactive</p>
|
||||
)}
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={device.id}
|
||||
className={cn(parentalControl === false && "cursor-pointer hover:bg-muted",)}
|
||||
onClick={() => {
|
||||
|
||||
if (parentalControl === true) return
|
||||
setDeviceCart((prev) =>
|
||||
devices.some((d) => d.id === device.id)
|
||||
? prev.filter((d) => d.id !== device.id)
|
||||
: [...prev, device]
|
||||
)
|
||||
}}
|
||||
>
|
||||
<TableCell>
|
||||
<div className="flex flex-col items-start">
|
||||
<Link
|
||||
className="font-medium hover:underline"
|
||||
href={`/devices/${device.id}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{device.name}
|
||||
</Link>
|
||||
<span className="text-muted-foreground">
|
||||
Active until{" "}
|
||||
{new Date(device.expiryDate || "").toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "2-digit",
|
||||
year: "numeric",
|
||||
})}
|
||||
</span>
|
||||
{(device.blockedBy === "ADMIN" && device.blocked) && (
|
||||
<div className="p-2 rounded border my-2">
|
||||
<span>Comment: </span>
|
||||
<p className="text-neutral-500">
|
||||
{device?.reasonForBlocking}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">{device.mac}</TableCell>
|
||||
<TableCell>
|
||||
{!parentalControl ? (
|
||||
<AddDevicesToCartButton device={device} />
|
||||
) : (
|
||||
<BlockDeviceDialog admin={admin} type={device.blocked ? "unblock" : "block"} device={device} />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow >
|
||||
)
|
||||
{device.blocked_by === "ADMIN" && device.blocked && (
|
||||
<div className="p-2 rounded border my-2">
|
||||
<span>Comment: </span>
|
||||
<p className="text-neutral-500">{device?.reason_for_blocking}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">{device.mac}</TableCell>
|
||||
<TableCell>
|
||||
{!parentalControl ? (
|
||||
<AddDevicesToCartButton device={device} />
|
||||
) : (
|
||||
<BlockDeviceDialog
|
||||
admin={admin}
|
||||
type={device.blocked ? "unblock" : "block"}
|
||||
device={device}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
@ -5,12 +5,14 @@ import DeviceCard from "@/components/device-card";
|
||||
import NumberInput from "@/components/number-input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { deviceCartAtom, numberOfMonths } from "@/lib/atoms";
|
||||
import type { PaymentType } from "@/lib/types";
|
||||
import type { NewPayment, Payment } from "@/lib/backend-types";
|
||||
import { tryCatch } from "@/utils/tryCatch";
|
||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
import { CircleDollarSign, Loader2 } from "lucide-react";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
export default function DevicesForPayment() {
|
||||
const baseAmount = 100;
|
||||
const discountPercentage = 75;
|
||||
@ -37,12 +39,10 @@ export default function DevicesForPayment() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data: PaymentType = {
|
||||
numberOfMonths: months,
|
||||
userId: session?.data?.user?.id ?? "",
|
||||
deviceIds: devices.map((device) => device.id),
|
||||
amount: Number.parseFloat(total.toFixed(2)),
|
||||
paid: false,
|
||||
const data: NewPayment = {
|
||||
amount: 100,
|
||||
number_of_months: 2,
|
||||
device_ids: devices.map((device) => device.id),
|
||||
};
|
||||
|
||||
return (
|
||||
@ -69,7 +69,12 @@ export default function DevicesForPayment() {
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setDisabled(true);
|
||||
await createPayment(data);
|
||||
const [error, respose] = await tryCatch(createPayment(data));
|
||||
if (error) {
|
||||
setDisabled(false);
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
setDeviceCart([]);
|
||||
setMonths(1);
|
||||
setDisabled(false);
|
||||
|
@ -1,189 +1,207 @@
|
||||
'use client'
|
||||
import { verifyPayment } from "@/actions/payment";
|
||||
"use client";
|
||||
import { processWalletPayment } from "@/actions/payment";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableRow,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import type { Payment } from "@/lib/backend-types";
|
||||
import { formatDate } from "@/lib/utils";
|
||||
import type { Prisma, User } from "@prisma/client";
|
||||
import { BadgeDollarSign, Clipboard, ClipboardCheck, Loader2, Wallet } from "lucide-react";
|
||||
import {
|
||||
BadgeDollarSign,
|
||||
Clipboard,
|
||||
ClipboardCheck,
|
||||
Loader2,
|
||||
Wallet,
|
||||
} from "lucide-react";
|
||||
import type { User } from "next-auth";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "./ui/button";
|
||||
|
||||
type PaymentWithDevices = Prisma.PaymentGetPayload<{
|
||||
include: {
|
||||
devices: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
export default function DevicesToPay({
|
||||
payment,
|
||||
user
|
||||
}: { payment?: PaymentWithDevices, user?: User }) {
|
||||
const [verifying, setVerifying] = useState(false)
|
||||
payment,
|
||||
user,
|
||||
}: { payment?: Payment; user?: User }) {
|
||||
const [verifying, setVerifying] = useState(false);
|
||||
|
||||
const devices = payment?.devices;
|
||||
if (devices?.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// 100+(n−1)×75
|
||||
const walletBalance = user?.walletBalance ?? 0;
|
||||
const isWalletPayVisible = walletBalance > (payment?.amount ?? 0);
|
||||
const devices = payment?.devices;
|
||||
if (devices?.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// 100+(n−1)×75
|
||||
// const walletBalance = user?.walletBalance ?? 0;
|
||||
// TODO - get wallet balance from backend
|
||||
const walletBalance = 110;
|
||||
|
||||
const isWalletPayVisible = walletBalance > (payment?.amount ?? 0);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="p-2 flex flex-col gap-2">
|
||||
<h3 className="title-bg my-1 p-2 border border-dashed rounded-md font-semibold text-lg">
|
||||
{!payment?.paid ? "Devices to pay" : "Devices Paid"}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{devices?.map((device) => (
|
||||
<div
|
||||
key={device.id}
|
||||
className="bg-muted border rounded p-2 flex gap-2 items-center"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-medium">{device.name}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{device.mac}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-2 flex items-end justify-end p-2 text-sm text-foreground border rounded">
|
||||
<Table>
|
||||
<TableCaption>
|
||||
<div className="max-w-sm mx-auto">
|
||||
<p>Please send the following amount to the payment address</p>
|
||||
<AccountInfomation
|
||||
accName="Baraveli Dev"
|
||||
accountNo="90101400028321000"
|
||||
/>
|
||||
{payment?.paid ? (
|
||||
<Button size={"lg"} variant={"secondary"} disabled className="dark:text-green-200 text-green-900 bg-green-500/20 uppercase font-semibold">Payment Verified</Button>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
{isWalletPayVisible && (
|
||||
<Button
|
||||
disabled={verifying}
|
||||
onClick={async () => {
|
||||
setVerifying(true);
|
||||
await verifyPayment({
|
||||
userId: user?.id ?? "",
|
||||
paymentId: payment?.id,
|
||||
benefName: user?.name ?? "",
|
||||
accountNo: user?.accNo ?? "",
|
||||
absAmount: String(payment?.amount),
|
||||
time: formatDate(new Date(payment?.createdAt || "")),
|
||||
type: "WALLET",
|
||||
});
|
||||
setVerifying(false);
|
||||
}}
|
||||
variant={"secondary"} size={"lg"}>
|
||||
{verifying ? "Paying..." : "Pay with wallet"}
|
||||
<Wallet />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
disabled={verifying}
|
||||
onClick={async () => {
|
||||
setVerifying(true);
|
||||
const res = await verifyPayment({
|
||||
userId: user?.id ?? "",
|
||||
paymentId: payment?.id,
|
||||
benefName: user?.name ?? "",
|
||||
accountNo: user?.accNo ?? "",
|
||||
absAmount: String(payment?.amount),
|
||||
type: "TRANSFER",
|
||||
time: formatDate(new Date(payment?.createdAt || "")),
|
||||
});
|
||||
setVerifying(false);
|
||||
switch (true) {
|
||||
case res?.success === true:
|
||||
toast.success(res.message);
|
||||
break;
|
||||
case res?.success === false:
|
||||
toast.error(res?.message);
|
||||
break;
|
||||
default:
|
||||
toast.error("Unexpected error occurred.");
|
||||
}
|
||||
}}
|
||||
size={"lg"} className="mb-4">
|
||||
{verifying ? "Verifying..." : "Verify Payment"}
|
||||
{verifying ? <Loader2 className="animate-spin" /> : <BadgeDollarSign />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
)}
|
||||
|
||||
</div>
|
||||
</TableCaption>
|
||||
<TableBody className="">
|
||||
<TableRow>
|
||||
<TableCell>Total Devices</TableCell>
|
||||
<TableCell className="text-right text-xl">{devices?.length}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Duration</TableCell>
|
||||
<TableCell className="text-right text-xl">{payment?.numberOfMonths} Months</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow className="">
|
||||
<TableCell colSpan={1}>Total Due</TableCell>
|
||||
<TableCell className="text-right text-3xl font-bold">{payment?.amount.toFixed(2)}</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="p-2 flex flex-col gap-2">
|
||||
<h3 className="title-bg my-1 p-2 border border-dashed rounded-md font-semibold text-lg">
|
||||
{!payment?.paid ? "Devices to pay" : "Devices Paid"}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{devices?.map((device) => (
|
||||
<div
|
||||
key={device.id}
|
||||
className="bg-muted border rounded p-2 flex gap-2 items-center"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-medium">{device.name}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{device.mac}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-2 flex items-end justify-end p-2 text-sm text-foreground border rounded">
|
||||
<Table>
|
||||
<TableCaption>
|
||||
<div className="max-w-sm mx-auto">
|
||||
<p>Please send the following amount to the payment address</p>
|
||||
<AccountInfomation
|
||||
accName="Baraveli Dev"
|
||||
accountNo="90101400028321000"
|
||||
/>
|
||||
{payment?.paid ? (
|
||||
<Button
|
||||
size={"lg"}
|
||||
variant={"secondary"}
|
||||
disabled
|
||||
className="dark:text-green-200 text-green-900 bg-green-500/20 uppercase font-semibold"
|
||||
>
|
||||
Payment Verified
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
{isWalletPayVisible && (
|
||||
<Button
|
||||
disabled={verifying}
|
||||
onClick={async () => {
|
||||
setVerifying(true);
|
||||
await processWalletPayment({
|
||||
amount: payment?.amount ?? 0,
|
||||
payment: payment,
|
||||
});
|
||||
setVerifying(false);
|
||||
}}
|
||||
variant={"secondary"}
|
||||
size={"lg"}
|
||||
>
|
||||
{verifying ? "Paying..." : "Pay with wallet"}
|
||||
<Wallet />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
disabled={verifying}
|
||||
onClick={async () => {
|
||||
setVerifying(true);
|
||||
// const res = await verifyPayment({
|
||||
// userId: user?.id ?? "",
|
||||
// paymentId: payment?.id,
|
||||
// benefName: user?.name ?? "",
|
||||
// accountNo: user?.accNo ?? "",
|
||||
// absAmount: String(payment?.amount),
|
||||
// type: "TRANSFER",
|
||||
// time: formatDate(new Date(payment?.createdAt || "")),
|
||||
// });
|
||||
setVerifying(false);
|
||||
// switch (true) {
|
||||
// case res?.success === true:
|
||||
// toast.success(res.message);
|
||||
// break;
|
||||
// case res?.success === false:
|
||||
// toast.error(res?.message);
|
||||
// break;
|
||||
// default:
|
||||
// toast.error("Unexpected error occurred.");
|
||||
// }
|
||||
}}
|
||||
size={"lg"}
|
||||
className="mb-4"
|
||||
>
|
||||
{verifying ? "Verifying..." : "Verify Payment"}
|
||||
{verifying ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
) : (
|
||||
<BadgeDollarSign />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableCaption>
|
||||
<TableBody className="">
|
||||
<TableRow>
|
||||
<TableCell>Total Devices</TableCell>
|
||||
<TableCell className="text-right text-xl">
|
||||
{devices?.length}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Duration</TableCell>
|
||||
<TableCell className="text-right text-xl">
|
||||
{payment?.number_of_months} Months
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow className="">
|
||||
<TableCell colSpan={1}>Total Due</TableCell>
|
||||
<TableCell className="text-right text-3xl font-bold">
|
||||
{payment?.amount?.toFixed(2)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountInfomation({
|
||||
accountNo,
|
||||
accName,
|
||||
accountNo,
|
||||
accName,
|
||||
}: {
|
||||
accountNo: string;
|
||||
accName: string;
|
||||
accountNo: string;
|
||||
accName: string;
|
||||
}) {
|
||||
const [accNo, setAccNo] = useState(false)
|
||||
return (
|
||||
<div className="justify-start items-start border my-4 flex flex-col gap-2 p-2 rounded-md">
|
||||
<h6 className="title-bg uppercase p-2 border rounded w-full font-semibold">
|
||||
Account Information
|
||||
</h6>
|
||||
<div className="border justify-start flex flex-col items-start bg-white/10 w-full p-2 rounded">
|
||||
<div className="text-sm font-semibold">Account Name</div>
|
||||
<span>{accName}</span>
|
||||
</div>
|
||||
<div className="border flex justify-between items-start gap-2 bg-white/10 w-full p-2 rounded">
|
||||
<div className="flex flex-col items-start justify-start">
|
||||
<p className="text-sm font-semibold">Account No</p>
|
||||
<span>{accountNo}</span>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTimeout(() => {
|
||||
setAccNo(true)
|
||||
navigator.clipboard.writeText(accountNo)
|
||||
}, 2000)
|
||||
toast.success("Account number copied!")
|
||||
setAccNo((prev) => !prev)
|
||||
}}
|
||||
variant={"link"}>
|
||||
{accNo ? <Clipboard /> : <ClipboardCheck color="green" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const [accNo, setAccNo] = useState(false);
|
||||
return (
|
||||
<div className="justify-start items-start border my-4 flex flex-col gap-2 p-2 rounded-md">
|
||||
<h6 className="title-bg uppercase p-2 border rounded w-full font-semibold">
|
||||
Account Information
|
||||
</h6>
|
||||
<div className="border justify-start flex flex-col items-start bg-white/10 w-full p-2 rounded">
|
||||
<div className="text-sm font-semibold">Account Name</div>
|
||||
<span>{accName}</span>
|
||||
</div>
|
||||
<div className="border flex justify-between items-start gap-2 bg-white/10 w-full p-2 rounded">
|
||||
<div className="flex flex-col items-start justify-start">
|
||||
<p className="text-sm font-semibold">Account No</p>
|
||||
<span>{accountNo}</span>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTimeout(() => {
|
||||
setAccNo(true);
|
||||
navigator.clipboard.writeText(accountNo);
|
||||
}, 2000);
|
||||
toast.success("Account number copied!");
|
||||
setAccNo((prev) => !prev);
|
||||
}}
|
||||
variant={"link"}
|
||||
>
|
||||
{accNo ? <Clipboard /> : <ClipboardCheck color="green" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -10,7 +10,10 @@ import {
|
||||
} from "@/components/ui/table";
|
||||
import Link from "next/link";
|
||||
|
||||
import { getPayments } from "@/actions/payment";
|
||||
import type { Payment } from "@/lib/backend-types";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { tryCatch } from "@/utils/tryCatch";
|
||||
import { Calendar } from "lucide-react";
|
||||
import Pagination from "./pagination";
|
||||
import { Badge } from "./ui/badge";
|
||||
@ -26,6 +29,7 @@ export async function PaymentsTable({
|
||||
sortBy: string;
|
||||
}>;
|
||||
}) {
|
||||
const query = (await searchParams)?.query || "";
|
||||
// const session = await auth.api.getSession({
|
||||
// headers: await headers(),
|
||||
// });
|
||||
@ -79,11 +83,15 @@ export async function PaymentsTable({
|
||||
// createdAt: "desc",
|
||||
// },
|
||||
// });
|
||||
const [error, payments] = await tryCatch(getPayments());
|
||||
|
||||
return null;
|
||||
if (error) {
|
||||
return <pre>{JSON.stringify(error, null, 2)}</pre>;
|
||||
}
|
||||
const { data, meta, links } = payments;
|
||||
return (
|
||||
<div>
|
||||
{payments.length === 0 ? (
|
||||
{data?.length === 0 ? (
|
||||
<div className="h-[calc(100svh-400px)] flex flex-col items-center justify-center my-4">
|
||||
<h3>No Payments yet.</h3>
|
||||
</div>
|
||||
@ -101,7 +109,7 @@ export async function PaymentsTable({
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="overflow-scroll">
|
||||
{payments.map((payment) => (
|
||||
{payments?.data?.map((payment) => (
|
||||
<TableRow key={payment.id}>
|
||||
<TableCell>
|
||||
<div
|
||||
@ -115,7 +123,7 @@ export async function PaymentsTable({
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar size={16} opacity={0.5} />
|
||||
<span className="text-muted-foreground">
|
||||
{new Date(payment.createdAt).toLocaleDateString(
|
||||
{new Date(payment.created_at).toLocaleDateString(
|
||||
"en-US",
|
||||
{
|
||||
month: "short",
|
||||
@ -162,7 +170,7 @@ export async function PaymentsTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
{payment.numberOfMonths} Months
|
||||
{payment.number_of_months} Months
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-semibold pr-2">
|
||||
@ -178,21 +186,25 @@ export async function PaymentsTable({
|
||||
<TableCell colSpan={2}>
|
||||
{query.length > 0 && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Showing {payments.length} locations for "{query}
|
||||
Showing {payments?.data?.length} locations for "
|
||||
{query}
|
||||
"
|
||||
</p>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{totalPayments} payments
|
||||
{meta.total} payments
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
<Pagination totalPages={totalPages} currentPage={page} />
|
||||
<Pagination
|
||||
totalPages={meta.total / meta.per_page}
|
||||
currentPage={meta.current_page}
|
||||
/>
|
||||
</div>
|
||||
<div className="sm:hidden block">
|
||||
{payments.map((payment) => (
|
||||
{data.map((payment) => (
|
||||
<MobilePaymentDetails key={payment.id} payment={payment} />
|
||||
))}
|
||||
</div>
|
||||
@ -202,7 +214,7 @@ export async function PaymentsTable({
|
||||
);
|
||||
}
|
||||
|
||||
function MobilePaymentDetails({ payment }: { payment: PaymentWithDevices }) {
|
||||
function MobilePaymentDetails({ payment }: { payment: Payment }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@ -215,7 +227,7 @@ function MobilePaymentDetails({ payment }: { payment: PaymentWithDevices }) {
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar size={16} opacity={0.5} />
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{new Date(payment.createdAt).toLocaleDateString("en-US", {
|
||||
{new Date(payment.created_at).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "2-digit",
|
||||
year: "numeric",
|
||||
@ -256,7 +268,7 @@ function MobilePaymentDetails({ payment }: { payment: PaymentWithDevices }) {
|
||||
<Separator className="my-2" />
|
||||
<h3 className="text-sm font-medium">Duration</h3>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{payment.numberOfMonths} Months
|
||||
{payment.number_of_months} Months
|
||||
</span>
|
||||
<Separator className="my-2" />
|
||||
<h3 className="text-sm font-medium">Amount</h3>
|
||||
|
@ -37,7 +37,7 @@ export function Wallet({
|
||||
}
|
||||
|
||||
const data: TopupType = {
|
||||
userId: session?.data?.user.id ?? "",
|
||||
userId: session?.data?.user?.id ?? "",
|
||||
amount: Number.parseFloat(amount.toFixed(2)),
|
||||
paid: false,
|
||||
};
|
||||
|
@ -50,3 +50,23 @@ export interface Api400Error {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Payment {
|
||||
id: string;
|
||||
devices: Device[];
|
||||
number_of_months: number;
|
||||
amount: number;
|
||||
paid: boolean;
|
||||
paid_at: string | null;
|
||||
method: string;
|
||||
expires_at: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
user: number;
|
||||
}
|
||||
|
||||
export interface NewPayment {
|
||||
device_ids: number[];
|
||||
number_of_months: number;
|
||||
amount: number;
|
||||
}
|
||||
|
@ -1,11 +1,3 @@
|
||||
export type PaymentType = {
|
||||
numberOfMonths: number;
|
||||
userId: string;
|
||||
deviceIds: string[];
|
||||
amount: number;
|
||||
paid: boolean;
|
||||
};
|
||||
|
||||
export type TopupType = {
|
||||
amount: number;
|
||||
userId: string;
|
||||
|
@ -19,6 +19,8 @@ export interface User {
|
||||
user_permissions: Permission[];
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
mobile?: string;
|
||||
wallet_balance?: number;
|
||||
is_superuser: boolean;
|
||||
date_joined: string;
|
||||
last_login: string;
|
||||
|
@ -28,7 +28,7 @@ export async function login({
|
||||
|
||||
export async function logout({ token }: { token: string }) {
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/auth/logout/`,
|
||||
`${process.env.SARLINK_API_BASE_URL}/auth/logout/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
@ -4,7 +4,7 @@ axios.defaults.xsrfCookieName = "csrftoken";
|
||||
axios.defaults.xsrfHeaderName = "X-CSRFToken";
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||
baseURL: process.env.SARLINK_API_BASE_URL,
|
||||
validateStatus: (status) => {
|
||||
return status < 500; // Resolve only if the status code is less than 500
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user