Skip to content

Commit

Permalink
feat: add renewal page
Browse files Browse the repository at this point in the history
  • Loading branch information
fricoben committed Sep 17, 2023
1 parent 88e6df7 commit d0aee95
Show file tree
Hide file tree
Showing 13 changed files with 480 additions and 39 deletions.
4 changes: 2 additions & 2 deletions components/domains/register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { utils } from "starknetid.js";
import { isHexString, numberToString } from "../../utils/stringService";
import { gweiToEth, hexToDecimal } from "../../utils/feltService";
import SelectDomain from "./selectDomains";
import SelectIdentity from "./selectIdentity";
import { useDisplayName } from "../../hooks/displayName.tsx";
import { Abi, Call } from "starknet";
import TxConfirmationModal from "../UI/txConfirmationModal";
Expand Down Expand Up @@ -318,7 +318,7 @@ const Register: FunctionComponent<RegisterProps> = ({
/>
</div>
</div>
<SelectDomain tokenId={tokenId} changeTokenId={changeTokenId} />
<SelectIdentity tokenId={tokenId} changeTokenId={changeTokenId} />
<div className={styles.cardCenter}>
<p className="text">
Price:&nbsp;
Expand Down
20 changes: 9 additions & 11 deletions components/domains/registerCheckboxes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,15 @@ const RegisterCheckboxes: FunctionComponent<RegisterCheckboxes> = ({
</a>
</p>
</div>
{renewalBox && (
<div
className="flex items-center justify-left text-xs cursor-pointer"
onClick={onChangeRenewalBox}
>
<Checkbox checked={renewalBox} sx={{ padding: 0 }} />
<p className="ml-1">
Enable auto-renewal and don&apos;t pay gas for your yearly renewal
</p>
</div>
)}
<div
className="flex items-center justify-left text-xs cursor-pointer"
onClick={onChangeRenewalBox}
>
<Checkbox checked={renewalBox} sx={{ padding: 0 }} />
<p className="ml-1">
Enable auto-renewal and don&apos;t pay gas for your yearly renewal
</p>
</div>
</div>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions components/domains/registerV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
hexToDecimal,
applyRateToBigInt,
} from "../../utils/feltService";
import SelectDomain from "./selectDomains";
import SelectIdentity from "./selectIdentity";
import { useDisplayName } from "../../hooks/displayName.tsx";
import { Abi, Call } from "starknet";
import { posthog } from "posthog-js";
Expand Down Expand Up @@ -121,7 +121,7 @@ const RegisterV2: FunctionComponent<RegisterV2Props> = ({ domain, groups }) => {
useEffect(() => {
// if price query does not work we use the off-chain hardcoded price
if (priceError || !priceData)
setPrice(getPriceFromDomain(duration, domain));
setPrice(getPriceFromDomain(duration, domain).toString());
else {
const high = priceData?.["price"].high << BigInt(128);
setPrice((priceData?.["price"].low + high).toString(10));
Expand Down Expand Up @@ -324,7 +324,7 @@ const RegisterV2: FunctionComponent<RegisterV2Props> = ({ domain, groups }) => {
color="secondary"
required
/>
<SelectDomain tokenId={tokenId} changeTokenId={changeTokenId} />
<SelectIdentity tokenId={tokenId} changeTokenId={changeTokenId} />
<NumberTextField
value={duration}
label="Years to register (max 25 years)"
Expand Down
273 changes: 273 additions & 0 deletions components/domains/renewal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
import React from "react";
import { FunctionComponent, useEffect, useState } from "react";
import Button from "../UI/button";
import { useEtherContract } from "../../hooks/contracts";
import {
useAccount,
useContractRead,
useContractWrite,
useTransactionManager,
} from "@starknet-react/core";
import { utils } from "starknetid.js";
import { getDomainWithStark, isValidEmail } from "../../utils/stringService";
import { gweiToEth, applyRateToBigInt } from "../../utils/feltService";
import { Abi, Call } from "starknet";
import { posthog } from "posthog-js";
import TxConfirmationModal from "../UI/txConfirmationModal";
import styles from "../../styles/components/registerV2.module.css";
import TextField from "../UI/textField";
import UsForm from "./usForm";
import { Divider } from "@mui/material";
import RegisterCheckboxes from "./registerCheckboxes";
import RegisterSummary from "./registerSummary";
import salesTax from "sales-tax";
import Wallets from "../UI/wallets";
import { computeMetadataHash, generateSalt } from "../../utils/userDataService";
import { getPriceFromDomains } from "../../utils/priceService";
import RenewalDomainsBox from "./renewalDomainsBox";

type RenewalProps = {
domain: string;
groups: string[];
};

const Renewal: FunctionComponent<RenewalProps> = ({ domain, groups }) => {
const [email, setEmail] = useState<string>("");
const [emailError, setEmailError] = useState<boolean>(true);
const [isUsResident, setIsUsResident] = useState<boolean>(false);
const [usState, setUsState] = useState<string>("DE");
const [salesTaxRate, setSalesTaxRate] = useState<number>(0);
const [salesTaxAmount, setSalesTaxAmount] = useState<string>("0");
const [callData, setCallData] = useState<Call[]>([]);
const [price, setPrice] = useState<string>("");
const [balance, setBalance] = useState<string>("");
const [invalidBalance, setInvalidBalance] = useState<boolean>(false);
const { contract: etherContract } = useEtherContract();
const [isTxModalOpen, setIsTxModalOpen] = useState(false);
const encodedDomain = utils
.encodeDomain(domain)
.map((element) => element.toString())[0];
const [termsBox, setTermsBox] = useState<boolean>(true);
const [renewalBox, setRenewalBox] = useState<boolean>(true);
const [walletModalOpen, setWalletModalOpen] = useState<boolean>(false);
const [sponsor, setSponsor] = useState<string>("0");
const [salt, setSalt] = useState<string | undefined>();
const [metadataHash, setMetadataHash] = useState<string | undefined>();
const [selectedDomains, setSelectedDomains] =
useState<Record<string, boolean>>();
const { address } = useAccount();
const { data: userBalanceData, error: userBalanceDataError } =
useContractRead({
address: etherContract?.address as string,
abi: etherContract?.abi as Abi,
functionName: "balanceOf",
args: [address],
});
const { writeAsync: execute, data: renewData } = useContractWrite({
// calls: renewalBox
// ? callData.concat(registerCalls.renewal(encodedDomain, price))
// : callData,
calls: callData,
});
const [domainsMinting, setDomainsMinting] = useState<Map<string, boolean>>(
new Map()
);

const { addTransaction } = useTransactionManager();

useEffect(() => {
if (!renewData?.transaction_hash || !salt) return;
posthog?.capture("renewal from page");

// register the metadata to the sales manager db
fetch(`${process.env.NEXT_PUBLIC_SALES_SERVER_LINK}/add_metadata`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
meta_hash: metadataHash,
email,
groups,
tax_state: isUsResident ? usState : "none",
salt: salt,
}),
})
.then((res) => res.json())
.catch((err) => console.log("Error on sending metadata:", err));

addTransaction({ hash: renewData.transaction_hash });
setIsTxModalOpen(true);
}, [renewData, salt, email, usState]);

// on first load, we generate a salt
useEffect(() => {
setSalt(generateSalt());
}, []);

// we update compute the purchase metadata hash
useEffect(() => {
// salt must not be empty to preserve privacy
if (!salt) return;
(async () => {
setMetadataHash(
await computeMetadataHash(
email,
groups, // default group for domain Owner
isUsResident ? usState : "none",
salt
)
);
})();
}, [email, usState, salt]);

useEffect(() => {
if (!selectedDomains) return;
setPrice(getPriceFromDomains(selectedDomains, 1).toString());
}, [selectedDomains]);

useEffect(() => {
if (userBalanceDataError || !userBalanceData) setBalance("");
else {
const high = userBalanceData?.["balance"].high << BigInt(128);
setBalance((userBalanceData?.["balance"].low + high).toString(10));
}
}, [userBalanceData, userBalanceDataError]);

useEffect(() => {
if (balance && price) {
if (gweiToEth(balance) > gweiToEth(price)) {
setInvalidBalance(false);
} else {
setInvalidBalance(true);
}
}
}, [balance, price]);

useEffect(() => {
const referralData = localStorage.getItem("referralData");
if (referralData) {
const data = JSON.parse(referralData);
if (data.sponsor && data?.expiry >= new Date().getTime()) {
setSponsor(data.sponsor);
} else {
setSponsor("0");
}
} else {
setSponsor("0");
}
}, []);

function changeEmail(value: string): void {
setEmail(value);
setEmailError(isValidEmail(value) ? false : true);
}

useEffect(() => {
if (isUsResident) {
salesTax.getSalesTax("US", usState).then((tax) => {
setSalesTaxRate(tax.rate);
if (price) setSalesTaxAmount(applyRateToBigInt(price, tax.rate));
});
} else {
setSalesTaxRate(0);
setSalesTaxAmount("");
}
}, [isUsResident, usState, price]);

return (
<div className={styles.container}>
<div className={styles.card}>
<div className={styles.form}>
<div className="flex flex-col items-start gap-4 self-stretch">
<p className={styles.legend}>Your renewal</p>
<h3 className={styles.domain}>{getDomainWithStark(domain)}</h3>
</div>
<div className="flex flex-col items-start gap-6 self-stretch">
<TextField
helperText="Secure your domain's future and stay ahead with vital updates. Your email stays private with us, always."
label="Email address"
value={email}
onChange={(e) => changeEmail(e.target.value)}
color="secondary"
error={emailError}
errorMessage={"Please enter a valid email address"}
type="email"
/>
<UsForm
isUsResident={isUsResident}
onUsResidentChange={() => setIsUsResident(!isUsResident)}
usState={usState}
changeUsState={(value) => setUsState(value)}
/>
<RenewalDomainsBox
helperText="Check the box of the domains you want to renew"
setSelectedDomains={setSelectedDomains}
selectedDomains={selectedDomains}
/>
</div>
</div>
<div className={styles.summary}>
<RegisterSummary
ethRegistrationPrice={price}
duration={1}
renewalBox={false}
salesTaxRate={salesTaxRate}
isUsResident={isUsResident}
/>
<Divider className="w-full" />
<RegisterCheckboxes
onChangeRenewalBox={() => setRenewalBox(!renewalBox)}
onChangeTermsBox={() => setTermsBox(!termsBox)}
termsBox={termsBox}
renewalBox={renewalBox}
/>
{address ? (
<Button
onClick={() =>
execute().then(() => {
setDomainsMinting((prev) =>
new Map(prev).set(encodedDomain.toString(), true)
);
})
}
disabled={
(domainsMinting.get(encodedDomain) as boolean) ||
!address ||
invalidBalance ||
!termsBox ||
(isUsResident && !usState) ||
emailError
}
>
{!termsBox
? "Please accept terms & policies"
: isUsResident && !usState
? "We need your US State"
: invalidBalance
? "You don't have enough eth"
: emailError
? "Enter a valid Email"
: "Register my domain"}
</Button>
) : (
<Button onClick={() => setWalletModalOpen(true)}>
Connect wallet
</Button>
)}
</div>
</div>
<img className={styles.image} src="/visuals/registerV2.webp" />
<TxConfirmationModal
txHash={renewData?.transaction_hash}
isTxModalOpen={isTxModalOpen}
closeModal={() => setIsTxModalOpen(false)}
title="Your domain is on it's way !"
/>
<Wallets
closeWallet={() => setWalletModalOpen(false)}
hasWallet={walletModalOpen}
/>
</div>
);
};

export default Renewal;
Loading

0 comments on commit d0aee95

Please sign in to comment.