+
+ Transfer
+
+
-}
\ No newline at end of file
+ );
+}
diff --git a/apps/user-app/app/lib/newTransactions/createOnRampTransactions.ts b/apps/user-app/app/lib/newTransactions/createOnRampTransactions.ts
new file mode 100644
index 0000000..246429a
--- /dev/null
+++ b/apps/user-app/app/lib/newTransactions/createOnRampTransactions.ts
@@ -0,0 +1,30 @@
+"use server";
+
+import { getServerSession } from "next-auth";
+import prisma from "@repo/db/client";
+import { authOptions } from "../auth";
+
+export default async function createOnRampTransactions(
+ amount: number,
+ provider: string
+) {
+ const session = await getServerSession(authOptions);
+ const userId = session?.user?.id;
+ if (!userId) {
+ message: "User not logged in";
+ }
+ const token = (Math.random() * 1000).toString();
+ await prisma.onRampTransaction.create({
+ data: {
+ provider,
+ status: "Processing",
+ userId: Number(session?.user?.id),
+ token: token,
+ amount: amount * 10,
+ startTime: new Date(),
+ },
+ });
+ return {
+ msg: "Done",
+ };
+}
diff --git a/apps/user-app/app/lib/p2ptransfer/p2p.ts b/apps/user-app/app/lib/p2ptransfer/p2p.ts
new file mode 100644
index 0000000..6870ee6
--- /dev/null
+++ b/apps/user-app/app/lib/p2ptransfer/p2p.ts
@@ -0,0 +1,55 @@
+"use server";
+import { getServerSession } from "next-auth";
+import { authOptions } from "../auth";
+import prisma from "@repo/db/client";
+
+export async function p2pTransfer(to: string, amount: number) {
+ const session = await getServerSession(authOptions);
+ const from = session?.user?.id;
+ if (!from) {
+ return {
+ message: "Error while sending",
+ };
+ }
+ const toUser = await prisma.user.findFirst({
+ where: {
+ number: to,
+ },
+ });
+
+ if (!toUser) {
+ return {
+ message: "User not found",
+ };
+ }
+
+ await prisma.$transaction(async (tx) => {
+ // await tx.$queryRaw`SELECT * FROM "Balance" WHERE "userId" = ${Number(from)} FOR UPDATE`;
+
+ const fromBalance = await tx.balance.findUnique({
+ where: { userId: Number(from) },
+ });
+
+ if (!fromBalance || fromBalance.amount < amount) {
+ throw new Error("Insufficient funds");
+ }
+
+ await tx.balance.update({
+ where: { userId: Number(from) },
+ data: { amount: { decrement: amount } },
+ });
+
+ await tx.balance.update({
+ where: { userId: toUser.id },
+ data: { amount: { increment: amount } },
+ });
+ await tx.p2pTransfer.create({
+ data: {
+ fromUserId: Number(from),
+ toUserId: toUser.id,
+ amount,
+ timestamp: new Date(),
+ },
+ });
+ });
+}
diff --git a/apps/user-app/components/AddMoneyCard.tsx b/apps/user-app/components/AddMoneyCard.tsx
index 562f021..cfc4a94 100644
--- a/apps/user-app/components/AddMoneyCard.tsx
+++ b/apps/user-app/components/AddMoneyCard.tsx
@@ -1,42 +1,65 @@
-"use client"
+"use client";
import { Button } from "@repo/ui/button";
import { Card } from "@repo/ui/card";
import { Center } from "@repo/ui/center";
import { Select } from "@repo/ui/select";
import { useState } from "react";
import { TextInput } from "@repo/ui/textinput";
+import createOnRampTransactions from "../app/lib/newTransactions/createOnRampTransactions";
-const SUPPORTED_BANKS = [{
+const SUPPORTED_BANKS = [
+ {
name: "HDFC Bank",
- redirectUrl: "https://netbanking.hdfcbank.com"
-}, {
+ redirectUrl: "https://netbanking.hdfcbank.com",
+ },
+ {
name: "Axis Bank",
- redirectUrl: "https://www.axisbank.com/"
-}];
+ redirectUrl: "https://www.axisbank.com/",
+ },
+];
export const AddMoney = () => {
- const [redirectUrl, setRedirectUrl] = useState(SUPPORTED_BANKS[0]?.redirectUrl);
- return
-
-
{
-
- }} />
-
- Bank
-
-
+
+ );
+};
diff --git a/apps/user-app/components/card-stack.tsx b/apps/user-app/components/card-stack.tsx
new file mode 100644
index 0000000..6274111
--- /dev/null
+++ b/apps/user-app/components/card-stack.tsx
@@ -0,0 +1,46 @@
+"use client";
+import { Button } from "@repo/ui/button";
+import { Card } from "@repo/ui/card";
+import { Center } from "@repo/ui/center";
+import { TextInput } from "@repo/ui/textinput";
+import { useState } from "react";
+import { p2pTransfer } from "../app/lib/p2ptransfer/p2p";
+
+export function SendCard() {
+ const [number, setNumber] = useState("");
+ const [amount, setAmount] = useState("");
+
+ return (
+
+
+
+
+
{
+ setNumber(value);
+ }}
+ />
+ {
+ setAmount(value);
+ }}
+ />
+
+
+ await p2pTransfer(number, Number(amount) * 100)
+ }
+ >
+ Send
+
+
+
+
+
+
+ );
+}
diff --git a/apps/user-app/components/p2pTransactions.tsx b/apps/user-app/components/p2pTransactions.tsx
new file mode 100644
index 0000000..6ac37f7
--- /dev/null
+++ b/apps/user-app/components/p2pTransactions.tsx
@@ -0,0 +1,39 @@
+import { Card } from "@repo/ui/card";
+const P2PTransaction = ({
+ transactions,
+}: {
+ transactions: {
+ time: Date;
+ amount: number;
+ toUserId: number;
+ }[];
+}) => {
+ if (!transactions.length) {
+ return (
+
+ No Recent transactions
+
+ );
+ }
+ return (
+
+
+ {transactions.map((t) => (
+
+
+
{`Send Money TO ${t.toUserId}`}
+
+ {t.time.toDateString()}
+
+
+
+ - Rs {t.amount / 100}
+
+
+ ))}
+
+
+ );
+};
+
+export default P2PTransaction;
diff --git a/apps/user-app/package.json b/apps/user-app/package.json
index 4f85ee6..4008439 100644
--- a/apps/user-app/package.json
+++ b/apps/user-app/package.json
@@ -15,6 +15,7 @@
"bcrypt": "^5.1.1",
"next": "^14.1.1",
"next-auth": "^4.24.7",
+ "prisma": "^5.14.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
diff --git a/apps/user-app/utils/cn.ts b/apps/user-app/utils/cn.ts
new file mode 100644
index 0000000..cec6ac9
--- /dev/null
+++ b/apps/user-app/utils/cn.ts
@@ -0,0 +1,6 @@
+import { ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/package-lock.json b/package-lock.json
index 1ba3d23..cf235b2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,11 @@
"apps/*",
"packages/*"
],
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "framer-motion": "^11.2.5",
+ "tailwind-merge": "^2.3.0"
+ },
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
@@ -85,6 +90,7 @@
"bcrypt": "^5.1.1",
"next": "^14.1.1",
"next-auth": "^4.24.7",
+ "prisma": "^5.14.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
@@ -103,6 +109,46 @@
"typescript": "^5.3.3"
}
},
+ "apps/user-app/node_modules/@prisma/debug": {
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.14.0.tgz",
+ "integrity": "sha512-iq56qBZuFfX3fCxoxT8gBX33lQzomBU0qIUaEj1RebsKVz1ob/BVH1XSBwwwvRVtZEV1b7Fxx2eVu34Ge/mg3w=="
+ },
+ "apps/user-app/node_modules/@prisma/engines": {
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.14.0.tgz",
+ "integrity": "sha512-lgxkKZ6IEygVcw6IZZUlPIfLQ9hjSYAtHjZ5r64sCLDgVzsPFCi2XBBJgzPMkOQ5RHzUD4E/dVdpn9+ez8tk1A==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@prisma/debug": "5.14.0",
+ "@prisma/engines-version": "5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48",
+ "@prisma/fetch-engine": "5.14.0",
+ "@prisma/get-platform": "5.14.0"
+ }
+ },
+ "apps/user-app/node_modules/@prisma/engines-version": {
+ "version": "5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48.tgz",
+ "integrity": "sha512-ip6pNkRo1UxWv+6toxNcYvItNYaqQjXdFNGJ+Nuk2eYtRoEdoF13wxo7/jsClJFFenMPVNVqXQDV0oveXnR1cA=="
+ },
+ "apps/user-app/node_modules/@prisma/fetch-engine": {
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.14.0.tgz",
+ "integrity": "sha512-VrheA9y9DMURK5vu8OJoOgQpxOhas3qF0IBHJ8G/0X44k82kc8E0w98HCn2nhnbOOMwbWsJWXfLC2/F8n5u0gQ==",
+ "dependencies": {
+ "@prisma/debug": "5.14.0",
+ "@prisma/engines-version": "5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48",
+ "@prisma/get-platform": "5.14.0"
+ }
+ },
+ "apps/user-app/node_modules/@prisma/get-platform": {
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.14.0.tgz",
+ "integrity": "sha512-/yAyBvcEjRv41ynZrhdrPtHgk47xLRRq/o5eWGcUpBJ1YrUZTYB8EoPiopnP7iQrMATK8stXQdPOoVlrzuTQZw==",
+ "dependencies": {
+ "@prisma/debug": "5.14.0"
+ }
+ },
"apps/user-app/node_modules/postcss": {
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
@@ -131,6 +177,21 @@
"node": "^10 || ^12 || >=14"
}
},
+ "apps/user-app/node_modules/prisma": {
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.14.0.tgz",
+ "integrity": "sha512-gCNZco7y5XtjrnQYeDJTiVZmT/ncqCr5RY1/Cf8X2wgLRmyh9ayPAGBNziI4qEE4S6SxCH5omQLVo9lmURaJ/Q==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@prisma/engines": "5.14.0"
+ },
+ "bin": {
+ "prisma": "build/index.js"
+ },
+ "engines": {
+ "node": ">=16.13"
+ }
+ },
"apps/web": {
"version": "1.0.0",
"extraneous": true,
@@ -3253,6 +3314,14 @@
"node": ">=0.8"
}
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -5374,6 +5443,30 @@
"url": "https://github.com/sponsors/rawify"
}
},
+ "node_modules/framer-motion": {
+ "version": "11.2.5",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.2.5.tgz",
+ "integrity": "sha512-X22i42hWY423wx2C1TlQlC4UnWonD+udND0qX1Fkt0dDlreSmuNY76obO6Y2d/UdJPhqVd5Zn6g1jAIwF6Xx9A==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -9786,6 +9879,18 @@
"url": "https://opencollective.com/unts"
}
},
+ "node_modules/tailwind-merge": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.3.0.tgz",
+ "integrity": "sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==",
+ "dependencies": {
+ "@babel/runtime": "^7.24.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
diff --git a/package.json b/package.json
index 10f09f0..9489cfd 100644
--- a/package.json
+++ b/package.json
@@ -20,5 +20,10 @@
"workspaces": [
"apps/*",
"packages/*"
- ]
+ ],
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "framer-motion": "^11.2.5",
+ "tailwind-merge": "^2.3.0"
+ }
}
diff --git a/packages/db/.env.example b/packages/db/.env.example
index fe001e3..ed7284f 100644
--- a/packages/db/.env.example
+++ b/packages/db/.env.example
@@ -1,2 +1,2 @@
-DATABASE_URL="postgresql://postgres:mysecretpassword@localhost:5432/postgres"
+DATABASE_URL="postgresql://ranu47243:Fg9sfTaItk5m@ep-withered-violet-a5esdc0g.us-east-2.aws.neon.tech/Demo?sslmode=require"
diff --git a/packages/db/prisma/migrations/20240521195456_add_p2p_transfer/migration.sql b/packages/db/prisma/migrations/20240521195456_add_p2p_transfer/migration.sql
new file mode 100644
index 0000000..f84afa1
--- /dev/null
+++ b/packages/db/prisma/migrations/20240521195456_add_p2p_transfer/migration.sql
@@ -0,0 +1,16 @@
+-- CreateTable
+CREATE TABLE "p2pTransfer" (
+ "id" SERIAL NOT NULL,
+ "amount" INTEGER NOT NULL,
+ "timestamp" TIMESTAMP(3) NOT NULL,
+ "fromUserId" INTEGER NOT NULL,
+ "toUserId" INTEGER NOT NULL,
+
+ CONSTRAINT "p2pTransfer_pkey" PRIMARY KEY ("id")
+);
+
+-- AddForeignKey
+ALTER TABLE "p2pTransfer" ADD CONSTRAINT "p2pTransfer_fromUserId_fkey" FOREIGN KEY ("fromUserId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "p2pTransfer" ADD CONSTRAINT "p2pTransfer_toUserId_fkey" FOREIGN KEY ("toUserId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index 05ad1f7..14ee162 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -15,6 +15,8 @@ model User {
password String
OnRampTransaction OnRampTransaction[]
Balance Balance[]
+ sentTransfers p2pTransfer[] @relation(name: "FromUserRelation")
+ receivedTransfers p2pTransfer[] @relation(name: "ToUserRelation")
}
model Merchant {
@@ -43,6 +45,16 @@ model Balance {
user User @relation(fields: [userId], references: [id])
}
+model p2pTransfer {
+ 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])
+}
+
enum AuthType {
Google
Github