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 5 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
14 changes: 13 additions & 1 deletion components/discount/discountCheckoutScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { FunctionComponent } from "react";
import styles from "../../styles/discount.module.css";
import RegisterDiscount from "./registerDiscount";
import RegisterDiscount, { GetCustomCalls } from "./registerDiscount";

type DiscountCheckoutScreenProps = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this component really useful ? It seems like a wrapper for RegisterDiscount

It would delete a lot of useless code to directly use RegisterDiscount where needed and add <div className={styles.container}> in it

You have more context than me as I don't remember so well this part of the code. Wdyt ?

domain: string;
Expand All @@ -10,6 +10,10 @@ type DiscountCheckoutScreenProps = {
price: string;
goBack: () => void;
mailGroupId: string;
couponCode?: boolean;
couponHelper?: string;
banner?: string;
getCustomCalls?: GetCustomCalls;
};

const DiscountCheckoutScreen: FunctionComponent<
Expand All @@ -22,6 +26,10 @@ const DiscountCheckoutScreen: FunctionComponent<
price,
goBack,
mailGroupId,
couponCode,
couponHelper,
banner,
getCustomCalls,
}) => {
return (
<div className={styles.container}>
Expand All @@ -36,6 +44,10 @@ const DiscountCheckoutScreen: FunctionComponent<
mailGroupId,
]} // Second group is the special group for discount group
goBack={goBack}
couponCode={couponCode}
couponHelper={couponHelper}
banner={banner}
getCustomCalls={getCustomCalls}
/>
</div>
);
Expand Down
47 changes: 47 additions & 0 deletions components/discount/discountOfferScreenVariant.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { FunctionComponent } from "react";
import styles from "../../styles/discount.module.css";
import SearchBar from "../UI/searchBar";
import Timer from "../UI/timer";

type DiscountOfferScreenVariantProps = {
Copy link
Contributor

Choose a reason for hiding this comment

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

change name to what FreeRegisterDiscount

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

const DiscountOfferScreenVariant: FunctionComponent<
DiscountOfferScreenVariantProps
> = ({ 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} />
<Timer expiry={expiry} fixed />
</div>
</div>
</div>
);
};

export default DiscountOfferScreenVariant;
86 changes: 84 additions & 2 deletions components/discount/registerDiscount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ import {
getDomainPriceAltcoin,
getTokenQuote,
} from "../../utils/altcoinService";
import { getFreeDomain } from "@/utils/campaignService";

export type GetCustomCalls = (
Copy link
Contributor

Choose a reason for hiding this comment

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

no need for a type.

Use the Call type and follow what we do in registrationCalls.ts

newTokenId: number,
encodedDomain: string,
signature: string[],
coupon: string,
txMetadataHash: HexString
) => Call[];

type RegisterDiscountProps = {
domain: string;
Expand All @@ -49,6 +58,10 @@ type RegisterDiscountProps = {
priceInEth: string;
mailGroups: string[];
goBack: () => void;
couponCode?: boolean;
couponHelper?: string;
banner?: string;
getCustomCalls?: GetCustomCalls;
};

const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
Expand All @@ -59,6 +72,10 @@ const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
priceInEth,
mailGroups,
goBack,
couponCode,
couponHelper,
banner = "/visuals/register.webp",
getCustomCalls,
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think of creating a freeRegister.tsx component based on this one instead of reusing it ?

you wouldn't need to pass the calls in props ...

I think it would be clearer tbh

}) => {
const [targetAddress, setTargetAddress] = useState<string>("");
const [email, setEmail] = useState<string>("");
Expand Down Expand Up @@ -89,6 +106,11 @@ const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
const [domainsMinting, setDomainsMinting] = useState<Map<string, boolean>>(
new Map()
);
const [coupon, setCoupon] = useState<string>("");
const [lastSuccessCoupon, setLastSuccessCoupon] = useState<string>("");
const [couponError, setCouponError] = useState<string>("");
const [signature, setSignature] = useState<string[]>(["", ""]);
const [loadingCoupon, setLoadingCoupon] = useState<boolean>(false);
const { addTransaction } = useNotificationManager();
const needsAllowance = useAllowanceCheck(displayedCurrency, address);
const tokenBalances = useBalances(address); // fetch the user balances for all whitelisted tokens
Expand Down Expand Up @@ -192,6 +214,17 @@ const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
const addressesMatch =
hexToDecimal(address) === hexToDecimal(targetAddress);

if (getCustomCalls) {
const customCalls = getCustomCalls(
newTokenId,
encodedDomain,
signature,
coupon,
txMetadataHash
);
return setCallData(customCalls);
}

// Common calls
const calls = [
registrationCalls.approve(price, ERC20Contract[displayedCurrency]),
Expand Down Expand Up @@ -282,6 +315,9 @@ const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
discountId,
quoteData,
displayedCurrency,
coupon,
signature,
getCustomCalls,
]);

useEffect(() => {
Expand Down Expand Up @@ -322,6 +358,34 @@ const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
setEmailError(isValidEmail(value) ? false : true);
}

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

useEffect(() => {
if (!coupon) {
setCouponError("Please enter a coupon code");
setLoadingCoupon(false);
return;
}
if (coupon === lastSuccessCoupon) {
setCouponError("");
setLoadingCoupon(false);
return;
}
if (!address) return;
getFreeDomain(address, `${domain}.stark`, coupon).then((res) => {
if (res.error) setCouponError(res.error);
else {
setSignature([res.r, res.s]);
setLastSuccessCoupon(coupon);
setCouponError("");
}
setLoadingCoupon(false);
});
Marchand-Nicolas marked this conversation as resolved.
Show resolved Hide resolved
}, [coupon, domain, address, lastSuccessCoupon]);

useEffect(() => {
if (isSwissResident) {
setSalesTaxRate(swissVatRate);
Expand All @@ -347,6 +411,8 @@ const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
setDisplayedCurrency(type);
};

const isFree = priceInEth === "0";

return (
<div className={styles.container}>
<div className={styles.card}>
Expand All @@ -370,6 +436,17 @@ const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
isSwissResident={isSwissResident}
Copy link
Contributor

Choose a reason for hiding this comment

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

Capture d’écran 2024-05-23 à 15 19 20

In the process of trying to make things as simple as possible, I would like to delete both of these TextField and isSwissForm and all the associated metadata ...

Considering this it might be better to rewrite a component freeRegister.tsx considering we'll have a lot of changes I think it would be cleaner overall

Copy link
Contributor

Choose a reason for hiding this comment

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

Capture d’écran 2024-05-23 à 15 22 31

In the same context we would like to delete this option and the call data associated

onSwissResidentChange={() => setIsSwissResident(!isSwissResident)}
/>
{couponCode ? (
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes so this is the kind of stuff we want to avoid in a component in general to make it as general as possible

<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}>
Expand All @@ -384,6 +461,7 @@ const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
onCurrencySwitch={onCurrencySwitch}
customMessage={customMessage}
loadingPrice={loadingPrice}
isFree={isFree}
/>
<Divider className="w-full" />
<RegisterCheckboxes
Expand All @@ -408,7 +486,9 @@ const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
!targetAddress ||
invalidBalance ||
!termsBox ||
emailError
emailError ||
Boolean(couponError) ||
loadingCoupon
}
>
{!termsBox
Expand All @@ -417,14 +497,16 @@ const RegisterDiscount: FunctionComponent<RegisterDiscountProps> = ({
? `You don't have enough ${displayedCurrency}`
: emailError
? "Enter a valid Email"
: couponError
? "Enter a valid Coupon"
: "Register my domain"}
</Button>
) : (
<ConnectButton />
)}
</div>
</div>
<img className={styles.image} src="/visuals/register.webp" />
<img className={styles.image} src={banner} />
<TxConfirmationModal
txHash={registerData?.transaction_hash}
isTxModalOpen={isTxModalOpen}
Expand Down
18 changes: 13 additions & 5 deletions components/domains/registerSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type RegisterSummaryProps = {
discountedPrice?: string; // price the user will pay after discount
discountedDuration?: number; // years the user will have the domain for after discount
areArCurrenciesEnabled?: boolean;
isFree?: boolean;
};

const RegisterSummary: FunctionComponent<RegisterSummaryProps> = ({
Expand All @@ -46,6 +47,7 @@ const RegisterSummary: FunctionComponent<RegisterSummaryProps> = ({
discountedPrice,
discountedDuration,
areArCurrenciesEnabled = false,
isFree = false,
}) => {
const [ethUsdPrice, setEthUsdPrice] = useState<string>("0"); // price of 1ETH in USD
const [usdRegistrationPrice, setUsdRegistrationPrice] = useState<string>("0");
Expand Down Expand Up @@ -150,15 +152,21 @@ const RegisterSummary: FunctionComponent<RegisterSummaryProps> = ({
<h4 className={styles.totalDueTitle}>Total due:</h4>
<div className={styles.priceContainer}>
<p className={styles.legend}>{getMessage()}</p>
{loadingPrice ? (
<Skeleton variant="text" width="150px" height="24px" />
{isFree ? (
"Free"
) : (
displayTokenPrice()
<>
{loadingPrice ? (
<Skeleton variant="text" width="150px" height="24px" />
) : (
displayTokenPrice()
)}
<p className={styles.legend}>≈ ${usdRegistrationPrice}</p>
</>
)}
<p className={styles.legend}>≈ ${usdRegistrationPrice}</p>
</div>
</div>
{areArCurrenciesEnabled ? (
{isFree ? null : areArCurrenciesEnabled ? (
<ArCurrencyDropdown
displayedCurrency={displayedCurrency as CurrencyType[]} // as CurrencyType[] is safe here cause we know the value is a CurrencyType[]
onCurrencySwitch={onCurrencySwitch as (type: CurrencyType[]) => void}
Expand Down
86 changes: 86 additions & 0 deletions pages/domain-gift.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { useEffect, useState } from "react";
Copy link
Contributor

Choose a reason for hiding this comment

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

freeregistration.tsx

import type { NextPage } from "next";
import homeStyles from "../styles/Home.module.css";
import DiscountEndScreen from "../components/discount/discountEndScreen";
import DiscountCheckoutScreen from "../components/discount/discountCheckoutScreen";

// Create a new discount in utils to create a new discount campaign
import { domainGift } from "../utils/discounts/domainGift";
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace domain gift by freeRegistration everywhere.

It's a better naming cause for the code it gives more information about you're doing.

import DiscountOfferScreenVariant from "@/components/discount/discountOfferScreenVariant";
import { GetCustomCalls } from "@/components/discount/registerDiscount";

const DomainGift: NextPage = () => {
const [searchResult, setSearchResult] = useState<SearchResult | undefined>();
const [screen, setScreen] = useState<number>(1);

useEffect(() => {
const currentDate = new Date();
const timestamp = currentDate.getTime();

if (timestamp >= domainGift.expiry) setScreen(0);
}, []);

function goBack() {
setScreen(screen - 1);
}

const getCustomCalls: GetCustomCalls = (
Copy link
Contributor

Choose a reason for hiding this comment

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

add to registrationCall.ts adding a relevant naming :)

newTokenId,
encodedDomain,
signature,
coupon,
txMetadataHash
) => {
return [
{
contractAddress: process.env.NEXT_PUBLIC_DOMAIN_GIFT_CONTRACT as string,
entrypoint: "get_free_domain",
calldata: [
newTokenId,
encodedDomain,
signature,
coupon,
txMetadataHash,
].flat(),
},
];
};

return (
<div className={homeStyles.screen}>
{screen === 0 ? (
<DiscountEndScreen
title={`${domainGift.name} discount has ended`}
image={domainGift.image}
/>
) : null}
{screen === 1 ? (
<DiscountOfferScreenVariant
Copy link
Contributor

Choose a reason for hiding this comment

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

No description provided.

title={domainGift.offer.title}
desc={domainGift.offer.desc}
image={domainGift.offer.image ?? domainGift.image}
setSearchResult={setSearchResult}
setScreen={setScreen}
expiry={domainGift.expiry}
/>
) : null}
{screen === 2 ? (
<DiscountCheckoutScreen
domain={searchResult?.name ?? ""}
duration={domainGift.offer.duration}
discountId={domainGift.offer.discountId}
customMessage={domainGift.offer.customMessage}
price={domainGift.offer.price}
goBack={goBack}
mailGroupId={domainGift.discountMailGroupId}
couponCode={domainGift.offer.couponCode}
couponHelper={domainGift.offer.couponHelper}
banner={domainGift.image}
getCustomCalls={getCustomCalls}
/>
) : null}
</div>
);
};

export default DomainGift;
Binary file added public/visuals/gift.webp
Binary file not shown.
Loading