ui: fix policy and terms acceptance ui and use dialog for invest

This commit is contained in:
Sosokker 2024-11-21 11:56:49 +07:00
parent 91cd786eb6
commit 5c5c736ce8
3 changed files with 203 additions and 83 deletions

View File

@ -0,0 +1,13 @@
import { Separator } from "@/components/ui/separator";
export function InvestmentAmountInfo({ amount }: { amount: number }) {
return (
<div className="mt-4">
<span className="flex flex-row justify-between">
<p>Amount to be paid</p>
<p>${amount}</p>
</span>
<Separator />
</div>
);
}

View File

@ -0,0 +1,60 @@
"use client";
import React from "react";
import { Elements } from "@stripe/react-stripe-js";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import CheckoutPage from "./checkoutPage";
import convertToSubcurrency from "@/lib/convertToSubcurrency";
import { Button } from "@/components/ui/button";
interface PaymentDialogProps {
open: boolean;
// eslint-disable-next-line no-unused-vars
onOpenChange: (open: boolean) => void;
amount: number;
stripePromise: Promise<any>;
isAcceptTermAndService: () => boolean;
projectId: number;
investorId: string;
}
export function PaymentDialog({
open,
onOpenChange,
amount,
stripePromise,
isAcceptTermAndService,
projectId,
investorId,
}: PaymentDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Payment Information</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<p>Proceed with the payment to complete your investment.</p>
<Elements
stripe={stripePromise}
options={{
mode: "payment",
amount: convertToSubcurrency(amount),
currency: "usd",
}}
>
<CheckoutPage
amount={amount}
isAcceptTermAndService={isAcceptTermAndService}
project_id={projectId}
investor_id={investorId}
/>
</Elements>
</div>
<DialogFooter>
<Button onClick={() => onOpenChange(false)}>Cancel</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@ -7,13 +7,17 @@ import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useQuery } from "@supabase-cache-helpers/postgrest-react-query"; import { useQuery } from "@supabase-cache-helpers/postgrest-react-query";
import convertToSubcurrency from "@/lib/convertToSubcurrency"; // import convertToSubcurrency from "@/lib/convertToSubcurrency";
import CheckoutPage from "./checkoutPage"; // import CheckoutPage from "./checkoutPage";
import { Elements } from "@stripe/react-stripe-js"; // import { Elements } from "@stripe/react-stripe-js";
import { loadStripe } from "@stripe/stripe-js"; import { loadStripe } from "@stripe/stripe-js";
import { getProjectDataQuery } from "@/lib/data/projectQuery"; import { getProjectDataQuery } from "@/lib/data/projectQuery";
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
import { InvestmentAmountInfo } from "./InvestmentAmountInfo";
import { Button } from "@/components/ui/button";
import { PaymentDialog } from "./PaymentDialog";
import Link from "next/link";
if (process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY === undefined) { if (process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY === undefined) {
throw new Error("NEXT_PUBLIC_STRIPE_PUBLIC_KEY is not defined"); throw new Error("NEXT_PUBLIC_STRIPE_PUBLIC_KEY is not defined");
@ -22,35 +26,35 @@ const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY);
const term_data = [ const term_data = [
{ {
term: "Minimum Investment", term: "Risk Disclosure",
description: "The minimum investment amount is $500.", description:
}, "Investments carry risks, including the potential loss of principal. It is important to carefully consider your risk tolerance before proceeding with any investment.",
{ link: "/risks",
term: "Investment Horizon",
description: "Investments are typically locked for a minimum of 12 months.",
}, },
{ {
term: "Fees", term: "Fees",
description: "A management fee of 2% will be applied annually.", description:
"A management fee of 3% will be taken from the company after the fundraising is completed. This fee is non-refundable.",
link: null,
}, },
{ {
term: "Returns", term: "Returns",
description: "Expected annual returns are between 8% and 12%.", description: "Expected annual returns are between 8% and 12%.",
}, link: null,
{
term: "Risk Disclosure",
description: "Investments carry risks, including the loss of principal.",
}, },
{ {
term: "Withdrawal Policy", term: "Withdrawal Policy",
description: "Withdrawals can be made after the lock-in period.", description:
"Withdrawals cannot be made after the fundraising period ends. Once the investment is finalized, it is non-refundable.",
link: null,
}, },
]; ];
export default function InvestPage() { export default function InvestPage() {
const [checkedTerms, setCheckedTerms] = useState(Array(term_data.length).fill(false)); const [checkboxStates, setCheckboxStates] = useState([false, false]);
const [investAmount, setInvestAmount] = useState(10); const [investAmount, setInvestAmount] = useState<number>(10);
const [investor_id, setInvestorId] = useState<string>(""); const [investor_id, setInvestorId] = useState<string>("");
const [showPaymentModal, setPaymentModal] = useState(false);
const params = useParams<{ id: string }>(); const params = useParams<{ id: string }>();
const supabase = createSupabaseClient(); const supabase = createSupabaseClient();
@ -77,16 +81,17 @@ export default function InvestPage() {
} = useQuery(getProjectDataQuery(supabase, Number(params.id))); } = useQuery(getProjectDataQuery(supabase, Number(params.id)));
const handleCheckboxChange = (index: number) => { const handleCheckboxChange = (index: number) => {
const updatedCheckedTerms = [...checkedTerms]; const updatedCheckboxStates = [...checkboxStates];
updatedCheckedTerms[index] = !updatedCheckedTerms[index]; updatedCheckboxStates[index] = !updatedCheckboxStates[index];
setCheckedTerms(updatedCheckedTerms); setCheckboxStates(updatedCheckboxStates);
}; };
const isAcceptTermAndService = () => { const isAcceptTermAndService = () => {
if (checkedTerms.some((checked) => !checked)) { return checkboxStates.every((checked) => checked);
return false; };
}
return true; const getInputBorderColor = (min: number) => {
return investAmount >= min ? "border-green-500" : "border-red-500";
}; };
return ( return (
@ -96,76 +101,118 @@ export default function InvestPage() {
) : projectError ? ( ) : projectError ? (
<p>Error loading project data. Please try again later.</p> <p>Error loading project data. Please try again later.</p>
) : projectData ? ( ) : projectData ? (
<> <div>
<h1 className="text-2xl md:text-4xl font-bold">Invest in {projectData.project_name}</h1> <h1 className="text-2xl md:text-4xl font-bold">Invest in {projectData.project_name}</h1>
<Separator className="my-4" /> <Separator className="my-4" />
<div className="flex gap-6">
<div id="investment" className="w-3/4">
{/* Investment Amount Section */}
<div className="w-3/4">
<h2 className="text:base md:text-2xl font-bold">Investment Amount</h2>
<h3 className="text-gray-500 text-md">Payments are processed immediately in USD$</h3>
<Input
className={`py-7 mt-4 text-lg border-2 ${getInputBorderColor(10)} rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus-visible:ring-0`}
type="number"
placeholder="min $10"
min={10}
onChangeCapture={(e) => setInvestAmount(Number(e.currentTarget.value))}
/>
<InvestmentAmountInfo amount={investAmount} />
</div>
<Separator className="my-4" />
{/* Investment Amount Section */} {/* Terms and Services Section */}
<div className="w-1/2 space-y-2"> <div className="md:w-2/3 space-y-2">
<h2 className="text:base md:text-2xl">Investment Amount</h2> <h2 className="text-2xl">Terms and Services</h2>
<Input <h3 className="text-gray-500 text-md">Please read and accept Term and Services first</h3>
className="w-52" <div id="term-condition">
type="number" <Table>
placeholder="min $10" <TableHeader>
min={10} <TableRow>
onChangeCapture={(e) => setInvestAmount(Number(e.currentTarget.value))} <TableHead>Term</TableHead>
/> <TableHead>Description</TableHead>
</div> </TableRow>
<Separator className="my-4" /> </TableHeader>
<TableBody>
{term_data.map((item, index) => (
<TableRow key={index}>
<TableCell>
{item.link != null ? (
<Link
href={item.link}
rel="noopener noreferrer"
target="_blank"
className="text-blue-500 underline"
>
{item.term}
</Link>
) : (
item.term
)}
</TableCell>
<TableCell>{item.description}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{/* Terms and Services Section */} <div className="mt-4 space-y-4">
<div className="md:w-2/3 space-y-2"> {/* First Checkbox with Terms */}
<h2 className="text-2xl">Terms and Services</h2> <div className="flex items-center space-x-2">
<Table> <Input
<TableHeader>
<TableRow>
<TableHead>Select</TableHead>
<TableHead>Term</TableHead>
<TableHead>Description</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{term_data.map((item, index) => (
<TableRow key={index}>
<TableCell>
<input
type="checkbox" type="checkbox"
checked={checkedTerms[index]} checked={checkboxStates[0]}
onChange={() => handleCheckboxChange(index)} onChange={() => handleCheckboxChange(0)}
className="h-4 w-4"
/> />
</TableCell> <p className="text-xs text-gray-500">
<TableCell>{item.term}</TableCell> I understand that this offering involves the use of a third-party custodian, who will act as the
<TableCell>{item.description}</TableCell> legal holder of the assets involved. As an investor, I acknowledge that I will be required to
</TableRow> create an account with this custodian and enter into necessary agreements, including those
))} related to the custody of the assets. I am aware that I may need to provide certain information
</TableBody> to verify my identity and complete the account creation process. I also understand that the
</Table> platform facilitating this offering does not manage or hold any custodial accounts for its
</div> investors. Additionally, I recognize that the platform, its affiliates, or its representatives
<Separator className="my-4" /> will not be held responsible for any damages, losses, costs, or expenses arising from (i) the
creation or management of custodial accounts, (ii) unauthorized access or loss of assets within
these accounts, or (iii) the custodian&apos;s failure to fulfill its obligations.
</p>
</div>
{/* Payment Information Section */} {/* Second Checkbox for Acceptance */}
<div className="w-full space-y-2"> <div className="flex items-center space-x-2">
<h2 className="text:base md:text-2xl">Payment Information</h2> <Input
<Elements type="checkbox"
stripe={stripePromise} checked={checkboxStates[1]}
options={{ onChange={() => handleCheckboxChange(1)}
mode: "payment", className="h-4 w-4"
amount: convertToSubcurrency(investAmount), />
currency: "usd", <p className="text-xs text-gray-500">I have read and accept the terms of investment.</p>
}} </div>
> </div>
<CheckoutPage </div>
amount={investAmount} </div>
isAcceptTermAndService={isAcceptTermAndService} <Separator className="my-4" />
project_id={Number(params.id)}
investor_id={investor_id} <Button disabled={!isAcceptTermAndService()} onClick={() => setPaymentModal(true)}>
/> Proceed to Payment
</Elements> </Button>
</div>
</div> </div>
</> </div>
) : ( ) : (
<p>No project data found.</p> <p>No project data found.</p>
)} )}
<PaymentDialog
open={showPaymentModal}
onOpenChange={setPaymentModal}
amount={investAmount}
stripePromise={stripePromise}
isAcceptTermAndService={isAcceptTermAndService}
projectId={Number(params.id)}
investorId={investor_id}
/>
</div> </div>
); );
} }