feat: add loading state and full-page loader component; update payment page and application layout to improve user experience
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 7m23s

This commit is contained in:
i701 2025-05-31 12:37:46 +05:00
parent c705addccc
commit bed426a6b4
Signed by: i701
GPG Key ID: 54A0DA1E26D8E587
14 changed files with 84 additions and 18 deletions

View File

@ -0,0 +1,8 @@
import FullPageLoader from '@/components/full-page-loader'
import React from 'react'
export default function Loading() {
return (
<FullPageLoader />
)
}

View File

@ -39,7 +39,9 @@ export default async function PaymentPage({
>
{payment?.paid ? "Paid" : "Pending"}
</Button>
<CancelPaymentButton paymentId={paymentId} />
{!payment.paid && (
<CancelPaymentButton paymentId={paymentId} />
)}
</div>
</div>

View File

@ -16,6 +16,7 @@ import { tryCatch } from "@/utils/tryCatch";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { AccountPopover } from "./account-popver";
import { WelcomeBanner } from "../welcome-banner";
export async function ApplicationLayout({
children,
@ -31,7 +32,6 @@ export async function ApplicationLayout({
return (
<SidebarProvider>
<AppSidebar />
<DeviceCartDrawer />
<SidebarInset>
<header className="flex justify-between sticky top-0 bg-background h-16 shrink-0 items-center gap-2 border-b px-4 z-10">
<div className="flex items-center gap-2 ">
@ -45,13 +45,14 @@ export async function ApplicationLayout({
<AccountPopover />
</div>
</header>
<div className="text-sm font-mono px-2 p-1 bg-green-500/10 text-green-900 dark:text-green-400">
Welcome,{" "}
<span className="font-semibold">
{session?.user?.first_name} {session?.user?.last_name}
</span>
<WelcomeBanner
firstName={session?.user?.first_name}
lastName={session?.user?.last_name}
/>
<DeviceCartDrawer />
<div className="p-4 flex flex-col flex-1 rounded-lg bg-background">
{children}
</div>
<div className="p-4 flex flex-col flex-1">{children}</div>
</SidebarInset>
</SidebarProvider>
);

View File

@ -20,7 +20,7 @@ export default function ClickableRow({
key={device.id}
className={cn(
(parentalControl === false && device.blocked) || device.is_active
? "cursor-not-allowed bg-accent-foreground/10 hover:bg-accent-foreground/10"
? "cursor-not-allowed hover:bg-accent-foreground/10"
: "cursor-pointer hover:bg-muted-foreground/10",
)}
onClick={() => {

View File

@ -19,7 +19,7 @@ export default function DeviceCard({
return (
<div
onKeyUp={() => {}}
onKeyUp={() => { }}
onClick={() => {
if (device.blocked) return;
if (device.is_active === true) return;
@ -36,9 +36,9 @@ export default function DeviceCard({
<div
className={cn(
"flex text-sm justify-between items-center my-2 p-4 border rounded-md",
isChecked ? "bg-accent" : "bg-",
isChecked ? "bg-accent" : "",
device.is_active
? "cursor-not-allowed bg-accent-foreground/10 hover:bg-accent-foreground/10"
? "cursor-not-allowed text-green-600 hover:bg-accent-foreground/10"
: "cursor-pointer hover:bg-muted-foreground/10",
)}
>

View File

@ -10,7 +10,6 @@ export function DeviceCartDrawer() {
const pathname = usePathname();
const devices = useAtomValue(deviceCartAtom);
const router = useRouter();
if (pathname === "/payment" || pathname === "/devices-to-pay") {
return null;
}
@ -19,7 +18,7 @@ export function DeviceCartDrawer() {
return (
<Button
size={"lg"}
className="bg-sarLinkOrange fixed bottom-20 w-80 uppercase h-12 z-20 left-1/2 transform -translate-x-1/2"
className="bg-sarLinkOrange dark:hover:bg-orange-900 fixed bottom-20 w-80 uppercase h-12 z-20 left-1/2 transform -translate-x-1/2 hover:ring-2 hover:ring-sarLinkOrange transition-all duration-200"
onClick={() => router.push("/devices-to-pay")}
variant="outline"
>

View File

@ -12,6 +12,7 @@ import { CircleDollarSign, Loader2 } from "lucide-react";
import { redirect, usePathname } from "next/navigation";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import FullPageLoader from "./full-page-loader";
export default function DevicesForPayment() {
const baseAmount = 100;
const discountPercentage = 75;
@ -45,7 +46,7 @@ export default function DevicesForPayment() {
};
if (disabled) {
return "Please wait...";
return <FullPageLoader />
}
return (
<div className="max-w-lg mx-auto space-y-4 px-4">

View File

@ -0,0 +1,9 @@
import React from 'react'
import { Loader2 } from 'lucide-react'
export default function FullPageLoader() {
return (
<div className='flex items-center justify-center h-screen'>
<Loader2 className='animate-spin' />
</div>
)
}

View File

@ -101,7 +101,7 @@ export default function AddDeviceDialogForm({ user_id }: { user_id?: string }) {
Device Name
</Label>
<Input
placeholder="eg: Iphone X"
placeholder="eg: iPhone X"
type="text"
{...register("name")}
id="device_name"

View File

@ -0,0 +1,33 @@
"use client";
import { useEffect, useState } from "react";
interface WelcomeBannerProps {
firstName?: string | null;
lastName?: string | null;
}
export function WelcomeBanner({ firstName, lastName }: WelcomeBannerProps) {
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
const timer = setTimeout(() => {
setIsVisible(false);
}, 3000);
return () => clearTimeout(timer);
}, []);
if (!isVisible) {
return null;
}
return (
<div className="text-sm font-mono px-2 p-1 fade-out-10 bg-green-500/10 text-green-900 dark:text-green-400">
Welcome,{" "}
<span className="font-semibold">
{firstName} {lastName}
</span>
</div>
);
}

View File

@ -15,6 +15,7 @@ export const formulaResultAtom = atom("");
export const deviceCartAtom = atom<Device[]>([]);
export const cartDrawerOpenAtom = atom(false);
export const WalletDrawerOpenAtom = atom(false);
export const loadingDevicesToPayAtom = atom(false);
// Export the atoms with their store
export const atoms = {
@ -27,4 +28,5 @@ export const atoms = {
deviceCartAtom,
cartDrawerOpenAtom,
walletTopUpValue,
loadingDevicesToPayAtom,
};

10
package-lock.json generated
View File

@ -58,6 +58,7 @@
"eslint-config-next": "15.1.2",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"tailwindcss-motion": "^1.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
}
@ -7975,6 +7976,15 @@
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/tailwindcss-motion": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tailwindcss-motion/-/tailwindcss-motion-1.1.0.tgz",
"integrity": "sha512-0lK6rA4+367ffJdi1TtB72GlMCxJi2TP/xRiHc6An5pZSlU6WfIHhSvLxpcGilGZfBrOqc2q4woH1DEP/lCNbQ==",
"dev": true,
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/tapable": {
"version": "2.2.1",
"dev": true,

View File

@ -59,6 +59,7 @@
"eslint-config-next": "15.1.2",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"tailwindcss-motion": "^1.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
},

View File

@ -1,6 +1,6 @@
import type { Config } from "tailwindcss";
import tailwindcssAnimate from "tailwindcss-animate";
import tailwindcssMotion from "tailwindcss-motion";
export default {
darkMode: ["class"],
content: [
@ -100,5 +100,5 @@ export default {
}
}
},
plugins: [tailwindcssAnimate],
plugins: [tailwindcssAnimate, tailwindcssMotion],
} satisfies Config;