Skip to content

Commit

Permalink
separating out payment UI + removing color overwrites + homogenizing …
Browse files Browse the repository at this point in the history
…styling | 3h
  • Loading branch information
svub committed Oct 9, 2024
1 parent b305820 commit 6eb78cc
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 75 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
},
"packageManager": "[email protected]",
"dependencies": {
"eslint-plugin-react": "^7.37.1"
"eslint-plugin-react": "^7.37.1",
"qrcode.react": "^4.0.1"
}
}
158 changes: 84 additions & 74 deletions packages/nextjs/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use client";

import { useState } from "react";
import { useEffect, useState } from "react";
import type { NextPage } from "next";
import { TextEncoder } from "util";
import QRCode from "qrcode.react";
import { parseEther } from "viem";
import { EtherInput } from "~~/components/scaffold-eth";
import { useScaffoldWatchContractEvent, useScaffoldWriteContract } from "~~/hooks/scaffold-eth";
import { SupportedCurrencies, createPaymentLink } from "~~/utils/link";

declare global {
interface Window {
Expand All @@ -18,51 +18,32 @@ const Home: NextPage = () => {
const [secret, setSecret] = useState("");
const [paymentReference, setPaymentReference] = useState("");
const [paymentAmount, setPaymentAmount] = useState("");
const [paymentReceipt, setPaymentReceipt] = useState("");
const [latestReference, setLastestReference] = useState("");
const [paymentLink, setPaymentLink] = useState("");
const [paymentCurrency, setPaymentCurrency] = useState<SupportedCurrencies>("ETH");
const [returnAddress, setReturnAddress] = useState("");
const [returnAmount, setReturnAmount] = useState("");
const { writeContractAsync } = useScaffoldWriteContract("NunyaBusiness");

const encoder: TextEncoder = new global.TextEncoder();
const decoder: TextDecoder = new global.TextDecoder();
// const encoder: TextEncoder = new global.TextEncoder();

useScaffoldWatchContractEvent({
contractName: "NunyaBusiness",
eventName: "reference-created",
// TODO
// eventName: "reference-created",
eventName: "Request_success",
onLogs: logs => {
logs.map(log => {
const { referenceBytes } = log.args;
const reference: string = decoder.decode(referenceBytes as ArrayBuffer);
logs.forEach(() => {
// const { referenceBytes } = log.args;
const reference = "123"; // decoder.decode(referenceBytes as ArrayBuffer);
console.log("📡 Reference " + reference + " created");
setLastestReference(reference);
setPaymentReference(reference);
});
},
});

const handlePay = async (event: React.FormEvent) => {
event.preventDefault();

await writeContractAsync({
functionName: "pay",
value: parseEther(paymentAmount),
args: [encoder.encode(secret), returnAddress],
});
};

useScaffoldWatchContractEvent({
contractName: "NunyaBusiness",
eventName: "payment-receipt-received",
onLogs: logs => {
logs.map(log => {
const { receiptBytes } = log.args;
// TODO extract the huamn readable part, show the bytes somehow (v2: show as QR code or write as file for book keeping)
const receipt: string = decoder.decode(receiptBytes as ArrayBuffer);
console.log("📡 Payment Receipt " + receipt + " created");
setPaymentReceipt(receipt);
});
},
});
useEffect(() => {
const link = createPaymentLink(paymentReference, paymentAmount, paymentCurrency);
setPaymentLink(link);
}, [paymentReference, paymentAmount, paymentCurrency]);

const handleWithdrawal = async (event: React.FormEvent) => {
event.preventDefault();
Expand All @@ -72,85 +53,114 @@ const Home: NextPage = () => {

// const encryptedSecret: ArrayBuffer = await encrypt(encoder.encode(secret));
await writeContractAsync({
functionName: "withdraw",
value: parseEther(returnAmount),
args: [encoder.encode(secret), returnAddress],
// TODO
// functionName: "withdraw",
functionName: "withdrawTo",
// value: parseEther(returnAmount),
// TODO args: [encoder.encode(secret) ...
args: [secret, parseEther(returnAmount), returnAddress],
});
};

return (
<>
<div className="bg-gray-50 min-h-screen flex flex-col justify-center py-10">
<div className="bg-base-200 min-h-screen flex flex-col justify-center py-10 w-full">
<h1 className="text-center">
<span className="block text-5xl font-bold text-gray-800">Nunya.business</span>
<span className="block text-3xl text-gray-600 mb-4">Receive Business Payments</span>
<span className="block text-5xl font-bold">Nunya.business</span>
<span className="block text-3xl bg-base-100 mb-4">Receive Business Payments</span>
</h1>
<div className="flex justify-center items-center space-x-2 flex-col sm:flex-row mb-8">
<p className="my-2 font-medium text-gray-700">... while not revealing what you earned from others.</p>
<p className="my-2 font-medium text-base">... without revealing to other clients what you&apos;ve earned.</p>
</div>

<h3 className="text-center text-2xl text-gray-800 mt-8">Make a Payment</h3>
<form onSubmit={handlePay} className="flex flex-col items-center mb-8 mx-5 space-y-4">
<div className="flex items-center justify-center space-x-3">
<input
className="border border-gray-300 bg-white text-gray-900 p-3 w-full md:w-1/2 lg:w-1/3 rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
value={paymentReference}
placeholder="Your secret..."
onChange={e => setPaymentReference(e.target.value)}
/>
<EtherInput value={paymentAmount} onChange={amount => setPaymentAmount(amount)} />
<button
className="btn bg-blue-600 text-white hover:bg-blue-700 transition duration-200 p-3 rounded-md"
type="submit"
>
Pay Bill Now
</button>
</div>
<p className="text-center text-gray-600">Receipt: {paymentReceipt}</p>
</form>
<p className="text-base">Create a payment link and add it to your invoice to start receiving payments.</p>

<p>Or start receiving your own payments now:</p>

<h2 className="text-center text-2xl text-gray-800 mt-8">Create Reference</h2>
<h2 className="text-center text-2xl mt-8">Create Payment Link</h2>
<form onSubmit={handleWithdrawal} className="flex flex-col items-center mb-8 mx-5 space-y-4">
<div className="flex items-center justify-center space-x-3">
<div className="flex flex-col space-y-3">
<input
className="border border-gray-300 bg-white text-gray-900 p-3 w-full md:w-1/2 lg:w-1/3 rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
className="border bg-base-100 p-3 w-full rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
value={secret}
placeholder="Your secret..."
onChange={e => setSecret(e.target.value)}
/>
<input
className="border bg-base-100 p-3 w-full rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
value={paymentAmount}
placeholder="How much do you charge?"
onChange={e => setPaymentAmount(e.target.value)}
/>
<div className="flex flex-row space-x-3">
<div>
<input
type="radio"
id="ethCurrency"
checked={paymentCurrency == "ETH"}
name="currency"
onChange={() => setPaymentCurrency("ETH")}
/>
<label htmlFor="ethCurrency" className="text-lg">
ETH
</label>
</div>
<div>
<input
type="radio"
id="usdCurrency"
checked={paymentCurrency == "USD"}
name="currency"
onChange={() => setPaymentCurrency("USD")}
/>
<label htmlFor="usdCurrency" className="text-lg">
USD
</label>
</div>
</div>
<button
className="btn bg-blue-600 text-white hover:bg-blue-700 transition duration-200 p-3 rounded-md"
type="submit"
>
Create New Billing Reference
Create new Payment Link
</button>
</div>
<p className="text-center text-gray-600">Reference: {latestReference}</p>
{paymentReference ? (
<div>
<p className="text-center text-gray-600">
Reference: {paymentReference}, amount: {paymentAmount}, currency: {paymentCurrency}, payment link:{" "}
<a href={paymentLink} target="_blank">
{paymentLink}
</a>
</p>
<p className="text-center text-gray-600">
QR code: <QRCode value={paymentLink} size={256} />
</p>
</div>
) : (
<></>
)}
</form>

<h2 className="text-center text-2xl text-gray-800 mt-8">Withdraw</h2>
<h2 className="text-center text-2xl mt-8">Withdraw</h2>
<form onSubmit={handleWithdrawal} className="flex flex-col items-center mb-8 mx-5 space-y-4">
<div className="flex flex-col space-y-3">
<input
className="border border-gray-300 bg-white text-gray-900 p-3 w-full rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
className="border bg-base-100 p-3 w-full rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
value={secret}
placeholder="Your secret..."
onChange={e => setSecret(e.target.value)}
/>
<input
className="border border-gray-300 bg-white text-gray-900 p-3 w-full rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
className="border bg-base-100 p-3 w-full rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
value={returnAmount}
placeholder="Amount to withdraw..."
onChange={e => setReturnAmount(e.target.value)}
/>
<input
className="border border-gray-300 bg-white text-gray-900 p-3 w-full rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
className="border bg-base-100 p-3 w-full rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
value={returnAddress}
placeholder="Return address..."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"use client";

import { useEffect, useState } from "react";
import type { NextPage } from "next";
import { EtherInput } from "~~/components/scaffold-eth";
import { useScaffoldWatchContractEvent, useTargetNetwork } from "~~/hooks/scaffold-eth";
import { fetchPriceFromUniswap } from "~~/utils/scaffold-eth";

declare global {
interface Window {
temp: unknown;
}
}

type PageProps = {
params: {
paymentReference: string;
amount: number;
currency: string;
};
};

const PaymentPage: NextPage<PageProps> = ({ params }: PageProps) => {
const paymentReferenceParam = params?.paymentReference as string;
const amountParam = params?.amount as number;
const currencyParam = params?.currency as string;

const [reference, setReference] = useState("");
const [amount, setAmount] = useState("");
// const [currency, setCurrency] = useState("ETH");
const [receipt, setReceipt] = useState(""); // TODO define struct of receipt
// const { writeContractAsync } = useScaffoldWriteContract("NunyaBusiness");
const { targetNetwork } = useTargetNetwork();
// const encoder: TextEncoder = new global.TextEncoder();

useEffect(() => {
setReference(paymentReferenceParam);

if (currencyParam === "USD") {
// convert amount to ETH
fetchPriceFromUniswap(targetNetwork).then(price => {
console.log("Convert to ETH", amountParam, price, amountParam / price);
if (price > 0) setAmount(amountParam / price + "");
else console.error("Couldn't fetch ETH price.");
});
} else {
setAmount(amountParam + "");
}

// setCurrency(currencyParam || "ETH");
}, []);

const handlePay = async (event: React.FormEvent) => {
event.preventDefault();

// TODO
// await writeContractAsync({
// functionName: "pay",
// value: parseEther(amount),
// args: [encoder.encode(reference)],
// });
};

useScaffoldWatchContractEvent({
contractName: "NunyaBusiness",
// TODO
// eventName: "payment-receipt-received",
eventName: "Request_success",
onLogs: logs => {
logs.forEach(() => {
// const { receiptBytes } = log.args;
// TODO extract the huamn readable part, show the bytes somehow (v2: show as QR code or write as file for book keeping)
const receipt = "123"; // decoder.decode(receiptBytes as ArrayBuffer);
console.log("📡 Payment Receipt " + receipt + " created");
setReceipt(receipt);
});
},
});

return (
<>
<div className="bg-base-200 min-h-screen flex flex-col justify-center py-10 w-full">
<h1 className="text-center">
<span className="block text-5xl font-bold">Nunya.business</span>
<span className="block text-3xl bg-base-100 mb-4">Receive Business Payments</span>
</h1>
<div className="flex justify-center items-center space-x-2 flex-col sm:flex-row mb-8">
<p className="my-2 font-medium text-base">... without revealing to other clients what you&apos;ve earned.</p>
</div>

<h3 className="text-center text-2xl mt-8">Make a Payment</h3>
<form onSubmit={handlePay} className="flex flex-col items-center mb-8 mx-5 space-y-4">
<div className="flex flex-col items-center justify-center space-y-3">
<input
className="border bg-base-100 p-3 w-full rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500"
type="text"
value={reference}
placeholder="Your payment reference..."
onChange={e => setReference(e.target.value)}
/>
<EtherInput value={amount} onChange={amount => setAmount(amount)} />
<button
className="btn bg-blue-600 text-white hover:bg-blue-700 transition duration-200 p-3 rounded-md"
type="submit"
>
Pay Bill Now
</button>
</div>
{receipt ? (
<>
<p className="text-center">Receipt: {receipt}</p>
</>
) : (
<></>
)}
</form>
</div>
</>
);
};

export default PaymentPage;
16 changes: 16 additions & 0 deletions packages/nextjs/utils/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type SupportedCurrencies = "ETH" | "USD";

export function createPaymentLink(
reference: string,
amount: string | number = 0,
currency: SupportedCurrencies = "ETH",
): string {
const root = global.location.origin;
const route = "pay";
const elements = [root, route, reference];
if (amount) {
elements.push(amount + "", currency);
}
const link = elements.join("/");
return link;
}

0 comments on commit 6eb78cc

Please sign in to comment.