Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Payout method #876

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tooltip": "^1.0.7",
"@types/bcrypt": "^5.0.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- CreateTable
CREATE TABLE "UPIWallet" (
"id" SERIAL NOT NULL,
"parentId" TEXT NOT NULL,
"address" TEXT NOT NULL,
"addedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "UPIWallet_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "SolanaWallet" (
"id" SERIAL NOT NULL,
"parentId" TEXT NOT NULL,
"address" TEXT NOT NULL,
"addedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "SolanaWallet_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "UPIWallet" ADD CONSTRAINT "UPIWallet_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "SolanaWallet" ADD CONSTRAINT "SolanaWallet_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
Warnings:

- A unique constraint covering the columns `[address]` on the table `SolanaWallet` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[address]` on the table `UPIWallet` will be added. If there are existing duplicate values, this will fail.

*/
-- CreateIndex
CREATE UNIQUE INDEX "SolanaWallet_address_key" ON "SolanaWallet"("address");

-- CreateIndex
CREATE UNIQUE INDEX "UPIWallet_address_key" ON "UPIWallet"("address");
18 changes: 18 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ model User {
questions Question[]
answers Answer[]
certificate Certificate[]
upis UPIWallet[]
solanaAddresses SolanaWallet[]
}

model DiscordConnect {
Expand Down Expand Up @@ -274,6 +276,22 @@ model Vote {
@@unique([commentId, userId])
}

model UPIWallet {
id Int @id @default(autoincrement())
parentId String
parent User @relation(fields: [parentId], references: [id])
address String @unique
addedAt DateTime @default(now())
}

model SolanaWallet {
id Int @id @default(autoincrement())
parentId String
parent User @relation(fields: [parentId], references: [id])
address String @unique
addedAt DateTime @default(now())
}

enum VoteType {
UPVOTE
DOWNVOTE
Expand Down
71 changes: 71 additions & 0 deletions src/app/api/addpayout/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import db from '@/db';
import { authOptions } from '@/lib/auth';
import { getServerSession } from 'next-auth';
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';

const requestBodySchema = z.object({
payoutType: z.enum(['upi', 'solana']),
address: z.string(),
});

async function handleCreateUpiAddress(userId: string, address: string) {
return db.user.update({
where: { id: userId },
data: {
upis: {
create: { address },
},
},
});
}

async function handleCreateSolanaAddress(userId: string, address: string) {
return db.user.update({
where: { id: userId },
data: {
solanaAddresses: {
create: { address },
},
},
});
}

export async function POST(req: NextRequest) {
try {
const parsedBody = requestBodySchema.safeParse(await req.json());
const session = await getServerSession(authOptions);

if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

if (!parsedBody.success) {
return NextResponse.json(
{ error: parsedBody.error.message },
{ status: 400 },
);
}

const { payoutType, address } = parsedBody.data;
const userId = session.user.id;

if (payoutType === 'upi') {
await handleCreateUpiAddress(userId, address);
return NextResponse.json({ message: 'UPI added successfully!' });
} else if (payoutType === 'solana') {
await handleCreateSolanaAddress(userId, address);
return NextResponse.json({
message: 'Solana address added successfully!',
});
}
} catch (err) {
return NextResponse.json(
{
error:
err instanceof Error ? err.message : 'An unexpected error occurred',
},
{ status: 500 },
);
}
}
82 changes: 82 additions & 0 deletions src/app/api/deletewallet/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import db from '@/db';
import { authOptions } from '@/lib/auth';
import { getServerSession } from 'next-auth';
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';

const deleteWalletSchema = z.object({
payoutType: z.enum(['upi', 'solana']),
id: z.number(),
});

export const handleDeleteUpiAddress = async (userId: string, id: number) => {
return db.user.update({
where: {
id: userId,
},
data: {
upis: {
delete: {
id,
},
},
},
});
};

export const handleDeleteSolanaAddress = async (userId: string, id: number) => {
return db.user.update({
where: {
id: userId,
},
data: {
solanaAddresses: {
delete: {
id,
},
},
},
});
};

export async function POST(req: NextRequest) {
try {
const session = await getServerSession(authOptions);

if (!session || !session.user) {
return NextResponse.json({
error: 'Unauthorized!',
});
}

const parsedBody = deleteWalletSchema.safeParse(await req.json());

if (!parsedBody.success) {
return NextResponse.json(
{ error: parsedBody.error.message },
{ status: 400 },
);
}

const { payoutType, id } = parsedBody.data;
const userId = session.user.id;

if (payoutType === 'upi') {
await handleDeleteUpiAddress(userId, id);
return NextResponse.json({ message: 'UPI added successfully!' });
} else if (payoutType === 'solana') {
await handleDeleteSolanaAddress(userId, id);
return NextResponse.json({
message: 'Solana address added successfully!',
});
}
} catch (err) {
return NextResponse.json(
{
error:
err instanceof Error ? err.message : 'An unexpected error occurred',
},
{ status: 500 },
);
}
}
32 changes: 32 additions & 0 deletions src/app/api/getwallets/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import prisma from '@/db';
import { authOptions } from '@/lib/auth';
import { getServerSession } from 'next-auth';
import { NextResponse } from 'next/server';

export async function GET() {
const session = await getServerSession(authOptions);

if (!session || !session.user) {
return NextResponse.json(
{
message: 'Unauthorized!',
},
{ status: 403 },
);
}

const wallets = await prisma.user.findFirst({
where: {
id: session.user.id,
},
select: {
upis: true,
solanaAddresses: true,
},
});

return NextResponse.json({
upiWallets: wallets?.upis,
solanaWallets: wallets?.solanaAddresses,
});
}
2 changes: 0 additions & 2 deletions src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
'use client';

import { Button } from '@/components/ui/button';
import { InfoIcon } from 'lucide-react';
import Link from 'next/link';
import { useEffect } from 'react';

export default function ErrorPage({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
Expand Down
11 changes: 11 additions & 0 deletions src/app/payouts/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { AddPayout } from '@/components/AddNewPayout';
import { Wallets } from '@/components/WalletDialog';

export default async function Payout() {
return (
<main className="flex flex-col items-center justify-center">
<AddPayout />
<Wallets />
</main>
);
}
62 changes: 62 additions & 0 deletions src/components/AddNewPayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use client';

import { useState } from 'react';
import { Input } from './ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Label } from '@/components/ui/label';
import { Button } from './ui/button';
import { useWallets } from '@/hooks/useWallets';

export const AddPayout = () => {
type payoutType = 'upi' | 'solana';
const [payoutMethod, setPayoutMethod] = useState<payoutType>();
const [address, setAddress] = useState('');
const { addWallet } = useWallets();

const handleValueChange = (value: payoutType) => {
setPayoutMethod(value);
};

const handleWalletAdd = async () => {
await addWallet(payoutMethod!, address);
};

return (
<diV className="mx-auto my-10 space-y-5 rounded-md border-2 p-10 sm:max-w-[300px] md:max-w-[450px]">
<Label htmlFor="address" className="text-xl">
Enter details of wallet to add
</Label>
<div className="space-y-5">
<div>
<Select onValueChange={handleValueChange}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select method" />
</SelectTrigger>
<SelectContent>
<SelectItem value="upi">UPI</SelectItem>
<SelectItem value="solana">Solana</SelectItem>
</SelectContent>
</Select>
</div>
<Input
onChange={(e) => {
setAddress(e.target.value);
}}
placeholder="Enter your address..."
id="address"
></Input>
</div>
<div className="flex justify-center">
<Button className="min-w-52" onClick={handleWalletAdd}>
Add {payoutMethod === 'upi' ? 'UPI' : 'Solana'}
</Button>
</div>
</diV>
);
};
41 changes: 41 additions & 0 deletions src/components/Address.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import React from 'react';
import { useWallets } from '@/hooks/useWallets';

export function WalletAddress({
address,
typeOfWallet,
id,
}: {
address: string;
typeOfWallet: 'upi' | 'solana';
id: number;
}) {
const { deleteWallet } = useWallets();

const handleDelete = async () => {
await deleteWallet(typeOfWallet, id);
};
return (
<Card className="w-full max-w-md">
<CardContent className="grid gap-4">
<div className="mb-3 mt-5 flex items-center justify-between rounded-md border border-muted bg-background px-4 py-3">
<div className="text-slate-300text-muted-foreground text-sm">
{address}
</div>
</div>
<div className="flex justify-between">
<Button
variant={'destructive'}
className="w-full max-w-[150px]"
onClick={handleDelete}
>
Delete wallet
</Button>
<Button className="w-full max-w-[150px]">Payout</Button>
</div>
</CardContent>
</Card>
);
}
Loading
Loading