Skip to content

Commit

Permalink
Create View Payment split page (#148)
Browse files Browse the repository at this point in the history
* Add view payment split page

* Get the meta data of a split

* Force commit

* Working version of getting meta data

* Add SplitsRecipients from data base

* Make create payment modal insert dummy payment address

* Fetch profile image based on wallet address

* Move balance to own comp

* Create splits balances

* Fix buttons being disabled, switch provider chain

* fix pr comments

* Add ALCHEMY providers

* Fix splits create and splits show balance

* implement distribute funds

* Fix pr comments

* Revert comment
  • Loading branch information
AlecErasmus authored May 15, 2024
1 parent cefd5a8 commit 7a16466
Show file tree
Hide file tree
Showing 19 changed files with 926 additions and 89 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"lint:fix": "eslint \"{src/components,src/app}/*/\" --ext=.ts,.tsx --fix"
},
"dependencies": {
"@0xsplits/splits-sdk-react": "^1.3.3",
"@0xsplits/splits-sdk-react": "2.0.3-beta.0",
"@ethereum-attestation-service/eas-sdk": "1.5.0",
"@mdx-js/loader": "^3.0.0",
"@mdx-js/react": "^3.0.0",
Expand Down Expand Up @@ -41,6 +41,7 @@
"@tanstack/react-table": "^8.11.8",
"@types/mdx": "^2.0.11",
"@vercel/analytics": "^1.2.2",
"alchemy-sdk": "^3.3.1",
"axios": "^1.6.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
Expand Down
19 changes: 15 additions & 4 deletions src/app/(dashboard)/projects/[projectId]/payments/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";

import { PaymentAddressDto } from "@/app/api/payments/service/paymentAddressService";
import PaymentsOnboarding from "@/components/payments/paymentsOnboarding";
import axios from "axios";
import { useState } from "react";
import { useEffect, useState } from "react";
import PaymentsView from "@/components/payments/paymentView";
import { PaymentAddressDto } from "@/app/api/payments/route";

interface PageProps {
params: { projectId: string };
Expand All @@ -22,12 +23,22 @@ export default function ProjectPaymentsPage({ params }: PageProps) {
}
};

useEffect(() => {
handleFetchProjectPaymentAddress();
}, []);

return (
<>
{projectPaymentAddress != null ? (
<>WIP: Payment coming soon</>
<PaymentsView
projectId={projectId}
paymentAddress={projectPaymentAddress}
></PaymentsView>
) : (
<PaymentsOnboarding projectId={projectId}></PaymentsOnboarding>
<PaymentsOnboarding
projectId={projectId}
onCreate={setProjectPaymentAddress}
></PaymentsOnboarding>
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
import {
PaymentAddressDto,
PaymentRecipientDto,
} from "@/app/api/payments/route";
import { PaymentSplitDto } from "@/app/api/payments/service/paymentSplitsService";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { useSplitsClient } from "@0xsplits/splits-sdk-react";
import { useAccount } from "wagmi";
import {
Dialog,
DialogClose,
DialogContent,
DialogTrigger,
} from "@/components/ui/dialog";
import { SplitRecipient, useCreateSplit } from "@0xsplits/splits-sdk-react";
import { useEffect, useState } from "react";
import { useAccount, useChainId } from "wagmi";
import axios from "axios";

type CreatePaymentAddressModalProps = {
projectId: string;
paymentSplits: PaymentSplitDto[];
onCreate: (param: PaymentAddressDto) => void;
};

export function CreatePaymentAddressModal({
projectId,
paymentSplits,
onCreate,
}: CreatePaymentAddressModalProps) {
const account = useAccount();
const chainId = useChainId();
const [paymentAddressRecipients, setPaymentAddressRecipients] = useState<
PaymentRecipientDto[]
>([]);
const { createSplit, status, error, splitAddress } = useCreateSplit();

const splitsClient = useSplitsClient({
// TODO: Use connected chainId
chainId: 8453,
publicClient: window.ethereum!,
});

const handleCreateSplit = async () => {
const recipients = paymentSplits
function filterAndMapSplitRecipient() {
return paymentSplits
.filter((split) => {
return split.paymentSplit != 0 || split.walletAddress == undefined;
})
Expand All @@ -32,28 +44,62 @@ export function CreatePaymentAddressModal({
split.paymentSplit!.toPrecision(2),
),
}));
}

function filterAndMapArmitageRecipient(splitRecipient: SplitRecipient[]) {
return splitRecipient
.map((paymentRecipient) => ({
wallet_address: paymentRecipient.address,
payment_percentage: paymentRecipient.percentAllocation,
}))
.filter((recipient) => {
return !(
recipient.payment_percentage == 0 ||
recipient.wallet_address == undefined
);
});
}

const handleCreateSplit = async () => {
const recipients = filterAndMapSplitRecipient();
const createSplitReq = {
recipients: recipients.filter(
(recipient) => recipient.address != undefined,
),
distributorFeePercent: 0,
controller: account.address,
};
try {
const args = {
splitAddress: "0x881985d5B0690598b84bcD7348c4A8c842e79419",
};
const response = await splitsClient.getSplitMetadata(args);
console.log(response);
} catch (error) {
console.log(error);
setPaymentAddressRecipients(filterAndMapArmitageRecipient(recipients));
createSplit(createSplitReq);
};

const handleSplitComplete = async () => {
const paymentAddress: PaymentAddressDto = {
chain_id: chainId.toString(),
team_id: projectId,
wallet_address: splitAddress!,
payment_receipents: paymentAddressRecipients,
};
const { data } = await axios.post(
`/api/payments?team_id=${projectId}`,
paymentAddress,
);
if (data.success) {
onCreate(data.paymentAddress);
}
};

function missingContributionWallets(): number {
return paymentSplits.filter((split) => split.walletAddress == null).length;
}

useEffect(() => {
console.log(`PayAddressUpdate: status[${status}] error[${error}]`);
if (status == "complete") {
handleSplitComplete();
}
}, [status]);

return (
<Dialog>
<DialogTrigger asChild>
Expand Down Expand Up @@ -82,23 +128,34 @@ export function CreatePaymentAddressModal({
)}
<div className="pt-6">
<div className="flex justify-between">
<Button
className="w-1/2 mr-2"
onClick={() => {
console.log("Hi");
}}
>
Cancel
</Button>
<DialogClose asChild>
<Button
className="w-1/2 mr-2"
disabled={!(status == null || status == "error")}
>
Cancel
</Button>
</DialogClose>

<Button
className="w-1/2 ml-2"
disabled={!account.isConnected}
disabled={
!(
account.isConnected &&
(status == null || status == "error")
)
}
onClick={() => {
handleCreateSplit();
}}
>
<>Continue</>
{status == "pendingApproval" ? (
<>Waiting for approval</>
) : status == "txInProgress" ? (
<>In progress</>
) : (
<>Continue</>
)}
</Button>
</div>
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/app/(dashboard)/utils/stringUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function truncateString(str: string, num: number) {
if (str.length <= num * 2) {
return str;
} else {
return str.slice(0, num) + "..." + str.slice(-num);
}
}
47 changes: 45 additions & 2 deletions src/app/api/payments/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,51 @@
import { getServerSession } from "next-auth";
import { NextRequest, NextResponse } from "next/server";
import { options } from "../auth/[...nextauth]/options";
import { fetchTeamPaymentAddresses } from "./service/paymentAddressService";
import {
createPaymentAddress,
fetchTeamPaymentAddresses,
} from "./service/paymentAddressService";

export type PaymentAddressDto = {
id?: string;
chain_id: string;
team_id: string;
wallet_address: string;
created_at?: Date;
payment_receipents: PaymentRecipientDto[];
};

export type PaymentRecipientDto = {
id?: string;
wallet_address: string;
payment_percentage: number;
};

export async function POST(req: NextRequest) {
const session = await getServerSession(options);
const teamId = req.nextUrl.searchParams.get("team_id");
const paymentAddressRequest = (await req.json()) as PaymentAddressDto;
if (session?.userId && teamId && paymentAddressRequest) {
const paymentAddress = await createPaymentAddress(
session?.userId,
teamId,
paymentAddressRequest,
);
if (paymentAddress) {
return NextResponse.json({
success: true,
paymentAddress: paymentAddress,
});
}
return NextResponse.json({
success: false,
});
} else {
return NextResponse.json({
success: false,
});
}
}

export async function GET(req: NextRequest) {
const session = await getServerSession(options);
Expand All @@ -15,7 +59,6 @@ export async function GET(req: NextRequest) {
} else {
return NextResponse.json({
success: false,
paymentAddress: [],
});
}
}
66 changes: 52 additions & 14 deletions src/app/api/payments/service/paymentAddressService.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
import prisma from "db";
import { PaymentAddressDto } from "../route";

export type PaymentAddressDto = {
id: string;
chain_id: string;
team_id: string;
wallet_address: string;
created_at: Date;
payment_receipents: PaymentRecipientDto[];
};
export async function createPaymentAddress(
userId: string,
teamId: string,
paymentAddress: PaymentAddressDto,
): Promise<PaymentAddressDto | undefined> {
try {
const foundTeam = await prisma.team.findFirst({
where: {
id: teamId,
owner_user_id: userId,
},
});
if (foundTeam == null) {
console.error(
`Team was not found for user userId:[${userId}] teanId:[${teamId}]`,
);
return;
}

export type PaymentRecipientDto = {
id: string;
wallet_address: string;
payment_percentage: number;
};
const createdPaymentAddress = await prisma.paymentAddress.create({
data: {
chain_id: paymentAddress.chain_id,
wallet_address: paymentAddress.wallet_address,
team_id: teamId,
},
});

if (!createdPaymentAddress) {
return;
} else {
await prisma.paymentRecipient.createMany({
data: paymentAddress.payment_receipents.map((receipent) => ({
...receipent,
payment_address_id: createdPaymentAddress.id,
})),
});
const paymentRecipients = await prisma.paymentRecipient.findMany({
where: {
payment_address_id: createdPaymentAddress.id,
},
});
return {
...createdPaymentAddress,
payment_receipents: paymentRecipients ? paymentRecipients : [],
};
}
} catch (error) {
console.error(error);
return;
}
}

export async function fetchTeamPaymentAddresses(
teamId: string,
Expand All @@ -38,7 +76,7 @@ export async function fetchTeamPaymentAddresses(
};
}
} catch (error) {
console.log(error);
console.error(error);
return;
}
}
Loading

0 comments on commit 7a16466

Please sign in to comment.