diff --git a/libs/model/src/config.ts b/libs/model/src/config.ts index a2b64f89d77..d8ab5f4f578 100644 --- a/libs/model/src/config.ts +++ b/libs/model/src/config.ts @@ -33,6 +33,7 @@ const { ALCHEMY_PUBLIC_APP_KEY, MEMBERSHIP_REFRESH_BATCH_SIZE, MEMBERSHIP_REFRESH_TTL_SECONDS, + NEYNAR_BOT_UUID, NEYNAR_API_KEY, NEYNAR_CAST_CREATED_WEBHOOK_SECRET, NEYNAR_REPLY_WEBHOOK_URL, @@ -91,6 +92,7 @@ export const config = configure( : 5, FLAG_FARCASTER_CONTEST: FLAG_FARCASTER_CONTEST === 'true', NEYNAR_API_KEY: NEYNAR_API_KEY, + NEYNAR_BOT_UUID: NEYNAR_BOT_UUID, NEYNAR_CAST_CREATED_WEBHOOK_SECRET: NEYNAR_CAST_CREATED_WEBHOOK_SECRET, NEYNAR_REPLY_WEBHOOK_URL: NEYNAR_REPLY_WEBHOOK_URL, FARCASTER_ACTION_URL: FARCASTER_ACTION_URL, @@ -195,10 +197,41 @@ export const config = configure( MIN_USER_ETH: z.number(), MAX_USER_POSTS_PER_CONTEST: z.number().int(), FLAG_FARCASTER_CONTEST: z.boolean().nullish(), - NEYNAR_API_KEY: z.string().nullish(), - NEYNAR_CAST_CREATED_WEBHOOK_SECRET: z.string().nullish(), - NEYNAR_REPLY_WEBHOOK_URL: z.string().nullish(), - FARCASTER_ACTION_URL: z.string().nullish(), + NEYNAR_BOT_UUID: z + .string() + .optional() + .refine( + (data) => !(target.APP_ENV === 'production' && !data), + 'NEYNAR_BOT_UUID must be set to a non-default value in production.', + ), + NEYNAR_API_KEY: z + .string() + .optional() + .refine( + (data) => !(target.APP_ENV === 'production' && !data), + 'NEYNAR_API_KEY must be set to a non-default value in production.', + ), + NEYNAR_CAST_CREATED_WEBHOOK_SECRET: z + .string() + .optional() + .refine( + (data) => !(target.APP_ENV === 'production' && !data), + 'NEYNAR_CAST_CREATED_WEBHOOK_SECRET must be set to a non-default value in production.', + ), + NEYNAR_REPLY_WEBHOOK_URL: z + .string() + .optional() + .refine( + (data) => !(target.APP_ENV === 'production' && !data), + 'NEYNAR_REPLY_WEBHOOK_URL must be set to a non-default value in production.', + ), + FARCASTER_ACTION_URL: z + .string() + .optional() + .refine( + (data) => !(target.APP_ENV === 'production' && !data), + 'FARCASTER_ACTION_URL must be set to a non-default value in production.', + ), }), AUTH: z .object({ diff --git a/libs/model/src/contest/Contests.projection.ts b/libs/model/src/contest/Contests.projection.ts index dbe604f14ed..cb5bb70a767 100644 --- a/libs/model/src/contest/Contests.projection.ts +++ b/libs/model/src/contest/Contests.projection.ts @@ -5,7 +5,9 @@ import { EvmEventSignatures, commonProtocol as cp, } from '@hicommonwealth/evm-protocols'; +import { config } from '@hicommonwealth/model'; import { ContestScore, events } from '@hicommonwealth/schemas'; +import { buildContestLeaderboardUrl, getBaseUrl } from '@hicommonwealth/shared'; import { QueryTypes } from 'sequelize'; import { z } from 'zod'; import { models } from '../database'; @@ -17,6 +19,8 @@ import { decodeThreadContentUrl, getChainNodeUrl, getDefaultContestImage, + parseFarcasterContentUrl, + publishCast, } from '../utils'; const log = logger(import.meta); @@ -295,7 +299,9 @@ export function Contests(): Projection { }, ContestContentAdded: async ({ payload }) => { - const { threadId } = decodeThreadContentUrl(payload.content_url); + const { threadId, isFarcaster } = decodeThreadContentUrl( + payload.content_url, + ); await models.ContestAction.create({ ...payload, contest_id: payload.contest_id || 0, @@ -306,6 +312,26 @@ export function Contests(): Projection { voting_power: '0', created_at: new Date(), }); + + // post confirmation via FC bot + if (isFarcaster) { + const contestManager = await models.ContestManager.findByPk( + payload.contest_address, + ); + const leaderboardUrl = buildContestLeaderboardUrl( + getBaseUrl(config.APP_ENV), + contestManager!.community_id, + contestManager!.contest_address, + ); + const { replyCastHash } = parseFarcasterContentUrl( + payload.content_url, + ); + await publishCast( + replyCastHash, + ({ username }) => + `Hey @${username}, your entry has been submitted to the contest: ${leaderboardUrl}`, + ); + } }, ContestContentUpvoted: async ({ payload }) => { diff --git a/libs/model/src/contest/FarcasterReplyCastCreatedWebhook.command.ts b/libs/model/src/contest/FarcasterReplyCastCreatedWebhook.command.ts index bbe43691238..006a8657bc7 100644 --- a/libs/model/src/contest/FarcasterReplyCastCreatedWebhook.command.ts +++ b/libs/model/src/contest/FarcasterReplyCastCreatedWebhook.command.ts @@ -4,7 +4,7 @@ import { NeynarAPIClient } from '@neynar/nodejs-sdk'; import { config } from '../config'; import { models } from '../database'; import { mustExist } from '../middleware/guards'; -import { emitEvent } from '../utils'; +import { emitEvent, publishCast } from '../utils'; const log = logger(import.meta); @@ -21,6 +21,7 @@ export function FarcasterReplyCastCreatedWebhook(): Command< mustExist('Farcaster Cast Author FID', payload.data.author?.fid); const client = new NeynarAPIClient(config.CONTESTS.NEYNAR_API_KEY!); + // get user verified address const { users } = await client.fetchBulkUsers([payload.data.author.fid]); const verified_address = users[0].verified_addresses.eth_addresses.at(0); @@ -28,6 +29,11 @@ export function FarcasterReplyCastCreatedWebhook(): Command< log.warn( 'Farcaster verified address not found for reply cast created event- content will be ignored.', ); + await publishCast( + payload.data.hash, + ({ username }) => + `Hey @${username}, you need a verified address to participate in the contest.`, + ); return; } diff --git a/libs/model/src/services/openai/generateTokenIdea.ts b/libs/model/src/services/openai/generateTokenIdea.ts index 64300819647..5da2d146440 100644 --- a/libs/model/src/services/openai/generateTokenIdea.ts +++ b/libs/model/src/services/openai/generateTokenIdea.ts @@ -134,9 +134,8 @@ const generateTokenIdea = async function* ({ // generate image url and send the generated url to the client (to save time on s3 upload) const imageResponse = await openai.images.generate({ - model: 'dall-e-3', prompt: TOKEN_AI_PROMPTS_CONFIG.image(tokenIdea.name, tokenIdea.symbol), - size: '512x512', + size: '256x256', n: 1, response_format: 'url', }); diff --git a/libs/model/src/user/GetUserProfile.query.ts b/libs/model/src/user/GetUserProfile.query.ts index 7552335cb4a..4a4a27003ef 100644 --- a/libs/model/src/user/GetUserProfile.query.ts +++ b/libs/model/src/user/GetUserProfile.query.ts @@ -3,20 +3,23 @@ import * as schemas from '@hicommonwealth/schemas'; import { Op } from 'sequelize'; import { z } from 'zod'; import { models } from '../database'; +import { mustExist } from '../middleware/guards'; export function GetUserProfile(): Query { return { ...schemas.GetUserProfile, auth: [], - secure: true, + secure: false, body: async ({ actor, payload }) => { - const user_id = payload.userId ?? actor.user.id; + const user_id = payload.userId ?? actor.user?.id; const user = await models.User.findOne({ where: { id: user_id }, attributes: ['profile', 'xp_points', 'referral_link'], }); + mustExist('User', user); + const addresses = await models.Address.findAll({ where: { user_id }, include: [ @@ -98,7 +101,7 @@ export function GetUserProfile(): Query { commentThreads: commentThreads.map( (c) => c.toJSON() as z.infer, ), - isOwner: actor.user.id === user_id, + isOwner: actor.user?.id === user_id, // ensure Tag is present in typed response tags: profileTags.map((t) => ({ id: t.Tag!.id!, name: t.Tag!.name })), xp_points: user!.xp_points ?? 0, diff --git a/libs/model/src/utils/buildFarcasterContentUrl.ts b/libs/model/src/utils/buildFarcasterContentUrl.ts deleted file mode 100644 index 143f9f81deb..00000000000 --- a/libs/model/src/utils/buildFarcasterContentUrl.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function buildFarcasterContentUrl( - parentCastHash: string, - replyCashHash: string, -) { - return `/farcaster/${parentCastHash}/${replyCashHash}`; -} diff --git a/libs/model/src/utils/farcasterUtils.ts b/libs/model/src/utils/farcasterUtils.ts new file mode 100644 index 00000000000..6c8704d231f --- /dev/null +++ b/libs/model/src/utils/farcasterUtils.ts @@ -0,0 +1,14 @@ +export function buildFarcasterContentUrl( + parentCastHash: string, + replyCashHash: string, +) { + return `/farcaster/${parentCastHash}/${replyCashHash}`; +} + +export function parseFarcasterContentUrl(url: string) { + const [, , parentCastHash, replyCastHash] = url.split('/'); + return { + parentCastHash, + replyCastHash, + }; +} diff --git a/libs/model/src/utils/index.ts b/libs/model/src/utils/index.ts index 2adea1cc9cb..12861f43c7c 100644 --- a/libs/model/src/utils/index.ts +++ b/libs/model/src/utils/index.ts @@ -1,8 +1,8 @@ -export * from './buildFarcasterContentUrl'; export * from './buildFarcasterWebhookName'; export * from './decodeContent'; export * from './defaultAvatar'; export * from './denormalizedCountUtils'; +export * from './farcasterUtils'; export * from './getDefaultContestImage'; export * from './getDelta'; export * from './makeGetBalancesOptions'; diff --git a/libs/model/src/utils/utils.ts b/libs/model/src/utils/utils.ts index f79fe8c8e3e..5ecff95c905 100644 --- a/libs/model/src/utils/utils.ts +++ b/libs/model/src/utils/utils.ts @@ -5,6 +5,7 @@ import { safeTruncateBody, type AbiType, } from '@hicommonwealth/shared'; +import { NeynarAPIClient } from '@neynar/nodejs-sdk'; import { createHash } from 'crypto'; import { hasher } from 'node-object-hash'; import { @@ -79,11 +80,13 @@ export function buildThreadContentUrl(communityId: string, threadId: number) { export function decodeThreadContentUrl(contentUrl: string): { communityId: string | null; threadId: number | null; + isFarcaster: boolean; } { if (contentUrl.startsWith('/farcaster/')) { return { communityId: null, threadId: null, + isFarcaster: true, }; } if (!contentUrl.includes('/discussion/')) { @@ -95,6 +98,7 @@ export function decodeThreadContentUrl(contentUrl: string): { return { communityId, threadId: parseInt(threadId, 10), + isFarcaster: false, }; } @@ -256,3 +260,26 @@ export function getSaltedApiKeyHash(apiKey: string, salt: string): string { export function buildApiKeySaltCacheKey(address: string) { return `salt_${address.toLowerCase()}`; } + +export async function publishCast( + replyCastHash: string, + messageBuilder: ({ username }: { username: string }) => string, +) { + const client = new NeynarAPIClient(config.CONTESTS.NEYNAR_API_KEY!); + try { + const { + result: { casts }, + } = await client.fetchBulkCasts([replyCastHash]); + const username = casts[0].author.username!; + await client.publishCast( + config.CONTESTS.NEYNAR_BOT_UUID!, + messageBuilder({ username }), + { + replyTo: replyCastHash, + }, + ); + log.info(`FC bot published reply to ${replyCastHash}`); + } catch (err) { + log.error(`Failed to post as FC bot`, err as Error); + } +} diff --git a/libs/shared/src/utils.ts b/libs/shared/src/utils.ts index 12e5493c9ed..4bac85176e2 100644 --- a/libs/shared/src/utils.ts +++ b/libs/shared/src/utils.ts @@ -426,3 +426,31 @@ export async function alchemyGetTokenPrices({ cause: { status: res.status, statusText: res.statusText }, }); } + +export const getBaseUrl = ( + env: 'local' | 'CI' | 'frick' | 'frack' | 'beta' | 'demo' | 'production', +) => { + switch (env) { + case 'local': + case 'CI': + return 'http://localhost:8080'; + case 'beta': + return 'https://qa.commonwealth.im'; + case 'demo': + return 'https://demo.commonwealth.im'; + case 'frick': + return 'https://frick.commonwealth.im'; + case 'frack': + return 'https://frack.commonwealth.im'; + default: + return `https://${PRODUCTION_DOMAIN}`; + } +}; + +export const buildContestLeaderboardUrl = ( + baseUrl: string, + communityId: string, + contestAddress: string, +) => { + return `${baseUrl}/${communityId}/contests/${contestAddress}`; +}; diff --git a/packages/commonwealth/client/assets/img/communitySelector/skale.svg b/packages/commonwealth/client/assets/img/communitySelector/skale.svg new file mode 100644 index 00000000000..4dcfcd4c0c5 --- /dev/null +++ b/packages/commonwealth/client/assets/img/communitySelector/skale.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/commonwealth/client/scripts/hooks/useHandleInviteLink.ts b/packages/commonwealth/client/scripts/hooks/useHandleInviteLink.ts index 87b656fbfdb..fe1063ebc63 100644 --- a/packages/commonwealth/client/scripts/hooks/useHandleInviteLink.ts +++ b/packages/commonwealth/client/scripts/hooks/useHandleInviteLink.ts @@ -25,13 +25,17 @@ export const useHandleInviteLink = ({ const activeChainId = app.activeChainId(); const generalInviteRoute = matchRoutes( - [{ path: '/dashboard/global' }], + [{ path: '/dashboard/global' }, { path: '/profile/id/*' }], location, ); const communityInviteRoute = matchRoutes( - [{ path: '/:scope' }, { path: '/:scope/discussions/*' }], + [ + { path: '/:scope' }, + { path: '/:scope/discussions/*' }, + { path: '/:scope/discussion/*' }, + ], location, ) && isInsideCommunity; diff --git a/packages/commonwealth/client/scripts/state/api/user/getReferralLink.ts b/packages/commonwealth/client/scripts/state/api/user/getReferralLink.ts index 5aeca2bfbc8..3eab91ff227 100644 --- a/packages/commonwealth/client/scripts/state/api/user/getReferralLink.ts +++ b/packages/commonwealth/client/scripts/state/api/user/getReferralLink.ts @@ -1,5 +1,17 @@ +import { useFlag } from 'hooks/useFlag'; import { trpc } from 'utils/trpcClient'; +import useUserStore from '../../ui/user'; export const useGetReferralLinkQuery = () => { - return trpc.user.getReferralLink.useQuery({}); + const user = useUserStore(); + const referralsEnabled = useFlag('referrals'); + + return trpc.user.getReferralLink.useQuery( + {}, + { + enabled: user?.isLoggedIn && referralsEnabled, + staleTime: Infinity, + cacheTime: Infinity, + }, + ); }; diff --git a/packages/commonwealth/client/scripts/views/components/CommunityInformationForm/CommunityInformationForm.tsx b/packages/commonwealth/client/scripts/views/components/CommunityInformationForm/CommunityInformationForm.tsx index bcc0a4b20d3..45c9e64906d 100644 --- a/packages/commonwealth/client/scripts/views/components/CommunityInformationForm/CommunityInformationForm.tsx +++ b/packages/commonwealth/client/scripts/views/components/CommunityInformationForm/CommunityInformationForm.tsx @@ -21,6 +21,7 @@ import { ETHEREUM_MAINNET_ID, OSMOSIS_ID, POLYGON_ETH_CHAIN_ID, + SKALE_ID, alphabeticallyStakeWiseSortedChains as sortedChains, } from './constants'; import { @@ -116,6 +117,8 @@ const CommunityInformationForm = ({ return options?.find((o) => o.value === OSMOSIS_ID); case CommunityType.Blast: return options?.find((o) => o.value === BLAST_ID); + case CommunityType.Skale: + return options?.find((o) => o.value === SKALE_ID); case CommunityType.Polygon: case CommunityType.Solana: return options?.[0]; diff --git a/packages/commonwealth/client/scripts/views/components/CommunityInformationForm/constants.ts b/packages/commonwealth/client/scripts/views/components/CommunityInformationForm/constants.ts index c298df1d805..bf0dc40dee5 100644 --- a/packages/commonwealth/client/scripts/views/components/CommunityInformationForm/constants.ts +++ b/packages/commonwealth/client/scripts/views/components/CommunityInformationForm/constants.ts @@ -9,6 +9,7 @@ export const ETHEREUM_MAINNET_ID = '1'; export const BASE_ID = '8453'; export const OSMOSIS_ID = 'osmosis'; export const BLAST_ID = '81457'; +export const SKALE_ID = '974399131'; const removeTestCosmosNodes = (nodeInfo: NodeInfo): boolean => { return !( diff --git a/packages/commonwealth/client/scripts/views/components/PageCounter/PageCounter.tsx b/packages/commonwealth/client/scripts/views/components/PageCounter/PageCounter.tsx index e38bc790cad..252896330dd 100644 --- a/packages/commonwealth/client/scripts/views/components/PageCounter/PageCounter.tsx +++ b/packages/commonwealth/client/scripts/views/components/PageCounter/PageCounter.tsx @@ -1,3 +1,4 @@ +import clsx from 'clsx'; import React from 'react'; import { CWText } from '../component_kit/cw_text'; import CWIconButton from '../component_kit/new_designs/CWIconButton'; @@ -8,6 +9,7 @@ type PageCounterProps = { activePage?: number; onPageChange?: (pageNumber: number) => void; disabled?: boolean; + className?: string; }; const PageCounter = ({ @@ -15,9 +17,10 @@ const PageCounter = ({ totalPages, onPageChange, disabled, + className, }: PageCounterProps) => { return ( -
+
{totalPages > 1 && ( { + const referralsEnabled = useFlag('referrals'); + + const { getReferralLink } = useReferralLink(); + const defaultRenderTrigger = ( onClick: (e: React.MouseEvent) => void, ) => ( @@ -30,6 +36,17 @@ export const SharePopover = ({ /> ); + const handleCopy = async () => { + if (referralsEnabled) { + const referralLink = await getReferralLink(); + const refLink = + linkToShare + (referralLink ? `?refcode=${referralLink}` : ''); + await saveToClipboard(refLink, true); + } else { + await saveToClipboard(linkToShare, true); + } + }; + return ( { - navigator.clipboard - .writeText(linkToShare) - .then(() => { - notifySuccess('Successfully copied! '); - }) - .catch(console.error); + handleCopy().catch(console.error); }, }, { diff --git a/packages/commonwealth/client/scripts/views/components/SublayoutHeader/DesktopHeader/DesktopHeader.tsx b/packages/commonwealth/client/scripts/views/components/SublayoutHeader/DesktopHeader/DesktopHeader.tsx index b41c0c9caa1..8751e07d37b 100644 --- a/packages/commonwealth/client/scripts/views/components/SublayoutHeader/DesktopHeader/DesktopHeader.tsx +++ b/packages/commonwealth/client/scripts/views/components/SublayoutHeader/DesktopHeader/DesktopHeader.tsx @@ -7,6 +7,7 @@ import KnockNotifications from 'views/components/KnockNotifications'; import { CWDivider } from 'views/components/component_kit/cw_divider'; import { CWIconButton } from 'views/components/component_kit/cw_icon_button'; import { isWindowSmallInclusive } from 'views/components/component_kit/helpers'; +import { CWButton } from 'views/components/component_kit/new_designs/CWButton'; import { CWSearchBar } from 'views/components/component_kit/new_designs/CWSearchBar'; import { CWTooltip } from 'views/components/component_kit/new_designs/CWTooltip'; import { CreateContentPopover } from 'views/menus/CreateContentMenu'; @@ -87,6 +88,24 @@ const DesktopHeader = ({ onMobile, onAuthModalOpen }: DesktopHeaderProps) => { })} > + {!isWindowSmallInclusive(window.innerWidth) && ( + ( + + window.open('https://landing.common.xyz', '_blank') + } + onMouseEnter={handleInteraction} + onMouseLeave={handleInteraction} + /> + )} + /> + )} - +
{JoinCommunityModals} diff --git a/packages/commonwealth/client/scripts/views/components/sidebar/sidebar_quick_switcher.tsx b/packages/commonwealth/client/scripts/views/components/sidebar/sidebar_quick_switcher.tsx index 849cce38499..4a9d093aee4 100644 --- a/packages/commonwealth/client/scripts/views/components/sidebar/sidebar_quick_switcher.tsx +++ b/packages/commonwealth/client/scripts/views/components/sidebar/sidebar_quick_switcher.tsx @@ -6,6 +6,8 @@ import useUserStore from 'state/ui/user'; import { CWCommunityAvatar } from '../component_kit/cw_community_avatar'; import { CWDivider } from '../component_kit/cw_divider'; import { CWIconButton } from '../component_kit/cw_icon_button'; +import { isWindowSmallInclusive } from '../component_kit/helpers'; +import { CWTooltip } from '../component_kit/new_designs/CWTooltip'; import './sidebar_quick_switcher.scss'; export const SidebarQuickSwitcher = ({ @@ -40,6 +42,23 @@ export const SidebarQuickSwitcher = ({ setMenu({ name: 'exploreCommunities' }); }} /> + {isWindowSmallInclusive(window.innerWidth) && ( + ( + + window.open('https://landing.common.xyz', '_blank') + } + onMouseEnter={handleInteraction} + onMouseLeave={handleInteraction} + /> + )} + /> + )}
diff --git a/packages/commonwealth/client/scripts/views/modals/AuthModal/types.ts b/packages/commonwealth/client/scripts/views/modals/AuthModal/types.ts index cbd5cf5edd8..c168e0fabb3 100644 --- a/packages/commonwealth/client/scripts/views/modals/AuthModal/types.ts +++ b/packages/commonwealth/client/scripts/views/modals/AuthModal/types.ts @@ -21,6 +21,7 @@ export type ModalVariantProps = { | ChainBase.CosmosSDK | ChainBase.Solana | ChainBase.Substrate; + showAuthOptionFor?: AuthWallets | AuthSSOs; onSignInClick?: () => void; }; diff --git a/packages/commonwealth/client/scripts/views/modals/InviteLinkModal/InviteLinkModal.tsx b/packages/commonwealth/client/scripts/views/modals/InviteLinkModal/InviteLinkModal.tsx index fc96bfa2b5d..f9d92acfc7d 100644 --- a/packages/commonwealth/client/scripts/views/modals/InviteLinkModal/InviteLinkModal.tsx +++ b/packages/commonwealth/client/scripts/views/modals/InviteLinkModal/InviteLinkModal.tsx @@ -12,43 +12,30 @@ import { CWTextInput } from '../../components/component_kit/new_designs/CWTextIn import { ShareSkeleton } from './ShareSkeleton'; import { getShareOptions } from './utils'; -import useRunOnceOnCondition from 'hooks/useRunOnceOnCondition'; import app from 'state'; -import { - useCreateReferralLinkMutation, - useGetReferralLinkQuery, -} from 'state/api/user'; import useUserStore from 'state/ui/user'; import './InviteLinkModal.scss'; +import useReferralLink from './useReferralLink'; interface InviteLinkModalProps { onModalClose: () => void; } const InviteLinkModal = ({ onModalClose }: InviteLinkModalProps) => { - const { data: refferalLinkData, isLoading: isLoadingReferralLink } = - useGetReferralLinkQuery(); - const user = useUserStore(); const hasJoinedCommunity = !!user.activeAccount; const communityId = hasJoinedCommunity ? app.activeChainId() : ''; - const { mutate: createReferralLink, isLoading: isLoadingCreateReferralLink } = - useCreateReferralLinkMutation(); + const { referralLink, isLoadingReferralLink, isLoadingCreateReferralLink } = + useReferralLink({ autorun: true }); - const referralLink = refferalLinkData?.referral_link; const currentUrl = window.location.origin; const inviteLink = referralLink ? `${currentUrl}${communityId ? `/${communityId}/discussions` : '/dashboard'}?refcode=${referralLink}` : ''; - useRunOnceOnCondition({ - callback: () => createReferralLink({}), - shouldRun: !isLoadingReferralLink && !referralLink, - }); - const handleCopy = () => { if (referralLink) { saveToClipboard(inviteLink, true).catch(console.error); diff --git a/packages/commonwealth/client/scripts/views/modals/InviteLinkModal/useReferralLink.ts b/packages/commonwealth/client/scripts/views/modals/InviteLinkModal/useReferralLink.ts new file mode 100644 index 00000000000..cce7b15921d --- /dev/null +++ b/packages/commonwealth/client/scripts/views/modals/InviteLinkModal/useReferralLink.ts @@ -0,0 +1,46 @@ +import { useFlag } from 'hooks/useFlag'; +import useRunOnceOnCondition from 'hooks/useRunOnceOnCondition'; +import { + useCreateReferralLinkMutation, + useGetReferralLinkQuery, +} from 'state/api/user'; + +const useReferralLink = ({ autorun = false }: { autorun?: boolean } = {}) => { + const referralsEnabled = useFlag('referrals'); + const { data: refferalLinkData, isLoading: isLoadingReferralLink } = + useGetReferralLinkQuery(); + + const { + mutate: createReferralLink, + mutateAsync: createReferralLinkAsync, + isLoading: isLoadingCreateReferralLink, + } = useCreateReferralLinkMutation(); + + const referralLink = refferalLinkData?.referral_link; + + useRunOnceOnCondition({ + callback: () => createReferralLink({}), + shouldRun: + autorun && referralsEnabled && !isLoadingReferralLink && !referralLink, + }); + + const getReferralLink = async () => { + if (referralLink) { + return referralLink; + } + + if (!isLoadingReferralLink && referralsEnabled) { + const result = await createReferralLinkAsync({}); + return result.referral_link; + } + }; + + return { + referralLink, + getReferralLink, + isLoadingReferralLink, + isLoadingCreateReferralLink, + }; +}; + +export default useReferralLink; diff --git a/packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/TokenLaunchDrawer/TokenLaunchDrawer.tsx b/packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/TokenLaunchDrawer/TokenLaunchDrawer.tsx index e09a1c26b33..dd6f3512a8b 100644 --- a/packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/TokenLaunchDrawer/TokenLaunchDrawer.tsx +++ b/packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/TokenLaunchDrawer/TokenLaunchDrawer.tsx @@ -9,9 +9,9 @@ import { import CWDrawer, { CWDrawerTopBar, } from 'views/components/component_kit/new_designs/CWDrawer'; +import QuickTokenLaunchForm from '../../../LaunchToken/QuickTokenLaunchForm'; // eslint-disable-next-line max-len -import { triggerTokenLaunchFormAbort } from '../../../LaunchToken/steps/TokenInformationStep/TokenInformationForm/helpers'; -import QuickTokenLaunchForm from '../QuickTokenLaunchForm'; +import { triggerTokenLaunchFormAbort } from '../../../LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/helpers'; import './TokenLaunchDrawer.scss'; type TokenLaunchDrawerProps = { diff --git a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/CommunityTypeStep.tsx b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/CommunityTypeStep.tsx index ed06e671f0c..75c5ff8c617 100644 --- a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/CommunityTypeStep.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/CommunityTypeStep.tsx @@ -83,8 +83,13 @@ const CommunityTypeStep = ({ handleContinue(); }; - const [baseOption, blastOption, ethereumOption, ...advancedOptions] = - communityTypeOptions; + const [ + baseOption, + blastOption, + ethereumOption, + skaleOption, + ...advancedOptions + ] = communityTypeOptions; return (
@@ -136,6 +141,19 @@ const CommunityTypeStep = ({ }) } /> + + handleCommunitySelection({ + type: skaleOption.type, + chainBase: skaleOption.chainBase, + }) + } + />
Advanced Options diff --git a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/helpers.ts b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/helpers.ts index 945cef7d2f2..e8179820a33 100644 --- a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/helpers.ts +++ b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/helpers.ts @@ -4,6 +4,7 @@ import blastImg from 'assets/img/communitySelector/blast.png'; import cosmosImg from 'assets/img/communitySelector/cosmos.svg'; import ethereumImg from 'assets/img/communitySelector/ethereum.svg'; import polygonImg from 'assets/img/communitySelector/polygon.svg'; +import skaleImg from 'assets/img/communitySelector/skale.svg'; import solanaImg from 'assets/img/communitySelector/solana.svg'; import { CommunityType } from 'views/components/component_kit/new_designs/CWCommunitySelector'; @@ -37,7 +38,19 @@ export const communityTypeOptions = [ 'Tokens built on the ERC20 protocol are fungible, meaning they are interchangeable. ' + 'Select this community type if you have minted a token on the Ethereum blockchain.', }, - + { + type: CommunityType.Skale, + img: skaleImg, + chainBase: ChainBase.Ethereum, + title: 'Skale', + isRecommended: false, + isHidden: false, + description: + // eslint-disable-next-line max-len + 'SKALE is an on-demand blockchain network with zero gas fees. ' + + // eslint-disable-next-line max-len + 'Allowing quick deployment of interoperable EVM-compatible chains without compromising security or decentralization', + }, { type: CommunityType.Cosmos, img: cosmosImg, diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.scss b/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.scss index 96c01eed471..789c60b54a0 100644 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.scss +++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.scss @@ -4,4 +4,5 @@ display: flex; flex-direction: column; gap: 16px; + max-width: 596px; } diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.tsx b/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.tsx index bfc5b530edd..9e8a356e58a 100644 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.tsx +++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/LaunchToken.tsx @@ -1,39 +1,17 @@ -import useBeforeUnload from 'hooks/useBeforeUnload'; 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 CommunityInformationStep from './steps/CommunityInformationStep'; -import SignatureStep from './steps/SignatureStep'; -import SuccessStep from './steps/SuccessStep'; -import TokenInformationStep from './steps/TokenInformationStep'; -import useCreateTokenCommunity from './useCreateTokenCommunity'; -import { CreateTokenCommunityStep, getFormSteps } from './utils'; +import QuickTokenLaunchForm from './QuickTokenLaunchForm'; const LaunchToken = () => { const navigate = useCommonNavigate(); - const { - baseNode, - createTokenCommunityStep, - onChangeStep, - draftTokenInfo, - selectedAddress, - setSelectedAddress, - setDraftTokenInfo, - createdCommunityId, - setCreatedCommunityId, - isTokenLaunched, - setIsTokenLaunched, - } = useCreateTokenCommunity(); const { isAddedToHomeScreen } = useAppStatus(); - useBeforeUnload(!!draftTokenInfo && !isTokenLaunched); - useBrowserAnalyticsTrack({ payload: { event: MixpanelCommunityCreationEvent.CREATE_TOKEN_COMMUNITY_VISITED, @@ -41,76 +19,13 @@ const LaunchToken = () => { }, }); - const isSuccessStep = - createTokenCommunityStep === CreateTokenCommunityStep.Success; - - const getCurrentStep = () => { - switch (createTokenCommunityStep) { - case CreateTokenCommunityStep.TokenInformation: - return ( - navigate('/')} // redirect to home - handleContinue={(tokenInfo) => { - setDraftTokenInfo(tokenInfo); - - onChangeStep(true); - }} - onAddressSelected={(address) => setSelectedAddress(address)} - selectedAddress={selectedAddress} - /> - ); - case CreateTokenCommunityStep.CommunityInformation: - return ( - onChangeStep(false)} - handleContinue={(communityId) => { - setCreatedCommunityId(communityId); - - onChangeStep(true); - }} - tokenInfo={draftTokenInfo} - selectedAddress={selectedAddress} - /> - ); - case CreateTokenCommunityStep.SignatureLaunch: - // this condition will never be triggered, adding this to avoid typescript errors - if (!createdCommunityId || !selectedAddress || !draftTokenInfo) - return <>; - - return ( - { - setIsTokenLaunched(isLaunched); - - onChangeStep(true); - }} - selectedAddress={selectedAddress} - /> - ); - case CreateTokenCommunityStep.Success: - // this condition will never be triggered, adding this to avoid typescript errors - if (!createdCommunityId) return <>; - - return ( - - ); - } - }; - return (
- {!isSuccessStep && ( - - )} - - {getCurrentStep()} + navigate('/')} + onCommunityCreated={() => {}} + />
); diff --git a/packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/QuickTokenLaunchForm/QuickTokenLaunchForm.scss b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/QuickTokenLaunchForm.scss similarity index 94% rename from packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/QuickTokenLaunchForm/QuickTokenLaunchForm.scss rename to packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/QuickTokenLaunchForm.scss index e5d35c534d7..73a36ae3f3e 100644 --- a/packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/QuickTokenLaunchForm/QuickTokenLaunchForm.scss +++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/QuickTokenLaunchForm.scss @@ -1,4 +1,4 @@ -@import '../../../../../styles/shared.scss'; +@import '../../../../styles/shared.scss'; .QuickTokenLaunchForm { div.h3 { diff --git a/packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/QuickTokenLaunchForm/QuickTokenLaunchForm.tsx b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/QuickTokenLaunchForm.tsx similarity index 97% rename from packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/QuickTokenLaunchForm/QuickTokenLaunchForm.tsx rename to packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/QuickTokenLaunchForm.tsx index 0b9154a6f8f..3845b767579 100644 --- a/packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/QuickTokenLaunchForm/QuickTokenLaunchForm.tsx +++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/QuickTokenLaunchForm.tsx @@ -23,13 +23,13 @@ import CWCircleMultiplySpinner from 'views/components/component_kit/new_designs/ import { CWTooltip } from 'views/components/component_kit/new_designs/CWTooltip'; import TokenLaunchButton from 'views/components/sidebar/TokenLaunchButton'; import { openConfirmation } from 'views/modals/confirmation_modal'; -import { generateCommunityNameFromToken } from '../../../LaunchToken/steps/CommunityInformationStep/utils'; -import SuccessStep from '../../../LaunchToken/steps/SuccessStep'; -import TokenInformationForm from '../../../LaunchToken/steps/TokenInformationStep/TokenInformationForm'; -import { FormSubmitValues } from '../../../LaunchToken/steps/TokenInformationStep/TokenInformationForm/types'; -import useCreateTokenCommunity from '../../../LaunchToken/useCreateTokenCommunity'; +import useCreateTokenCommunity from '../useCreateTokenCommunity'; import './QuickTokenLaunchForm.scss'; +import SuccessStep from './steps/SuccessStep'; +import TokenInformationFormStep from './steps/TokenInformationFormStep'; +import { FormSubmitValues } from './steps/TokenInformationFormStep/types'; import { useGenerateTokenIdea } from './useGenerateTokenIdea'; +import { generateCommunityNameFromToken } from './utils'; type QuickTokenLaunchFormProps = { onCancel: () => void; @@ -393,7 +393,7 @@ export const QuickTokenLaunchForm = ({ {createdCommunityId ? ( ) : ( - {/* allows to switch b/w generated ideas */} { +}: TokenInformationFormStepProps) => { const user = useUserStore(); const [baseOption] = communityTypeOptions; @@ -180,7 +180,7 @@ const TokenInformationForm = ({ onSubmit={handleSubmit} onWatch={onFormUpdate} validationSchema={tokenInformationFormValidationSchema} - className={clsx('TokenInformationForm', containerClassName)} + className={clsx('TokenInformationFormStep', containerClassName)} >
@@ -294,4 +294,4 @@ const TokenInformationForm = ({ ); }; -export default TokenInformationForm; +export default TokenInformationFormStep; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/helpers.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/helpers.ts similarity index 100% rename from packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/helpers.ts rename to packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/helpers.ts diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/index.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/index.ts new file mode 100644 index 00000000000..8491651f82e --- /dev/null +++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/index.ts @@ -0,0 +1,3 @@ +import TokenInformationFormStep from './TokenInformationFormStep'; + +export default TokenInformationFormStep; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/types.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/types.ts similarity index 96% rename from packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/types.ts rename to packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/types.ts index 8d16673a231..7f09f989f29 100644 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/types.ts +++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/types.ts @@ -10,7 +10,7 @@ export type FormSubmitValues = { imageURL: string; }; -export type TokenInformationFormProps = { +export type TokenInformationFormStepProps = { onSubmit: (values: FormSubmitValues) => void; onCancel: () => void; onFormUpdate?: (values: FormSubmitValues) => void; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/validation.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/validation.ts similarity index 100% rename from packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/validation.ts rename to packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/steps/TokenInformationFormStep/validation.ts diff --git a/packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/QuickTokenLaunchForm/useGenerateTokenIdea.tsx b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/useGenerateTokenIdea.tsx similarity index 100% rename from packages/commonwealth/client/scripts/views/pages/Communities/IdeaLaunchpad/QuickTokenLaunchForm/useGenerateTokenIdea.tsx rename to packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/useGenerateTokenIdea.tsx diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/utils.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/utils.ts similarity index 72% rename from packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/utils.ts rename to packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/utils.ts index 2a53023b084..406101340be 100644 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/utils.ts +++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/QuickTokenLaunchForm/utils.ts @@ -5,5 +5,5 @@ export const generateCommunityNameFromToken = ({ tokenName: string; tokenSymbol: string; }) => { - return `${tokenName} (${tokenSymbol}) Community`; + return `${tokenName} (${tokenSymbol})`; }; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/CommunityInformationStep.scss b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/CommunityInformationStep.scss deleted file mode 100644 index f3062f52c77..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/CommunityInformationStep.scss +++ /dev/null @@ -1,32 +0,0 @@ -@import '../../../../../styles/shared'; - -$form-width: 596px; - -.CommunityInformationStep { - display: flex; - flex-direction: column; - gap: 24px; - position: relative; - - > .header { - margin-top: 8px; - width: 100%; - - .description { - color: $neutral-500; - } - } - - .CWBanner { - max-width: $form-width !important; - - .content-container { - max-width: calc($form-width - 70px) !important; - } - } - - @include smallInclusive { - flex-direction: column; - gap: 16px; - } -} diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/CommunityInformationStep.tsx b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/CommunityInformationStep.tsx deleted file mode 100644 index 113b38380a4..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/CommunityInformationStep.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { commonProtocol } from '@hicommonwealth/evm-protocols'; -import { ChainBase } from '@hicommonwealth/shared'; -import { notifyError } from 'controllers/app/notifications'; -import useAppStatus from 'hooks/useAppStatus'; -import { useBrowserAnalyticsTrack } from 'hooks/useBrowserAnalyticsTrack'; -import AddressInfo from 'models/AddressInfo'; -import React from 'react'; -import { - BaseMixpanelPayload, - MixpanelCommunityCreationEvent, - MixpanelLoginPayload, -} from 'shared/analytics/types'; -import useCreateCommunityMutation, { - buildCreateCommunityInput, -} from 'state/api/communities/createCommunity'; -import { fetchCachedNodes } from 'state/api/nodes'; -import useUserStore from 'state/ui/user'; -import CommunityInformationForm from 'views/components/CommunityInformationForm/CommunityInformationForm'; -import { CommunityInformationFormSubmitValues } from 'views/components/CommunityInformationForm/types'; -import { CWText } from 'views/components/component_kit/cw_text'; -import CWBanner from 'views/components/component_kit/new_designs/CWBanner'; -import { openConfirmation } from 'views/modals/confirmation_modal'; -import { TokenInfo } from '../../types'; -import './CommunityInformationStep.scss'; -import { generateCommunityNameFromToken } from './utils'; - -interface CommunityInformationStepProps { - handleGoBack: () => void; - handleContinue: (communityId: string) => void; - tokenInfo?: TokenInfo; - selectedAddress?: AddressInfo; -} - -const CommunityInformationStep = ({ - handleGoBack, - handleContinue, - tokenInfo, - selectedAddress, -}: CommunityInformationStepProps) => { - const user = useUserStore(); - const { isAddedToHomeScreen } = useAppStatus(); - - const initialValues = { - communityName: generateCommunityNameFromToken({ - tokenName: tokenInfo?.name || '', - tokenSymbol: tokenInfo?.symbol || '', - }), - communityDescription: tokenInfo?.description || '', - communityProfileImageURL: tokenInfo?.imageURL || '', - }; - - const { trackAnalytics } = useBrowserAnalyticsTrack< - MixpanelLoginPayload | BaseMixpanelPayload - >({ - onAction: true, - }); - - const { - mutateAsync: createCommunityMutation, - isLoading: createCommunityLoading, - } = useCreateCommunityMutation(); - - const handleSubmit = async ( - values: CommunityInformationFormSubmitValues & { communityId: string }, - ) => { - const nodes = fetchCachedNodes(); - const baseNode = nodes?.find( - (n) => n.ethChainId === commonProtocol.ValidChains.SepoliaBase, - ); - - // this condition will never be triggered, adding this to avoid typescript errors - if (!baseNode || !baseNode.ethChainId) { - notifyError('Could not find base chain node'); - return; - } - - try { - if (selectedAddress?.address) { - user.setData({ - addressSelectorSelectedAddress: selectedAddress.address, - }); - } - - const input = buildCreateCommunityInput({ - id: values.communityId, - name: values.communityName, - chainBase: ChainBase.Ethereum, - description: values.communityDescription, - iconUrl: values.communityProfileImageURL, - socialLinks: values.links ?? [], - chainNodeId: baseNode.id, - }); - await createCommunityMutation(input); - handleContinue(values.communityId); - } catch (err) { - notifyError(err.message); - } - }; - - const handleCancel = () => { - trackAnalytics({ - event: MixpanelCommunityCreationEvent.CREATE_TOKEN_COMMUNITY_CANCELLED, - isPWA: isAddedToHomeScreen, - }); - - openConfirmation({ - title: 'Are you sure you want to cancel?', - description: - 'Your details will not be saved. Cancel create token community flow?', - buttons: [ - { - label: 'Yes, cancel', - buttonType: 'destructive', - buttonHeight: 'sm', - onClick: handleGoBack, - }, - { - label: 'No, continue', - buttonType: 'primary', - buttonHeight: 'sm', - }, - ], - }); - }; - - return ( -
-
- Tell us about your community - - Let's start with some basic information about your community - -
- - - - -
- ); -}; - -export default CommunityInformationStep; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/index.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/index.ts deleted file mode 100644 index 4c30a45a445..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/CommunityInformationStep/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CommunityInformationStep from './CommunityInformationStep'; - -export default CommunityInformationStep; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignTokenTransactions/SignTokenTransactions.scss b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignTokenTransactions/SignTokenTransactions.scss deleted file mode 100644 index 6b51f99805c..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignTokenTransactions/SignTokenTransactions.scss +++ /dev/null @@ -1,43 +0,0 @@ -@import '../../../../../../styles/shared'; - -$form-width: 596px; - -.SignTokenTransactions { - display: flex; - - .header { - margin-top: 8px; - width: 100%; - display: flex; - gap: 16px; - flex-direction: column; - max-width: 596px; - margin-right: 110px; - - @include smallInclusive { - margin-right: unset; - } - - .description { - color: $neutral-500; - } - - .Divider { - margin-block: 8px; - } - - .action-buttons { - display: flex; - justify-content: space-between; - } - } - - .CWBanner { - display: flex !important; - max-width: $form-width !important; - - .content-container { - max-width: calc($form-width - 70px) !important; - } - } -} diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignTokenTransactions/SignTokenTransactions.tsx b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignTokenTransactions/SignTokenTransactions.tsx deleted file mode 100644 index c659ce8778e..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignTokenTransactions/SignTokenTransactions.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React from 'react'; -import { useUpdateCommunityMutation } from 'state/api/communities'; -import { useLaunchTokenMutation } from 'state/api/launchPad'; -import { useCreateTokenMutation } from 'state/api/tokens'; -import useUserStore from 'state/ui/user'; -import { CWDivider } from 'views/components/component_kit/cw_divider'; -import { CWText } from 'views/components/component_kit/cw_text'; -import CWBanner from 'views/components/component_kit/new_designs/CWBanner'; -import { CWButton } from 'views/components/component_kit/new_designs/CWButton'; -import ActionSteps from 'views/pages/CreateCommunity/components/ActionSteps'; -import { ActionStepsProps } from 'views/pages/CreateCommunity/components/ActionSteps/types'; -import { SignTokenTransactionsProps } from '../types'; -import './SignTokenTransactions.scss'; - -const SignTokenTransactions = ({ - createdCommunityId, - selectedAddress, - baseNode, - tokenInfo, - onSuccess, - onCancel, -}: SignTokenTransactionsProps) => { - const user = useUserStore(); - - const { - mutateAsync: launchToken, - isLoading: isCreatingToken, - error: tokenLaunchError, - data: createdToken, - } = useLaunchTokenMutation(); - - const { mutateAsync: createToken } = useCreateTokenMutation(); - - const { mutateAsync: updateCommunity } = useUpdateCommunityMutation({ - communityId: createdCommunityId, - }); - - const handleSign = async () => { - try { - if (selectedAddress?.address) { - user.setData({ - addressSelectorSelectedAddress: selectedAddress.address, - }); - } - - // 1. Attempt Launch token on chain - const payload = { - chainRpc: baseNode.url, - ethChainId: baseNode.ethChainId || 0, // this will always exist, adding 0 to avoid typescript issues - name: tokenInfo.name.trim(), - symbol: tokenInfo.symbol.trim(), - walletAddress: selectedAddress.address, - }; - - const txReceipt = await launchToken(payload); - - // 2. store `tokenInfo` on db - const token = await createToken({ - transaction_hash: txReceipt.transactionHash, - chain_node_id: baseNode.id, - community_id: createdCommunityId, - icon_url: tokenInfo?.imageURL?.trim() || '', - description: tokenInfo?.description?.trim() || '', - }); - - // 3. update community to reference the created token - await updateCommunity({ - community_id: createdCommunityId, - token_name: payload.name, - namespace: token.namespace, - transactionHash: txReceipt.transactionHash, - }); - - onSuccess(); - } catch (e) { - // this will be displayed in the action step as `errorText`, no need to notify here - console.error(e); - } - }; - - const getActionSteps = (): ActionStepsProps['steps'] => { - return [ - { - label: 'Launch token', - state: isCreatingToken - ? 'loading' - : createdToken - ? 'completed' - : 'not-started', - actionButton: { - label: 'Sign', - disabled: false, - onClick: () => { - handleSign().catch(console.error); - }, - }, - errorText: tokenLaunchError - ? 'Something went wrong when creating the token' - : '', - }, - ]; - }; - - return ( -
-
- - Sign transactions to launch token and community - - - In order to launch token and community you will need to sign a - transaction. This transaction has associated gas fees. - - - - - - - - -
- -
-
-
- ); -}; - -export default SignTokenTransactions; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignTokenTransactions/index.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignTokenTransactions/index.ts deleted file mode 100644 index 6a8cf9074a2..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignTokenTransactions/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import SignTokenTransactions from './SignTokenTransactions'; - -export default SignTokenTransactions; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignatureStep.tsx b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignatureStep.tsx deleted file mode 100644 index 9ff77474622..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/SignatureStep.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import SignTokenTransactions from './SignTokenTransactions'; -import { SignatureStepProps } from './types'; - -const SignatureStep = ({ - goToSuccessStep, - createdCommunityId, - selectedAddress, - baseNode, - tokenInfo, -}: SignatureStepProps) => { - return ( -
- goToSuccessStep(true)} - onCancel={() => goToSuccessStep(false)} - selectedAddress={selectedAddress} - createdCommunityId={createdCommunityId} - baseNode={baseNode} - tokenInfo={tokenInfo} - /> -
- ); -}; - -export default SignatureStep; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/index.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/index.ts deleted file mode 100644 index 1b5ab5a2ce8..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import SignatureStep from './SignatureStep'; - -export default SignatureStep; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/types.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/types.ts deleted file mode 100644 index f665baef81c..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/SignatureStep/types.ts +++ /dev/null @@ -1,32 +0,0 @@ -import NodeInfo from 'client/scripts/models/NodeInfo'; -import AddressInfo from 'models/AddressInfo'; -import { ActionStepProps } from 'views/pages/CreateCommunity/components/ActionSteps/types'; -import { TokenInfo } from '../../types'; - -export type ActionState = { - state: ActionStepProps['state']; - errorText: string; -}; - -type BaseProps = { - createdCommunityId: string; - selectedAddress: AddressInfo; - tokenInfo: TokenInfo; - baseNode: NodeInfo; -}; - -export type SignatureStepProps = { - goToSuccessStep: (isLaunched: boolean) => void; -} & BaseProps; - -export type SignTokenTransactionsProps = { - onSuccess: () => void; - onCancel: () => void; - selectedAddress: AddressInfo; - createdCommunityId: string; -} & BaseProps; - -export const defaultActionState: ActionState = { - state: 'not-started', - errorText: '', -}; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/index.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/index.ts deleted file mode 100644 index ea9d67d873f..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationForm/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import TokenInformationForm from './TokenInformationForm'; - -export default TokenInformationForm; 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 deleted file mode 100644 index ddb67a65634..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationStep.scss +++ /dev/null @@ -1,24 +0,0 @@ -@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 deleted file mode 100644 index c6fa0767e67..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/TokenInformationStep.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import AddressInfo from 'models/AddressInfo'; -import React from 'react'; -import { CWText } from 'views/components/component_kit/cw_text'; -import TokenInformationForm from './TokenInformationForm/TokenInformationForm'; -import { FormSubmitValues } from './TokenInformationForm/types'; -import './TokenInformationStep.scss'; - -interface TokenInformationStepProps { - handleGoBack: () => void; - handleContinue: (values: FormSubmitValues) => void; - selectedAddress?: AddressInfo; - onAddressSelected: (address: AddressInfo) => void; -} - -const TokenInformationStep = ({ - handleGoBack, - handleContinue, - selectedAddress, - onAddressSelected, -}: 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 deleted file mode 100644 index ead76717620..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/steps/TokenInformationStep/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import TokenInformationStep from './TokenInformationStep'; - -export default TokenInformationStep; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/useCreateTokenCommunity.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/useCreateTokenCommunity.ts index 3ba6786140e..2fa71108238 100644 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/useCreateTokenCommunity.ts +++ b/packages/commonwealth/client/scripts/views/pages/LaunchToken/useCreateTokenCommunity.ts @@ -3,8 +3,6 @@ import AddressInfo from 'models/AddressInfo'; import NodeInfo from 'models/NodeInfo'; import { useState } from 'react'; import { fetchCachedNodes } from 'state/api/nodes'; -import { TokenInfo } from './types'; -import { CreateTokenCommunityStep, handleChangeStep } from './utils'; const useCreateTokenCommunity = () => { // get base chain node info @@ -13,35 +11,15 @@ const useCreateTokenCommunity = () => { (n) => n.ethChainId === commonProtocol.ValidChains.SepoliaBase, ) as NodeInfo; // this is expected to exist - const [createTokenCommunityStep, setCreateTokenCommunityStep] = - useState( - CreateTokenCommunityStep.TokenInformation, - ); const [selectedAddress, setSelectedAddress] = useState(); - const [draftTokenInfo, setDraftTokenInfo] = useState(); const [createdCommunityId, setCreatedCommunityId] = useState(); - const [isTokenLaunched, setIsTokenLaunched] = useState(false); - - const onChangeStep = (forward: boolean) => { - handleChangeStep( - forward, - createTokenCommunityStep, - setCreateTokenCommunityStep, - ); - }; return { baseNode, - createTokenCommunityStep, - onChangeStep, selectedAddress, setSelectedAddress, - draftTokenInfo, - setDraftTokenInfo, createdCommunityId, setCreatedCommunityId, - isTokenLaunched, - setIsTokenLaunched, }; }; diff --git a/packages/commonwealth/client/scripts/views/pages/LaunchToken/utils.ts b/packages/commonwealth/client/scripts/views/pages/LaunchToken/utils.ts deleted file mode 100644 index 8cc705fc89d..00000000000 --- a/packages/commonwealth/client/scripts/views/pages/LaunchToken/utils.ts +++ /dev/null @@ -1,67 +0,0 @@ -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/server/farcaster/frames/contest/contestCard.tsx b/packages/commonwealth/server/farcaster/frames/contest/contestCard.tsx index 504ed566e2e..42de9b5296f 100644 --- a/packages/commonwealth/server/farcaster/frames/contest/contestCard.tsx +++ b/packages/commonwealth/server/farcaster/frames/contest/contestCard.tsx @@ -3,7 +3,7 @@ import { Contest, config as modelConfig } from '@hicommonwealth/model'; import { Button } from 'frames.js/express'; import React from 'react'; -import { PRODUCTION_DOMAIN } from '@hicommonwealth/shared'; +import { buildContestLeaderboardUrl, getBaseUrl } from '@hicommonwealth/shared'; import { frames } from '../../config'; export const contestCard = frames(async (ctx) => { @@ -70,6 +70,12 @@ export const contestCard = frames(async (ctx) => { }; } + const leaderboardUrl = buildContestLeaderboardUrl( + getBaseUrl(config.APP_ENV), + contestManager.community_id, + contestManager.contest_address, + ); + return { title: contestManager.name, image: ( @@ -109,11 +115,7 @@ export const contestCard = frames(async (ctx) => {
), buttons: [ - ,