+ {withRadioButton && (
+
+
+
+ )}
diff --git a/packages/commonwealth/client/scripts/views/components/component_kit/new_designs/cw_radio_button.tsx b/packages/commonwealth/client/scripts/views/components/component_kit/new_designs/cw_radio_button.tsx
index e3a56e4070c..ec1f9f96205 100644
--- a/packages/commonwealth/client/scripts/views/components/component_kit/new_designs/cw_radio_button.tsx
+++ b/packages/commonwealth/client/scripts/views/components/component_kit/new_designs/cw_radio_button.tsx
@@ -20,9 +20,10 @@ type RadioButtoFormValidationProps = {
hookToForm?: boolean;
};
-type RadioButtonProps = {
+export type RadioButtonProps = {
groupName?: string;
onChange?: (e?: any) => void;
+ hideLabels?: boolean;
} & Omit
&
RadioButtonStyleProps &
RadioButtoFormValidationProps;
@@ -37,6 +38,7 @@ export const CWRadioButton = (props: RadioButtonProps) => {
onChange,
checked,
value,
+ hideLabels,
} = props;
const formContext = useFormContext();
@@ -64,9 +66,11 @@ export const CWRadioButton = (props: RadioButtonProps) => {
await onChange?.(e);
}}
/>
-
- {label || value}
-
+ {!hideLabels && (
+
+ {label || value}
+
+ )}
);
};
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.scss b/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.scss
new file mode 100644
index 00000000000..855ba4271cf
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.scss
@@ -0,0 +1,7 @@
+@import '../../../../styles/shared';
+
+.LaunchToken {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.tsx b/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.tsx
new file mode 100644
index 00000000000..7c679eafd0b
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.tsx
@@ -0,0 +1,60 @@
+import { useCommonNavigate } from 'navigation/helpers';
+import React from 'react';
+import CWFormSteps from 'views/components/component_kit/new_designs/CWFormSteps';
+import CWPageLayout from 'views/components/component_kit/new_designs/CWPageLayout';
+import { MixpanelCommunityCreationEvent } from '../../../../../shared/analytics/types';
+import useAppStatus from '../../../hooks/useAppStatus';
+import { useBrowserAnalyticsTrack } from '../../../hooks/useBrowserAnalyticsTrack';
+import './LaunchToken.scss';
+import TokenInformationStep from './steps/TokenInformationStep';
+import useCreateCommunity from './useCreateCommunity';
+import { CreateTokenCommunityStep, getFormSteps } from './utils';
+
+const LaunchToken = () => {
+ const navigate = useCommonNavigate();
+ const { createTokenCommunityStep, onChangeStep } = useCreateCommunity();
+
+ const { isAddedToHomeScreen } = useAppStatus();
+
+ useBrowserAnalyticsTrack({
+ payload: {
+ event: MixpanelCommunityCreationEvent.CREATE_TOKEN_COMMUNITY_VISITED,
+ isPWA: isAddedToHomeScreen,
+ },
+ });
+
+ const isSuccessStep =
+ createTokenCommunityStep === CreateTokenCommunityStep.Success;
+
+ const getCurrentStep = () => {
+ switch (createTokenCommunityStep) {
+ case CreateTokenCommunityStep.TokenInformation:
+ return (
+ navigate('/')} // redirect to home
+ handleContinue={() => onChangeStep(true)}
+ />
+ );
+ case CreateTokenCommunityStep.CommunityInformation:
+ // TODO: https://github.com/hicommonwealth/commonwealth/issues/8706
+ return <>Not Implemented>;
+ case CreateTokenCommunityStep.SignatureLaunch:
+ // TODO: https://github.com/hicommonwealth/commonwealth/issues/8707
+ return <>Not Implemented>;
+ }
+ };
+
+ return (
+
+
+ {!isSuccessStep && (
+
+ )}
+
+ {getCurrentStep()}
+
+
+ );
+};
+
+export default LaunchToken;
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/index.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/index.ts
new file mode 100644
index 00000000000..41918969a59
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/index.ts
@@ -0,0 +1,3 @@
+import LaunchToken from './LaunchToken';
+
+export default LaunchToken;
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/TokenInformationForm.scss b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/TokenInformationForm.scss
new file mode 100644
index 00000000000..06f321bad4d
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/TokenInformationForm.scss
@@ -0,0 +1,30 @@
+@import '../../../../../../../styles/shared.scss';
+
+.TokenInformationForm {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ width: 100%;
+ max-width: 596px;
+
+ .optional-label {
+ font-family: $font-family-silka;
+ color: $neutral-500;
+ margin-left: 6px;
+ margin-top: auto;
+ }
+
+ .action-buttons {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ margin-top: 24px;
+ margin-bottom: 24px;
+ gap: 10px;
+
+ @include smallInclusive {
+ justify-content: flex-end;
+ }
+ }
+}
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/TokenInformationForm.tsx b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/TokenInformationForm.tsx
new file mode 100644
index 00000000000..719b5927d58
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/TokenInformationForm.tsx
@@ -0,0 +1,154 @@
+import useAppStatus from 'hooks/useAppStatus';
+import { useBrowserAnalyticsTrack } from 'hooks/useBrowserAnalyticsTrack';
+import React, { useState } from 'react';
+import {
+ BaseMixpanelPayload,
+ MixpanelCommunityCreationEvent,
+ MixpanelLoginPayload,
+} from 'shared/analytics/types';
+import {
+ CWCoverImageUploader,
+ ImageBehavior,
+} from 'views/components/component_kit/cw_cover_image_uploader';
+import { CWLabel } from 'views/components/component_kit/cw_label';
+import { CWTextArea } from 'views/components/component_kit/cw_text_area';
+import { CWButton } from 'views/components/component_kit/new_designs/CWButton';
+import CWCommunitySelector from 'views/components/component_kit/new_designs/CWCommunitySelector';
+import { CWForm } from 'views/components/component_kit/new_designs/CWForm';
+import { CWTextInput } from 'views/components/component_kit/new_designs/CWTextInput';
+import { openConfirmation } from 'views/modals/confirmation_modal';
+import { communityTypeOptions } from 'views/pages/CreateCommunity/steps/CommunityTypeStep/helpers';
+import './TokenInformationForm.scss';
+import { FormSubmitValues, TokenInformationFormProps } from './types';
+import { tokenInformationFormValidationSchema } from './validation';
+
+const TokenInformationForm = ({
+ onSubmit,
+ onCancel,
+}: TokenInformationFormProps) => {
+ const [isProcessingProfileImage, setIsProcessingProfileImage] =
+ useState(false);
+
+ const { isAddedToHomeScreen } = useAppStatus();
+
+ const [baseOption] = communityTypeOptions;
+
+ const { trackAnalytics } = useBrowserAnalyticsTrack<
+ MixpanelLoginPayload | BaseMixpanelPayload
+ >({
+ onAction: true,
+ });
+
+ const handleSubmit = (values: FormSubmitValues) => {
+ // TODO 8705: call token launch endpoint
+ console.log('values => ', values);
+ onSubmit();
+ };
+
+ const handleCancel = () => {
+ openConfirmation({
+ title: 'Are you sure you want to cancel?',
+ description: 'Your details will not be saved. Cancel create token flow?',
+ buttons: [
+ {
+ label: 'Yes, cancel',
+ buttonType: 'destructive',
+ buttonHeight: 'sm',
+ onClick: () => {
+ trackAnalytics({
+ event:
+ MixpanelCommunityCreationEvent.CREATE_TOKEN_COMMUNITY_CANCELLED,
+ isPWA: isAddedToHomeScreen,
+ });
+
+ onCancel();
+ },
+ },
+ {
+ label: 'No, continue',
+ buttonType: 'primary',
+ buttonHeight: 'sm',
+ },
+ ],
+ });
+ };
+
+ return (
+
+
+
+ {}}
+ withRadioButton={{
+ value: baseOption.chainBase,
+ checked: true,
+ hideLabels: true,
+ hookToForm: true,
+ name: 'tokenChain',
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+ {/* Action buttons */}
+
+
+ );
+};
+
+export default TokenInformationForm;
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/types.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/types.ts
new file mode 100644
index 00000000000..ec00d841d58
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/types.ts
@@ -0,0 +1,12 @@
+export type FormSubmitValues = {
+ tokenChain: string;
+ tokenName: string;
+ tokenTicker: string;
+ tokenDescription: string;
+ tokenImageURL: string;
+};
+
+export type TokenInformationFormProps = {
+ onSubmit: () => void;
+ onCancel: () => void;
+};
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/validation.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/validation.ts
new file mode 100644
index 00000000000..7e33f7d9a04
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/validation.ts
@@ -0,0 +1,25 @@
+import { VALIDATION_MESSAGES } from 'helpers/formValidations/messages';
+import z from 'zod';
+
+export const tokenInformationFormValidationSchema = z.object({
+ tokenChain: z
+ .string({ invalid_type_error: VALIDATION_MESSAGES.NO_INPUT })
+ .nonempty({ message: VALIDATION_MESSAGES.NO_INPUT })
+ .max(100, { message: VALIDATION_MESSAGES.MAX_CHAR_LIMIT_REACHED }),
+ tokenName: z
+ .string({ invalid_type_error: VALIDATION_MESSAGES.NO_INPUT })
+ .nonempty({ message: VALIDATION_MESSAGES.NO_INPUT })
+ .max(100, { message: VALIDATION_MESSAGES.MAX_CHAR_LIMIT_REACHED }),
+ tokenTicker: z
+ .string({ invalid_type_error: VALIDATION_MESSAGES.NO_INPUT })
+ .nonempty({ message: VALIDATION_MESSAGES.NO_INPUT })
+ .min(3, { message: VALIDATION_MESSAGES.MIN_CHAR_LIMIT_REQUIRED(3) })
+ .max(6, { message: VALIDATION_MESSAGES.MAX_CHAR_LIMIT_REACHED }),
+ tokenDescription: z
+ .string({ invalid_type_error: VALIDATION_MESSAGES.NO_INPUT })
+ .max(180, { message: VALIDATION_MESSAGES.MAX_CHAR_LIMIT_REACHED })
+ .optional(),
+ tokenImageURL: z
+ .string({ invalid_type_error: VALIDATION_MESSAGES.NO_INPUT })
+ .optional(),
+});
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationStep.scss b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationStep.scss
new file mode 100644
index 00000000000..a99ac7e490f
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationStep.scss
@@ -0,0 +1,24 @@
+@import '../../../../../../styles/shared';
+
+$form-width: 596px;
+
+.TokenInformationStep {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ position: relative;
+
+ .header {
+ margin-top: 8px;
+ width: 100%;
+
+ .description {
+ color: $neutral-500;
+ }
+ }
+
+ @include smallInclusive {
+ flex-direction: column;
+ gap: 16px;
+ }
+}
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationStep.tsx b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationStep.tsx
new file mode 100644
index 00000000000..cdb59beeb14
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationStep.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+
+import { CWText } from 'views/components/component_kit/cw_text';
+import TokenInformationForm from './TokenInformationForm/TokenInformationForm';
+
+import './TokenInformationStep.scss';
+
+interface TokenInformationStepProps {
+ handleGoBack: () => void;
+ handleContinue: () => void;
+}
+
+const TokenInformationStep = ({
+ handleGoBack,
+ handleContinue,
+}: TokenInformationStepProps) => {
+ return (
+
+
+ Launch Token
+
+ Something about launching a token
+
+
+
+
+
+ );
+};
+
+export default TokenInformationStep;
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/index.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/index.ts
new file mode 100644
index 00000000000..ead76717620
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/index.ts
@@ -0,0 +1,3 @@
+import TokenInformationStep from './TokenInformationStep';
+
+export default TokenInformationStep;
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/useCreateCommunity.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/useCreateCommunity.ts
new file mode 100644
index 00000000000..3f052f77e01
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/useCreateCommunity.ts
@@ -0,0 +1,24 @@
+import { useState } from 'react';
+import { CreateTokenCommunityStep, handleChangeStep } from './utils';
+
+const useCreateCommunity = () => {
+ const [createTokenCommunityStep, setCreateTokenCommunityStep] =
+ useState(
+ CreateTokenCommunityStep.TokenInformation,
+ );
+
+ const onChangeStep = (forward: boolean) => {
+ handleChangeStep(
+ forward,
+ createTokenCommunityStep,
+ setCreateTokenCommunityStep,
+ );
+ };
+
+ return {
+ createTokenCommunityStep,
+ onChangeStep,
+ };
+};
+
+export default useCreateCommunity;
diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/utils.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/utils.ts
new file mode 100644
index 00000000000..8cc705fc89d
--- /dev/null
+++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/utils.ts
@@ -0,0 +1,67 @@
+import React from 'react';
+import { CWFormStepsProps } from 'views/components/component_kit/new_designs/CWFormSteps/CWFormSteps';
+
+export enum CreateTokenCommunityStep {
+ TokenInformation = 'TokenInformation',
+ CommunityInformation = 'CommunityInformation',
+ SignatureLaunch = 'SignatureLaunch',
+ Success = 'Success',
+}
+
+export const getFormSteps = (
+ activeStep: CreateTokenCommunityStep,
+): CWFormStepsProps['steps'] => {
+ return [
+ {
+ label: 'Token Details',
+ state:
+ activeStep === CreateTokenCommunityStep.TokenInformation
+ ? 'active'
+ : 'completed',
+ },
+ {
+ label: 'Community',
+ state:
+ activeStep < CreateTokenCommunityStep.CommunityInformation
+ ? 'inactive'
+ : activeStep === CreateTokenCommunityStep.CommunityInformation
+ ? 'active'
+ : 'completed',
+ },
+ {
+ label: 'Sign and Launch',
+ state:
+ activeStep < CreateTokenCommunityStep.SignatureLaunch
+ ? 'inactive'
+ : activeStep === CreateTokenCommunityStep.SignatureLaunch
+ ? 'active'
+ : 'completed',
+ },
+ ];
+};
+
+export const handleChangeStep = (
+ forward: boolean,
+ activeStep: CreateTokenCommunityStep,
+ setActiveStep: React.Dispatch>,
+) => {
+ switch (activeStep) {
+ case CreateTokenCommunityStep.TokenInformation:
+ setActiveStep(CreateTokenCommunityStep.CommunityInformation);
+ return;
+ case CreateTokenCommunityStep.CommunityInformation:
+ setActiveStep(
+ forward
+ ? CreateTokenCommunityStep.SignatureLaunch
+ : CreateTokenCommunityStep.TokenInformation,
+ );
+ return;
+ case CreateTokenCommunityStep.SignatureLaunch:
+ setActiveStep(
+ forward
+ ? CreateTokenCommunityStep.Success
+ : CreateTokenCommunityStep.CommunityInformation,
+ );
+ return;
+ }
+};
diff --git a/packages/commonwealth/client/styles/components/component_kit/cw_text_area.scss b/packages/commonwealth/client/styles/components/component_kit/cw_text_area.scss
index 664351d3239..28f39d6ac76 100644
--- a/packages/commonwealth/client/styles/components/component_kit/cw_text_area.scss
+++ b/packages/commonwealth/client/styles/components/component_kit/cw_text_area.scss
@@ -17,6 +17,6 @@
}
.character-count {
- margin-top: 12px;
+ margin-top: 4px;
}
}
diff --git a/packages/commonwealth/client/vite.config.ts b/packages/commonwealth/client/vite.config.ts
index 772e09ba80e..3dc91832724 100644
--- a/packages/commonwealth/client/vite.config.ts
+++ b/packages/commonwealth/client/vite.config.ts
@@ -41,6 +41,9 @@ export default defineConfig(({ mode }) => {
'process.env.FLAG_FARCASTER_CONTEST': JSON.stringify(
env.FLAG_FARCASTER_CONTEST,
),
+ 'process.env.FLAG_TOKENIZED_COMMUNITY': JSON.stringify(
+ env.FLAG_TOKENIZED_COMMUNITY,
+ ),
};
const config = {
diff --git a/packages/commonwealth/shared/analytics/types.ts b/packages/commonwealth/shared/analytics/types.ts
index 0145dc8f2d7..e8b9d70230d 100644
--- a/packages/commonwealth/shared/analytics/types.ts
+++ b/packages/commonwealth/shared/analytics/types.ts
@@ -65,6 +65,8 @@ export const enum MixpanelCommunityCreationEvent {
CONNECT_NEW_WALLET_PRESSED = 'Connect New Wallet Button Pressed',
NEW_COMMUNITY_CREATION = 'New Community Creation',
CREATE_COMMUNITY_CANCELLED = 'Create Community Cancel Button Pressed',
+ CREATE_TOKEN_COMMUNITY_VISITED = '/createTokenCommunity Page Visited',
+ CREATE_TOKEN_COMMUNITY_CANCELLED = 'Create Token Community Cancel Button Pressed',
}
export const enum MixpanelSnapshotEvents {