mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-10-14 03:31:36 +00:00
chore: add skeletons to tables and loading.tsx files for routes and run formatting ♻️
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 12m20s
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 12m20s
This commit is contained in:
@@ -3,27 +3,27 @@ import { AgreementCard } from "@/components/agreement-card";
|
||||
import { tryCatch } from "@/utils/tryCatch";
|
||||
|
||||
export default async function Agreements() {
|
||||
const [error, profile] = await tryCatch(getProfile());
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
|
||||
<h3 className="text-sarLinkOrange text-2xl">Agreements</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1">
|
||||
{error ? (
|
||||
<div className="text-red-500">
|
||||
An error occurred while fetching agreements: {error.message}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{profile.agreement ? (
|
||||
<AgreementCard agreement={profile.agreement} />
|
||||
) : (
|
||||
<div className="text-gray-500">No agreement found.</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const [error, profile] = await tryCatch(getProfile());
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
|
||||
<h3 className="text-sarLinkOrange text-2xl">Agreements</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1">
|
||||
{error ? (
|
||||
<div className="text-red-500">
|
||||
An error occurred while fetching agreements: {error.message}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{profile.agreement ? (
|
||||
<AgreementCard agreement={profile.agreement} />
|
||||
) : (
|
||||
<div className="text-gray-500">No agreement found.</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -1,76 +0,0 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function DevicesTableSkeleton() {
|
||||
return (
|
||||
<>
|
||||
<div className="hidden sm:block">
|
||||
<Table className="overflow-scroll">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Device Name</TableHead>
|
||||
<TableHead>MAC Address</TableHead>
|
||||
<TableHead>#</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="overflow-scroll">
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<TableRow key={`${i + 1}`}>
|
||||
<TableCell>
|
||||
<Skeleton className="w-full h-10 rounded" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Skeleton className="w-full h-10 rounded" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Skeleton className="w-full h-10 rounded" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>
|
||||
<Skeleton className="w-full h-4 rounded" />
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
<Skeleton className="w-20 h-4 rounded" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="sm:hidden my-4">
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<DeviceCardSkeleton key={`${i + 1}`} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function DeviceCardSkeleton() {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex text-sm justify-between items-center my-2 p-4 border rounded-md bg-gray-100",
|
||||
)}
|
||||
>
|
||||
<div className="font-semibold flex w-full flex-col items-start gap-2 mb-2 relative">
|
||||
<Skeleton className="w-32 h-6" />
|
||||
<Skeleton className="w-36 h-6" />
|
||||
<Skeleton className="w-32 h-4" />
|
||||
<Skeleton className="w-40 h-8" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
22
app/(dashboard)/devices/loading.tsx
Normal file
22
app/(dashboard)/devices/loading.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import DevicesTableSkeleton from "@/components/device-table-skeleton";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export default function LoadingComponent() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
|
||||
<Skeleton className="w-48 h-8" />
|
||||
<Skeleton className="w-20 h-8" />
|
||||
</div>
|
||||
<div
|
||||
id="user-filters"
|
||||
className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-endO"
|
||||
>
|
||||
<DevicesTableSkeleton
|
||||
headers={["Device Name", "Mac Address", "Vendor", "#"]}
|
||||
length={10}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { Suspense } from "react";
|
||||
import { authOptions } from "@/app/auth";
|
||||
import DevicesTableSkeleton from "@/components/device-table-skeleton";
|
||||
import { DevicesTable } from "@/components/devices-table";
|
||||
import DynamicFilter from "@/components/generic-filter";
|
||||
import AddDeviceDialogForm from "@/components/user/add-device-dialog";
|
||||
import DevicesTableSkeleton from "./device-table-skeleton";
|
||||
|
||||
export default async function Devices({
|
||||
searchParams,
|
||||
@@ -53,7 +53,15 @@ export default async function Devices({
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<Suspense key={query || page} fallback={<DevicesTableSkeleton />}>
|
||||
<Suspense
|
||||
key={query || page}
|
||||
fallback={
|
||||
<DevicesTableSkeleton
|
||||
headers={["Device Name", "Mac Address", "Vendor", "#"]}
|
||||
length={10}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DevicesTable parentalControl={false} searchParams={searchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
22
app/(dashboard)/parental-control/loading.tsx
Normal file
22
app/(dashboard)/parental-control/loading.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import DevicesTableSkeleton from "@/components/device-table-skeleton";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export default function LoadingComponent() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
|
||||
<Skeleton className="w-48 h-8" />
|
||||
<Skeleton className="w-20 h-8" />
|
||||
</div>
|
||||
<div
|
||||
id="user-filters"
|
||||
className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-endO"
|
||||
>
|
||||
<DevicesTableSkeleton
|
||||
headers={["Device Name", "Mac Address", "Vendor", "#"]}
|
||||
length={10}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
import { Suspense } from "react";
|
||||
import DevicesTableSkeleton from "@/components/device-table-skeleton";
|
||||
import { DevicesTable } from "@/components/devices-table";
|
||||
import DynamicFilter from "@/components/generic-filter";
|
||||
|
||||
@@ -51,7 +52,15 @@ export default async function ParentalControl({
|
||||
]}
|
||||
/>{" "}
|
||||
</div>
|
||||
<Suspense key={(await searchParams).page} fallback={"loading...."}>
|
||||
<Suspense
|
||||
key={(await searchParams).page}
|
||||
fallback={
|
||||
<DevicesTableSkeleton
|
||||
headers={["Device Name", "Mac Address", "Vendor", "#"]}
|
||||
length={10}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DevicesTable
|
||||
parentalControl={true}
|
||||
searchParams={searchParams}
|
||||
|
22
app/(dashboard)/payments/loading.tsx
Normal file
22
app/(dashboard)/payments/loading.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import DevicesTableSkeleton from "@/components/device-table-skeleton";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export default function LoadingComponent() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
|
||||
<Skeleton className="w-48 h-8" />
|
||||
<Skeleton className="w-20 h-8" />
|
||||
</div>
|
||||
<div
|
||||
id="user-filters"
|
||||
className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-endO"
|
||||
>
|
||||
<DevicesTableSkeleton
|
||||
headers={["Details", "Duration", "Status", "Amount"]}
|
||||
length={10}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
import { Suspense } from "react";
|
||||
import DevicesTableSkeleton from "@/components/device-table-skeleton";
|
||||
import DynamicFilter from "@/components/generic-filter";
|
||||
import { PaymentsTable } from "@/components/payments-table";
|
||||
|
||||
@@ -14,8 +15,8 @@ export default async function Payments({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
|
||||
<h3 className="text-sarLinkOrange text-2xl">My Payments</h3>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-3 mb-4">
|
||||
<h3 className="text-sarLinkOrange text-2xl">My Subscriptions</h3>
|
||||
</div>
|
||||
<div
|
||||
id="user-filters"
|
||||
@@ -72,7 +73,15 @@ export default async function Payments({
|
||||
]}
|
||||
/>{" "}
|
||||
</div>
|
||||
<Suspense key={query} fallback={"loading...."}>
|
||||
<Suspense
|
||||
key={query}
|
||||
fallback={
|
||||
<DevicesTableSkeleton
|
||||
headers={["Details", "Duration", "Status", "Amount"]}
|
||||
length={10}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<PaymentsTable searchParams={searchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
@@ -9,72 +9,72 @@ import { TextShimmer } from "@/components/ui/text-shimmer";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { tryCatch } from "@/utils/tryCatch";
|
||||
export default async function TopupPage({
|
||||
params,
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ topupId: string }>;
|
||||
params: Promise<{ topupId: string }>;
|
||||
}) {
|
||||
const topupId = (await params).topupId;
|
||||
const [error, topup] = await tryCatch(getTopup({ id: topupId }));
|
||||
if (error) {
|
||||
if (error.message === "Invalid token.") redirect("/auth/signin");
|
||||
return <ClientErrorMessage message={error.message} />;
|
||||
}
|
||||
const topupId = (await params).topupId;
|
||||
const [error, topup] = await tryCatch(getTopup({ id: topupId }));
|
||||
if (error) {
|
||||
if (error.message === "Invalid token.") redirect("/auth/signin");
|
||||
return <ClientErrorMessage message={error.message} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-4 mb-4 mx-2">
|
||||
<h3 className="text-sarLinkOrange text-2xl">Topup</h3>
|
||||
<div className="flex flex-col gap-4 items-end w-full">
|
||||
{!topup.is_expired && topup.paid && topup.status !== "PENDING" && (
|
||||
<Button
|
||||
disabled
|
||||
className={cn(
|
||||
"rounded-md opacity-100! uppercase font-semibold",
|
||||
topup?.paid
|
||||
? "text-green-900 bg-green-500/20"
|
||||
: "text-inherit bg-yellow-400",
|
||||
)}
|
||||
>
|
||||
{topup.status}
|
||||
</Button>
|
||||
)}
|
||||
{topup.status === "PENDING" && !topup.is_expired && (
|
||||
<Button>
|
||||
<TextShimmer>Payment Pending</TextShimmer>{" "}
|
||||
</Button>
|
||||
)}
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-4 mb-4 mx-2">
|
||||
<h3 className="text-sarLinkOrange text-2xl">Topup</h3>
|
||||
<div className="flex flex-col gap-4 items-end w-full">
|
||||
{!topup.is_expired && topup.paid && topup.status !== "PENDING" && (
|
||||
<Button
|
||||
disabled
|
||||
className={cn(
|
||||
"rounded-md opacity-100! uppercase font-semibold",
|
||||
topup?.paid
|
||||
? "text-green-900 bg-green-500/20"
|
||||
: "text-inherit bg-yellow-400",
|
||||
)}
|
||||
>
|
||||
{topup.status}
|
||||
</Button>
|
||||
)}
|
||||
{topup.status === "PENDING" && !topup.is_expired && (
|
||||
<Button>
|
||||
<TextShimmer>Payment Pending</TextShimmer>{" "}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{!topup.paid &&
|
||||
(topup.is_expired ? (
|
||||
<Button
|
||||
disabled
|
||||
className="rounded-md opacity-100! uppercase font-semibold text-red-500 bg-red-500/20"
|
||||
>
|
||||
Topup Expired
|
||||
</Button>
|
||||
) : topup.status === "PENDING" ? (
|
||||
<CancelTopupButton topupId={topupId} />
|
||||
) : topup.status === "CANCELLED" ? (
|
||||
<Button disabled>Topup Cancelled</Button>
|
||||
) : (
|
||||
""
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{!topup.paid && topup.status === "PENDING" && !topup.is_expired && (
|
||||
<ExpiryCountDown expiryLabel="Top up" expiresAt={topup.expires_at} />
|
||||
)}
|
||||
<div
|
||||
id="user-topup-details"
|
||||
className="pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
|
||||
>
|
||||
<TopupToPay
|
||||
disabled={
|
||||
topup.paid || topup.is_expired || topup.status === "CANCELLED"
|
||||
}
|
||||
topup={topup || undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
{!topup.paid &&
|
||||
(topup.is_expired ? (
|
||||
<Button
|
||||
disabled
|
||||
className="rounded-md opacity-100! uppercase font-semibold text-red-500 bg-red-500/20"
|
||||
>
|
||||
Topup Expired
|
||||
</Button>
|
||||
) : topup.status === "PENDING" ? (
|
||||
<CancelTopupButton topupId={topupId} />
|
||||
) : topup.status === "CANCELLED" ? (
|
||||
<Button disabled>Topup Cancelled</Button>
|
||||
) : (
|
||||
""
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{!topup.paid && topup.status === "PENDING" && !topup.is_expired && (
|
||||
<ExpiryCountDown expiryLabel="Top up" expiresAt={topup.expires_at} />
|
||||
)}
|
||||
<div
|
||||
id="user-topup-details"
|
||||
className="pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
|
||||
>
|
||||
<TopupToPay
|
||||
disabled={
|
||||
topup.paid || topup.is_expired || topup.status === "CANCELLED"
|
||||
}
|
||||
topup={topup || undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
22
app/(dashboard)/top-ups/loading.tsx
Normal file
22
app/(dashboard)/top-ups/loading.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import DevicesTableSkeleton from "@/components/device-table-skeleton";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export default function LoadingComponent() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
|
||||
<Skeleton className="w-48 h-8" />
|
||||
<Skeleton className="w-20 h-8" />
|
||||
</div>
|
||||
<div
|
||||
id="user-filters"
|
||||
className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-endO"
|
||||
>
|
||||
<DevicesTableSkeleton
|
||||
headers={["Details", "Status", "Amount"]}
|
||||
length={10}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
import { Suspense } from "react";
|
||||
import DevicesTableSkeleton from "@/components/device-table-skeleton";
|
||||
import DynamicFilter from "@/components/generic-filter";
|
||||
import { TopupsTable } from "@/components/topups-table";
|
||||
|
||||
@@ -78,7 +79,15 @@ export default async function Topups({
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<Suspense key={query} fallback={"loading...."}>
|
||||
<Suspense
|
||||
key={query}
|
||||
fallback={
|
||||
<DevicesTableSkeleton
|
||||
headers={["Details", "Status", "Amount"]}
|
||||
length={10}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TopupsTable searchParams={searchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
@@ -1,63 +1,78 @@
|
||||
import { Suspense } from "react";
|
||||
import DevicesTableSkeleton from "@/components/device-table-skeleton";
|
||||
import DynamicFilter from "@/components/generic-filter";
|
||||
import { WalletTransactionsTable } from "@/components/wallet-transactions-table";
|
||||
|
||||
export default async function Wallet({
|
||||
searchParams,
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{
|
||||
query: string;
|
||||
page: number;
|
||||
sortBy: string;
|
||||
status: string;
|
||||
}>;
|
||||
searchParams: Promise<{
|
||||
query: string;
|
||||
page: number;
|
||||
sortBy: string;
|
||||
status: string;
|
||||
}>;
|
||||
}) {
|
||||
const query = (await searchParams)?.query || "";
|
||||
const query = (await searchParams)?.query || "";
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
|
||||
<h3 className="text-sarLinkOrange text-2xl">Transaction History</h3>
|
||||
</div>
|
||||
<div
|
||||
id="wallet-filters"
|
||||
className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
|
||||
>
|
||||
<DynamicFilter
|
||||
inputs={[
|
||||
{
|
||||
label: "Type",
|
||||
name: "transaction_type",
|
||||
type: "radio-group",
|
||||
options: [
|
||||
{
|
||||
label: "All",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
label: "Debit",
|
||||
value: "debit",
|
||||
},
|
||||
{
|
||||
label: "Credit",
|
||||
value: "credit",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Topup Amount",
|
||||
name: "amount",
|
||||
type: "dual-range-slider",
|
||||
min: 0,
|
||||
max: 1000,
|
||||
step: 10,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<Suspense key={query} fallback={"loading...."}>
|
||||
<WalletTransactionsTable searchParams={searchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
|
||||
<h3 className="text-sarLinkOrange text-2xl">Transaction History</h3>
|
||||
</div>
|
||||
<div
|
||||
id="wallet-filters"
|
||||
className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-start"
|
||||
>
|
||||
<DynamicFilter
|
||||
inputs={[
|
||||
{
|
||||
label: "Type",
|
||||
name: "transaction_type",
|
||||
type: "radio-group",
|
||||
options: [
|
||||
{
|
||||
label: "All",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
label: "Debit",
|
||||
value: "debit",
|
||||
},
|
||||
{
|
||||
label: "Credit",
|
||||
value: "credit",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Topup Amount",
|
||||
name: "amount",
|
||||
type: "dual-range-slider",
|
||||
min: 0,
|
||||
max: 1000,
|
||||
step: 10,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<Suspense
|
||||
key={query}
|
||||
fallback={
|
||||
<DevicesTableSkeleton
|
||||
headers={[
|
||||
"Description",
|
||||
"Amount",
|
||||
"Transaction Type",
|
||||
"View Details",
|
||||
"Created at",
|
||||
]}
|
||||
length={10}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<WalletTransactionsTable searchParams={searchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user