Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: domain gift page #801

Merged
merged 24 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ba22ed4
feat: domain gift page
Marchand-Nicolas May 22, 2024
83269b3
feat: API & contract interactions
Marchand-Nicolas May 22, 2024
89ee166
fix: only 5+ length domains accepted
Marchand-Nicolas May 22, 2024
9ea8ecd
Update utils/campaignService.ts
Marchand-Nicolas May 22, 2024
c5a0c7c
cleaning the code
Marchand-Nicolas May 22, 2024
10063fd
cleaning the code
Marchand-Nicolas May 22, 2024
eb0c9b5
Update pages/freeregistration.tsx
Marchand-Nicolas May 22, 2024
59ddb60
feat: cleaning the code + better UX
Marchand-Nicolas May 23, 2024
5d71464
fixing build
Marchand-Nicolas May 23, 2024
c691787
Update components/discount/registerFree.tsx
Marchand-Nicolas May 23, 2024
23f9132
Update components/discount/registerFree.tsx
Marchand-Nicolas May 23, 2024
825ef55
using local storage for signature
Marchand-Nicolas May 24, 2024
070e7ed
Update components/discount/registerFree.tsx
Marchand-Nicolas May 25, 2024
60679a7
fix: duplicate import
Marchand-Nicolas May 25, 2024
b81af6f
Merge branch 'testnet' into feat/domain-gift-page
Marchand-Nicolas May 29, 2024
baf51ff
fix: removing coupon from starknet call
Marchand-Nicolas May 30, 2024
2061244
Adding identity mint
Marchand-Nicolas May 31, 2024
8177da2
cleaning the code
Marchand-Nicolas May 31, 2024
6fd2460
fix: gift image
Marchand-Nicolas May 31, 2024
e1d7dc0
Update components/discount/FreeRegisterPresentation.tsx
Marchand-Nicolas May 31, 2024
2ea43bd
Update components/discount/FreeRegisterPresentation.tsx
Marchand-Nicolas May 31, 2024
462cf13
Update components/discount/FreeRegisterPresentation.tsx
Marchand-Nicolas May 31, 2024
ed5b0e1
cleaning the code
Marchand-Nicolas Jun 3, 2024
e2a19d0
cleaning the code
Marchand-Nicolas Jun 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions components/discount/FreeRegisterPresentation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { FunctionComponent } from "react";
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
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}
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
</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} />
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
<Timer expiry={expiry} fixed />
</div>
</div>
</div>
);
};

export default FreeRegisterPresentation;
44 changes: 0 additions & 44 deletions components/discount/discountCheckoutScreen.tsx

This file was deleted.

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 = {
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
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;
230 changes: 230 additions & 0 deletions components/discount/registerFree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import React from "react";
import type { FunctionComponent } from "react";
import { useEffect, useState } from "react";
import type { Call } from "starknet";
import Button from "../UI/button";
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
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 TxConfirmationModal from "../UI/txConfirmationModal";
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";

type FreeRegisterCheckoutProps = {
domain: string;
duration: number;
customMessage: string;
goBack: () => void;
couponCode?: boolean;
couponHelper?: string;
banner: string;
};
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved

const FreeRegisterCheckout: FunctionComponent<FreeRegisterCheckoutProps> = ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the name of the file is different that the name of the component

domain,
duration,
customMessage,
goBack,
couponCode,
couponHelper,
banner,
}) => {
const [targetAddress, setTargetAddress] = useState<string>("");
const [callData, setCallData] = useState<Call[]>([]);
const [salt, setSalt] = useState<string | undefined>();
const [isTxModalOpen, setIsTxModalOpen] = useState(false);
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 [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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you minting the identity ? I think it's missing

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(() => {
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
if (!registerData?.transaction_hash) return;
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
posthog?.capture("register");
addTransaction({
timestamp: Date.now(),
subtext: "Domain registration",
type: NotificationType.TRANSACTION,
data: {
type: TransactionType.BUY_DOMAIN,
hash: registerData.transaction_hash,
status: "pending",
},
});
setIsTxModalOpen(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [registerData]);
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved

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>
<img className={styles.image} src={banner} alt="Banner image" />
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
<TxConfirmationModal
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
txHash={registerData?.transaction_hash}
isTxModalOpen={isTxModalOpen}
closeModal={() => setIsTxModalOpen(false)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having this modal, you can reuse the model we have on the registration part by sending it to a different page (confirmation.tsx with the URL /confirmation?tokenId=ID).

title="Your domain is on it's way !"
/>
</div>
);
};

export default FreeRegisterCheckout;
Loading