mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-06-06 00:46:20 +00:00
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
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 7m23s
This commit is contained in:
parent
c705addccc
commit
bed426a6b4
8
app/(dashboard)/devices-to-pay/loading.tsx
Normal file
8
app/(dashboard)/devices-to-pay/loading.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import FullPageLoader from '@/components/full-page-loader'
|
||||
import React from 'react'
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<FullPageLoader />
|
||||
)
|
||||
}
|
@ -39,7 +39,9 @@ export default async function PaymentPage({
|
||||
>
|
||||
{payment?.paid ? "Paid" : "Pending"}
|
||||
</Button>
|
||||
<CancelPaymentButton paymentId={paymentId} />
|
||||
{!payment.paid && (
|
||||
<CancelPaymentButton paymentId={paymentId} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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={() => {
|
||||
|
@ -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",
|
||||
)}
|
||||
>
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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">
|
||||
|
9
components/full-page-loader.tsx
Normal file
9
components/full-page-loader.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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"
|
||||
|
33
components/welcome-banner.tsx
Normal file
33
components/welcome-banner.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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
10
package-lock.json
generated
@ -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,
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user