diff --git a/packages/web/components/QuestChain/MintNFTTile.tsx b/packages/web/components/QuestChain/MintNFTTile.tsx index 43c76d6dc..2019e3dc9 100644 --- a/packages/web/components/QuestChain/MintNFTTile.tsx +++ b/packages/web/components/QuestChain/MintNFTTile.tsx @@ -63,9 +63,9 @@ export const MintNFTTile: React.FC = ({ provider.getSigner(), ); - const tx = await (questChain.version === '1' - ? (contract as contracts.V1.QuestChain).mintToken() - : (contract as contracts.V0.QuestChain).mintToken(address)); + const tx = await (questChain.version === '0' + ? (contract as contracts.V0.QuestChain).mintToken(address) + : (contract as contracts.V1.QuestChain).mintToken()); addToast({ description: 'Transaction submitted. Waiting for 1 block confirmation', duration: null, @@ -99,8 +99,7 @@ export const MintNFTTile: React.FC = ({ } }, [onSuccess, questChain, address, chainId, provider, addToast]); - const details = - QuestChainPlaybooksDetails[name]; + const details = QuestChainPlaybooksDetails[name]; const image = details?.image; return ( diff --git a/packages/web/pages/join/guild/[guildname].tsx b/packages/web/pages/join/guild/[guildname].tsx new file mode 100644 index 000000000..13becc365 --- /dev/null +++ b/packages/web/pages/join/guild/[guildname].tsx @@ -0,0 +1,175 @@ +import { Flex, LoadingState, MetaHeading, useToast } from '@metafam/ds'; +import { FlexContainer } from 'components/Container'; +import { EditGuildFormInputs, GuildForm } from 'components/Guild/GuildForm'; +import { + GuildInfoInput, + GuildType_ActionEnum, + LinkType_Enum, + useAddGuildLinkMutation, + useGetGuildQuery, + useUpdateGuildMutation, +} from 'graphql/autogen/types'; +import { useWeb3 } from 'lib/hooks'; +import { useRouter } from 'next/router'; +import Page404 from 'pages/404'; +import React, { lazy,useCallback } from 'react'; +import { errorHandler } from 'utils/errorHandler'; + +const PageContainer = lazy(() => import('components/Container')); + +const SetupGuild: React.FC = () => { + const router = useRouter(); + const guildNameRouter = router.query.guildname as string; + const toast = useToast(); + + const [, addLink] = useAddGuildLinkMutation(); + const [updateGuildState, updateGuild] = useUpdateGuildMutation(); + const [res] = useGetGuildQuery({ variables: { guildname: guildNameRouter } }); + const guild = res.data?.guild[0]; + const { w3storage } = useWeb3(); + const onSubmit = useCallback( + async (editGuildFormInputs: EditGuildFormInputs) => { + if (!guild) return; + + const { + type, + discordAdminRoles: adminRoles, + discordMembershipRoles: membershipRoles, + logoFile, + logoURL, + daos, + websiteURL, + githubURL, + twitterURL, + description, + guildname, + name, + discordInviteURL, + joinURL, + } = editGuildFormInputs; + + let newLogoURL = logoURL; + + if (logoFile?.[0]) { + try { + const ipfsHash = await w3storage?.uploadFile(logoFile[0]); + newLogoURL = `ipfs://${ipfsHash}`; + } catch (error) { + toast({ + title: 'Error Saving Logo', + description: (error as Error).message, + status: 'warning', + isClosable: true, + duration: 8000, + }); + errorHandler(error as Error); + return; + } + } + + const twitterGuildLink = { + guildId: guild.id, + name: 'Find Us On Twitter', + url: twitterURL || '', + type: 'TWITTER' as LinkType_Enum, + }; + await addLink(twitterGuildLink); + + const discordGuildLink = { + guildId: guild.id, + name: 'Join Us On Discord', + url: discordInviteURL || '', + type: 'DISCORD' as LinkType_Enum, + }; + await addLink(discordGuildLink); + + const githubGuildLink = { + guildId: guild.id, + name: 'Find Us On Github', + url: githubURL || '', + type: 'GITHUB' as LinkType_Enum, + }; + await addLink(githubGuildLink); + + const payload: GuildInfoInput = { + guildname, + name, + description, + joinURL, + daos, + websiteURL, + discordAdminRoles: adminRoles.map((o) => o.value), + discordMembershipRoles: membershipRoles.map((o) => o.value), + type: type as unknown as GuildType_ActionEnum, + uuid: guild.id, + logoURL: newLogoURL, + }; + + const response = await updateGuild({ guildInfo: payload }); + + const saveGuildResponse = response.data?.saveGuildInformation; + if (saveGuildResponse?.success) { + toast({ + title: 'Guild information submitted', + description: 'Thanks! Your guild will go live shortly 🚀', + status: 'success', + isClosable: true, + duration: 5000, + }); + setTimeout(() => { + router.push('/dashboard'); + }, 5000); + } else { + toast({ + title: 'Error while saving guild information', + description: + response.error?.message || + saveGuildResponse?.error || + 'unknown error', + status: 'error', + isClosable: true, + duration: 10000, + }); + } + }, + [guild, router, toast, updateGuild, addLink, w3storage], + ); + + if (res.fetching || res.data == null) { + return ; + } + + if (res.data.guild.length === 0 || guild == null) { + return ; + } + + return ( + + + + Add guild information + + + + + + + ); +}; + +export default SetupGuild; \ No newline at end of file diff --git a/packages/web/pages/join/guild/auth.tsx b/packages/web/pages/join/guild/auth.tsx new file mode 100644 index 000000000..474479942 --- /dev/null +++ b/packages/web/pages/join/guild/auth.tsx @@ -0,0 +1,70 @@ +import { Box, Link } from '@metafam/ds'; +import { useAuthenticateDiscordGuildMutation } from 'graphql/autogen/types'; +import { get, remove } from 'lib/store'; +import { useRouter } from 'next/router'; +import React, { lazy,useEffect, useState } from 'react'; + +import { discordAuthStateGuidKey } from './start'; + +const PageContainer = lazy(() => import('components/Container')); + +const GuildSetupAuthCallback: React.FC = () => { + const router = useRouter(); + + const [authGuildRes, authGuild] = useAuthenticateDiscordGuildMutation(); + const [error, setError] = useState(''); + const [fetching, setFetching] = useState(false); + + useEffect(() => { + // when auth request is denied, we get `error=access_denied` and `error_description` and `state` parameters + const { code, error_description: discordErrorDetail, state } = router.query; + + const localState = get(discordAuthStateGuidKey); + + if (discordErrorDetail != null) { + setError(discordErrorDetail as string); + return; + } + + const submitAuthCode = async () => { + const { data, error: mutationError } = await authGuild({ + code: code as string, + }); + const response = data?.authenticateDiscordGuild; + if (mutationError || response?.success === false) { + setError(mutationError?.message || 'An unexpected error occurred.'); + } else if (response?.guildname != null) { + // clean up guid + remove(discordAuthStateGuidKey); + router.push(`/join/guild/${response?.guildname}`); + } + }; + if (!fetching && code) { + if (localState == null || localState !== state) { + setError('State did not match.'); + return; + } + setFetching(true); + submitAuthCode(); + } + }, [router, authGuild, error, fetching]); + + return ( + + {error && ( +
+ Could not load your guild information from Discord: {error} + + + Try again + + +
+ )} + {authGuildRes.fetching && + 'Loading your guild information from Discord...'} +
+ ); +}; + +export default GuildSetupAuthCallback; \ No newline at end of file diff --git a/packages/web/pages/join/guild/start.tsx b/packages/web/pages/join/guild/start.tsx new file mode 100644 index 000000000..f83058720 --- /dev/null +++ b/packages/web/pages/join/guild/start.tsx @@ -0,0 +1,101 @@ +import { + Box, + Flex, + ListItem, + MetaButton, + MetaHeading, + Text, + UnorderedList, +} from '@metafam/ds'; +import { Constants, generateUUID } from '@metafam/utils'; +import { CONFIG } from 'config'; +import { useUser } from 'lib/hooks'; +import { get, set } from 'lib/store'; +import React, { lazy,useEffect, useState } from 'react'; + +const discordOAuthCallbackURL = `${CONFIG.publicURL}/${Constants.DISCORD_OAUTH_CALLBACK_PATH}`; +export const discordAuthStateGuidKey = 'metagame-add-guild'; + +const PageContainer = lazy(() => import('components/Container')); + +const GuildSetupAuthCallback: React.FC = () => { + const { user } = useUser(); + + const [stateGuid, setStateGuid] = useState(); + + useEffect(() => { + let guid = get(discordAuthStateGuidKey); + if (guid == null) { + guid = generateUUID(); + set(discordAuthStateGuidKey, guid); + } + setStateGuid(guid); + }, [setStateGuid]); + + const discordAuthParams = new URLSearchParams({ + response_type: 'code', + client_id: Constants.DISCORD_BOT_CLIENT_ID, + // This will be passed-back and verified after the Discord auth redirect + state: stateGuid as string, + permissions: Constants.JOIN_GUILD_DISCORD_BOT_PERMISSIONS, + redirect_uri: encodeURI(discordOAuthCallbackURL), + scope: Constants.JOIN_GUILD_DISCORD_OAUTH_SCOPES, + }); + + const discordAuthURL = `${ + CONFIG.discordAPIBaseUrl + }/oauth2/authorize?${discordAuthParams.toString()}`; + + return ( + + Join as a Guild + + {stateGuid?.length && user ? ( + <> + + Clicking the link below will redirect to a Discord page asking for + your permission to collect this information about your guild from + your guild's Discord server: + + + Read messages / history. Optional, but this allows us + to display announcements from your Discord announcements + channel(s) on your MyMeta guild's page. + + + + + Wait, why Discord? + + + Well, turns out that (at this moment anyway) there is no + standardized source of truth for determining who is a "member" of + a guild. We built an integration with Discord because just about + every guild has a Discord server. Most servers use roles to give + certain community members additional privileges, which is often a + good approximation for "membership". So, by linking your Discord + server and telling us what roles determine what, we can determine + which MyMeta users are members of your guild! + + + Join + + + ) : ( + + Please log in or create a player profile by pressing the "Connect" + button to start the guild setup process. + + )} + + + ); +}; + +export default GuildSetupAuthCallback; \ No newline at end of file