Skip to content

Commit

Permalink
feat: domain gift page (#801)
Browse files Browse the repository at this point in the history
* feat: domain gift page

* feat: API & contract interactions

* fix: only 5+ length domains accepted

* Update utils/campaignService.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* cleaning the code

* cleaning the code

* Update pages/freeregistration.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat: cleaning the code + better UX

* fixing build

* Update components/discount/registerFree.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update components/discount/registerFree.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* using local storage for signature

* Update components/discount/registerFree.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: duplicate import

* fix: removing coupon from starknet call

* Adding identity mint

* cleaning the code

* fix: gift image

* Update components/discount/FreeRegisterPresentation.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update components/discount/FreeRegisterPresentation.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update components/discount/FreeRegisterPresentation.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* cleaning the code

* cleaning the code

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
Marchand-Nicolas and coderabbitai[bot] committed Jun 6, 2024
1 parent 468f089 commit df74337
Show file tree
Hide file tree
Showing 20 changed files with 658 additions and 123 deletions.
44 changes: 0 additions & 44 deletions components/discount/discountCheckoutScreen.tsx

This file was deleted.

232 changes: 232 additions & 0 deletions components/discount/freeRegisterCheckout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import React from "react";
import type { FunctionComponent } from "react";
import { useEffect, useState } from "react";
import type { Call } from "starknet";
import Button from "../UI/button";
import { useAccount, useContractWrite } from "@starknet-react/core";
import { utils } from "starknetid.js";
import { getDomainWithStark } from "../../utils/stringService";
import { numberToFixedString } from "../../utils/feltService";
import { posthog } from "posthog-js";
import styles from "../../styles/components/registerV2.module.css";
import TextField from "../UI/textField";
import { Divider } from "@mui/material";
import RegisterSummary from "../domains/registerSummary";
import registrationCalls from "../../utils/callData/registrationCalls";
import { computeMetadataHash, generateSalt } from "../../utils/userDataService";
import BackButton from "../UI/backButton";
import { useNotificationManager } from "../../hooks/useNotificationManager";
import { NotificationType, TransactionType } from "../../utils/constants";
import ConnectButton from "../UI/connectButton";
import { getFreeDomain } from "@/utils/campaignService";
import TermCheckbox from "../domains/termCheckbox";
import { useRouter } from "next/router";

type FreeRegisterCheckoutProps = {
domain: string;
duration: number;
customMessage: string;
goBack: () => void;
couponCode?: boolean;
couponHelper?: string;
banner: string;
};

const FreeRegisterCheckout: FunctionComponent<FreeRegisterCheckoutProps> = ({
domain,
duration,
customMessage,
goBack,
couponCode,
couponHelper,
banner,
}) => {
const [targetAddress, setTargetAddress] = useState<string>("");
const [callData, setCallData] = useState<Call[]>([]);
const [salt, setSalt] = useState<string | undefined>();
const encodedDomain = utils
.encodeDomain(domain)
.map((element) => element.toString())[0];
const [termsBox, setTermsBox] = useState<boolean>(true);
const [metadataHash, setMetadataHash] = useState<string | undefined>();
const { account, address } = useAccount();
const { writeAsync: execute, data: registerData } = useContractWrite({
calls: callData,
});
const [domainsMinting, setDomainsMinting] = useState<Map<string, boolean>>(
new Map()
);
const router = useRouter();
const [tokenId, setTokenId] = useState<number>(0);
const [coupon, setCoupon] = useState<string>("");
const [couponError, setCouponError] = useState<string>("");
const [signature, setSignature] = useState<string[]>(["", ""]);
const [loadingCoupon, setLoadingCoupon] = useState<boolean>(false);
const { addTransaction } = useNotificationManager();

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

useEffect(() => {
if (address) setTargetAddress(address);
}, [address]);

useEffect(() => {
// salt must not be empty to preserve privacy
if (!salt) return;
(async () => {
setMetadataHash(await computeMetadataHash("none", "none", salt));
})();
}, [salt]);

useEffect(() => {
// Variables
const newTokenId: number = Math.floor(Math.random() * 1000000000000);
setTokenId(newTokenId);
const txMetadataHash = `0x${metadataHash}` as HexString;

const freeRegisterCalls = registrationCalls.getFreeRegistrationCalls(
newTokenId,
encodedDomain,
signature,
txMetadataHash
);
return setCallData(freeRegisterCalls);
}, [metadataHash, encodedDomain, signature]);

function changeCoupon(value: string): void {
setCoupon(value);
setLoadingCoupon(true);
}

useEffect(() => {
if (!registerData?.transaction_hash) return;
posthog?.capture("register");
addTransaction({
timestamp: Date.now(),
subtext: "Domain registration",
type: NotificationType.TRANSACTION,
data: {
type: TransactionType.BUY_DOMAIN,
hash: registerData.transaction_hash,
status: "pending",
},
});

router.push(`/confirmation?tokenId=${tokenId}`);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [registerData, tokenId]);

useEffect(() => {
if (!coupon) {
setCouponError("Please enter a coupon code");
setLoadingCoupon(false);
return;
}
const lastSuccessCoupon = localStorage.getItem("lastSuccessCoupon");
if (coupon === lastSuccessCoupon) {
setCouponError("");
setLoadingCoupon(false);
const signature = JSON.parse(
localStorage.getItem("couponSignature") as string
);
setSignature(signature);
return;
}
if (!address) return;
getFreeDomain(address, `${domain}.stark`, coupon).then((res) => {
if (res.error)
setCouponError(
typeof res.error === "string" ? res.error : JSON.stringify(res.error)
);
else {
const signature = [res.r, res.s];
setSignature(signature);
setCouponError("");
// Write in local storage
localStorage.setItem("lastSuccessCoupon", coupon);
localStorage.setItem("couponSignature", JSON.stringify(signature));
}
setLoadingCoupon(false);
});
}, [coupon, domain, address]);

const handleRegister = () =>
execute().then(() =>
setDomainsMinting((prev) =>
new Map(prev).set(encodedDomain.toString(), true)
)
);

return (
<div className={styles.container}>
<div className={styles.card}>
<div className={styles.form}>
<BackButton onClick={() => goBack()} />
<div className="flex flex-col items-start gap-4 self-stretch">
<p className={styles.legend}>Your registration</p>
<h3 className={styles.domain}>{getDomainWithStark(domain)}</h3>
</div>
<div className="flex flex-col items-start gap-6 self-stretch">
{couponCode ? (
<TextField
helperText={couponHelper}
label="Coupon code"
value={coupon}
onChange={(e) => changeCoupon(e.target.value)}
color="secondary"
error={Boolean(couponError)}
errorMessage={couponError}
/>
) : null}
</div>
</div>
<div className={styles.summary}>
<RegisterSummary
duration={Number(numberToFixedString(duration / 365))}
renewalBox={false}
customMessage={customMessage}
isFree={true}
/>
<Divider className="w-full" />
<TermCheckbox
checked={termsBox}
onChange={() => setTermsBox(!termsBox)}
/>
{address ? (
<Button
onClick={handleRegister}
disabled={
(domainsMinting.get(encodedDomain) as boolean) ||
!account ||
!duration ||
!targetAddress ||
!termsBox ||
Boolean(couponError) ||
loadingCoupon
}
>
{!termsBox
? "Please accept terms & policies"
: couponError
? "Enter a valid Coupon"
: "Register my domain"}
</Button>
) : (
<ConnectButton />
)}
</div>
</div>
<div
className={styles.image}
style={{
backgroundImage: `url(${banner})`,
}}
/>
</div>
);
};

export default FreeRegisterCheckout;
52 changes: 52 additions & 0 deletions components/discount/freeRegisterPresentation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from "react";
import type { FunctionComponent } from "react";
import styles from "../../styles/discount.module.css";
import SearchBar from "../UI/searchBar";
import Timer from "../UI/timer";

type FreeRegisterPresentationProps = {
setSearchResult: (searchResult: SearchResult) => void;
setScreen: (screen: number) => void;
title: { desc: string; catch: string; descAfter?: string };
desc: string;
image: string;
expiry: number;
};

const FreeRegisterPresentation: FunctionComponent<
FreeRegisterPresentationProps
> = ({ setSearchResult, setScreen, title, desc, image, expiry }) => {
function onSearch(searchResult: SearchResult) {
setSearchResult(searchResult);
setScreen(2);
}

return (
<div className={styles.wrapperScreen}>
<div className={styles.containerVariant}>
<div className="max-w-4xl flex flex-col items-start justify-start gap-5 mx-5 mb-5">
<div className="flex flex-col lg:items-start items-center text-center sm:text-start gap-3">
<h1 className={styles.titleVariant}>
{title.desc} <span className="text-primary">{title.catch}</span>
{title.descAfter && ` ${title.descAfter}`}
</h1>
<p className={styles.descriptionVariant}>{desc}</p>
</div>
<div className={styles.searchBarVariant}>
<SearchBar onSearch={onSearch} showHistory={false} is5LettersOnly />
</div>
</div>
<div className={styles.illustrationContainerVariant}>
<img
src={image}
className={styles.illustrationVariant}
alt="Registration illustration"
/>
<Timer expiry={expiry} fixed />
</div>
</div>
</div>
);
};

export default FreeRegisterPresentation;
6 changes: 3 additions & 3 deletions components/discount/freeRenewalDiscount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import {
import { areArraysEqual } from "@/utils/arrayService";
import useNeedSubscription from "@/hooks/useNeedSubscription";

type FreeRenewalDiscountProps = {
type FreeRenewalCheckoutProps = {
groups: string[];
goBack: () => void;
duration: number;
Expand All @@ -61,7 +61,7 @@ type FreeRenewalDiscountProps = {
renewPrice: string;
};

const FreeRenewalDiscount: FunctionComponent<FreeRenewalDiscountProps> = ({
const FreeRenewalCheckout: FunctionComponent<FreeRenewalCheckoutProps> = ({
groups,
priceInEth,
renewPrice,
Expand Down Expand Up @@ -442,4 +442,4 @@ const FreeRenewalDiscount: FunctionComponent<FreeRenewalDiscountProps> = ({
);
};

export default FreeRenewalDiscount;
export default FreeRenewalCheckout;
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@ import homeStyles from "../../styles/Home.module.css";
import Timer from "../UI/timer";
import Button from "../UI/button";

type DiscountRenewalScreenProps = {
type FreeRenewalPresentationProps = {
title: { desc: string; catch: string };
desc: string;
image: string;
expiry: number;
setScreen: (screen: number) => void;
};

const DiscountRenewalScreen: FunctionComponent<DiscountRenewalScreenProps> = ({
title,
desc,
image,
expiry,
setScreen,
}) => {
const FreeRenewalPresentation: FunctionComponent<
FreeRenewalPresentationProps
> = ({ title, desc, image, expiry, setScreen }) => {
return (
<div className={homeStyles.wrapperScreen}>
<div className={styles.container}>
Expand All @@ -42,4 +38,4 @@ const DiscountRenewalScreen: FunctionComponent<DiscountRenewalScreenProps> = ({
);
};

export default DiscountRenewalScreen;
export default FreeRenewalPresentation;
Loading

0 comments on commit df74337

Please sign in to comment.