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

[ERC20 Launchpad] - UI for Launch token community step #9194

Merged
merged 12 commits into from
Sep 13, 2024
Merged
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import '../../../../../../../styles/shared.scss';
@import '../../../../styles/shared';

.BasicInformationForm {
.CommunityInformationForm {
display: flex;
flex-direction: column;
gap: 24px;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import React, { useState } from 'react';
import { slugifyPreserveDashes } from 'utils';

import { ChainBase } from '@hicommonwealth/shared';
import { notifyError } from 'controllers/app/notifications';
import useCreateCommunityMutation, {
buildCreateCommunityInput,
} from 'state/api/communities/createCommunity';
import { useFetchConfigurationQuery } from 'state/api/configuration';
import {
CWCoverImageUploader,
Expand All @@ -19,15 +14,7 @@ import { CommunityType } from 'views/components/component_kit/new_designs/CWComm
import { CWForm } from 'views/components/component_kit/new_designs/CWForm';
import { CWSelectList } from 'views/components/component_kit/new_designs/CWSelectList';
import { CWTextInput } from 'views/components/component_kit/new_designs/CWTextInput';
import { openConfirmation } from 'views/modals/confirmation_modal';
import {
BaseMixpanelPayload,
MixpanelCommunityCreationEvent,
MixpanelLoginPayload,
} from '../../../../../../../../shared/analytics/types';
import useAppStatus from '../../../../../../hooks/useAppStatus';
import { useBrowserAnalyticsTrack } from '../../../../../../hooks/useBrowserAnalyticsTrack';
import './BasicInformationForm.scss';
import './CommunityInformationForm.scss';
import {
BASE_ID,
BLAST_ID,
Expand All @@ -36,19 +23,26 @@ import {
POLYGON_ETH_CHAIN_ID,
alphabeticallyStakeWiseSortedChains as sortedChains,
} from './constants';
import { BasicInformationFormProps, FormSubmitValues } from './types';
import {
CommunityInformationFormProps,
CommunityInformationFormSubmitValues,
} from './types';
import useSocialLinks from './useSocialLinks';
import { basicInformationFormValidationSchema } from './validation';

const socialLinksDisplay = false; // TODO: Set this when design figures out how we will integrate the social links
import {
baseCommunityInformationFormValidationSchema,
communityChainValidation,
} from './validation';

const BasicInformationForm = ({
selectedAddress,
selectedCommunity,
const CommunityInformationForm = ({
onSubmit,
onCancel,
handleSelectedChainId,
}: BasicInformationFormProps) => {
onWatch,
withChainsConfig,
withSocialLinks = false,
initialValues,
isCreatingCommunity,
submitBtnLabel,
}: CommunityInformationFormProps) => {
const [communityName, setCommunityName] = useState('');
const [isProcessingProfileImage, setIsProcessingProfileImage] =
useState(false);
Expand All @@ -63,22 +57,15 @@ const BasicInformationForm = ({

const { data: configurationData } = useFetchConfigurationQuery();

const { isAddedToHomeScreen } = useAppStatus();

const { trackAnalytics } = useBrowserAnalyticsTrack<
MixpanelLoginPayload | BaseMixpanelPayload
>({
onAction: true,
});

const {
mutateAsync: createCommunityMutation,
isLoading: createCommunityLoading,
} = useCreateCommunityMutation();

const communityId = slugifyPreserveDashes(communityName.toLowerCase());
const isCommunityNameTaken = !!configurationData?.redirects?.[communityId];

const validation = withChainsConfig
? baseCommunityInformationFormValidationSchema.merge(
communityChainValidation,
)
: baseCommunityInformationFormValidationSchema;

const getChainOptions = () => {
const mappedChainValue = (chainType) => ({
helpText: chainType.hasStakeEnabled ? 'Community Stake' : '',
Expand All @@ -88,13 +75,13 @@ const BasicInformationForm = ({

// Since we are treating polygon as an ecosystem, we will only have a single option, which will be
// preselected and further input's will be disabled
if (selectedCommunity.type === CommunityType.Polygon) {
if (withChainsConfig?.community?.type === CommunityType.Polygon) {
return sortedChains
.filter((chainType) => chainType.value === POLYGON_ETH_CHAIN_ID)
.map(mappedChainValue);
}

if (selectedCommunity.type === CommunityType.Solana) {
if (withChainsConfig?.community?.type === CommunityType.Solana) {
return sortedChains
.filter((chainType) => chainType.chainBase === CommunityType.Solana)
.map(mappedChainValue);
Expand All @@ -104,113 +91,55 @@ const BasicInformationForm = ({
.filter(
(chainType) =>
chainType.chainBase ===
(selectedCommunity.type === CommunityType.Cosmos
(withChainsConfig?.community?.type === CommunityType.Cosmos
? CommunityType.Cosmos
: CommunityType.Ethereum),
)
.map(mappedChainValue);
};

const getInitialValue = () => {
switch (selectedCommunity.type) {
case CommunityType.Base:
return {
chain: getChainOptions()?.find((o) => o.value === BASE_ID),
};
case CommunityType.Ethereum:
return {
chain: getChainOptions()?.find(
(o) => o.value === ETHEREUM_MAINNET_ID,
),
};
case CommunityType.Cosmos:
return {
chain: getChainOptions()?.find((o) => o.value === OSMOSIS_ID),
};
case CommunityType.Blast:
return {
chain: getChainOptions()?.find((o) => o.value === BLAST_ID),
};
case CommunityType.Polygon:
case CommunityType.Solana:
return { chain: getChainOptions()?.[0] };
}
return {
...(initialValues || {}),
...(withChainsConfig && {
chain: (() => {
const options = getChainOptions();

switch (withChainsConfig.community.type) {
case CommunityType.Base:
return options?.find((o) => o.value === BASE_ID);
case CommunityType.Ethereum:
return options?.find((o) => o.value === ETHEREUM_MAINNET_ID);
case CommunityType.Cosmos:
return options?.find((o) => o.value === OSMOSIS_ID);
case CommunityType.Blast:
return options?.find((o) => o.value === BLAST_ID);
case CommunityType.Polygon:
case CommunityType.Solana:
return options?.[0];
}
})(),
}),
};
};

const handleSubmit = async (values: FormSubmitValues) => {
const handleSubmit = async (values: CommunityInformationFormSubmitValues) => {
const hasLinksError = validateSocialLinks();

if (isCommunityNameTaken || hasLinksError) return;
// @ts-expect-error StrictNullChecks
values.links = socialLinks.map((link) => link.value).filter(Boolean);

const selectedChainNode = sortedChains.find(
(chain) => String(chain.value) === values.chain.value,
);

try {
const input = buildCreateCommunityInput({
id: communityId,
name: values.communityName,
chainBase: selectedCommunity.chainBase,
description: values.communityDescription,
iconUrl: values.communityProfileImageURL,
socialLinks: values.links ?? [],
nodeUrl: selectedChainNode!.nodeUrl!,
altWalletUrl: selectedChainNode!.altWalletUrl!,
userAddress: selectedAddress.address,
...(selectedCommunity.chainBase === ChainBase.Ethereum && {
ethChainId: values.chain.value,
}),
...(selectedCommunity.chainBase === ChainBase.CosmosSDK && {
cosmosChainId: values.chain.value,
bech32Prefix: selectedChainNode?.bech32Prefix,
}),
isPWA: isAddedToHomeScreen,
});
await createCommunityMutation(input);
onSubmit(communityId, values.communityName);
} catch (err) {
notifyError(err.message);
}
};

const handleCancel = () => {
trackAnalytics({
event: MixpanelCommunityCreationEvent.CREATE_COMMUNITY_CANCELLED,
isPWA: isAddedToHomeScreen,
});

openConfirmation({
title: 'Are you sure you want to cancel?',
description: 'Your details will not be saved. Cancel create community?',
buttons: [
{
label: 'Yes, cancel',
buttonType: 'destructive',
buttonHeight: 'sm',
onClick: onCancel,
},
{
label: 'No, continue',
buttonType: 'primary',
buttonHeight: 'sm',
},
],
});
};

const handleWatchForm = (values: any) => {
handleSelectedChainId(values?.chain?.value);
await onSubmit({ ...values, communityId }).catch(console.error);
};

return (
<CWForm
validationSchema={basicInformationFormValidationSchema}
validationSchema={validation}
onSubmit={handleSubmit}
className="BasicInformationForm"
className="CommunityInformationForm"
initialValues={getInitialValue()}
onWatch={handleWatchForm}
onWatch={onWatch}
>
{/* Form fields */}
<CWTextInput
Expand All @@ -225,18 +154,20 @@ const BasicInformationForm = ({
}
/>

<CWSelectList
name="chain"
hookToForm
isClearable={false}
label="Select chain"
placeholder="Select chain"
isDisabled={
selectedCommunity.type === CommunityType.Polygon ||
selectedCommunity.type === CommunityType.Solana
}
options={getChainOptions()}
/>
{withChainsConfig && (
mzparacha marked this conversation as resolved.
Show resolved Hide resolved
<CWSelectList
name="chain"
hookToForm
isClearable={false}
label="Select chain"
placeholder="Select chain"
isDisabled={
withChainsConfig.community.type === CommunityType.Polygon ||
withChainsConfig.community.type === CommunityType.Solana
}
options={getChainOptions()}
/>
)}

<CWTextInput
label="Community URL"
Expand Down Expand Up @@ -265,7 +196,7 @@ const BasicInformationForm = ({
enableGenerativeAI
/>

{socialLinksDisplay ? (
{withSocialLinks ? (
mzparacha marked this conversation as resolved.
Show resolved Hide resolved
<>
<section className="header">
<CWText type="h4">
Expand Down Expand Up @@ -297,12 +228,16 @@ const BasicInformationForm = ({
)
}
onBlur={() =>
// @ts-expect-error StrictNullChecks
updateAndValidateSocialLinkAtIndex(socialLink.value, index)
updateAndValidateSocialLinkAtIndex(
socialLink.value || '',
index,
)
}
onFocus={() =>
// @ts-expect-error StrictNullChecks
updateAndValidateSocialLinkAtIndex(socialLink.value, index)
updateAndValidateSocialLinkAtIndex(
socialLink.value || '',
index,
)
}
/>
<CWIconButton
Expand All @@ -320,7 +255,9 @@ const BasicInformationForm = ({
</button>
</section>
</>
) : null}
) : (
<></>
)}

{/* Action buttons */}
<section className="action-buttons">
Expand All @@ -329,17 +266,17 @@ const BasicInformationForm = ({
label="Cancel"
buttonWidth="wide"
buttonType="secondary"
onClick={handleCancel}
onClick={onCancel}
/>
<CWButton
type="submit"
buttonWidth="wide"
label="Launch Community"
disabled={createCommunityLoading || isProcessingProfileImage}
label={submitBtnLabel}
disabled={isCreatingCommunity || isProcessingProfileImage}
/>
</section>
</CWForm>
);
};

export default BasicInformationForm;
export default CommunityInformationForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { SelectedCommunity } from 'views/components/component_kit/new_designs/CWCommunitySelector';

export type SocialLinkField = {
value?: string;
error?: string;
};

export type CommunityInformationFormSubmitValues = {
communityName: string;
communityDescription: string;
communityProfileImageURL: string;
chain?: {
label: string;
value: string;
};
links?: string[];
};

export type CommunityInformationFormProps = {
onSubmit: (
values: CommunityInformationFormSubmitValues & { communityId: string },
) => Promise<void>;
onCancel: () => void;
onWatch?: (values: CommunityInformationFormSubmitValues) => void;
withChainsConfig?: {
community: SelectedCommunity;
};
withSocialLinks?: boolean;
isCreatingCommunity: boolean;
initialValues?: CommunityInformationFormSubmitValues;
submitBtnLabel: string;
};
Loading
Loading