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(launchpad): Add Collection Creation form front #1477

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

WaDadidou
Copy link
Collaborator

@WaDadidou WaDadidou commented Jan 5, 2025

more description could come soon...


Testable here: https://deploy-preview-1477--teritori-dapp.netlify.app/launchpad/apply (Teritori Testnet)


What's in this PR?

This PR is extracted from this one: #1024
It adds the Collection Creation form
image

The flow starts here:
<OmniLink
noHoverEffect
to={{ screen: "LaunchpadCreate" }}
style={{
flex: 1,
marginHorizontal: width >= MD_BREAKPOINT ? layout.spacing_x1_5 : 0,
marginVertical: width >= MD_BREAKPOINT ? 0 : layout.spacing_x1_5,
}}
>
<LargeBoxButton {...BUTTONS[1]} />
</OmniLink>

image

The flow ends here:
await nftLaunchpadContractClient.submitCollection({
collection,
});

image

I made this Zod object to pilot the Collection Creation form

export const ZodCollectionFormValues = z.object({
name: z.string().trim().min(1, DEFAULT_FORM_ERRORS.required),
description: z.string().trim().min(1, DEFAULT_FORM_ERRORS.required),
symbol: z
.string()
.trim()
.toUpperCase()
.min(1, DEFAULT_FORM_ERRORS.required)
.refine(
(value) => LETTERS_REGEXP.test(value),
DEFAULT_FORM_ERRORS.onlyLetters,
),
websiteLink: z
.string()
.trim()
.min(1, DEFAULT_FORM_ERRORS.required)
.refine((value) => URL_REGEX.test(value), DEFAULT_FORM_ERRORS.onlyUrl),
email: z
.string()
.trim()
.min(1, DEFAULT_FORM_ERRORS.required)
.refine((value) => EMAIL_REGEXP.test(value), DEFAULT_FORM_ERRORS.onlyEmail),
projectTypes: z.array(z.string().trim()).min(1, DEFAULT_FORM_ERRORS.required),
revealTime: z.number().optional(),
teamDescription: z.string().trim().min(1, DEFAULT_FORM_ERRORS.required),
partnersDescription: z.string().trim().min(1, DEFAULT_FORM_ERRORS.required),
investDescription: z.string().trim().min(1, DEFAULT_FORM_ERRORS.required),
investLink: z.string().trim().min(1, DEFAULT_FORM_ERRORS.required),
artworkDescription: z.string().trim().min(1, DEFAULT_FORM_ERRORS.required),
coverImage: ZodLocalFileData,
isPreviouslyApplied: z.boolean(),
isDerivativeProject: z.boolean(),
isReadyForMint: z.boolean(),
isDox: z.boolean(),
escrowMintProceedsPeriod: z
.string()
.trim()
.min(1, DEFAULT_FORM_ERRORS.required),
daoWhitelistCount: z
.string()
.trim()
.min(1, DEFAULT_FORM_ERRORS.required)
.refine(
(value) => NUMBERS_REGEXP.test(value),
DEFAULT_FORM_ERRORS.onlyNumbers,
),
mintPeriods: z.array(ZodCollectionMintPeriodFormValues).nonempty(),
royaltyAddress: z.string().trim().optional(),
royaltyPercentage: z
.string()
.trim()
.refine(
(value) => !value || NUMBERS_REGEXP.test(value),
DEFAULT_FORM_ERRORS.onlyNumbers,
)
.optional(),
assetsMetadatas: ZodCollectionAssetsMetadatasFormValues.optional(),
baseTokenUri: z
.string()
.trim()
.refine(
(value) => !value || isIpfsPathValid(value),
DEFAULT_FORM_ERRORS.onlyIpfsUri,
)
.optional(),
coverImageUri: z
.string()
.trim()
.refine(
(value) => !value || isIpfsPathValid(value),
DEFAULT_FORM_ERRORS.onlyIpfsUri,
)
.optional(),
});

The data will be send onchain using the nft-launchpad contract's submit_collection:

About Assets & Metadata

Use these files to test the step 6: test-files-step-6.zip
You will get these 2 warnings. It's expected, I wrongly filled the files to have these warnings as a demo.
So, you will get 3 images instead of 4 at the end.
You can fix the CSV files if you want, add assets, break the files, etc
image

The warnings are don't block the flow, it just ignores the wrong assets. But I added errors that block the flow and ask the user to provide correct mapping files:
image

All these behaviors are handled here: https://github.com/TERITORI/teritori-dapp/blob/6ca68e031a528902d29ea9d559b466175de57902/packages/screens/Launchpad/LaunchpadApply/LaunchpadCreate/components/steps/LaunchpadAssetsAndMetadata/AssetsTab.tsx
I tried to well comment the code


What after this PR?

I want to separate the front in parts

  • Apply - Create Collection (This PR)
  • Apply - My Collections + Complete Collection
  • Admin - Overview
  • Admin - Collection Review

Possible enhancement that can be done in another PRs

We have to write a documentation

image
Especialy to guide the user on this step:
image
We must also provide two CSV templates: Attributes mapping file and an Assets mapping file

Copy link

netlify bot commented Jan 5, 2025

Deploy Preview for teritori-dapp ready!

Name Link
🔨 Latest commit 6ca68e0
🔍 Latest deploy log https://app.netlify.com/sites/teritori-dapp/deploys/677d83c2243a0c00083a6e36
😎 Deploy Preview https://deploy-preview-1477--teritori-dapp.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

netlify bot commented Jan 5, 2025

Deploy Preview for gno-dapp ready!

Name Link
🔨 Latest commit 6ca68e0
🔍 Latest deploy log https://app.netlify.com/sites/gno-dapp/deploys/677d83c21227d60008e77133
😎 Deploy Preview https://deploy-preview-1477--gno-dapp.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@WaDadidou WaDadidou force-pushed the feat-launchpad-submit-collection-front branch from 2bf16bf to 6ca68e0 Compare January 7, 2025 19:42
Copy link
Member

@MikaelVallenet MikaelVallenet left a comment

Choose a reason for hiding this comment

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

LGTM

Comment on lines +75 to +129
const hasErrors = (stepKey: number) => {
if (
(stepKey === 1 &&
(!!collectionForm.getFieldState("name").error ||
!!collectionForm.getFieldState("description").error ||
!!collectionForm.getFieldState("symbol").error)) ||
!!collectionForm.getFieldState("coverImage").error ||
!!collectionForm.getFieldState("assetsMetadatas.nftApiKey").error
) {
return true;
}
if (
stepKey === 2 &&
(!!collectionForm.getFieldState("websiteLink").error ||
!!collectionForm.getFieldState("isDerivativeProject").error ||
!!collectionForm.getFieldState("projectTypes").error ||
!!collectionForm.getFieldState("isPreviouslyApplied").error ||
!!collectionForm.getFieldState("email").error)
) {
return true;
}
if (
stepKey === 3 &&
(!!collectionForm.getFieldState("teamDescription").error ||
!!collectionForm.getFieldState("partnersDescription").error ||
!!collectionForm.getFieldState("investDescription").error ||
!!collectionForm.getFieldState("investLink").error)
) {
return true;
}
if (
stepKey === 4 &&
(!!collectionForm.getFieldState("artworkDescription").error ||
!!collectionForm.getFieldState("isReadyForMint").error ||
!!collectionForm.getFieldState("isDox").error ||
!!collectionForm.getFieldState("daoWhitelistCount").error ||
!!collectionForm.getFieldState("escrowMintProceedsPeriod").error)
) {
return true;
}
if (
stepKey === 5 &&
(!!collectionForm.getFieldState("mintPeriods").error ||
!!collectionForm.getFieldState("royaltyAddress").error ||
!!collectionForm.getFieldState("royaltyPercentage").error)
) {
return true;
}
if (
stepKey === 6 &&
!!collectionForm.getFieldState("assetsMetadatas").error
) {
return true;
}
};
Copy link
Member

Choose a reason for hiding this comment

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

Do react hook form not give some methods to handle this cleaner ?
Or should we do a map[step]list of field key to just loop through the fields corresponding to the steps
like for_, val := map[step] { if !! colectionForm.getFieldState(val).error} kinda

Copy link
Collaborator

Choose a reason for hiding this comment

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

In the return of useForm hook you have an object named formState and inside of it you have errors. I don't know what infos you have exactly but can be a solution.

const { formState: { errors } } = useForm()

But agree with @MikaelVallenet that this if forest is not very clean, and if you can't handle this with errors the map can be a better solution too

@MikaelVallenet
Copy link
Member

What is this about, i can choose between 1, 2 & 3

image

@MikaelVallenet
Copy link
Member

Why would they give us some whitelist ? should we incentivize it or maybe turn this like "Teritori develop these tools with ❤️, if you appreciate it you could contribute by giving some WL spot to our DAO" this way it does not look like "we would love to have some WL to share to our DAO members" but more like you can reward use for the dev. of these tools by giving some. wdyt
image

@MikaelVallenet
Copy link
Member

image
Would be nice to display the timezone to avoid misunderstandings

Copy link
Collaborator

@clegirar clegirar left a comment

Choose a reason for hiding this comment

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

In general could you please use fontRegular instead of fontMedium or fontSemiBold ? I can do it if you want 👍

About using useFormContext i think my mind can change because when you call it, you specify the type of the form so i'm not confusing when you call it, and found it better to read, it's not a lot of additional abstraction.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This component is used once and it's almost the same than MultipleSelectInput maybe you can use MultipleSelectInput instead of create an other one ?

Comment on lines +75 to +129
const hasErrors = (stepKey: number) => {
if (
(stepKey === 1 &&
(!!collectionForm.getFieldState("name").error ||
!!collectionForm.getFieldState("description").error ||
!!collectionForm.getFieldState("symbol").error)) ||
!!collectionForm.getFieldState("coverImage").error ||
!!collectionForm.getFieldState("assetsMetadatas.nftApiKey").error
) {
return true;
}
if (
stepKey === 2 &&
(!!collectionForm.getFieldState("websiteLink").error ||
!!collectionForm.getFieldState("isDerivativeProject").error ||
!!collectionForm.getFieldState("projectTypes").error ||
!!collectionForm.getFieldState("isPreviouslyApplied").error ||
!!collectionForm.getFieldState("email").error)
) {
return true;
}
if (
stepKey === 3 &&
(!!collectionForm.getFieldState("teamDescription").error ||
!!collectionForm.getFieldState("partnersDescription").error ||
!!collectionForm.getFieldState("investDescription").error ||
!!collectionForm.getFieldState("investLink").error)
) {
return true;
}
if (
stepKey === 4 &&
(!!collectionForm.getFieldState("artworkDescription").error ||
!!collectionForm.getFieldState("isReadyForMint").error ||
!!collectionForm.getFieldState("isDox").error ||
!!collectionForm.getFieldState("daoWhitelistCount").error ||
!!collectionForm.getFieldState("escrowMintProceedsPeriod").error)
) {
return true;
}
if (
stepKey === 5 &&
(!!collectionForm.getFieldState("mintPeriods").error ||
!!collectionForm.getFieldState("royaltyAddress").error ||
!!collectionForm.getFieldState("royaltyPercentage").error)
) {
return true;
}
if (
stepKey === 6 &&
!!collectionForm.getFieldState("assetsMetadatas").error
) {
return true;
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

In the return of useForm hook you have an object named formState and inside of it you have errors. I don't know what infos you have exactly but can be a solution.

const { formState: { errors } } = useForm()

But agree with @MikaelVallenet that this if forest is not very clean, and if you can't handle this with errors the map can be a better solution too

const { width: windowWidth } = useWindowDimensions();
const scrollViewRef = useRef<ScrollView>(null);
const isMobile = useIsMobile();
const collectionForm = useFormContext<CollectionFormValues>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

In my refacto on react-hook-form, with norman we said to each other that don't use useFormContext to handle our form. I don't have final opinion actually on how to use it, but i just want to be consistent when we use it

Copy link
Collaborator

Choose a reason for hiding this comment

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

Found this file too big.. Can't extract functions ?

Comment on lines +64 to +69
style={[
fontMedium14,
{
color: neutral77,
},
]}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can fit in one line

Comment on lines +74 to +78
fontMedium14,
{
color: primaryColor,
},
]}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here

Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here you can't use MultipleSelectInput ?

@clegirar
Copy link
Collaborator

clegirar commented Jan 8, 2025

Screenshot 2025-01-08 at 11 52 51
Select file button is not centered, found that weird

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants