mirror of
https://github.com/i701/sarlink-portal.git
synced 2025-07-07 18:56:29 +00:00
feat: add topup management features including topup creation, cancellation, and countdown timer ✨
This commit is contained in:
37
components/billing/cancel-topup-button.tsx
Normal file
37
components/billing/cancel-topup-button.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
import { Loader2, Trash2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
import { cancelTopup } from "@/actions/payment";
|
||||
import { tryCatch } from "@/utils/tryCatch";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
export default function CancelTopupButton({
|
||||
topupId,
|
||||
}: { topupId: string }) {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
return (
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
const [error, x] = await tryCatch(cancelTopup({ id: topupId }));
|
||||
console.log(x);
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
setLoading(false);
|
||||
} else {
|
||||
toast.success("Topup cancelled successfully!")
|
||||
router.replace("/top-ups");
|
||||
}
|
||||
}}
|
||||
disabled={loading}
|
||||
variant={"destructive"}
|
||||
>
|
||||
Cancel Topup
|
||||
{loading ? <Loader2 className="animate-spin" /> : <Trash2 />}
|
||||
</Button>
|
||||
);
|
||||
}
|
57
components/billing/expiry-time-countdown.tsx
Normal file
57
components/billing/expiry-time-countdown.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
'use client'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
|
||||
const calculateTimeLeft = (expiresAt: string) => {
|
||||
const now = Date.now()
|
||||
const expirationTime = new Date(expiresAt).getTime()
|
||||
return Math.max(0, Math.floor((expirationTime - now) / 1000))
|
||||
}
|
||||
|
||||
const HumanizeTimeLeft = (seconds: number) => {
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const remainingSeconds = seconds % 60
|
||||
return `${minutes}m ${remainingSeconds}s`
|
||||
}
|
||||
|
||||
export default function ExpiryCountDown({ expiresAt }: { expiresAt: string }) {
|
||||
const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(expiresAt))
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setTimeLeft(calculateTimeLeft(expiresAt))
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(timer)
|
||||
}, [expiresAt])
|
||||
|
||||
useEffect(() => {
|
||||
if (timeLeft <= 0) {
|
||||
router.replace(pathname)
|
||||
}
|
||||
}, [timeLeft, router, pathname])
|
||||
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<div className='overflow-clip relative mx-2 p-4 rounded-md border border-dashed flex items-center justify-between text-muted-foreground'>
|
||||
<div className='absolute inset-0 title-bg mask-b-from-0' />
|
||||
{timeLeft ? (
|
||||
<span>Time left: {HumanizeTimeLeft(timeLeft)}</span>
|
||||
) : (
|
||||
<span>Top up has expired. Please make another topup to add balance to your wallet.</span>
|
||||
)}
|
||||
{timeLeft > 0 && (
|
||||
<Progress className='absolute bottom-0 left-0 right-0' color='#f49b5b' value={timeLeft / 600 * 100} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user