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 16 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
44 changes: 0 additions & 44 deletions components/discount/discountCheckoutScreen.tsx

This file was deleted.

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

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

const FreeRegisterDiscount: FunctionComponent<FreeRegisterDiscountProps> = ({
setSearchResult,
setScreen,
title,
desc,
image,
expiry,
}) => {
function onSearch(searchResult: SearchResult) {
setSearchResult(searchResult);
setScreen(2);
}
fricoben marked this conversation as resolved.
Show resolved Hide resolved

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} />
<Timer expiry={expiry} fixed />
</div>
</div>
</div>
);
};

export default FreeRegisterDiscount;
229 changes: 229 additions & 0 deletions components/discount/registerFree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
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 RegisterFreeProps = {
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 RegisterFree: FunctionComponent<RegisterFreeProps> = ({
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]);

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={() =>
execute().then(() =>
setDomainsMinting((prev) =>
new Map(prev).set(encodedDomain.toString(), true)
)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Make a function here to make the code cleaner

}
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 RegisterFree;
42 changes: 6 additions & 36 deletions components/domains/registerCheckboxes.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { FunctionComponent } from "react";
import { Checkbox } from "@mui/material";
import styles from "../../styles/components/variants.module.css";
import InputHelper from "../UI/inputHelper";
import { gweiToEth } from "../../utils/feltService";
import { CurrencyType } from "@/utils/constants";
import TermCheckbox from "./termCheckbox";

type RegisterCheckboxes = {
termsBox: boolean;
Expand Down Expand Up @@ -47,41 +47,11 @@ const RegisterCheckboxes: FunctionComponent<RegisterCheckboxes> = ({
return (
<div className="w-full mb-3">
<div className="flex flex-col gap-3">
<div className="flex items-center justify-left text-xs mr-2">
<Checkbox
checked={termsBox}
className={
variant === "white"
? styles.whiteCheckbox
: styles.defaultCheckbox
}
onClick={onChangeTermsBox}
/>
<p className="ml-2 text-left">
<span className="cursor-pointer" onClick={onChangeTermsBox}>
Accept
</span>{" "}
<a
className="underline"
href={process.env.NEXT_PUBLIC_STARKNET_ID + "/pdfs/Terms.pdf"}
target="_blank"
rel="noreferrer"
>
terms
</a>{" "}
&{" "}
<a
className="underline"
href={
process.env.NEXT_PUBLIC_STARKNET_ID + "/pdfs/PrivacyPolicy.pdf"
}
target="_blank"
rel="noreferrer"
>
policies
</a>
</p>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

Super !

<TermCheckbox
checked={termsBox}
onChange={onChangeTermsBox}
variant={variant}
/>
{!isArOnforced ? (
<InputHelper helperText={getHelperText()}>
<div
Expand Down
Loading