Skip to content

Instantly share code, notes, and snippets.

@ilhamdoanggg
Created June 9, 2023 04:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ilhamdoanggg/6644666176179d1caa0833587cbcac8e to your computer and use it in GitHub Desktop.
Save ilhamdoanggg/6644666176179d1caa0833587cbcac8e to your computer and use it in GitHub Desktop.
"use client";
import React, { useEffect, useState } from "react";
import {
FontManrope,
FontManrope600,
FontNunito,
FontRubik,
} from "@/styles/fonts";
import { Button, Input, Typography } from "@material-tailwind/react";
import ListDetail from "@/components/ListDetail";
import Image from "next/image";
import { formatNumber } from "@/utils/formater";
import numeral from "numeral";
export default function SimulationPage() {
const [isCashout, setIsCashout] = useState<boolean>(true);
const [enableButton, setEnableButton] = useState<boolean>(true);
const [isPaylater, setIsPaylater] = useState<boolean>(false);
const [value, setValue] = useState<number | string>(0);
const [listTenor, setListTenor] = useState<string[]>([
"1",
"3",
"6",
"9",
"12",
]);
const [selectedTenor, setSelectedTenor] = useState<number>(3);
const [errorMessage, setErrorMessage] = useState<string>("");
const [alertAmount, setAlertAmount] = useState<boolean>(false);
const [customerName, setCustomerName] = useState<string>("Riesandy Faturachman");
const [customerAccountNo, setCustomerAccountNo] = useState<string>("0129123213");
const [merchantName, setMerchantName] = useState<string>("GOPAY");
const [merchantId, setMerchantId] = useState<string>("GOPAY");
const [cashoutFeeNumber, setCashoutFeeNumber] = useState<number>();
const [minimumCashoutFee, setMinimumCashoutFee] = useState<number>(20000);
const [minimumCashoutAmount, setMinimumCashoutAmount] = useState<number>(100000);
const [cashoutFeeMultiplier, setCashoutFeeMultiplier] =
useState<number>(0.05);
const [availableAmountForCashout, setAvailableAmountForCashout] =
useState<number>(150000);
const [availableAmountForCashoutIdr, setAvailableAmountForCashoutIdr] =
useState<string>("Rp. 150.000,00");
const [simulationList, setSimulationList] = useState<[{}]>([
[
{
tenor: "1",
yearly_interest_rate: "1,42",
monthly_interest_rate: "0,12",
yearly_interest_rate_info: "1,42% per tahun flat rate",
monthly_interest_rate_info: "0,12% per bulan flat rate",
principal: "1",
principal_installment: "1",
monthly_installment_incl_interest: "1",
total_interest: "0",
monthly_interest: "0",
total_installment_incl_interest: "1",
first_total_installment_incl_interest: "20001",
default_currency: "IDR",
principal_in_idr: "Rp 1",
principal_installment_in_idr: "Rp 1",
monthly_installment_incl_interest_in_idr: "Rp 1",
total_interest_in_idr: "Rp 0",
monthly_interest_in_idr: "Rp 0",
total_installment_incl_interest_in_idr: "Rp 1",
first_total_installment_incl_interest_in_idr: "Rp 20.001",
},
{
tenor: "3",
yearly_interest_rate: "1,42",
monthly_interest_rate: "0,12",
yearly_interest_rate_info: "1,42% per tahun flat rate",
monthly_interest_rate_info: "0,12% per bulan flat rate",
principal: "1",
principal_installment: "0",
monthly_installment_incl_interest: "1",
total_interest: "1",
monthly_interest: "0",
total_installment_incl_interest: "2",
first_total_installment_incl_interest: "20001",
default_currency: "IDR",
principal_in_idr: "Rp 1",
principal_installment_in_idr: "Rp 0",
monthly_installment_incl_interest_in_idr: "Rp 1",
total_interest_in_idr: "Rp 1",
monthly_interest_in_idr: "Rp 0",
total_installment_incl_interest_in_idr: "Rp 2",
first_total_installment_incl_interest_in_idr: "Rp 20.001",
},
{
tenor: "6",
yearly_interest_rate: "1,42",
monthly_interest_rate: "0,12",
yearly_interest_rate_info: "1,42% per tahun flat rate",
monthly_interest_rate_info: "0,12% per bulan flat rate",
principal: "1",
principal_installment: "0",
monthly_installment_incl_interest: "0",
total_interest: "1",
monthly_interest: "0",
total_installment_incl_interest: "2",
first_total_installment_incl_interest: "20000",
default_currency: "IDR",
principal_in_idr: "Rp 1",
principal_installment_in_idr: "Rp 0",
monthly_installment_incl_interest_in_idr: "Rp 0",
total_interest_in_idr: "Rp 1",
monthly_interest_in_idr: "Rp 0",
total_installment_incl_interest_in_idr: "Rp 2",
first_total_installment_incl_interest_in_idr: "Rp 20.000",
},
{
tenor: "9",
yearly_interest_rate: "1,42",
monthly_interest_rate: "0,12",
yearly_interest_rate_info: "1,42% per tahun flat rate",
monthly_interest_rate_info: "0,12% per bulan flat rate",
principal: "1",
principal_installment: "0",
monthly_installment_incl_interest: "0",
total_interest: "2",
monthly_interest: "0",
total_installment_incl_interest: "3",
first_total_installment_incl_interest: "20000",
default_currency: "IDR",
principal_in_idr: "Rp 1",
principal_installment_in_idr: "Rp 0",
monthly_installment_incl_interest_in_idr: "Rp 0",
total_interest_in_idr: "Rp 2",
monthly_interest_in_idr: "Rp 0",
total_installment_incl_interest_in_idr: "Rp 3",
first_total_installment_incl_interest_in_idr: "Rp 20.000",
},
{
tenor: "12",
yearly_interest_rate: "1,42",
monthly_interest_rate: "0,12",
yearly_interest_rate_info: "1,42% per tahun flat rate",
monthly_interest_rate_info: "0,12% per bulan flat rate",
principal: "1",
principal_installment: "0",
monthly_installment_incl_interest: "0",
total_interest: "2",
monthly_interest: "0",
total_installment_incl_interest: "3",
first_total_installment_incl_interest: "20000",
default_currency: "IDR",
principal_in_idr: "Rp 1",
principal_installment_in_idr: "Rp 0",
monthly_installment_incl_interest_in_idr: "Rp 0",
total_interest_in_idr: "Rp 2",
monthly_interest_in_idr: "Rp 0",
total_installment_incl_interest_in_idr: "Rp 3",
first_total_installment_incl_interest_in_idr: "Rp 20.000",
},
],
]);
const [selectedSimulation, setSelectedSimulation] = useState<[{}]>([{
}]);
const [interesRate, setInteresRate] = useState<string>("1,42");
const [paylaterDetail, setPaylaterDetail] = useState<{}>({});
useEffect(() => {
// console.log(isPaylater);
// console.log(theme);
}, );
function _mockSimulate(paymentAmount: string) {
// console.log({ paymentAmount });
let parseFloatPaymentAmount = Number.parseFloat(paymentAmount);
// console.log({ parseFloatPaymentAmount });
const yearlyInterestRate: number = +Number.parseFloat(
interesRate.replaceAll(",", ".")
).toFixed(2);
let tenorList = listTenor.map((tenor: any) => tenor);
const data = tenorList.map((tenor) => {
let paymentAmountNumber: number = +paymentAmount;
const interestPerMonth =
(paymentAmountNumber * (yearlyInterestRate * 12)) / 100;
const installmentAmount = paymentAmountNumber / tenor + interestPerMonth;
const totalInterest = interestPerMonth * tenor;
return {
paymentAmount,
tenor,
yearlyInterestRate,
installmentAmount,
totalInterest,
repaymentAmount: parseFloatPaymentAmount + totalInterest,
};
});
// console.log({ data });
return data;
}
function _simulationResponseTransformer(
grossAmount: number,
is_cashout: boolean
) {
return (data: any) => {
// console.log(data);
const {
paymentAmount,
tenor,
installmentAmount,
totalInterest,
yearlyInterestRate,
repaymentAmount,
} = data;
if (paymentAmount !== grossAmount) {
return {};
}
const monthlyInterestRate: number = parseFloat(yearlyInterestRate) / 12;
const interestRate = {
yearly_interest_rate: yearlyInterestRate
.toFixed(2)
.toString()
.replace(".", ","),
monthly_interest_rate: monthlyInterestRate
.toFixed(2)
.toString()
.replace(".", ","),
};
const interestRateInfo = {
yearly_interest_rate_info: `${interestRate.yearly_interest_rate}% per tahun flat rate`,
monthly_interest_rate_info: `${interestRate.monthly_interest_rate}% per bulan flat rate`,
};
const basicData = {
principal: Number.parseFloat(paymentAmount).toFixed(0),
principal_installment: (paymentAmount / tenor).toFixed(0),
monthly_installment_incl_interest:
Number.parseFloat(installmentAmount).toFixed(0),
total_interest: Number.parseFloat(totalInterest).toFixed(0),
monthly_interest: Number.parseFloat(
(totalInterest / tenor).toString()
).toFixed(0),
total_installment_incl_interest:
Number.parseFloat(repaymentAmount).toFixed(0),
};
const currencyDataInfo = {
default_currency: "IDR",
principal_in_idr: `Rp ${formatNumber(Number(basicData.principal))}`,
principal_installment_in_idr: `Rp ${formatNumber(
Number(basicData.principal_installment)
)}`,
monthly_installment_incl_interest_in_idr: `Rp ${formatNumber(
basicData.monthly_installment_incl_interest
)}`,
total_interest_in_idr: `Rp ${formatNumber(basicData.total_interest)}`,
monthly_interest_in_idr: `Rp ${formatNumber(
basicData.monthly_interest
)}`,
total_installment_incl_interest_in_idr: `Rp ${formatNumber(
basicData.total_installment_incl_interest
)}`,
};
let minimumCashoutFe = minimumCashoutFee;
if (is_cashout) {
let cashoutFee = grossAmount * cashoutFeeMultiplier;
console.log(cashoutFee)
if (cashoutFee < parseFloat(minimumCashoutFe.toString())) {
cashoutFee = parseFloat(minimumCashoutFe.toString());
}
// this.setState({
// minimumCashoutFee: cashoutFee,
// cashoutFee:
// "Rp " + numeral(cashoutFee).format("0,0").replace(/,/g, "."),
// });
// setMinimumCashoutFee()
let first_total_installment =
parseFloat(basicData.monthly_installment_incl_interest) +
parseFloat(String(cashoutFee));
currencyDataInfo.first_total_installment_incl_interest_in_idr = `Rp ${formatNumber(
first_total_installment
)}`;
basicData.first_total_installment_incl_interest = Number.parseFloat(
first_total_installment
).toFixed(0);
}
return {
tenor: tenor,
...interestRate,
...interestRateInfo,
...basicData,
...currencyDataInfo,
};
};
}
const _doSimulate = (value: number, tenorUser: number) => {
const body = _mockSimulate(value);
let valueCasToNumber: number = +value;
let dataResult = body.map(
_simulationResponseTransformer(valueCasToNumber, true)
);
// console.log({ dataResult });
return dataResult;
};
function _calcuatePaylater(value:number){
}
const formattedValue =
value !== undefined ? value.toLocaleString("id-ID") : "";
const _handleInputAmount = (event: React.ChangeEvent<HTMLInputElement>) => {
// console.log(value);
// console.log(event);
if (value !== undefined && value.toString().length <= 9) {
const inputValue = event.target.value;
let selectTenorUser = selectedTenor;
if (selectedTenor === 200) {
selectTenorUser = 1;
}
// console.log({ inputValue });
let numericValue:number = 0
numericValue= Number(inputValue.replace(/[^0-9]/g, ""));
console.log(numericValue)
_doSimulate(numericValue, selectTenorUser);
if(numericValue===0){
setAlertAmount(false)
setErrorMessage('')
}else if(numericValue<minimumCashoutAmount){
setAlertAmount(false)
setErrorMessage('Limit pencairan anda kurang dari nominal Pencairan')
}else if(numericValue>=availableAmountForCashout){
setErrorMessage(`Limit pencairan anda melebihi dari nominal ${availableAmountForCashoutIdr}`)
setAlertAmount(true)
}else {
setAlertAmount(false)
setErrorMessage('')
}
setValue(numericValue | 0);
// setEnableButton(false);
}
};
const handleClickButtonTenor = (
event: React.MouseEvent<HTMLButtonElement>
) => {
// event.preventDefault();
event.preventDefault()
const button: HTMLButtonElement = event.currentTarget;
console.log({ event });
setIsPaylater(true)
};
const renderInfo = () => {
return (
<section
className="bg-ceria-200 rounded-lg text-ceria-400 px-4 py-3 flex w-full flex-col gap-3 mb-6"
role="alert"
>
<div className="flex items-center justify-between">
<div className="py-2 px-2">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 6V10M10 14H10.01M19 10C19 11.1819 18.7672 12.3522 18.3149 13.4442C17.8626 14.5361 17.1997 15.5282 16.364 16.364C15.5282 17.1997 14.5361 17.8626 13.4442 18.3149C12.3522 18.7672 11.1819 19 10 19C8.8181 19 7.64778 18.7672 6.55585 18.3149C5.46392 17.8626 4.47177 17.1997 3.63604 16.364C2.80031 15.5282 2.13738 14.5361 1.68508 13.4442C1.23279 12.3522 1 11.1819 1 10C1 7.61305 1.94821 5.32387 3.63604 3.63604C5.32387 1.94821 7.61305 1 10 1C12.3869 1 14.6761 1.94821 16.364 3.63604C18.0518 5.32387 19 7.61305 19 10Z"
stroke="#8000C7"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
<Typography
variant="paragraph"
className="text-textButton font-medium"
style={{ fontSize: "1.5vh" }}
>
Pastikan detail transaksi dan jangka waktu pembayaran sudah benar
sebelum membayar.
</Typography>
</div>
</section>
);
};
const renderDetailTransaction = () => {
return (
<section>
<Typography variant="h6" className={`${FontManrope600.className} mb-2`}>
Detail Harga
</Typography>
<ListDetail
detailText="Headphone Sony XW500MF ASIAN VERSION (Qty: 1x)"
priceText="Rp 7.791.000"
/>
<ListDetail detailText="Biaya Kirim (Qty: 1x)" priceText="Rp 12.000" />
<ListDetail
detailText="Biaya Layanan (Qty: 1x)"
priceText="Rp 7.260"
isGray
/>
<ListDetail
detailText="Diskon Tambahan (Qty: 1x)"
priceText="Rp - 10.260"
isGray
/>
<ListDetail
detailText="Donasi (Qty: 1x)"
priceText="Rp 10.260"
isGray
classNames="border-b-3 border-dashed pb-4"
/>
<ListDetail
detailText="Total Harga"
priceText="Rp7.810.260"
classNames="border-b-3 border-solid py-4 "
/>
</section>
);
};
const renderInputCurrency = () => {
return (
<div className="relative flex w-full">
<Input
type="tel"
variant="static"
value={formattedValue}
onChange={_handleInputAmount}
onBlur={_handleInputAmount}
className={`${FontNunito.className} pl-10`}
containerProps={{
className: "min-w-0",
}}
style={{
color: "#5E07A0",
fontWeight: "600",
fontSize: "30px",
}}
/>
<Typography
variant="paragraph"
className={`${FontRubik.className} !absolute left-1 top-1 rounded`}
style={{
color: "#5E07A0",
fontWeight: "400",
fontSize: "18px",
}}
>
Rp.
</Typography>
</div>
);
};
const renderDetailCashout = () => {
return (
<section>
<div className="pb-8 border-b-3 border-solid">
<Typography
variant="h4"
className={`font-bold ${FontManrope.className}`}
style={{ fontWeight: "600" }}
>
Detail Pinjam Tunai
</Typography>
<div className="flex justify-between items-baseline mt-5">
<Typography
variant="lead"
className={`${FontRubik.className} font-semibold`}
style={{ fontWeight: "500" }}
>
Rekening Tujuan
</Typography>
<Image
src="../images/info.svg"
alt="Asking Alexandria"
width="20"
height="20"
/>
</div>
<Typography
variant="paragraph"
className={`${FontNunito.className} mt-3`}
style={{ color: "#333" }}
>
{customerName}
</Typography>
<Typography
variant="paragraph"
className={`${FontNunito.className}`}
style={{ color: "#333" }}
>
{merchantName} - {customerAccountNo}
</Typography>
</div>
<div className="py-8 border-b-3 border-solid">
<Typography
variant="h6"
className={`font-semibold ${FontRubik.className}`}
>
Limit Kredit Pencairan
</Typography>
<Typography variant="paragraph" className={`${FontNunito.className}`}
style={alertAmount?{color: "#F53938"}:{color: "#333333"}}>
{availableAmountForCashoutIdr}
</Typography>
</div>
<div className="pt-5 pb-1">
<Typography
variant="paragraph"
className={`${FontRubik.className} font-light`}
style={{ color: "#8B8B8B", fontWeight: "400" }}
>
Masukan jumlah Pinjam Tunai*
</Typography>
{renderInputCurrency()}
<div className="flex justify-between items-baseline mt-5">
<Typography
variant="paragraph"
className={`${FontRubik.className} font-light`}
style={{ color: "#F53938", fontWeight: "400" }}
>
{errorMessage?errorMessage:''}
</Typography>
</div>
</div>
</section>
);
};
const renderInfoTransaction = () => {
return (
<section>
<Typography
variant="h6"
className={`${FontManrope600.className} mb-2 mt-5`}
>
Detail Cicilan
</Typography>
<ListDetail
detailText="Cicilan Perbulan"
priceText="Rp2.640.388"
classNames="border-b-3 border-dashed pb-1"
/>
<ListDetail
detailText="Total Cicilan 3 Bulan"
priceText="Rp 201.616"
isTotal
classNames="my-3"
/>
<ListDetail
detailText="Bunga 1,42%"
priceText="Rp 201.616"
isGray
isInterest
classNames="border-b-3 border-solid pb-6"
/>
</section>
);
};
const renderInfoCashout = () => {
return (
<section className="border-b-3 border-solid mt-3">
<Typography variant="h6" className={`${FontRubik.className} mb-2`}>
Detail Cicilan
</Typography>
{simulationList.map((response, index) => {
// console.log(response);
return (
<ListDetail
key={index}
detailText="Cicilan bulan pertama"
priceText="Rp 201.616"
isLight
classNames={`${FontNunito.className} mb-1 text-cashoutText`}
/>
);
})}
{/* <ListDetail
detailText="Cicilan selanjutnya"
priceText="Rp 176.616"
isLight
classNames={`${FontNunito.className} mb-1 text-cashoutText`}
/>
<ListDetail
detailText="Total Cicilan "
priceText="Rp554.850"
classNames={`${FontNunito.className} mb-3 text-cashoutText`}
/>
<ListDetail
detailText="Biaya Pencairan"
priceText="Rp25.000"
isLight
classNames={`${FontNunito.className} mb-1 text-cashoutText`}
/>
<ListDetail
detailText="Total Bunga (3x1,99% perbulan)"
priceText="Rp29.850"
isLight
classNames={`${FontNunito.className} mb-3 text-cashoutText`}
/>
<ListDetail
detailText="Total Pencairan "
priceText="Rp500.000"
classNames={`${FontNunito.className} mb-6 text-cashoutText`}
/> */}
</section>
);
};
const renderButtonTenor = () => {
return (
<section className="grid gap-x-5 gap-y-4 grid-cols-2 pb-8 border-solid border-b-3 ">
<button
type="button"
className={`${isPaylater? "btn_tenor accent-ceria-400": 'btn_tenor border-ceriaText-1000'}`}
onClick={handleClickButtonTenor}
>
<div
className={`text-start ${FontManrope.className} mb-2 active:font-bold hover:font-bold`}
>
Bayar dalam 30 hari
</div>
<div className="text-start text-ceriaText-500">(bunga 500%)</div>
</button>
{listTenor.map((response, index) => {
return (
<button
key={response}
type="button"
className="btn_tenor"
// onClick={handleClickButtonTenor(index)}
>
<div
className={`text-start ${FontManrope.className} mb-2 active:font-bold hover:font-bold`}
>
{`Cicilan ${response} bulan`}
</div>
<div className="text-start text-ceriaText-500">(bunga 500%)</div>
</button>
);
})}
</section>
);
};
const renderButtonContinue = () => {
return (
<section className="bg- flex flex-col items-center justify-center mt-5">
<Button
disabled={enableButton}
className={`mb-3 w-full bg-ceria-800 flex items-center justify-center font-normal text-sm ${FontManrope.className}`}
style={{ textTransform: "none" }}
>
<Image
src="../images/guard.svg"
alt="Guard Image"
width="20"
height="20"
className="mr-3"
/>
{isCashout ? "Cairkan Sekarang" : "Bayar Sekarang"}
</Button>
</section>
);
};
const renderBoxInfoCashout = () => {
return (
<section className="bg-bgInfoCashout rounded-2xl p-4 mt-5 text-textInfoCashout mb-3">
<Typography variant="paragraph" className={`${FontManrope.className}`}>
1. Minimal penarikan limit{" "}
<strong className="font-bold">100 ribu</strong>
</Typography>
<Typography
variant="paragraph"
className={`${FontManrope.className} my-2`}
>
2. Maksimal pinjam tunai sebanyak{" "}
<strong className="font-bold">3x perbulan</strong>
</Typography>
<Typography variant="paragraph" className={`${FontManrope.className}`}>
3. Limit pinjam tunai sebesar{" "}
<strong className="font-bold">30% dari total limit kredit</strong>
</Typography>
</section>
);
};
return (
<main
className={`min-h-screen p-5 flex-col justify-between ${FontManrope.className}`}
style={{ backgroundColor: "#fafafa" }}
>
{!isCashout ? renderInfo() : ""}
{isCashout ? renderDetailCashout() : renderDetailTransaction()}
{isCashout ? renderBoxInfoCashout() : ""}
<Typography
variant="h6"
className={`${FontManrope600.className} mb-3 mt-7`}
>
{isCashout ? "Pilih tenor yang kamu inginkan" : "Pilih Tenor"}
</Typography>
{renderButtonTenor()}
{isCashout ? renderInfoCashout() : renderInfoTransaction()}
{renderButtonContinue()}
</main>
);
}
// export function getServerSideProps(req: NextRequest, res: NextResponse) {
// return { props: { token: req.cookies.get('simulation') || "" } };
// }
@ilhamdoanggg
Copy link
Author

import formatNum from "format-num";

export const formatNumber = (number) => {
return formatNum(number).replace(/,/g, ".");
};

@ilhamdoanggg
Copy link
Author

"@heroicons/react": "^2.0.18",
"@material-tailwind/react": "^2.0.0",
"@tailwindcss/typography": "^0.5.9",
"@types/js-cookie": "^3.0.3",
"@types/node": "18.15.10",
"@types/numeral": "^2.0.2",
"@types/react": "18.0.29",
"@types/react-dom": "18.0.11",
"autoprefixer": "^10.4.14",
"axios": "^1.4.0",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"express": "^4.18.2",
"format-num": "^1.0.0",
"moment": "^2.29.4",
"numeral": "^2.0.6",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.7",
"typescript": "5.0.2"

@ilhamdoanggg
Copy link
Author

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
body {
@apply bg-ceriaText-50 m-auto max-w-xl;
}
.btn_primary {
@apply border-0 p-2 px-6 rounded-md bg-ceria-800 mt-6 text-white w-full font-bold;
}
.btn_tenor {
@apply
focus:text-ceria-700
focus:bg-ceria-10
focus:ring-2
focus:outline-none
focus:ring-ceria-100
focus:border-ceria-500
focus:border-2
hover:text-ceria-400
hover:bg-ceria-10
hover:border-ceria-500
hover:border-2
border border-ceriaText-1000
active:bg-ceria-200
font-medium
rounded-lg
text-sm
px-5 py-2
text-center
mr-0 mb-0
dark:border-ceria-500
dark:text-ceria-500
dark:hover:text-white
dark:hover:bg-ceria-800
dark:focus:ring-ceria-900;
}
.root {
@apply my-16;
}
.dot {
@apply flex justify-center w-[16.6666666667%];
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment