diff --git a/apps/bank-webhook/package.json b/apps/bank-webhook/package.json index 6b1d789..55a0da9 100644 --- a/apps/bank-webhook/package.json +++ b/apps/bank-webhook/package.json @@ -15,7 +15,6 @@ "@repo/db": "*", "@types/express": "^4.17.21", "@types/ws": "^8.18.1", - "axios": "^1.13.2", "cors": "^2.8.5", "crypto": "^1.0.1", "esbuild": "^0.25.10", diff --git a/apps/bank-webhook/src/index.ts b/apps/bank-webhook/src/index.ts index 28a6255..d321e9f 100644 --- a/apps/bank-webhook/src/index.ts +++ b/apps/bank-webhook/src/index.ts @@ -3,147 +3,85 @@ import cors from "cors"; import crypto from "crypto"; import db from "@repo/db/client"; import prisma from "@repo/db/client"; -import axios from "axios"; const app = express(); app.use(cors({ origin: ["http://localhost:3001"] })); app.use(express.json()); app.post("/hdfcWebhook", async (req, res) => { - const { token, amount, status, type } = req.body; + const { token, amount, status, type } = req.body; - try { - if (type === "ONRAMP") { - const transaction = await db.onRampTransaction.findUnique({ - where: { token }, - }); - - if (!transaction) { - return res.status(404).json({ message: "Transaction not found" }); - } - - const paymentInformation = { - token, - userId: transaction.userId, - amount: Number(amount), - }; - - const [balanceResult, transactionResult] = await db.$transaction([ - db.balance.upsert({ - where: { userId: paymentInformation.userId }, - update: { amount: { increment: paymentInformation.amount } }, - create: { - userId: paymentInformation.userId, - amount: paymentInformation.amount, - locked: 0, - }, - }), - db.onRampTransaction.update({ - where: { token: paymentInformation.token }, - data: { status: status === "SUCCESS" ? "Success" : "Failure" }, - }), - ]); - - console.log("✅ Balance:", balanceResult.amount); - console.log("✅ Transaction:", transactionResult.status); - } else if (type === "BILL") { - const bill = await db.billSchedule.findUnique({ - where: { token }, - }); - - if (!bill) { - return res.status(404).json({ message: "Bill not found" }); - } - - const billStatus = status === "SUCCESS" ? "PAID" : "OVERDUE"; - await db.billSchedule.update({ - where: { token }, - data: { status: billStatus }, - }); - - console.log("✅ Bill:", bill.id, billStatus); - - const notifyEndpoints = ["http://localhost:3001/api/bills/notify"]; - - for (const endpoint of notifyEndpoints) { - await fetch(endpoint, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ billId: bill.id, status: billStatus, token }), - }); - } - } else if (type === "P2P") { - const transaction = await db.p2pTransfer.findUnique({ - where: { id: token }, - }); - - if (!transaction) - return res.status(404).json({ message: "P2P not found" }); - - if (status === "SUCCESS") { - await db.$transaction([ - db.balance.upsert({ - where: { userId: transaction.toUserId! }, - update: { amount: { increment: transaction.amount } }, - create: { - userId: transaction.toUserId!, - amount: transaction.amount, - locked: 0, - }, - }), - db.p2pTransfer.update({ - where: { id: transaction.id }, - data: { status: "SUCCESS" }, - }), - ]); - } - } else if (type === "FREEZE") { - // Find the wrong-send request by the bank token stored on the wrongSendRequest record. - // (Querying transaction.bankToken failed because p2pTransfer doesn't have that field.) - const request = await db.wrongSendRequest.findFirst({ - where: { txnId : Number(token) }, - include: { - sender: { select: { id: true, name: true, number: true } }, - transaction: true, - }, - }); - - if (!request) return res.status(404).json({ message: "Request not found" }); - - // Safe access (use optional chaining). convert paise -> rupees for message. - const amountRupees = Number(request.amount) / 100; - const sender = (request as any).sender; - const senderName = sender?.name ?? "Someone"; - const senderNumber = sender?.number; - const receiverNumber = request.receiverNumber; - - // Notify receiver via SMS - if (receiverNumber) { - const msg = `${senderName} sent ₹${amountRupees} by mistake. Return in 24 hrs or ₹50 fee: yourapp.com/return/${request.id}`; - await axios.get( - `https://www.fast2sms.com/dev/bulkV2?authorization=${process.env.FAST2SMS}&message=${encodeURIComponent( - msg - )}&numbers=${receiverNumber}` - ); - } - - // Notify sender - if (senderNumber) { - await axios.get( - `https://www.fast2sms.com/dev/bulkV2?authorization=${process.env.FAST2SMS}&message=${encodeURIComponent( - `Bank frozen ₹${amountRupees}. Receiver notified.` - )}&numbers=${senderNumber}` - ); - } - } else { - return res.status(400).json({ message: "Invalid type" }); + try { + if (type === "ONRAMP") { + const transaction = await db.onRampTransaction.findUnique({ + where: { token } + }); + + if (!transaction) { + return res.status(404).json({ message: "Transaction not found" }); + } + + const paymentInformation = { + token, + userId: transaction.userId, + amount: Number(amount) + }; + + const [balanceResult, transactionResult] = await db.$transaction([ + db.balance.upsert({ + where: { userId: paymentInformation.userId }, + update: { amount: { increment: paymentInformation.amount } }, + create: { + userId: paymentInformation.userId, + amount: paymentInformation.amount, + locked: 0 + } + }), + db.onRampTransaction.update({ + where: { token: paymentInformation.token }, + data: { status: status === "SUCCESS" ? "Success" : "Failure" } + }) + ]); + + console.log("✅ Balance:", balanceResult.amount); + console.log("✅ Transaction:", transactionResult.status); + } else if (type === "BILL") { + const bill = await db.billSchedule.findUnique({ + where: { token } + }); + + if (!bill) { + return res.status(404).json({ message: "Bill not found" }); + } + + const billStatus = status === "SUCCESS" ? "PAID" : "OVERDUE"; + await db.billSchedule.update({ + where: { token }, + data: { status: billStatus } + }); + + console.log("✅ Bill:", bill.id, billStatus); + + const notifyEndpoints = [ + "http://localhost:3001/api/bills/notify" + ]; + + for (const endpoint of notifyEndpoints) { + await fetch(endpoint, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ billId: bill.id, status: billStatus, token }) + }); + } + } else { + return res.status(400).json({ message: "Invalid type" }); + } + + res.json({ message: "Captured" }); + } catch (e) { + console.error("❌ Webhook error:", e); + res.status(500).json({ message: "Processing failed" }); } - - res.json({ message: "Captured" }); - } catch (e) { - console.error("❌ Webhook error:", e); - res.status(500).json({ message: "Processing failed" }); - } }); app.post("/webhook/upi-payment", async (req, res) => { @@ -153,9 +91,7 @@ app.post("/webhook/upi-payment", async (req, res) => { return res.status(400).json({ error: "Missing qrId or transactionId" }); } - const payment = await prisma.merchantPayment.findUnique({ - where: { qrId }, - }); + const payment = await prisma.merchantPayment.findUnique({ where: { qrId } }); if (!payment) { return res.status(404).json({ error: "Payment not found" }); } @@ -192,9 +128,7 @@ app.post("/webhook/qr-payment", async (req, res) => { if (event === "qr_code.paid") { const { qr_id, amount, payment_id } = req.body.payload.qr_code.entity; try { - const payment = await prisma.merchantPayment.findUnique({ - where: { qrId: qr_id }, - }); + const payment = await prisma.merchantPayment.findUnique({ where: { qrId: qr_id } }); if (!payment) { return res.status(404).json({ error: "Payment not found" }); } @@ -206,15 +140,11 @@ app.post("/webhook/qr-payment", async (req, res) => { }); // Transfer amount to merchant (simplified, update merchant balance or trigger transfer) - const merchant = await prisma.merchant.findUnique({ - where: { id: payment.merchantId }, - }); + const merchant = await prisma.merchant.findUnique({ where: { id: payment.merchantId } }); // Add logic to update merchant balance or initiate payout if needed // Trigger notification (e.g., via email or push notification) - console.log( - `Payment of ₹${amount / 100} successful for merchant ${merchant?.name}` - ); + console.log(`Payment of ₹${amount / 100} successful for merchant ${merchant?.name}`); res.status(200).json({ status: "success" }); } catch (error) { @@ -231,4 +161,4 @@ app.get("/health", (req, res) => { }); const PORT = process.env.PORT || 3002; -app.listen(PORT, () => console.log(`Webhook backend running on port ${PORT}`)); +app.listen(PORT, () => console.log(`Webhook backend running on port ${PORT}`)); \ No newline at end of file diff --git a/apps/user-app/app/(dashboard)/dashboard/page.tsx b/apps/user-app/app/(dashboard)/dashboard/page.tsx index 5416bb5..d8dcbed 100644 --- a/apps/user-app/app/(dashboard)/dashboard/page.tsx +++ b/apps/user-app/app/(dashboard)/dashboard/page.tsx @@ -1,122 +1,118 @@ -// components/ReturnPendingList.tsx -"use client"; -import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Lock, Eye, EyeOff } from "lucide-react"; -import toast from "react-hot-toast"; -import confetti from "canvas-confetti"; -import formatDistanceToNow from "date-fns/formatDistanceToNow"; -import axios from "axios"; +import { getServerSession } from "next-auth"; +import prisma from "@repo/db/client"; +import { BalanceCard } from "@/components/BalanceCard"; +import { TransferList } from "@/components/TransferList"; +import { OnRampList } from "@/components/OnRampList"; +import { StatsCards } from "@/components/StatsCards"; +import { DashboardClient } from "@/components/DashboardClient"; +import { Avatar, AvatarFallback, AvatarImage } from "../../../../../packages/ui/src/avatar"; +import { Toaster } from "react-hot-toast"; +import { authOptions } from "@/app/lib/auth"; +import { startOfDay, subDays, format } from "date-fns"; -type ReturnItem = { - id: number; - senderName: string; - amount: number; - expiresAt: Date; - timestamp: Date; -}; +async function getDashboardData(userId: number) { + const balance = await prisma.balance.findFirst({ where: { userId } }); -export default function ReturnPendingList({ returns }: { returns: ReturnItem[] }) { - const [selected, setSelected] = useState(null); - const [pin, setPin] = useState(""); - const [showPin, setShowPin] = useState(false); - const [loading, setLoading] = useState(false); + const transfers = await prisma.p2pTransfer.findMany({ + where: { + OR: [{ fromUserId: userId }, { toUserId: userId }], + }, + include: { fromUser: true, toUser: true }, + orderBy: { timestamp: "desc" }, + take: 5, + }); - const handleReturn = async () => { - if (!selected || pin.length !== 4) return; - setLoading(true); - try { - await axios.post("/api/wrong-send/approve", { requestId: selected.id, pin }); - confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); - toast.success(`Returned ₹${selected.amount}!`); - setSelected(null); - setPin(""); - } catch (err: any) { - toast.error(err.response?.data?.error === "Wrong PIN" ? "Wrong PIN" : "Failed"); - setPin(""); - } finally { - setLoading(false); - } + const onRamps = await prisma.onRampTransaction.findMany({ + where: { userId }, + orderBy: { startTime: "desc" }, + take: 5, + }); + + // Aggregate data for chart (last 6 days) + const startDate = startOfDay(subDays(new Date(), 5)); + const endDate = startOfDay(new Date()); + + const sentTransfers = await prisma.p2pTransfer.groupBy({ + by: ["timestamp"], + where: { fromUserId: userId, timestamp: { gte: startDate, lte: endDate } }, + _sum: { amount: true }, + orderBy: { timestamp: "asc" }, + }); + + const receivedTransfers = await prisma.p2pTransfer.groupBy({ + by: ["timestamp"], + where: { toUserId: userId, timestamp: { gte: startDate, lte: endDate } }, + _sum: { amount: true }, + orderBy: { timestamp: "asc" }, + }); + + const onRampTransactions = await prisma.onRampTransaction.groupBy({ + by: ["startTime"], + where: { userId, startTime: { gte: startDate, lte: endDate } }, + _sum: { amount: true }, + orderBy: { startTime: "asc" }, + }); + + // Generate labels and data for chart + const labels = Array.from({ length: 6 }, (_, i) => format(subDays(new Date(), 5 - i), "yyyy-MM-dd")); + const sentData = labels.map((date) => { + const dayData = sentTransfers.find((t) => format(t.timestamp, "yyyy-MM-dd") === date); + return (dayData?._sum?.amount || 0) / 100; + }); + const receivedData = labels.map((date) => { + const dayData = receivedTransfers.find((t) => format(t.timestamp, "yyyy-MM-dd") === date); + return (dayData?._sum?.amount || 0) / 100; + }); + const onRampData = labels.map((date) => { + const dayData = onRampTransactions.find((t) => format(t.startTime, "yyyy-MM-dd") === date); + return (dayData?._sum?.amount || 0) / 100; + }); + + return { + balance: { amount: balance?.amount || 0, locked: balance?.locked || 0 }, + transfers, + onRamps, + chartData: { labels, sentData, receivedData, onRampData }, }; +} - if (!selected) { - return ( -
- {returns.map((r) => ( - setSelected(r)} - > - -
-
-

{r.senderName} sent you

-

₹{r.amount}

-

- Return in {formatDistanceToNow(r.expiresAt, { addSuffix: true })} -

-
- -
-
-
- ))} -
- ); +export default async function DashboardPage() { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return
Please log in to view your dashboard.
; } + const userId = Number(session.user.id); + const { balance, transfers, onRamps } = await getDashboardData(userId); + return ( - - - - - Confirm Return - - - -
-

{selected.senderName} sent you

-

₹{selected.amount}

-

- Expires {formatDistanceToNow(selected.expiresAt, { addSuffix: true })} -

+ +
+ {/* User Greeting */} +
+ + + {session.user.name?.[0] || "U"} + +

+ Welcome, {session.user.name || "User"} +

-
- setPin(e.target.value.slice(0, 4))} - placeholder="••••" - maxLength={4} - className="w-full text-center text-2xl tracking-widest font-mono bg-zinc-700 border border-zinc-600 rounded-lg p-4 text-white" - /> - + {/* Top Section */} +
+ +
-
- - + {/* Transfers & OnRamps */} +
+ +
- - + + +
+ ); } \ No newline at end of file diff --git a/apps/user-app/app/(dashboard)/layout.tsx b/apps/user-app/app/(dashboard)/layout.tsx index 711ddd9..db8ba28 100644 --- a/apps/user-app/app/(dashboard)/layout.tsx +++ b/apps/user-app/app/(dashboard)/layout.tsx @@ -1,6 +1,6 @@ "use client"; import { useSession } from "next-auth/react"; -import { BrickWallFire, Home, LayoutDashboard, Repeat, Send, Wallet,} from 'lucide-react'; +import { BrickWallFire, Home, LayoutDashboard, Repeat, Send,} from 'lucide-react'; import { Avatar, AvatarFallback, @@ -23,7 +23,6 @@ const sidebarLinks: Links[] = [ { label: "Transfer", href: "/transfer", icon: }, { label: "P2P Transfer", href: "/p2p", icon: }, { label: "Bills", href: "/bills", icon: }, - { label: "Recharge", href: "/recharge", icon: }, ]; diff --git a/apps/user-app/app/(dashboard)/recharge/page.tsx b/apps/user-app/app/(dashboard)/recharge/page.tsx deleted file mode 100644 index 4d0814e..0000000 --- a/apps/user-app/app/(dashboard)/recharge/page.tsx +++ /dev/null @@ -1,52 +0,0 @@ - -import { getServerSession } from "next-auth"; -import prisma from "@repo/db/client"; -import { authOptions } from "@/app/lib/auth"; -import RechargeForm from "@/components/RechargeForm"; - -export default async function RechargePage() { - const session = await getServerSession(authOptions); - - if (!session?.user?.id) { - return
Please log in to access recharge.
; - } - - const user = await prisma.user.findUnique({ - where: { id: Number(session.user.id) }, - select: { - id: true, - name: true, - userpin: true, - Balance: { - select: { - amount: true, - locked: true, - }, - }, - }, - }); - - if (!user || !user.Balance || user.Balance.length === 0) { - return
User not found or balance missing.
; - } - - const balanceInRupees = Number(user.Balance[0]?.amount ?? 0) / 100; - - return ( -
-

Mobile Recharge

- - {!user.userpin ? ( -
Please set your transaction PIN before proceeding.
- ) : ( - - )} -
- ); -} \ No newline at end of file diff --git a/apps/user-app/app/(dashboard)/return/[id]/page.tsx b/apps/user-app/app/(dashboard)/return/[id]/page.tsx deleted file mode 100644 index 2992332..0000000 --- a/apps/user-app/app/(dashboard)/return/[id]/page.tsx +++ /dev/null @@ -1,212 +0,0 @@ -'use client'; -import { useEffect, useState, useCallback, useRef } from 'react'; -import axios from 'axios'; -import toast from 'react-hot-toast'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; -import { Timer, AlertTriangle, Lock, Eye, EyeOff, CheckCircle } from 'lucide-react'; -import confetti from 'canvas-confetti'; -import { motion } from 'framer-motion'; - -export default function ReturnPage({ params }: { params: { id: string } }) { - const [request, setRequest] = useState(null); - const [timeLeft, setTimeLeft] = useState(0); - const [showPin, setShowPin] = useState(false); - const [pin, setPin] = useState(''); - const [pinInvalid, setPinInvalid] = useState(false); - const [loading, setLoading] = useState(false); - const [stage, setStage] = useState<'info' | 'pin' | 'success'>('info'); - const pinInputRef = useRef(null); - - // Fetch request - useEffect(() => { - axios.get(`/api/wrong-send/${params.id}`) - .then(res => { - setRequest(res.data); - const diff = new Date(res.data.expiresAt).getTime() - Date.now(); - setTimeLeft(Math.max(0, diff)); - }) - .catch(() => toast.error('Invalid or expired link')); - }, [params.id]); - - // Timer - useEffect(() => { - if (timeLeft <= 0) return; - const timer = setInterval(() => setTimeLeft(t => Math.max(0, t - 1000)), 1000); - return () => clearInterval(timer); - }, [timeLeft]); - - // Focus PIN on open - useEffect(() => { - if (stage === 'pin' && pinInputRef.current) { - pinInputRef.current.focus(); - } - }, [stage]); - - const formatTime = (ms: number) => { - const h = String(Math.floor(ms / 3600000)).padStart(2, '0'); - const m = String(Math.floor((ms % 3600000) / 60000)).padStart(2, '0'); - const s = String(Math.floor((ms % 60000) / 1000)).padStart(2, '0'); - return `${h}:${m}:${s}`; - }; - - const handleReturn = useCallback(() => { - setStage('pin'); - }, []); - - const handlePinConfirm = useCallback(async () => { - if (pin.length !== 4) { - setPinInvalid(true); - return; - } - - setLoading(true); - try { - await axios.post('/api/wrong-send/approve', { requestId: params.id, pin }); - confetti({ particleCount: 120, spread: 70, origin: { y: 0.6 } }); - setStage('success'); - } catch (err: any) { - toast.error(err.response?.data?.error || 'Wrong PIN'); - setPin(''); - setPinInvalid(true); - } finally { - setLoading(false); - } - }, [pin, params.id]); - - if (!request) return ( -
- -

Loading...

-
-
- ); - - if (stage === 'success') return ( -
- - - - ₹{request.amount} Returned! -

Money sent back to {request.senderName}

-
-
-
- ); - - return ( -
- - - {stage === 'info' ? ( - <> - - - - - - {request.senderName} sent you - -

₹{request.amount}

- - by mistake - -
- - -
-
- - {formatTime(timeLeft)} -
-

- Return now or ₹50 fee after 24h -

-
- - -
- - ) : ( - <> - - - - Confirm with PIN - - - Enter your 4-digit PIN to return ₹{request.amount} - - - - -
- { - setPin(e.target.value.slice(0, 4)); - setPinInvalid(false); - }} - placeholder="••••" - maxLength={4} - className={`w-full text-center text-2xl tracking-widest font-mono bg-zinc-700 border ${ - pinInvalid ? 'border-red-500' : 'border-zinc-600' - } rounded-lg p-4 text-white placeholder-zinc-500`} - /> - -
- - {pinInvalid && ( -

Wrong PIN

- )} - -
- - -
-
- - )} -
-
-
- ); -} \ No newline at end of file diff --git a/apps/user-app/app/(dashboard)/rewards/page.tsx b/apps/user-app/app/(dashboard)/rewards/page.tsx deleted file mode 100644 index 6a43b2c..0000000 --- a/apps/user-app/app/(dashboard)/rewards/page.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import RewardsDashboard from '@/components/RewardsDash'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/app/lib/auth'; -import prisma from '@repo/db/client'; - -export const metadata = { title: 'Rewards & Cashback' }; - -export default async function RewardsPage() { - const session = await getServerSession(authOptions); - if (!session?.user?.id) return
Please log in
; - - const user = await prisma.user.findUnique({ - where: { id: Number(session.user.id) }, - }); - - const referral = await prisma.referral.findFirst({ - where: { referrerId: Number(session.user.id) }, - }); - - return ( -
- -
- ); -} \ No newline at end of file diff --git a/apps/user-app/app/api/p2p-history/route.ts b/apps/user-app/app/api/p2p-history/route.ts deleted file mode 100644 index 9f63227..0000000 --- a/apps/user-app/app/api/p2p-history/route.ts +++ /dev/null @@ -1,26 +0,0 @@ - -import { NextResponse } from 'next/server'; -import prisma from '@repo/db/client'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/app/lib/auth'; - -export async function GET() { - const session = await getServerSession(authOptions); - if (!session?.user?.id) return NextResponse.json([]); - - const userId = Number(session.user.id); - - const transactions = await prisma.p2pTransfer.findMany({ - where: { - OR: [{ fromUserId: userId }, { toUserId: userId }], - }, - include: { - fromUser: { select: { id: true, name: true, number: true } }, - toUser: { select: { id: true, name: true, number: true } }, - wrongSendRequest: true, // THIS LINE WAS MISSING - }, - orderBy: { timestamp: 'desc' }, - }); - - return NextResponse.json(transactions); -} \ No newline at end of file diff --git a/apps/user-app/app/api/recharge/history/route.ts b/apps/user-app/app/api/recharge/history/route.ts deleted file mode 100644 index a2d5d85..0000000 --- a/apps/user-app/app/api/recharge/history/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import prisma from "@repo/db/client"; - - -export async function GET(req: Request){ - const { searchParams } = new URL(req.url); - const userId = searchParams.get("userId"); - - if(!userId){ - return new Response(JSON.stringify({error: "missing userId"}), {status: 400}); - } - try{ - const history = await prisma.rechargeOrder.findMany({ - where: { userId : parseInt(userId) }, - include: { - plan: true - }, - orderBy: { createdAt: 'desc' } - }); - return new Response(JSON.stringify(history), {status: 200}); - }catch(e){ - console.error("Error fetching recharge history:", e); - return new Response(JSON.stringify({error: "Failed to fetch recharge history"}), {status: 500}); - } -} \ No newline at end of file diff --git a/apps/user-app/app/api/recharge/initiate/route.ts b/apps/user-app/app/api/recharge/initiate/route.ts deleted file mode 100644 index 7e33580..0000000 --- a/apps/user-app/app/api/recharge/initiate/route.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { NextResponse } from "next/server"; -import db from "@repo/db/client"; -import { getServerSession } from "next-auth"; -import { authOptions } from "@/app/lib/auth"; -import { triggerRechargeRewards } from "@/app/lib/rewards"; -import prisma from "@repo/db/client"; - -export async function POST(req: Request) { - const session = await getServerSession(authOptions); - if (!session?.user?.id) { - return NextResponse.json({ success: false, error: "Unauthorized" }, { status: 401 }); - } - - try { - const body = await req.json(); - const { - userId, - mobileNumber, - operator, - circle, - planId, - userpin, - } = body; - - // ---------- validation ---------- - if (!userId || !mobileNumber || !operator || !circle || !planId || !userpin) { - return NextResponse.json({ success: false, error: "All fields required" }, { status: 400 }); - } - if (mobileNumber.length !== 10) { - return NextResponse.json({ success: false, error: "Invalid mobile number" }, { status: 400 }); - } - - // ---------- user + balance ---------- - const user = await db.user.findUnique({ - where: { id: Number(userId) }, - select: { id: true, Balance: { select: { amount: true } }, userpin: true }, - }); - if (!user) return NextResponse.json({ success: false, error: "User not found" }, { status: 404 }); - if (user.userpin !== userpin) return NextResponse.json({ success: false, error: "Incorrect PIN" }, { status: 403 }); - - const plan = await db.rechargePlan.findUnique({ - where: { id: planId }, - }); - if (!plan || plan.operator !== operator || plan.circle !== circle) { - return NextResponse.json({ success: false, error: "Invalid plan" }, { status: 400 }); - } - - const balanceInPaise = Number(user.Balance[0]?.amount ?? 0); - if (balanceInPaise < plan.amount * 100) { - return NextResponse.json({ success: false, error: "Insufficient balance" }, { status: 400 }); - } - - // ---------- transaction ---------- - const [order] = await db.$transaction([ - db.rechargeOrder.create({ - data: { - userId: Number(userId), - mobileNumber, - operator, - circle, - amount: plan.amount, - status: "SUCCESS", - planId: plan.id, - orderId: `RECH-${Date.now()}-${Math.floor(Math.random() * 1000)}`, - }, - }), - db.user.update({ - where: { id: Number(userId) }, - data: { Balance: { updateMany: { where: {}, data: { amount: { decrement: plan.amount * 100 } } } } }, - }), - ]); - const isFirstRecharge = !(await prisma.rechargeOrder.findFirst({ where: { userId: Number(userId) } })); - await triggerRechargeRewards(Number(userId), plan.amount, isFirstRecharge); - - // ---------- return new balance ---------- - const newBalance = (balanceInPaise - plan.amount * 100) / 100; - - return NextResponse.json({ - success: true, - message: "Recharge successful!", - newBalance, // <-- NEW - orderId: order.id, - }); - } catch (e) { - console.error(e); - return NextResponse.json({ success: false, error: "Server error" }, { status: 500 }); - } -} \ No newline at end of file diff --git a/apps/user-app/app/api/recharge/plans/route.ts b/apps/user-app/app/api/recharge/plans/route.ts deleted file mode 100644 index 5804e14..0000000 --- a/apps/user-app/app/api/recharge/plans/route.ts +++ /dev/null @@ -1,7 +0,0 @@ -import prisma from "@repo/db/client"; -import { NextResponse } from "next/server"; - -export async function GET() { - const plans = await prisma.rechargePlan.findMany(); - return NextResponse.json({ plans }); -} diff --git a/apps/user-app/app/api/rewards/redeem/route.ts b/apps/user-app/app/api/rewards/redeem/route.ts deleted file mode 100644 index 7d7c699..0000000 --- a/apps/user-app/app/api/rewards/redeem/route.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { NextResponse } from 'next/server'; -import prisma from '@repo/db/client'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/app/lib/auth'; - -const REDEEM_OPTIONS = [ - { id: 'amazon100', name: '₹100 Amazon', points: 10000, cashback: 10000 }, - { id: 'flipkart50', name: '₹50 Flipkart', points: 5000, cashback: 5000 }, -]; - -export async function POST(req: Request) { - const session = await getServerSession(authOptions); - if (!session?.user?.id) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - - const { optionId } = await req.json(); - const userId = Number(session.user.id); - - const option = REDEEM_OPTIONS.find(o => o.id === optionId); - if (!option) return NextResponse.json({ error: 'Invalid option' }, { status: 400 }); - - // Fetch user (no select of non-existent fields) - const user = await prisma.user.findUnique({ where: { id: userId } }); - if (!user) return NextResponse.json({ error: 'User not found' }, { status: 404 }); - - // Determine available points: - // 1) If user object has a rewardPoints property at runtime, use it. - // 2) Otherwise, fall back to summing eligible reward records (conservative). - let availablePoints = 0; - if ((user as any).rewardPoints !== undefined) { - availablePoints = Number((user as any).rewardPoints || 0); - } else { - // Aggregate sum of reward.amount for POINTS-type available rewards (adjust filters to your schema) - const agg = await prisma.reward.aggregate({ - where: { - userId, - type: 'CASHBACK', // adjust if your project uses different enum/value - status: 'CLAIMED', // adjust if needed - }, - _sum: { amount: true }, - }); - availablePoints = Number(agg._sum.amount ?? 0); - } - - if (availablePoints < option.points) { - return NextResponse.json({ error: 'Insufficient points' }, { status: 400 }); - } - - // cashback is stored in paise (int) - const cashbackPaise = Number(option.cashback); - - // Build transaction operations: - const ops: any[] = []; - - // 1) Deduct points: if user model actually has rewardPoints, update it safely using `any` to avoid TS-select errors. - if ((user as any).rewardPoints !== undefined) { - // use (prisma as any) to bypass TypeScript compile-time model mismatch - ops.push( - (prisma as any).user.update({ - where: { id: userId }, - data: { - rewardPoints: { decrement: option.points }, - // update totalCashbackEarned only if field exists at runtime - ...(('totalCashbackEarned' in user) ? { totalCashbackEarned: { increment: cashbackPaise } } : {}), - }, - }) - ); - } else { - // If points are tracked via reward records, create a "point spend" record to reflect deduction. - ops.push( - prisma.reward.create({ - data: { - userId, - type: 'POINT_REDEEM', // use a type your schema supports or adapt - amount: -option.points, - status: 'CLAIMED', - metadata: { redeemed: option.id }, - } as any, - }) - ); - } - - // 2) Credit cashback to user's balance (safe typed update) - ops.push( - prisma.balance.update({ - where: { userId }, - data: { amount: { increment: cashbackPaise } }, - }) - ); - - // 3) Create a reward/transaction record for cashback - ops.push( - prisma.reward.create({ - data: { - userId, - type: 'CASHBACK', - amount: cashbackPaise, - status: 'CLAIMED', - metadata: { redeem: option.name }, - } as any, - }) - ); - - // Run transaction - try { - await prisma.$transaction(ops); - return NextResponse.json({ success: true, option: option.name }); - } catch (err: any) { - console.error('Redeem error:', err); - return NextResponse.json({ error: 'Redeem failed' }, { status: 500 }); - } -} \ No newline at end of file diff --git a/apps/user-app/app/api/rewards/route.ts b/apps/user-app/app/api/rewards/route.ts deleted file mode 100644 index 820face..0000000 --- a/apps/user-app/app/api/rewards/route.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { NextResponse } from 'next/server'; -import prisma from '@repo/db/client'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/app/lib/auth'; - -const PAGE_SIZE = 10; - -export async function GET(req: Request) { - const session = await getServerSession(authOptions); - if (!session?.user?.id) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - - const { searchParams } = new URL(req.url); - const page = Number(searchParams.get('page') || 1); - const userId = Number(session.user.id); - - const [rewards, count, summary] = await Promise.all([ - prisma.reward.findMany({ - where: { userId }, - orderBy: { earnedAt: 'desc' }, - skip: (page - 1) * PAGE_SIZE, - take: PAGE_SIZE, - }), - prisma.reward.count({ where: { userId } }), - prisma.user.findUnique({ - where: { id: userId }, - select: { number: true }, // Use an existing property from your user schema - }), - ]); - - const scratchCount = await prisma.reward.count({ - where: { userId, type: 'SCRATCH', status: 'PENDING' }, - }); - - return NextResponse.json({ - rewards, - pagination: { page, total: count, pages: Math.ceil(count / PAGE_SIZE) }, - summary: { - totalEarned: 0, // Set to 0 or fetch from another source if needed - points: summary?.number || 0, - scratchCardsLeft: scratchCount, - }, - }); -} \ No newline at end of file diff --git a/apps/user-app/app/api/rewards/scratch/route.ts b/apps/user-app/app/api/rewards/scratch/route.ts deleted file mode 100644 index 425b54c..0000000 --- a/apps/user-app/app/api/rewards/scratch/route.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { NextResponse } from 'next/server'; -import prisma from '@repo/db/client'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/app/lib/auth'; - -const SCRATCH_AMOUNTS = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]; - -export async function POST() { - const session = await getServerSession(authOptions); - if (!session?.user?.id) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - - const userId = Number(session.user.id); - - const pendingScratch = await prisma.reward.findFirst({ - where: { userId, type: 'SCRATCH', status: 'PENDING' }, - }); - - if (!pendingScratch) { - return NextResponse.json({ error: 'No scratch cards left' }, { status: 400 }); - } - - const winAmount = SCRATCH_AMOUNTS[Math.floor(Math.random() * SCRATCH_AMOUNTS.length)]; - if (!winAmount) { - return NextResponse.json({ error: 'Error determining win amount' }, { status: 500 }); - } - - // Use number (paise) because Balance.amount is an Int in schema - const winPaise = winAmount * 100; - - // Update reward and balance in a transaction. - // Update Balance via the balance model (where userId) instead of nested update on User. - const [updatedReward, updatedBalance] = await prisma.$transaction([ - prisma.reward.update({ - where: { id: pendingScratch.id }, - data: { status: 'CLAIMED', amount: winPaise }, - }), - prisma.balance.update({ - where: { userId }, - data: { amount: { increment: winPaise } }, - }), - ]); - - return NextResponse.json({ - success: true, - winAmount, - newBalance: updatedBalance.amount / 100, - }); -} \ No newline at end of file diff --git a/apps/user-app/app/api/simulate-freeze/route.ts b/apps/user-app/app/api/simulate-freeze/route.ts deleted file mode 100644 index 762e391..0000000 --- a/apps/user-app/app/api/simulate-freeze/route.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NextResponse } from 'next/server'; -import axios from 'axios'; - -export async function POST(req: Request) { - const body = await req.json(); - // Simulate bank freeze - setTimeout(() => { - axios.post('http://localhost:3002/hdfcWebhook', { - token: body.token, - amount: body.amount, - status: 'FREEZE_SUCCESS', - type: 'FREEZE' - }); - }, 1000); - return NextResponse.json({ ok: true }); -} \ No newline at end of file diff --git a/apps/user-app/app/api/webhooks/razorpay/route.ts b/apps/user-app/app/api/webhooks/razorpay/route.ts deleted file mode 100644 index 2397dd2..0000000 --- a/apps/user-app/app/api/webhooks/razorpay/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NextResponse } from 'next/server'; -import prisma from '@repo/db/client'; -import crypto from 'crypto'; - -export async function POST(req: Request) { - const body = await req.text(); - const signature = req.headers.get('x-razorpay-signature')!; - - const expected = crypto.createHmac('sha256', process.env.RAZORPAY_WEBHOOK_SECRET!) - .update(body).digest('hex'); - - if (signature !== expected) return new NextResponse('Invalid', { status: 400 }); - - const event = JSON.parse(body); - if (event.event === 'refund.processed') { - const refundId = event.payload.refund.entity.id; - await prisma.wrongSendRequest.updateMany({ - where: { razorpayRefundId: refundId }, - data: { status: 'RETURNED' }, - }); - } - - return new NextResponse('OK'); -} \ No newline at end of file diff --git a/apps/user-app/app/api/wrong-send/[id]/route.ts b/apps/user-app/app/api/wrong-send/[id]/route.ts deleted file mode 100644 index df19c57..0000000 --- a/apps/user-app/app/api/wrong-send/[id]/route.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NextResponse } from 'next/server'; -import prisma from '@repo/db/client'; - -export async function GET(_: Request, { params }: { params: { id: string } }) { - const request = await prisma.wrongSendRequest.findUnique({ - where: { id: Number(params.id) }, - include: { - sender: { select: { name: true } }, - transaction: { select: { amount: true } }, - }, - }); - - if (!request) return NextResponse.json({ error: 'Not found' }, { status: 404 }); - - return NextResponse.json({ - senderName: request.sender.name, - amount: Number(request.amount) / 100, - expiresAt: request.expiresAt, - }); -} \ No newline at end of file diff --git a/apps/user-app/app/api/wrong-send/approve/route.ts b/apps/user-app/app/api/wrong-send/approve/route.ts deleted file mode 100644 index e79aaaa..0000000 --- a/apps/user-app/app/api/wrong-send/approve/route.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { NextResponse } from 'next/server'; -import prisma from '@repo/db/client'; -import bcrypt from 'bcrypt'; - -export async function POST(req: Request) { - const { requestId, pin } = await req.json(); - - const request = await prisma.wrongSendRequest.findUnique({ - where: { id: Number(requestId) }, - include: { - transaction: { - include: { - // schema field is `userpin` — select that instead of non-existent `pin` - toUser: { select: { id: true, userpin: true } }, // hashed PIN - }, - }, - }, - }); - - if (!request || request.status !== 'PENDING') { - return NextResponse.json({ error: 'Invalid' }, { status: 400 }); - } - - // HASHED PIN COMPARISON - const hashedPin = request.transaction.toUser?.userpin; - if (!hashedPin) { - return NextResponse.json({ error: 'Receiver PIN not set' }, { status: 400 }); - } - const isValid = await bcrypt.compare(pin, hashedPin); - if (!isValid) { - return NextResponse.json({ error: 'Wrong PIN' }, { status: 401 }); - } - - await prisma.$transaction([ - prisma.wrongSendRequest.update({ - where: { id: request.id }, - data: { status: 'RETURNED' }, - }), - prisma.p2pTransfer.update({ - where: { id: request.txnId }, - data: { status: 'REFUNDED' }, - }), - prisma.balance.upsert({ - where: { userId: request.transaction.fromUserId }, - update: { amount: { increment: Number(request.amount) } }, - create: { userId: request.transaction.fromUserId, amount: Number(request.amount), locked: 0 }, - }), - ]); - - return NextResponse.json({ success: true }); -} \ No newline at end of file diff --git a/apps/user-app/app/api/wrong-send/route.ts b/apps/user-app/app/api/wrong-send/route.ts deleted file mode 100644 index 05f773e..0000000 --- a/apps/user-app/app/api/wrong-send/route.ts +++ /dev/null @@ -1,43 +0,0 @@ - -import { NextResponse } from 'next/server'; -import prisma from '@repo/db/client'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/app/lib/auth'; - -export async function POST(req: Request) { - const session = await getServerSession(authOptions); - if (!session?.user?.id) return NextResponse.redirect(new URL('/login', req.url)); - - const formData = await req.formData(); - const txnId = Number(formData.get('txnId')); - - const txn = await prisma.p2pTransfer.findUnique({ - where: { id: txnId }, - include: { fromUser: true }, - }); - - if (!txn || txn.fromUserId !== Number(session.user.id) || txn.status !== 'SUCCESS') { - return NextResponse.redirect(new URL('/p2p?error=invalid', req.url)); - } - - const existing = await prisma.wrongSendRequest.findUnique({ where: { txnId } }); - if (existing) return NextResponse.redirect(new URL('/p2p?error=already', req.url)); - - await prisma.p2pTransfer.update({ - where: { id: txnId }, - data: { status: 'REFUND_REQUESTED' } - }); - - await prisma.wrongSendRequest.create({ - data: { - txnId, - senderId: Number(session.user.id), - receiverNumber: txn.receiverNumber || 'Unknown', - amount: BigInt(txn.amount), - status: 'PENDING', - expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), - }, - }); - - return NextResponse.redirect(new URL('/p2p?refund=requested', req.url)); -} \ No newline at end of file diff --git a/apps/user-app/app/globals.css b/apps/user-app/app/globals.css index da4574a..f5fdcb9 100644 --- a/apps/user-app/app/globals.css +++ b/apps/user-app/app/globals.css @@ -30,19 +30,6 @@ scrollbar-width: none; /* Firefox */ } -@keyframes fadeInZoom { - from { - opacity: 0; - transform: scale(0.8); - } - to { - opacity: 1; - transform: scale(1); - } -} -.animate-in { - animation: fadeInZoom 0.3s ease-out; -} @layer utilities { .mask-radial-at-bottom { -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 30%, black 70%, transparent 100%); diff --git a/apps/user-app/app/home/page.tsx b/apps/user-app/app/home/page.tsx index a390f44..f6a0232 100644 --- a/apps/user-app/app/home/page.tsx +++ b/apps/user-app/app/home/page.tsx @@ -30,10 +30,6 @@ import pic13 from "@/public/four.png"; import pic14 from "@/public/three.png"; import pic15 from "@/public/two.png"; import { PointerHighlight } from "../../components/ui/pointer-highlight"; -import { BackgroundBeamsWithCollision } from "@/components/ui/background-beams-with-collision"; -import { HeroParallax } from "@/components/ui/hero-parallax"; -import { ShootingStars } from "@/components/ui/shooting-stars"; -import { StarsBackground } from "@/components/ui/stars-background"; const Page = () => { const navItems = [ @@ -136,113 +132,85 @@ const Page = () => { description: "PCI DSS & GDPR certified", }, ]; - const products = [ - { - title: "Renderwork Studio", - link: "https://renderwork.studio", - thumbnail: pic11.src, - }, - { - title: "Creme Digital", - link: "https://cremedigital.com", - thumbnail: pic12.src, - }, - { - title: "Invoker Labs", - link: "https://invoker.lol", - thumbnail: pic14.src, - }, - { - title: "Golden Bells Academy", - link: "https://goldenbellsacademy.com", - thumbnail: pic13.src, - }, - - { - title: "E Free Invoice", - link: "https://efreeinvoice.com", - thumbnail: pic15.src, - }, - { - title: "Moonbeam", - link: "https://gomoonbeam.com", - thumbnail: pic14.src, - }, - - { - title: "Algochurn", - link: "https://algochurn.com", - thumbnail: pic7.src, - }, - { - title: "Aceternity UI", - link: "https://ui.aceternity.com", - thumbnail: pic8.src, - }, - - { - title: "Cursor", - link: "https://cursor.so", - thumbnail: pic2.src, - }, - { - title: "Rogue", - link: "https://userogue.com", - thumbnail: pic3.src, - }, - ]; const { status } = useSession(); const router = useRouter(); return ( -
+
{/* Floating Navbar */} {/* Background with content */} - -
-
-

- — with - confidence. -

+
+
+

+ — with + confidence. +

-

- Experience effortless payments built for security and speed. - CalxSecure empowers you to handle every transaction with total - trust. +

+ Experience effortless payments built for security and speed. + CalxSecure empowers you to handle every transaction with total + trust. +

+ + {status === "authenticated" && ( + + )} + {status === "unauthenticated" && ( + + )} + +
+ + +
+ + {/* Features Section */} +
+
+ +
+
+

+ Powerful Features for Modern Finance +

+ +

+ CalxSecure handles everything from peer-to-peer transactions to + automated bank webhooks — built to scale beyond a million users.

- - {status === "authenticated" && ( - - )} - {status === "unauthenticated" && ( - - )} -
+ +
- +
{/* Security Section */} -
-
- {/* Header */} +
+ {/* Background Pattern */} +
+
+
-
+
+ {/* Header */} +
🔒 Bank-Grade Protection

Enterprise-Level Security

-

+

Your funds are protected with military-grade encryption, AI-powered fraud detection, and 24/7 monitoring—trusted by millions worldwide. @@ -250,7 +218,7 @@ const Page = () => {

{/* Features Grid */} -
+
{S1.map((item, index) => ( { viewport={{ once: true }} className="group relative" > -
+
{/* Icon */}
@@ -271,30 +239,28 @@ const Page = () => {
{/* Content */} -

+

{item.title}

-

+

{item.description || "Advanced protection"}

+ +
))} - -
+
-
-

- Members -

+
{/* CTA Section */} -
+
Ready to Get @@ -304,7 +270,7 @@ const Page = () => { Join thousands of users and merchants powering their payments with CalxSecure

- + {status === "authenticated" && (
{/* Footer */} -
+

© 2025 CalxSecure. All rights reserved.

diff --git a/apps/user-app/app/layout.tsx b/apps/user-app/app/layout.tsx index 3966af6..bf0e305 100644 --- a/apps/user-app/app/layout.tsx +++ b/apps/user-app/app/layout.tsx @@ -3,7 +3,6 @@ import "./globals.css"; import type { Metadata } from "next"; import { Inter } from "next/font/google"; import { Providers } from "../provider"; -import { ThemeProvider } from "../components/ui/theme-provider"; const inter = Inter({ subsets: ["latin"] }); @@ -22,13 +21,11 @@ export default function RootLayout({ children: React.ReactNode; }): JSX.Element { return ( - + -
- - {children} - +
+ {children}
diff --git a/apps/user-app/app/lib/rewards.ts b/apps/user-app/app/lib/rewards.ts deleted file mode 100644 index f07cf11..0000000 --- a/apps/user-app/app/lib/rewards.ts +++ /dev/null @@ -1,41 +0,0 @@ -import prisma from '@repo/db/client'; - -export async function triggerRechargeRewards(userId: number, rechargeAmount: number, isFirst: boolean) { - const cashback = Math.floor(rechargeAmount * 0.05); - const cashbackPaise = Math.floor(cashback * 100); // use number (Int) — Prisma Int expects number - - await prisma.$transaction([ - // 1) Cashback reward record - prisma.reward.create({ - data: { - userId, - type: 'CASHBACK', - amount: cashbackPaise, - status: 'CLAIMED', - metadata: { rechargeAmount }, - }, - }), - - // 2) Credit the user's Balance (update Balance model directly — Balance is a separate model) - prisma.balance.update({ - where: { userId }, - data: { amount: { increment: cashbackPaise } }, - }), - - // 4) Optional: first recharge scratch reward - ...(isFirst - ? [ - prisma.reward.create({ - data: { - userId, - type: 'SCRATCH', - amount: 0, // placeholder - status: 'PENDING', - expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), - metadata: { name: 'Welcome Scratch' }, - }, - }), - ] - : []), - ]); -} \ No newline at end of file diff --git a/apps/user-app/components/P2PTransactionHistory.tsx b/apps/user-app/components/P2PTransactionHistory.tsx index fe70116..7a22b53 100644 --- a/apps/user-app/components/P2PTransactionHistory.tsx +++ b/apps/user-app/components/P2PTransactionHistory.tsx @@ -1,133 +1,112 @@ -"" -import React from "react"; -import { getP2PTransactions } from "@/app/lib/actions/getP2PTransactions"; -import WrongSendModal from "./WrongSendModal"; -import { Button } from "@/components/ui/button"; -import { AlertTriangle, Clock } from "lucide-react"; +"use client"; +import { useEffect, useState } from "react"; +import { Clock, ArrowRight, ArrowLeft, AlertCircle } from "lucide-react"; -export async function P2PTransactionHistory() { - const transactions = await getP2PTransactions(); +export function P2PTransactionHistory({ reload }: { reload: number }) { + const [transactions, setTransactions] = useState([]); + const [requests, setRequests] = useState([]); + const [loading, setLoading] = useState(true); - // ------------------------------------------------- - // 1. Get the current userId from session (same way the API does) - // ------------------------------------------------- - const { getServerSession } = await import("next-auth"); - const { authOptions } = await import("@/app/lib/auth"); - const session = await getServerSession(authOptions); - const currentUserId = session?.user?.id ? Number(session.user.id) : null; + const fetchData = async () => { + setLoading(true); + const [transRes, reqRes] = await Promise.all([ + fetch("/api/p2p-history"), + fetch("/api/p2p-requests") + ]); + const transactions = await transRes.json(); + const requests = await reqRes.json(); + setTransactions(transactions); + setRequests(requests); + setLoading(false); + }; - if (!transactions.length) { - return
No transactions found.
; - } + useEffect(() => { + fetchData(); + // eslint-disable-next-line + }, [reload]); - // ------------------------------------------------- - // 2. Helper – open modal for a specific txn - // ------------------------------------------------- - const openWrongSend = (txnId: number, amountRupees: number) => { - // We render the modal directly in this server component. - // The modal itself is a client component, so we use a tiny wrapper. - // (Next‑13+ allows client components inside server components.) - return ( - {}} - txnId={txnId} - amount={amountRupees} - /> - ); - }; + if (loading) return
Loading...
; + if (!transactions.length && !requests.length) return
No transactions or requests found.
; return ( -
-

- Transaction History +
+

+ + P2P History

-
- - - - - - - - - - - - - {transactions.map((tx) => { - const t = tx as any; - const isOutgoing = currentUserId === tx.fromUserId; - const canReport = - isOutgoing && tx.status === "SUCCESS" && !t.wrongSendRequest; + {/* 🚨 NEW: PENDING REQUESTS SECTION */} + {requests.length > 0 && ( +
+

+ + Pending Requests ({requests.length}) +

+
+ {requests.map(req => ( +
+
+

+ {req.status === "PENDING" ? "Request" : "Sent"} ₹{(req.amount/100).toFixed(0)} +

+

+ {req.sender?.name || req.senderNumber} • {new Date(req.createdAt).toLocaleDateString()} +

+ {req.message &&

{req.message}

} +
+ + {req.status} + +
+ ))} +
+
+ )} - return ( - - {/* DATE */} - +
DateFromToAmountAction
- {new Date(tx.timestamp).toLocaleString()} + {/* 🚨 YOUR ORIGINAL: COMPLETED TRANSFERS TABLE */} + {transactions.length > 0 && ( + <> +

Completed Transfers

+ + + + + + + + + + + {transactions.map(tx => ( + + + - - {/* FROM */} - - - {/* TO */} - - - {/* AMOUNT */} - - - {/* ACTION */} - - ); - })} - -
DateFromToAmount
{new Date(tx.timestamp).toLocaleString()} + + {tx.fromUser?.name || tx.fromUser?.number} + {tx.fromUserId === Number(localStorage.getItem('userId')) && } + - {tx.fromUser?.name || tx.fromUser?.number} + + + {tx.toUser?.name || tx.toUser?.number} + {tx.toUserId === Number(localStorage.getItem('userId')) && } + - {tx.toUser?.name || tx.toUser?.number} - + ₹{(tx.amount / 100).toFixed(0)} - {canReport ? ( - - ) : t.wrongSendRequest ? ( - - - Refund Requested - - ) : null} -
- + ))} +
+ + )}
); } \ No newline at end of file diff --git a/apps/user-app/components/P2pTransationsHistory.tsx b/apps/user-app/components/P2pTransationsHistory.tsx index d1b7c8c..0bace90 100644 --- a/apps/user-app/components/P2pTransationsHistory.tsx +++ b/apps/user-app/components/P2pTransationsHistory.tsx @@ -1,90 +1,36 @@ +import React from "react"; import { getP2PTransactions } from "@/app/lib/actions/getP2PTransactions"; -import { Button } from "@/components/ui/button"; -import { AlertTriangle, Clock } from "lucide-react"; export async function P2PTransactionHistory() { const transactions = await getP2PTransactions(); - const { getServerSession } = await import("next-auth"); - const { authOptions } = await import("@/app/lib/auth"); - const session = await getServerSession(authOptions); - const currentUserId = session?.user?.id ? Number(session.user.id) : null; - if (!transactions.length) { return
No transactions found.
; } return (
-

- Transaction History -

- -
- - - - - - - - +

Transaction History

+
DateFromToAmountAction
+ + + + + + + + + + {transactions.map(tx => ( + + + + + - - - {transactions.map((tx) => { - const t = tx as any; - const isOutgoing = currentUserId === tx.fromUserId; - const canReport = isOutgoing && tx.status === "SUCCESS" && !t.wrongSendRequest; - - return ( - - - - - - - - ); - })} - -
DateFromToAmount
{new Date(tx.timestamp).toLocaleString()}{tx.fromUser?.name || tx.fromUser?.number}{tx.toUser?.name || tx.toUser?.number}{(tx.amount / 100).toFixed(2)} INR
- {new Date(tx.timestamp).toLocaleString()} - - {tx.fromUser?.name || tx.fromUser?.number} - - {tx.toUser?.name || tx.toUser?.number} - - ₹{(tx.amount / 100).toFixed(0)} - - {canReport ? ( -
- - -
- ) : t.wrongSendRequest ? ( - - - Refund Requested - - ) : null} -
-
+ ))} + +
); } \ No newline at end of file diff --git a/apps/user-app/components/RechargeForm.tsx b/apps/user-app/components/RechargeForm.tsx deleted file mode 100644 index a881f08..0000000 --- a/apps/user-app/components/RechargeForm.tsx +++ /dev/null @@ -1,259 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import axios from "axios"; -import toast from "react-hot-toast"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { - Select, - SelectItem, - SelectTrigger, - SelectValue, - SelectContent, -} from "@/components/ui/select"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogFooter, -} from "@/components/ui/dialog"; - -import { CheckCircle, Wallet, Loader2 } from "lucide-react"; -import { Label } from "../../../packages/ui/src/label"; - -interface Plan { id: number; amount: number; description: string; validity: string; } -interface Props { user: { id: number; balance: number; userpin: string; }; } - -export default function RechargeForm({ user }: Props) { - const [balance, setBalance] = useState(user.balance); - const [showSuccess, setShowSuccess] = useState(false); - const [operator, setOperator] = useState(""); - const [circle, setCircle] = useState(""); - const [mobile, setMobile] = useState(""); - const [plans, setPlans] = useState([]); - const [loading, setLoading] = useState(false); - const [selectedPlan, setSelectedPlan] = useState(null); - const [showPlanDialog, setShowPlanDialog] = useState(false); - const [showPinDialog, setShowPinDialog] = useState(false); - const [pinInput, setPinInput] = useState(""); - const [pinError, setPinError] = useState(""); - - // ---------- FETCH PLANS ---------- - useEffect(() => { - if (!operator || !circle) { setPlans([]); return; } - (async () => { - setLoading(true); - try { - const { data } = await axios.get("/api/recharge/plans", { params: { operator, circle } }); - setPlans(data?.plans ?? []); - } catch { toast.error("Failed to load plans."); } - finally { setLoading(false); } - })(); - }, [operator, circle]); - - // ---------- PLAN SELECTION ---------- - const selectPlan = (plan: Plan) => { - if (!mobile || mobile.length !== 10) return toast.error("Enter a valid 10-digit mobile number."); - if (balance < plan.amount) return toast.error(`Insufficient balance. Need ₹${plan.amount}`); - setSelectedPlan(plan); - setShowPlanDialog(true); - }; - - // ---------- CONFIRM → PIN ---------- - const proceedToPin = () => { - setShowPlanDialog(false); - setShowPinDialog(true); - setPinInput(""); - setPinError(""); - }; - - // ---------- FINAL RECHARGE ---------- - const confirmRecharge = async () => { - if (pinInput !== user.userpin) return setPinError("Incorrect PIN"); - if (!selectedPlan) return; - - setLoading(true); - try { - const res = await axios.post("/api/recharge/initiate", { - userId: user.id, - mobileNumber: mobile, - operator, - circle, - planId: selectedPlan.id, - userpin: pinInput, - }); - - if (res.data.success) { - toast.success(`Recharge of ₹${selectedPlan.amount} successful!`); - setBalance(res.data.newBalance); - setShowPinDialog(false); - setMobile(""); - setPinInput(""); - setSelectedPlan(null); - setShowSuccess(true); - setTimeout(() => setShowSuccess(false), 2500); - } else { - toast.error(res.data.error ?? "Recharge failed"); - } - } catch (e: any) { - toast.error(e.response?.data?.error ?? "Recharge failed"); - } finally { - setLoading(false); - } - }; - - return ( -
- {/* ---------- HEADER ---------- */} -
-

Mobile Recharge

-
- - Balance: ₹{balance.toFixed(2)} -
-
- - {/* ---------- INPUT ROW ---------- */} -
- setMobile(e.target.value.replace(/\D/g, "").slice(0, 10))} - maxLength={10} - className="bg-zinc-900 text-zinc-100 border-zinc-800 placeholder:text-zinc-500" - /> - - - - -
- - {/* ---------- PLANS GRID ---------- */} - {loading ? ( -
- ) : plans.length ? ( -
- {plans.map(p => ( - - -
-

₹{p.amount}

-

{p.description}

-

Validity: {p.validity}

-
- -
-
- ))} -
- ) : ( -

Select operator & circle to view plans.

- )} - - {/* ---------- CONFIRM DIALOG ---------- */} - - - - Confirm Recharge - - {selectedPlan && ( -
-
Mobile{mobile}
-
Operator{operator}
-
Circle{circle}
-
Plan₹{selectedPlan.amount} – {selectedPlan.validity}
-
Total₹{selectedPlan.amount}
-
- )} - - - - -
-
- - {/* ---------- PIN DIALOG ---------- */} - - - - Enter Transaction PIN - -
- - { setPinInput(e.target.value.replace(/\D/g, "").slice(0,4)); setPinError(""); }} - className="text-center text-2xl tracking-widest bg-zinc-800 border-zinc-700" - placeholder="••••" - autoFocus - /> - {pinError &&

{pinError}

} -
- - - - -
-
- - {/* ---------- SUCCESS OVERLAY ---------- */} - {showSuccess && ( -
-
- -

Recharge Successful!

-

- ₹{selectedPlan?.amount} credited to {mobile} -

- -
-
- )} -
- ); -} \ No newline at end of file diff --git a/apps/user-app/components/ReturnPendingList.tsx b/apps/user-app/components/ReturnPendingList.tsx deleted file mode 100644 index d6c52ba..0000000 --- a/apps/user-app/components/ReturnPendingList.tsx +++ /dev/null @@ -1,152 +0,0 @@ -"use client"; -import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Lock, Eye, EyeOff, CheckCircle } from "lucide-react"; -import toast from "react-hot-toast"; -import confetti from "canvas-confetti"; -import formatDistanceToNow from "date-fns/formatDistanceToNow"; -import axios from "axios"; - -type ReturnItem = { - id: number; - senderName: string; - amount: number; - expiresAt: Date; - timestamp: Date; -}; - -export default function ReturnPendingList({ returns }: { returns: ReturnItem[] }) { - const [selected, setSelected] = useState(null); - const [pin, setPin] = useState(""); - const [showPin, setShowPin] = useState(false); - const [loading, setLoading] = useState(false); - - const handleReturn = async () => { - if (!selected || pin.length !== 4) return; - setLoading(true); - try { - await axios.post("/api/wrong-send/approve", { requestId: selected.id, pin }); - confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); - toast.success(`Returned ₹${selected.amount}!`); - setSelected(null); - setPin(""); - } catch { - toast.error("Wrong PIN"); - setPin(""); - } finally { - setLoading(false); - } - }; - - if (!selected) { - return ( -
- {returns.map((r) => ( - setSelected(r)} - > - -
-
-

- {r.senderName} sent you -

-

₹{r.amount}

-

- Return in - {formatDistanceToNow(r.expiresAt, { addSuffix: true })} - -

-
- -
-
-
- ))} -
- ); - } - - return ( - - - - - Return ₹{selected.amount} - -

- {selected.senderName} sent this by mistake -

-
- - - {/* Amount Display */} -
-

Amount to Return

-

₹{selected.amount}

-
- - {/* PIN Input */} -
- setPin(e.target.value.slice(0, 4))} - placeholder="••••" - maxLength={4} - className="w-full text-center text-3xl tracking-widest font-mono bg-zinc-700 border border-zinc-600 rounded-xl p-5 text-white placeholder-zinc-500 focus:border-green-500 focus:outline-none" - /> - -
- - {pin.length !== 4 && pin.length > 0 && ( -

Enter full 4-digit PIN

- )} - - {/* Buttons */} -
- - -
-
-
- ); -} \ No newline at end of file diff --git a/apps/user-app/components/RewardsDash.tsx b/apps/user-app/components/RewardsDash.tsx deleted file mode 100644 index 68e7348..0000000 --- a/apps/user-app/components/RewardsDash.tsx +++ /dev/null @@ -1,201 +0,0 @@ -'use client'; - -import { useState, useEffect } from 'react'; -import axios from 'axios'; -import toast from 'react-hot-toast'; -import { motion } from 'framer-motion'; -import { Button } from '@/components/ui/button'; -import { Card } from '@/components/ui/card'; -import { Progress } from '@/components/ui/progress'; -import { Badge } from '@/components/ui/badge'; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import { QRCodeSVG } from 'qrcode.react'; -import confetti from 'canvas-confetti'; -import { Copy, Gift, QrCode, Star, Trophy, Wallet } from 'lucide-react'; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table'; - -interface Reward { id: number; type: string; amount: number; status: string; earnedAt: string; metadata?: any } -interface Summary { totalEarned: number; points: number; scratchCardsLeft: number } - -export default function RewardsDashboard({ initialPoints, initialEarned, referralCode }: { initialPoints: number; initialEarned: number; referralCode: string }) { - const [rewards, setRewards] = useState([]); - const [summary, setSummary] = useState({ totalEarned: initialEarned, points: initialPoints, scratchCardsLeft: 0 }); - const [showQR, setShowQR] = useState(false); - const [showRedeem, setShowRedeem] = useState(false); - const [winAmount, setWinAmount] = useState(0); - const [showWin, setShowWin] = useState(false); - - useEffect(() => { fetchRewards(); }, []); - - const fetchRewards = async (page = 1) => { - try { - const { data } = await axios.get(`/api/rewards?page=${page}`); - setRewards(data.rewards ?? []); - setSummary(data.summary ?? { totalEarned: 0, points: 0, scratchCardsLeft: 0 }); - } catch (err: any) { - console.error("fetchRewards error:", err?.response?.data ?? err); - toast.error(err?.response?.data?.error || "Failed to load rewards"); - // keep previous state or clear - setRewards([]); - } - }; - - const handleScratch = async () => { - try { - const { data } = await axios.post('/api/rewards/scratch'); - setWinAmount(data.winAmount); - setShowWin(true); - confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); - toast.success(`You won ₹${data.winAmount}!`); - fetchRewards(); - } catch (err: any) { - toast.error(err.response?.data?.error || 'No cards left'); - } - }; - - const handleRedeem = async (option: string) => { - try { - await axios.post('/api/rewards/redeem', { optionId: option }); - toast.success('Redeemed successfully!'); - setShowRedeem(false); - fetchRewards(); - } catch (err: any) { - toast.error(err.response?.data?.error); - } - }; - - const copyCode = () => { - navigator.clipboard.writeText(referralCode); - toast.success('Code copied!'); - }; - - const milestone = summary.totalEarned < 500 ? 'Bronze' : summary.totalEarned < 2000 ? 'Silver' : 'Gold'; - const nextMilestone = milestone === 'Bronze' ? 500 : milestone === 'Silver' ? 2000 : 5000; - const progress = (summary.totalEarned / nextMilestone) * 100; - - return ( -
- {/* Hero */} - -

Rewards Dashboard

-
-
₹{summary.totalEarned.toFixed(2)} earned
-
- - {summary.points} points -
-
{summary.scratchCardsLeft} scratch cards
-
-
- - {/* Progress */} - -
- Next Milestone: {nextMilestone} - {milestone} -
- -
- - {/* Grid */} -
- {/* Cashback History */} - -
-

- Cashback History -

-
- - - - Type - Amount - Date - - - - {rewards.slice(0, 5).map(r => ( - - {r.type} - ₹{(Number(r.amount) / 100).toFixed(2)} - {new Date(r.earnedAt).toLocaleDateString()} - - ))} - -
-
- - {/* Scratch Cards */} - - -

Scratch & Win

-

{summary.scratchCardsLeft} cards left

- -
- - {/* Referral */} - -
-

Refer & Earn ₹100

- -
-
- {referralCode} - -
-
- - {/* Redeem Store */} - - -

Redeem Points

-
- - -
-
-
- - {/* QR Modal */} - - - - Share Referral - -
- -
-
-
- - {/* Win Modal */} - - - -
₹{winAmount}
-

Added to your wallet!

-
-
-
-
- ); -} \ No newline at end of file diff --git a/apps/user-app/components/WrongSendModal.tsx b/apps/user-app/components/WrongSendModal.tsx deleted file mode 100644 index 98eec62..0000000 --- a/apps/user-app/components/WrongSendModal.tsx +++ /dev/null @@ -1,47 +0,0 @@ -'use client'; -import { Button } from '@/components/ui/button'; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'; -import { AlertTriangle, IndianRupee } from 'lucide-react'; -import axios from 'axios'; -import toast from 'react-hot-toast'; - -export default function WrongSendModal({ open, setOpen, txnId, amount }: { open: boolean; setOpen: (v: boolean) => void; txnId: number; amount: number }) { - const handleRequest = async () => { - try { - await axios.post('/api/wrong-send', { txnId }); - toast.success('Bank notified. Money will be returned in 24 hrs.'); - setOpen(false); - } catch { - toast.error('Failed'); - } - }; - - return ( - - - - - Wrong Number? - - - Bank will contact receiver. Return in 24 hrs or ₹50 fee applies. - - -
-
- Amount - ₹{amount} -
-
- Penalty (if late) - 50 -
-
- - - - -
-
- ); -} \ No newline at end of file diff --git a/apps/user-app/components/ui/background-beams-with-collision.tsx b/apps/user-app/components/ui/background-beams-with-collision.tsx deleted file mode 100644 index f0228d4..0000000 --- a/apps/user-app/components/ui/background-beams-with-collision.tsx +++ /dev/null @@ -1,259 +0,0 @@ -"use client"; -import { cn } from "@/lib/utils"; -import { motion, AnimatePresence } from "motion/react"; -import React, { useRef, useState, useEffect } from "react"; - -export const BackgroundBeamsWithCollision = ({ - children, - className, -}: { - children: React.ReactNode; - className?: string; -}) => { - const containerRef = useRef(null); - const parentRef = useRef(null); - - const beams = [ - { - initialX: 10, - translateX: 10, - duration: 7, - repeatDelay: 3, - delay: 2, - }, - { - initialX: 600, - translateX: 600, - duration: 3, - repeatDelay: 3, - delay: 4, - }, - { - initialX: 100, - translateX: 100, - duration: 7, - repeatDelay: 7, - className: "h-6", - }, - { - initialX: 400, - translateX: 400, - duration: 5, - repeatDelay: 14, - delay: 4, - }, - { - initialX: 800, - translateX: 800, - duration: 11, - repeatDelay: 2, - className: "h-20", - }, - { - initialX: 1000, - translateX: 1000, - duration: 4, - repeatDelay: 2, - className: "h-12", - }, - { - initialX: 1200, - translateX: 1200, - duration: 6, - repeatDelay: 4, - delay: 2, - className: "h-6", - }, - ]; - - return ( -
- {beams.map((beam) => ( - - ))} - - {children} -
-
- ); -}; - -const CollisionMechanism = React.forwardRef< - HTMLDivElement, - { - containerRef: React.RefObject; - parentRef: React.RefObject; - beamOptions?: { - initialX?: number; - translateX?: number; - initialY?: number; - translateY?: number; - rotate?: number; - className?: string; - duration?: number; - delay?: number; - repeatDelay?: number; - }; - } ->(({ parentRef, containerRef, beamOptions = {} }, ref) => { - const beamRef = useRef(null); - const [collision, setCollision] = useState<{ - detected: boolean; - coordinates: { x: number; y: number } | null; - }>({ - detected: false, - coordinates: null, - }); - const [beamKey, setBeamKey] = useState(0); - const [cycleCollisionDetected, setCycleCollisionDetected] = useState(false); - - useEffect(() => { - const checkCollision = () => { - if ( - beamRef.current && - containerRef.current && - parentRef.current && - !cycleCollisionDetected - ) { - const beamRect = beamRef.current.getBoundingClientRect(); - const containerRect = containerRef.current.getBoundingClientRect(); - const parentRect = parentRef.current.getBoundingClientRect(); - - if (beamRect.bottom >= containerRect.top) { - const relativeX = - beamRect.left - parentRect.left + beamRect.width / 2; - const relativeY = beamRect.bottom - parentRect.top; - - setCollision({ - detected: true, - coordinates: { - x: relativeX, - y: relativeY, - }, - }); - setCycleCollisionDetected(true); - } - } - }; - - const animationInterval = setInterval(checkCollision, 50); - - return () => clearInterval(animationInterval); - }, [cycleCollisionDetected, containerRef]); - - useEffect(() => { - if (collision.detected && collision.coordinates) { - setTimeout(() => { - setCollision({ detected: false, coordinates: null }); - setCycleCollisionDetected(false); - }, 2000); - - setTimeout(() => { - setBeamKey((prevKey) => prevKey + 1); - }, 2000); - } - }, [collision]); - - return ( - <> - - - {collision.detected && collision.coordinates && ( - - )} - - - ); -}); - -CollisionMechanism.displayName = "CollisionMechanism"; - -const Explosion = ({ ...props }: React.HTMLProps) => { - const spans = Array.from({ length: 20 }, (_, index) => ({ - id: index, - initialX: 0, - initialY: 0, - directionX: Math.floor(Math.random() * 80 - 40), - directionY: Math.floor(Math.random() * -50 - 10), - })); - - return ( -
- - {spans.map((span) => ( - - ))} -
- ); -}; diff --git a/apps/user-app/components/ui/button.tsx b/apps/user-app/components/ui/button.tsx index 11a48b9..65d4fcd 100644 --- a/apps/user-app/components/ui/button.tsx +++ b/apps/user-app/components/ui/button.tsx @@ -5,14 +5,14 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-zinc-500 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700 focus-visible:ring-zinc-300 dark:focus-visible:ring-zinc-600", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { default: - "bg-zinc-00 text-primary-foreground shadow hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-600", + "bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: - "bg-zinc-800 text-destructive-foreground shadow-sm hover:bg-destructive/90", + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", secondary: diff --git a/apps/user-app/components/ui/floating-navbar.tsx b/apps/user-app/components/ui/floating-navbar.tsx index 9d6cfe3..45dbd08 100644 --- a/apps/user-app/components/ui/floating-navbar.tsx +++ b/apps/user-app/components/ui/floating-navbar.tsx @@ -8,9 +8,6 @@ import { Avatar, AvatarFallback, AvatarImage } from "../../../../packages/ui/src import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "./dropdown-menu"; import { DropdownMenuItem } from "@radix-ui/react-dropdown-menu"; import { TextHoverEffect } from "../../../../packages/ui/src/text-hover-effect"; -import { useTheme } from "next-themes"; -import { Moon, Sun } from "lucide-react"; -import { Button } from "./button"; export const FloatingNav = ({ navItems, @@ -23,7 +20,6 @@ export const FloatingNav = ({ }[]; className?: string; }) => { - const { theme , setTheme } = useTheme(); const { data: session, status } = useSession(); const router = useRouter(); @@ -87,20 +83,9 @@ export const FloatingNav = ({ - )} - - {theme === "light" ? ( - - ) : ( - - )} - +
); diff --git a/apps/user-app/components/ui/hero-parallax.tsx b/apps/user-app/components/ui/hero-parallax.tsx index 8a30932..0712440 100644 --- a/apps/user-app/components/ui/hero-parallax.tsx +++ b/apps/user-app/components/ui/hero-parallax.tsx @@ -57,7 +57,7 @@ export const HeroParallax = ({ return (
{ return ( -
-

- Powerful Features for Modern Finance -

- -

- CalxSecure handles everything from peer-to-peer transactions to - automated bank webhooks — built to scale beyond a million users. +

+

+ The Ultimate
development studio +

+

+ We build beautiful products with the latest technologies and frameworks. + We are a team of passionate developers and designers that love to build + amazing products.

); diff --git a/apps/user-app/components/ui/hover-border-gradient.tsx b/apps/user-app/components/ui/hover-border-gradient.tsx index 9bb67b4..07ed06a 100644 --- a/apps/user-app/components/ui/hover-border-gradient.tsx +++ b/apps/user-app/components/ui/hover-border-gradient.tsx @@ -62,14 +62,14 @@ export function HoverBorderGradient({ }} onMouseLeave={() => setHovered(false)} className={cn( - "relative flex rounded-full border content-center transition duration-500 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-fit", + "relative flex rounded-full border content-center bg-white hover:bg-gray-200 transition duration-500 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-fit", containerClassName )} {...props} >
diff --git a/apps/user-app/components/ui/progress.tsx b/apps/user-app/components/ui/progress.tsx deleted file mode 100644 index 4fc3b47..0000000 --- a/apps/user-app/components/ui/progress.tsx +++ /dev/null @@ -1,28 +0,0 @@ -"use client" - -import * as React from "react" -import * as ProgressPrimitive from "@radix-ui/react-progress" - -import { cn } from "@/lib/utils" - -const Progress = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, value, ...props }, ref) => ( - - - -)) -Progress.displayName = ProgressPrimitive.Root.displayName - -export { Progress } diff --git a/apps/user-app/components/ui/shooting-stars.tsx b/apps/user-app/components/ui/shooting-stars.tsx deleted file mode 100644 index 205bfcc..0000000 --- a/apps/user-app/components/ui/shooting-stars.tsx +++ /dev/null @@ -1,146 +0,0 @@ -"use client"; -import { cn } from "@/lib/utils"; -import React, { useEffect, useState, useRef } from "react"; - -interface ShootingStar { - id: number; - x: number; - y: number; - angle: number; - scale: number; - speed: number; - distance: number; -} - -interface ShootingStarsProps { - minSpeed?: number; - maxSpeed?: number; - minDelay?: number; - maxDelay?: number; - starColor?: string; - trailColor?: string; - starWidth?: number; - starHeight?: number; - className?: string; -} - -const getRandomStartPoint = () => { - const side = Math.floor(Math.random() * 4); - const offset = Math.random() * window.innerWidth; - - switch (side) { - case 0: - return { x: offset, y: 0, angle: 45 }; - case 1: - return { x: window.innerWidth, y: offset, angle: 135 }; - case 2: - return { x: offset, y: window.innerHeight, angle: 225 }; - case 3: - return { x: 0, y: offset, angle: 315 }; - default: - return { x: 0, y: 0, angle: 45 }; - } -}; -export const ShootingStars: React.FC = ({ - minSpeed = 10, - maxSpeed = 30, - minDelay = 1200, - maxDelay = 4200, - starColor = "#9E00FF", - trailColor = "#2EB9DF", - starWidth = 10, - starHeight = 1, - className, -}) => { - const [star, setStar] = useState(null); - const svgRef = useRef(null); - - useEffect(() => { - const createStar = () => { - const { x, y, angle } = getRandomStartPoint(); - const newStar: ShootingStar = { - id: Date.now(), - x, - y, - angle, - scale: 1, - speed: Math.random() * (maxSpeed - minSpeed) + minSpeed, - distance: 0, - }; - setStar(newStar); - - const randomDelay = Math.random() * (maxDelay - minDelay) + minDelay; - setTimeout(createStar, randomDelay); - }; - - createStar(); - - return () => {}; - }, [minSpeed, maxSpeed, minDelay, maxDelay]); - - useEffect(() => { - const moveStar = () => { - if (star) { - setStar((prevStar) => { - if (!prevStar) return null; - const newX = - prevStar.x + - prevStar.speed * Math.cos((prevStar.angle * Math.PI) / 180); - const newY = - prevStar.y + - prevStar.speed * Math.sin((prevStar.angle * Math.PI) / 180); - const newDistance = prevStar.distance + prevStar.speed; - const newScale = 1 + newDistance / 100; - if ( - newX < -20 || - newX > window.innerWidth + 20 || - newY < -20 || - newY > window.innerHeight + 20 - ) { - return null; - } - return { - ...prevStar, - x: newX, - y: newY, - distance: newDistance, - scale: newScale, - }; - }); - } - }; - - const animationFrame = requestAnimationFrame(moveStar); - return () => cancelAnimationFrame(animationFrame); - }, [star]); - - return ( - - {star && ( - - )} - - - - - - - - ); -}; diff --git a/apps/user-app/components/ui/stars-background.tsx b/apps/user-app/components/ui/stars-background.tsx deleted file mode 100644 index 098aca5..0000000 --- a/apps/user-app/components/ui/stars-background.tsx +++ /dev/null @@ -1,143 +0,0 @@ -"use client"; -import { cn } from "@/lib/utils"; -import React, { - useState, - useEffect, - useRef, - RefObject, - useCallback, -} from "react"; - -interface StarProps { - x: number; - y: number; - radius: number; - opacity: number; - twinkleSpeed: number | null; -} - -interface StarBackgroundProps { - starDensity?: number; - allStarsTwinkle?: boolean; - twinkleProbability?: number; - minTwinkleSpeed?: number; - maxTwinkleSpeed?: number; - className?: string; -} - -export const StarsBackground: React.FC = ({ - starDensity = 0.00015, - allStarsTwinkle = true, - twinkleProbability = 0.7, - minTwinkleSpeed = 0.5, - maxTwinkleSpeed = 1, - className, -}) => { - const [stars, setStars] = useState([]); - const canvasRef: RefObject = - useRef(null); - - const generateStars = useCallback( - (width: number, height: number): StarProps[] => { - const area = width * height; - const numStars = Math.floor(area * starDensity); - return Array.from({ length: numStars }, () => { - const shouldTwinkle = - allStarsTwinkle || Math.random() < twinkleProbability; - return { - x: Math.random() * width, - y: Math.random() * height, - radius: Math.random() * 0.05 + 0.5, - opacity: Math.random() * 0.5 + 0.5, - twinkleSpeed: shouldTwinkle - ? minTwinkleSpeed + - Math.random() * (maxTwinkleSpeed - minTwinkleSpeed) - : null, - }; - }); - }, - [ - starDensity, - allStarsTwinkle, - twinkleProbability, - minTwinkleSpeed, - maxTwinkleSpeed, - ] - ); - - useEffect(() => { - const updateStars = () => { - if (canvasRef.current) { - const canvas = canvasRef.current; - const ctx = canvas.getContext("2d"); - if (!ctx) return; - - const { width, height } = canvas.getBoundingClientRect(); - canvas.width = width; - canvas.height = height; - setStars(generateStars(width, height)); - } - }; - - updateStars(); - - const resizeObserver = new ResizeObserver(updateStars); - if (canvasRef.current) { - resizeObserver.observe(canvasRef.current); - } - - return () => { - if (canvasRef.current) { - resizeObserver.unobserve(canvasRef.current); - } - }; - }, [ - starDensity, - allStarsTwinkle, - twinkleProbability, - minTwinkleSpeed, - maxTwinkleSpeed, - generateStars, - ]); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext("2d"); - if (!ctx) return; - - let animationFrameId: number; - - const render = () => { - ctx.clearRect(0, 0, canvas.width, canvas.height); - stars.forEach((star) => { - ctx.beginPath(); - ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2); - ctx.fillStyle = `rgba(255, 255, 255, ${star.opacity})`; - ctx.fill(); - - if (star.twinkleSpeed !== null) { - star.opacity = - 0.5 + - Math.abs(Math.sin((Date.now() * 0.001) / star.twinkleSpeed) * 0.5); - } - }); - - animationFrameId = requestAnimationFrame(render); - }; - - render(); - - return () => { - cancelAnimationFrame(animationFrameId); - }; - }, [stars]); - - return ( - - ); -}; diff --git a/apps/user-app/components/ui/table.tsx b/apps/user-app/components/ui/table.tsx deleted file mode 100644 index c0df655..0000000 --- a/apps/user-app/components/ui/table.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import * as React from "react" - -import { cn } from "@/lib/utils" - -const Table = React.forwardRef< - HTMLTableElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
- - -)) -Table.displayName = "Table" - -const TableHeader = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)) -TableHeader.displayName = "TableHeader" - -const TableBody = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)) -TableBody.displayName = "TableBody" - -const TableFooter = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - tr]:last:border-b-0", - className - )} - {...props} - /> -)) -TableFooter.displayName = "TableFooter" - -const TableRow = React.forwardRef< - HTMLTableRowElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( - -)) -TableRow.displayName = "TableRow" - -const TableHead = React.forwardRef< - HTMLTableCellElement, - React.ThHTMLAttributes ->(({ className, ...props }, ref) => ( -
[role=checkbox]]:translate-y-[2px]", - className - )} - {...props} - /> -)) -TableHead.displayName = "TableHead" - -const TableCell = React.forwardRef< - HTMLTableCellElement, - React.TdHTMLAttributes ->(({ className, ...props }, ref) => ( - [role=checkbox]]:translate-y-[2px]", - className - )} - {...props} - /> -)) -TableCell.displayName = "TableCell" - -const TableCaption = React.forwardRef< - HTMLTableCaptionElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -TableCaption.displayName = "TableCaption" - -export { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, -} diff --git a/apps/user-app/components/ui/theme-provider.tsx b/apps/user-app/components/ui/theme-provider.tsx deleted file mode 100644 index e018a73..0000000 --- a/apps/user-app/components/ui/theme-provider.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client" - -import * as React from "react" -import { ThemeProvider as NextThemesProvider } from "next-themes" - -export function ThemeProvider({ - children, - ...props -}: React.ComponentProps) { - return {children} -} \ No newline at end of file diff --git a/apps/user-app/cron/panelty.ts b/apps/user-app/cron/panelty.ts deleted file mode 100644 index fcdf0ab..0000000 --- a/apps/user-app/cron/panelty.ts +++ /dev/null @@ -1,42 +0,0 @@ -import prisma from '@repo/db/client'; -import axios from 'axios'; - -async function run() { - const expired = await prisma.wrongSendRequest.findMany({ - where: { status: 'PENDING', expiresAt: { lt: new Date() } }, - include: { sender: true, transaction: true }, - }); - - for (const req of expired) { - // use number (paise) to match Balance.amount (Int) - const penalty = 5000; // ₹50 => 5000 paise - const refundAmount = Number(req.amount) + penalty; - - await prisma.$transaction([ - prisma.wrongSendRequest.update({ - where: { id: req.id }, - data: { status: 'EXPIRED', penaltyPaid: true }, - }), - // Update Balance via the Balance model (not nested on User) - prisma.balance.upsert({ - where: { userId: req.senderId }, - update: { amount: { increment: refundAmount } }, - create: { userId: req.senderId, amount: refundAmount, locked: 0 }, - }), - ]); - - // SMS to sender — convert paise -> rupees for human readable message - const amountRupees = Number(req.amount) / 100; - const totalRupees = refundAmount / 100; - const senderNumber = (req as any).sender?.number; - if (senderNumber) { - await axios.get( - `https://www.fast2sms.com/dev/bulkV2?authorization=${process.env.FAST2SMS}&message=${encodeURIComponent( - `₹${amountRupees} + ₹50 penalty refunded! Total refunded: ₹${totalRupees}` - )}&numbers=${senderNumber}` - ); - } - } -} - -run().catch(console.error); \ No newline at end of file diff --git a/apps/user-app/next-env.d.ts b/apps/user-app/next-env.d.ts index 725dd6f..36a4fe4 100644 --- a/apps/user-app/next-env.d.ts +++ b/apps/user-app/next-env.d.ts @@ -1,6 +1,7 @@ /// /// /// +/// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/user-app/package.json b/apps/user-app/package.json index fdccb31..7324b88 100644 --- a/apps/user-app/package.json +++ b/apps/user-app/package.json @@ -13,7 +13,6 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", @@ -31,15 +30,13 @@ "chartjs-adapter-date-fns": "^3.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "date-fns": "^2.30.0", + "date-fns": "^4.1.0", "html5-qrcode": "^2.3.8", - "ioredis": "^5.8.2", "lucide-react": "^0.543.0", "mini-svg-data-uri": "^1.4.4", "motion": "^12.23.24", "next": "^14.1.1", "next-auth": "^4.24.7", - "next-themes": "^0.4.6", "node-cron": "^4.2.1", "razorpay": "^2.9.6", "react": "^19.2.0", diff --git a/package-lock.json b/package-lock.json index ac838be..1e0a332 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,6 @@ "@repo/db": "*", "@types/express": "^4.17.21", "@types/ws": "^8.18.1", - "axios": "^1.13.2", "cors": "^2.8.5", "crypto": "^1.0.1", "esbuild": "^0.25.10", @@ -114,7 +113,6 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", @@ -132,15 +130,13 @@ "chartjs-adapter-date-fns": "^3.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "date-fns": "^2.30.0", + "date-fns": "^4.1.0", "html5-qrcode": "^2.3.8", - "ioredis": "^5.8.2", "lucide-react": "^0.543.0", "mini-svg-data-uri": "^1.4.4", "motion": "^12.23.24", "next": "^14.1.1", "next-auth": "^4.24.7", - "next-themes": "^0.4.6", "node-cron": "^4.2.1", "razorpay": "^2.9.6", "react": "^19.2.0", @@ -184,22 +180,6 @@ "@types/json-schema": "*" } }, - "apps/user-app/node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "apps/user-app/node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", @@ -1717,12 +1697,6 @@ } } }, - "node_modules/@ioredis/commands": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", - "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", - "license": "MIT" - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2758,86 +2732,6 @@ } } }, - "node_modules/@radix-ui/react-progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", - "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-context": "1.1.3", - "@radix-ui/react-primitive": "2.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", - "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-slot": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", - "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", @@ -5404,9 +5298,9 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -6144,15 +6038,6 @@ "node": ">=6" } }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -6552,7 +6437,6 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -6729,15 +6613,6 @@ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "license": "MIT" }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -9983,30 +9858,6 @@ "node": ">=12" } }, - "node_modules/ioredis": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz", - "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==", - "license": "MIT", - "dependencies": { - "@ioredis/commands": "1.4.0", - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.4", - "denque": "^2.1.0", - "lodash.defaults": "^4.2.0", - "lodash.isarguments": "^3.1.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } - }, "node_modules/ip-address": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", @@ -10932,12 +10783,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "license": "MIT" - }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -10952,12 +10797,6 @@ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "license": "MIT" }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "license": "MIT" - }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -11513,16 +11352,6 @@ } } }, - "node_modules/next-themes": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", - "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" - } - }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -13472,27 +13301,6 @@ } } }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "license": "MIT", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -14383,12 +14191,6 @@ "dev": true, "license": "MIT" }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", - "license": "MIT" - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/packages/db/prisma/RePlans.js b/packages/db/prisma/RePlans.js deleted file mode 100644 index 4dbb073..0000000 --- a/packages/db/prisma/RePlans.js +++ /dev/null @@ -1,131 +0,0 @@ -"use strict"; -var __assign = (this && this.__assign) || function () { - __assign = Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); - return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -// prisma/seed-live.ts -var client_1 = require("@prisma/client"); -var prisma = new client_1.PrismaClient(); -var circles = [ - "Andhra Pradesh", "Assam", "Bihar & Jharkhand", "Chennai", "Delhi NCR", - "Gujarat", "Haryana", "Himachal Pradesh", "Jammu & Kashmir", "Karnataka", - "Kerala", "Kolkata", "Madhya Pradesh & Chhattisgarh", "Maharashtra & Goa", - "Mumbai", "North East", "Odisha", "Punjab", "Rajasthan", "Tamil Nadu", - "Uttar Pradesh (East)", "Uttar Pradesh (West)", "West Bengal" -]; -var jioPlans = [ - { amount: 149, validity: "28 days", desc: "1.5 GB/day + Unlimited Calls + 100 SMS/day" }, - { amount: 299, validity: "28 days", desc: "2 GB/day + Unlimited Calls + 100 SMS/day" }, - { amount: 349, validity: "28 days", desc: "2.5 GB/day + Unlimited Calls + JioHotstar" }, - { amount: 399, validity: "56 days", desc: "2 GB/day + Unlimited Calls + JioCinema" }, - { amount: 666, validity: "84 days", desc: "1.5 GB/day + Unlimited Calls + Disney+ Hotstar" }, - { amount: 899, validity: "84 days", desc: "2 GB/day + Unlimited Calls + JioHotstar 3-month" }, - { amount: 1199, validity: "84 days", desc: "3 GB/day + Unlimited Calls + Netflix" }, - { amount: 3599, validity: "365 days", desc: "2 GB/day + Unlimited Calls + Amazon Prime" }, - { amount: 3999, validity: "365 days", desc: "2.5 GB/day + Unlimited Calls + Netflix/Prime" }, -]; -var airtelPlans = [ - { amount: 199, validity: "28 days", desc: "1 GB/day + Unlimited Calls + Xstream Play" }, - { amount: 299, validity: "28 days", desc: "1.5 GB/day + Unlimited Calls + Disney+ Hotstar" }, - { amount: 379, validity: "30 days", desc: "2 GB/day + Unlimited Calls + Apollo 24|7" }, - { amount: 449, validity: "28 days", desc: "3 GB/day + Unlimited 5G + Amazon Prime" }, - { amount: 719, validity: "84 days", desc: "1.5 GB/day + Unlimited Calls + Hotstar" }, - { amount: 999, validity: "84 days", desc: "2.5 GB/day + Unlimited Calls + Netflix" }, - { amount: 2999, validity: "365 days", desc: "2 GB/day + Unlimited Calls + Disney+ Hotstar" }, - { amount: 3599, validity: "365 days", desc: "2.5 GB/day + Unlimited Calls + Netflix/Prime" }, -]; -var viPlans = [ - { amount: 99, validity: "28 days", desc: "₹99 Talktime + 200 MB + Night Data" }, - { amount: 179, validity: "28 days", desc: "1 GB/day + Unlimited Calls + Weekend Rollover" }, - { amount: 349, validity: "28 days", desc: "1.5 GB/day + Unlimited Calls + SonyLIV" }, - { amount: 449, validity: "28 days", desc: "3 GB/day + Unlimited Calls + Data Delights" }, - { amount: 996, validity: "84 days", desc: "2 GB/day + Unlimited Calls + SonyLIV Premium" }, - { amount: 1449, validity: "180 days", desc: "1.5 GB/day + Unlimited Calls + Hero Unlimited" }, - { amount: 3599, validity: "365 days", desc: "2 GB/day + Unlimited Calls + 16 OTTs" }, - { amount: 3799, validity: "365 days", desc: "2.5 GB/day + Unlimited Night Data + Amazon Prime" }, -]; -function main() { - return __awaiter(this, void 0, void 0, function () { - var allPlans, _i, circles_1, circle, _a, jioPlans_1, p, _b, airtelPlans_1, p, _c, viPlans_1, p, payload; - return __generator(this, function (_d) { - switch (_d.label) { - case 0: return [4 /*yield*/, prisma.rechargePlan.deleteMany({})]; - case 1: - _d.sent(); - allPlans = []; - for (_i = 0, circles_1 = circles; _i < circles_1.length; _i++) { - circle = circles_1[_i]; - for (_a = 0, jioPlans_1 = jioPlans; _a < jioPlans_1.length; _a++) { - p = jioPlans_1[_a]; - allPlans.push(__assign({ operator: "Jio", circle: circle }, p)); - } - for (_b = 0, airtelPlans_1 = airtelPlans; _b < airtelPlans_1.length; _b++) { - p = airtelPlans_1[_b]; - allPlans.push(__assign({ operator: "Airtel", circle: circle }, p)); - } - for (_c = 0, viPlans_1 = viPlans; _c < viPlans_1.length; _c++) { - p = viPlans_1[_c]; - allPlans.push(__assign({ operator: "Vi", circle: circle }, p)); - } - } - payload = allPlans.map(function (p) { return (__assign(__assign({}, p), { planCode: "".concat(p.operator, "-").concat(p.amount, "-").concat(p.circle.slice(0, 3)).replace(/[^a-z0-9]/gi, '_'), planType: p.amount < 200 ? "TOPUP" : "DATA" })); }); - return [4 /*yield*/, prisma.rechargePlan.createMany({ - data: payload, - skipDuplicates: true, - })]; - case 2: - _d.sent(); - console.log("\u2705 LIVE NOV 2025 DATA SEEDED"); - console.log(" \uD83D\uDCF6 Circles: ".concat(circles.length)); - console.log(" \uD83D\uDCF1 Plans : ".concat(payload.length, " (Jio ").concat(jioPlans.length, " \u00D7 23, Airtel ").concat(airtelPlans.length, " \u00D7 23, Vi ").concat(viPlans.length, " \u00D7 23)")); - console.log(" \uD83D\uDE80 Run: npx tsx prisma/seed-live.ts"); - return [2 /*return*/]; - } - }); - }); -} -main().catch(function (e) { throw e; }).finally(function () { return prisma.$disconnect(); }); diff --git a/packages/db/prisma/migrations/20251104141035_add_recharge_models/migration.sql b/packages/db/prisma/migrations/20251104141035_add_recharge_models/migration.sql deleted file mode 100644 index 9b9c6d6..0000000 --- a/packages/db/prisma/migrations/20251104141035_add_recharge_models/migration.sql +++ /dev/null @@ -1,43 +0,0 @@ --- CreateTable -CREATE TABLE "recharge_plans" ( - "id" SERIAL NOT NULL, - "operator" TEXT NOT NULL, - "circle" TEXT NOT NULL, - "planCode" TEXT NOT NULL, - "amount" INTEGER NOT NULL, - "planType" TEXT NOT NULL, - "validity" TEXT, - "data" TEXT, - "description" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "recharge_plans_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "recharge_orders" ( - "id" SERIAL NOT NULL, - "userId" INTEGER NOT NULL, - "planId" INTEGER, - "mobileNumber" TEXT NOT NULL, - "operator" TEXT NOT NULL, - "circle" TEXT NOT NULL, - "amount" INTEGER NOT NULL, - "status" TEXT NOT NULL, - "providerTxnId" TEXT, - "orderId" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "recharge_orders_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "recharge_orders_orderId_key" ON "recharge_orders"("orderId"); - --- AddForeignKey -ALTER TABLE "recharge_orders" ADD CONSTRAINT "recharge_orders_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "recharge_orders" ADD CONSTRAINT "recharge_orders_planId_fkey" FOREIGN KEY ("planId") REFERENCES "recharge_plans"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/db/prisma/migrations/20251104142535_add_recharge_models1/migration.sql b/packages/db/prisma/migrations/20251104142535_add_recharge_models1/migration.sql deleted file mode 100644 index 255c77f..0000000 --- a/packages/db/prisma/migrations/20251104142535_add_recharge_models1/migration.sql +++ /dev/null @@ -1,55 +0,0 @@ -/* - Warnings: - - - You are about to drop the `recharge_orders` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `recharge_plans` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropForeignKey -ALTER TABLE "public"."recharge_orders" DROP CONSTRAINT "recharge_orders_planId_fkey"; - --- DropForeignKey -ALTER TABLE "public"."recharge_orders" DROP CONSTRAINT "recharge_orders_userId_fkey"; - --- DropTable -DROP TABLE "public"."recharge_orders"; - --- DropTable -DROP TABLE "public"."recharge_plans"; - --- CreateTable -CREATE TABLE "RechargePlan" ( - "id" SERIAL NOT NULL, - "operator" TEXT NOT NULL, - "circle" TEXT NOT NULL, - "amount" INTEGER NOT NULL, - "type" TEXT NOT NULL, - "validity" TEXT NOT NULL, - "description" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "RechargePlan_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "RechargeOrder" ( - "id" SERIAL NOT NULL, - "userId" INTEGER NOT NULL, - "planId" INTEGER NOT NULL, - "operator" TEXT NOT NULL, - "circle" TEXT NOT NULL, - "number" TEXT NOT NULL, - "amount" INTEGER NOT NULL, - "status" TEXT NOT NULL DEFAULT 'PROCESSING', - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "RechargeOrder_pkey" PRIMARY KEY ("id") -); - --- AddForeignKey -ALTER TABLE "RechargeOrder" ADD CONSTRAINT "RechargeOrder_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "RechargeOrder" ADD CONSTRAINT "RechargeOrder_planId_fkey" FOREIGN KEY ("planId") REFERENCES "RechargePlan"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/db/prisma/migrations/20251104143227_add_recharge_models/migration.sql b/packages/db/prisma/migrations/20251104143227_add_recharge_models/migration.sql deleted file mode 100644 index db8f7be..0000000 --- a/packages/db/prisma/migrations/20251104143227_add_recharge_models/migration.sql +++ /dev/null @@ -1,62 +0,0 @@ -/* - Warnings: - - - You are about to drop the `RechargeOrder` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `RechargePlan` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropForeignKey -ALTER TABLE "public"."RechargeOrder" DROP CONSTRAINT "RechargeOrder_planId_fkey"; - --- DropForeignKey -ALTER TABLE "public"."RechargeOrder" DROP CONSTRAINT "RechargeOrder_userId_fkey"; - --- DropTable -DROP TABLE "public"."RechargeOrder"; - --- DropTable -DROP TABLE "public"."RechargePlan"; - --- CreateTable -CREATE TABLE "recharge_plans" ( - "id" SERIAL NOT NULL, - "operator" TEXT NOT NULL, - "circle" TEXT NOT NULL, - "planCode" TEXT NOT NULL, - "amount" INTEGER NOT NULL, - "planType" TEXT NOT NULL, - "validity" TEXT, - "data" TEXT, - "description" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "recharge_plans_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "recharge_orders" ( - "id" SERIAL NOT NULL, - "userId" INTEGER NOT NULL, - "planId" INTEGER, - "mobileNumber" TEXT NOT NULL, - "operator" TEXT NOT NULL, - "circle" TEXT NOT NULL, - "amount" INTEGER NOT NULL, - "status" TEXT NOT NULL DEFAULT 'PENDING', - "providerTxnId" TEXT, - "orderId" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "recharge_orders_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "recharge_orders_orderId_key" ON "recharge_orders"("orderId"); - --- AddForeignKey -ALTER TABLE "recharge_orders" ADD CONSTRAINT "recharge_orders_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "recharge_orders" ADD CONSTRAINT "recharge_orders_planId_fkey" FOREIGN KEY ("planId") REFERENCES "recharge_plans"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/db/prisma/migrations/20251104193507_add_reward_referal/migration.sql b/packages/db/prisma/migrations/20251104193507_add_reward_referal/migration.sql deleted file mode 100644 index d357a6d..0000000 --- a/packages/db/prisma/migrations/20251104193507_add_reward_referal/migration.sql +++ /dev/null @@ -1,50 +0,0 @@ --- CreateEnum -CREATE TYPE "RewardType" AS ENUM ('CASHBACK', 'SCRATCH', 'REFERRAL', 'MILESTONE'); - --- CreateEnum -CREATE TYPE "RewardStatus" AS ENUM ('PENDING', 'CLAIMED', 'EXPIRED'); - --- CreateTable -CREATE TABLE "Reward" ( - "id" SERIAL NOT NULL, - "userId" INTEGER NOT NULL, - "type" "RewardType" NOT NULL, - "amount" BIGINT NOT NULL, - "status" "RewardStatus" NOT NULL DEFAULT 'PENDING', - "earnedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "expiresAt" TIMESTAMP(3), - "metadata" JSONB, - - CONSTRAINT "Reward_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Referral" ( - "id" SERIAL NOT NULL, - "referrerId" INTEGER NOT NULL, - "referredUserId" INTEGER, - "referralCode" TEXT NOT NULL, - - CONSTRAINT "Referral_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE INDEX "Reward_userId_idx" ON "Reward"("userId"); - --- CreateIndex -CREATE INDEX "Reward_type_idx" ON "Reward"("type"); - --- CreateIndex -CREATE UNIQUE INDEX "Referral_referralCode_key" ON "Referral"("referralCode"); - --- CreateIndex -CREATE INDEX "Referral_referralCode_idx" ON "Referral"("referralCode"); - --- AddForeignKey -ALTER TABLE "Reward" ADD CONSTRAINT "Reward_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Referral" ADD CONSTRAINT "Referral_referrerId_fkey" FOREIGN KEY ("referrerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Referral" ADD CONSTRAINT "Referral_referredUserId_fkey" FOREIGN KEY ("referredUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/db/prisma/migrations/20251104220423_refund_request/migration.sql b/packages/db/prisma/migrations/20251104220423_refund_request/migration.sql deleted file mode 100644 index 3368dc1..0000000 --- a/packages/db/prisma/migrations/20251104220423_refund_request/migration.sql +++ /dev/null @@ -1,54 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[razorpayPaymentId]` on the table `p2pTransfer` will be added. If there are existing duplicate values, this will fail. - -*/ --- CreateEnum -CREATE TYPE "WrongSendStatus" AS ENUM ('PENDING', 'RETURNED', 'EXPIRED'); - --- DropForeignKey -ALTER TABLE "public"."p2pTransfer" DROP CONSTRAINT "p2pTransfer_toUserId_fkey"; - --- AlterTable -ALTER TABLE "p2pTransfer" ADD COLUMN "razorpayPaymentId" TEXT, -ADD COLUMN "receiverNumber" TEXT, -ADD COLUMN "status" TEXT NOT NULL DEFAULT 'SUCCESS', -ALTER COLUMN "toUserId" DROP NOT NULL; - --- CreateTable -CREATE TABLE "WrongSendRequest" ( - "id" SERIAL NOT NULL, - "txnId" INTEGER NOT NULL, - "senderId" INTEGER NOT NULL, - "receiverNumber" TEXT NOT NULL, - "amount" BIGINT NOT NULL, - "status" "WrongSendStatus" NOT NULL DEFAULT 'PENDING', - "expiresAt" TIMESTAMP(3) NOT NULL, - "penaltyPaid" BOOLEAN NOT NULL DEFAULT false, - "razorpayRefundId" TEXT, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "WrongSendRequest_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "WrongSendRequest_txnId_key" ON "WrongSendRequest"("txnId"); - --- CreateIndex -CREATE UNIQUE INDEX "WrongSendRequest_razorpayRefundId_key" ON "WrongSendRequest"("razorpayRefundId"); - --- CreateIndex -CREATE INDEX "WrongSendRequest_status_idx" ON "WrongSendRequest"("status"); - --- CreateIndex -CREATE UNIQUE INDEX "p2pTransfer_razorpayPaymentId_key" ON "p2pTransfer"("razorpayPaymentId"); - --- AddForeignKey -ALTER TABLE "p2pTransfer" ADD CONSTRAINT "p2pTransfer_toUserId_fkey" FOREIGN KEY ("toUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "WrongSendRequest" ADD CONSTRAINT "WrongSendRequest_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "WrongSendRequest" ADD CONSTRAINT "WrongSendRequest_txnId_fkey" FOREIGN KEY ("txnId") REFERENCES "p2pTransfer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index eaa7f37..7a47b5a 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -26,27 +26,16 @@ model User { receivedRequests P2PRequest[] @relation("P2PRequestReceiver") billSchedules BillSchedule[] merchantPayments MerchantPayment[] @relation("UserMerchantPayments") - rechargeOrders RechargeOrder[] - rewards Reward[] - referralsAsReferrer Referral[] @relation("Referrer") - referralsAsReferred Referral[] @relation("Referred") - wrongSendRequests WrongSendRequest[] @relation("UserWrongSendRequests") - - } model p2pTransfer { - id Int @id @default(autoincrement()) - amount Int - timestamp DateTime - fromUserId Int - fromUser User @relation("FromUserRelation", fields: [fromUserId], references: [id]) - toUserId Int? - toUser User? @relation("ToUserRelation", fields: [toUserId], references: [id]) - receiverNumber String? - status String @default("SUCCESS") // SUCCESS, REFUND_PENDING, REFUNDED - razorpayPaymentId String? @unique - wrongSendRequest WrongSendRequest? + id Int @id @default(autoincrement()) + amount Int + timestamp DateTime + fromUserId Int + fromUser User @relation(name: "FromUserRelation", fields: [fromUserId], references: [id]) + toUserId Int + toUser User @relation(name: "ToUserRelation", fields: [toUserId], references: [id]) } model Merchant { id Int @id @default(autoincrement()) @@ -117,91 +106,10 @@ model BillSchedule { paymentMethod String @default("UPI") status String @default("PENDING") token String? @unique - @@map("bill_schedules") } -model RechargePlan{ - id Int @id @default(autoincrement()) - operator String - circle String - planCode String - amount Int - planType String - validity String? - data String? - description String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - rechargeOrders RechargeOrder[] - @@map("recharge_plans") -} -model RechargeOrder{ - id Int @id @default(autoincrement()) - userId Int - user User @relation(fields: [userId], references: [id]) - planId Int? - plan RechargePlan? @relation(fields: [planId], references: [id]) - mobileNumber String - operator String - circle String - amount Int - status String @default("PENDING") // PENDING, SUCCESS, FAILED - providerTxnId String? // Transaction ID from operator - orderId String @unique - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@map("recharge_orders") -} - -model Reward { - id Int @id @default(autoincrement()) - userId Int - type RewardType - amount BigInt // paise - status RewardStatus @default(PENDING) - earnedAt DateTime @default(now()) - expiresAt DateTime? - metadata Json? - user User @relation(fields: [userId], references: [id]) - - @@index([userId]) - @@index([type]) -} - -model Referral { - id Int @id @default(autoincrement()) - referrerId Int - referredUserId Int? - referralCode String @unique - referrer User @relation("Referrer", fields: [referrerId], references: [id]) - referred User? @relation("Referred", fields: [referredUserId], references: [id]) - - @@index([referralCode]) -} -model WrongSendRequest { - id Int @id @default(autoincrement()) - txnId Int @unique - senderId Int - receiverNumber String - amount BigInt - status WrongSendStatus @default(PENDING) - expiresAt DateTime - penaltyPaid Boolean @default(false) - razorpayRefundId String? @unique - createdAt DateTime @default(now()) - sender User @relation("UserWrongSendRequests", fields: [senderId], references: [id]) - transaction p2pTransfer @relation(fields: [txnId], references: [id]) - - @@index([status]) -} -enum WrongSendStatus { - PENDING - RETURNED - EXPIRED -} enum BillType { ELECTRICITY WATER @@ -209,18 +117,6 @@ enum BillType { PHONE_RECHARGE DTH } -enum RewardType { - CASHBACK - SCRATCH - REFERRAL - MILESTONE -} - -enum RewardStatus { - PENDING - CLAIMED - EXPIRED -} enum P2PRequestStatus { PENDING diff --git a/packages/db/prisma/seed-rewards.ts b/packages/db/prisma/seed-rewards.ts deleted file mode 100644 index 8f8581f..0000000 --- a/packages/db/prisma/seed-rewards.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -const prisma = new PrismaClient(); - -async function main() { - const users = await prisma.user.findMany({ take: 2 }); - if (users.length < 2) return; - - const [nasir, vikas] = users; - if (!nasir || !vikas) return; - - // Referral codes - await prisma.referral.upsert({ - where: { referralCode: 'NASIR100' }, - update: {}, - create: { referrerId: nasir.id, referralCode: 'NASIR100' }, - }); - - await prisma.referral.upsert({ - where: { referralCode: 'VIKAS50' }, - update: {}, - create: { referrerId: vikas.id, referralCode: 'VIKAS50' }, - }); - - // Scratch Cards (₹5-₹50) - const scratchAmounts = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]; - const scratches = scratchAmounts.flatMap(amount => - Array(3).fill(null).map(() => ({ - userId: nasir.id, - type: 'SCRATCH' as const, - amount: BigInt(amount * 100), - status: 'PENDING' as const, - expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), - metadata: { name: `Scratch ₹${amount}` }, - })) - ); - - // Cashback (5% on recharges) - const cashbacks = [ - { userId: nasir.id, amount: 14900, recharge: 299 }, - { userId: nasir.id, amount: 14900, recharge: 299 }, - { userId: vikas.id, amount: 9950, recharge: 199 }, - ].map(c => ({ - userId: c.userId, - type: 'CASHBACK' as const, - amount: BigInt(c.amount), - status: 'CLAIMED' as const, - metadata: { rechargeAmount: c.recharge }, - })); - - // Referral reward - const referralReward = { - userId: nasir.id, - type: 'REFERRAL' as const, - amount: BigInt(10000), - status: 'CLAIMED' as const, - metadata: { referredUser: vikas.number }, - }; - - // Milestone - const milestone = { - userId: nasir.id, - type: 'MILESTONE' as const, - amount: BigInt(50000), - status: 'CLAIMED' as const, - metadata: { level: 'Gold' }, - }; - - await prisma.reward.createMany({ - data: [...scratches, ...cashbacks, referralReward, milestone], - skipDuplicates: true, - }); - - console.log('50+ Rewards seeded'); -} - -main().catch(e => console.error(e)).finally(() => prisma.$disconnect()); \ No newline at end of file diff --git a/packages/db/prisma/seed.ts b/packages/db/prisma/seed.ts index 8469256..cbd5821 100644 --- a/packages/db/prisma/seed.ts +++ b/packages/db/prisma/seed.ts @@ -1,62 +1,11 @@ -import { PrismaClient, AuthType } from '@prisma/client'; +import { PrismaClient, AuthType } from '@prisma/client' import bcrypt from "bcrypt"; import { v4 as uuid } from "uuid"; -const prisma = new PrismaClient(); +const prisma = new PrismaClient() -/* -------------------------------------------------------------------------- */ -/* 1. ALL 23 CIRCLES (official TRAI list – Nov 2025) */ -/* -------------------------------------------------------------------------- */ -const circles = [ - "Andhra Pradesh", "Assam", "Bihar & Jharkhand", "Chennai", "Delhi NCR", - "Gujarat", "Haryana", "Himachal Pradesh", "Jammu & Kashmir", "Karnataka", - "Kerala", "Kolkata", "Madhya Pradesh & Chhattisgarh", "Maharashtra & Goa", - "Mumbai", "North East", "Odisha", "Punjab", "Rajasthan", "Tamil Nadu", - "Uttar Pradesh (East)", "Uttar Pradesh (West)", "West Bengal" -]; - -/* -------------------------------------------------------------------------- */ -/* 2. REAL 2025 PLANS (price, validity, description) */ -/* -------------------------------------------------------------------------- */ -const jioPlans = [ - { amount: 149, validity: "28 days", desc: "1.5 GB/day + Unlimited Calls + 100 SMS/day" }, - { amount: 299, validity: "28 days", desc: "2 GB/day + Unlimited Calls + 100 SMS/day" }, - { amount: 349, validity: "28 days", desc: "2.5 GB/day + Unlimited Calls + JioHotstar" }, - { amount: 399, validity: "56 days", desc: "2 GB/day + Unlimited Calls + JioCinema" }, - { amount: 666, validity: "84 days", desc: "1.5 GB/day + Unlimited Calls + Disney+ Hotstar" }, - { amount: 899, validity: "84 days", desc: "2 GB/day + Unlimited Calls + JioHotstar 3-month" }, - { amount: 1199, validity: "84 days", desc: "3 GB/day + Unlimited Calls + Netflix" }, - { amount: 3599, validity: "365 days", desc: "2 GB/day + Unlimited Calls + Amazon Prime" }, - { amount: 3999, validity: "365 days", desc: "2.5 GB/day + Unlimited Calls + Netflix/Prime" }, -]; - -const airtelPlans = [ - { amount: 199, validity: "28 days", desc: "1 GB/day + Unlimited Calls + Xstream Play" }, - { amount: 299, validity: "28 days", desc: "1.5 GB/day + Unlimited Calls + Disney+ Hotstar" }, - { amount: 379, validity: "30 days", desc: "2 GB/day + Unlimited Calls + Apollo 24|7" }, - { amount: 449, validity: "28 days", desc: "3 GB/day + Unlimited 5G + Amazon Prime" }, - { amount: 719, validity: "84 days", desc: "1.5 GB/day + Unlimited Calls + Hotstar" }, - { amount: 999, validity: "84 days", desc: "2.5 GB/day + Unlimited Calls + Netflix" }, - { amount: 2999, validity: "365 days", desc: "2 GB/day + Unlimited Calls + Disney+ Hotstar" }, - { amount: 3599, validity: "365 days", desc: "2.5 GB/day + Unlimited Calls + Netflix/Prime" }, -]; - -const viPlans = [ - { amount: 99, validity: "28 days", desc: "₹99 Talktime + 200 MB + Night Data" }, - { amount: 179, validity: "28 days", desc: "1 GB/day + Unlimited Calls + Weekend Rollover" }, - { amount: 349, validity: "28 days", desc: "1.5 GB/day + Unlimited Calls + SonyLIV" }, - { amount: 449, validity: "28 days", desc: "3 GB/day + Unlimited Calls + Data Delights" }, - { amount: 996, validity: "84 days", desc: "2 GB/day + Unlimited Calls + SonyLIV Premium" }, - { amount: 1449,validity: "180 days",desc: "1.5 GB/day + Unlimited Calls + Hero Unlimited" }, - { amount: 3599,validity: "365 days",desc: "2 GB/day + Unlimited Calls + 16 OTTs" }, - { amount: 3799,validity: "365 days",desc: "2.5 GB/day + Unlimited Night Data + Amazon Prime" }, -]; - -/* -------------------------------------------------------------------------- */ -/* 3. MAIN SEED FUNCTION */ -/* -------------------------------------------------------------------------- */ async function main() { - /* ---------- USERS ---------- */ + // 🚨 USERS const Nasir = await prisma.user.upsert({ where: { number: '1111111111' }, update: {}, @@ -65,13 +14,24 @@ async function main() { password: await bcrypt.hash('Nasir', 10), userpin: '1234', name: 'Nasir', - sessionToken: uuid(), - Balance: { create: { amount: 20000, locked: 0 } }, + sessionToken:uuid(), + Balance: { + create: { + amount: 20000, + locked: 0 + } + }, OnRampTransaction: { - create: { startTime: new Date(), status: "Success", amount: 20000, token: "token__1", provider: "HDFC Bank" }, + create: { + startTime: new Date(), + status: "Success", + amount: 20000, + token: "token__1", + provider: "HDFC Bank", + }, }, }, - }); + }) const Vikas = await prisma.user.upsert({ where: { number: '2222222222' }, @@ -81,87 +41,205 @@ async function main() { password: await bcrypt.hash('Vikas', 10), userpin: '5678', name: 'Vikas', - sessionToken: uuid(), - Balance: { create: { amount: 2000, locked: 0 } }, + sessionToken:uuid(), + Balance: { + create: { + amount: 2000, + locked: 0 + } + }, OnRampTransaction: { - create: { startTime: new Date(), status: "Failure", amount: 2000, token: "token__2", provider: "HDFC Bank" }, + create: { + startTime: new Date(), + status: "Failure", + amount: 2000, + token: "token__2", + provider: "HDFC Bank", + }, }, }, - }); + }) - /* ---------- MERCHANTS ---------- */ + // 🚨 MERCHANTS const merchantsData = [ - { email: "bses@merchant.com", name: "BSES Rajdhani Power Ltd", upiId: "upi-bses-rajdhani", auth_type: AuthType.Google }, - { email: "delhijal@merchant.com", name: "Delhi Jal Board", upiId: "upi-delhi-jal", auth_type: AuthType.Github }, - { email: "indane@merchant.com", name: "Indane Gas", upiId: "upi-indane-gas", auth_type: AuthType.Google }, - { email: "airtel@merchant.com", name: "Airtel Payments", upiId: "upi-airtel-payments", auth_type: AuthType.Google }, - { email: "tatasky@merchant.com", name: "Tata Sky DTH", upiId: "upi-tata-sky", auth_type: AuthType.Github }, - { email: "tpddl@merchant.com", name: "TPDDL Electricity", upiId: "upi-tpddl-electricity", auth_type: AuthType.Google }, - { email: "jio@merchant.com", name: "Jio Recharge", upiId: "upi-jio-recharge", auth_type: AuthType.Github }, + { + email: "bses@merchant.com", + name: "BSES Rajdhani Power Ltd", + upiId: "upi-bses-rajdhani", + auth_type: AuthType.Google, + }, + { + email: "delhijal@merchant.com", + name: "Delhi Jal Board", + upiId: "upi-delhi-jal", + auth_type: AuthType.Github, + }, + { + email: "indane@merchant.com", + name: "Indane Gas", + auth_type: AuthType.Google, + upiId: "upi-indane-gas", + }, + { + email: "airtel@merchant.com", + name: "Airtel Payments", + auth_type: AuthType.Google, + upiId: "upi-airtel-payments", + }, + { + email: "tatasky@merchant.com", + name: "Tata Sky DTH", + auth_type: AuthType.Github, + upiId: "upi-tata-sky", + }, + { + email: "tpddl@merchant.com", + name: "TPDDL Electricity", + auth_type: AuthType.Google, + upiId: "upi-tpddl-electricity", + }, + { + email: "jio@merchant.com", + name: "Jio Recharge", + auth_type: AuthType.Github, + upiId: "upi-jio-recharge", + }, ]; - await prisma.merchant.createMany({ data: merchantsData, skipDuplicates: true }); + + // Create merchants but skip duplicates when emails already exist + await prisma.merchant.createMany({ + data: merchantsData, + skipDuplicates: true, + }); + const merchantList = await prisma.merchant.findMany(); - /* ---------- P2P REQUESTS ---------- */ + // 🚨 P2P Requests await prisma.p2PRequest.createMany({ data: [ - { senderId: Nasir.id, receiverNumber: "2222222222", amount: 500, message: "Dinner split", status: "PENDING" }, - { senderId: Vikas.id, receiverNumber: "1111111111", amount: 200, message: "Movie ticket", status: "PENDING" }, + { + senderId: Nasir.id, + receiverNumber: "2222222222", + amount: 500, + message: "Dinner split", + status: "PENDING", + }, + { + senderId: Vikas.id, + receiverNumber: "1111111111", + amount: 200, + message: "Movie ticket", + status: "PENDING", + }, ], }); - /* ---------- P2P TRANSFERS ---------- */ + // 🚨 P2P Transfers await prisma.p2pTransfer.createMany({ data: [ - { amount: 1000, timestamp: new Date(), fromUserId: Nasir.id, toUserId: Vikas.id }, - { amount: 300, timestamp: new Date(Date.now() - 86400000), fromUserId: Vikas.id, toUserId: Nasir.id }, + { + amount: 1000, + timestamp: new Date(), + fromUserId: Nasir.id, + toUserId: Vikas.id, + }, + { + amount: 300, + timestamp: new Date(Date.now() - 86400000), + fromUserId: Vikas.id, + toUserId: Nasir.id, + }, ], }); - /* ---------- BILL SCHEDULES ---------- */ - const findMerchant = (name: string) => merchantList.find(m => m.name?.includes(name))?.id ?? null; + // 🚨 BILL SCHEDULES (with Merchant linking) + const findMerchant = (name: string) => + merchantList.find((m) => m.name?.includes(name))?.id ?? null; + await prisma.billSchedule.createMany({ data: [ - { userId: Nasir.id, merchantId: findMerchant("BSES"), billType: "ELECTRICITY", provider: "BSES Rajdhani", accountNo: "EL-123456789", amount: 751, dueDate: new Date(Date.now() + 3 * 86400000), nextPayment: new Date(Date.now() + 30 * 86400000) }, - { userId: Nasir.id, merchantId: findMerchant("Delhi Jal"), billType: "WATER", provider: "Delhi Jal Board", accountNo: "WB-987654321", amount: 320, dueDate: new Date(Date.now() + 7 * 86400000) }, - { userId: Nasir.id, merchantId: findMerchant("Indane"), billType: "GAS", provider: "Indane Gas", accountNo: "GAS-456789123", amount: 950, dueDate: new Date(Date.now() + 10 * 86400000) }, - { userId: Nasir.id, merchantId: findMerchant("Airtel"), billType: "PHONE_RECHARGE", provider: "Airtel", accountNo: "1111111111", amount: 299, dueDate: new Date(Date.now() + 20 * 86400000) }, - { userId: Nasir.id, merchantId: findMerchant("Tata Sky"), billType: "DTH", provider: "Tata Sky", accountNo: "DTH-789123456", amount: 451, dueDate: new Date(Date.now() + 25 * 86400000) }, - { userId: Vikas.id, merchantId: findMerchant("TPDDL"), billType: "ELECTRICITY", provider: "TPDDL", accountNo: "EL-456789123", amount: 420, dueDate: new Date(Date.now() + 5 * 86400000) }, - { userId: Vikas.id, merchantId: findMerchant("Jio"), billType: "PHONE_RECHARGE", provider: "Jio", accountNo: "2222222222", amount: 199, dueDate: new Date(Date.now() + 15 * 86400000) }, + { + userId: Nasir.id, + merchantId: findMerchant("BSES"), + billType: "ELECTRICITY", + provider: "BSES Rajdhani", + accountNo: "EL-123456789", + amount: 751, + dueDate: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), + nextPayment: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), + }, + { + userId: Nasir.id, + merchantId: findMerchant("Delhi Jal"), + billType: "WATER", + provider: "Delhi Jal Board", + accountNo: "WB-987654321", + amount: 320, + dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), + }, + { + userId: Nasir.id, + merchantId: findMerchant("Indane"), + billType: "GAS", + provider: "Indane Gas", + accountNo: "GAS-456789123", + amount: 950, + dueDate: new Date(Date.now() + 10 * 24 * 60 * 60 * 1000), + }, + { + userId: Nasir.id, + merchantId: findMerchant("Airtel"), + billType: "PHONE_RECHARGE", + provider: "Airtel", + accountNo: "1111111111", + amount: 299, + dueDate: new Date(Date.now() + 20 * 24 * 60 * 60 * 1000), + }, + { + userId: Nasir.id, + merchantId: findMerchant("Tata Sky"), + billType: "DTH", + provider: "Tata Sky", + accountNo: "DTH-789123456", + amount: 451, + dueDate: new Date(Date.now() + 25 * 24 * 60 * 60 * 1000), + }, + { + userId: Vikas.id, + merchantId: findMerchant("TPDDL"), + billType: "ELECTRICITY", + provider: "TPDDL", + accountNo: "EL-456789123", + amount: 420, + dueDate: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000), + }, + { + userId: Vikas.id, + merchantId: findMerchant("Jio"), + billType: "PHONE_RECHARGE", + provider: "Jio", + accountNo: "2222222222", + amount: 199, + dueDate: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000), + }, ], }); - /* ---------- REAL RECHARGE PLANS (ALL 23 CIRCLES) ---------- */ - const allPlans: any[] = []; - - for (const circle of circles) { - for (const p of jioPlans) allPlans.push({ operator: "Jio", circle, ...p }); - for (const p of airtelPlans) allPlans.push({ operator: "Airtel", circle, ...p }); - for (const p of viPlans) allPlans.push({ operator: "Vi", circle, ...p }); - } - - const payload = allPlans.map(p => ({ - operator: p.operator, - circle: p.circle, - amount: p.amount, - description: p.desc, - validity: p.validity, - planCode: `${p.operator}-${p.amount}-${p.circle.slice(0,3)}`.replace(/[^a-z0-9]/gi, '_'), - planType: p.amount < 200 ? "TOPUP" : "DATA", - })); - - await prisma.rechargePlan.deleteMany({}); // wipe old fake data - await prisma.rechargePlan.createMany({ data: payload, skipDuplicates: true }); - - console.log(`LIVE 2025 DATA SEEDED`); - console.log(` Circles : ${circles.length}`); - console.log(` Plans : ${payload.length} (Jio ${jioPlans.length} × 23, Airtel ${airtelPlans.length} × 23, Vi ${viPlans.length} × 23)`); + console.log(` +✅ SEEDED DATA: +📱 Users: Nasir & Vikas +🏦 Balances + OnRamp Transactions +💳 Merchants: ${merchantList.length} +🧾 BillSchedules: 7 linked to Merchants +💸 P2P Transfers & Requests Created +🚀 READY! + `); } -/* -------------------------------------------------------------------------- */ -/* RUN */ -/* -------------------------------------------------------------------------- */ main() .then(async () => await prisma.$disconnect()) - .catch(async e => { console.error(e); await prisma.$disconnect(); process.exit(1); }); \ No newline at end of file + .catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) + }) diff --git a/packages/ui/src/animated-testimonials.tsx b/packages/ui/src/animated-testimonials.tsx index 30949c9..36f21b4 100644 --- a/packages/ui/src/animated-testimonials.tsx +++ b/packages/ui/src/animated-testimonials.tsx @@ -47,7 +47,7 @@ export const AnimatedTestimonials = ({ if (testimonials.length === 0) return null; return ( -
+
@@ -96,7 +96,7 @@ export const AnimatedTestimonials = ({
-
+
-

+

{testimonials[active]?.name ?? ""}

-

+

{testimonials[active]?.designation ?? ""}

- + {(testimonials[active]?.description ?? "") .split(" ") .filter(Boolean)