`
- )}")`,
- }}
- />
-
-
-
-
-
-
-
-
-
-
- {children}
-
-
-
- )
-}
-
-export default CreateProfile
diff --git a/src/app/(marketing)/create-profile/page.tsx b/src/app/(marketing)/create-profile/page.tsx
deleted file mode 100644
index 0aef57f654..0000000000
--- a/src/app/(marketing)/create-profile/page.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-"use client"
-
-import { walletSelectorModalAtom } from "@/components/Providers/atoms"
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import { Anchor } from "@/components/ui/Anchor"
-import { Button, buttonVariants } from "@/components/ui/Button"
-import { SignIn } from "@phosphor-icons/react"
-import { useSetAtom } from "jotai"
-import { useRouter, useSearchParams } from "next/navigation"
-import { useEffect } from "react"
-import { useIsClient } from "usehooks-ts"
-
-const Page = () => {
- const { isWeb3Connected } = useWeb3ConnectionManager()
- const setIsWalletSelectorModalOpen = useSetAtom(walletSelectorModalAtom)
- const router = useRouter()
- const searchParams = useSearchParams()
- const isClient = useIsClient()
-
- useEffect(() => {
- if (isWeb3Connected) {
- router.replace(
- ["/create-profile/prompt-referrer", searchParams]
- .filter(Boolean)
- .map(String)
- .join("?")
- )
- }
- }, [isWeb3Connected, router.replace, searchParams])
-
- return (
-
-
-
- Sign in to create your profile
-
-
- Start your new profile adventure by signing in: earn experience, display
- achievements and explore new rewards!
-
-
-
-
- Back to home
-
-
setIsWalletSelectorModalOpen(true)}
- colorScheme="primary"
- size="lg"
- className="w-full"
- isLoading={isClient && isWeb3Connected === null}
- leftIcon={ }
- >
- Sign in
-
-
-
- )
-}
-
-export default Page
diff --git a/src/app/(marketing)/layout.tsx b/src/app/(marketing)/layout.tsx
deleted file mode 100644
index 64efbb26a2..0000000000
--- a/src/app/(marketing)/layout.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { ConfettiProvider } from "@/components/Confetti"
-import { PropsWithChildren } from "react"
-
-const Layout = ({ children }: PropsWithChildren) => {
- return
{children}
-}
-
-export default Layout
diff --git a/src/app/(marketing)/profile/[username]/constants.ts b/src/app/(marketing)/profile/[username]/constants.ts
deleted file mode 100644
index a5e0ae2ec0..0000000000
--- a/src/app/(marketing)/profile/[username]/constants.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-export const MAX_LEVEL = 100
-export const XP_SUM = 1e6
-export const RANKS = [
- { color: "#78c93d", title: "novice", polygonCount: 20 },
- { color: "#88d525", title: "learner", polygonCount: 20 },
- { color: "#f6ca45", title: "knight", polygonCount: 4 },
- { color: "#f19b38", title: "veteran", polygonCount: 4 },
- { color: "#ec5a53", title: "champion", polygonCount: 4 },
- { color: "#53adf0", title: "hero", polygonCount: 5 },
- { color: "#c385f8", title: "master", polygonCount: 5 },
- { color: "#3e6fc3", title: "grand master", polygonCount: 5 },
- { color: "#be4681", title: "legend", polygonCount: 6 },
- { color: "#000000", title: "mythic", polygonCount: 6 },
- {
- color: "#eeeeee",
- title: "???",
- requiredXp: 1e19,
- polygonCount: 6,
- },
-] as const
diff --git a/src/app/(marketing)/profile/[username]/not-found.tsx b/src/app/(marketing)/profile/[username]/not-found.tsx
deleted file mode 100644
index deee809fa2..0000000000
--- a/src/app/(marketing)/profile/[username]/not-found.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Button } from "@/components/ui/Button"
-import { House } from "@phosphor-icons/react/dist/ssr"
-import GuildGhost from "static/avatars/58.svg"
-
-const NotFound = () => {
- return (
-
-
-
-
Profile not found
-
-
-
-
-
- )
-}
-
-export default NotFound
diff --git a/src/app/(marketing)/profile/[username]/page.tsx b/src/app/(marketing)/profile/[username]/page.tsx
deleted file mode 100644
index 5b5c987963..0000000000
--- a/src/app/(marketing)/profile/[username]/page.tsx
+++ /dev/null
@@ -1,289 +0,0 @@
-import { Header } from "@/components/Header"
-import {
- Layout,
- LayoutBanner,
- LayoutFooter,
- LayoutHero,
- LayoutMain,
-} from "@/components/Layout"
-import { SWRProvider } from "@/components/SWRProvider"
-import { FarcasterProfile, Guild, Role, Schemas } from "@guildxyz/types"
-import { env } from "env"
-import { Metadata } from "next"
-import Image from "next/image"
-import { notFound } from "next/navigation"
-import { GuildBase } from "types"
-import { JoinProfileAction } from "../_components/JoinProfileAction"
-import { Profile } from "../_components/Profile"
-import { ProfileColorBanner } from "../_components/ProfileColorBanner"
-import { ProfileHero } from "../_components/ProfileHero"
-import { selectOperatedGuilds } from "../_utils/selectOperatedGuilds"
-
-type PageProps = { params: { username: string } }
-
-export const generateMetadata = async ({ params: { username } }: PageProps) => {
- const { profile } = await fetchPublicProfileData({
- username,
- fetchFallback: false,
- })
- return {
- title: `${profile.name || profile.username} (@${profile.username}) | Guild.xyz`,
- description: profile.bio,
- openGraph: {
- images: [profile.profileImageUrl, profile.backgroundImageUrl].filter(
- Boolean
- ) as string[],
- },
- } satisfies Metadata
-}
-
-const api = env.NEXT_PUBLIC_API
-
-async function ssrFetcher
(...args: Parameters) {
- return (await fetch(...args)).json() as T
-}
-
-const fetchPublicProfileData = async ({
- username,
- fetchFallback = true,
-}: { username: string; fetchFallback?: boolean }) => {
- const profileRequest = new URL(`v2/profiles/${username}`, api)
- const profileResponse = await fetch(profileRequest, {
- next: {
- tags: [profileRequest.pathname],
- revalidate: 3600,
- },
- })
-
- if (profileResponse.status === 404) notFound()
- if (!profileResponse.ok) throw new Error("couldn't to fetch /profile")
-
- const profile = (await profileResponse.json()) as Schemas["Profile"]
- if (!fetchFallback) {
- return { profile }
- }
- const farcasterProfilesRequest = new URL(
- `/v2/users/${profile.userId}/farcaster-profiles`,
- api
- )
- const farcasterProfiles = await ssrFetcher(
- farcasterProfilesRequest,
- {
- next: {
- tags: [farcasterProfilesRequest.pathname],
- revalidate: 3600,
- },
- }
- )
- const fcProfile = farcasterProfiles.at(0)
- const neynarRequest =
- fcProfile &&
- new URL(
- `/api/farcaster/relevant-followers/1/${fcProfile.fid}`,
- env.NEXT_PUBLIC_URL
- )
- const fcFollowers =
- neynarRequest &&
- (await ssrFetcher(neynarRequest, {
- next: {
- revalidate: 12 * 3600,
- },
- }))
-
- const referredUsersRequest = new URL(
- `/v2/profiles/${username}/referred-users`,
- api
- )
- const referredUsers = await ssrFetcher(
- referredUsersRequest,
- {
- next: {
- tags: [profileRequest.pathname],
- revalidate: 3600,
- },
- }
- )
- const contributionsRequest = new URL(`v2/profiles/${username}/contributions`, api)
- const contributions = await ssrFetcher(
- contributionsRequest,
- {
- next: {
- tags: [contributionsRequest.pathname],
- revalidate: 3600,
- },
- }
- )
- const collectionRequests = contributions.map(
- ({ id }) =>
- new URL(`v2/profiles/${username}/contributions/${id}/collection`, api)
- )
- let collections: Schemas["ContributionCollection"][] | undefined
- try {
- collections = await Promise.all(
- collectionRequests.map((req) =>
- ssrFetcher(req, {
- next: {
- tags: ["collections"],
- revalidate: 3600,
- },
- })
- )
- )
- } catch (e) {
- console.error(e)
- }
- const operatedGuildsRequest = new URL(`/v2/guilds?username=${username}`, api)
- const operatedGuilds = await ssrFetcher(operatedGuildsRequest)
- const selectedOperatedGuildRequests = selectOperatedGuilds({
- guilds: operatedGuilds,
- }).map(({ id }) => new URL(`v2/guilds/guild-page/${id}`, api))
- const selectedOperatedGuilds = await Promise.all(
- selectedOperatedGuildRequests.map((req) =>
- ssrFetcher(req, {
- next: {
- revalidate: 3 * 3600,
- },
- })
- )
- )
- const roleRequests = contributions.map(
- ({ roleId, guildId }) => new URL(`v2/guilds/${guildId}/roles/${roleId}`, api)
- )
- const guildRequests = contributions.map(
- ({ guildId }) => new URL(`v2/guilds/${guildId}`, api)
- )
- const guilds = await Promise.all(
- guildRequests.map((req) =>
- ssrFetcher(req, {
- next: {
- revalidate: 3 * 3600,
- },
- })
- )
- )
- const roles = await Promise.all(
- roleRequests.map((req) =>
- ssrFetcher(req, {
- next: {
- revalidate: 3 * 3600,
- },
- })
- )
- )
- const collectionsZipped = collections
- ? collectionRequests.map(({ pathname }, i) => [pathname, collections[i]])
- : []
- const guildsZipped = guildRequests.map(({ pathname }, i) => [pathname, guilds[i]])
- const rolesZipped = roleRequests.map(({ pathname }, i) => [pathname, roles[i]])
- const selectedOperatedGuildsZipped = selectedOperatedGuildRequests.map(
- ({ pathname }, i) => [pathname, selectedOperatedGuilds[i]]
- )
- const experiencesRequest = new URL(`/v2/profiles/${username}/experiences`, api)
- const experiences = await ssrFetcher(experiencesRequest, {
- next: {
- revalidate: 1200,
- },
- })
- const experienceCountRequest = new URL(
- `/v2/profiles/${username}/experiences?count=true`,
- api
- )
- const experienceCount = await ssrFetcher(experienceCountRequest, {
- next: {
- revalidate: 1200,
- },
- })
-
- return {
- profile,
- fallback: Object.fromEntries(
- [
- [profileRequest.pathname, profile],
- [contributionsRequest.pathname, contributions],
- [farcasterProfilesRequest.pathname, farcasterProfiles],
- [neynarRequest?.pathname, fcFollowers],
- [referredUsersRequest.pathname, referredUsers],
- [experiencesRequest.pathname, experiences],
- [
- experienceCountRequest.pathname + experienceCountRequest.search,
- experienceCount,
- ],
- [
- operatedGuildsRequest.pathname + operatedGuildsRequest.search,
- operatedGuilds,
- ],
- ...selectedOperatedGuildsZipped,
- ...collectionsZipped,
- ...guildsZipped,
- ...rolesZipped,
- ].filter(([key, value]) => key && value)
- ),
- }
-}
-
-const Page = async ({ params: { username } }: PageProps) => {
- let profileData: Awaited>
- try {
- profileData = await fetchPublicProfileData({ username })
- } catch (error) {
- const e = error instanceof Error ? error : undefined
- throw new Error(
- ["Failed to retrieve profile data", e?.message].filter(Boolean).join(": ")
- )
- }
- const { profile, fallback } = profileData
- const isBgColor = profile.backgroundImageUrl?.startsWith("#")
-
- return (
-
-
-
-
-
- {isBgColor ? (
-
- ) : (
- profile.backgroundImageUrl && (
-
- )
- )}
-
-
-
-
-
-
-
-
-
- Guild Profiles are currently in early access.
-
-
-
-
-
- )
-}
-
-// biome-ignore lint/style/noDefaultExport: page route
-export default Page
diff --git a/src/app/(marketing)/profile/[username]/types.ts b/src/app/(marketing)/profile/[username]/types.ts
deleted file mode 100644
index f587d90b1f..0000000000
--- a/src/app/(marketing)/profile/[username]/types.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { RANKS } from "./constants"
-
-export type Rank = (typeof RANKS)[number]
diff --git a/src/app/(marketing)/profile/_components/ActivityChart.tsx b/src/app/(marketing)/profile/_components/ActivityChart.tsx
deleted file mode 100644
index 621a80659c..0000000000
--- a/src/app/(marketing)/profile/_components/ActivityChart.tsx
+++ /dev/null
@@ -1,218 +0,0 @@
-import { Separator } from "@/components/ui/Separator"
-import { Skeleton } from "@/components/ui/Skeleton"
-import { Schemas } from "@guildxyz/types"
-import { localPoint } from "@visx/event"
-import { Group } from "@visx/group"
-import ParentSize from "@visx/responsive/lib/components/ParentSize"
-import { scaleBand, scaleLinear } from "@visx/scale"
-import { Bar } from "@visx/shape"
-import { useTooltip, useTooltipInPortal } from "@visx/tooltip"
-import { useMemo } from "react"
-import { useExperienceProgression } from "../_hooks/useExperienceProgression"
-import { useExperiences } from "../_hooks/useExperiences"
-
-type GroupedByEventType = Partial<
- Record<
- Schemas["Experience"]["eventType"],
- { entries: Schemas["Experience"][]; sum: number }
- >
->
-type TooltipData = { sum: Schemas["Experience"]; entries: GroupedByEventType }
-const verticalMargin = 0
-
-const getX = (xp: Schemas["Experience"]) => xp.id.toString()
-const getY = (xp: Schemas["Experience"]) => xp.amount
-
-export type BarsProps = {
- width: number
- height: number
-}
-
-let tooltipTimeout: number
-
-const ActivityChartChildren = ({
- width,
- height,
- rawData,
-}: BarsProps & {
- rawData: Schemas["Experience"][]
-}) => {
- const {
- tooltipOpen,
- tooltipLeft,
- tooltipTop,
- tooltipData,
- hideTooltip,
- showTooltip,
- } = useTooltip()
-
- const { containerRef, TooltipInPortal } = useTooltipInPortal({
- scroll: true,
- })
- const xp = useExperienceProgression()
- const groupedData = new Map()
- for (const rawXp of rawData) {
- const createdAt = new Date(rawXp.createdAt)
- const commonDay = new Date(
- createdAt.getFullYear(),
- createdAt.getMonth(),
- createdAt.getDate()
- ).valueOf()
- groupedData.set(commonDay, [...(groupedData.get(commonDay) ?? []), rawXp])
- }
- const groupedByDate = [...groupedData.entries()]
- const data = groupedByDate
- .reduce((acc, [_, xpGroup]) => {
- return [
- ...acc,
- {
- ...xpGroup[0],
- amount: xpGroup.reduce((sumAcc, xp) => sumAcc + xp.amount, 0),
- },
- ]
- }, [])
- .sort(
- (a, b) => new Date(a.createdAt).valueOf() - new Date(b.createdAt).valueOf()
- )
-
- const xMax = width
- const yMax = height - verticalMargin
- const xScale = useMemo(
- () =>
- scaleBand({
- range: [0, Math.min(data.length * 14, xMax)],
- round: true,
- domain: data.map(getX),
- padding: 0,
- }),
- [xMax]
- )
- const yScale = useMemo(
- () =>
- scaleLinear({
- range: [yMax, 0],
- round: true,
- domain: [0, Math.max(...data.map(getY))],
- }),
- [yMax]
- )
-
- const extendedExperiences: GroupedByEventType[] = []
- for (let i = 0; i < groupedByDate.length; i += 1) {
- const [_, byDate] = groupedByDate[i]
- const groupedByEventType: GroupedByEventType = {}
- for (const entry of byDate) {
- const prev = groupedByEventType[entry.eventType]
- groupedByEventType[entry.eventType] = {
- sum: (prev?.sum || 0) + entry.amount,
- entries: [...(prev?.entries || []), entry],
- }
- }
- extendedExperiences[i] = groupedByEventType
- }
- return width < 10 ? null : (
-
-
-
- {data.map((currentXp, i) => {
- const x = getX(currentXp)
- const barWidth = xScale.bandwidth()
- const barHeight = yMax - (yScale(getY(currentXp)) ?? 0)
- const barX = xScale(x)
- const barY = yMax - barHeight
- return (
-
- {
- tooltipTimeout = window.setTimeout(() => {
- hideTooltip()
- }, 120)
- }}
- onMouseMove={(event) => {
- if (tooltipTimeout) clearTimeout(tooltipTimeout)
- const eventSvgCoords = localPoint(event)
- const left = (barX || 0) + barWidth / 2
- showTooltip({
- tooltipData: {
- sum: currentXp,
- entries: extendedExperiences[i],
- },
- tooltipTop: eventSvgCoords?.y,
- tooltipLeft: left,
- })
- }}
- />
-
-
- )
- })}
-
-
- {tooltipOpen && tooltipData && (
-
-
-
+{tooltipData.sum.amount} XP
-
- {new Date(tooltipData.sum.createdAt).toLocaleDateString("en-US", {
- year: "numeric",
- month: "short",
- day: "numeric",
- })}
-
-
-
-
- {Object.entries(tooltipData.entries).map(([key, value]) => (
-
- +{value.sum}{" "}
-
- ({value.entries.length})
- {" "}
- {key.replace("_", " ").toLowerCase()}{" "}
-
- ))}
-
-
- )}
-
- )
-}
-
-export const ActivityChart = () => {
- const { data: rawData } = useExperiences({ count: false })
-
- if (!rawData) return
-
- if (rawData.length === 0)
- return There's no recent activity
-
- return (
-
-
- {({ width, height }) => (
-
- )}
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/CardWithGuildLabel.tsx b/src/app/(marketing)/profile/_components/CardWithGuildLabel.tsx
deleted file mode 100644
index b483b4d50d..0000000000
--- a/src/app/(marketing)/profile/_components/CardWithGuildLabel.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { Anchor } from "@/components/ui/Anchor"
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/Avatar"
-import { Card } from "@/components/ui/Card"
-import { cn } from "@/lib/utils"
-import { Guild } from "@guildxyz/types"
-import Color from "color"
-import { PropsWithChildren } from "react"
-
-export const CardWithGuildLabel = ({
- guild,
- children,
-}: PropsWithChildren<{ guild: Guild }>) => {
- const color = guild.theme.color && Color(guild.theme.color)
-
- return (
-
-
-
-
-
-
-
-
- {guild.name}
-
-
-
-
- {children}
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/ContributionCard.tsx b/src/app/(marketing)/profile/_components/ContributionCard.tsx
deleted file mode 100644
index 8288685002..0000000000
--- a/src/app/(marketing)/profile/_components/ContributionCard.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Skeleton } from "@/components/ui/Skeleton"
-import { Guild, Role, Schemas } from "@guildxyz/types"
-import useSWRImmutable from "swr/immutable"
-import { useProfile } from "../_hooks/useProfile"
-import { ContributionCardView } from "./ContributionCardView"
-
-export const ContributionCard = ({
- contribution,
-}: { contribution: Schemas["Contribution"] }) => {
- const guild = useSWRImmutable(`/v2/guilds/${contribution.guildId}`)
- const role = useSWRImmutable(
- `/v2/guilds/${contribution.guildId}/roles/${contribution.roleId}`
- )
- const profile = useProfile()
- const collection = useSWRImmutable(
- profile.data
- ? `/v2/profiles/${profile.data.username}/contributions/${contribution.id}/collection`
- : null
- )
-
- if (!role.data || !guild.data) {
- return
- }
-
- return (
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/ContributionCardView.tsx b/src/app/(marketing)/profile/_components/ContributionCardView.tsx
deleted file mode 100644
index 1f15f30e9b..0000000000
--- a/src/app/(marketing)/profile/_components/ContributionCardView.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Anchor } from "@/components/ui/Anchor"
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/Avatar"
-import { Separator } from "@/components/ui/Separator"
-import { Guild, Role, Schemas } from "@guildxyz/types"
-import { Users } from "@phosphor-icons/react/dist/ssr"
-import { CardWithGuildLabel } from "./CardWithGuildLabel"
-import { ContributionCollection } from "./ContributionCollection"
-
-export const ContributionCardView = ({
- guild,
- role,
- collection,
-}: {
- guild: Guild
- role: Role
- collection?: Schemas["ContributionCollection"]
-}) => {
- const roleAcquirementPercentage = Number(
- ((role.memberCount / guild.memberCount || 0) * 100).toFixed(1)
- )
- const collections = [collection?.NFTs, collection?.pins, collection?.points]
- .filter(Boolean)
- .flat()
- return (
-
-
-
-
-
-
-
-
- TOP ROLE
-
-
-
- {role.name}
-
-
-
-
-
- {roleAcquirementPercentage < 18 && "Only "}
- {roleAcquirementPercentage}% of members have this role
-
-
-
- {collection && !!collections.length && (
-
-
-
- COLLECTION:
-
-
-
-
-
-
- )}
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/ContributionCollection.tsx b/src/app/(marketing)/profile/_components/ContributionCollection.tsx
deleted file mode 100644
index fbd162c0ec..0000000000
--- a/src/app/(marketing)/profile/_components/ContributionCollection.tsx
+++ /dev/null
@@ -1,189 +0,0 @@
-import { Anchor } from "@/components/ui/Anchor"
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/Avatar"
-import { Badge } from "@/components/ui/Badge"
-import {
- HoverCard,
- HoverCardContent,
- HoverCardTrigger,
-} from "@/components/ui/HoverCard"
-import { Guild, GuildReward, Schemas } from "@guildxyz/types"
-import { Ranking } from "@phosphor-icons/react"
-import { ArrowRight } from "@phosphor-icons/react/dist/ssr"
-import { GuildAction } from "components/[guild]/Requirements/components/GuildCheckout/MintGuildPinContext"
-import { env } from "env"
-import Image from "next/image"
-import { ReactNode } from "react"
-import Star from "static/icons/star.svg"
-import useSWRImmutable from "swr/immutable"
-import shortenHex from "utils/shortenHex"
-
-export const ContributionCollection = ({
- collection,
- guild,
-}: { collection: Schemas["ContributionCollection"]; guild: Guild }) => {
- const collectionPoint = collection.points.at(0)
- const collectionNft = collection.NFTs.at(0)
- const collectionPin = collection.pins.at(0)
- const { data: pinHash } = useSWRImmutable(
- collectionPin
- ? `/v2/guilds/${guild.id}/pin?guildAction=${GuildAction[collectionPin.action]}`
- : null
- )
- const pin = pinHash ? new URL(pinHash, env.NEXT_PUBLIC_IPFS_GATEWAY) : undefined
- const { data: point } = useSWRImmutable(
- collectionPoint
- ? `/v2/guilds/${collectionPoint.guildId}/guild-platforms/${collectionPoint.guildPlatformId}`
- : null
- )
-
- return (
- <>
- {pin && (
-
-
-
-
-
-
-
-
-
-
- Guild Pin}
- title={`Minted ${guild.name} pin`}
- />
-
-
- )}
- {collectionNft?.data.imageUrl && (
-
-
-
-
-
-
-
-
-
-
- NFT
-
- {shortenHex(collectionNft.data.contractAddress)}
-
- >
- }
- title={collectionNft.data.name ?? "Unknown NFT"}
- href={`/${guild.urlName}/collect/${collectionNft.data.chain}/${collectionNft.data.contractAddress}`}
- description={collectionNft.data.description}
- />
-
-
- )}
- {point && collectionPoint && (
- <>
-
- {point.platformGuildData.imageUrl ? (
- <>
-
-
- >
- ) : (
-
- )}
-
-
-
- {collectionPoint.totalPoints}
- {point.platformGuildData.name}
-
-
- #{collectionPoint.rank}
-
-
- >
- )}
- >
- )
-}
-
-const CollectionItemHoverCardDetails = ({
- badges,
- title,
- href,
- description,
-}: {
- badges: ReactNode
- title: string
- href?: string
- description?: string
-}) => (
-
- {badges}
-
-
-
- {href ? (
-
- {title}
-
-
- ) : (
- title
- )}
-
- {!!description && (
- {description}
- )}
-
-
-)
diff --git a/src/app/(marketing)/profile/_components/EditContributions.tsx b/src/app/(marketing)/profile/_components/EditContributions.tsx
deleted file mode 100644
index deb6524199..0000000000
--- a/src/app/(marketing)/profile/_components/EditContributions.tsx
+++ /dev/null
@@ -1,263 +0,0 @@
-"use client"
-
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/Avatar"
-import { Button } from "@/components/ui/Button"
-import { Card } from "@/components/ui/Card"
-import {
- Dialog,
- DialogBody,
- DialogCloseButton,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/Dialog"
-import { IconButton } from "@/components/ui/IconButton"
-import { Label } from "@/components/ui/Label"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/Select"
-import { useToast } from "@/components/ui/hooks/useToast"
-import { useYourGuilds } from "@/hooks/useYourGuilds"
-import { Guild, MembershipResult, Role, Schemas } from "@guildxyz/types"
-import { WarningCircle, X } from "@phosphor-icons/react"
-import { PencilSimple } from "@phosphor-icons/react"
-import { DialogDescription } from "@radix-ui/react-dialog"
-import { useState } from "react"
-import useSWRImmutable from "swr/immutable"
-import { useContributions } from "../_hooks/useContributions"
-import { useCreateContribution } from "../_hooks/useCreateContribution"
-import { useDeleteContribution } from "../_hooks/useDeleteContribution"
-import { useMemberships } from "../_hooks/useMemberships"
-import { useUpdateContribution } from "../_hooks/useUpdateContribution"
-import { CardWithGuildLabel } from "./CardWithGuildLabel"
-
-const EditContributionCard = ({
- contribution,
-}: { contribution: Schemas["Contribution"] }) => {
- const { data: guild } = useSWRImmutable(
- `/v2/guilds/${contribution.guildId}`
- )
- const memberships = useMemberships()
- const editContribution = useUpdateContribution({ contributionId: contribution.id })
- const deleteContribution = useDeleteContribution({
- contributionId: contribution.id,
- })
- if (!guild || !memberships.data) return
- const roleIds = memberships.data.find(
- (membership) => membership.guildId === guild.id
- )?.roleIds
-
- return (
-
-
- }
- variant="ghost"
- onClick={deleteContribution.onSubmit}
- isLoading={deleteContribution.isLoading}
- className="absolute top-2 right-2 size-7 rounded-full"
- />
-
- TOP ROLE
-
- {
- editContribution.onSubmit({ roleId: parseInt(value), guildId: guild.id })
- }}
- >
-
-
-
-
- {roleIds?.map((roleId) => (
-
- ))}
-
-
-
-
- )
-}
-
-export const EditContributions = () => {
- const contributions = useContributions()
- const memberships = useMemberships()
- const [guildId, setGuildId] = useState("")
- const [roleId, setRoleId] = useState("")
- const { toast } = useToast()
-
- const { data: baseGuilds } = useYourGuilds()
- const guilds = baseGuilds?.filter(({ tags }) => tags.includes("VERIFIED"))
-
- const roleIds = memberships.data?.find(
- (membership) => membership.guildId.toString() === guildId
- )?.roleIds
- const createContribution = useCreateContribution()
-
- return (
-
-
- }
- variant="solid"
- size="sm"
- className="rounded-full"
- />
-
-
-
- Edit top contributions
-
-
-
-
-
- {guilds && guilds.length === 0 && (
-
-
-
- You have no verified guild membership to select from
-
-
- )}
- {contributions.data?.slice(0, 3).map((contribution) => (
-
- ))}
-
-
-
- Add contribution
-
-
-
- Guild
- {
- setGuildId(value)
- setRoleId("")
- }}
- value={guildId}
- >
-
-
-
-
- {guilds?.map((data) => (
-
- ))}
-
-
-
-
- Role
-
-
-
-
-
- {roleIds?.map((roleId) => (
-
- ))}
-
-
-
- {
- if (contributions.data && contributions.data.length >= 3) {
- toast({
- title: "Cannot add more than 3 contributions",
- description: "Please remove one first before adding a new one",
- variant: "error",
- })
- return
- }
- setGuildId("")
- setRoleId("")
- createContribution.onSubmit({
- guildId: parseInt(guildId),
- roleId: parseInt(roleId),
- })
- }}
- >
- Add
-
-
-
-
-
-
- )
-}
-
-const GuildSelectItem = ({ guildId }: Pick) => {
- const { data } = useSWRImmutable(`/v2/guilds/${guildId}`)
- if (!data) return
- return (
-
-
-
- )
-}
-
-const RoleSelectItem = ({
- roleId,
- guildId,
-}: Pick & {
- roleId: MembershipResult["roleIds"][number]
-}) => {
- const { data: data } = useSWRImmutable(
- `/v2/guilds/${guildId}/roles/${roleId}`
- )
- if (!data) return
- return (
-
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/EditProfile/EditProfile.tsx b/src/app/(marketing)/profile/_components/EditProfile/EditProfile.tsx
deleted file mode 100644
index f92b443ae8..0000000000
--- a/src/app/(marketing)/profile/_components/EditProfile/EditProfile.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-"use client"
-
-import { Button } from "@/components/ui/Button"
-import {
- Dialog,
- DialogBody,
- DialogCloseButton,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/Dialog"
-import {
- FormControl,
- FormErrorMessage,
- FormField,
- FormItem,
- FormLabel,
-} from "@/components/ui/Form"
-import { Input } from "@/components/ui/Input"
-import { Separator } from "@/components/ui/Separator"
-import { Switch } from "@/components/ui/Switch"
-import { Textarea } from "@/components/ui/Textarea"
-import { useDisclosure } from "@/hooks/useDisclosure"
-import { filterOnDirtyFormFields } from "@/lib/filterOnDirtyFormFields"
-import { Schemas, schemas } from "@guildxyz/types"
-import { zodResolver } from "@hookform/resolvers/zod"
-import usePinata from "hooks/usePinata"
-import useSubmitWithUpload from "hooks/useSubmitWithUpload"
-import { PropsWithChildren } from "react"
-import { FormProvider, useForm } from "react-hook-form"
-import { useProfile } from "../../_hooks/useProfile"
-import { useUpdateProfile } from "../../_hooks/useUpdateProfile"
-import { EditProfileBanner } from "./EditProfileBanner"
-import { EditProfileDropdown } from "./EditProfileDropdown"
-import { EditProfilePicture } from "./EditProfilePicture"
-
-export const EditProfile = ({ children }: PropsWithChildren) => {
- const { data: profile } = useProfile()
- const form = useForm({
- resolver: zodResolver(schemas.ProfileUpdateSchema),
- defaultValues: {
- ...schemas.ProfileUpdateSchema.parse(profile),
- },
- mode: "onTouched",
- })
- const disclosure = useDisclosure()
- const { onSubmit, isLoading } = useUpdateProfile({ onSuccess: disclosure.onClose })
-
- const profilePicUploader = usePinata({
- control: form.control,
- fieldToSetOnSuccess: "profileImageUrl",
- })
-
- const backgroundUploader = usePinata({
- control: form.control,
- fieldToSetOnSuccess: "backgroundImageUrl",
- })
-
- const { handleSubmit, isUploadingShown, uploadLoadingText } = useSubmitWithUpload(
- form.handleSubmit((params) => {
- onSubmit(
- filterOnDirtyFormFields(params, {
- ...form.formState.dirtyFields,
- showActivityLog: true,
- })
- )
- }),
- profilePicUploader.isUploading || backgroundUploader.isUploading
- )
-
- return (
-
- {children}
-
-
-
- Edit profile
-
-
-
-
-
-
-
-
-
-
- (
-
- Name
-
-
-
-
-
- )}
- />
- (
-
- Username
-
-
-
-
-
- )}
- />
- (
-
- Bio
-
-
-
-
-
- )}
- />
-
-
-
- (
-
-
-
-
-
- Show recent activities
-
-
-
- )}
- />
-
-
-
- Save
-
-
-
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/EditProfile/EditProfileBanner.tsx b/src/app/(marketing)/profile/_components/EditProfile/EditProfileBanner.tsx
deleted file mode 100644
index 2043c9ccec..0000000000
--- a/src/app/(marketing)/profile/_components/EditProfile/EditProfileBanner.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { FormField, FormItem } from "@/components/ui/Form"
-import { IconButton } from "@/components/ui/IconButton"
-import { Separator } from "@/components/ui/Separator"
-import { Eyedropper, Image as ImageIcon } from "@phosphor-icons/react"
-import { Uploader } from "hooks/usePinata/usePinata"
-import { ProfileBackgroundImageUploader } from "./ProfileBackgroundImageUploader"
-import { ProfileColorPicker } from "./ProfileColorPicker"
-
-export const EditProfileBanner = ({
- backgroundUploader,
-}: { backgroundUploader: Uploader }) => (
- {
- const isImage = field.value?.startsWith("http") || field.value?.startsWith("/")
-
- return (
-
-
- {isImage ? (
-
- ) : (
-
- )}
-
-
-
}
- tooltipLabel={isImage ? "Change image" : "Upload image"}
- className="text-white"
- />
-
-
-
- }
- variant="ghost"
- className="text-white"
- />
-
-
-
- )
- }}
- />
-)
diff --git a/src/app/(marketing)/profile/_components/EditProfile/EditProfileDropdown.tsx b/src/app/(marketing)/profile/_components/EditProfile/EditProfileDropdown.tsx
deleted file mode 100644
index 1de014088c..0000000000
--- a/src/app/(marketing)/profile/_components/EditProfile/EditProfileDropdown.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/DropdownMenu"
-import { IconButton } from "@/components/ui/IconButton"
-import { uploadImageUrlToPinata } from "@/lib/uploadImageUrlToPinata"
-import {
- ArrowsClockwise,
- DotsThreeVertical,
- Spinner,
- TrashSimple,
-} from "@phosphor-icons/react"
-import useUser from "components/[guild]/hooks/useUser"
-import { Uploader } from "hooks/usePinata/usePinata"
-import { FunctionComponent } from "react"
-import { useFormContext } from "react-hook-form"
-import { useDeleteProfile } from "../../_hooks/useDeleteProfile"
-
-export const EditProfileDropdown: FunctionComponent<{ uploader: Uploader }> = ({
- uploader,
-}) => {
- const { farcasterProfiles } = useUser()
- const farcasterProfile = farcasterProfiles?.at(0)
- const { setValue } = useFormContext()
- const deleteProfile = useDeleteProfile()
-
- return (
-
-
- }
- variant="ghost"
- size="sm"
- />
-
-
- {farcasterProfile && (
- <>
- {
- if (farcasterProfile.username) {
- setValue("name", farcasterProfile.username, {
- shouldValidate: true,
- })
- }
- if (!farcasterProfile.avatar) return
- uploadImageUrlToPinata({
- onUpload: uploader.onUpload,
- image: new URL(farcasterProfile.avatar),
- })
- }}
- >
- Fill data by Farcaster
-
-
- >
- )}
-
- {
- // keep dropdown open to show loading state
- e.preventDefault()
- deleteProfile.onSubmit()
- }}
- disabled={deleteProfile.isLoading}
- className="gap-2 px-4 py-6 font-semibold text-destructive-subtle-foreground"
- >
- {deleteProfile.isLoading ? (
-
- ) : (
-
- )}
- Delete profile
-
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/EditProfile/EditProfilePicture.tsx b/src/app/(marketing)/profile/_components/EditProfile/EditProfilePicture.tsx
deleted file mode 100644
index 1f7d08d2e3..0000000000
--- a/src/app/(marketing)/profile/_components/EditProfile/EditProfilePicture.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-"use client"
-
-import { Avatar, AvatarFallback } from "@/components/ui/Avatar"
-import { Button } from "@/components/ui/Button"
-import { FormField } from "@/components/ui/Form"
-import { toast } from "@/components/ui/hooks/useToast"
-import { cn } from "@/lib/utils"
-import { Image, Spinner, UploadSimple, User } from "@phosphor-icons/react"
-import { AvatarImage } from "@radix-ui/react-avatar"
-import useDropzone from "hooks/useDropzone"
-import { Uploader } from "hooks/usePinata/usePinata"
-import { useState } from "react"
-
-export const EditProfilePicture = ({
- uploader: { onUpload, isUploading },
- className,
-}: { uploader: Uploader; className?: string }) => {
- const [uploadProgress, setUploadProgress] = useState(0)
-
- const showErrorToast = (description: string) =>
- toast({
- variant: "error",
- title: "Couldn't upload image",
- description,
- })
-
- const { isDragActive, getRootProps, getInputProps } = useDropzone({
- multiple: false,
- noClick: false,
- onDrop: (acceptedFiles, rejectedFiles) => {
- setUploadProgress(0)
- if (acceptedFiles.length > 0) {
- onUpload({ data: [acceptedFiles[0]], onProgress: setUploadProgress })
- }
- if (rejectedFiles.length > 0)
- showErrorToast(rejectedFiles[0].errors[0].message)
- },
- onError: (err) => {
- showErrorToast(err.message)
- },
- })
-
- return (
- (
-
-
-
- {field.value && (
-
- )}
-
-
-
-
-
- {isUploading ? (
- uploadProgress ? (
-
{(uploadProgress * 100).toFixed(0)}%
- ) : (
-
- )
- ) : isDragActive ? (
-
- ) : (
-
- )}
-
-
- )}
- />
- )
-}
diff --git a/src/app/(marketing)/profile/_components/EditProfile/ProfileBackgroundImageUploader.tsx b/src/app/(marketing)/profile/_components/EditProfile/ProfileBackgroundImageUploader.tsx
deleted file mode 100644
index 06b5ba588c..0000000000
--- a/src/app/(marketing)/profile/_components/EditProfile/ProfileBackgroundImageUploader.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { buttonVariants } from "@/components/ui/Button"
-import { IconButtonProps } from "@/components/ui/IconButton"
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/Tooltip"
-import { useToast } from "@/components/ui/hooks/useToast"
-import { cn } from "@/lib/utils"
-import { UploadSimple } from "@phosphor-icons/react"
-import Button from "components/common/Button"
-import useDropzone from "hooks/useDropzone"
-import { Uploader } from "hooks/usePinata/usePinata"
-import { PropsWithChildren, useState } from "react"
-
-type Props = {
- uploader: Uploader
- tooltipLabel: string
-} & Omit
-
-export const ProfileBackgroundImageUploader = ({
- uploader: { isUploading, onUpload },
- children,
- tooltipLabel,
- icon,
- ...buttonProps
-}: PropsWithChildren): JSX.Element => {
- const [progress, setProgress] = useState(0)
- const { toast } = useToast()
- const showErrorToast = (description: string) =>
- toast({
- variant: "error",
- title: "Couldn't upload image",
- description,
- })
-
- const { isDragActive, getRootProps, getInputProps } = useDropzone({
- multiple: false,
- noClick: false,
- onDrop: (accepted, fileRejections) => {
- setProgress(0)
- if (accepted.length > 0) {
- onUpload({ data: [accepted[0]], onProgress: setProgress })
- }
- if (fileRejections.length > 0)
- showErrorToast(fileRejections[0].errors[0].message)
- },
- onError: (err) => {
- showErrorToast(err.message)
- },
- })
-
- if (isUploading)
- return (
-
- {(progress * 100).toFixed(0)}%
-
- )
-
- return (
-
-
-
-
- {isDragActive ? : icon}
-
-
- {tooltipLabel}
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/EditProfile/ProfileColorPicker.tsx b/src/app/(marketing)/profile/_components/EditProfile/ProfileColorPicker.tsx
deleted file mode 100644
index cd0d134c68..0000000000
--- a/src/app/(marketing)/profile/_components/EditProfile/ProfileColorPicker.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { ColorPicker } from "@/components/ui/ColorPicker"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/DropdownMenu"
-import { Eyedropper } from "@phosphor-icons/react"
-import { PropsWithChildren } from "react"
-import { useFormContext, useWatch } from "react-hook-form"
-import getColorByImage from "utils/getColorByImage"
-
-export const ProfileColorPicker = ({ children }: PropsWithChildren) => {
- const { setValue } = useFormContext()
-
- const profileImageUrl = useWatch({ name: "profileImageUrl" })
-
- const setColorByProfilePic = async () => {
- const color = await getColorByImage(profileImageUrl)
- setValue("backgroundImageUrl", color)
- }
-
- return (
-
- {children}
-
-
-
-
-
-
-
- Get color from profile pic
-
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/JoinProfileAction.tsx b/src/app/(marketing)/profile/_components/JoinProfileAction.tsx
deleted file mode 100644
index 7b49791a67..0000000000
--- a/src/app/(marketing)/profile/_components/JoinProfileAction.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-"use client"
-
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import { Anchor } from "@/components/ui/Anchor"
-import { Card } from "@/components/ui/Card"
-import { REFERRER_USER_SEARCH_PARAM_KEY } from "@app/(marketing)/create-profile/(onboarding)/constants"
-import { ArrowRight, Mountains } from "@phosphor-icons/react"
-import useUser from "components/[guild]/hooks/useUser"
-import { useProfile } from "../_hooks/useProfile"
-
-export const JoinProfileAction = () => {
- const profile = useProfile()
- const user = useUser()
- const { isWeb3Connected } = useWeb3ConnectionManager()
- if (
- !profile.data ||
- isWeb3Connected === null ||
- user.guildProfile ||
- user.isLoading
- ) {
- return
- }
- return (
-
-
-
-
-
-
-
- Join{" "}
-
- {profile.data.name || profile.data.username}
- {" "}
- on their adventure
-
-
-
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/LevelBadge.tsx b/src/app/(marketing)/profile/_components/LevelBadge.tsx
deleted file mode 100644
index 9c6aa6466a..0000000000
--- a/src/app/(marketing)/profile/_components/LevelBadge.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Polygon } from "@/components/Polygon"
-import { VariantProps, cva } from "class-variance-authority"
-import { Rank } from "../[username]/types"
-
-const levelBadgeVariants = cva("flex items-center justify-center", {
- variants: {
- size: {
- md: "size-7 text-xs",
- lg: "text-lg md:text-xl size-10 md:size-12",
- },
- },
- defaultVariants: {
- size: "md",
- },
-})
-
-type LevelBadgeProps = {
- level: number
- rank: Rank
- className?: string
-} & VariantProps
-
-export const LevelBadge = ({ rank, level, size, className }: LevelBadgeProps) => {
- return (
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/OperatedGuilds.tsx b/src/app/(marketing)/profile/_components/OperatedGuilds.tsx
deleted file mode 100644
index 1cbee23b58..0000000000
--- a/src/app/(marketing)/profile/_components/OperatedGuilds.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { CheckMark } from "@/components/CheckMark"
-import { Anchor } from "@/components/ui/Anchor"
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/Avatar"
-import { Card } from "@/components/ui/Card"
-import { Skeleton } from "@/components/ui/Skeleton"
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/Tooltip"
-import { Icon } from "@phosphor-icons/react"
-import {
- Calendar,
- FolderSimpleUser,
- Star,
- User,
-} from "@phosphor-icons/react/dist/ssr"
-import useGuild from "components/[guild]/hooks/useGuild"
-import Image from "next/image"
-import { PropsWithChildren } from "react"
-import { GuildBase } from "types"
-import formatRelativeTimeFromNow from "utils/formatRelativeTimeFromNow"
-
-export const OperatedGuild = ({ guildBase }: { guildBase: GuildBase }) => {
- const guild = useGuild(guildBase.id)
- const rewardCount =
- guild.guildPlatforms &&
- guild.guildPlatforms.length +
- (guild?.roles?.reduce((acc, val) => acc + val?.rolePlatforms.length, 0) || 0)
-
- return (
-
-
-
- {guild.theme?.backgroundImage ? (
-
- ) : (
- guild.theme?.color && (
-
- )
- )}
-
-
-
-
-
-
-
-
-
- {guildBase.name}
- {guildBase.tags.includes("VERIFIED") && (
-
- )}
-
-
-
-
-
-
-
- {new Intl.NumberFormat("en-US", {
- notation: "compact",
- maximumFractionDigits: 2,
- }).format(guildBase.memberCount)}
- {" "}
- members
-
-
- {guildBase.rolesCount}
- roles
-
-
- {rewardCount}
- rewards in total
-
- {guild.createdAt && (
-
- Created{" "}
-
-
-
- {formatRelativeTimeFromNow(
- Date.now().valueOf() - new Date(guild.createdAt).valueOf()
- )}
-
-
-
- Created at{" "}
-
- {new Date(guild.createdAt).toLocaleString()}
-
-
- {" "}
- ago
-
- )}
-
-
- )
-}
-
-const OperatedGuildDetail = ({
- Icon,
- children,
-}: PropsWithChildren<{ Icon: Icon }>) => (
-
-
- {children}
-
-)
-
-const EmphasizedData = ({ children }: PropsWithChildren) => (
- {children}
-)
diff --git a/src/app/(marketing)/profile/_components/Profile.tsx b/src/app/(marketing)/profile/_components/Profile.tsx
deleted file mode 100644
index 2e14e1ca53..0000000000
--- a/src/app/(marketing)/profile/_components/Profile.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-"use client"
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import { Card } from "@/components/ui/Card"
-import { ProgressIndicator, ProgressRoot } from "@/components/ui/Progress"
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/Tooltip"
-import { cn } from "@/lib/utils"
-import { Info } from "@phosphor-icons/react"
-import { PropsWithChildren } from "react"
-import useSWRImmutable from "swr/immutable"
-import { GuildBase } from "types"
-import { ContributionCard } from "../_components/ContributionCard"
-import { EditContributions } from "../_components/EditContributions"
-import { ProfileOwnerGuard } from "../_components/ProfileOwnerGuard"
-import { useContributions } from "../_hooks/useContributions"
-import { useExperienceProgression } from "../_hooks/useExperienceProgression"
-import { useProfile } from "../_hooks/useProfile"
-import { useReferredUsers } from "../_hooks/useReferredUsers"
-import { selectOperatedGuilds } from "../_utils/selectOperatedGuilds"
-import { ActivityChart } from "./ActivityChart"
-import { LevelBadge } from "./LevelBadge"
-import { OperatedGuild } from "./OperatedGuilds"
-import { ProfileMainSkeleton } from "./ProfileSkeleton"
-import { RecentActivity } from "./RecentActivity/RecentActivity"
-import RecentActivityFallback from "./RecentActivity/RecentActivityFallback"
-
-export const Profile = () => {
- const { data: profile } = useProfile()
- const { data: contributions } = useContributions()
- const { data: referredUsers } = useReferredUsers()
- const { isWeb3Connected } = useWeb3ConnectionManager()
- const xp = useExperienceProgression()
- let { data: operatedGuilds } = useSWRImmutable(
- profile ? `/v2/guilds?username=${profile.username}` : null
- )
- operatedGuilds = operatedGuilds && selectOperatedGuilds({ guilds: operatedGuilds })
-
- if (!profile || !contributions || !referredUsers || !xp)
- return
-
- return (
- <>
-
-
- Experience
-
-
-
-
-
-
-
{xp.rank.title}
-
-
- {`${xp.experienceCount} / ${xp.nextLevelXp} XP`}
-
-
- {`${xp.currentLevelXp} -> `}
- {xp.experienceCount} XP
- {` -> ${xp.nextLevelXp}`}
-
-
-
-
-
-
-
-
-
-
-
Recent engagement
-
-
-
-
-
- {!!operatedGuilds?.length && (
-
-
Operated guilds
-
- {operatedGuilds.map((guild) => (
-
- ))}
-
-
- )}
-
-
Top contributions
-
-
-
-
-
- {contributions.length === 0 && (
-
-
-
-
- Contributions will appear here
-
-
- This profile doesn't have any contribution yet
-
-
-
- )}
- {contributions.slice(0, 3).map((contribution) => (
-
- ))}
-
- {profile.showActivityLog ? (
-
- Recent activity
- {isWeb3Connected ? : }
-
- ) : (
-
-
-
Recent activity
-
-
-
- Your recent activity is hidden, which can be changed in the{" "}
- Edit profile section. This message is only visible
- for you.
-
-
-
-
- )}
- >
- )
-}
-
-const SectionTitle = ({
- className,
- children,
-}: PropsWithChildren<{ className?: string }>) => (
-
- {children}
-
-)
diff --git a/src/app/(marketing)/profile/_components/ProfileColorBanner.tsx b/src/app/(marketing)/profile/_components/ProfileColorBanner.tsx
deleted file mode 100644
index 654b9b9e85..0000000000
--- a/src/app/(marketing)/profile/_components/ProfileColorBanner.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-"use client"
-
-import { cn } from "@/lib/utils"
-import Color from "color"
-import { useProfile } from "../_hooks/useProfile"
-
-export const ProfileColorBanner = () => {
- const { data: profile } = useProfile()
-
- if (!profile?.backgroundImageUrl?.startsWith("#")) return null
-
- const color = Color(profile.backgroundImageUrl)
- const patternOpacity =
- color.lightness() > 90
- ? "opacity-20"
- : color.lightness() > 75
- ? "opacity-15"
- : color.lightness() > 60
- ? "opacity-10"
- : "opacity-5"
-
- return (
- <>
-
-
- >
- )
-}
diff --git a/src/app/(marketing)/profile/_components/ProfileHero.tsx b/src/app/(marketing)/profile/_components/ProfileHero.tsx
deleted file mode 100644
index d49ad3e445..0000000000
--- a/src/app/(marketing)/profile/_components/ProfileHero.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-"use client"
-
-import { CheckMark } from "@/components/CheckMark"
-import { CircularProgressBar } from "@/components/CircularProgressBar"
-import { LayoutContainer } from "@/components/Layout"
-import { ProfileAvatar } from "@/components/ProfileAvatar"
-import { Avatar } from "@/components/ui/Avatar"
-import { Button } from "@/components/ui/Button"
-import { Card } from "@/components/ui/Card"
-import { PencilSimple } from "@phosphor-icons/react/dist/ssr"
-import { ProfileOwnerGuard } from "../_components/ProfileOwnerGuard"
-import { useExperienceProgression } from "../_hooks/useExperienceProgression"
-import { useProfile } from "../_hooks/useProfile"
-import { EditProfile } from "./EditProfile/EditProfile"
-import { LevelBadge } from "./LevelBadge"
-import { ProfileHeroSkeleton } from "./ProfileSkeleton"
-import { ProfileSocialCounters } from "./ProfileSocialCounters"
-
-export const ProfileHero = () => {
- const { data: profile } = useProfile()
- const xp = useExperienceProgression()
-
- if (!profile || !xp) return
- const { rank, level, progress } = xp
-
- return (
-
-
-
-
-
- }
- className="max-sm:h-8 max-sm:px-3 max-sm:text-sm"
- >
- Edit profile
-
-
-
-
-
-
- {profile.name || profile.username}
-
-
-
@{profile.username}
-
- {profile.bio}
-
-
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/ProfileOwnerGuard.tsx b/src/app/(marketing)/profile/_components/ProfileOwnerGuard.tsx
deleted file mode 100644
index 4128609e61..0000000000
--- a/src/app/(marketing)/profile/_components/ProfileOwnerGuard.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-"use client"
-
-import { useUserPublic } from "@/hooks/useUserPublic"
-import { PropsWithChildren, useMemo } from "react"
-import { useProfile } from "../_hooks/useProfile"
-
-export const ProfileOwnerGuard = ({
- children,
- reverseLogic = false,
-}: PropsWithChildren<{ reverseLogic?: boolean }>) => {
- const { data: profile } = useProfile()
- const { id: publicUserId } = useUserPublic()
- const isProfileOwner = useMemo(
- () => !!profile?.userId && publicUserId === profile.userId,
- [publicUserId]
- )
- if (reverseLogic ? isProfileOwner : !isProfileOwner) return
- return children
-}
diff --git a/src/app/(marketing)/profile/_components/ProfileSkeleton.tsx b/src/app/(marketing)/profile/_components/ProfileSkeleton.tsx
deleted file mode 100644
index cf6a8edede..0000000000
--- a/src/app/(marketing)/profile/_components/ProfileSkeleton.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { Card } from "@/components/ui/Card"
-import { Separator } from "@/components/ui/Separator"
-import { Skeleton } from "@/components/ui/Skeleton"
-
-export const ProfileMainSkeleton = () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
-
-export const ProfileHeroSkeleton = () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
diff --git a/src/app/(marketing)/profile/_components/ProfileSocialCounters.tsx b/src/app/(marketing)/profile/_components/ProfileSocialCounters.tsx
deleted file mode 100644
index 799bcf6823..0000000000
--- a/src/app/(marketing)/profile/_components/ProfileSocialCounters.tsx
+++ /dev/null
@@ -1,220 +0,0 @@
-import FarcasterImage from "@/../static/socialIcons/farcaster.svg"
-import { CopyLink } from "@/components/CopyLink"
-import { ProfileAvatar } from "@/components/ProfileAvatar"
-import { Anchor } from "@/components/ui/Anchor"
-import { Avatar, AvatarFallback } from "@/components/ui/Avatar"
-import { Badge } from "@/components/ui/Badge"
-import {
- Dialog,
- DialogBody,
- DialogCloseButton,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/Dialog"
-import { Separator } from "@/components/ui/Separator"
-import { Skeleton } from "@/components/ui/Skeleton"
-import { cn } from "@/lib/utils"
-import { REFERRER_USER_SEARCH_PARAM_KEY } from "@app/(marketing)/create-profile/(onboarding)/constants"
-import { AvatarImage } from "@radix-ui/react-avatar"
-import { PropsWithChildren } from "react"
-import pluralize from "utils/pluralize"
-import {
- useFarcasterProfile,
- useRelevantFarcasterFollowers,
-} from "../_hooks/useFarcasterProfile"
-import { useProfile } from "../_hooks/useProfile"
-import { useReferredUsers } from "../_hooks/useReferredUsers"
-import { ProfileOwnerGuard } from "./ProfileOwnerGuard"
-
-export const ProfileSocialCounters = ({ className }: any) => {
- const { data: referredUsers } = useReferredUsers()
- const { data: profile } = useProfile()
- const { data: farcasterProfile } = useFarcasterProfile(profile?.userId)
- const { data: relevantFollowers } = useRelevantFarcasterFollowers(
- farcasterProfile?.fid
- )
-
- const inviteLink =
- profile &&
- `https://guild.xyz/create-profile/prompt-referrer?${REFERRER_USER_SEARCH_PARAM_KEY}=${profile.username}`
-
- return (
-
- {referredUsers ? (
-
-
-
-
- Guildmates
-
-
-
-
-
-
- Guildmates
-
- {referredUsers.length}
-
-
-
-
- Profiles created using this referral
-
-
-
- {inviteLink && (
- <>
-
- Share this link and earn XP for each user who joins:
-
-
- >
- )}
-
-
-
-
-
- {referredUsers.length ? (
- referredUsers.map((user) => (
-
-
-
-
-
-
- {user.name || user.username}
-
-
- @{user.username}
-
-
-
- ))
- ) : (
-
- This profile has no guildmates to show yet.
-
- )}
-
-
-
- ) : (
-
- )}
- {farcasterProfile && (
- <>
-
-
-
- Followers
-
-
- {!!relevantFollowers && relevantFollowers?.length >= 1 && (
- <>
-
-
- >
- )}
- >
- )}
-
- )
-}
-
-const SocialCountTile = ({
- count,
- children,
-}: PropsWithChildren<{ count: number }>) => (
-
-)
-
-const RelevantFollowers = ({
- relevantFollowers,
-}: {
- relevantFollowers: ReturnType["data"]
-}) => {
- if (!relevantFollowers || !relevantFollowers.length) {
- throw new Error(
- "Relevant followers must have at least one farcaster profile to display"
- )
- }
- const [firstFc, secondFc] = relevantFollowers
- const remainingFollowers =
- relevantFollowers.length - Math.min(2, relevantFollowers.length)
-
- return (
-
-
- {relevantFollowers.slice(0, 3).map(({ pfp_url, fid, username }) => (
-
-
-
-
-
-
- ))}
-
-
- Followed by{" "}
-
- {firstFc.display_name}
- {secondFc && , }
-
- {secondFc && (
-
- {secondFc.display_name}
-
- )}
- {!!remainingFollowers && ` and ${pluralize(remainingFollowers, "other")}`} on
- Farcaster
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx b/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx
deleted file mode 100644
index b726d751fa..0000000000
--- a/src/app/(marketing)/profile/_components/RecentActivity/ActivityCard.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-"use client"
-
-import { Anchor } from "@/components/ui/Anchor"
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/Avatar"
-import { Badge } from "@/components/ui/Badge"
-import { Card } from "@/components/ui/Card"
-import { cn } from "@/lib/utils"
-import { Guild } from "@guildxyz/types"
-import Color from "color"
-import { ActivityLogActionResponse } from "components/[guild]/activity/ActivityLogContext"
-import { ActivityLogAction } from "components/[guild]/activity/constants"
-import ClientOnly from "components/common/ClientOnly"
-import useSWRWithOptionalAuth from "hooks/useSWRWithOptionalAuth"
-import useSWRImmutable from "swr/immutable"
-import formatRelativeTimeFromNow, {
- MINUTE_IN_MS,
-} from "utils/formatRelativeTimeFromNow"
-import { useProfile } from "../../_hooks/useProfile"
-import { ProfileActionLabel } from "./ProfileActionLabel"
-
-export const ActivityCard = ({ activity }: { activity: ActivityLogAction }) => {
- const { data: guildLatest, error } = useSWRImmutable(
- activity.ids.guild ? `/v2/guilds/${activity.ids.guild}` : null,
- { shouldRetryOnError: false }
- )
- const profile = useProfile()
- const { data: guildFallback } = useSWRWithOptionalAuth(
- activity.ids.guild && error && profile.data
- ? `/v2/audit-log?guildId=${activity.ids.guild}&limit=1&userId=${profile.data.userId}`
- : null
- )
- const guild = guildLatest
- ? { ...guildLatest, ...guildLatest?.theme }
- : guildFallback?.entries.at(0)?.data
- const color = guild?.color && Color(guild.color)
-
- return (
-
- {guild && (
-
-
-
-
-
-
-
- {guild.name}
-
-
-
- )}
-
-
-
-
-
-
-
-
- {(() => {
- const since = Date.now() - parseInt(activity.timestamp)
- const sinceMinutes = since / MINUTE_IN_MS
- return sinceMinutes === 0
- ? "Just now"
- : `${formatRelativeTimeFromNow(since)} ago`
- })()}
-
-
-
-
- {activity.xpAmount && (
-
- +{activity.xpAmount} XP
-
- )}
-
-
- )
-}
diff --git a/src/app/(marketing)/profile/_components/RecentActivity/BadgeSkeleton.tsx b/src/app/(marketing)/profile/_components/RecentActivity/BadgeSkeleton.tsx
deleted file mode 100644
index 6915b95123..0000000000
--- a/src/app/(marketing)/profile/_components/RecentActivity/BadgeSkeleton.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { Skeleton } from "@/components/ui/Skeleton"
-
-export const BadgeSkeleton = () => (
-
-)
diff --git a/src/app/(marketing)/profile/_components/RecentActivity/ProfileActionLabel.tsx b/src/app/(marketing)/profile/_components/RecentActivity/ProfileActionLabel.tsx
deleted file mode 100644
index 4d4915cf42..0000000000
--- a/src/app/(marketing)/profile/_components/RecentActivity/ProfileActionLabel.tsx
+++ /dev/null
@@ -1,228 +0,0 @@
-"use client"
-
-import { Badge } from "@/components/ui/Badge"
-import { Guild, Role } from "@guildxyz/types"
-import { Confetti, Rocket } from "@phosphor-icons/react"
-import { ActivityLogActionResponse } from "components/[guild]/activity/ActivityLogContext"
-import { ACTION, ActivityLogAction } from "components/[guild]/activity/constants"
-import useSWRWithOptionalAuth from "hooks/useSWRWithOptionalAuth"
-import { FunctionComponent } from "react"
-import rewards from "rewards"
-import useSWRImmutable from "swr/immutable"
-import capitalize from "utils/capitalize"
-import { useProfile } from "../../_hooks/useProfile"
-import { BadgeSkeleton } from "./BadgeSkeleton"
-import { RewardBadge } from "./RewardBadge"
-
-const GuildBadge: FunctionComponent<{ guildId?: number }> = ({ guildId }) => {
- const { data: guildLatest, error } = useSWRImmutable(
- guildId ? `/v2/guilds/${guildId}` : null,
- { shouldRetryOnError: false }
- )
- const profile = useProfile()
- const { data: guildFallback } = useSWRWithOptionalAuth(
- guildId && error && profile.data
- ? `/v2/audit-log?guildId=${guildId}&limit=1&userId=${profile.data.userId}`
- : null
- )
- const guild = guildLatest
- ? { ...guildLatest, ...guildLatest?.theme }
- : guildFallback?.entries.at(0)?.data
- if (!guild) {
- return
- }
- return {guild.name}
-}
-
-const RoleBadge: FunctionComponent<{
- roleId?: number
- guildId?: number
-}> = ({ roleId, guildId }) => {
- const { data: role, isLoading } = useSWRImmutable(
- roleId === undefined || guildId === undefined
- ? null
- : `/v2/guilds/${guildId}/roles/${roleId}`
- )
-
- if (isLoading) return
-
- if (!role) {
- return Deleted role
- }
-
- return (
-
-
- {role.name}
-
- )
-}
-
-/** This component could just extend the original ActionLabel used in activity log, overriding actions
- * that we want to display differently, but we've decided to just copy & simplify it for now */
-export const ProfileActionLabel: FunctionComponent<{
- activity: ActivityLogAction
-}> = ({ activity }) => {
- const { action, ids, data, parentId } = activity
- const capitalizedName = capitalize(action)
-
- return (() => {
- switch (action) {
- case ACTION.CreateGuild:
- case ACTION.LeaveGuild:
- return (
- <>
- {capitalizedName}
- {/* */}
- >
- )
- case ACTION.UpdateGuild:
- return (
- <>
- {capitalizedName}
- {/* */}
- >
- )
- case ACTION.DeleteGuild:
- return (
- <>
- {capitalizedName}
- {/* */}
- >
- )
- case ACTION.AddAdmin:
- case ACTION.RemoveAdmin:
- return (
- <>
- {capitalizedName}:
- {/* */}
- >
- )
- case ACTION.CreateRole:
- case ACTION.UpdateRole:
- case ACTION.DeleteRole:
- return (
- <>
- {capitalizedName}
-
- {/* */}
- >
- )
- case ACTION.AddReward:
- case ACTION.RemoveReward:
- case ACTION.UpdateReward:
- return (
- <>
- {capitalizedName}
-
- to role
-
- >
- )
- case ACTION.SendReward:
- case ACTION.RevokeReward:
- return (
- <>
- {capitalizedName}
-
- >
- )
- case ACTION.LoseReward:
- return (
- <>
- {capitalizedName}
-
- >
- )
- case ACTION.GetReward:
- return (
- <>
- {capitalizedName}
-
- >
- )
- case ACTION.JoinGuild:
- return (
- <>
- Join Guild
- {/* */}
- >
- )
- case ACTION.ClickJoinOnPlatform:
- return (
- <>
- {`Join Guild through ${rewards[data.platformName].name}`}
- {/* */}
- >
- )
- case ACTION.UserStatusUpdate:
- case ACTION.OptOut:
- case ACTION.OptIn:
- return (
- <>
- {capitalizedName}
- {/* */}
- >
- )
- case ACTION.GetRole:
- case ACTION.LoseRole:
- return (
- <>
- {capitalizedName}:
-
- >
- )
- case ACTION.AddRequirement:
- case ACTION.UpdateRequirement:
- case ACTION.RemoveRequirement:
- return (
- <>
- {capitalizedName}
- {!parentId && }
- {/* */}
- >
- )
- // @ts-ignore TODO: add and move this to backend
- case "create profile":
- return (
- <>
- {capitalizedName}
-
- @{data?.username}
-
- >
- )
- // @ts-ignore TODO: add and move this to backend
- case "refer profile":
- return (
- <>
- {capitalizedName}
- @{data?.username}
- >
- )
- default:
- return (
- <>
- {capitalizedName}
- {ids.role ? : null}
- >
- )
- }
- })()
-}
diff --git a/src/app/(marketing)/profile/_components/RecentActivity/RecentActivity.tsx b/src/app/(marketing)/profile/_components/RecentActivity/RecentActivity.tsx
deleted file mode 100644
index cc3ba555b4..0000000000
--- a/src/app/(marketing)/profile/_components/RecentActivity/RecentActivity.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-"use client"
-
-import { Card } from "@/components/ui/Card"
-import { Skeleton } from "@/components/ui/Skeleton"
-import { ToggleGroup, ToggleGroupItem } from "@/components/ui/ToggleGroup"
-import { ActivityLogActionResponse } from "components/[guild]/activity/ActivityLogContext"
-import { ACTION } from "components/[guild]/activity/constants"
-import useSWRWithOptionalAuth from "hooks/useSWRWithOptionalAuth"
-import { atom, useAtom } from "jotai"
-import { useState } from "react"
-import { useProfile } from "../../_hooks/useProfile"
-import { ActivityCard } from "./ActivityCard"
-
-const ACTIVITY_FILTERS = ["All", "Editing", "Join", "Rewards"] as const
-const FILTER_ACTIONS: Record<(typeof ACTIVITY_FILTERS)[number], ACTION[]> = {
- All: [
- ACTION.JoinGuild,
- ACTION.LeaveGuild,
- ACTION.CreateGuild,
- ACTION.CreateRole,
- ACTION.DeleteRole,
- ACTION.DeleteGuild,
- ACTION.AddReward,
- ACTION.RemoveReward,
- ACTION.GetReward,
- ACTION.LoseReward,
- ],
- Join: [
- ACTION.JoinGuild,
- ACTION.LeaveGuild,
- // ACTION.CreateProfile,
- // ACTION.ReferProfile,
- ] as const,
- Editing: [
- ACTION.CreateGuild,
- ACTION.CreateRole,
- ACTION.DeleteRole,
- ACTION.DeleteGuild,
- ACTION.AddReward,
- ACTION.RemoveReward,
- ] as const,
- Rewards: [ACTION.GetReward, ACTION.LoseReward] as const,
-}
-
-export const activityValuesAtom = atom(
- null
-)
-
-export const RecentActivity = () => {
- const [activityFilter, setActivityFilter] =
- useState<(typeof ACTIVITY_FILTERS)[number]>("All")
- const [_, setActivityValues] = useAtom(activityValuesAtom)
- const profile = useProfile()
- const searchParams =
- profile.data?.userId &&
- new URLSearchParams([
- ["username", profile.data.username],
- ["userId", profile.data.userId.toString()],
- ["limit", "20"],
- ["offset", "0"],
- ...FILTER_ACTIONS[activityFilter].map((action) => ["action", action]),
- ])
- const auditLog = useSWRWithOptionalAuth(
- searchParams ? `/v2/audit-log?${searchParams}` : null,
- {
- // for some reason, ts errors on the setActivityValues part
- // @ts-ignore
- onSuccess: (data) => setActivityValues(data.values),
- }
- )
-
- return (
- <>
-
- value && setActivityFilter(value as (typeof ACTIVITY_FILTERS)[number])
- }
- value={activityFilter}
- >
- {ACTIVITY_FILTERS.map((filter) => (
-
- {filter}
-
- ))}
-
-
- {auditLog.isLoading ? (
- Array.from({ length: 20 }, (_, i) => (
-
-
-
- ))
- ) : auditLog.data?.entries?.length ? (
- <>
- {auditLog.data.entries.map((activity) => (
-
- ))}
- {auditLog.data?.entries?.length >= 20 && (
-
- … only last 20 actions are shown
-
- )}
- >
- ) : (
-
- No recent actions
-
- )}
-
- >
- )
-}
diff --git a/src/app/(marketing)/profile/_components/RecentActivity/RecentActivityFallback.tsx b/src/app/(marketing)/profile/_components/RecentActivity/RecentActivityFallback.tsx
deleted file mode 100644
index d5dea8f809..0000000000
--- a/src/app/(marketing)/profile/_components/RecentActivity/RecentActivityFallback.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { walletSelectorModalAtom } from "@/components/Providers/atoms"
-import { Button } from "@/components/ui/Button"
-import { Card } from "@/components/ui/Card"
-import { SignIn } from "@phosphor-icons/react"
-import { useSetAtom } from "jotai"
-import { useProfile } from "../../_hooks/useProfile"
-
-const RecentActivityFallback = () => {
- const { data: profile } = useProfile()
- const setIsWalletSelectorModalOpen = useSetAtom(walletSelectorModalAtom)
-
- return (
-
-
- {`Sign in to view `}
-
- {profile?.name ?? profile?.username ?? "the profile"}
-
- 's activity
-
- }
- onClick={() => setIsWalletSelectorModalOpen(true)}
- colorScheme="primary"
- >
- Sign in
-
-
- )
-}
-
-export default RecentActivityFallback
diff --git a/src/app/(marketing)/profile/_components/RecentActivity/RewardBadge.tsx b/src/app/(marketing)/profile/_components/RecentActivity/RewardBadge.tsx
deleted file mode 100644
index 67c1a143a2..0000000000
--- a/src/app/(marketing)/profile/_components/RecentActivity/RewardBadge.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-"use client"
-
-import { Badge } from "@/components/ui/Badge"
-import { useAtomValue } from "jotai"
-import { FunctionComponent } from "react"
-import rewards from "rewards"
-import { PlatformType } from "types"
-import { BadgeSkeleton } from "./BadgeSkeleton"
-import { activityValuesAtom } from "./RecentActivity"
-
-export const RewardBadge: FunctionComponent<{
- guildId?: number
- roleId?: number
- rolePlatformId?: number
-}> = ({ roleId, rolePlatformId }) => {
- const activityValues = useAtomValue(activityValuesAtom)
-
- if (!activityValues) return
-
- const reward = activityValues.rolePlatforms.find((rp) => rp.id === rolePlatformId)
- const role = activityValues.roles.find((r) => r.id === roleId)
-
- const rewardName = reward?.platformGuildName ?? reward?.data?.name
- const name =
- reward?.platformId === PlatformType.DISCORD
- ? (role?.name ?? "Unknown role")
- : rewardName
-
- const Icon = rewards[reward?.platformName]?.icon
- const colorScheme = rewards[reward?.platformName]?.colorScheme
-
- if (!reward) return Deleted reward
-
- return (
-
-
- {name}
-
- )
-}
diff --git a/src/app/(marketing)/profile/_hooks/useContributions.tsx b/src/app/(marketing)/profile/_hooks/useContributions.tsx
deleted file mode 100644
index 6c51947abf..0000000000
--- a/src/app/(marketing)/profile/_hooks/useContributions.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Schemas } from "@guildxyz/types"
-import useSWRImmutable from "swr/immutable"
-import { useProfile } from "./useProfile"
-
-export const useContributions = () => {
- const { data: profileData } = useProfile()
- return useSWRImmutable(
- profileData ? `/v2/profiles/${profileData.username}/contributions` : null
- )
-}
diff --git a/src/app/(marketing)/profile/_hooks/useCreateContribution.tsx b/src/app/(marketing)/profile/_hooks/useCreateContribution.tsx
deleted file mode 100644
index e055fc202b..0000000000
--- a/src/app/(marketing)/profile/_hooks/useCreateContribution.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { useToast } from "@/components/ui/hooks/useToast"
-import { Schemas } from "@guildxyz/types"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import fetcher from "utils/fetcher"
-import { revalidateContributions } from "../_server_actions/revalidateContributions"
-import { useContributions } from "./useContributions"
-import { useProfile } from "./useProfile"
-
-export const useCreateContribution = () => {
- const { toast } = useToast()
- const { data: profile } = useProfile()
- const contributions = useContributions()
-
- if (!profile)
- throw new Error("Tried to create contribution outside profile context")
- const update = async (signedValidation: SignedValidation) => {
- return fetcher(
- `/v2/profiles/${(profile as Schemas["Profile"]).username}/contributions`,
- {
- method: "POST",
- ...signedValidation,
- }
- )
- }
-
- const submitWithSign = useSubmitWithSign(update, {
- onOptimistic: (response, payload) => {
- if (!profile?.userId) return
- contributions.mutate(
- async () => {
- if (!contributions.data) return
- const contribution = await response
- contributions.data[
- contributions.data.findLastIndex(({ id }) => id === -1)
- ] = contribution
- return contributions.data.filter(({ id }) => id !== -1)
- },
- {
- revalidate: false,
- rollbackOnError: true,
- optimisticData: () => {
- const fakeContribution: Schemas["Contribution"] = {
- ...payload,
- id: -1,
- userId: profile.userId,
- }
- if (!contributions.data) return [fakeContribution]
- contributions.data.push(fakeContribution)
- return contributions.data
- },
- }
- )
- },
- onSuccess: () => {
- revalidateContributions({ username: profile.username })
- },
- onError: (response) => {
- toast({
- variant: "error",
- title: "Failed to create contribution",
- description: response.error,
- })
- },
- })
- return {
- ...submitWithSign,
- onSubmit: (payload: Schemas["ContributionUpdate"]) =>
- submitWithSign.onSubmit(payload),
- }
-}
diff --git a/src/app/(marketing)/profile/_hooks/useDeleteContribution.tsx b/src/app/(marketing)/profile/_hooks/useDeleteContribution.tsx
deleted file mode 100644
index 60613d1978..0000000000
--- a/src/app/(marketing)/profile/_hooks/useDeleteContribution.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { useToast } from "@/components/ui/hooks/useToast"
-import { Schemas } from "@guildxyz/types"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import fetcher from "utils/fetcher"
-import { revalidateContributions } from "../_server_actions/revalidateContributions"
-import { useContributions } from "./useContributions"
-import { useProfile } from "./useProfile"
-
-export const useDeleteContribution = ({
- contributionId,
-}: { contributionId: Schemas["Contribution"]["id"] }) => {
- const { toast } = useToast()
- const { data: profile } = useProfile()
- const contributions = useContributions()
-
- if (!profile)
- throw new Error("Tried to delete contribution outside profile context")
- const update = async (signedValidation: SignedValidation) => {
- return fetcher(
- `/v2/profiles/${profile.username}/contributions/${contributionId}`,
- {
- method: "DELETE",
- ...signedValidation,
- }
- )
- }
-
- const submitWithSign = useSubmitWithSign(update, {
- onOptimistic: (response) => {
- contributions.mutate(
- async () => {
- await response
- return contributions.data?.filter((p) => p.id !== contributionId)
- },
- {
- revalidate: false,
- rollbackOnError: true,
- optimisticData: () => {
- if (!contributions.data) return []
- return contributions.data.filter((p) => p.id !== contributionId)
- },
- }
- )
- },
- onSuccess: () => {
- revalidateContributions({ username: profile.username })
- },
- onError: (response) => {
- toast({
- variant: "error",
- title: "Failed to delete contribution",
- description: response.error,
- })
- },
- })
- return {
- ...submitWithSign,
- onSubmit: () => profile && submitWithSign.onSubmit(),
- }
-}
diff --git a/src/app/(marketing)/profile/_hooks/useDeleteProfile.ts b/src/app/(marketing)/profile/_hooks/useDeleteProfile.ts
deleted file mode 100644
index 43aa5f3bf6..0000000000
--- a/src/app/(marketing)/profile/_hooks/useDeleteProfile.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { useToast } from "@/components/ui/hooks/useToast"
-import useUser from "components/[guild]/hooks/useUser"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import { useRouter } from "next/navigation"
-import fetcher from "utils/fetcher"
-import { revalidateProfile } from "../_server_actions/revalidateProfile"
-import { useProfile } from "./useProfile"
-
-export const useDeleteProfile = () => {
- const { toast } = useToast()
- const router = useRouter()
- const { data: profile } = useProfile()
- const user = useUser()
-
- if (!profile) throw new Error("Tried to delete profile outside profile context")
- const submit = async (signedValidation: SignedValidation) => {
- return fetcher(`/v2/profiles/${profile.username}`, {
- method: "DELETE",
- ...signedValidation,
- })
- }
-
- const submitWithSign = useSubmitWithSign(submit, {
- onSuccess: () => {
- console.log("revalidating", profile)
- revalidateProfile({ username: profile.username })
- user.mutate()
- router.replace("/create-profile/prompt-referrer")
- toast({
- variant: "success",
- title: "Successfully deleted profile",
- description: "Redirecting you to the Create profile page",
- })
- },
- onError: (response) => {
- toast({
- variant: "error",
- title: "Failed to delete profile",
- description: response.error,
- })
- },
- })
- return {
- ...submitWithSign,
- onSubmit: () => profile?.username && submitWithSign.onSubmit(),
- }
-}
diff --git a/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts b/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts
deleted file mode 100644
index a030296933..0000000000
--- a/src/app/(marketing)/profile/_hooks/useExperienceProgression.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { MAX_LEVEL, RANKS, XP_SUM } from "../[username]/constants"
-import { useExperiences } from "../_hooks/useExperiences"
-
-const generateExponentialArray = (
- steps: number,
- sum: number,
- exponent: number
-): number[] => {
- const baseSum = (Math.pow(exponent, steps) - 1) / (exponent - 1)
- const scaleFactor = sum / baseSum
- return Array.from({ length: steps }, (_, i) => Math.pow(exponent, i) * scaleFactor)
-}
-
-const calculateXpProgression = ({
- experienceCount,
-}: { experienceCount: number }) => {
- if (MAX_LEVEL < 1) throw new Error(`max level must be positive`)
- const levels = generateExponentialArray(MAX_LEVEL, XP_SUM, 1.008)
- .map((num) => Math.floor(num))
- .map((value, _, arr) => value - arr[0])
- const levelIndex = levels.findIndex((xp) => experienceCount < xp)
- const level = levelIndex === -1 ? MAX_LEVEL : levelIndex
- const currentLevelXp = level > 0 ? levels[level - 1] : 0
- const nextLevelXp = level < MAX_LEVEL ? levels[level] : levels[MAX_LEVEL - 1]
- const progress = Math.min(
- (experienceCount - currentLevelXp) / (nextLevelXp - currentLevelXp),
- 1
- )
- const rank = RANKS[Math.floor(level / (MAX_LEVEL / RANKS.length))]
-
- return { progress, rank, currentLevelXp, nextLevelXp, experienceCount, level }
-}
-
-export const useExperienceProgression = (showOwnProfile?: boolean) => {
- const { data: experienceCount } = useExperiences({ showOwnProfile, count: true })
- return typeof experienceCount === "number"
- ? calculateXpProgression({ experienceCount })
- : undefined
-}
diff --git a/src/app/(marketing)/profile/_hooks/useExperiences.ts b/src/app/(marketing)/profile/_hooks/useExperiences.ts
deleted file mode 100644
index 250ca4d02b..0000000000
--- a/src/app/(marketing)/profile/_hooks/useExperiences.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Schemas } from "@guildxyz/types"
-import useSWRImmutable from "swr/immutable"
-import { useProfile } from "./useProfile"
-
-export const useExperiences = ({
- count,
- showOwnProfile,
- startTime,
-}: { count: T; showOwnProfile?: boolean; startTime?: number }) => {
- const { data: profile } = useProfile(showOwnProfile)
- const params = new URLSearchParams(
- [
- ["count", count && count.toString()],
- ["startTime", startTime && startTime.toString()],
- ].filter(([_, value]) => value) as string[][]
- )
- return useSWRImmutable(
- profile
- ? [
- `/v2/profiles/${profile.username}/experiences`,
- params.size && params.toString(),
- ]
- .filter(Boolean)
- .join("?")
- : null
- )
-}
diff --git a/src/app/(marketing)/profile/_hooks/useFarcasterProfile.tsx b/src/app/(marketing)/profile/_hooks/useFarcasterProfile.tsx
deleted file mode 100644
index 67fc089c31..0000000000
--- a/src/app/(marketing)/profile/_hooks/useFarcasterProfile.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import {
- GetFarcasterRelevantFollowersResponse,
- GetFarcasterUserByFIDResponse,
-} from "@app/api/farcaster/types"
-import { FarcasterProfile } from "@guildxyz/types"
-import useUser from "components/[guild]/hooks/useUser"
-import useSWRImmutable from "swr/immutable"
-
-export const useFarcasterProfile = (guildUserId?: number) => {
- const linkedFcProfile = useSWRImmutable(
- guildUserId ? `/v2/users/${guildUserId}/farcaster-profiles` : null
- ).data?.at(0)
-
- return useSWRImmutable(
- linkedFcProfile ? `/api/farcaster/users/${linkedFcProfile.fid}` : null
- )
-}
-
-export const useRelevantFarcasterFollowers = (farcasterId?: number) => {
- const currentUser = useUser()
- const currentUserFcProfile = currentUser.farcasterProfiles?.at(0)
-
- return useSWRImmutable(
- farcasterId && currentUserFcProfile
- ? `/api/farcaster/relevant-followers/${currentUserFcProfile.fid ?? 1}/${farcasterId}`
- : null
- )
-}
diff --git a/src/app/(marketing)/profile/_hooks/useMemberships.tsx b/src/app/(marketing)/profile/_hooks/useMemberships.tsx
deleted file mode 100644
index e971f58f59..0000000000
--- a/src/app/(marketing)/profile/_hooks/useMemberships.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { useUserPublic } from "@/hooks/useUserPublic"
-import { MembershipResult } from "@guildxyz/types"
-import useSWRImmutable from "swr/immutable"
-
-export const useMemberships = () => {
- const { id: userId } = useUserPublic()
- return useSWRImmutable(
- userId === undefined ? null : `/v2/users/${userId}/memberships`
- )
-}
diff --git a/src/app/(marketing)/profile/_hooks/useProfile.tsx b/src/app/(marketing)/profile/_hooks/useProfile.tsx
deleted file mode 100644
index a4146c1be6..0000000000
--- a/src/app/(marketing)/profile/_hooks/useProfile.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Schemas } from "@guildxyz/types"
-import useUser from "components/[guild]/hooks/useUser"
-import { useParams } from "next/navigation"
-import useSWRImmutable from "swr/immutable"
-
-export const useProfile = (showOwnProfile?: boolean) => {
- const params = useParams<{ username: string }>()
- const user = useUser()
- const username = showOwnProfile ? user.guildProfile?.username : params?.username
- return useSWRImmutable(
- username ? `/v2/profiles/${username}` : null
- )
-}
diff --git a/src/app/(marketing)/profile/_hooks/useReferredUsers.tsx b/src/app/(marketing)/profile/_hooks/useReferredUsers.tsx
deleted file mode 100644
index bad67c0544..0000000000
--- a/src/app/(marketing)/profile/_hooks/useReferredUsers.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Schemas } from "@guildxyz/types"
-import useSWRImmutable from "swr/immutable"
-import { useProfile } from "./useProfile"
-
-export const useReferredUsers = () => {
- const { data: profile } = useProfile()
- return useSWRImmutable(
- profile ? `/v2/profiles/${profile.username}/referred-users` : null
- )
-}
diff --git a/src/app/(marketing)/profile/_hooks/useUpdateContribution.tsx b/src/app/(marketing)/profile/_hooks/useUpdateContribution.tsx
deleted file mode 100644
index 69acbffe7b..0000000000
--- a/src/app/(marketing)/profile/_hooks/useUpdateContribution.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { useToast } from "@/components/ui/hooks/useToast"
-import { Schemas } from "@guildxyz/types"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import fetcher from "utils/fetcher"
-import { revalidateContributions } from "../_server_actions/revalidateContributions"
-import { useContributions } from "./useContributions"
-import { useProfile } from "./useProfile"
-
-export const useUpdateContribution = ({
- contributionId,
-}: { contributionId: Schemas["Contribution"]["id"] }) => {
- const { toast } = useToast()
- const { data: profile } = useProfile()
- const contributions = useContributions()
-
- if (!profile)
- throw new Error("Tried to update contribution outside profile context")
- const update = async (signedValidation: SignedValidation) => {
- return fetcher(
- `/v2/profiles/${profile.username}/contributions/${contributionId}`,
- {
- method: "PUT",
- ...signedValidation,
- }
- )
- }
-
- const submitWithSign = useSubmitWithSign(update, {
- onOptimistic: (response, payload) => {
- if (!profile?.userId) return
- contributions.mutate(
- async () => {
- if (!contributions.data) return
- const contribution = await response
- return contributions.data.map((data) =>
- data.id === contributionId ? contribution : data
- )
- },
- {
- revalidate: false,
- rollbackOnError: true,
- optimisticData: () => {
- if (!contributions.data) return []
- return contributions.data.map((data) =>
- data.id === contributionId
- ? { ...data, ...(payload as Schemas["Contribution"]) }
- : data
- )
- },
- }
- )
- },
- onSuccess: () => {
- revalidateContributions({ username: profile.username })
- },
- onError: (response) => {
- toast({
- variant: "error",
- title: "Failed to update contribution",
- description: response.error,
- })
- },
- })
- return {
- ...submitWithSign,
- onSubmit: (payload: Schemas["ContributionUpdate"]) =>
- submitWithSign.onSubmit(payload),
- }
-}
diff --git a/src/app/(marketing)/profile/_hooks/useUpdateProfile.ts b/src/app/(marketing)/profile/_hooks/useUpdateProfile.ts
deleted file mode 100644
index 799f893c89..0000000000
--- a/src/app/(marketing)/profile/_hooks/useUpdateProfile.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { useToast } from "@/components/ui/hooks/useToast"
-import { Schemas, schemas } from "@guildxyz/types"
-import useUser from "components/[guild]/hooks/useUser"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import { UseSubmitOptions } from "hooks/useSubmit/types"
-import { useRouter } from "next/navigation"
-import fetcher from "utils/fetcher"
-import { revalidateContributions } from "../_server_actions/revalidateContributions"
-import { revalidateProfile } from "../_server_actions/revalidateProfile"
-import { useProfile } from "./useProfile"
-
-export const useUpdateProfile = ({ onSuccess }: UseSubmitOptions) => {
- const { toast } = useToast()
- const router = useRouter()
- const { mutate: mutateProfile, data: profile } = useProfile()
- const user = useUser()
-
- if (!profile) throw new Error("Tried to update profile outside profile context")
- const updateProfile = async (signedValidation: SignedValidation) => {
- return fetcher(`/v2/profiles/${profile.username}`, {
- method: "PUT",
- ...signedValidation,
- })
- }
-
- const submitWithSign = useSubmitWithSign(updateProfile, {
- onOptimistic: (response, payload) => {
- mutateProfile(() => response, {
- revalidate: false,
- rollbackOnError: true,
- optimisticData: () => ({ ...profile, ...payload }),
- })
- },
- onSuccess: async (response) => {
- user.mutate()
- revalidateProfile({ username: profile.username })
- if (profile.username !== response.username) {
- mutateProfile()
- revalidateContributions({ username: profile.username })
- router.replace(`/profile/${response.username}`)
- }
- onSuccess?.()
- },
- onError: (response) => {
- toast({
- variant: "error",
- title: "Failed to update profile",
- description: response.error,
- })
- },
- })
- return {
- ...submitWithSign,
- onSubmit: (payload: Schemas["ProfileUpdate"]) =>
- submitWithSign.onSubmit(schemas.ProfileUpdateSchema.parse(payload)),
- }
-}
diff --git a/src/app/(marketing)/profile/_server_actions/revalidateContributions.ts b/src/app/(marketing)/profile/_server_actions/revalidateContributions.ts
deleted file mode 100644
index 1990c1c60d..0000000000
--- a/src/app/(marketing)/profile/_server_actions/revalidateContributions.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-"use server"
-
-import { Schemas } from "@guildxyz/types"
-import { revalidateTag } from "next/cache"
-
-export async function revalidateContributions({
- username,
-}: Pick) {
- revalidateTag(`/v2/profiles/${username}/contributions`)
-}
diff --git a/src/app/(marketing)/profile/_server_actions/revalidateProfile.ts b/src/app/(marketing)/profile/_server_actions/revalidateProfile.ts
deleted file mode 100644
index 9d53f65d72..0000000000
--- a/src/app/(marketing)/profile/_server_actions/revalidateProfile.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-"use server"
-
-import { Schemas } from "@guildxyz/types"
-import { revalidateTag } from "next/cache"
-
-export const revalidateProfile = async ({
- username,
-}: Pick) => {
- revalidateTag(`/v2/profiles/${username}`)
-}
diff --git a/src/app/(marketing)/profile/_utils/selectOperatedGuilds.ts b/src/app/(marketing)/profile/_utils/selectOperatedGuilds.ts
deleted file mode 100644
index 924816194a..0000000000
--- a/src/app/(marketing)/profile/_utils/selectOperatedGuilds.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { GuildBase } from "types"
-
-export const selectOperatedGuilds = ({
- guilds,
-}: { guilds: GuildBase[] }): GuildBase[] => {
- return guilds
- .filter((guild) => guild.isAdmin && guild.tags.includes("VERIFIED"))
- .sort((a, b) => b.memberCount - a.memberCount)
- .slice(0, 2)
-}
diff --git a/src/app/api/farcaster/casts/route.ts b/src/app/api/farcaster/casts/route.ts
deleted file mode 100644
index eb04dba21d..0000000000
--- a/src/app/api/farcaster/casts/route.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-export const dynamic = "force-dynamic"
-
-import { NextRequest } from "next/server"
-import { neynarAPIClient } from "../neynarAPIClient"
-import { GetFarcasterCastResponse } from "../types"
-
-export async function GET(request: NextRequest) {
- const type = request.nextUrl.searchParams.get("type")
-
- if (type !== "hash" && type !== "url") {
- return Response.json(
- {
- error: "Invalid param: type",
- },
- {
- status: 400,
- }
- )
- }
-
- const identifier = request.nextUrl.searchParams.get("identifier")
-
- if (
- !identifier ||
- (!identifier.startsWith("http") && !identifier.startsWith("0x"))
- ) {
- return Response.json(
- {
- error: "Invalid param: identifier",
- },
- {
- status: 400,
- }
- )
- }
-
- let castResponse
-
- try {
- castResponse = await neynarAPIClient.lookUpCastByHashOrWarpcastUrl(
- identifier,
- type
- )
- } catch {
- return Response.json(
- {
- error: "Cast not found",
- },
- {
- status: 404,
- headers: {
- "Cache-Control": "s-maxage=300", // 5 minutes
- },
- }
- )
- }
-
- const {
- hash,
- timestamp,
- author: { pfp_url, username, display_name },
- reactions: { likes_count, recasts_count },
- replies: { count: replies_count },
- } = castResponse.cast
-
- return Response.json(
- {
- hash,
- timestamp,
- author: {
- pfp_url,
- username,
- display_name,
- },
- likes_count,
- recasts_count,
- replies_count,
- } satisfies GetFarcasterCastResponse,
- {
- headers: {
- "Cache-Control": "s-maxage=3600", // 1 hour
- },
- }
- )
-}
diff --git a/src/app/api/farcaster/channels/[id]/route.ts b/src/app/api/farcaster/channels/[id]/route.ts
deleted file mode 100644
index f52747357a..0000000000
--- a/src/app/api/farcaster/channels/[id]/route.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { neynarAPIClient } from "../../neynarAPIClient"
-
-export const dynamic = "force-dynamic"
-
-export async function GET(_: Request, { params }: { params: { id: string } }) {
- if (!params.id)
- return Response.json(
- {
- error: "Missing param: id",
- },
- {
- status: 400,
- }
- )
-
- let channel
-
- try {
- channel = await neynarAPIClient.lookupChannel(params.id)
- } catch {
- return Response.json(
- {
- error: "Channel not found",
- },
- {
- status: 404,
- headers: {
- "Cache-Control": "s-maxage=300", // 5 minutes
- },
- }
- )
- }
-
- const { id, name, image_url } = channel.channel
-
- return Response.json(
- { id, name, image_url },
- {
- headers: {
- "Cache-Control": "s-maxage=3600", // 1 hour
- },
- }
- )
-}
diff --git a/src/app/api/farcaster/channels/search/route.ts b/src/app/api/farcaster/channels/search/route.ts
deleted file mode 100644
index 9060e31ddf..0000000000
--- a/src/app/api/farcaster/channels/search/route.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-export const dynamic = "force-dynamic"
-
-import { NextRequest } from "next/server"
-import { neynarAPIClient } from "../../neynarAPIClient"
-import { SearchFarcasterChannelsResponse } from "../../types"
-
-export async function GET(request: NextRequest) {
- const q = request.nextUrl.searchParams.get("q")
-
- if (!q) {
- return Response.json(
- {
- error: "Invalid param: q",
- },
- {
- status: 400,
- }
- )
- }
-
- let channelsResponse
-
- try {
- channelsResponse = await neynarAPIClient.searchChannels(q, {
- limit: 10,
- })
- } catch {
- return Response.json(
- {
- error: "Couldn't find channels",
- },
- {
- status: 404,
- headers: {
- "Cache-Control": "s-maxage=300", // 5 minutes
- },
- }
- )
- }
-
- return Response.json(
- channelsResponse.channels.map(({ id, name, image_url }) => ({
- id,
- name,
- image_url,
- })) satisfies SearchFarcasterChannelsResponse,
- {
- headers: {
- "Cache-Control": "s-maxage=3600", // 1 hour
- },
- }
- )
-}
diff --git a/src/app/api/farcaster/neynarAPIClient.ts b/src/app/api/farcaster/neynarAPIClient.ts
deleted file mode 100644
index 032883d001..0000000000
--- a/src/app/api/farcaster/neynarAPIClient.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { NeynarAPIClient } from "@neynar/nodejs-sdk"
-import { env } from "env"
-
-export const neynarAPIClient = new NeynarAPIClient(env.NEYNAR_API_KEY)
diff --git a/src/app/api/farcaster/relevant-followers/[viewerfid]/[targetfid]/route.ts b/src/app/api/farcaster/relevant-followers/[viewerfid]/[targetfid]/route.ts
deleted file mode 100644
index 5c2403792f..0000000000
--- a/src/app/api/farcaster/relevant-followers/[viewerfid]/[targetfid]/route.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-export const dynamic = "force-dynamic"
-
-import { GetFarcasterRelevantFollowersResponse } from "@app/api/farcaster/types"
-import { neynarAPIClient } from "../../../neynarAPIClient"
-
-export async function GET(
- _: Request,
- { params }: { params: { viewerfid: string; targetfid: string } }
-) {
- if (!params.viewerfid)
- return Response.json(
- {
- error: "Missing param: viewerfid",
- },
- {
- status: 400,
- }
- )
-
- if (!params.targetfid)
- return Response.json(
- {
- error: "Missing param: targetfid",
- },
- {
- status: 400,
- }
- )
-
- let relevantFollowers
-
- try {
- relevantFollowers = await neynarAPIClient.fetchRelevantFollowers(
- +params.targetfid,
- +params.viewerfid
- )
- } catch {
- return Response.json(
- {
- error: "Not found",
- },
- {
- status: 404,
- headers: {
- "Cache-Control": "s-maxage=300", // 5 minutes
- },
- }
- )
- }
-
- const mappedResponse = relevantFollowers.top_relevant_followers_hydrated
- .filter((relevantFollower) => !!relevantFollower.user)
- .map((relevantFollower) => {
- if (!relevantFollower.user)
- return null as unknown as GetFarcasterRelevantFollowersResponse
- return {
- fid: relevantFollower.user.fid,
- username: relevantFollower.user.username,
- display_name: relevantFollower.user.display_name,
- pfp_url: relevantFollower.user.pfp_url,
- }
- })
- .filter(Boolean) as GetFarcasterRelevantFollowersResponse
-
- return Response.json(mappedResponse, {
- headers: {
- "Cache-Control": "s-maxage=3600", // 1 hour
- },
- })
-}
diff --git a/src/app/api/farcaster/types.ts b/src/app/api/farcaster/types.ts
deleted file mode 100644
index 9485ef89fe..0000000000
--- a/src/app/api/farcaster/types.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { NeynarAPIClient } from "@neynar/nodejs-sdk"
-
-type GetFarcasterUserByFIDResponse = Pick<
- Awaited>["users"][number],
- "fid" | "username" | "display_name" | "pfp_url"
-> & {
- follower_count: number // To make sure it's required
-}
-
-type SearchFarcasterUsersResponse = GetFarcasterUserByFIDResponse[]
-
-type GetFarcasterRelevantFollowersResponse = Pick<
- NonNullable<
- Awaited<
- ReturnType
- >["top_relevant_followers_hydrated"][number]["user"]
- >,
- "fid" | "username" | "display_name" | "pfp_url"
->[]
-
-type GetFarcasterCastRawResponse = NonNullable<
- Awaited>["cast"]
->
-type GetFarcasterCastResponse = {
- hash: GetFarcasterCastRawResponse["hash"]
- timestamp: GetFarcasterCastRawResponse["timestamp"]
- author: {
- pfp_url: GetFarcasterCastRawResponse["author"]["pfp_url"]
- username: GetFarcasterCastRawResponse["author"]["username"]
- display_name?: GetFarcasterCastRawResponse["author"]["display_name"]
- }
- likes_count: GetFarcasterCastRawResponse["reactions"]["likes_count"]
- recasts_count: GetFarcasterCastRawResponse["reactions"]["recasts_count"]
- replies_count: GetFarcasterCastRawResponse["replies"]["count"]
-}
-
-type GetFarcasterChannelResponse = Pick<
- NonNullable>>["channel"],
- "id" | "name" | "image_url"
->
-
-type SearchFarcasterChannelsResponse = GetFarcasterChannelResponse[]
-
-export type {
- GetFarcasterUserByFIDResponse,
- SearchFarcasterUsersResponse,
- GetFarcasterRelevantFollowersResponse,
- GetFarcasterCastResponse,
- GetFarcasterChannelResponse,
- SearchFarcasterChannelsResponse,
-}
diff --git a/src/app/api/farcaster/users/[fid]/route.ts b/src/app/api/farcaster/users/[fid]/route.ts
deleted file mode 100644
index ee3c6b40ab..0000000000
--- a/src/app/api/farcaster/users/[fid]/route.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-export const dynamic = "force-dynamic"
-
-import { neynarAPIClient } from "../../neynarAPIClient"
-import { GetFarcasterUserByFIDResponse } from "../../types"
-
-export async function GET(_: Request, { params }: { params: { fid: string } }) {
- if (!params.fid)
- return Response.json(
- {
- error: "Missing param: fid",
- },
- {
- status: 400,
- }
- )
-
- let user
-
- try {
- user = await neynarAPIClient
- .fetchBulkUsers([+params.fid])
- .then((res) => res.users[0])
- } catch {
- return Response.json(
- {
- error: "User not found",
- },
- {
- status: 404,
- headers: {
- "Cache-Control": "s-maxage=300", // 5 minutes
- },
- }
- )
- }
-
- const { fid, username, display_name, pfp_url, follower_count } = user
-
- return Response.json(
- {
- fid,
- username,
- display_name,
- pfp_url,
- follower_count,
- } satisfies GetFarcasterUserByFIDResponse,
- {
- headers: {
- "Cache-Control": "s-maxage=3600", // 1 hour
- },
- }
- )
-}
diff --git a/src/app/api/farcaster/users/search/route.ts b/src/app/api/farcaster/users/search/route.ts
deleted file mode 100644
index fc91bfa8f5..0000000000
--- a/src/app/api/farcaster/users/search/route.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-export const dynamic = "force-dynamic"
-
-import { NextRequest } from "next/server"
-import { neynarAPIClient } from "../../neynarAPIClient"
-import { SearchFarcasterUsersResponse } from "../../types"
-
-export async function GET(request: NextRequest) {
- const q = request.nextUrl.searchParams.get("q")
-
- if (!q) {
- return Response.json(
- {
- error: "Invalid param: q",
- },
- {
- status: 400,
- }
- )
- }
-
- let usersResponse
-
- try {
- usersResponse = await neynarAPIClient.searchUser(q, undefined, {
- limit: 10,
- })
- } catch {
- return Response.json(
- {
- error: "Couldn't find users",
- },
- {
- status: 404,
- headers: {
- "Cache-Control": "s-maxage=300", // 5 minutes
- },
- }
- )
- }
-
- return Response.json(
- usersResponse.result.users.map(
- ({ fid, username, display_name, pfp_url, follower_count }) => ({
- fid,
- username,
- display_name,
- pfp_url,
- follower_count,
- })
- ) satisfies SearchFarcasterUsersResponse,
- {
- headers: {
- "Cache-Control": "s-maxage=3600", // 1 hour
- },
- }
- )
-}
diff --git a/src/app/api/ip-geodata/route.ts b/src/app/api/ip-geodata/route.ts
deleted file mode 100644
index 87a586a52c..0000000000
--- a/src/app/api/ip-geodata/route.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export const dynamic = "force-dynamic"
-
-export type IpGeodata = { country: any }
-
-export async function GET(request: Request) {
- return Response.json({
- country: request.headers.get("x-vercel-ip-country"),
- } satisfies IpGeodata)
-}
diff --git a/src/app/api/leaderboard/route.ts b/src/app/api/leaderboard/route.ts
deleted file mode 100644
index e362375e43..0000000000
--- a/src/app/api/leaderboard/route.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { GetLeaderboardResponse } from "@guildxyz/types"
-import { env } from "env"
-import { NextRequest, NextResponse } from "next/server"
-import z from "zod"
-
-export const dynamic = "force-dynamic"
-
-const convertLeaderboardToCsv = ({
- leaderboard,
-}: GetLeaderboardResponse): string => {
- if (leaderboard.length === 0) return ""
- const headers = Object.keys(leaderboard[0]).join(",") + "\n"
- const rows = leaderboard.map((item) => Object.values(item).join(",")).join("\n")
- return headers + rows
-}
-
-const searchParamsSchema = z.object({
- guildId: z.coerce.number().positive(),
- pointsId: z.coerce.number().positive(),
-})
-
-export async function GET(req: NextRequest) {
- try {
- const { guildId, pointsId } = searchParamsSchema.parse(
- Object.fromEntries(req.nextUrl.searchParams)
- )
- const leaderboardRequest = new URL(
- `/v2/guilds/${guildId}/points/${pointsId}/leaderboard?forceRecalculate=true&isAllUser=true`,
- env.NEXT_PUBLIC_API
- )
-
- const jsonResponse = await fetch(leaderboardRequest, {
- cache: "no-store",
- next: { revalidate: 0 },
- })
- const jsonData = await jsonResponse.json()
- if (!jsonResponse.ok) {
- throw new Error(JSON.stringify(jsonData))
- }
-
- const csvData = convertLeaderboardToCsv(jsonData)
-
- return new Response(csvData, {
- status: 200,
- headers: {
- "Content-Disposition": `attachment; filename="leaderboard-${guildId}-${pointsId}.csv"`,
- "Content-Type": "text/csv",
- },
- })
- } catch (error) {
- return NextResponse.json({ message: error }, { status: 500 })
- }
-}
diff --git a/src/app/api/link-metadata/route.ts b/src/app/api/link-metadata/route.ts
deleted file mode 100644
index 5de51690c8..0000000000
--- a/src/app/api/link-metadata/route.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { type NextRequest } from "next/server"
-import { LinkMetadata } from "./types"
-
-export async function GET(request: NextRequest) {
- const url = request.nextUrl.searchParams.get("url")
-
- if (!url) {
- return Response.json(
- { error: "Missing required query param: URL" } satisfies LinkMetadata,
- {
- status: 400,
- }
- )
- }
-
- const html = await fetch(url.toString()).then((res) => res.text())
- const [, , title] = new RegExp(/(.*)<\/title>/gi).exec(html) ?? []
-
- if (!title) {
- return Response.json({ error: "Not found" } satisfies LinkMetadata, {
- status: 404,
- })
- }
-
- // Cache the response for 5 minutes
- return Response.json({ title } satisfies LinkMetadata, {
- headers: {
- "Cache-Control": "s-maxage=300",
- },
- })
-}
diff --git a/src/app/api/link-metadata/types.ts b/src/app/api/link-metadata/types.ts
deleted file mode 100644
index eb88dcddee..0000000000
--- a/src/app/api/link-metadata/types.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { OneOf } from "types"
-
-export type LinkMetadata = OneOf<
- {
- title: string
- },
- { error: string }
->
diff --git a/src/app/api/sitemap.xml/route.ts b/src/app/api/sitemap.xml/route.ts
deleted file mode 100644
index cabb66e193..0000000000
--- a/src/app/api/sitemap.xml/route.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { env } from "env"
-import { GuildBase } from "types"
-
-export async function GET() {
- const baseUrl = {
- development: "http://localhost:3000",
- test: "http://localhost:3000",
- production: "https://guild.xyz",
- }[process.env.NODE_ENV]
-
- const guilds: GuildBase[] = await fetch(
- `${env.NEXT_PUBLIC_API.replace("v1", "v2")}/guilds?sort=members&limit=1000`
- )
- .then((res) => res.json())
- .catch((_) => [])
-
- const sitemap = `
-
- ${guilds
- .map(
- (guild) => `
-
- ${baseUrl}/${guild.urlName}
- weekly
- 1.0
-
- `
- )
- .join("")}
-
- `
-
- return new Response(sitemap, {
- headers: {
- "Content-Type": "text/xml",
- },
- })
-}
diff --git a/src/app/api/timestamp/route.ts b/src/app/api/timestamp/route.ts
deleted file mode 100644
index 06d130308c..0000000000
--- a/src/app/api/timestamp/route.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export const dynamic = "force-dynamic"
-
-export async function GET() {
- return Response.json(Date.now().toString())
-}
diff --git a/src/app/create-guild/_components/CreateGuildButton.tsx b/src/app/create-guild/_components/CreateGuildButton.tsx
deleted file mode 100644
index bc689b3f71..0000000000
--- a/src/app/create-guild/_components/CreateGuildButton.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-"use client"
-
-import { walletSelectorModalAtom } from "@/components/Providers/atoms"
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import { Button } from "@/components/ui/Button"
-import { Collapsible, CollapsibleContent } from "@/components/ui/Collapsible"
-import { useSetAtom } from "jotai"
-import { useFormContext } from "react-hook-form"
-import { useCreateGuild } from "../_hooks/useCreateGuild"
-import { CreateGuildFormType } from "../types"
-
-const CreateGuildButton = () => {
- const { handleSubmit } = useFormContext()
- const { onSubmit, isLoading } = useCreateGuild()
-
- const { isWeb3Connected } = useWeb3ConnectionManager()
- const setIsWalletSelectorModalOpen = useSetAtom(walletSelectorModalAtom)
-
- return (
-
-
-
- setIsWalletSelectorModalOpen(true)}
- className="w-full"
- >
- Connect wallet
-
-
-
-
-
- Create guild
-
-
- )
-}
-
-export { CreateGuildButton }
diff --git a/src/app/create-guild/_components/CreateGuildCard.tsx b/src/app/create-guild/_components/CreateGuildCard.tsx
deleted file mode 100644
index 762ed68654..0000000000
--- a/src/app/create-guild/_components/CreateGuildCard.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"use client"
-
-import { Card } from "@/components/ui/Card"
-import {
- FormControl,
- FormErrorMessage,
- FormField,
- FormItem,
- FormLabel,
-} from "@/components/ui/Form"
-import { Input } from "@/components/ui/Input"
-import { useFormContext } from "react-hook-form"
-import { CreateGuildFormType } from "../types"
-import { CreateGuildButton } from "./CreateGuildButton"
-import { CreateGuildImageUploader } from "./CreateGuildImageUploader"
-import { EmailFormField } from "./EmailFormField"
-
-const CreateGuildCard = () => {
- const { control } = useFormContext()
-
- return (
-
-
- Begin your guild
-
-
-
-
-
- (
-
- Guild name
-
-
-
-
-
-
- )}
- />
-
-
-
-
-
-
- )
-}
-
-export { CreateGuildCard }
diff --git a/src/app/create-guild/_components/CreateGuildFormProvider.tsx b/src/app/create-guild/_components/CreateGuildFormProvider.tsx
deleted file mode 100644
index 56cd60d9d9..0000000000
--- a/src/app/create-guild/_components/CreateGuildFormProvider.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-"use client"
-
-import { schemas } from "@guildxyz/types"
-import { zodResolver } from "@hookform/resolvers/zod"
-import { PropsWithChildren } from "react"
-import { FormProvider, useForm } from "react-hook-form"
-import getRandomInt from "utils/getRandomInt"
-import { CreateGuildFormType } from "../types"
-
-const defaultValues = {
- name: "",
- imageUrl: "",
- contacts: [
- {
- type: "EMAIL",
- contact: "",
- },
- ],
- /**
- * We need to define these values so the Zod resolver won't throw errors, but we'll actually overwrite the urlName with a proper value in the `useCreateGuild` hook
- *
- * Temporarily creating a default Member role, later the users will be able to pick from Guild Templates
- */
- urlName: "",
- roles: [
- {
- name: "Member",
- imageUrl: `/guildLogos/${getRandomInt(286)}.svg`,
- requirements: [
- {
- type: "FREE",
- },
- ],
- },
- ],
-} satisfies CreateGuildFormType
-
-const CreateGuildFormProvider = ({ children }: PropsWithChildren) => {
- const methods = useForm({
- mode: "all",
- resolver: zodResolver(schemas.GuildCreationPayloadSchema),
- defaultValues,
- })
-
- return {children}
-}
-
-export { CreateGuildFormProvider }
diff --git a/src/app/create-guild/_components/CreateGuildImageUploader.tsx b/src/app/create-guild/_components/CreateGuildImageUploader.tsx
deleted file mode 100644
index 18acebbfc8..0000000000
--- a/src/app/create-guild/_components/CreateGuildImageUploader.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { Button } from "@/components/ui/Button"
-import { FormControl, FormErrorMessage, FormItem } from "@/components/ui/Form"
-import { Image, Spinner, UploadSimple } from "@phosphor-icons/react/dist/ssr"
-import {
- convertFilesFromEvent,
- getWidthAndHeightFromFile,
- imageDimensionsValidator,
-} from "components/create-guild/IconSelector/utils"
-import useDropzone, { ERROR_MESSAGES } from "hooks/useDropzone"
-import usePinata from "hooks/usePinata"
-import { useState } from "react"
-import { useFormContext, useWatch } from "react-hook-form"
-import { CreateGuildFormType } from "../types"
-
-const MIN_WIDTH = 512
-const MIN_HEIGHT = 512
-
-/**
- * This is a pretty specific component right now, but we should generalise it once we start using it in other places too (e.g. on the create profile page)
- */
-const CreateGuildImageUploader = () => {
- const { control } = useFormContext()
- const imageUrl = useWatch({ control, name: "imageUrl" })
-
- const [placeholder, setPlaceholder] = useState(null)
- const { fileRejections, getRootProps, getInputProps, isDragActive } = useDropzone({
- multiple: false,
- noClick: false,
- // We need to use any here unfortunately, but this is the correct usage according to the react-dropzone source code
- getFilesFromEvent: async (event: any) => {
- const filesFromEvent = await convertFilesFromEvent(event)
-
- const filePromises = []
-
- for (const file of filesFromEvent) {
- filePromises.push(
- new Promise(async (resolve) => {
- if (file.type.includes("svg")) {
- resolve(file)
- } else {
- const { width, height } = await getWidthAndHeightFromFile(file)
- Object.defineProperty(file, "width", { value: width })
- Object.defineProperty(file, "height", { value: height })
- resolve(file)
- }
- })
- )
- }
-
- const files = await Promise.all(filePromises)
- return files
- },
- validator: (file) =>
- MIN_WIDTH || MIN_HEIGHT
- ? imageDimensionsValidator(file, MIN_WIDTH ?? 0, MIN_HEIGHT ?? 0)
- : null,
- onDrop: (accepted) => {
- if (accepted.length > 0) {
- const generatedBlob = URL.createObjectURL(accepted[0])
- setPlaceholder(generatedBlob)
- onUpload({ data: [accepted[0]] })
- }
- },
- })
-
- const fileRejectionError = fileRejections?.[0]?.errors?.[0]
-
- const { onUpload, isUploading } = usePinata({
- control,
- fieldToSetOnError: "imageUrl",
- fieldToSetOnSuccess: "imageUrl",
- })
-
- return (
-
-
-
-
- {isUploading ? (
- <>
- {placeholder && (
-
-
-
- )}
-
- >
- ) : imageUrl ? (
-
- ) : isDragActive ? (
-
- ) : (
-
- )}
-
-
-
-
- {fileRejectionError?.code in ERROR_MESSAGES
- ? ERROR_MESSAGES[fileRejectionError.code as keyof typeof ERROR_MESSAGES]
- : fileRejectionError?.message}
-
-
- )
-}
-
-export { CreateGuildImageUploader }
diff --git a/src/app/create-guild/_components/EmailFormField.tsx b/src/app/create-guild/_components/EmailFormField.tsx
deleted file mode 100644
index 01f780436c..0000000000
--- a/src/app/create-guild/_components/EmailFormField.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import {
- FormControl,
- FormErrorMessage,
- FormField,
- FormItem,
- FormLabel,
-} from "@/components/ui/Form"
-import { Input } from "@/components/ui/Input"
-import useUser from "components/[guild]/hooks/useUser"
-import { useEffect } from "react"
-import { useFormContext, useWatch } from "react-hook-form"
-import { CreateGuildFormType } from "../types"
-
-const EmailFormField = () => {
- const {
- control,
- setValue,
- formState: { touchedFields },
- } = useFormContext()
- const { emails, platformUsers } = useUser()
-
- const providedEmail = useWatch({ control, name: "contacts.0.contact" })
- useEffect(() => {
- if (!!providedEmail || touchedFields.contacts?.[0]?.contact) return
-
- const emailAddress = emails?.emailAddress
- const googleEmailAddress = platformUsers?.find(
- (pu) => pu.platformName === "GOOGLE"
- )?.platformUserId
-
- if (!emailAddress && !googleEmailAddress) return
-
- setValue("contacts.0.contact", emailAddress ?? googleEmailAddress)
- }, [touchedFields.contacts, emails, platformUsers, providedEmail, setValue])
-
- return (
- (
-
- Your e-mail
-
-
-
-
-
-
- )}
- />
- )
-}
-
-export { EmailFormField }
diff --git a/src/app/create-guild/_hooks/useCreateGuild.ts b/src/app/create-guild/_hooks/useCreateGuild.ts
deleted file mode 100644
index 85f01f0c81..0000000000
--- a/src/app/create-guild/_hooks/useCreateGuild.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import { useConfetti } from "@/components/Confetti"
-import { usePostHogContext } from "@/components/Providers/PostHogProvider"
-import { useErrorToast } from "@/components/ui/hooks/useErrorToast"
-import { useToast } from "@/components/ui/hooks/useToast"
-import { useYourGuilds } from "@/hooks/useYourGuilds"
-import { Schemas } from "@guildxyz/types"
-import { processConnectorError } from "components/[guild]/JoinModal/utils/processConnectorError"
-import useMatchMutate from "hooks/useMatchMutate"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import { useRouter } from "next/navigation"
-import { Guild, GuildBase } from "types"
-import fetcher from "utils/fetcher"
-import getRandomInt from "utils/getRandomInt"
-import slugify from "utils/slugify"
-import { CreateGuildFormType } from "../types"
-
-const useCreateGuild = ({
- onError,
- onSuccess,
-}: {
- onError?: (err: unknown) => void
- onSuccess?: () => void
-} = {}) => {
- const { captureEvent } = usePostHogContext()
-
- const { mutate: mutateYourGuilds } = useYourGuilds()
- const matchMutate = useMatchMutate()
-
- const { toast } = useToast()
- const errorToast = useErrorToast()
- const { confettiPlayer } = useConfetti()
- const router = useRouter()
-
- const fetchData = async (signedValidation: SignedValidation): Promise =>
- fetcher("/v2/guilds", signedValidation)
-
- const useSubmitResponse = useSubmitWithSign(fetchData, {
- onError: (error_) => {
- errorToast({
- error: processConnectorError(error_.error) ?? error_.error,
- correlationId: error_.correlationId,
- })
- onError?.(error_)
- },
- onSuccess: (response_) => {
- confettiPlayer.current("Confetti from left and right")
-
- captureEvent("Created guild", {
- $set: {
- createdGuild: true,
- },
- })
-
- mutateYourGuilds((prev) => mutateGuildsCache(prev, response_), {
- revalidate: false,
- })
- matchMutate(
- /\/guilds\?order/,
- (prev) => mutateGuildsCache(prev, response_),
- { revalidate: false }
- )
-
- toast({
- title: "Guild successfully created!",
- description: "You're being redirected to its page",
- variant: "success",
- })
- onSuccess?.()
- router.push(`/${response_.urlName}`)
- },
- })
-
- return {
- ...useSubmitResponse,
-
- onSubmit: (data: CreateGuildFormType) =>
- useSubmitResponse.onSubmit({
- ...data,
- urlName: slugify(data.name),
- imageUrl: data.imageUrl || `/guildLogos/${getRandomInt(286)}.svg`,
- } satisfies Schemas["GuildCreationPayload"]),
- }
-}
-
-const mutateGuildsCache = (prev: GuildBase[] | undefined, createdGuild: Guild) => [
- ...(prev ?? []),
- {
- id: createdGuild.id,
- name: createdGuild.name,
- urlName: createdGuild.urlName,
- imageUrl: createdGuild.imageUrl,
- memberCount: 1,
- rolesCount: createdGuild.roles.length,
- tags: [],
- hideFromExplorer: false,
- },
-]
-
-export { useCreateGuild }
diff --git a/src/app/create-guild/page.tsx b/src/app/create-guild/page.tsx
deleted file mode 100644
index cad14e109b..0000000000
--- a/src/app/create-guild/page.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { ConfettiProvider } from "@/components/Confetti"
-import { Header } from "@/components/Header"
-import { Layout, LayoutBanner, LayoutHero, LayoutMain } from "@/components/Layout"
-import svgToTinyDataUri from "mini-svg-data-uri"
-import { CreateGuildCard } from "./_components/CreateGuildCard"
-import { CreateGuildFormProvider } from "./_components/CreateGuildFormProvider"
-
-export const metadata = {
- title: "Begin your guild",
-}
-
-const Page = () => (
-
-
-
- `
- )}")`,
- }}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
-
-export default Page
diff --git a/src/app/create-guild/types.ts b/src/app/create-guild/types.ts
deleted file mode 100644
index 11cb102897..0000000000
--- a/src/app/create-guild/types.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { Schemas } from "@guildxyz/types"
-
-export type CreateGuildFormType = Pick<
- Schemas["GuildCreationPayload"],
- "name" | "urlName" | "imageUrl" | "contacts" | "roles" | "theme"
->
diff --git a/src/app/error.tsx b/src/app/error.tsx
index 39f49f165f..142f8237b5 100644
--- a/src/app/error.tsx
+++ b/src/app/error.tsx
@@ -1,47 +1,21 @@
-"use client" // Error components must be Client Components
+"use client";
-import { Button } from "@/components/ui/Button"
-import Bugsnag from "@bugsnag/js"
-import { ChatCircle, House } from "@phosphor-icons/react/dist/ssr"
-import { useEffect } from "react"
-import GuildGhost from "static/avatars/58.svg"
-import { triggerChat } from "utils/intercom"
+import { ErrorPage } from "@/components/ErrorPage";
-export default function Error({
+const ErrorBoundary = ({
error,
}: {
- error: Error & { digest?: string }
-}) {
- useEffect(() => {
- Bugsnag.notify(error, (event) => {
- event.severity = "error"
- event.unhandled = true
- })
- }, [error])
-
+ error: Error & { digest?: string; statusCode?: string };
+}) => {
+ console.log(error.cause);
return (
-
-
-
-
Client-side error
-
-
{error.message}
-
-
-
- )
-}
+export default ErrorBoundary;
diff --git a/src/app/explorer/_components/Explorer.tsx b/src/app/explorer/_components/Explorer.tsx
deleted file mode 100644
index d4446f7a96..0000000000
--- a/src/app/explorer/_components/Explorer.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-"use client"
-import { GuildSearchBar } from "app/explorer/_components/GuildSearchBar"
-import { YourGuilds } from "app/explorer/_components/YourGuilds"
-import useIsStuck from "hooks/useIsStuck"
-import { useSetAtom } from "jotai"
-import { Suspense } from "react"
-import { SearchParams } from "types"
-import { isSearchStuckAtom } from "../atoms"
-import { ActiveSection } from "../types"
-import { GuildInfiniteScroll } from "./GuildInfiniteScroll"
-import { StickyBar } from "./StickyBar"
-
-export const Explorer = ({ searchParams }: { searchParams: SearchParams }) => {
- const setIsSearchStuck = useSetAtom(isSearchStuckAtom)
- const { ref: searchRef } = useIsStuck(setIsSearchStuck)
-
- return (
- <>
-
-
-
-
- Explore verified guilds
-
-
-
-
-
-
-
-
-
- >
- )
-}
diff --git a/src/app/explorer/_components/ExplorerSWRProvider.tsx b/src/app/explorer/_components/ExplorerSWRProvider.tsx
deleted file mode 100644
index a1a59fbd46..0000000000
--- a/src/app/explorer/_components/ExplorerSWRProvider.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-"use client"
-
-import { PropsWithChildren } from "react"
-import { SWRConfig, type SWRConfiguration } from "swr"
-
-export const ExplorerSWRProvider = ({
- children,
- value,
-}: PropsWithChildren<{ value: SWRConfiguration }>) => {
- return
{children}
-}
diff --git a/src/app/explorer/_components/GuildInfiniteScroll.tsx b/src/app/explorer/_components/GuildInfiniteScroll.tsx
deleted file mode 100644
index d209f64d26..0000000000
--- a/src/app/explorer/_components/GuildInfiniteScroll.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-"use client"
-
-import { GuildCardSkeleton, GuildCardWithLink } from "@/components/GuildCard"
-import { Spinner } from "@phosphor-icons/react"
-import useUser from "components/[guild]/hooks/useUser"
-import { env } from "env"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import { useGetKeyForSWRWithOptionalAuth } from "hooks/useGetKeyForSWRWithOptionalAuth"
-import { useScrollBatchedRendering } from "hooks/useScrollBatchedRendering"
-import { memo, useRef } from "react"
-import { SWRConfiguration } from "swr"
-import useSWRInfinite from "swr/infinite"
-import { GuildBase, SearchParams } from "types"
-
-const BATCH_SIZE = 24
-
-const GuildCardMemo = memo(GuildCardWithLink)
-
-const GuildCards = ({ guildData }: { guildData?: GuildBase[] }) => {
- if (guildData?.length) {
- return guildData.map((data) =>
)
- }
- return Array.from({ length: BATCH_SIZE }, (_, i) =>
)
-}
-
-const useExploreGuilds = (searchParams?: SearchParams) => {
- const { isSuperAdmin } = useUser()
- const getKeyForSWRWithOptionalAuth = useGetKeyForSWRWithOptionalAuth()
- const fetcherWithSign = useFetcherWithSign()
- const options: SWRConfiguration = {
- dedupingInterval: 60_000,
- }
-
- // sending authed request for superAdmins, so they can see unverified & hideFromExplorer guilds too
- // @ts-expect-error TODO: resolve this type error
- return useSWRInfinite
(
- (pageIndex, previousPageData) => {
- if (Array.isArray(previousPageData) && previousPageData.length !== BATCH_SIZE)
- return null
- const url = new URL("/v2/guilds", env.NEXT_PUBLIC_API)
- const params: Record = {
- order: "FEATURED",
- ...searchParams,
- offset: (BATCH_SIZE * pageIndex).toString(),
- limit: BATCH_SIZE.toString(),
- }
- for (const entry of Object.entries(params)) {
- url.searchParams.set(...entry)
- }
-
- const urlString = url.pathname + url.search
- if (isSuperAdmin) return getKeyForSWRWithOptionalAuth(urlString)
- return urlString
- },
- isSuperAdmin ? fetcherWithSign : options,
- isSuperAdmin ? options : null
- )
-}
-
-export const GuildInfiniteScroll = ({
- searchParams,
-}: { searchParams: SearchParams }) => {
- const search = searchParams.search
- const ref = useRef(null)
- const {
- data: filteredGuilds,
- setSize,
- isValidating,
- isLoading,
- } = useExploreGuilds(searchParams)
- const renderedGuilds = filteredGuilds?.flat()
-
- useScrollBatchedRendering({
- batchSize: 1,
- scrollTarget: ref,
- disableRendering: isValidating,
- setElementCount: setSize,
- offsetPixel: 420,
- })
-
- if (!renderedGuilds?.length && !isLoading) {
- if (!isValidating && !search?.length) {
- return (
- Can't fetch guilds from the backend right now. Check back later!
- )
- } else {
- return {`No results for ${search}`}
- }
- }
-
- return (
- <>
-
-
- >
- )
-}
diff --git a/src/app/explorer/_components/GuildSearchBar.tsx b/src/app/explorer/_components/GuildSearchBar.tsx
deleted file mode 100644
index b3561c29ce..0000000000
--- a/src/app/explorer/_components/GuildSearchBar.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-"use client"
-import { MagnifyingGlass, PushPin, Sparkle } from "@phosphor-icons/react"
-import { ActiveSection } from "app/explorer/types"
-import useDebouncedState from "hooks/useDebouncedState"
-import { usePathname, useRouter, useSearchParams } from "next/navigation"
-import { useEffect, useState } from "react"
-import { Input } from "../../../v2/components/ui/Input"
-import { ToggleGroup, ToggleGroupItem } from "../../../v2/components/ui/ToggleGroup"
-import { smoothScrollTo } from "./StickyBar"
-
-enum Order {
- Featured = "FEATURED",
- Newest = "NEWEST",
-}
-
-export const GuildSearchBar = () => {
- const router = useRouter()
- const searchParams = useSearchParams()
- const pathname = usePathname()
-
- const [order, setOrder] = useState(
- (searchParams?.get("order")?.toString() as Order) || Order.Featured
- )
- const [search, setSearch] = useState(searchParams?.get("search")?.toString() || "")
- const debouncedSearch = useDebouncedState(search, 200)
-
- useEffect(() => {
- const newSearchParams = new URLSearchParams(
- Object.entries({ order, search: debouncedSearch }).filter(
- ([_, value]) => value
- )
- )
-
- router.push(`${pathname}?${newSearchParams.toString()}`, {
- scroll: false,
- })
- }, [search, debouncedSearch, order])
-
- return (
-
-
setSearch(currentTarget.value)}
- value={search}
- />
-
-
-
-
value && setOrder(value as Order)}
- value={order}
- >
- smoothScrollTo(ActiveSection.ExploreGuilds)}
- >
-
- featured
-
- smoothScrollTo(ActiveSection.ExploreGuilds)}
- >
-
- newest
-
-
-
- )
-}
diff --git a/src/app/explorer/_components/HeaderBackground.tsx b/src/app/explorer/_components/HeaderBackground.tsx
deleted file mode 100644
index ec91d98f66..0000000000
--- a/src/app/explorer/_components/HeaderBackground.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-"use client"
-
-import { cn } from "@/lib/utils"
-import { useAtomValue } from "jotai"
-import { isNavStuckAtom, isSearchStuckAtom } from "../atoms"
-
-export const HeaderBackground = () => {
- const isNavStuck = useAtomValue(isNavStuckAtom)
- const isSearchStuck = useAtomValue(isSearchStuckAtom)
-
- return (
-
- )
-}
diff --git a/src/app/explorer/_components/StickyBar.tsx b/src/app/explorer/_components/StickyBar.tsx
deleted file mode 100644
index 1c4959e197..0000000000
--- a/src/app/explorer/_components/StickyBar.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-"use client"
-
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import { buttonVariants } from "@/components/ui/Button"
-import { ToggleGroup, ToggleGroupItem } from "@/components/ui/ToggleGroup"
-import { cn } from "@/lib/utils"
-import { Plus } from "@phosphor-icons/react"
-import useIsStuck from "hooks/useIsStuck"
-import useScrollspy from "hooks/useScrollSpy"
-import { useAtom, useAtomValue, useSetAtom } from "jotai"
-import Link from "next/link"
-import { useEffect } from "react"
-import { activeSectionAtom, isNavStuckAtom, isSearchStuckAtom } from "../atoms"
-import { ActiveSection } from "../types"
-
-export const smoothScrollTo = (id: string) => {
- const target = document.getElementById(id)
-
- if (!target) return
-
- window.scrollTo({
- behavior: "smooth",
- top: target.offsetTop,
- })
-}
-
-const Nav = () => {
- const isNavStuck = useAtomValue(isNavStuckAtom)
- const isSearchStuck = useAtomValue(isSearchStuckAtom)
- const [activeSection, setActiveSection] = useAtom(activeSectionAtom)
- const spyActiveSection = useScrollspy(Object.values(ActiveSection), 100)
- useEffect(() => {
- if (!spyActiveSection) return
- setActiveSection(spyActiveSection as ActiveSection)
- }, [spyActiveSection, setActiveSection])
-
- return (
- value && setActiveSection(value as ActiveSection)}
- value={activeSection}
- >
- smoothScrollTo(ActiveSection.YourGuilds)}
- >
- Your guilds
-
- smoothScrollTo(ActiveSection.ExploreGuilds)}
- >
- Explore guilds
-
-
- )
-}
-
-const CreateGuildLink = () => {
- const isNavStuck = useAtomValue(isNavStuckAtom)
- return (
-
-
- Create guild
-
- )
-}
-
-export const StickyBar = () => {
- const { isWeb3Connected } = useWeb3ConnectionManager()
- const setIsNavStuck = useSetAtom(isNavStuckAtom)
- const isSearchStuck = useAtomValue(isSearchStuckAtom)
- const { ref: navToggleRef } = useIsStuck(setIsNavStuck)
-
- return (
-
-
-
- {isWeb3Connected && }
-
-
- )
-}
diff --git a/src/app/explorer/_components/YourGuilds.tsx b/src/app/explorer/_components/YourGuilds.tsx
deleted file mode 100644
index 9a18a43b69..0000000000
--- a/src/app/explorer/_components/YourGuilds.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-"use client"
-
-import { GuildCardSkeleton, GuildCardWithLink } from "@/components/GuildCard"
-import { walletSelectorModalAtom } from "@/components/Providers/atoms"
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import { Anchor } from "@/components/ui/Anchor"
-import { Button, buttonVariants } from "@/components/ui/Button"
-import { Card } from "@/components/ui/Card"
-import { useYourGuilds } from "@/hooks/useYourGuilds"
-import { Plus, SignIn } from "@phosphor-icons/react/dist/ssr"
-import { useSetAtom } from "jotai"
-import Image from "next/image"
-
-export const YourGuilds = () => {
- const { isWeb3Connected } = useWeb3ConnectionManager()
- const { data, isLoading } = useYourGuilds()
- const setIsWalletSelectorModalOpen = useSetAtom(walletSelectorModalAtom)
-
- if (!isLoading && !data?.length)
- return (
-
-
- {isWeb3Connected ? (
-
- You're not a member of any guilds yet. Explore and join some below, or
- create your own!
-
- ) : (
- <>
-
- Sign in to view your guilds / create new ones
- >
- )}
-
-
- {isWeb3Connected ? (
-
-
- Create guild
-
- ) : (
- setIsWalletSelectorModalOpen(true)}
- colorScheme="primary"
- leftIcon={ }
- >
- Sign in
-
- )}
-
- )
-
- return (
-
- {isLoading || !data
- ? Array.from({ length: 6 }, (_, i) => )
- : data.map((guild) => (
-
- ))}
-
- )
-}
diff --git a/src/app/explorer/atoms.ts b/src/app/explorer/atoms.ts
deleted file mode 100644
index 84f5a37a73..0000000000
--- a/src/app/explorer/atoms.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { atom } from "jotai"
-import { ActiveSection } from "./types"
-
-export const isNavStuckAtom = atom(false)
-export const isSearchStuckAtom = atom(false)
-export const activeSectionAtom = atom(ActiveSection.YourGuilds)
diff --git a/src/app/explorer/page.tsx b/src/app/explorer/page.tsx
deleted file mode 100644
index 001cbae80f..0000000000
--- a/src/app/explorer/page.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { Header } from "@/components/Header"
-import {
- Layout,
- LayoutBanner,
- LayoutFooter,
- LayoutHeadline,
- LayoutHero,
- LayoutMain,
- LayoutTitle,
-} from "@/components/Layout"
-import { env } from "env"
-import { unstable_serialize as infinite_unstable_serialize } from "swr/infinite"
-import { SearchParams } from "types"
-import { Explorer } from "./_components/Explorer"
-import { ExplorerSWRProvider } from "./_components/ExplorerSWRProvider"
-import { HeaderBackground } from "./_components/HeaderBackground"
-import { ActiveSection } from "./types"
-
-export const metadata = {
- icons: {
- // @ts-ignore: "as" prop not typed out.
- other: [{ rel: "preload", url: "/banner.svg", as: "image" }],
- },
-}
-
-const Page = async ({ searchParams }: { searchParams: SearchParams }) => {
- const featuredPath = `/v2/guilds?order=FEATURED&offset=0&limit=24`
- const newestPath = `/v2/guilds?order=NEWEST&offset=0&limit=24`
- const [ssrFeaturedGuilds, ssrNewestGuilds] = await Promise.all([
- fetch(`${env.NEXT_PUBLIC_API.replace("/v1", "")}${featuredPath}`, {
- next: {
- revalidate: 600,
- },
- })
- .then((res) => res.json())
- .catch((_) => []),
- fetch(`${env.NEXT_PUBLIC_API.replace("/v1", "")}${newestPath}`, {
- next: {
- revalidate: 600,
- },
- })
- .then((res) => res.json())
- .catch((_) => []),
- ])
-
- return (
- featuredPath)]: ssrFeaturedGuilds,
- [infinite_unstable_serialize(() => newestPath)]: ssrNewestGuilds,
- },
- }}
- >
-
-
-
-
-
-
-
-
-
-
-
- Guildhall
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-export default Page
diff --git a/src/app/explorer/types.ts b/src/app/explorer/types.ts
deleted file mode 100644
index 0eeb89dee4..0000000000
--- a/src/app/explorer/types.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export enum ActiveSection {
- YourGuilds = "your-guilds",
- ExploreGuilds = "explore-guilds",
-}
diff --git a/src/app/globals.css b/src/app/globals.css
deleted file mode 100644
index 8c2d18becf..0000000000
--- a/src/app/globals.css
+++ /dev/null
@@ -1,418 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-@layer base {
- :root {
- --background: 240 5% 96%;
- --foreground: 240 4% 16%;
-
- --skeleton: 240 5% 84%;
-
- --card: 0 0% 100%;
- --card-secondary: 0 0% 98%;
- --card-foreground: var(--foreground);
-
- --image: 240 5% 26%;
-
- --popover: var(--card);
- --popover-foreground: var(--card-foreground);
-
- --tooltip: 0 0% 0%;
- --tooltip-foreground: 0 0% 100%;
-
- --primary: 239 78% 65%;
- --primary-hover: 243 75% 59%;
- --primary-active: 245 58% 51%;
- --primary-foreground: 0 0% 100%;
- --primary-subtle: 230 94% 82%;
- --primary-subtle-foreground: var(--primary);
-
- --secondary: 0, 0%, 0%, 0.06;
- --secondary-hover: 0, 0%, 0%, 0.08;
- --secondary-active: 0, 0%, 0%, 0.16;
- --secondary-foreground: 220.9 39.3% 11%;
- --secondary-subtle: 240 5% 65%;
- --secondary-subtle-foreground: var(--foreground);
-
- --info: 217 91 60%;
- --info-hover: 221 83% 53%;
- --info-active: 224 76% 48%;
- --info-foreground: 0 0% 100%;
- --info-subtle: 212 96% 78%;
- --info-subtle-foreground: var(--info);
-
- --destructive: 0 84% 60%;
- --destructive-hover: 0 72% 51%;
- --destructive-active: 0 74% 42%;
- --destructive-foreground: 210 20% 98%;
- --destructive-subtle: 0 94% 82%;
- --destructive-subtle-foreground: var(--destructive);
-
- --success: 142 71% 45%;
- --success-hover: 142 76% 36%;
- --success-active: 142 72% 29%;
- --success-foreground: 0 0% 100%;
- --success-subtle: 141 79% 85%;
- /* --success-subtle-foreground: var(--success); */
- --success-subtle-foreground: 142 76% 36%; /* green 600 */
-
- --warning: 24 75% 50%;
- --warning-hover: 20 71% 44%;
- --warning-active: 16 65% 37%;
- --warning-foreground: 0 0% 100%;
- --warning-subtle: 33 90% 65%;
- --warning-subtle-foreground: var(--warning);
-
- /* these are stored as hex so it's consistent with custom values on profiles with backgroundImageUrl. We plan to migrate all colors to hex values */
- --banner: #27272a;
- --banner-dark: #1e1e1f;
- --banner-foreground: #f9fafb;
- --banner-opacity: 1;
-
- --muted: 0 0% 100%;
- --muted-foreground: 240 5% 46%;
-
- --accent: 240 5% 96%;
- --accent-foreground: 240 4% 16%;
-
- --anchor-foreground: 221 83% 53%;
-
- --border: 240 5.9% 90%;
- --border-muted: 240 3% 93%;
-
- --input-border: 240, 6%, 90%, 100%;
- --input-border-accent: 240, 5%, 84%, 100%;
- --input-border-invalid: 0 84% 60%;
- --input-background: 0, 0%, 100%, 100%;
-
- --ring: 240, 0%, 45%, 25%;
-
- --alert-success: 137 64% 98%;
- --alert-success-icon: 142 71% 45%;
- --alert-info: 218 80% 98%;
- --alert-info-icon: 217 91% 60%;
- --alert-warning: 40 79% 96%;
- --alert-warning-icon: 27 96% 61%;
- --alert-error: 0 78% 98%;
- --alert-error-icon: 0 84% 60%;
-
- --blackAlpha-soft: rgba(0, 0, 0, 0.06);
- --blackAlpha-medium: rgba(0, 0, 0, 0.16);
- --blackAlpha-hard: rgba(0, 0, 0, 0.8);
-
-
- /* not sure about this approach, we should think about this one too */
- --gray: 240 3% 46%; /* gray.500 from Chakra */
-
- --discord: 233 78% 63%;
- --discord-hover: 243 75% 59%;
- --discord-active: 245 58% 51%;
- --telegram: 200 73% 50%;
- --telegram-hover: 200 98% 39%;
- --telegram-active: 201 96% 32%;
- --email: 217 91% 60%;
- --email-hover: 221 83% 53%;
- --email-active: 224 76% 48%;
- --google: 217 91% 60%;
- --google-hover: 221 83% 53%;
- --google-active: 224 76% 48%;
- --twitter: 240 4% 14%;
- --twitter-hover: 240 4% 10%;
- --twitter-active: 240 4% 6%;
- --github: 240 4% 14%;
- --github-hover: 240 4% 10%;
- --github-active: 240 4% 6%;
- --worldid: 240 4% 14%;
- --worldid-hover: 240 4% 10%;
- --worldid-active: 240 4% 6%;
-
- --farcaster: 261 55% 61%;
- --farcaster-hover: 261 55% 49%;
- --farcaster-active: 261 54% 37%;
-
- --uniswap: 331 100% 45%;
- --uniswap-hover: 332 100% 35%;
- --uniswap-active: 331 100% 25%;
-
- --web3inbox: 179 99% 32%;
- --web3inbox-hover: 179 99% 29%;
- --web3inbox-active: 179 98% 25%;
-
- /**
- * Missing reward colors to work with the current Chakra driven solution.
- * Should find a better solution when we ditch Chakra and won't need to be backward compatible
- *
- * Examples of usage -> new: RewardBadge, old: RewardTag
- */
- --gather_town: 232, 66%, 55%;
- --cyan: 190, 100%, 42%;
- --blue: 217, 91%, 60%;
- --gold: 40 67% 51%;
- --gray: 240, 4%, 46%;
-
- --sm: 640px;
- --md: 768px;
- --lg: 1024px;
- --xl: 1280px;
- }
-
- :root[data-theme="dark"], [data-theme="dark"] {
- --background: 240 3.7% 15.88%;
- --foreground: 210 20% 98%;
-
- --skeleton: 240 5% 40%;
-
- --card: 240 5.26% 26.08%;
- --card-secondary: 240 5% 22%;
- --card-foreground: var(--foreground);
-
- --image: 240 5% 34%;
-
- --popover: var(--card);
- --popover-foreground: var(--card-foreground);
-
- --tooltip: 240 5% 84%;
- --tooltip-foreground: 240 4% 16%;
-
- --primary: 239 78% 65%;
- --primary-hover: 234 89% 74%;
- --primary-active: 230 94% 82%;
- --primary-foreground: 0 0% 100%;
- --primary-subtle: 230 94% 82%;
- --primary-subtle-foreground: var(--primary-subtle);
-
- --secondary: 0, 0%, 100%, 0.08;
- --secondary-hover: 0, 0%, 100%, 0.16;
- --secondary-active: 0, 0%, 100%, 0.24;
- --secondary-foreground: 0 0% 100%;
- --secondary-subtle: 240 6% 90%;
- --secondary-subtle-foreground: var(--foreground);
-
- --info: 217 91% 60%;
- --info-hover: 213 94% 68%;
- --info-active: 212 96% 78%;
- --info-foreground: 0 0% 100%;
- --info-subtle: 212 96% 78%;
- --info-subtle-foreground: var(--info-subtle);
-
- --destructive: 0 84% 60%;
- --destructive-hover: 0 91% 71%;
- --destructive-active: 0 94% 82%;
- --destructive-foreground: 210 20% 98%;
- --destructive-subtle: 0 94% 82%;
- --destructive-subtle-foreground: var(--destructive-subtle);
-
- --success: 142 71% 45%;
- --success-hover: 142 69% 58%;
- --success-active: 142 77% 73%;
- --success-foreground: 0 0% 100%;
- --success-subtle: 141 79% 85%;
- --success-subtle-foreground: var(--success-subtle);
-
- --warning: 24 75% 50%;
- --warning-hover: 27 84% 57%;
- --warning-active: 33 90% 65%;
- --warning-foreground: 0 0% 100%;
- --warning-subtle: 33 90% 65%;
- --warning-subtle-foreground: var(--warning-subtle);
-
- --banner: #36363a;
-
- --muted: 240 3% 13%;
- --muted-foreground: 240 5% 60%;
-
- --accent: 240 2% 35%;
- --accent-foreground: 0 0% 92%;
-
- --anchor-foreground: 213 94% 68%;
-
- --border: 240 3% 38%;
- --border-muted: 240 3% 28%;
-
- --input-border: 0, 0%, 100%, 16%;
- --input-border-accent: 0, 0%, 100%, 24%;
- --input-border-invalid: 0 94% 82%;
- --input-background: 0, 0%, 0%, 16%;
-
- --ring: 240 0% 45%;
-
- --alert-success: 180 4% 35%;
- --alert-success-icon: 141 79% 85%;
- --alert-info: 226 7% 36%;
- --alert-info-icon: 213 97% 87%;
- --alert-warning: 30 6% 34%;
- --alert-warning-icon: 32 98% 83%;
- --alert-error: 317 4% 35%;
- --alert-error-icon: 0 94% 82%;
-
- --discord-hover: 234 89% 74%;
- --discord-active: 230 94% 82%;
- --telegram-hover: 198 93% 60%;
- --telegram-active: 199 95% 74%;
- --email-hover: 213 94% 68%;
- --email-active: 212 96% 78%;
- --google-hover: 213 94% 68%;
- --google-active: 212 96% 78%;
-
- --farcaster-hover: 261 54% 69%;
- --farcaster-active: 262 54% 77%;
-
- --uniswap-hover: 331 100% 55%;
- --uniswap-active: 331 100% 65%;
-
- --web3inbox-hover: 179 99% 38%;
- --web3inbox-active: 179 99% 44%;
- }
-}
-
-@layer base {
- * {
- @apply border-border;
- }
-
- body {
- @apply min-h-screen bg-background font-sans text-foreground antialiased;
- }
-}
-
-@layer utilities {
- .scroll-shadow,
- .scroll-shadow [data-radix-scroll-area-viewport] {
- --scroll-shadow-bg: hsl(var(--card-secondary));
- --scroll-shadow-from: rgba(0, 0, 0, 0.1);
- --scroll-shadow-to: rgba(0, 0, 0, 0);
-
- background:
- /* Shadow Cover TOP */
- linear-gradient(
- var(--scroll-shadow-bg) 30%,
- rgba(255, 255, 255, 0)
- ) center top,
-
- /* Shadow Cover BOTTOM */
- linear-gradient(
- rgba(255, 255, 255, 0),
- var(--scroll-shadow-bg) 70%
- ) center bottom,
-
- /* Shadow TOP */
- linear-gradient(
- to bottom,
- var(--scroll-shadow-from),
- var(--scroll-shadow-to)
- ) center top,
-
- /* Shadow BOTTOM */
- linear-gradient(
- to top,
- var(--scroll-shadow-from),
- var(--scroll-shadow-to)
- ) center bottom;
-
- background-repeat: no-repeat;
- background-size: 100% 2.5rem, 100% 2.5rem, 100% 1rem, 100% 1rem;
- background-attachment: local, local, scroll, scroll;
- }
-
- [data-theme="dark"] .scroll-shadow {
- --scroll-shadow-from: rgba(0, 0, 0, 0.25);
- --scroll-shadow-to: rgba(0, 0, 0, 0);
- }
-
- .custom-scrollbar::-webkit-scrollbar,
- .custom-menu-list > div::-webkit-scrollbar {
- width: 8px;
- height: 8px;
- border-radius: 4px;
- }
-
- .custom-scrollbar::-webkit-scrollbar-track,
- .custom-menu-list > div::-webkit-scrollbar-track {
- background: rgba(0, 0, 0, 0.1);
- border-radius: 4px;
- }
-
- .custom-scrollbar::-webkit-scrollbar-thumb,
- .custom-menu-list > div::-webkit-scrollbar-thumb {
- border-radius: 4px;
- background: rgba(0, 0, 0, 0.4);
- }
-
- .invisible-scrollbar::-webkit-scrollbar,
- .invisible-menu-list > div::-webkit-scrollbar {
- width: 0;
- height: 0;
- }
-
- .invisible-scrollbar::-webkit-scrollbar-track,
- .invisible-menu-list > div::-webkit-scrollbar-track {
- width: 0;
- height: 0;
- }
-
- .invisible-scrollbar::-webkit-scrollbar-thumb,
- .invisible-menu-list > div::-webkit-scrollbar-thumb {
- width: 0;
- height: 0;
- }
-
- .prose-counters {
- counter-reset: main-counter;
- }
-
- .prose-counters section:not(:first-child) {
- counter-increment: main-counter;
- }
-
- .prose-counters ol li::marker {
- content: counter(main-counter) ". ";
- }
-
- .prose-counters ol li ol {
- counter-reset: sub-counter;
- }
-
- .prose-counters ol li ol li {
- counter-increment: sub-counter;
- }
-
- .prose-counters ol li ol li::marker {
- content: counter(main-counter) "." counter(sub-counter) " ";
- }
-
- .prose table {
- @apply border
- }
-
- .prose table thead,
- .prose table tr {
- @apply border-border
- }
-
- .prose table th,
- .prose table td {
- @apply p-4
- }
-}
-
-/*
- Hides the default reCaptcha badge. We do indicate reCaptcha usage in the WalletSelectorModal
- https://stackoverflow.com/questions/44543157/how-to-hide-the-google-invisible-recaptcha-badge#answer-44543771
-*/
-.grecaptcha-badge {
- visibility: hidden;
-}
-
-/*
- Make sure the captcha works inside modals...
-*/
-iframe[src*='/recaptcha'] {
- pointer-events: all;
-}
-
-div[class^="intercom-with-namespace-"],
-div[aria-label="Load Chat"],
-.intercom-launcher {
- @apply !bottom-3
-}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 308e7e96fe..94c5da2364 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,73 +1,40 @@
-import { Providers } from "@/components/Providers"
-import { PostHogPageViews } from "@/components/Providers/PostHogPageViews"
-import { dystopian, inter } from "fonts"
-import { type ReactNode, Suspense } from "react"
-import "./globals.css"
-import { TermsOfUseUpdateDialog } from "@/components/TermsOfUseUpdateDialog"
-import { cn } from "@/lib/utils"
-import type { Metadata, Viewport } from "next"
-import Script from "next/script"
-import NextTopLoader from "nextjs-toploader"
-
-interface RootLayoutProps {
- children: ReactNode
-}
+import type { Metadata } from "next";
+import "@/styles/globals.css";
+import { PreloadResources } from "@/components/PreloadResources";
+import { Providers } from "@/components/Providers";
+import { SignInDialog } from "@/components/SignInDialog";
+import { Toaster } from "@/components/ui/Toaster";
+import { dystopian } from "@/lib/fonts";
+import { cn } from "lib/cssUtils";
export const metadata: Metadata = {
title: "Guildhall",
applicationName: "Guildhall",
description:
"Automated membership management for the platforms your community already uses.",
- icons: {
- icon: "/guild-icon.png",
- },
-}
-
-export const viewport: Viewport = {
- themeColor: [
- { media: "(prefers-color-scheme: dark)", color: "#27272a" },
- { media: "(prefers-color-scheme: light)", color: "#f4f4f5" },
- ],
- colorScheme: "dark light",
-}
+ // icons: {
+ // icon: "/guild-icon.png",
+ // },
+};
-export default function RootLayout({ children }: RootLayoutProps) {
+const RootLayout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
return (
-
- {process.env.NODE_ENV === "production" && (
-
- )}
-
-
-
-
+
+
{children}
-
-
-
-
+
+
-
-
- )
-}
+ );
+};
+
+export default RootLayout;
diff --git a/src/app/manifest.ts b/src/app/manifest.ts
deleted file mode 100644
index 1ceeb12d8a..0000000000
--- a/src/app/manifest.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { MetadataRoute } from "next"
-
-export default function manifest(): MetadataRoute.Manifest {
- return {
- name: "Guild.xyz",
- short_name: "Guild.xyz",
- description:
- "Automated membership management for the platforms your community already uses.",
- theme_color: "#fff",
- background_color: "#6062eb",
- display: "standalone",
- start_url: "/",
- screenshots: [
- {
- src: "banner.svg",
- type: "wide",
- sizes: "1791x565",
- },
- ],
- icons: [
- {
- src: `guild-castle.svg`,
- sizes: `64x64`,
- type: "image/svg",
- // @ts-ignore: "maskable any" is not typed out as an option
- purpose: "maskable any",
- },
- {
- src: `guild-castle.png`,
- sizes: `144x144`,
- type: "image/png",
- // @ts-ignore: "maskable any" is not typed out as an option
- purpose: "maskable any",
- },
- {
- src: `guild-icon.png`,
- sizes: `64x64`,
- type: "image/png",
- purpose: "maskable",
- },
- ],
- }
-}
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
new file mode 100644
index 0000000000..71e433da4e
--- /dev/null
+++ b/src/app/not-found.tsx
@@ -0,0 +1,12 @@
+import "server-only";
+import { ErrorPage } from "@/components/ErrorPage";
+
+export default function NotFound() {
+ return (
+
+ );
+}
diff --git a/src/app/oauth-result/page.tsx b/src/app/oauth-result/page.tsx
deleted file mode 100644
index a6a096a56d..0000000000
--- a/src/app/oauth-result/page.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-"use client"
-
-import { useRouter } from "next/navigation"
-import { useEffect, useState } from "react"
-import rewards from "rewards"
-import { OAuthResultParams } from "./types"
-
-const OauthResultPage = ({ searchParams }: { searchParams: OAuthResultParams }) => {
- const { push } = useRouter()
- const [hasReceivedConfirmation, setHasReceivedConfirmation] = useState(false)
-
- useEffect(() => {
- let timeout: NodeJS.Timeout
- if (searchParams.platform) {
- const channel = new BroadcastChannel(`guild-${searchParams.platform}`)
- channel.onmessage = (event) => {
- if (
- event.isTrusted &&
- event.origin === window.origin &&
- event.data?.type === "oauth-confirmation"
- ) {
- setHasReceivedConfirmation(true)
- window.close()
- if (timeout) {
- clearTimeout(timeout)
- }
- }
- }
-
- channel.postMessage(searchParams)
- }
-
- if ("path" in searchParams) {
- timeout = setTimeout(() => {
- const params = new URLSearchParams({
- "oauth-platform": searchParams.platform as string,
- "oauth-status": searchParams.status as string,
- ...("message" in searchParams
- ? { "oauth-message": searchParams.message }
- : {}),
- }).toString()
-
- push(`${searchParams.path}?${params}`)
- }, 1000)
- }
- }, [searchParams, push])
-
- const rewardConfig =
- searchParams.platform && searchParams.platform in rewards
- ? rewards[searchParams.platform as keyof typeof rewards]
- : undefined
-
- return (
-
-
- {searchParams.status === "success"
- ? `${rewardConfig?.name} successfully connected!`
- : searchParams.platform
- ? `${rewardConfig?.name} connection failed`
- : "Connection unsuccessful"}
-
-
- {searchParams.status === "success"
- ? hasReceivedConfirmation
- ? "You may now close this window"
- : "Taking you back to Guild"
- : searchParams.message}
-
-
- )
-}
-
-export default OauthResultPage
diff --git a/src/app/oauth-result/types.ts b/src/app/oauth-result/types.ts
deleted file mode 100644
index 3bc7afced8..0000000000
--- a/src/app/oauth-result/types.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { PlatformName } from "@guildxyz/types"
-
-export type OAuthResultParams =
- | {
- status: "success"
- platform: PlatformName
- path: string
- }
- | {
- status: "error"
- message: string
- platform?: PlatformName
- path?: string
- }
diff --git a/src/app/oauth/page.tsx b/src/app/oauth/page.tsx
deleted file mode 100644
index 0d02fbefa8..0000000000
--- a/src/app/oauth/page.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-"use client"
-
-import { usePostHogContext } from "@/components/Providers/PostHogProvider"
-import { useErrorToast } from "@/components/ui/hooks/useErrorToast"
-import { PlatformName } from "@guildxyz/types"
-import { useRouter } from "next/navigation"
-import { useCallback, useEffect } from "react"
-import timeoutPromise from "utils/timeoutPromise"
-import { Message, OAuthLocalStorageInfo, OAuthResponse } from "./types"
-
-const OAUTH_CONFIRMATION_TIMEOUT_MS = 500
-
-const getDataFromState = (
- state: string
-): { csrfToken: string; platformName: PlatformName } => {
- if (!state) {
- return {} as { csrfToken: string; platformName: PlatformName }
- }
- const [platformName, csrfToken] = state.split("-")
- return { csrfToken, platformName: platformName as PlatformName }
-}
-
-const OAuthPage = ({ searchParams }: { searchParams: Record }) => {
- const { push } = useRouter()
- const { captureEvent } = usePostHogContext()
- const errorToast = useErrorToast()
-
- const handleOauthResponse = useCallback(async () => {
- if (typeof window === "undefined") return null
-
- let params: OAuthResponse
- if (
- typeof searchParams?.state !== "string" &&
- typeof searchParams?.oauth_token !== "string" &&
- typeof searchParams?.denied !== "string"
- ) {
- const fragment = new URLSearchParams(window.location.hash.slice(1))
- const { state, ...rest } = Object.fromEntries(fragment.entries())
- params = { ...getDataFromState(state), ...rest }
- } else {
- const { state, ...rest } = searchParams
- params = { ...getDataFromState(state?.toString()), ...rest }
- }
-
- if (
- !params.oauth_token &&
- !params.denied &&
- (!params.csrfToken || !params.platformName)
- ) {
- captureEvent("OAuth - No params found, or it is in invalid form", {
- params,
- })
- await push("/")
- return
- }
-
- const localStorageInfoKey = `${params.platformName ?? "TWITTER_V1"}_oauthinfo`
- const localStorageInfo: OAuthLocalStorageInfo = JSON.parse(
- window.localStorage.getItem(localStorageInfoKey) ?? "{}"
- )
- window.localStorage.removeItem(localStorageInfoKey)
-
- if (
- !params.oauth_token &&
- !params.denied &&
- !!localStorageInfo.csrfToken &&
- localStorageInfo.csrfToken !== params.csrfToken
- ) {
- captureEvent(`OAuth - Invalid CSRF token`, {
- received: params.csrfToken,
- expected: localStorageInfo.csrfToken,
- })
- errorToast(`Failed to connect ${params.platformName}`)
- await push(localStorageInfo.from)
- return
- }
-
- const channel = new BroadcastChannel(
- !params.platformName ? "TWITTER_V1" : params.csrfToken
- )
-
- const isMessageConfirmed = timeoutPromise(
- new Promise((resolve) => {
- channel.onmessage = () => resolve()
- }),
- OAUTH_CONFIRMATION_TIMEOUT_MS
- )
- .then(() => true)
- .catch(() => false)
-
- let response: Message
- if (params.denied) {
- response = {
- type: "OAUTH_ERROR",
- data: {
- error: "Rejected",
- errorDescription: "Authorization request has been rejected",
- },
- }
- } else if (params.error) {
- const { error, error_description: errorDescription } = params
- response = {
- type: "OAUTH_ERROR",
- data: {
- error: error ?? "Unknown error",
- errorDescription: errorDescription ?? "Unknown error",
- },
- }
- } else {
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { error, error_description, csrfToken, platformName, ...data } = params
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { csrfToken: _csrfToken, from, ...infoRest } = localStorageInfo
- response = { type: "OAUTH_SUCCESS", data: { ...data, ...infoRest } }
- }
-
- channel.postMessage(response)
-
- const isReceived = await isMessageConfirmed
-
- if (isReceived) {
- channel.close()
- window.close()
- } else {
- localStorage.setItem(
- `${params.platformName ?? "TWITTER_V1"}_shouldConnect`,
- JSON.stringify(response)
- )
- push(localStorageInfo.from)
- }
- }, [captureEvent, errorToast, push, searchParams])
-
- useEffect(() => {
- handleOauthResponse().catch((error) => {
- console.error(error)
- captureEvent("OAuth - Unexpected error", {
- error:
- error?.message ?? error?.toString?.() ?? JSON.stringify(error ?? null),
- trace: error?.stack,
- })
- errorToast(`An unexpected error happened while connecting a platform`)
- push("/")
- })
- }, [captureEvent, errorToast, handleOauthResponse, push])
-
- return (
-
-
You're being redirected
-
Closing the authentication window and taking you back to the site...
-
- )
-}
-
-export default OAuthPage
diff --git a/src/app/oauth/types.ts b/src/app/oauth/types.ts
deleted file mode 100644
index cb9121cb8d..0000000000
--- a/src/app/oauth/types.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { PlatformName } from "@guildxyz/types"
-import { OneOf } from "types"
-
-export type OAuthResponse = {
- error_description?: string
- error?: string
- csrfToken: string
- platformName: PlatformName
-} & Record
-
-export type OAuthLocalStorageInfo = {
- csrfToken: string
- from: string
- redirect_url: string
- scope: string
-}
-
-export type Message = OneOf<
- { type: "OAUTH_ERROR"; data: { error: string; errorDescription: string } },
- { type: "OAUTH_SUCCESS"; data: any }
->
diff --git a/src/app/privacy-policy/page.tsx b/src/app/privacy-policy/page.tsx
deleted file mode 100644
index bd5cb726df..0000000000
--- a/src/app/privacy-policy/page.tsx
+++ /dev/null
@@ -1,527 +0,0 @@
-import { Header } from "@/components/Header"
-import {
- Layout,
- LayoutHeadline,
- LayoutHero,
- LayoutMain,
- LayoutTitle,
-} from "@/components/Layout"
-import { Anchor } from "@/components/ui/Anchor"
-
-export const metadata = {
- title: "Privacy Policy",
-}
-
-const Page = (): JSX.Element => (
-
-
-
-
- Privacy Policy
-
-
-
-
-
- Welcome to Guild.xyz!
-
- Guild.xyz is an online platform for automated membership management of
- communities based on blockchain. This notice summarizes how we collect, use
- and safeguard the information you provide to us and what choices you have
- with respect to your privacy.
-
-
- This notice applies to the data processing of Z Gen Kibernetika Korlátolt
- Felelősségű Társaság (registered seat: 6720 Szeged, Kelemen László utca
- 11., company registration number: Cg.06-09-025397, tax number:
- 26787015-2-06) ("Us") and its relevant Affiliates (as specified below) in
- relation to our Services and our Website available at{" "}
-
- https://guild.xyz/
-
- .
-
-
-
- As a Customer, Guild Admin or Subscriber of our Services, the collection,
- use and sharing of your personal data is subject to this notice. Please
- note that this notice does not apply to any Third-Party Platforms connected
- with your profile via the Services, or to the privacy practices of any
- Guilds that you join. Unless defined otherwise, capitalized terms in this
- notice will have the same meaning as in the Terms of Use.
-
-
-
- What type of data do we collect?
-
- When you use our Services, we need to process some information about you to
- make our Services work and to evaluate how you use our Services. This
- information may include personal data about you, such as:
-
-
-
-
- Registration data : When you create your profile, we will request
- certain information about you, such as your public wallet address, a
- social media ID or you email address. If you do not provide this
- information, you may not be able to enjoy all features of the Services
-
-
-
- Social account information : If you decide to connect a certain
- third-party account with our platform, by providing your credentials and
- authorizing the relevant platform to share certain data with us, we will
- access and process such data relating to your social account. Such data
- may include:
-
-
-
- Third-party Platform
- Scope of data
-
-
-
-
-
- Discord
-
- Username, avatar, banner, the discord servers you have joined,
- and specific discord server information (such as nickname,
- avatar, roles etc.)
-
-
-
-
- Twitter
-
- Twitter account information (such as your name, profile picture
- and description), information about your posts and followers, the
- lists you have created or are a member of
-
-
-
-
- Google
-
- Email address and profile information (including any information
- you have made publicly available)
-
-
-
-
- GitHub
-
- User profile data, public and private repositories, commit
- statuses, repository invitations, collaborators, deployment
- statuses, and repository webhooks
-
-
-
-
- Whether or not you will share such information with us is entirely up to
- you. Upon your consent, we may also share such information with the
- Guilds selected by you.
-
-
-
- Membership information : When you create or join a Guild, we will
- process information about your Roles, Rewards as well as any Requirements
- that you have set or completed. Such data is necessary for us to provide
- the Services. Please note that this information will also be available
- for others you collaborate with, in particular for the relevant Guilds
- that you have joined and with whom you decide to share your Profile.
-
-
-
- Blockchain information : Depending on your choices, you may enter
- into different transactions with others via our platform. In such cases,
- information about your transactions (such as the public address of your
- digital wallet, the public address of the addressee’s digital wallet,
- block number, timestamp and other input data) that may also include
- personal data about you may be stored on a blockchain through the
- execution of smart contracts.
-
-
-
- Content that you upload to the Services : You may also upload
- content to the Services, such as your profile picture and background.
- Such content may include personal data about you.
-
-
-
- Billing and subscription information : If you subscribe to a paid
- Service, you need to provide certain billing information (such as your
- name, address and VAT number). You will also be required to provide
- payment information, such as payment card details, which we collect via
- our secure payment processing service providers, Stripe Inc and Coinbase
- Ireland Limited. This data is necessary to provide you with our
- Subscription Services.
-
-
-
- Other information : You may decide to share further information,
- including personal data, with us when you contact us, provide feedback to
- us regarding the Services or otherwise communicate with us. It is solely
- your decision to share any other data with us during such communications,
- so our processing of such data will be based on your consent.
-
-
-
- Information about your use of the Services : As most websites and
- services provided through the Internet, we gather certain information and
- store it in log files when you interact with our Services. This
- information includes internet protocol (IP) address, as well as browser
- type and browser version, operating system, screen resolution and device
- type.
-
-
-
- Cookie information : When you access our websites and Services, we
- use cookies and other information gathering technologies for a variety of
- purposes. These technologies may provide us with personal data,
- information about devices and networks you utilize to access our website,
- and other information regarding your interactions. For detailed
- information about the use of cookies, please see our Cookie Notice
-
-
-
-
- Why do we process your data?
-
- We may process your personal data for several purposes. How we use your
- personal data depends on your on how you use the Services and your
- preferences you have communicated to us.
-
-
-
- Services : We will use some of your personal data for the provision
- and maintenance of your profile, and for authentication purposes. E.g. We
- use your public digital wallet address to enable you to login to our
- Services.
-
-
- Billing : We will process certain information, such as financial
- data for billing purposes, i.e. to complete transactions, and send you
- purchase confirmations and invoices.
-
-
- Customer support : If needed, we use data (which can include your
- communications) to investigate, respond to and resolve complaints
- relating to our Services.
-
-
- Developing Services : We use analytics to better understand the
- behavior of our users to grow our business. For this purpose, we collect
- certain information, such as how often our Services are used, and the
- events that occur while using our websites. We use this aggregate
- information to identify usage patterns and trends.
-
-
- Communication : We may send you information regarding the Services,
- such as administrative messages, to your email address or public wallet
- address provided to us. Upon your consent, we may also enable
- communications between you and others through our Services.
-
-
- Marketing : Upon your consent, we may use your email address to
- send you marketing communications and to provide you with updates about
- our services and products.
-
-
- Security : We use information about you to secure your profile,
- verify accounts, to monitor suspicious or fraudulent activity and to
- identify violations of our Terms of Use or Master Subscription Agreement.
-
-
- Protecting our legitimate business interests and legal rights :
- Where required by law or where we believe it is necessary to protect our
- legal rights, interests and the interests of others, we use information
- about you in connection with legal claims, compliance, regulatory, and
- audit functions, and disclosures in connection with the acquisition,
- merger or sale of our business.
-
-
- Other : We may also process your data for any other purposes for
- which we obtain your consent where necessary or otherwise in accordance
- applicable law and this policy.
-
-
-
-
- What is the legal basis of our data processing? (for EEA users)
-
- If you are an individual in the European Economic Area (EEA), we collect
- and process information about you only where we have legal bases for doing
- so under applicable EU laws. This means we collect and use your information
- only where:
-
-
-
- It is necessary in order to provide you the Services, including to set up
- and maintain your profile and to provide customer support;
-
-
- It satisfies a legitimate interest (which is not overridden by your data
- protection interests), such as for research and development, and to
- protect our legal rights and interests;
-
- You give us consent to do so for a specific purpose;
- It is needed to comply with a legal obligation.
-
-
-
-
- Do we share your data with third parties?
-
- We never sell your personal data to third parties. However, in certain
- cases we need to share your personal data with our Affiliates and third
- parties. In any case, we will share your personal data only in accordance
- with applicable laws and this notice, and in the following cases:
-
-
-
-
- Guild owners : Depending on your use of the Services, you may
- decide to share certain personal data with others. In particular, if you
- join a Guild, the Administrators of the relevant Guild may get access to
- your personal data in the course of the management of their Guild.
- Besides, you may also decide to share additional information, such as
- enhanced details with our Subscribers. If you have any questions about
- the data processing practices of the relevant Guild, you should consult
- their Privacy Policy.
-
-
-
- Public blockchain : When you initiate a blockchain transaction,
- then your data will be shared with the blockchain so that a verifiable
- proof is created of your transaction, your membership or attendance of
- certain events.
-
-
-
- Third-party service providers: In certain cases, we use third party data
- providers. For example, we may outsource billing and payment transactions
- to third parties, we may engage hosting service providers, IT providers,
- operating systems and platforms, internet service providers, and data
- analytics companies.
-
- Currently, these third parties include the following providers:
-
-
-
- Google LLC , for data hosting,
-
-
- Ipfs , for storing and sharing files,,
-
-
- Posthog Inc , for product analytics,,
-
-
- Datadog Inc , to monitor, troubleshoot and optimize application
- performance,,
-
-
- Intercom R&D Unlimited Company for customer communications,
- and,
-
-
- Plausible , for data analytics.
-
-
-
- If you purchase through our website, depending on the payment method
- you choose, your payment and subscription information will be processed
- by:
-
-
-
- Stripe Inc , in accordance with its{" "}
-
- Privacy Policy
-
- , or
-
-
- Coinbase Ireland Limited , in accordance with its{" "}
-
- Privacy Policy
-
- .
-
-
- In the case of certain transactions, your transaction data may also be
- stored on the blockchain as specified in section b) above.
-
-
-
- Professional advisors : We may share your data with professional
- advisers acting as service providers, processors, controllers, or joint
- controllers - including lawyers, bankers, auditors, and insurers who
- provide consultancy, banking, legal, insurance and accounting services,
- and to the extent we are legally obliged to share or have a legitimate
- interest in sharing your data.
-
-
-
- Legal compliance : We may transmit personal data if the applicable
- legal provisions so require, or when such action is necessary to comply
- with any laws, e.g. with criminal authorities if we are required to
- cooperate for such purposes. We may also need to share personal data for
- the protection of our rights and interests, to protect your safety or the
- safety of others or to investigate fraud, in accordance with the
- applicable laws.
-
-
-
- During a change to our business : If we are involved in a merger,
- acquisition, bankruptcy, dissolution, reorganisation, sale of some or all
- of our assets, or a similar transaction or proceeding, or steps in
- contemplation of such activities, certain information may be shared or
- transferred, subject to standard confidentiality arrangements.
-
-
-
-
-
- Do we share your data with third parties?
-
- Our website and Services are hosted by Google, in Switzerland. If you
- access our website or Services from any other region of the world with laws
- or other requirements governing personal data collection, use, or
- disclosure that differ from applicable laws in the European Economic Area,
- then through your continued use of our sites and Services, you are
- transferring your data outside of the European Economic Area, and you agree
- to have your data transferred to and processed in different jurisdictions,
- such as the United States.
-
-
-
-
- How long will we retain your data?
-
- We will retain your personal data as long as it is needed to fulfil the
- purposes specified above (for example, to provide you with our Services),
- unless a longer retention period is required or permitted by law (such as
- tax, accounting or other legal requirements). Upon your lawful request or
- when we have no ongoing legitimate business need to process your personal
- data, we will either delete or anonymize it in accordance with our deletion
- policy.
-
-
- With respect to the data stored on the public blockchain, due to the
- immutable nature of the blockchain, your transaction data that may also
- include your public wallet address may not be modified or deleted.
-
-
-
-
- You data privacy rights
- You may ask us to:
-
-
- provide information to you about the personal data that we or our
- processors process about you,
-
- correct inaccuracies or amend your personal data,
-
- stop processing your personal data and/or to stop sending you marketing
- communications,
-
- delete your personal data.
-
-
-
- If you are from a country where the General Data Protection Regulation of
- the EU (GDPR) applies, you may have additional rights such as:
-
-
-
-
- In certain circumstances, you may have a broader right to erasure of your
- personal data. For example, if it is no longer necessary in relation to
- the purposes for which it was originally collected. Please note, however,
- that we may need to retain certain information for record keeping
- purposes, to complete transactions or to comply with our legal
- obligations.
-
-
- You may have the right to request that we restrict processing of your
- personal data in certain circumstances (for example, where you believe
- that the personal data we hold about you is inaccurate or unlawfully
- held).
-
-
- In certain circumstances, you may have the right to be provided with your
- personal data in a structured, machine readable and commonly used format
- and to request that we transfer the personal data to another data
- controller without hindrance.
-
-
-
-
-
- Who is responsible for the processing of your data?
-
- Guild.xyz services are provided by Z Gen Kibernetika Korlátolt Felelősségű
- Társaság (registered seat: 6720 Szeged, Kelemen László utca 11., company
- registration number: Cg.06-09-025397, tax number: 26787015-2-06) and it
- acts as a data controller with respect to the processing of your data.
-
-
- Where you decide that you would like to share data with a Guild, then they
- will be considered as a data controller with respect to their processing.
- To learn more about their data processing practices, you should consult the
- Privacy Policy of the relevant Guild.
-
-
-
-
- Children’ privacy
-
- Our Site and the Service are not directed to anyone under the age of 18.
- The Site does not knowingly collect or solicit information from anyone
- under the age of 18, or allow anyone under the age of 18 to sign up for the
- Service. In the event that we learn that we have gathered information from
- anyone under the age of 18 without the consent of a parent or guardian, we
- will delete that information as soon as possible. If you believe we have
- collected such information, please contact us at{" "}
-
- help@guild.xyz
-
-
-
-
-
- Changes
-
- We reserve the right to change this notice from time to time. Changes will
- be published on this website and any material changes will go into effect
- eights days following such notification. We encourage you to periodically
- review this page for the latest information on our privacy practices. Your
- continued use of our website or our Services constitutes your agreement to
- be bound by such changes to this notice. Your only remedy, if you do not
- accept the terms of notice, is to discontinue use of our Website and
- Services.
-
-
-
-
- Any further questions?
-
- If you have any further questions in relation to the processing of your
- data, please contact{" "}
-
- help@guild.xyz
-
- .
-
-
-
-
-)
-
-export default Page
diff --git a/src/app/terms-of-use/page.tsx b/src/app/terms-of-use/page.tsx
deleted file mode 100644
index a2154acabd..0000000000
--- a/src/app/terms-of-use/page.tsx
+++ /dev/null
@@ -1,732 +0,0 @@
-import { Header } from "@/components/Header"
-import {
- Layout,
- LayoutFooter,
- LayoutHeadline,
- LayoutHero,
- LayoutMain,
- LayoutTitle,
-} from "@/components/Layout"
-import { Anchor } from "@/components/ui/Anchor"
-
-export const metadata = {
- title: "Terms of Use",
-}
-
-const Page = (): JSX.Element => (
-
-
-
-
- Terms of Use
-
-
-
-
-
- Terms of Use
- Welcome to Guild.xyz!
-
- Guild.xyz is an online platform for automated membership management of
- communities based on blockchain. This term of use ("this
- Agreement") governs your access to and use of the services available
- at
-
- https://guild.xyz/
-
- ("Website") and any relating technologies, functionalities,
- features, and software (the "Services").
-
-
- The Services are provided by Z Gen Kibernetika Korlátolt Felelősségű
- Társaság (registered seat: 6720 Szeged, Kelemen László utca 11., company
- registration number: Cg.06-09-025397, tax number: 26787015-2-06)
- ("Service Provider" or "Us"). This Agreement is entered
- into by the individual or entity using or accessing the Services (referred
- to as "Customer" or "You") and the Service Provider. If
- you are agreeing to this Agreement not as an individual but on behalf of
- your organization, this Agreement will bind your organization, unless your
- organization has a separate agreement in effect with us. You confirm that
- you have the necessary authority to enter into this Agreement on behalf of
- your organization before proceeding.
-
-
- By accessing and using the Services you agree to be bound by this
- Agreement, so please read this document carefully. The offering of the
- Services to you is conditional on your acceptance of this Agreement. If you
- do not agree to this Agreement, you must not access or use the Service.
- Please do not use the Services if you are under 18 of age or barred from
- doing so under applicable law.
-
-
-
-
- 1. Service
-
-
- Scope : This Agreement governs access and use of the
- Services. You may access and use the Services in accordance with this
- Agreement and the Privacy Policy. Certain services or functionalities may
- be subject to additional terms specific to the relevant service or
- functionality ("Feature-Specific Terms") as specified in the
- relevant Feature-Specific Terms which are hereby incorporated into
- section 15 of this Agreement by reference. By accessing or using the
- relevant feature, you agree to be bound by such Feature-Specific Terms.
-
-
- Changes : The Service Provider may update the Services or
- modify features and functionality from time to time. The Service Provider
- also reserves the right to revise the terms of this Agreement from time
- to time by posting the modified terms on the Website, and such amendments
- will enter into effect eight days after its publication. By continuing to
- access or use the Services after the amendment enters into effect, you
- agree to be bound by the revised Agreement.
-
-
-
-
-
- 2. Using the Services
-
-
- Access rights : Subject to the terms of this Agreement,
- the Service Provider grants you a limited, non-exclusive,
- non-transferable and revokable right to use the Service during the Term.
- Except as expressly agreed in writing by the Service Provider, you shall
- not distribute, sublicense, transfer, sell, offer for sale, disclose, or
- make available any part of the Service to any third party.
-
-
- Eligibility to use : To use the Services, you must be at
- least of legal age (18 years of age or older or otherwise of legal age in
- your resident jurisdiction). You represent and warrant that: (i) you are
- of legal age in your resident jurisdiction, and competent to agree to
- this Agreement; and (ii) you have validly entered into this Agreement and
- have the legal power to do so.
-
-
- Digital wallet : To use our Service, you must use one of
- the supported third-party wallets ("Digital Wallet") which
- allows you to engage in transactions on blockchains and access or use
- your Tokens. By connecting your Digital Wallet, you create a user profile
- with the Service Provider ("Profile"). The Service Provides
- does not undertake any responsibility for, or liability in connection
- with your use of a Digital Wallet and makes no representations or
- warranties regarding how the Service will operate with any specific
- Digital Wallet.
-
-
-
-
-
- 3. Community management
-
-
- Guilds : For membership management purposes, the Services
- may enable you to create or join a "Guild". Once you create a
- Guild, you will become the "Guild Admin", with control over the
- membership structure of the Guild, including the customization of Roles,
- Requirements and Rewards (as defined below).
-
-
- Roles, Requirements and Rewards : Guild Admins may freely
- set up the membership structure of the Guild and define "Roles"
- that are specific to each Guild. Guild Admins may also specify
- "Rewards", in particular in the form of permitting certain
- actions or enabling access to something. Rewards means in any digital
- points awarded to users by the Guild Admin and any Tokens supplied by the
- Guild Admin into the Pool and which may be claimed by users in accordance
- with the applicable requirements and other terms of the Guild.
-
-
- Roles and Rewards may be conditional upon the "Requirements"
- set by the Guild Admin.
-
-
- Joining a Guild : If you meet certain Requirements, you
- may be able to join a Guild in a specific Role and/or you may be entitled
- to Rewards. You understand and acknowledge that (i) Guild Admins are
- solely responsible for the terms of their Guilds, including the relevant
- Requirements, Roles and Reward; (ii) the Service Provider has no
- responsibility for such terms, and you cannot claim access to any
- Requirements or any Rewards directly from the Service Provider in
- relation to the completion of any Requirements; (iii) Guilds may change
- the Requirements for certain Roles or Rewards from time to time and the
- Service Provider has no responsibility for any such changes.
-
-
- Connecting third-party platforms : The Services may allow
- you to connect certain third-party platforms ("Third-party
- Platforms") with your Profile. Providing your credentials and
- authorizing access to such Third-party Platforms, may result in the
- access, use, disclosure of certain Personal Data to the Service Provider
- and depending on your choices, also to certain Guilds. You acknowledge
- that the use of any Third-party Platform is governed solely by the terms
- and conditions and privacy policy of such Third-party Platform.
-
-
-
-
-
- 4. Data protection
-
-
- Data processing : In connection with your use of the
- Services, the Service Provider will process certain information about you
- that may contain personal data. The
-
- Privacy Policy
-
- describes how the Service Provider collects, uses, transfers, discloses
- and stores your personal data. You understand and acknowledge that, in
- connection with your use of certain features of the Services, your
- personal data may be stored on a blockchain through the execution of the
- smart contracts, and this means that, due to the immutable nature of the
- blockchain, some of your personal data may not be modified or deleted.
-
-
- Data sharing : In accordance with section 3, you may
- decide to join or leave certain Guilds. Depending on the different
- Third-Party Platforms connected with your Profile and whether you decide
- to share your Profile with a certain Guild, this may result in the
- disclosure, access, or use of your personal data shared with us. You
- understand and acknowledge that the data processing activities of a Guild
- are governed solely by the terms and conditions of and privacy policy of
- the relevant Guild. The Service Provider does not endorse, is not liable
- for, and makes no representations in respect of any Guilds, or the manner
- in which such Guild uses, stores, or processes personal data. You agree
- that (i) it is solely the responsibility of the relevant Guild to inform
- you of any relevant policies and practices that may impact the processing
- of your data, (ii) obtain any rights, permissions or consent from you
- that are necessary for the lawful use of your data, (iii) ensure that the
- transfer and processing of your data is lawful, (iv) respond to and
- resolve any dispute with you relating to our personal data.
-
-
- Third-Party Platforms : The Service Provider does not
- endorse, is not liable for, and makes no representations in respect of
- any Third-party Platform, or the way such Third-party Platform uses,
- stores, or processes personal data. If you choose to connect your Profile
- with a Third-party Platform, you grant the Service Provider permission to
- access your relevant Personal Data as appropriate for the interoperation
- of such Third-Party Platform with the Services.
-
-
- Sub-processors and data transfers : You understand and
- agree that the Service Provider will engage third-party service providers
- that may access and process your personal data to assist in providing the
- Services to you. You agree that the Service Provider and third-party
- service providers may access and process your personal data in countries
- outside of the European Economic Area (EEA). You also understand and
- agree that by joining a Guild, you consent that your relevant personal
- data will be shared with such Guild and that the Guild may be subject to
- the laws of a jurisdiction outside of the EEA.
-
-
-
-
-
- 5. Your responsibility
-
-
- Security : You are solely responsible for your Profile
- and for keeping your connected Digital Wallet and your wallet credentials
- secure. The Service Provider is not liable for any acts or omissions by
- you in connection with your Profile or as a result of your Digital Wallet
- being compromised. You agree to immediately notify us if you discover any
- unauthorized or suspicious activity relating to the Service or your
- Profile. In the case of an issue related to your Digital Wallet, please
- contact your wallet provider.
-
-
- Compliance and taxes : You will ensure that your use of
- the Services is compliant with all applicable laws and regulations, and
- the provisions of this Agreement. You are solely responsible for
- determining if any reporting, tax or other legal obligations apply to you
- in relation to your use of the Services. You agree and understand that
- the Service Provider does not provide any legal, tax, or investment
- advice and you agree to seek your own advice as necessary, and to comply
- with any reporting, tax or legal obligations you may have in connection
- with your use of our Services.
-
-
- Embargoed Countries : The use of the Services may be
- subject to import or export controls or other restrictions under the laws
- of the country where you intend to use the Services. It is your sole
- responsibility to check such limitations before using the Services and to
- comply with such restrictions and limitations. You shall not access or
- use the Services if you are located in any jurisdiction in which the
- provision of the Services, Software or other components is prohibited
- under applicable laws or regulations (a "Prohibited
- Jurisdiction"). You represent and warrant that (i) you are not
- prohibited from receiving EU or US exports; and (ii) you are not a
- national of, or a legal entity registered in, any Prohibited
- Jurisdiction.
-
-
-
-
-
- 6. Fees and payment
-
-
- Paid services : The Service Provider offers both free and
- paid Services. Unless indicated otherwise, the Services are offered for
- free. However, certain transactions carried out via the Services may be
- subject to a fee ("Protocol Fee" and “Gas Fee”) in accordance
- with the Fee Schedule available on the Website.
-
-
- Fees : Customer is responsible for the payment of all
- fees, payment method and in the currency quoted at the time of the
- initiation of the transaction. (i) Protocol Fee: This fee is for the use
- of smart contracts that forward the transaction to the final destination.
- These smart contracts are deployed by Guild.xyz. (ii) Gas Fee: This fee
- is for the Ethereum nodes to validate the transaction. It is not related
- to Guild. Read about it here:
- https://www.investopedia.com/terms/g/gas-ethereum.asp Transaction fees
- are final and are non-refundable.
-
-
- Changes of fees : The Service Provider reserves the right
- to revise and update the Fee Schedule, at any time at its sole
- discretion. Any such revision or updates to the fees will apply
- prospectively following the effective date of the fee revision or update.
-
-
-
-
-
- 7. Transactions
-
-
- Smart contracts/P2P assumption : The Service Provider
- provides a web3 service that enables automated membership management, and
- the use of the Services may result in certain transactions between users.
- The nature of such transactions is entirely P2P (peer-to-peer), and the
- Service Provider has no control whatsoever over the transmission,
- processing, and execution of such transactions. The Service Provider is
- not a financial institution, payments processor, crypto-asset service
- provider, creditor, wallet provider, exchange, or financial advisor. The
- Service Provider cannot make any representation or guarantee regarding
- any specific outcomes resulting from making digital assets available for
- minting or collecting, or engaging in any other transactions or
- activities via the Services.
-
-
- Risk assumption : The transactions that can be carried
- out via the Services rely upon certain blockchain technology, including
- decentralized, distributed public ledger(s). By accessing or using the
- Services, you confirm that you are aware of the risks inherent in such
- technologies, such as the uncertainty of the regulatory regime governing
- blockchain technologies, digital assets, tokens and cryptocurrencies, the
- immutable nature of blockchain-based transactions, the novel and
- experimentative status of blockchain technology, and that the lack of use
- or public interest in the creation and development of distributed
- ecosystems could negatively impact the development of those ecosystems as
- well as the potential utility or value of such assets. You also confirm
- that you are aware that the price and liquidity of blockchain-based
- assets, including non-fungible tokens (NFTs), are extremely volatile and
- may be subject to fluctuations. Fluctuations in the price of other
- blockchain-based assets could materially and adversely affect digital
- assets. You also confirm that you are aware that the potential utility or
- value of such digital assets may go to zero, and that the Service
- Provider in no way or form guarantees the potential utility or value of
- such digital assets.
-
-
- Platform only : You expressly acknowledge that the
- Service Provider provides a platform only, and it is not party to any
- smart contracts, arrangements or agreements. The Service Provider does
- not have custody or control over any digital assets issued on blockchain
- networks or protocols, including fungible and non-fungible digital assets
- ("Tokens") you are interacting with, and we do not execute or
- effectuate issuances, purchases, transfers, or sales of Tokens. You are
- responsible for verifying the identity, legitimacy, and authenticity of
- Tokens that you purchase from third-party sellers using the Services and
- we make no claims, guarantees, or recommendations about the identity,
- legitimacy, functionality, or authenticity of users, Tokens (and any
- content associated with such Tokens) visible on the Service. It is your
- sole responsibility to verify all transaction details before initiating
- such transactions. You should always carefully check the relevant
- transaction details. The Service Provider is not responsible or liable
- for any losses you may incur due to incorrect transaction information.
-
-
-
-
-
- 8. Intellectual property
-
-
- Reservation of rights : Each party shall retain all
- rights, title and interest in and to all its respective patents,
- inventions, copyrights, trademarks, domain names, databases trade
- secrets, know-how and any other intellectual property and/or proprietary
- rights (collectively, "Intellectual Property Rights"). This
- Agreement does not grant any right, title, or interest to you with
- respect to the Services or in any of the Service Provider’s Intellectual
- Property Rights, except as expressly set out in this Agreement.
-
-
- Customer Content : The Services may enable you to submit
- and upload content to the Services, such as images and text
- ("Customer Content"). You own all right, title, and interest in
- and to Customer Content. You hereby grant us a worldwide, royalty-free,
- fully paid-up, and sublicensable license to display, host, copy, and
- reproduce (in any form) Customer Content to the extent necessary to
- provide and maintain the Services. You represent and warrant that you own
- all rights, title, and interest in and to the Customer Content or you
- have otherwise obtained all necessary consents, licenses and waivers
- required to create, submit, store, and use Customer Content in connection
- with the Services.
-
-
- Feedback : The Service Provider may use any feedback,
- ideas, comments, enhancement requests, recommendations or suggestions
- ("Suggestions") that you send or share with the Service
- Provider without any obligation to you. You hereby grant to the Service
- Provider a world-wide, royalty free, irrevocable, perpetual license to
- use and otherwise incorporate any Suggestions.
-
-
-
-
-
- 9. Acceptable Use
-
-
- Restrictions : You will not and will not permit any third
- party to: (i) reverse engineer, decompile, disassemble or otherwise
- attempt to discover the source code, object code or underlying structure,
- ideas, know-how or algorithms relevant to the Services except for the
- scope in which such limitation is explicitly prohibited by law; (ii)
- access the Services for competitive purposes; (iii) use the Services in a
- manner that violates the rights of a third party, or applicable laws and
- regulations; or (iv) use the Services in any manner that is likely to
- damage, disable, overburden, or impair the Services or its related
- systems and networks.
-
-
- Prohibited Customer Content : You are exclusively
- responsible for all Customer Content. You agree that you will not upload,
- use or in connection with the Services any prohibited Customer Content
- including, without limitation, Customer Content that (i) exploits, harms,
- or attempts to exploit or harm minors in any way by exposing them to
- inappropriate content or otherwise; (ii) is pornographic, sexually
- explicit or offensive, obscene, defamatory, libelous, bullying, profane,
- indecent; (iii) promotes racism, violence or hatred, or otherwise conveys
- a message of hate against any individual or group; (iv) is factually
- inaccurate, false, misleading or deceptive; (v) contains viruses, trojan
- horses, worms or any other harmful or deleterious programs; (vi) furthers
- or promotes criminal activity or provides instructional information about
- illegal activities.
-
-
- Prohibited Use : You also agree that you will not and not
- permit any third party to: (i) detrimentally interfere with, intercept,
- or expropriate any system, data, or information; (ii) attempt to gain
- unauthorized access to the Services, other wallets or Tokens not
- belonging to you, computer systems or networks connected to the Services,
- through password mining or any other means; (iii) harvest or otherwise
- collect information from the Services about others, including without
- limitation email addresses and/or public or private wallet keys, without
- proper consent; (iv) use the Service to carry out any financial
- activities subject to registration or licensing, including but not
- limited to creating, offering, selling, or buying securities,
- commodities, options, or debt instruments; (v) use the Service to create,
- sell, or buy Tokens that give prospective holders rights to participate
- in an ICO or any securities offering, or that are redeemable for
- securities, commodities, or other financial instruments; (vi) use the
- Service to engage in price manipulation, fraud, or other deceptive,
- misleading, or manipulative activity; (vii) use the Service to buy, sell,
- or transfer stolen items, fraudulently obtained items, items taken
- without authorization, and/or any other illegally obtained items; (viii)
- impersonate or attempt to impersonate any person.
-
-
- Monitoring : The Service Provider reserves the right to
- investigate and take appropriate action against anyone who, in its sole
- discretion, violates the provisions of this section 9, including removing
- any Customer Content with or without prior notice, terminating or
- suspending your access to the Services and/or (where required by
- applicable law) reporting such Customer Content or activities to law
- enforcement authorities.
-
-
-
-
-
- 10. Warranty
-
-
- Free Services are provided "AS IS" and "as
- available". To the maximum extent permitted by applicable law, THE
- SERVICE PROVIDER DISCLAIMS ANY IMPLIED WARRANTIES INCLUDING WITHOUT
- LIMITATION MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR
- NON-INFRINGEMENT.
-
-
-
-
-
- 11. Indemnity
-
-
- To the extent permitted by law, Customer will defend, indemnify and hold
- harmless the Service Provider, including its employees and Affiliates,
- from and against any third-party claims, incidents, liabilities,
- procedures, damages, losses and expenses, including reasonable legal and
- accounting fees, arising out of or in any way connected with (i) your
- access or use of the Services, (ii) your breach of this Agreement, or
- (iii) your violation of applicable laws, rules or regulations in
- connection with the Services.
-
-
-
-
-
- 12. Limitation of liability
-
-
- Exclusion of damages : TO THE FULLEST EXTENT PROVIDED BY
- LAW, IN NO EVENT WILL THE SERVICE PROVIDER OR ITS AFFILIATES, OR THEIR
- LICENSORS, EMPLOYEES, CONTRACTORS, AGENTS, OFFICERS OR DIRECTORS, BE
- LIABLE FOR ANY LOST PROFITS, REVENUES, OR BUSINESS OPPORTUNITIES, LOSS OF
- USE, LOSS OF DATA, LOSS OF CONFIDENTIAL OR OTHER INFORMATION, LOSS OF
- DIGITAL ASSETS, LOSS OF ACCESS TO ANY DIGITAL WALLET, BUSINESS
- INTERRUPTION AND ANY OTHER INDIRECT, SPECIAL, INCIDENTAL, CRIMINAL,
- SUBSEQUENT OR CONSEQUENTIAL DAMAGES WHATSOEVER, WHETHER BASED ON
- CONTRACT, TORT, NEGLIGENCE, PRODUCT LIABILITY OR OTHERWISE, ARISING OUT
- OF OR IN ANY WAY RELATED TO THE USE OF OR INABILITY TO USE THE SERVICE,
- REGARDLESS WHETHER THE SERVICE PROVIDER HAS BEEN ADVISED OR SHOULD HAVE
- HAD KNOWLEDGE OF THE POSSIBILITY OF SUCH DAMAGES. THE SERVICE PROVIDER
- WILL NOT BE LIABLE FOR THE ACTIONS OR OMISSIONS OF THIRD PARTIES, NOR FOR
- ANY DAMAGES THAT MAY OCCUR AS A RESULT OF YOUR TRANSACTIONS OR OTHER
- INTERACTIONS WITH THIRD PARTIES VIA THE SERVICES.
-
-
- Limitation of liability : YOU AGREE THAT THE SOLE AND
- EXCLUSIVE REMEDY FOR UNSATISFACTORY SERVICE SHALL BE TERMINATION OF THE
- SERVICE AND A REFUND OF ANY AMOUNT ALREADY PAID BY YOU NOTWITHSTANDING
- ANYTHING TO THE CONTRARY IN THESE TERMS. THE AGGREGATE LIABILITY OF THE
- SERVICE PROVIDER FOR ALL CLAIMS RELATING TO THE SERVICES, THE ACCESS TO
- AND USE OF THE SERVICE, CONTENT, DIGITAL ASSETS OR ANY PRODUCTS OR
- SERVICES PURCHASED ON THE SERVICES SHALL NOT EXCEED THE GREATER OF (A)USD
- 100 OR (B) THE AMOUNT RECEIVED BY THE SERVICE PROVIDER FROM YOU RELATED
- TO THE RELEVANT DIGITAL ASSET THAT IS THE SUBJECT OF THE APPLICABLE
- CLAIM.
-
-
- Exceptions : AFOREMENTIONED LIMITATIONS OF LIABILITY DO
- NOT APPLY FOR INTENTIONAL OR GROSS NEGLIGENT BREACH OF OBLIGATIONS BY THE
- SERVICE PROVIDER AND DAMAGES CAUSED TO LIFE, BODY OR HEALTH, AND FOR ANY
- OTHER LIABILITY THAT MAY NOT, UNDER APPLICABLE LAW, BE LIMITED OR
- EXCLUDED. IN SUCH JURISDICTIONS, OUR LIABILITY IS LIMITED TO THE EXTENT
- PERMITTED BY LAW, THEREBY MINIMIZING OUR LIABILITY TO YOU TO THE LOWEST
- AMOUNT PERMITTED BY APPLICABLE LAW.
-
-
- Force Majeure : The Service Provider will not be liable
- to Customer or to any other third party for failure to perform or any
- delay in the performance of the Service due to fire, flood, war, riot,
- strike, explosion, lock out, injunction, natural disaster, interruption
- of transportation, acts of war, terrorism, labor disputes, acts of civil
- or military authority, power blackouts, denial of service attacks, bugs,
- computer viruses, trojan horses or any other event beyond the Service
- Provider's reasonable control.
-
-
-
-
-
- 13. Term and termination
-
-
- Term : This Agreement enters into effect on the day when
- you accept this Agreement and continues until terminated either by you or
- the Service Provider ("Term").
-
-
- Cancellation : Customer may terminate a free Service
- immediately without cause. If applicable, Subscription Services may be
- terminated in accordance with the Subscription Terms.
-
-
- Termination for Cause : Either party may terminate this
- Agreement with notice if the other party materially breaches this
- Agreement and such breach is not cured within fifteen days after the
- non-breaching party provides notice of the breach. The Service Provider
- may immediately terminate this Agreement for cause without notice if
- Customer or its users violate section 9 (Acceptable Use) of this
- Agreement.
-
-
- Survival : The following sections will survive expiry or
- termination of this Agreement: 5 (Your responsibility), 6 (Fees and
- payment), 7 (Transactions), 8 (Intellectual Property Rights), 10-12, 13
- (Term and termination), 14 (Miscellaneous).
-
-
- Effect : If this Agreement is terminated: (i) the rights
- and licenses granted by the Service Provider to you will cease
- immediately; (ii) the Service Provider may delete any data relating to
- your Profile in a commercially reasonable period of time in accordance
- with its Privacy Notice.
-
-
-
-
-
- 14. Miscellaneous
-
-
- Severability; Entire agreement : The provisions of this
- Agreement apply to the maximum extent permitted by relevant law. If any
- court or relevant authority decides that any part of this Agreement is
- unlawful, unenforceable, or invalid, the remaining clauses will remain in
- full force and effect. This is the entire contract between the Parties
- regarding the Service. It supersedes any prior contract or oral or
- written statements regarding your use of the Service.
-
-
- Remedy : The failure of either Party to enforce a
- provision of this Agreement is not a waiver of its right to do so later.
- The waiver by the Service Provider of any breach shall not be deemed a
- waiver of any subsequent breach of the same or any other term of this
- Agreement. Any remedy made available to the Service Provider by any of
- the provisions of this Agreement is not intended to be exclusive of any
- other remedy.
-
-
- Expiration of Claims : Both parties agree that except for
- claims related to the indemnification obligations above, all claims
- arising under or related to this Agreement must be brought within one
- year after the date the cause of action arose.
-
-
- Assignment : You may not assign or transfer this
- Agreement or any rights or obligations under this Agreement without the
- Service Provider’s written consent. The Service Provider may freely
- assign its rights and obligations under this Agreement in its entirety to
- an Affiliate or in connection with a merger, acquisition, corporate
- reorganization, or sale of all or substantially all of its assets. For
- the purposes of this Agreement, "affiliate" means any entity
- that controls, is controlled by or is under common control with a party,
- where "control" means the ability to direct the management and
- policies of an entity.
-
-
- Governing law, dispute resolution : This Agreement is
- governed by Hungarian law, excluding the Hungarian conflict of law rules
- and the Vienna Convention on Contracts for International Sale of Goods.
-
-
- Consumer protection : If any consumer laws are applicable
- to the parties’ relationship, and cannot otherwise be lawfully excluded,
- nothing in this Agreement will restrict, exclude or modify any mandatory
- rules of law. Any consumer complaints relating to this Agreement, or the
- Services may be communicated by the following means: (i) by sending a
- letter to the address of the Service Provider (6720 Szeged, Kelemen
- László utca 11.) or (ii) by sending an e-mail to
-
- help@guild.xyz
-
- . Any consumer complaint will be assessed and duly answered within 30
- days from the date when the complaint is received. If your complaint is
- rejected, then you may submit your complaint to the competent
- conciliation body at the place of your habitual or temporary residence.
- Prior to the proceeding of the conciliation body, you must attempt to
- solve the dispute with us directly. Should you decide to turn to a
- conciliation body other than the competent conciliation body, then – upon
- your request – that conciliation body will have competence to proceed. If
- you believe that your consumer rights have been violated, then you may
- also file a complaint with the competent consumer protection authority.
- The authority will decide whether to initiate proceedings upon the
- assessment of the complaint. The competence of the consumer protection
- authority depends on the place of your habitual residence. The list of
- authorities is available here. There is no code of practice available at
- the Service Provider as defined under the act on the prohibition of
- unfair commercial practices.
-
-
- Notice : Notices are to be sent by electronic means, in
- the form of an email. Notices through email will be deemed to have been
- duly given the day after it is sent. The Service Provider may be
- contacted at
-
- help@guild.xyz
-
- . The Service Provider reserves the right to make reasonable steps to
- verify your identity before responding to or acting upon Customer's
- request.
-
-
-
-
-
- 15. Feature-Specific Terms – Pool management.
-
-
- Pools : "Pools" shall mean the pool created by
- Guild Admins interacting with the ITokenPool smart contract, with such
- pool being solely managed and owned by Guild Admins, and is also supplied
- with Tokens solely and exclusively by the Guild Admins.
-
-
- Creation and ownership of Pools : Guild Admins are solely
- responsible for the creation of Pools, and retain ownership rights over
- such Pools at all times. Such pool being solely managed and owned by
- Guild Admins, and is also supplied with Tokens solely and exclusively by
- the Guild Admins. Guild Admins may elect, at their discretion, to supply
- the relevant Pool with Crypto-Assets, and may also elect to remove any or
- all of the supply of Crypto-Assets in the relevant Pool. The Guild Admin
- may also elect to transfer the ownership of the relevant Pool at their
- discretion.
-
-
- Guild Member claims : Customer accessing and/or forming
- part of the Guild ("Guild Member") may be allowed, by the Guild
- Admin, to claim Tokens and Rewards subject to the fulfilment of the
- requirements set by the Guild Admin. Guild Members hereby acknowledge and
- agree that any terms applicable to the fulfilment of requirements in
- order to potentially claim Rewards, as well as any Tokens from the Pool
- are solely set by, and within the discretion of, the Guild Admin. Guild
- Admins are also fully responsible for any distributions of Tokens.
-
-
- Token supply to the Pool : Guild Admins hereby undertake
- full responsibility over any Tokens supplied to the Pool, including full
- adherence to, and compliance with, any applicable laws concerning the
- Tokens themselves or the issuance thereof. Guild Admins shall not
- display, on the Platform, any information pertaining to the Tokens other
- than: i) the name of the Token; ii) the identifier/ticker of the Token;
- iii) the address for the issuing smart contract for such Token; and iv) a
- hyperlink to any third-party hosted documentation pertaining to the
- Token.
-
-
- Claiming fees : Guild Admins may elect, at their
- discretion, to set fees applicable for any claims of Tokens or Rewards
- initiated by Guild Members. Such fees are sent to an address selected by
- the Guild Admin, with the Service Provider not having any rights
- whatsoever to receive any of the fees set, received, or otherwise
- directed by the Guild Admin. The Guild Admin also retains full control
- over the parameters and amounts of such fees.
-
-
- Warranties : You acknowledge and agree that the Services
- are non-custodial and that You are solely responsible for the use and
- security of your Tokens including their custody and their use in
- transactions. You also confirm that any Tokens used or provided by You in
- connection with the Services are legitimately owned or controlled by you
- and such Tokens have been legally obtained or issued.
-
-
-
-
-
-
-)
-
-export default Page
diff --git a/src/bugsnag.ts b/src/bugsnag.ts
deleted file mode 100644
index 9628b18598..0000000000
--- a/src/bugsnag.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import Bugsnag from "@bugsnag/js"
-import { env } from "env"
-
-export const bugsnagStart = () => {
- if (
- typeof window === "undefined" ||
- process.env.NODE_ENV !== "production" ||
- window.location.host !== "guild.xyz"
- )
- return
-
- Bugsnag.start({
- apiKey: env.NEXT_PUBLIC_BUGSNAG_KEY,
- plugins: [],
- endpoints: {
- notify: "/api/bugsnag/notify",
- sessions: "/api/bugsnag/sessions",
- },
- })
-}
diff --git a/src/components/AuthBoundary.tsx b/src/components/AuthBoundary.tsx
new file mode 100644
index 0000000000..d90ec5af4e
--- /dev/null
+++ b/src/components/AuthBoundary.tsx
@@ -0,0 +1,16 @@
+import { tryGetToken } from "@/lib/token";
+
+export const AuthBoundary = async ({
+ fallback,
+ children,
+}: Readonly<{
+ fallback: React.ReactNode;
+ children: React.ReactNode;
+}>) => {
+ try {
+ await tryGetToken();
+ return <>{children}>;
+ } catch {
+ return <>{fallback}>;
+ }
+};
diff --git a/src/components/ConfettiProvider.tsx b/src/components/ConfettiProvider.tsx
new file mode 100644
index 0000000000..1878f5f2e3
--- /dev/null
+++ b/src/components/ConfettiProvider.tsx
@@ -0,0 +1,99 @@
+"use client";
+
+import {
+ type MutableRefObject,
+ type PropsWithChildren,
+ createContext,
+ useContext,
+ useRef,
+} from "react";
+import ReactCanvasConfetti from "react-canvas-confetti/dist";
+import type { TCanvasConfettiInstance } from "react-canvas-confetti/dist/types";
+
+const doubleConfetti = (confetti: TCanvasConfettiInstance) => {
+ const count = 200;
+ const defaultsPerBarrage: confetti.Options[] = [
+ {
+ origin: { x: -0.05 },
+ angle: 50,
+ },
+ {
+ origin: { x: 1.05 },
+ angle: 130,
+ },
+ ] as const;
+
+ const fire = (particleRatio: number, opts: confetti.Options) => {
+ confetti({
+ ...opts,
+ particleCount: Math.floor(count * particleRatio),
+ });
+ };
+
+ for (const defaults of defaultsPerBarrage) {
+ fire(0.25, {
+ spread: 26,
+ startVelocity: 55,
+ ...defaults,
+ });
+ fire(0.2, {
+ spread: 60,
+ ...defaults,
+ });
+ fire(0.35, {
+ spread: 100,
+ decay: 0.91,
+ scalar: 0.8,
+ ...defaults,
+ });
+ fire(0.1, {
+ spread: 120,
+ startVelocity: 25,
+ decay: 0.92,
+ scalar: 1.2,
+ ...defaults,
+ });
+ fire(0.1, {
+ spread: 120,
+ startVelocity: 45,
+ ...defaults,
+ });
+ }
+};
+
+const ConfettiContext = createContext>(
+ {} as MutableRefObject,
+);
+
+export const useConfetti = () => useContext(ConfettiContext);
+
+type ConfettiPlayer = () => void;
+
+export const ConfettiProvider = ({ children }: PropsWithChildren) => {
+ const confettiRef = useRef(() => {
+ return;
+ });
+
+ const onInitHandler = ({
+ confetti,
+ }: { confetti: TCanvasConfettiInstance }) => {
+ const confettiClosure: ConfettiPlayer = () => {
+ doubleConfetti(confetti);
+ };
+ confettiRef.current = confettiClosure;
+ };
+
+ return (
+
+ {children}
+
+
+ );
+};
diff --git a/src/components/DashboardContainer.tsx b/src/components/DashboardContainer.tsx
new file mode 100644
index 0000000000..143cdd4dbe
--- /dev/null
+++ b/src/components/DashboardContainer.tsx
@@ -0,0 +1,27 @@
+import { cn } from "@/lib/cssUtils";
+import { Slot } from "@radix-ui/react-slot";
+import type { HTMLAttributes } from "react";
+
+interface DashboardContainerProps extends HTMLAttributes {
+ asChild?: boolean;
+}
+
+export const DashboardContainer = ({
+ children,
+ className,
+ asChild = false,
+ ...props
+}: DashboardContainerProps) => {
+ const Comp = asChild ? Slot : "div";
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/components/ErrorPage.tsx b/src/components/ErrorPage.tsx
new file mode 100644
index 0000000000..332ca88646
--- /dev/null
+++ b/src/components/ErrorPage.tsx
@@ -0,0 +1,61 @@
+import { DashboardContainer } from "@/components/DashboardContainer";
+import { DataBlockWithCopy } from "@/components/requirements/DataBlockWithCopy";
+import { Anchor } from "@/components/ui/Anchor";
+import { buttonVariants } from "@/components/ui/Button";
+
+// TODO: simple compound component when design is certain
+
+const ErrorPage = ({
+ title,
+ description,
+ correlationId,
+ errorCode,
+}: {
+ title: string;
+ description: string;
+ correlationId?: string;
+ errorCode: string;
+}) => {
+ return (
+
+
+
+
+ {title}
+
+
+ {description}
+
+
+ Return Home
+
+
+
+ {correlationId && (
+
+ correlation id:
+
+ )}
+
+
+
+
+ );
+};
+
+export { ErrorPage };
diff --git a/src/components/GuildImage.tsx b/src/components/GuildImage.tsx
new file mode 100644
index 0000000000..3ee8f5e1d3
--- /dev/null
+++ b/src/components/GuildImage.tsx
@@ -0,0 +1,30 @@
+import { cn } from "@/lib/cssUtils";
+import type { Guild } from "@/lib/schemas/guild";
+import { ImageSquare } from "@phosphor-icons/react/dist/ssr";
+import { Avatar, AvatarFallback, AvatarImage } from "@radix-ui/react-avatar";
+
+type Props = {
+ className?: string;
+ name: Guild["name"];
+ imageUrl: Guild["imageUrl"];
+};
+
+export const GuildImage = ({ name, imageUrl, className }: Props) => (
+
+
+
+
+
+
+);
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 0000000000..0cfa438d55
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,17 @@
+import { AuthBoundary } from "./AuthBoundary";
+import { NavMenu } from "./NavMenu";
+import { SignInButton } from "./SignInButton";
+import { SignOutButton } from "./SignOutButton";
+import { Card } from "./ui/Card";
+
+export const Header = () => (
+
+);
diff --git a/src/components/ImageUploader.tsx b/src/components/ImageUploader.tsx
new file mode 100644
index 0000000000..37120b4df0
--- /dev/null
+++ b/src/components/ImageUploader.tsx
@@ -0,0 +1,117 @@
+"use client";
+
+import { getPinataKey } from "@/actions/getPinataKey";
+import { pinata } from "@/config/pinata.client";
+import { cn } from "@/lib/cssUtils";
+import {
+ CircleNotch,
+ UploadSimple,
+ XCircle,
+} from "@phosphor-icons/react/dist/ssr";
+import { useMutation } from "@tanstack/react-query";
+import { type InputHTMLAttributes, useCallback, useRef, useState } from "react";
+import { toast } from "sonner";
+import { Button, type ButtonProps } from "./ui/Button";
+
+type Props = Omit & {
+ maxSizeMB?: number;
+ onSuccess?: (imageUrl: string) => void;
+ onError?: (error: string) => void;
+ onFileInputChange?: InputHTMLAttributes["onChange"];
+};
+
+const mbToBytes = (mb: number) => mb * 10 ** 6;
+
+export const ImageUploader = ({
+ className,
+ maxSizeMB = 5,
+ onSuccess,
+ onError,
+ onFileInputChange,
+ ...props
+}: Props) => {
+ const fileInputRef = useRef(null);
+
+ const [uploadedImage, setUploadedImage] = useState("");
+
+ const { mutate: upload, isPending } = useMutation({
+ mutationFn: async (file: File) => {
+ const pinataJWT = await getPinataKey();
+ const upload = await pinata.upload.file(file).key(pinataJWT.JWT);
+ const url = await pinata.gateways.convert(upload.IpfsHash);
+ return url;
+ },
+ onSuccess: (imageUrl: string) => {
+ setUploadedImage(imageUrl);
+ if (typeof onSuccess === "function") {
+ onSuccess(imageUrl);
+ }
+ },
+ onError: (error) => {
+ toast("Upload error", {
+ description: error.message,
+ icon: ,
+ });
+
+ if (typeof onError === "function") {
+ onError(error.message);
+ }
+ },
+ });
+
+ const validateFiles = useCallback(
+ (files: FileList | null) => {
+ if (!files) return;
+
+ const [file] = Array.from(files);
+
+ if (file.size > mbToBytes(maxSizeMB)) {
+ if (typeof onError === "function")
+ onError(`Max file size is ${maxSizeMB}MB`);
+ return;
+ }
+
+ upload(file);
+ },
+ [maxSizeMB, onError, upload],
+ );
+
+ return (
+ fileInputRef.current?.click()}
+ disabled={isPending}
+ style={
+ uploadedImage
+ ? {
+ backgroundImage: `url(${uploadedImage})`,
+ backgroundSize: "cover",
+ }
+ : undefined
+ }
+ {...props}
+ >
+ {uploadedImage ? null : isPending ? (
+
+ ) : (
+
+ )}
+ {
+ validateFiles(e.target.files);
+ if (typeof onFileInputChange === "function") {
+ onFileInputChange(e);
+ }
+ }}
+ />
+
+ );
+};
diff --git a/src/components/NavMenu.tsx b/src/components/NavMenu.tsx
new file mode 100644
index 0000000000..58decd17f1
--- /dev/null
+++ b/src/components/NavMenu.tsx
@@ -0,0 +1,233 @@
+"use client";
+
+import { cn } from "@/lib/cssUtils";
+import {
+ Book,
+ Code,
+ Desktop,
+ DiscordLogo,
+ File,
+ House,
+ Info,
+ List,
+ Moon,
+ Package,
+ Palette,
+ Plus,
+ Shield,
+ Sun,
+ UsersThree,
+ XLogo,
+} from "@phosphor-icons/react/dist/ssr";
+import { useIsClient } from "foxact/use-is-client";
+import { useTheme } from "next-themes";
+import { usePathname } from "next/navigation";
+import { type ComponentProps, type ReactNode, useState } from "react";
+import GuildLogo from "static/logo.svg";
+import { Anchor } from "./ui/Anchor";
+import { Button, buttonVariants } from "./ui/Button";
+import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
+import { ToggleGroup, ToggleGroupItem } from "./ui/ToggleGroup";
+
+export const NavMenu = () => {
+ const [open, setOpen] = useState(false);
+
+ return (
+
+
+
+
+ Guild
+
+
+
+
+
+
+ setOpen(false)}>
+
+ Explore guilds
+
+
+ setOpen(false)}>
+
+ Create guild
+
+
+ setOpen(false)}
+ >
+
+ Guide
+
+
+ setOpen(false)}
+ >
+
+ Case studies
+
+
+ setOpen(false)}>
+
+ Privacy Policy
+
+
+ setOpen(false)}>
+
+ Terms of Use
+
+
+
+
+ setOpen(false)}
+ >
+
+ Discord
+
+
+ setOpen(false)}
+ >
+
+ Twitter
+
+
+ setOpen(false)}
+ >
+
+ Code
+
+
+ setOpen(false)}
+ >
+
+ Guild SDK
+
+
+ setOpen(false)}
+ >
+
+ Team
+
+
+ setOpen(false)}
+ >
+
+ Brand kit
+
+
+
+
+
+ Theme:
+
+
+
+
+ );
+};
+
+const NavGroup = ({
+ title,
+ children,
+}: { title: string; children: ReactNode }) => (
+
+
+ {title}
+
+ {children}
+
+);
+
+const NavButton = ({
+ href,
+ children,
+ ...props
+}: ComponentProps) => {
+ const pathname = usePathname();
+ const externalProps = href.toString().startsWith("/")
+ ? {}
+ : {
+ target: "_blank",
+ rel: "noopener noreferrer",
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+const ThemeToggle = () => {
+ const { setTheme, theme } = useTheme();
+ const isClient = useIsClient();
+
+ if (!isClient) {
+ return;
+ }
+
+ return (
+ setTheme(selected)}
+ aria-label="Toggle between themes"
+ variant="primary"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/PreloadResources.tsx b/src/components/PreloadResources.tsx
new file mode 100644
index 0000000000..e586a0ea65
--- /dev/null
+++ b/src/components/PreloadResources.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import ReactDOM from "react-dom";
+
+export const PreloadResources = () => {
+ ReactDOM.preload(
+ "https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-opsz-normal.woff2",
+ {
+ as: "font",
+ crossOrigin: "",
+ type: "font/woff2",
+ },
+ );
+ ReactDOM.preload(
+ "https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-opsz-italic.woff2",
+ {
+ as: "font",
+ crossOrigin: "",
+ type: "font/woff2",
+ },
+ );
+ return null;
+};
diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx
new file mode 100644
index 0000000000..51c80a8193
--- /dev/null
+++ b/src/components/Providers.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { wagmiConfig } from "@/config/wagmi";
+import { getQueryClient } from "@/lib/getQueryClient";
+import { QueryClientProvider } from "@tanstack/react-query";
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+import { Provider as JotaiProvider } from "jotai";
+import { ThemeProvider } from "next-themes";
+import type { FunctionComponent, PropsWithChildren } from "react";
+import { WagmiProvider } from "wagmi";
+import { TooltipProvider } from "./ui/Tooltip";
+
+export const Providers: FunctionComponent = ({
+ children,
+}) => {
+ const queryClient = getQueryClient();
+
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/PubilcProse.tsx b/src/components/PubilcProse.tsx
new file mode 100644
index 0000000000..f4d70ef623
--- /dev/null
+++ b/src/components/PubilcProse.tsx
@@ -0,0 +1,55 @@
+import "server-only";
+import { promises as fs } from "node:fs";
+import path from "node:path";
+import { Anchor } from "@/components/ui/Anchor";
+import Markdown from "react-markdown";
+import rehypeExternalLinks from "rehype-external-links";
+import rehypeSlug from "rehype-slug";
+import remarkGfm from "remark-gfm";
+import remarkTextr from "remark-textr";
+// @ts-expect-error: no typedef from module
+import base from "typographic-base";
+
+const applyTypographicBase = (prose: string) => {
+ return base(prose);
+};
+
+export const PublicProse = async ({ fileName }: { fileName: string }) => {
+ const file = await fs.readFile(
+ path.join(process.cwd(), `public/prose/${fileName}.md`),
+ "utf8",
+ );
+ return (
+
+ (
+
+ ),
+ }}
+ remarkPlugins={[
+ remarkGfm,
+ [
+ remarkTextr,
+ {
+ options: { locale: "en-us" },
+ plugins: [applyTypographicBase],
+ },
+ ],
+ ]}
+ rehypePlugins={[
+ [
+ rehypeExternalLinks,
+ {
+ rel: ["nofollow noreferrer noopener"],
+ target: "_blank",
+ },
+ ],
+ rehypeSlug,
+ ]}
+ >
+ {file}
+
+
+ );
+};
diff --git a/src/components/SignInButton.tsx b/src/components/SignInButton.tsx
new file mode 100644
index 0000000000..9715059b4b
--- /dev/null
+++ b/src/components/SignInButton.tsx
@@ -0,0 +1,21 @@
+"use client";
+
+import { signInDialogOpenAtom } from "@/config/atoms";
+import { SignIn } from "@phosphor-icons/react/dist/ssr";
+import { useSetAtom } from "jotai";
+import type { ComponentProps } from "react";
+import { Button } from "./ui/Button";
+
+export const SignInButton = (props: ComponentProps) => {
+ const setSignInDialogOpen = useSetAtom(signInDialogOpenAtom);
+
+ return (
+ }
+ onClick={() => setSignInDialogOpen(true)}
+ >
+ Sign in
+
+ );
+};
diff --git a/src/components/SignInDialog.tsx b/src/components/SignInDialog.tsx
new file mode 100644
index 0000000000..a4b8456274
--- /dev/null
+++ b/src/components/SignInDialog.tsx
@@ -0,0 +1,213 @@
+"use client";
+
+import { signIn } from "@/actions/auth";
+import { signInDialogOpenAtom } from "@/config/atoms";
+import { fetchGuildApi } from "@/lib/fetchGuildApi";
+import {
+ SignIn,
+ SignOut,
+ User,
+ Wallet,
+ XCircle,
+} from "@phosphor-icons/react/dist/ssr";
+import { DialogDescription } from "@radix-ui/react-dialog";
+import { useMutation } from "@tanstack/react-query";
+import { useAtom, useSetAtom } from "jotai";
+import { shortenHex } from "lib/shortenHex";
+import { toast } from "sonner";
+import { createSiweMessage } from "viem/siwe";
+import { useAccount, useConnect, useDisconnect, useSignMessage } from "wagmi";
+import { z } from "zod";
+import { Anchor, anchorVariants } from "./ui/Anchor";
+import { Button } from "./ui/Button";
+import {
+ ResponsiveDialog,
+ ResponsiveDialogBody,
+ ResponsiveDialogContent,
+ ResponsiveDialogHeader,
+ ResponsiveDialogTitle,
+} from "./ui/ResponsiveDialog";
+
+const CUSTOM_CONNECTOR_ICONS = {
+ "com.brave.wallet": "/walletLogos/brave.svg",
+ walletConnect: "/walletLogos/walletconnect.svg",
+ coinbaseWalletSDK: "/walletLogos/coinbasewallet.png",
+} as const;
+
+export const SignInDialog = () => {
+ const { isConnected } = useAccount();
+ const [open, setOpen] = useAtom(signInDialogOpenAtom);
+
+ return (
+
+
+
+ Sign in
+
+
+
+
+ {isConnected ? : }
+
+
+
+ );
+};
+
+const WalletList = () => {
+ const { connectors, connect, isPending, variables } = useConnect();
+ const setOpen = useSetAtom(signInDialogOpenAtom);
+
+ return (
+
+
+ {connectors.map((connector) => {
+ const connetorIcon =
+ CUSTOM_CONNECTOR_ICONS[
+ connector.id as keyof typeof CUSTOM_CONNECTOR_ICONS
+ ] ?? connector.icon;
+ return (
+
connect({ connector })}
+ leftIcon={
+ connetorIcon ? (
+
+ ) : (
+
+ )
+ }
+ size="xl"
+ isLoading={
+ !!variables?.connector &&
+ "id" in variables.connector &&
+ variables.connector.id === connector.id &&
+ isPending
+ }
+ loadingText="Check your wallet"
+ className="justify-start"
+ >
+ {connector.name}
+
+ );
+ })}
+
+
+
+
+ {"New to Ethereum wallets? "}
+
+ Learn more
+
+
+
+
+ {"By continuing, you agree to our "}
+ setOpen(false)}
+ >
+ Privacy Policy
+
+ {" and "}
+ setOpen(false)}
+ >
+ Terms of use
+
+
+
+
+ );
+};
+
+const SignInWithEthereum = () => {
+ const { address } = useAccount();
+ const { signMessageAsync } = useSignMessage();
+
+ const setSignInDialogOpen = useSetAtom(signInDialogOpenAtom);
+
+ const { mutate: signInWithEthereum, isPending } = useMutation({
+ mutationKey: ["SIWE"],
+ mutationFn: async () => {
+ const nonceResponse = await fetchGuildApi("auth/siwe/nonce");
+ const { nonce } = z
+ .object({ nonce: z.string() })
+ .parse(nonceResponse.data);
+
+ const url = new URL(window.location.href);
+
+ const message = createSiweMessage({
+ // biome-ignore lint: address is defined at this point, since we only render this component if `isConnected` is `true`
+ address: address!,
+ chainId: 1,
+ domain: url.hostname,
+ nonce,
+ uri: url.origin,
+ version: "1",
+ });
+
+ const signature = await signMessageAsync({ message });
+
+ return signIn({ message, signature });
+ },
+ onSuccess: () => setSignInDialogOpen(false),
+ onError: (error) => {
+ toast("Sign in error", {
+ description: error.message,
+ icon: ,
+ });
+ console.error(error);
+ },
+ });
+
+ const { disconnect } = useDisconnect();
+
+ return (
+
+ }
+ size="xl"
+ className="justify-start"
+ disabled
+ >
+ {/* biome-ignore lint: address is defined at this point, since we only render this component if `isConnected` is `true` */}
+ {shortenHex(address!)}
+
+ }
+ colorScheme="success"
+ size="xl"
+ onClick={() => signInWithEthereum()}
+ isLoading={isPending}
+ loadingText="Check your wallet"
+ >
+ Sign in with Ethereum
+
+ }
+ size="sm"
+ variant="unstyled"
+ className={anchorVariants({
+ variant: "secondary",
+ className: "mx-auto max-w-max px-0",
+ })}
+ onClick={() => disconnect()}
+ >
+ Disconnect wallet
+
+
+ );
+};
diff --git a/src/components/SignOutButton.tsx b/src/components/SignOutButton.tsx
new file mode 100644
index 0000000000..38540a6e67
--- /dev/null
+++ b/src/components/SignOutButton.tsx
@@ -0,0 +1,19 @@
+"use client";
+
+import { signOut } from "@/actions/auth";
+import { SignOut } from "@phosphor-icons/react/dist/ssr";
+import { usePathname } from "next/navigation";
+import { Button } from "./ui/Button";
+
+export const SignOutButton = () => {
+ const pathname = usePathname();
+ return (
+ }
+ onClick={() => signOut(pathname)}
+ >
+ Sign out
+
+ );
+};
diff --git a/src/components/[guild]/AccessHub/AccessHub.tsx b/src/components/[guild]/AccessHub/AccessHub.tsx
deleted file mode 100644
index fd3f2ac0ef..0000000000
--- a/src/components/[guild]/AccessHub/AccessHub.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Collapsible, CollapsibleContent } from "@/components/ui/Collapsible"
-import useMembership from "components/explorer/hooks/useMembership"
-import dynamic from "next/dynamic"
-import useGuild from "../hooks/useGuild"
-import useGuildPermission from "../hooks/useGuildPermission"
-import useRoleGroup from "../hooks/useRoleGroup"
-import CampaignCards from "./components/CampaignCards"
-
-const DynamicGuildPinRewardCard = dynamic(
- () => import("./components/GuildPinRewardCard")
-)
-
-const DynamicCreatedPageCard = dynamic(() =>
- import("./components/CreatePageCard").then((m) => m.CreatePageCard)
-)
-
-const AccessHub = (): JSX.Element => {
- const { featureFlags, guildPin, groups, roles } = useGuild()
-
- const group = useRoleGroup()
- const { isAdmin } = useGuildPermission()
- const { isMember } = useMembership()
-
- const shouldShowGuildPin =
- !group &&
- featureFlags?.includes("GUILD_CREDENTIAL") &&
- ((isMember && guildPin?.isActive) || isAdmin)
-
- const hasVisiblePages = !!groups?.length && roles?.some((role) => !!role.groupId)
-
- const showAccessHub = isAdmin || (hasVisiblePages && !group) || shouldShowGuildPin
-
- return (
-
-
-
-
- {isAdmin && }
- {shouldShowGuildPin && }
-
-
-
- )
-}
-
-export { AccessHub }
diff --git a/src/components/[guild]/AccessHub/components/AccessedGuildPlatformCard.tsx b/src/components/[guild]/AccessHub/components/AccessedGuildPlatformCard.tsx
deleted file mode 100644
index 45eadaed08..0000000000
--- a/src/components/[guild]/AccessHub/components/AccessedGuildPlatformCard.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import PlatformCard from "components/[guild]/RolePlatforms/components/PlatformCard"
-import { useRolePlatform } from "components/[guild]/RolePlatforms/components/RolePlatformProvider"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useGuildPermission from "components/[guild]/hooks/useGuildPermission"
-import { cardPropsHooks } from "rewards/cardPropsHooks"
-import rewardComponentsConfig from "rewards/components"
-import { PlatformName, PlatformType } from "types"
-import PlatformAccessButton from "./PlatformAccessButton"
-
-const AccessedGuildPlatformCard = () => {
- const { guildPlatform } = useRolePlatform()
- const { isDetailed } = useGuild()
- const { isAdmin } = useGuildPermission()
-
- const rewardComponents =
- rewardComponentsConfig[PlatformType[guildPlatform.platformId] as PlatformName]
- const useCardProps = cardPropsHooks[PlatformType[guildPlatform.platformId]]
-
- if (!rewardComponents || !useCardProps) return null
-
- const {
- cardMenuComponent: PlatformCardMenu,
- cardWarningComponent: PlatformCardWarning,
- cardButton: PlatformCardButton,
- } = rewardComponents
-
- return (
-
- {PlatformCardWarning && }
- {isAdmin && isDetailed && PlatformCardMenu && (
-
- )}
- >
- }
- h="full"
- p={{ base: 3, sm: 4 }}
- boxShadow="none"
- >
- {PlatformCardButton ? (
-
- ) : (
-
- )}
-
- )
-}
-export default AccessedGuildPlatformCard
diff --git a/src/components/[guild]/AccessHub/components/CampaignCards/CampaignCards.tsx b/src/components/[guild]/AccessHub/components/CampaignCards/CampaignCards.tsx
deleted file mode 100644
index 97539f9f03..0000000000
--- a/src/components/[guild]/AccessHub/components/CampaignCards/CampaignCards.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/Avatar"
-import { Badge } from "@/components/ui/Badge"
-import { buttonVariants } from "@/components/ui/Button"
-import { Card } from "@/components/ui/Card"
-import { Skeleton } from "@/components/ui/Skeleton"
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/Tooltip"
-import { cn } from "@/lib/utils"
-import { ArrowRight, EyeSlash, Plus } from "@phosphor-icons/react/dist/ssr"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useGuildPermission from "components/[guild]/hooks/useGuildPermission"
-import dynamic from "next/dynamic"
-import Link from "next/link"
-import { useRouter } from "next/router"
-
-const DynamicCampaignCardMenu = dynamic(
- () => import("./components/CampaignCardMenu")
-)
-
-const CampaignCards = () => {
- const { isAdmin } = useGuildPermission()
-
- const {
- groups,
- roles,
- imageUrl: guildImageUrl,
- urlName: guildUrlName,
- } = useGuild()
- const { query } = useRouter()
-
- if (!groups?.length || !!query.group) return null
-
- const renderedGroups = isAdmin
- ? groups
- : groups.filter((group) => !group.hideFromGuildPage)
-
- return (
- <>
- {renderedGroups.map(({ id, imageUrl, name, urlName, hideFromGuildPage }) => {
- const groupHasRoles = roles?.some((role) => role.groupId === id)
- if (!isAdmin && !groupHasRoles) return null
-
- let campaignImage = ""
- if (typeof imageUrl === "string" && imageUrl.length > 0)
- campaignImage = imageUrl
- else if (typeof guildImageUrl === "string" && guildImageUrl.length > 0)
- campaignImage = guildImageUrl
-
- return (
-
- {isAdmin && }
-
-
-
-
-
-
-
-
-
-
-
{name}
- {hideFromGuildPage && (
-
-
-
-
- Hidden
-
-
-
-
- Members don't see this, they can only access this page by
- visiting its link directly
-
-
-
- )}
-
-
- {groupHasRoles ? (
-
- View page
-
-
- ) : (
-
-
- Add roles
-
- )}
-
- )
- })}
- >
- )
-}
-
-export default CampaignCards
diff --git a/src/components/[guild]/AccessHub/components/CampaignCards/components/CampaignCardMenu.tsx b/src/components/[guild]/AccessHub/components/CampaignCards/components/CampaignCardMenu.tsx
deleted file mode 100644
index 222a996066..0000000000
--- a/src/components/[guild]/AccessHub/components/CampaignCards/components/CampaignCardMenu.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import {
- AlertDialogBody,
- AlertDialogContent,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogOverlay,
- Box,
- MenuItem,
- useColorModeValue,
- useDisclosure,
-} from "@chakra-ui/react"
-import { PencilSimple, TrashSimple } from "@phosphor-icons/react"
-import PlatformCardMenu from "components/[guild]/RolePlatforms/components/PlatformCard/components/PlatformCardMenu"
-import Button from "components/common/Button"
-import { Alert } from "components/common/Modal"
-import { useRef } from "react"
-import useDeleteRoleGroup from "../hooks/useDeleteRoleGroup"
-import EditCampaignModal from "./EditCampaignModal"
-
-type Props = {
- groupId: number
-}
-
-const CampaignCardMenu = ({ groupId }: Props) => {
- const { isOpen, onOpen, onClose } = useDisclosure()
- const {
- isOpen: isDeleteOpen,
- onOpen: onDeleteOpen,
- onClose: onDeleteClose,
- } = useDisclosure()
-
- const removeMenuItemColor = useColorModeValue("red.600", "red.300")
-
- return (
-
-
- } onClick={onOpen}>
- Customize page
-
- }
- color={removeMenuItemColor}
- onClick={onDeleteOpen}
- >
- Delete page
-
-
-
-
-
-
- )
-}
-
-const DeleteCampaignAlert = ({ groupId, isOpen, onClose }) => {
- const { onSubmit: onDeleteRoleGroup, isLoading } = useDeleteRoleGroup(groupId)
- const cancelRef = useRef(null)
-
- return (
-
-
-
- Delete page
-
- Are you sure to delete this page and all roles in it?
-
-
-
- Cancel
-
- onDeleteRoleGroup()}
- isLoading={isLoading}
- colorScheme="red"
- >
- Delete page
-
-
-
-
- )
-}
-
-export default CampaignCardMenu
diff --git a/src/components/[guild]/AccessHub/components/CampaignCards/components/EditCampaignModal.tsx b/src/components/[guild]/AccessHub/components/CampaignCards/components/EditCampaignModal.tsx
deleted file mode 100644
index e4e19b7014..0000000000
--- a/src/components/[guild]/AccessHub/components/CampaignCards/components/EditCampaignModal.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import {
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
-} from "@chakra-ui/react"
-import CampaignForm, {
- CampaignFormType,
-} from "components/[guild]/CreateCampaignModal/components/CampaignForm"
-import useRoleGroup from "components/[guild]/hooks/useRoleGroup"
-import Button from "components/common/Button"
-import { Modal } from "components/common/Modal"
-import usePinata from "hooks/usePinata"
-import useSubmitWithUpload from "hooks/useSubmitWithUpload"
-import { FormProvider, useForm } from "react-hook-form"
-import { Group } from "types"
-import useEditRoleGroup from "../hooks/useEditRoleGroup"
-
-type Props = {
- isOpen: boolean
- onClose: () => void
- groupId: number
- onSuccess: (res: Group) => void
-}
-
-const EditCampaignModal = ({ groupId, onSuccess, ...modalProps }: Props) => {
- const group = useRoleGroup(groupId)
- const { name, imageUrl, description, hideFromGuildPage } = group ?? {}
-
- const methods = useForm({
- mode: "all",
- defaultValues: {
- name,
- imageUrl: imageUrl ?? "",
- description: description ?? "",
- hideFromGuildPage,
- },
- })
- const { handleSubmit } = methods
-
- const iconUploader = usePinata({
- fieldToSetOnSuccess: "imageUrl",
- control: methods.control,
- })
-
- const { onSubmit, isLoading } = useEditRoleGroup(groupId, onSuccess)
-
- const { handleSubmit: handleSubmitWithUpload, isUploadingShown } =
- useSubmitWithUpload(handleSubmit(onSubmit), iconUploader.isUploading)
-
- return (
-
-
-
-
- Edit page
-
-
-
-
-
-
-
-
- Save
-
-
-
-
-
- )
-}
-export default EditCampaignModal
diff --git a/src/components/[guild]/AccessHub/components/CampaignCards/hooks/useDeleteRoleGroup.ts b/src/components/[guild]/AccessHub/components/CampaignCards/hooks/useDeleteRoleGroup.ts
deleted file mode 100644
index ec9a2b67d7..0000000000
--- a/src/components/[guild]/AccessHub/components/CampaignCards/hooks/useDeleteRoleGroup.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import useToast from "hooks/useToast"
-import fetcher from "utils/fetcher"
-
-const useDeleteRoleGroup = (groupId: number) => {
- const { id, mutateGuild } = useGuild()
-
- const toast = useToast()
- const showErrorToast = useShowErrorToast()
-
- const deleteRoleGroup = (signedValidation: SignedValidation) =>
- fetcher(`/v2/guilds/${id}/groups/${groupId}`, {
- ...signedValidation,
- method: "DELETE",
- })
-
- return useSubmitWithSign(deleteRoleGroup, {
- onSuccess: () => {
- toast({
- status: "success",
- title: "Successfully deleted page",
- })
- mutateGuild(
- (curr) => ({
- ...curr,
- groups: curr.groups.filter((group) => group.id !== groupId),
- }),
- { revalidate: false }
- )
- },
- onError: (error) => showErrorToast(error),
- })
-}
-
-export default useDeleteRoleGroup
diff --git a/src/components/[guild]/AccessHub/components/CampaignCards/hooks/useEditRoleGroup.ts b/src/components/[guild]/AccessHub/components/CampaignCards/hooks/useEditRoleGroup.ts
deleted file mode 100644
index 6dc64cde77..0000000000
--- a/src/components/[guild]/AccessHub/components/CampaignCards/hooks/useEditRoleGroup.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import useToast from "hooks/useToast"
-import { Group } from "types"
-import fetcher from "utils/fetcher"
-
-const useEditRoleGroup = (groupId: number, onSuccess: (res: Group) => void) => {
- const { id, mutateGuild } = useGuild()
-
- const toast = useToast()
- const showErrorToast = useShowErrorToast()
-
- const editRoleGroup = (signedValidation: SignedValidation) =>
- fetcher(`/v2/guilds/${id}/groups/${groupId}`, {
- ...signedValidation,
- method: "PUT",
- })
-
- return useSubmitWithSign(editRoleGroup, {
- onSuccess: (response) => {
- toast({
- status: "success",
- title: "Successfully edited page",
- })
- mutateGuild(
- (curr) => ({
- ...curr,
- groups: curr.groups.map((group) =>
- group.id !== groupId ? group : response
- ),
- }),
- { revalidate: false }
- )
- onSuccess(response)
- },
- onError: (error) => showErrorToast(error),
- })
-}
-
-export default useEditRoleGroup
diff --git a/src/components/[guild]/AccessHub/components/CampaignCards/index.ts b/src/components/[guild]/AccessHub/components/CampaignCards/index.ts
deleted file mode 100644
index 5e2c903f88..0000000000
--- a/src/components/[guild]/AccessHub/components/CampaignCards/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import CampaignCards from "./CampaignCards"
-
-export default CampaignCards
diff --git a/src/components/[guild]/AccessHub/components/CreatePageCard.tsx b/src/components/[guild]/AccessHub/components/CreatePageCard.tsx
deleted file mode 100644
index 74b1382db6..0000000000
--- a/src/components/[guild]/AccessHub/components/CreatePageCard.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Button } from "@/components/ui/Button"
-import { Card } from "@/components/ui/Card"
-import { useDisclosure } from "@/hooks/useDisclosure"
-import { FolderSimplePlus, Plus } from "@phosphor-icons/react/dist/ssr"
-import CreateCampaignModal from "components/[guild]/CreateCampaignModal"
-
-const CreatePageCard = () => {
- const { isOpen, onOpen, onClose } = useDisclosure()
-
- return (
-
-
-
-
-
-
-
- Create page
-
- Group roles on a separate page
-
-
-
- }>
- Create page
-
-
-
- )
-}
-
-export { CreatePageCard }
diff --git a/src/components/[guild]/AccessHub/components/EditRewardAvailabilityMenuItem.tsx b/src/components/[guild]/AccessHub/components/EditRewardAvailabilityMenuItem.tsx
deleted file mode 100644
index c12d9a3322..0000000000
--- a/src/components/[guild]/AccessHub/components/EditRewardAvailabilityMenuItem.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { MenuItem, useDisclosure } from "@chakra-ui/react"
-import { Clock } from "@phosphor-icons/react"
-import EditRewardAvailabilityModal from "components/[guild]/RolePlatforms/components/EditRewardAvailabilityModal"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useToast from "hooks/useToast"
-import { PlatformName, PlatformType } from "types"
-import useEditRolePlatform from "../hooks/useEditRolePlatform"
-
-type Props = {
- platformGuildId: string
-}
-
-const EditRewardAvailabilityMenuItem = ({ platformGuildId }: Props) => {
- const { guildPlatforms, roles } = useGuild()
-
- const guildPlatform = guildPlatforms.find(
- (gp) => gp.platformGuildId === platformGuildId
- )
- const rolePlatform = roles
- .flatMap((role) => role.rolePlatforms)
- .find((rp) => rp.guildPlatformId === guildPlatform?.id)
-
- const { isOpen, onOpen, onClose } = useDisclosure()
-
- const toast = useToast()
-
- const { onSubmit, isLoading } = useEditRolePlatform({
- rolePlatformId: rolePlatform?.id,
- onSuccess: () => {
- toast({
- status: "success",
- title: "Successfully updated reward",
- })
- onClose()
- },
- })
-
- return (
- <>
- } onClick={onOpen}>
- Edit availability
-
-
-
- >
- )
-}
-export default EditRewardAvailabilityMenuItem
diff --git a/src/components/[guild]/AccessHub/components/GuildPinRewardCard.tsx b/src/components/[guild]/AccessHub/components/GuildPinRewardCard.tsx
deleted file mode 100644
index 509f67b1b0..0000000000
--- a/src/components/[guild]/AccessHub/components/GuildPinRewardCard.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { Avatar, AvatarImage } from "@/components/ui/Avatar"
-import { Card } from "@/components/ui/Card"
-import { useMintGuildPinContext } from "components/[guild]/Requirements/components/GuildCheckout/MintGuildPinContext"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useGuildPermission from "components/[guild]/hooks/useGuildPermission"
-import dynamic from "next/dynamic"
-
-const DynamicMintGuildPin = dynamic(
- () =>
- import("components/[guild]/Requirements/components/GuildCheckout/MintGuildPin")
-)
-const DynamicMintFuelGuildPin = dynamic(
- () =>
- import(
- "components/[guild]/Requirements/components/GuildCheckout/MintGuildPin/Fuel/MintFuelGuildPin"
- )
-)
-
-const GuildPinRewardCard = () => {
- const { isAdmin } = useGuildPermission()
- const { guildPin } = useGuild()
-
- const { isInvalidImage, isTooSmallImage } = useMintGuildPinContext()
-
- return (
-
-
-
-
-
-
-
- Guild Pin
-
- {!guildPin?.isActive
- ? "Mintable badge of membership"
- : "Onchain badge that shows your support and belonging to this community."}
-
-
-
- {(!(isInvalidImage || isTooSmallImage) || isAdmin) &&
- (guildPin?.chain !== "FUEL" ? (
-
- ) : (
-
- ))}
-
- )
-}
-
-// biome-ignore lint/style/noDefaultExport: we only load this component dynamically, so it's much more convenient to use a default export here
-export default GuildPinRewardCard
diff --git a/src/components/[guild]/AccessHub/components/PlatformAccessButton.tsx b/src/components/[guild]/AccessHub/components/PlatformAccessButton.tsx
deleted file mode 100644
index f33018aab4..0000000000
--- a/src/components/[guild]/AccessHub/components/PlatformAccessButton.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Icon } from "@chakra-ui/react"
-import { ArrowSquareOut } from "@phosphor-icons/react"
-import rewards from "rewards"
-import { RewardCardButton } from "rewards/components/RewardCardButton"
-import { GuildPlatform, PlatformType } from "types"
-import usePlatformAccessButton from "./usePlatformAccessButton"
-
-type Props = {
- platform: GuildPlatform
-}
-
-const PlatformAccessButton = ({ platform }: Props) => {
- const { label, ...buttonProps } = usePlatformAccessButton(platform)
- const { colorScheme, icon } = rewards[PlatformType[platform.platformId]]
-
- return (
- }
- rightIcon={buttonProps.href && }
- colorScheme={colorScheme}
- >
- {label}
-
- )
-}
-
-export default PlatformAccessButton
diff --git a/src/components/[guild]/AccessHub/components/RemovePlatformMenuItem.tsx b/src/components/[guild]/AccessHub/components/RemovePlatformMenuItem.tsx
deleted file mode 100644
index 9467e27e92..0000000000
--- a/src/components/[guild]/AccessHub/components/RemovePlatformMenuItem.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { MenuItem, useColorModeValue, useDisclosure } from "@chakra-ui/react"
-import { TrashSimple } from "@phosphor-icons/react"
-import useRemoveGuildPlatform from "components/[guild]/AccessHub/hooks/useRemoveGuildPlatform"
-import { AlreadyGrantedAccessesWillRemainInfo } from "components/[guild]/RolePlatforms/components/RemovePlatformButton/RemovePlatformButton"
-import useGuild from "components/[guild]/hooks/useGuild"
-import ConfirmationAlert from "components/create-guild/Requirements/components/ConfirmationAlert"
-import rewards from "rewards"
-import { PlatformType } from "types"
-
-type Props = {
- platformGuildId: string
-}
-
-const RemovePlatformMenuItem = ({ platformGuildId }: Props): JSX.Element => {
- const { isOpen, onOpen, onClose } = useDisclosure()
-
- const { guildPlatforms } = useGuild()
- const guildPlatform = guildPlatforms.find(
- (gp) => gp.platformGuildId === platformGuildId
- )
-
- const { isPlatform } = rewards[PlatformType[guildPlatform?.platformId]] ?? {}
-
- const { onSubmit, isLoading, isSigning } = useRemoveGuildPlatform(
- guildPlatform?.id
- )
-
- const color = useColorModeValue("red.600", "red.300")
-
- return (
- <>
- } onClick={onOpen} color={color}>
- Remove reward...
-
-
-
- This reward type will be removed completely from the guild, from all
- roles and pages. If you only want to remove it from the current role,
- please open "Edit role" !
- {isPlatform && }
- >
- }
- confirmationText="Remove"
- />
- >
- )
-}
-
-export default RemovePlatformMenuItem
diff --git a/src/components/[guild]/AccessHub/components/usePlatformAccessButton.tsx b/src/components/[guild]/AccessHub/components/usePlatformAccessButton.tsx
deleted file mode 100644
index c477314bf5..0000000000
--- a/src/components/[guild]/AccessHub/components/usePlatformAccessButton.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { ButtonProps, LinkProps } from "@chakra-ui/react"
-import { useConnectPlatform } from "components/[guild]/JoinModal/hooks/useConnectPlatform"
-import useUser from "components/[guild]/hooks/useUser"
-import useToast from "hooks/useToast"
-import rewards from "rewards"
-import { GuildPlatform, PlatformName, PlatformType } from "types"
-
-function sanitizeInviteLink(inviteLink: string) {
- if (inviteLink.startsWith("https://")) {
- return inviteLink
- }
-
- if (inviteLink.startsWith("http://")) {
- return inviteLink.replace("http://", "https://")
- }
-
- return `https://${inviteLink}`
-}
-
-const usePlatformAccessButton = (
- platform: GuildPlatform
-): { label: string } & LinkProps & ButtonProps => {
- const { platformUsers } = useUser()
- const platformName: PlatformName = PlatformType[
- platform.platformId
- ] as PlatformName
-
- const toast = useToast()
- const onSuccess = () =>
- toast({
- title: `Successfully connected ${rewards[platformName].name}`,
- description: `You can now go to ${rewards[platformName].name} and enjoy your access(es)`,
- status: "success",
- })
-
- const { onConnect, isLoading, loadingText, response } = useConnectPlatform(
- platformName,
- onSuccess
- )
-
- const platformFromDb = platformUsers?.some(
- (platformAccount) => platformAccount.platformName === platformName
- )
-
- if (!platformFromDb && !response)
- return {
- label: "Connect to claim access",
- onClick: onConnect,
- isLoading: isLoading,
- loadingText: loadingText,
- }
-
- if (platform.invite)
- return {
- label: `Go to ${rewards[platformName].gatedEntity}`,
- as: "a",
- target: "_blank",
- href: sanitizeInviteLink(platform.invite),
- }
-
- return {
- label: "Couldn't fetch link",
- isDisabled: true,
- }
-}
-
-export default usePlatformAccessButton
diff --git a/src/components/[guild]/AccessHub/hooks/useAccessedGuildPoints.ts b/src/components/[guild]/AccessHub/hooks/useAccessedGuildPoints.ts
deleted file mode 100644
index bae9a626e8..0000000000
--- a/src/components/[guild]/AccessHub/hooks/useAccessedGuildPoints.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import useGuildPermission from "components/[guild]/hooks/useGuildPermission"
-import useMembership from "components/explorer/hooks/useMembership"
-import { PlatformType } from "types"
-
-type GuildPointsAccessFilter = "ALL" | "ACCESSED_ONLY"
-
-/**
- * Custom hook to filter guild platforms based on role access and optionally,
- * additional privacy settings for points rewards.
- *
- * @param filter - ACCESSED_ONLY: apply access filter to every points platform (if
- * the user does not have any roles that reward the point type, the point type will
- * be filtered out)
- *
- * ALL: apply access filter only to private points platforms (if the user does not
- * have any roles that reward the point type, the point type will _still be
- * included_, except for when it is set as private reward by all roles)
- */
-export const useAccessedGuildPoints = (
- filter: GuildPointsAccessFilter = "ACCESSED_ONLY"
-) => {
- const { guildPlatforms, roles } = useGuild()
- const { roleIds } = useMembership()
- const { isAdmin } = useGuildPermission()
-
- const accessedGuildPoints =
- guildPlatforms?.filter((gp) => {
- if (gp.platformId !== PlatformType.POINTS) return false
- if (isAdmin) return true
-
- const visibleRelatedRolePlatformsToUser = roles
- ?.flatMap((role) => role.rolePlatforms)
- ?.filter((rp) => {
- if (rp.guildPlatformId !== gp.id || rp.visibility === "HIDDEN")
- return false
-
- if (filter === "ALL" && rp.visibility === "PUBLIC") return true
-
- return roleIds?.includes(rp.roleId)
- })
-
- return visibleRelatedRolePlatformsToUser.length > 0
- }) || []
-
- return accessedGuildPoints
-}
diff --git a/src/components/[guild]/AccessHub/hooks/useEditGuildPlatform.ts b/src/components/[guild]/AccessHub/hooks/useEditGuildPlatform.ts
deleted file mode 100644
index 4b628a1c23..0000000000
--- a/src/components/[guild]/AccessHub/hooks/useEditGuildPlatform.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { ContractCallFunction } from "components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddContractCallPanel/components/CreateNftForm/hooks/useCreateNft"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useGuildPlatform from "components/[guild]/hooks/useGuildPlatform"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import useSubmit from "hooks/useSubmit"
-import { CAPACITY_TIME_PLATFORMS } from "rewards"
-import { GuildPlatform, PlatformName, PlatformType } from "types"
-
-type PartialGuildPlatform = Partial<
- Omit & {
- platformGuildData: Partial
- }
->
-
-const useEditGuildPlatform = ({
- guildPlatformId,
- onSuccess,
-}: {
- guildPlatformId: number
- onSuccess?: () => void
-}) => {
- const { id, mutateGuild } = useGuild()
- const { guildPlatform: originalGuildPlatform } = useGuildPlatform(guildPlatformId)
-
- const showErrorToast = useShowErrorToast()
-
- const fetcherWithSign = useFetcherWithSign()
- const submit = async (data: Partial) =>
- fetcherWithSign([
- `/v2/guilds/${id}/guild-platforms/${guildPlatformId}`,
- {
- method: "PUT",
- /**
- * We need to send the whole platformGuildData here, since our API replaces
- * this object with the data we send inside the request body
- */
- body: {
- platformGuildData: {
- ...originalGuildPlatform.platformGuildData,
- ...data.platformGuildData,
- },
- },
- },
- ])
-
- return useSubmit(submit, {
- onSuccess: (response) => {
- onSuccess?.()
-
- const isLegacyContractCallReward =
- response.platformGuildData?.function ===
- ContractCallFunction.DEPRECATED_SIMPLE_CLAIM
-
- mutateGuild(
- (prevGuild) => ({
- ...prevGuild,
- guildPlatforms: prevGuild.guildPlatforms.map((gp) => {
- if (gp.id === guildPlatformId) return response
- return gp
- }),
- roles:
- CAPACITY_TIME_PLATFORMS.includes(
- PlatformType[response.platformId] as PlatformName
- ) || isLegacyContractCallReward
- ? prevGuild.roles.map((role) => {
- if (
- !role.rolePlatforms?.some(
- (rp) => rp.guildPlatformId === guildPlatformId
- )
- )
- return role
-
- return {
- ...role,
- rolePlatforms: role.rolePlatforms.map((rp) => {
- if (rp.guildPlatformId !== guildPlatformId) return rp
-
- return {
- ...rp,
- capacity:
- response.platformGuildData?.texts?.length ?? rp.capacity,
- }
- }),
- }
- })
- : prevGuild.roles,
- }),
- { revalidate: false }
- )
- },
- onError: (error) => showErrorToast(error),
- })
-}
-
-export default useEditGuildPlatform
diff --git a/src/components/[guild]/AccessHub/hooks/useEditRolePlatform.ts b/src/components/[guild]/AccessHub/hooks/useEditRolePlatform.ts
deleted file mode 100644
index 542e041e9e..0000000000
--- a/src/components/[guild]/AccessHub/hooks/useEditRolePlatform.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import { RolePlatform } from "types"
-import fetcher from "utils/fetcher"
-
-const useEditRolePlatform = ({
- rolePlatformId,
- onSuccess,
-}: {
- rolePlatformId: number
- onSuccess?: () => void
-}) => {
- const { id, roles, mutateGuild } = useGuild()
- const roleId = roles.find(
- (role) => !!role.rolePlatforms.find((rp) => rp.id === rolePlatformId)
- )?.id
-
- const showErrorToast = useShowErrorToast()
-
- const submit = async (signedValidation: SignedValidation) =>
- fetcher(`/v2/guilds/${id}/roles/${roleId}/role-platforms/${rolePlatformId}`, {
- method: "PUT",
- ...signedValidation,
- })
-
- return useSubmitWithSign(submit, {
- onSuccess: (response) => {
- onSuccess?.()
-
- mutateGuild(
- (prevGuild) => ({
- ...prevGuild,
- roles: prevGuild.roles.map((role) => {
- if (role.id !== roleId) return role
-
- return {
- ...role,
- rolePlatforms: role.rolePlatforms.map((rp) => {
- if (rp.id !== rolePlatformId) return { ...rp, roleId: role.id }
- return { ...response, roleId: role.id }
- }),
- }
- }),
- }),
- { revalidate: false }
- )
- },
- onError: (error) => showErrorToast(error),
- })
-}
-
-export default useEditRolePlatform
diff --git a/src/components/[guild]/AccessHub/hooks/useRemoveGuildPlatform.ts b/src/components/[guild]/AccessHub/hooks/useRemoveGuildPlatform.ts
deleted file mode 100644
index cb21cfd9b0..0000000000
--- a/src/components/[guild]/AccessHub/hooks/useRemoveGuildPlatform.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import { UseSubmitOptions } from "hooks/useSubmit/types"
-import useToast from "hooks/useToast"
-import fetcher from "utils/fetcher"
-
-const useRemoveGuildPlatform = (
- guildPlatformId: number,
- { onSuccess, onError }: UseSubmitOptions = {}
-) => {
- const { id, mutateGuild } = useGuild()
- const toast = useToast()
- const showErrorToast = useShowErrorToast()
-
- const submit = async (signedValidation: SignedValidation) =>
- fetcher(`/v2/guilds/${id}/guild-platforms/${guildPlatformId}`, {
- method: "DELETE",
- ...signedValidation,
- })
-
- return useSubmitWithSign(submit, {
- forcePrompt: true,
- onSuccess: (res) => {
- toast({
- title: "Reward removed!",
- status: "success",
- })
-
- mutateGuild(
- (prevGuild) => ({
- ...prevGuild,
- guildPlatforms:
- prevGuild.guildPlatforms?.filter(
- (prevGuildPlatform) => prevGuildPlatform.id !== guildPlatformId
- ) ?? [],
- roles:
- prevGuild.roles?.map((prevRole) => ({
- ...prevRole,
- rolePlatforms:
- prevRole.rolePlatforms?.filter(
- (prevRolePlatform) =>
- prevRolePlatform.guildPlatformId !== guildPlatformId
- ) ?? [],
- })) ?? [],
- }),
- { revalidate: false }
- )
- onSuccess?.(res)
- },
- onError: (error) => {
- showErrorToast(error)
- onError?.(error)
- },
- })
-}
-
-export default useRemoveGuildPlatform
diff --git a/src/components/[guild]/AccessHub/hooks/useTokenRewards.ts b/src/components/[guild]/AccessHub/hooks/useTokenRewards.ts
deleted file mode 100644
index 18b573fd75..0000000000
--- a/src/components/[guild]/AccessHub/hooks/useTokenRewards.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import useMembership from "components/explorer/hooks/useMembership"
-import { PlatformType } from "types"
-
-export const useTokenRewards = (
- accessedOnly?: boolean,
- pointPlatformId?: number
-) => {
- const { guildPlatforms, roles } = useGuild()
- const { roleIds } = useMembership()
-
- const tokenRewards = guildPlatforms?.filter(
- (gp) => gp.platformId === PlatformType.ERC20
- )
-
- if (!pointPlatformId && !accessedOnly) return tokenRewards || []
-
- const accessedTokenRewards = tokenRewards?.filter((gp) => {
- const relevantRoles = roles.filter((role) =>
- role.rolePlatforms.find(
- (rp) =>
- rp.guildPlatformId === gp.id &&
- (pointPlatformId
- ? rp.platformRoleData?.pointGuildPlatformId == pointPlatformId
- : true)
- )
- )
- if (!accessedOnly) return !!relevantRoles.length
-
- const accessedRolesWithReward = relevantRoles.filter((role) =>
- roleIds?.includes(role.id)
- )
-
- return accessedRolesWithReward.length
- })
-
- return accessedTokenRewards || []
-}
diff --git a/src/components/[guild]/AccessHub/index.ts b/src/components/[guild]/AccessHub/index.ts
deleted file mode 100644
index 71b7006523..0000000000
--- a/src/components/[guild]/AccessHub/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { AccessHub } from "./AccessHub"
-
-export { AccessHub }
diff --git a/src/components/[guild]/ActiveStatusUpdates.tsx b/src/components/[guild]/ActiveStatusUpdates.tsx
deleted file mode 100644
index de06d35a45..0000000000
--- a/src/components/[guild]/ActiveStatusUpdates.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import {
- Alert,
- AlertDescription,
- AlertIcon,
- AlertTitle,
- Collapse,
- Spinner,
- Stack,
-} from "@chakra-ui/react"
-import Card from "components/common/Card"
-import useActiveStatusUpdates from "hooks/useActiveStatusUpdates"
-
-const ActiveStatusUpdates = () => {
- const { status } = useActiveStatusUpdates()
-
- return (
-
-
-
-
-
- Syncing members
-
- Roles & accesses may not be accurate. This can take a few hours, please
- be patient.
-
-
-
-
-
- )
-}
-
-export default ActiveStatusUpdates
diff --git a/src/components/[guild]/AddAndOrderRoles/AddAndOrderRoles.tsx b/src/components/[guild]/AddAndOrderRoles/AddAndOrderRoles.tsx
deleted file mode 100644
index 2a93f80405..0000000000
--- a/src/components/[guild]/AddAndOrderRoles/AddAndOrderRoles.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import {
- ButtonGroup,
- Divider,
- Icon,
- IconButton,
- Menu,
- MenuButton,
- MenuItem,
- MenuList,
- useDisclosure,
-} from "@chakra-ui/react"
-import { CaretDown, ListNumbers, Plus } from "@phosphor-icons/react"
-import Button from "components/common/Button"
-import useIsStuck from "hooks/useIsStuck"
-import { useEffect, useRef } from "react"
-import { RecheckAccessesButton } from "../RecheckAccessesButton"
-import useGuild from "../hooks/useGuild"
-import AddRoleDrawer from "./components/AddRoleDrawer"
-import OrderRolesModal from "./components/OrderRolesModal"
-
-const AddAndOrderRoles = ({ setIsStuck = null }): JSX.Element => {
- const {
- isOpen: isAddDrawerOpen,
- onOpen: onAddDrawerOpen,
- onClose: onAddDrawerClose,
- } = useDisclosure()
- const {
- isOpen: isOrderModalOpen,
- onOpen: onOrderModalOpen,
- onClose: onOrderModalClose,
- } = useDisclosure()
-
- const orderButtonRef = useRef(null)
- const { ref: addRoleButtonRef, isStuck } = useIsStuck()
- useEffect(() => {
- setIsStuck?.(isStuck)
- }, [isStuck, setIsStuck])
-
- /**
- * Passing role IDs as the OrderRolesModal key, so we re-populate the form's default values when the admin adds a new role to their guild
- */
- const { roles } = useGuild()
- const orderRolesModalKey = roles?.map((r) => r.id).join("-")
-
- return (
- <>
-
- }
- onClick={onAddDrawerOpen}
- data-test="add-role-button"
- >
- Add role
-
-
-
-
-
- }
- borderTopLeftRadius="0"
- borderBottomLeftRadius="0"
- />
-
- }
- >
- Reorder roles
-
-
-
-
-
-
- >
- )
-}
-
-export default AddAndOrderRoles
diff --git a/src/components/[guild]/AddAndOrderRoles/components/AddRoleDrawer.tsx b/src/components/[guild]/AddAndOrderRoles/components/AddRoleDrawer.tsx
deleted file mode 100644
index e00e4b0409..0000000000
--- a/src/components/[guild]/AddAndOrderRoles/components/AddRoleDrawer.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import {
- Box,
- Drawer,
- DrawerBody,
- DrawerContent,
- DrawerFooter,
- DrawerOverlay,
- FormLabel,
- HStack,
- VStack,
- useDisclosure,
-} from "@chakra-ui/react"
-import { ClientStateRequirementHandlerProvider } from "components/[guild]/RequirementHandlerContext"
-import AddRolePlatforms from "components/[guild]/RolePlatforms/AddRolePlatforms"
-import Button from "components/common/Button"
-import DiscardAlert from "components/common/DiscardAlert"
-import Section from "components/common/Section"
-import Description from "components/create-guild/Description"
-import DynamicDevTool from "components/create-guild/DynamicDevTool"
-import IconSelector from "components/create-guild/IconSelector"
-import Name from "components/create-guild/Name"
-import SetRequirements from "components/create-guild/Requirements"
-import useJsConfetti from "components/create-guild/hooks/useJsConfetti"
-import useCreateRRR from "hooks/useCreateRRR"
-import usePinata from "hooks/usePinata"
-import useSubmitWithUpload from "hooks/useSubmitWithUpload"
-import useToast from "hooks/useToast"
-import useWarnIfUnsavedChanges from "hooks/useWarnIfUnsavedChanges"
-import { useRef } from "react"
-import { FormProvider } from "react-hook-form"
-import useAddRoleForm from "../hooks/useAddRoleForm"
-import AddRoleDrawerHeader from "./AddRoleDrawerHeader"
-
-const AddRoleDrawer = ({ isOpen, onClose, finalFocusRef }): JSX.Element => {
- const methods = useAddRoleForm()
-
- /**
- * TODO: for some reason, isDirty was true & dirtyFields was an empty object and I
- * couldn't find the underlying problem, so used this workaround here, but we
- * should definitely find out what causes this strange behaviour!
- */
- const isDirty = Object.values(methods.formState.dirtyFields).length > 0
- useWarnIfUnsavedChanges(isDirty && !methods.formState.isSubmitted)
-
- const {
- isOpen: isAlertOpen,
- onOpen: onAlertOpen,
- onClose: onAlertClose,
- } = useDisclosure()
-
- const onCloseAndClear = () => {
- methods.reset(methods.defaultValues)
- onAlertClose()
- onClose()
- }
-
- const iconUploader = usePinata({
- fieldToSetOnSuccess: "imageUrl",
- fieldToSetOnError: "imageUrl",
- control: methods.control,
- })
-
- const drawerBodyRef = useRef()
- const toast = useToast()
- const triggerConfetti = useJsConfetti()
-
- const { onSubmit, isLoading, loadingText } = useCreateRRR({
- onSuccess: () => {
- triggerConfetti()
- toast({
- title: "Role successfully created!",
- status: "success",
- })
- methods.reset(methods.defaultValues)
- onClose()
- },
- })
-
- const { handleSubmit, isUploadingShown, uploadLoadingText } = useSubmitWithUpload(
- methods.handleSubmit(onSubmit, (formErrors) => {
- if (formErrors.requirements && drawerBodyRef.current) {
- drawerBodyRef.current.scrollBy({
- top: drawerBodyRef.current.scrollHeight,
- behavior: "smooth",
- })
- }
- }),
- iconUploader.isUploading
- )
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- Choose a logo and name for your role
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Cancel
-
-
- Save
-
-
-
-
-
-
-
- >
- )
-}
-
-export default AddRoleDrawer
diff --git a/src/components/[guild]/AddAndOrderRoles/components/AddRoleDrawerHeader.tsx b/src/components/[guild]/AddAndOrderRoles/components/AddRoleDrawerHeader.tsx
deleted file mode 100644
index 272a48cd6d..0000000000
--- a/src/components/[guild]/AddAndOrderRoles/components/AddRoleDrawerHeader.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Box } from "@chakra-ui/react"
-import SetVisibility from "components/[guild]/SetVisibility"
-import useVisibilityModalProps from "components/[guild]/SetVisibility/hooks/useVisibilityModalProps"
-import DrawerHeader from "components/common/DrawerHeader"
-import { useFormContext } from "react-hook-form"
-import { RoleFormType } from "types"
-
-const AddRoleDrawerHeader = () => {
- const setVisibilityModalProps = useVisibilityModalProps()
-
- const { setValue, getValues } = useFormContext()
-
- const handleVisibilitySave = ({ visibility, visibilityRoleId }) => {
- setValue("visibility", visibility)
- setValue("visibilityRoleId", visibilityRoleId)
- setVisibilityModalProps.onClose()
- }
-
- return (
-
-
-
-
-
- )
-}
-
-export default AddRoleDrawerHeader
diff --git a/src/components/[guild]/AddAndOrderRoles/components/DraggableRoleCard.tsx b/src/components/[guild]/AddAndOrderRoles/components/DraggableRoleCard.tsx
deleted file mode 100644
index e32b32cad5..0000000000
--- a/src/components/[guild]/AddAndOrderRoles/components/DraggableRoleCard.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { HStack, Heading, Icon, Spacer } from "@chakra-ui/react"
-import { DotsSixVertical } from "@phosphor-icons/react"
-import { MemberCount } from "components/[guild]/RoleCard/components/MemberCount"
-import { Visibility } from "components/[guild]/Visibility"
-import Card from "components/common/Card"
-import GuildLogo from "components/common/GuildLogo"
-import { Role } from "types"
-
-type Props = {
- role: Role
-}
-
-const DraggableRoleCard = ({ role }: Props) => {
- if (!role) return null
-
- return (
-
-
-
-
-
-
- {role.name}
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-export default DraggableRoleCard
diff --git a/src/components/[guild]/AddAndOrderRoles/components/NewRolePlatformCard.tsx b/src/components/[guild]/AddAndOrderRoles/components/NewRolePlatformCard.tsx
deleted file mode 100644
index 779e8d404e..0000000000
--- a/src/components/[guild]/AddAndOrderRoles/components/NewRolePlatformCard.tsx
+++ /dev/null
@@ -1,198 +0,0 @@
-import { CloseButton, useColorModeValue, useDisclosure } from "@chakra-ui/react"
-import AvailabilitySetup from "components/[guild]/AddRewardButton/components/AvailabilitySetup"
-import DynamicTag from "components/[guild]/RoleCard/components/DynamicReward/DynamicTag"
-import { ContractCallFunction } from "components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddContractCallPanel/components/CreateNftForm/hooks/useCreateNft"
-import EditRolePlatformModal from "components/[guild]/RolePlatforms/components/EditRolePlatformModal"
-import PlatformCard from "components/[guild]/RolePlatforms/components/PlatformCard"
-import { RolePlatformProvider } from "components/[guild]/RolePlatforms/components/RolePlatformProvider"
-import SetVisibility from "components/[guild]/SetVisibility"
-import useVisibilityModalProps from "components/[guild]/SetVisibility/hooks/useVisibilityModalProps"
-import useGuild from "components/[guild]/hooks/useGuild"
-import Button from "components/common/Button"
-import { useFormContext, useWatch } from "react-hook-form"
-import { CAPACITY_TIME_PLATFORMS } from "rewards"
-import { cardSettings } from "rewards/CardSettings"
-import NftAvailabilityTags from "rewards/ContractCall/components/NftAvailabilityTags"
-import { cardPropsHooks } from "rewards/cardPropsHooks"
-import {
- GuildPlatformWithOptionalId,
- PlatformName,
- PlatformType,
- RolePlatform,
-} from "types"
-
-type Props = {
- rolePlatform: RolePlatform
- remove: () => void
-}
-
-const NewRolePlatformCard = ({ rolePlatform, remove }: Props) => {
- const { guildPlatforms } = useGuild()
- const { setValue } = useFormContext()
-
- const setVisibilityModalProps = useVisibilityModalProps()
- const removeButtonColor = useColorModeValue("gray.700", "gray.400")
-
- let guildPlatform: GuildPlatformWithOptionalId, type: PlatformName
- if (rolePlatform.guildPlatformId) {
- guildPlatform = guildPlatforms.find(
- (platform) => platform.id === rolePlatform.guildPlatformId
- )
- type = PlatformType[guildPlatform?.platformId] as PlatformName
- } else {
- guildPlatform = rolePlatform.guildPlatform
- type = guildPlatform?.platformName
- }
-
- const {
- isOpen: isEditOpen,
- onClose: onEditClose,
- onOpen: onEditOpen,
- } = useDisclosure()
-
- const rolePlatformData = useWatch({ name: `rolePlatforms.${rolePlatform.id}` })
-
- if (!type) return null
-
- const isLegacyContractCallReward =
- type === "CONTRACT_CALL" &&
- guildPlatform.platformGuildData.function ===
- ContractCallFunction.DEPRECATED_SIMPLE_CLAIM
-
- const cardSettingsComponent = cardSettings[type]
- const useCardProps = cardPropsHooks[type]
-
- const shouldRenderCustomContentRow =
- CAPACITY_TIME_PLATFORMS.includes(type) ||
- type === "CONTRACT_CALL" ||
- isLegacyContractCallReward ||
- !!rolePlatform.dynamicAmount
-
- return (
-
- {
- setValue(`rolePlatforms.${rolePlatform.id}.visibility`, visibility, {
- shouldDirty: true,
- })
- setValue(
- `rolePlatforms.${rolePlatform.id}.visibilityRoleId`,
- visibilityRoleId,
- {
- shouldDirty: true,
- }
- )
- setVisibilityModalProps.onClose()
- }}
- {...setVisibilityModalProps}
- />
- }
- cornerButton={
-
- }
- actionRow={
- cardSettingsComponent && (
- <>
-
- Edit
-
- {
- setValue(
- `rolePlatforms.${rolePlatform.id}`,
- {
- ...rolePlatformData,
- ...data,
- },
- { shouldDirty: true }
- )
- onEditClose()
- }}
- />
- >
- )
- }
- contentRow={
- shouldRenderCustomContentRow ? (
- <>
- {CAPACITY_TIME_PLATFORMS.includes(type) ||
- isLegacyContractCallReward ? (
- {
- setValue(`rolePlatforms.${rolePlatform.id}.capacity`, capacity, {
- shouldDirty: true,
- })
- setValue(
- `rolePlatforms.${rolePlatform.id}.startTime`,
- startTime,
- {
- shouldDirty: true,
- }
- )
- setValue(`rolePlatforms.${rolePlatform.id}.endTime`, endTime, {
- shouldDirty: true,
- })
- }}
- />
- ) : type === "CONTRACT_CALL" ? (
-
- ) : null}
- {!!rolePlatform.dynamicAmount && (
-
- )}
- >
- ) : undefined
- }
- />
-
- )
-}
-
-export default NewRolePlatformCard
diff --git a/src/components/[guild]/AddAndOrderRoles/components/OrderRolesModal.tsx b/src/components/[guild]/AddAndOrderRoles/components/OrderRolesModal.tsx
deleted file mode 100644
index 5c808ed703..0000000000
--- a/src/components/[guild]/AddAndOrderRoles/components/OrderRolesModal.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import {
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Text,
- useDisclosure,
-} from "@chakra-ui/react"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useRoleGroup from "components/[guild]/hooks/useRoleGroup"
-import Button from "components/common/Button"
-import DiscardAlert from "components/common/DiscardAlert"
-import { Modal } from "components/common/Modal"
-import { Reorder } from "framer-motion"
-import { useController, useForm } from "react-hook-form"
-import useReorderRoles from "../hooks/useReorderRoles"
-import DraggableRoleCard from "./DraggableRoleCard"
-
-type OrderRolesForm = {
- roleIds: number[]
-}
-
-const OrderRolesModal = ({ isOpen, onClose, finalFocusRef }): JSX.Element => {
- const { roles } = useGuild()
- const group = useRoleGroup()
- const relevantRoles = group
- ? roles?.filter((role) => role.groupId === group.id)
- : roles?.filter((role) => !role.groupId)
-
- const {
- isOpen: isAlertOpen,
- onOpen: onAlertOpen,
- onClose: onAlertClose,
- } = useDisclosure()
-
- const publicAndSecretRoles = relevantRoles?.filter(
- (role) => role.visibility !== "HIDDEN"
- )
-
- const defaultRoleIdsOrder = publicAndSecretRoles?.map((role) => role.id)
-
- const {
- control,
- reset,
- formState: { isDirty },
- handleSubmit,
- } = useForm({
- mode: "all",
- defaultValues: { roleIds: defaultRoleIdsOrder },
- })
- const {
- field: { value: roleIdsOrder, onChange: onRoleIdsOrderChange },
- } = useController({
- control,
- name: "roleIds",
- })
-
- const { isLoading, onSubmit } = useReorderRoles(onClose)
-
- const onChangeRoleOrders = (data: OrderRolesForm) => {
- const changedRoles = data.roleIds
- .map((roleId, i) => ({
- id: roleId,
- position: i,
- }))
- .filter(({ id: roleId, position }) =>
- (relevantRoles ?? []).some(
- (prevRole) => prevRole.id === roleId && prevRole.position !== position
- )
- )
-
- return onSubmit(changedRoles)
- }
-
- const onCloseAndClear = () => {
- reset({ roleIds: defaultRoleIdsOrder })
- onClose()
- onAlertClose()
- }
-
- return (
- <>
-
-
-
- Role order
-
-
- onRoleIdsOrderChange(newOrder)}
- >
- {relevantRoles?.length ? (
- roleIdsOrder?.map((roleId) => (
-
- role.id === roleId)}
- />
-
- ))
- ) : (
- No roles yet
- )}
-
-
-
-
-
- Cancel
-
-
- Save
-
-
-
-
-
- >
- )
-}
-
-export default OrderRolesModal
diff --git a/src/components/[guild]/AddAndOrderRoles/hooks/useAddRoleForm.ts b/src/components/[guild]/AddAndOrderRoles/hooks/useAddRoleForm.ts
deleted file mode 100644
index d7f953597b..0000000000
--- a/src/components/[guild]/AddAndOrderRoles/hooks/useAddRoleForm.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import { RoleToCreate } from "components/create-guild/hooks/useCreateRole"
-import { useForm } from "react-hook-form"
-import { RoleFormType } from "types"
-import getRandomInt from "utils/getRandomInt"
-
-const useAddRoleForm = () => {
- const { id } = useGuild()
-
- const defaultValues: RoleToCreate = {
- guildId: id as number, // Safe to cast here, since we'll have guildId by the time we call this hook
- name: "",
- description: "",
- logic: "AND",
- requirements: [
- {
- type: "FREE",
- },
- ],
- roleType: "NEW",
- imageUrl: `/guildLogos/${getRandomInt(286)}.svg`,
- visibility: "PUBLIC",
- rolePlatforms: [],
- }
-
- const methods = useForm({
- mode: "all",
- defaultValues,
- })
-
- return { ...methods, defaultValues }
-}
-
-export default useAddRoleForm
diff --git a/src/components/[guild]/AddAndOrderRoles/hooks/useReorderRoles.ts b/src/components/[guild]/AddAndOrderRoles/hooks/useReorderRoles.ts
deleted file mode 100644
index 6af81175a5..0000000000
--- a/src/components/[guild]/AddAndOrderRoles/hooks/useReorderRoles.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import useSubmit from "hooks/useSubmit/useSubmit"
-import useToast from "hooks/useToast"
-
-const useReorderRoles = (onClose) => {
- const { id, mutateGuild } = useGuild()
- const toast = useToast()
- const showErrorToast = useShowErrorToast()
- const fetcherWithSign = useFetcherWithSign()
-
- const submit = (roleOrdering: Array<{ id: number; position: number }>) =>
- Promise.all(
- roleOrdering.map(({ id: roleId, position }) =>
- fetcherWithSign([
- `/v2/guilds/${id}/roles/${roleId}`,
- { method: "PUT", body: { position } },
- ])
- )
- )
-
- const { onSubmit, isLoading } = useSubmit(submit, {
- onSuccess: (newRoles) => {
- toast({
- status: "success",
- title: "Successfully edited role order",
- })
- onClose()
- mutateGuild(
- (oldData) => ({
- ...oldData,
- // requirements, and rolePlatforms are not returned, so we need to spread older data too
- // Plus, we don't update all the roles, only the ones that changed, so this also retains those that weren't updated
- roles: (oldData?.roles ?? [])
- .map((prevRole) => ({
- ...prevRole,
- ...(newRoles ?? []).find((newRole) => newRole.id === prevRole.id),
- }))
- .sort(
- (role1, role2) =>
- (role1.position ?? Infinity) - (role2.position ?? Infinity)
- ),
- }),
- {
- revalidate: false,
- }
- )
- },
- onError: (error) => showErrorToast(error),
- })
- return { onSubmit, isLoading }
-}
-
-export default useReorderRoles
diff --git a/src/components/[guild]/AddAndOrderRoles/index.ts b/src/components/[guild]/AddAndOrderRoles/index.ts
deleted file mode 100644
index 2c07a4a319..0000000000
--- a/src/components/[guild]/AddAndOrderRoles/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from "./AddAndOrderRoles"
diff --git a/src/components/[guild]/AddRewardButton/SelectRolePanel.tsx b/src/components/[guild]/AddRewardButton/SelectRolePanel.tsx
deleted file mode 100644
index 065a484c0f..0000000000
--- a/src/components/[guild]/AddRewardButton/SelectRolePanel.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import { usePostHogContext } from "@/components/Providers/PostHogProvider"
-import {
- HStack,
- IconButton,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- Stack,
- Text,
- Tooltip,
- useColorModeValue,
-} from "@chakra-ui/react"
-import { Visibility } from "@guildxyz/types"
-import { ArrowLeft, Info } from "@phosphor-icons/react"
-import Button from "components/common/Button"
-import useJsConfetti from "components/create-guild/hooks/useJsConfetti"
-import useCreateRRR, { SubmitData } from "hooks/useCreateRRR"
-import useToast from "hooks/useToast"
-import { useState } from "react"
-import { useFormContext, useWatch } from "react-hook-form"
-import rewards, { CAPACITY_TIME_PLATFORMS } from "rewards"
-import { RewardPreviews } from "rewards/RewardPreviews"
-import SelectRoleOrSetRequirements from "rewards/components/SelectRoleOrSetRequirements"
-import { RoleTypeToAddTo, useAddRewardContext } from "../AddRewardContext"
-import useGuild from "../hooks/useGuild"
-import AvailabilitySetup from "./components/AvailabilitySetup"
-import { ADD_REWARD_FORM_DEFAULT_VALUES } from "./constants"
-
-const SelectRolePanel = ({
- onSuccess,
-}: {
- onSuccess?: Parameters[0]["onSuccess"]
-}) => {
- const { modalRef, selection, activeTab, setStep, isBackButtonDisabled } =
- useAddRewardContext()
-
- const { urlName } = useGuild()
- const { captureEvent } = usePostHogContext()
-
- const lightModalBgColor = useColorModeValue("white", "gray.700")
-
- const methods = useFormContext()
- const rolePlatform = methods.getValues("rolePlatforms.0")
-
- const requirements = useWatch({ name: "requirements", control: methods.control })
- const roleIds = useWatch({ name: "roleIds", control: methods.control })
-
- const postHogOptions = {
- guild: urlName,
- type: activeTab,
- requirements: requirements,
- roleIds: roleIds,
- }
- const toast = useToast()
- const triggerConfetti = useJsConfetti()
-
- const { onSubmit, isLoading } = useCreateRRR({
- onSuccess: (res) => {
- triggerConfetti()
- toast({
- title: "Reward successfully created!",
- status: "success",
- })
- captureEvent("reward created (AddRewardButton)", postHogOptions)
- onSuccess?.(res)
- },
- })
-
- const [saveAsDraft, setSaveAsDraft] = useState(false)
-
- const isAddRewardButtonDisabled =
- activeTab === RoleTypeToAddTo.NEW_ROLE ? !requirements?.length : !roleIds?.length
-
- const RewardPreview = RewardPreviews.hasOwnProperty(selection)
- ? RewardPreviews[selection as keyof typeof RewardPreviews]
- : null
-
- const goBack = () => {
- if (!rewards[selection].autoRewardSetup)
- methods.reset(ADD_REWARD_FORM_DEFAULT_VALUES)
- setStep("REWARD_SETUP")
- }
-
- return (
-
-
-
-
-
- }
- variant="ghost"
- onClick={goBack}
- />
- {`Add ${rewards[selection]?.name} reward`}
-
-
- {RewardPreview && (
-
- {CAPACITY_TIME_PLATFORMS.includes(selection) && (
- {
- methods.setValue(`rolePlatforms.0.capacity`, capacity)
- methods.setValue(`rolePlatforms.0.startTime`, startTime)
- methods.setValue(`rolePlatforms.0.endTime`, endTime)
- }}
- />
- )}
-
- )}
-
-
-
-
-
-
-
-
- {
- setSaveAsDraft(true)
- const draftData = changeDataToDraft(data as SubmitData)
- onSubmit(draftData)
- })}
- isLoading={saveAsDraft && isLoading}
- rightIcon={
-
-
-
- }
- >
- Save as draft
-
-
- Save
-
-
-
- )
-}
-
-const changeDataToDraft = (data: SubmitData): SubmitData => {
- if (!data.roleIds?.length) {
- return { ...data, visibility: "HIDDEN" as Visibility }
- }
-
- const { rolePlatforms, requirements, roleIds } = data
-
- const hiddenRolePlatforms = rolePlatforms.map((rp) => ({
- ...rp,
- visibility: "HIDDEN" as Visibility,
- }))
- const hiddenRequirements = requirements.map((req) =>
- req.type === "FREE" ? req : { ...req, visibility: "HIDDEN" as Visibility }
- )
-
- return {
- rolePlatforms: hiddenRolePlatforms,
- requirements: hiddenRequirements,
- roleIds,
- }
-}
-
-export default SelectRolePanel
diff --git a/src/components/[guild]/AddRewardButton/components/AvailabilitySetup.tsx b/src/components/[guild]/AddRewardButton/components/AvailabilitySetup.tsx
deleted file mode 100644
index 8c96571a2b..0000000000
--- a/src/components/[guild]/AddRewardButton/components/AvailabilitySetup.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { useDisclosure } from "@chakra-ui/react"
-import EditRewardAvailabilityButton from "components/[guild]/RolePlatforms/components/EditRewardAvailabilityButton"
-import EditRewardAvailabilityModal, {
- RolePlatformAvailabilityForm,
-} from "components/[guild]/RolePlatforms/components/EditRewardAvailabilityModal"
-import AvailabilityTags, {
- shouldShowAvailabilityTags,
-} from "components/[guild]/RolePlatforms/components/PlatformCard/components/AvailabilityTags"
-import { PlatformName, RoleFormType, RolePlatform } from "types"
-
-type Props = {
- platformType: PlatformName
- rolePlatform?: NonNullable[number]
- defaultValues?: RolePlatformAvailabilityForm
- onDone: (data: RolePlatformAvailabilityForm) => void
- isLoading?: boolean
-}
-
-const AvailabilitySetup = ({
- platformType,
- rolePlatform,
- defaultValues,
- onDone,
- isLoading,
-}: Props) => {
- const { isOpen, onOpen, onClose } = useDisclosure()
- const showAvailabilityTags = shouldShowAvailabilityTags(rolePlatform)
-
- // If claim is already started by date, and thats the only limitation, show the full button
- const notCompact =
- !rolePlatform?.capacity &&
- !rolePlatform?.endTime &&
- rolePlatform?.startTime &&
- new Date(rolePlatform.startTime).getTime() < Date.now()
-
- return (
-
-
- {
- await onDone(data)
- onClose()
- }}
- />
-
- )
-}
-export default AvailabilitySetup
diff --git a/src/components/[guild]/AddRewardButton/constants.ts b/src/components/[guild]/AddRewardButton/constants.ts
deleted file mode 100644
index 64e3a8e952..0000000000
--- a/src/components/[guild]/AddRewardButton/constants.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { atom } from "jotai"
-import { AddRewardForm } from "./types"
-
-export const ADD_REWARD_FORM_DEFAULT_VALUES: AddRewardForm = {
- rolePlatforms: [],
- requirements: [{ type: "FREE" }],
- roleIds: [],
- visibility: "PUBLIC",
-}
-
-export const canCloseAddRewardModalAtom = atom(true)
diff --git a/src/components/[guild]/AddRewardButton/hooks/useAddReward.ts b/src/components/[guild]/AddRewardButton/hooks/useAddReward.ts
deleted file mode 100644
index ef0e94eece..0000000000
--- a/src/components/[guild]/AddRewardButton/hooks/useAddReward.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { usePostHogContext } from "@/components/Providers/PostHogProvider"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useCustomPosthogEvents from "hooks/useCustomPosthogEvents"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import useToast from "hooks/useToast"
-import { GuildPlatform, PlatformType } from "types"
-import fetcher from "utils/fetcher"
-
-type AddRewardResponse = GuildPlatform & { roleIds?: number[] }
-
-/**
- * To be used when a reward is entirely new, so the guildPlatform doesn't exist yet
- * (the BE handles adding rolePlatforms automatically to it too by the data we send).
- * If the guildPlatform already exists and we just want to create new rolePlatforms
- * to it, we should use the useAddRewardWithExistingGP hook
- */
-const useAddReward = ({
- onSuccess,
- onError,
-}: {
- onSuccess?: (res?: AddRewardResponse) => void
- onError?: (err: any) => void
-}) => {
- const { id, urlName, memberCount, mutateGuild } = useGuild()
-
- const { captureEvent } = usePostHogContext()
- const { rewardCreated } = useCustomPosthogEvents()
- const postHogOptions = { guild: urlName, memberCount }
-
- const showErrorToast = useShowErrorToast()
- const toast = useToast()
-
- const fetchData = async (signedValidation: SignedValidation) =>
- fetcher(`/v2/guilds/${id}/guild-platforms`, signedValidation)
-
- return useSubmitWithSign(fetchData, {
- onError: (error) => {
- showErrorToast(error)
- captureEvent("useAddReward error", { ...postHogOptions, error })
- onError?.(error)
- },
- onSuccess: (response) => {
- rewardCreated(response.platformId)
-
- if (response.platformId === PlatformType.CONTRACT_CALL) {
- captureEvent("Created NFT reward", {
- ...postHogOptions,
- hook: "useAddReward",
- })
- }
-
- toast({ status: "success", title: "Reward successfully added" })
- mutateGuild()
- onSuccess?.(response)
- },
- })
-}
-
-export default useAddReward
diff --git a/src/components/[guild]/AddRewardButton/hooks/useAddRewardDiscardAlert.ts b/src/components/[guild]/AddRewardButton/hooks/useAddRewardDiscardAlert.ts
deleted file mode 100644
index c7de403e28..0000000000
--- a/src/components/[guild]/AddRewardButton/hooks/useAddRewardDiscardAlert.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { atom, useAtom } from "jotai"
-import { useEffect } from "react"
-
-const isAddRewardPanelDirtyAtom = atom(false)
-
-export const useAddRewardDiscardAlert = (isDirty?: boolean) => {
- const [isAddRewardPanelDirty, setIsAddRewardPanelDirty] = useAtom(
- isAddRewardPanelDirtyAtom
- )
-
- useEffect(() => {
- if (isDirty) setIsAddRewardPanelDirty(isDirty)
- }, [isDirty, setIsAddRewardPanelDirty])
-
- return [isAddRewardPanelDirty, setIsAddRewardPanelDirty] as const
-}
diff --git a/src/components/[guild]/AddRewardButton/hooks/useCreateRequirements.ts b/src/components/[guild]/AddRewardButton/hooks/useCreateRequirements.ts
deleted file mode 100644
index b9ef88d71c..0000000000
--- a/src/components/[guild]/AddRewardButton/hooks/useCreateRequirements.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { usePostHogContext } from "@/components/Providers/PostHogProvider"
-import useGuild from "components/[guild]/hooks/useGuild"
-import { RequirementIdMap } from "hooks/useCreateRRR"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import {
- RequirementCreateResponseOutput,
- RequirementCreationPayloadWithTempID,
-} from "types"
-
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import preprocessRequirement from "utils/preprocessRequirement"
-
-const useCreateRequirements = () => {
- const { id: guildId } = useGuild()
- const showErrorToast = useShowErrorToast()
- const fetcherWithSign = useFetcherWithSign()
- const { captureEvent } = usePostHogContext()
- const postHogOptions = {
- hook: "useCreateRequirements",
- }
-
- const createRequirements = async (
- // We can assign generated IDs to requirements on our frontend, so it's safe to extend this type with an ID
- requirements: RequirementCreationPayloadWithTempID[],
- roleIds: number[]
- ) => {
- const requirementIdMap: RequirementIdMap = {}
- const requirementsToCreate = requirements.filter((req) => req.type !== "FREE")
-
- const promises = roleIds.flatMap((roleId) =>
- requirementsToCreate.map((req) =>
- fetcherWithSign([
- `/v2/guilds/${guildId}/roles/${roleId}/requirements`,
- {
- method: "POST",
- body: preprocessRequirement(req),
- },
- ])
- .then((res: RequirementCreateResponseOutput) => {
- if (!requirementIdMap[req.id]) requirementIdMap[req.id] = {}
- requirementIdMap[req.id][roleId] = res.id
- return { status: "fulfilled", result: res }
- })
- .catch((error) => {
- showErrorToast(`Failed to create a requirement (${req.type})`)
- captureEvent("Failed to create requirement", {
- ...postHogOptions,
- requirement: req,
- roleId,
- error,
- })
- console.error("Failed to create requirement: ", error)
- return { status: "rejected", result: error }
- })
- )
- )
-
- const results = await Promise.all(promises)
- const createdRequirements = results
- .filter((res) => res.status === "fulfilled")
- .map((res) => res.result)
-
- return { createdRequirements, requirementIdMap }
- }
-
- return {
- createRequirements,
- }
-}
-
-export default useCreateRequirements
diff --git a/src/components/[guild]/AddRewardButton/hooks/useCreateRolePlatforms.ts b/src/components/[guild]/AddRewardButton/hooks/useCreateRolePlatforms.ts
deleted file mode 100644
index eb95c395a4..0000000000
--- a/src/components/[guild]/AddRewardButton/hooks/useCreateRolePlatforms.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { usePostHogContext } from "@/components/Providers/PostHogProvider"
-import { Schemas } from "@guildxyz/types"
-import useGuild from "components/[guild]/hooks/useGuild"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { RolePlatform } from "types"
-
-// TODO: create a Zod schema for this in our types package
-export type CreateRolePlatformResponse = RolePlatform & {
- deletedRequirements?: number[]
- createdGuildPlatform: Schemas["GuildReward"]
-}
-
-const useCreateRolePlatforms = () => {
- const { id: guildId } = useGuild()
- const showErrorToast = useShowErrorToast()
- const fetcherWithSign = useFetcherWithSign()
-
- const { captureEvent } = usePostHogContext()
- const postHogOptions = {
- hook: "useCreateRolePlatforms",
- }
-
- const createRolePlatforms = async (
- rolePlatforms: Omit[]
- ): Promise => {
- const promises = rolePlatforms.map((rolePlatform) =>
- fetcherWithSign([
- `/v2/guilds/${guildId}/roles/${rolePlatform.roleId}/role-platforms`,
- { method: "POST", body: rolePlatform },
- ])
- .then((res) => ({
- status: "fulfilled",
- result: {
- ...res,
- roleId: rolePlatform.roleId,
- } as CreateRolePlatformResponse,
- }))
- .catch((error) => {
- showErrorToast("Failed to create a reward")
- captureEvent("Failed to create role platform", {
- ...postHogOptions,
- rolePlatform,
- error,
- })
- console.error(error)
- return { status: "rejected", result: error }
- })
- )
-
- const results = await Promise.all(promises)
- return results
- .filter((res) => res.status === "fulfilled")
- .map((res) => res.result)
- }
-
- return {
- createRolePlatforms,
- }
-}
-
-export default useCreateRolePlatforms
diff --git a/src/components/[guild]/AddRewardButton/hooks/useMutateAdditionsToRoles.ts b/src/components/[guild]/AddRewardButton/hooks/useMutateAdditionsToRoles.ts
deleted file mode 100644
index ae0dd72991..0000000000
--- a/src/components/[guild]/AddRewardButton/hooks/useMutateAdditionsToRoles.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import { Requirement } from "@guildxyz/types"
-import useGuild from "components/[guild]/hooks/useGuild"
-import { useMutateOptionalAuthSWRKey } from "hooks/useSWRWithOptionalAuth"
-import {
- Guild,
- GuildPlatform,
- RequirementCreateResponseOutput,
- RolePlatform,
-} from "types"
-import { CreateRolePlatformResponse } from "./useCreateRolePlatforms"
-
-const groupRequirementsByRoleId = (
- roleIds: number[],
- requirements: RequirementCreateResponseOutput[]
-): { [roleId: number]: RequirementCreateResponseOutput[] } =>
- roleIds.reduce((acc, roleId) => {
- acc[roleId] = requirements.filter((req) => req.roleId === roleId)
- return acc
- }, {})
-
-const groupRolePlatformsByRoleId = (
- roleIds: number[],
- rolePlatforms: RolePlatform[]
-): { [roleId: number]: RolePlatform[] } =>
- roleIds.reduce((acc, roleId) => {
- acc[roleId] = rolePlatforms.filter((rp) => rp.roleId === roleId)
- return acc
- }, {})
-
-const useMutateAdditionsToRoles = () => {
- const { mutateGuild, id: guildId } = useGuild()
- const mutateOptionalAuthSWRKey = useMutateOptionalAuthSWRKey()
-
- const mutateRequirements = (
- roleIds: number[],
- createdRequirements: RequirementCreateResponseOutput[]
- ) => {
- const createdRequirementsByRoleId = groupRequirementsByRoleId(
- roleIds,
- createdRequirements
- )
-
- roleIds.forEach((roleId) => {
- const createdRequirementsOnRole = createdRequirementsByRoleId[roleId]
- const reqIdsToDelete = createdRequirementsOnRole
- .filter((req) => !!req.deletedRequirements)
- .flatMap((req) => req.deletedRequirements)
-
- mutateOptionalAuthSWRKey(
- `/v2/guilds/${guildId}/roles/${roleId}/requirements`,
- (prevRequirements) => [
- ...prevRequirements?.filter((req) => !reqIdsToDelete.includes(req.id)),
- ...createdRequirementsOnRole,
- ],
- { revalidate: false }
- )
- })
- }
-
- const mutateAdditionsInGuild = (
- roleIds: number[],
- createdRequirements: RequirementCreateResponseOutput[],
- // TODO: create a RoleRewardCreateResponse schema in our types package
- createdRolePlatforms: CreateRolePlatformResponse[]
- ) => {
- const createdRequirementsByRoleId = groupRequirementsByRoleId(
- roleIds,
- createdRequirements
- )
- const createdRolePlatformsByRoleId = groupRolePlatformsByRoleId(
- roleIds,
- createdRolePlatforms
- )
-
- const createdGuildPlatforms = createdRolePlatforms
- .map((rp) => rp.createdGuildPlatform)
- .filter(Boolean)
-
- mutateGuild(
- (prev) => {
- // Create a deep copy of previous roles
- const prevRoles = prev.roles.map((role) => ({ ...role }))
-
- // Update the roles with the new requirements and role platforms
- const updatedRoles = prevRoles.map((role) => {
- if (!roleIds.includes(role.id)) return role
-
- const createdRolePlatformsOnRole = createdRolePlatformsByRoleId[role.id]
- const createdRequirementsOnRole = createdRequirementsByRoleId[role.id]
- const reqIdsToDelete = createdRequirementsOnRole
- .filter((req) => !!req.deletedRequirements)
- .flatMap((req) => req.deletedRequirements)
-
- return {
- ...role,
- requirements: [
- ...role.requirements.filter((req) => !reqIdsToDelete.includes(req.id)),
- ...createdRequirementsOnRole,
- ],
- rolePlatforms: [...role.rolePlatforms, ...createdRolePlatformsOnRole],
- }
- })
-
- // Return the updated data
- return !!prev
- ? ({
- ...prev,
- // TODO: we can remove the GuildPlatform[] cast once we start using the Guild schema from our types package in the useGuild hook
- guildPlatforms: [
- ...(prev?.guildPlatforms ?? []),
- ...(createdGuildPlatforms as GuildPlatform[]),
- ],
- roles: updatedRoles,
- } satisfies Guild)
- : undefined
- },
- { revalidate: false }
- )
- }
-
- const mutateAdditionsToRoles = (
- roleIds: number[],
- createdRequirements: RequirementCreateResponseOutput[],
- // TODO: create a RoleRewardCreateResponse schema in our types package
- createdRolePlatforms: CreateRolePlatformResponse[]
- ) => {
- mutateRequirements(roleIds, createdRequirements)
- mutateAdditionsInGuild(roleIds, createdRequirements, createdRolePlatforms)
- }
-
- return mutateAdditionsToRoles
-}
-
-export default useMutateAdditionsToRoles
diff --git a/src/components/[guild]/AddRewardButton/hooks/useMutateCreatedRole.ts b/src/components/[guild]/AddRewardButton/hooks/useMutateCreatedRole.ts
deleted file mode 100644
index ffd1d9e348..0000000000
--- a/src/components/[guild]/AddRewardButton/hooks/useMutateCreatedRole.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { useYourGuilds } from "@/hooks/useYourGuilds"
-import useGuild from "components/[guild]/hooks/useGuild"
-import { mutateGuildsCache } from "components/create-guild/hooks/useCreateRole"
-import useMatchMutate from "hooks/useMatchMutate"
-import { useMutateOptionalAuthSWRKey } from "hooks/useSWRWithOptionalAuth"
-import { GuildBase, GuildPlatform, Requirement, Role } from "types"
-import { CreateRolePlatformResponse } from "./useCreateRolePlatforms"
-
-const useMutateCreatedRole = () => {
- const { mutate: mutateYourGuilds } = useYourGuilds()
- const matchMutate = useMatchMutate()
- const { mutateGuild, id: guildId } = useGuild()
- const mutateOptionalAuthSWRKey = useMutateOptionalAuthSWRKey()
-
- const mutateCreatedRole = (
- createdRole: Role,
- createdRequirements: Requirement[],
- createdRolePlatforms: CreateRolePlatformResponse[]
- ) => {
- const completeRole = {
- ...createdRole,
- requirements:
- createdRequirements.length > 0
- ? createdRequirements
- : createdRole.requirements,
- rolePlatforms:
- createdRolePlatforms.length > 0
- ? createdRolePlatforms
- : createdRole.rolePlatforms,
- }
-
- mutateYourGuilds((prev) => mutateGuildsCache(prev, guildId), {
- revalidate: false,
- })
- matchMutate(
- /\/guilds\?order/,
- (prev) => mutateGuildsCache(prev, guildId),
- { revalidate: false }
- )
-
- const createdGuildPlatforms = createdRolePlatforms
- .map((rp) => rp.createdGuildPlatform)
- .filter(Boolean)
-
- mutateGuild(
- (prev) => {
- if (!prev) return undefined
-
- return {
- ...prev,
- guildPlatforms: [
- ...prev.guildPlatforms,
- ...(createdGuildPlatforms as GuildPlatform[]),
- ],
- roles: prev.roles.some((role) => role.id === createdRole.id)
- ? prev.roles.map((role) =>
- role.id === createdRole.id ? completeRole : role
- )
- : [...prev.roles, completeRole],
- }
- },
- { revalidate: false }
- )
-
- mutateOptionalAuthSWRKey(
- `/v2/guilds/${guildId}/roles/${createdRole.id}/requirements`,
- () => completeRole.requirements,
- { revalidate: false }
- )
- }
-
- return mutateCreatedRole
-}
-
-export default useMutateCreatedRole
diff --git a/src/components/[guild]/AddRewardButton/hooks/useSetRoleImageAndNameFromPlatformData.ts b/src/components/[guild]/AddRewardButton/hooks/useSetRoleImageAndNameFromPlatformData.ts
deleted file mode 100644
index f9a3af3a3d..0000000000
--- a/src/components/[guild]/AddRewardButton/hooks/useSetRoleImageAndNameFromPlatformData.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import {
- RoleTypeToAddTo,
- useAddRewardContext,
-} from "components/[guild]/AddRewardContext"
-import { env } from "env"
-import usePinata from "hooks/usePinata/usePinata"
-import { useEffect, useState } from "react"
-import { useFormContext } from "react-hook-form"
-
-const useSetRoleImageAndNameFromPlatformData = (
- platformImage: string,
- platformName: string
-) => {
- const [alreadyUploaded, setAlreadyUploaded] = useState(false)
- const { activeTab } = useAddRewardContext()
-
- const { setValue } = useFormContext()
-
- const { onUpload } = usePinata({
- fieldToSetOnSuccess: "imageUrl",
- })
-
- useEffect(() => {
- if (activeTab !== RoleTypeToAddTo.NEW_ROLE || !(platformName?.length > 0)) return
-
- setValue("name", platformName)
- }, [activeTab, platformName, setValue])
-
- useEffect(() => {
- if (
- alreadyUploaded ||
- activeTab !== RoleTypeToAddTo.NEW_ROLE ||
- !(platformImage?.length > 0)
- )
- return
-
- setAlreadyUploaded(true)
-
- if (platformImage?.startsWith(env.NEXT_PUBLIC_IPFS_GATEWAY)) {
- setValue("imageUrl", platformImage)
- return
- }
-
- fetch(platformImage)
- .then((response) => response.blob())
- .then((blob) =>
- onUpload({
- data: [new File([blob], `${platformName}.png`, { type: "image/png" })],
- })
- )
- }, [alreadyUploaded, activeTab, platformImage, setValue, platformName, onUpload])
-}
-
-export default useSetRoleImageAndNameFromPlatformData
diff --git a/src/components/[guild]/AddRewardButton/types.ts b/src/components/[guild]/AddRewardButton/types.ts
deleted file mode 100644
index e9cfffa39a..0000000000
--- a/src/components/[guild]/AddRewardButton/types.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Schemas, Visibility } from "@guildxyz/types"
-import { RoleFormType } from "types"
-
-export type AddRewardForm = {
- // TODO: we could simplify the form - we don't need a rolePlatforms array here, we only need one rolePlatform
- rolePlatforms: RoleFormType["rolePlatforms"][number][]
- // TODO: use proper types, e.g. name & symbol shouldn't be required on this type
- requirements?: Schemas["RequirementCreationPayload"][]
- roleIds?: number[]
- visibility: Visibility
- roleName?: string // Name for role, if new role is created with reward
-}
diff --git a/src/components/[guild]/AddRewardButton/useCreateTokenReward.ts b/src/components/[guild]/AddRewardButton/useCreateTokenReward.ts
deleted file mode 100644
index ddd5f425d3..0000000000
--- a/src/components/[guild]/AddRewardButton/useCreateTokenReward.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import { usePostHogContext } from "@/components/Providers/PostHogProvider"
-import { Schemas } from "@guildxyz/types"
-import useCreateRole from "components/create-guild/hooks/useCreateRole"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { PlatformType, Requirement, RolePlatform } from "types"
-import getRandomInt from "utils/getRandomInt"
-import { useMembershipUpdate } from "../JoinModal/hooks/useMembershipUpdate"
-import useGuild from "../hooks/useGuild"
-import useAddReward from "./hooks/useAddReward"
-
-type CreateData = {
- rolePlatforms: RolePlatform[]
- roleIds?: number[]
- requirements: Requirement[]
- name?: string
-}
-
-const isRequirementAmountOrAccess = (
- input: Schemas["DynamicAmount"]["operation"]["input"]
-): input is Extract<
- Schemas["DynamicAmount"]["operation"]["input"],
- { type: "REQUIREMENT_AMOUNT" } | { type: "REQUIREMENT_ACCESS" }
-> =>
- "type" in input &&
- (input.type === "REQUIREMENT_AMOUNT" || input.type === "REQUIREMENT_ACCESS")
-
-const getRewardSubmitData = (
- data: any,
- saveAs: "DRAFT" | "PUBLIC",
- roleId: number
-) => ({
- ...data.rolePlatforms[0].guildPlatform,
- rolePlatforms: [
- {
- ...data.rolePlatforms[0],
- roleId: Number(roleId),
- platformRoleId:
- data.rolePlatforms[0].guildPlatform.platformGuildId ||
- `${roleId}-${Date.now()}`,
- visibility: saveAs === "DRAFT" ? "HIDDEN" : "PUBLIC",
- },
- ],
-})
-
-/**
- * For requirement based rewards, we need to create the requirement first, so that we
- * can reference it in the dynamicAmount fields when creating the role platform.
- */
-const useCreateReqBasedTokenReward = ({
- onSuccess,
- onError,
-}: {
- onSuccess: (res?: ReturnType["response"]) => void
- onError: (err: any) => void
-}) => {
- const showErrorToast = useShowErrorToast()
- const { triggerMembershipUpdate } = useMembershipUpdate()
-
- const { id: guildId, urlName } = useGuild()
-
- const { captureEvent } = usePostHogContext()
- const postHogOptions = {
- guild: urlName,
- hook: "useCreateReqBasedTokenReward",
- }
-
- const { onSubmit: onAddRewardSubmit, isLoading: creatingReward } = useAddReward({
- onSuccess: (res) => {
- captureEvent(
- "createTokenReward(AddToExistingRole) Reward created",
- postHogOptions
- )
- triggerMembershipUpdate()
- onSuccess(res)
- },
- onError: (err) => {
- captureEvent("createTokenReward(CreateWithNewRole) Failed to create reward", {
- ...postHogOptions,
- err,
- })
- showErrorToast("Failed to create reward")
- onError(err)
- },
- })
-
- const { onSubmit: onCreateRoleSubmit, isLoading: creatingRole } = useCreateRole({
- onError: (error) => {
- captureEvent("createTokenReward(CreateWithNewRole) Failed to create role", {
- ...postHogOptions,
- error,
- })
- showErrorToast(
- "Failed to create the role for the reward, aborting reward creation."
- )
- },
- })
-
- const createWithNewRole = async (data: CreateData, saveAs: "DRAFT" | "PUBLIC") => {
- const roleVisibility = saveAs === "DRAFT" ? "HIDDEN" : "PUBLIC"
-
- const createdRole = await onCreateRoleSubmit({
- ...data,
- name:
- data.name ||
- `${data.rolePlatforms[0].guildPlatform.platformGuildData.name} role`,
- imageUrl:
- data.rolePlatforms[0].guildPlatform.platformGuildData?.imageUrl ||
- `/guildLogos/${getRandomInt(286)}.svg`,
- visibility: roleVisibility,
- rolePlatforms: [],
- logic: "AND",
- guildId,
- })
-
- if (!createdRole) return
- captureEvent("createTokenReward(CreateWithNewRole) Role created", {
- postHogOptions,
- roleId: createdRole.id,
- })
-
- const modifiedData: any = { ...data }
- const tokenGuildPlatformExists = !!data.rolePlatforms[0].guildPlatformId
-
- if (tokenGuildPlatformExists) {
- // Removing guild platform data, as we add to an already existing one
- modifiedData.rolePlatforms[0].guildPlatform = {
- platformId: PlatformType.ERC20,
- platformName: "ERC20",
- platformGuildId: "",
- platformGuildData: {},
- }
- }
-
- // Setting dynamic amount fields
- if (
- isRequirementAmountOrAccess(
- modifiedData.rolePlatforms[0].dynamicAmount.operation.input
- )
- ) {
- modifiedData.rolePlatforms[0].dynamicAmount.operation.input.roleId = Number(
- createdRole.id
- )
- modifiedData.rolePlatforms[0].dynamicAmount.operation.input.requirementId =
- createdRole.requirements[0].id
- }
-
- const rewardSubmitData = getRewardSubmitData(
- modifiedData,
- saveAs,
- createdRole.id
- )
- const createdReward = await onAddRewardSubmit(rewardSubmitData)
- if (!createdReward) return
- captureEvent("createTokenReward(CreateWithNewRole) Reward created", {
- postHogOptions,
- guildPlatformId: createdReward.id,
- })
-
- triggerMembershipUpdate()
- }
-
- return {
- submitCreate: createWithNewRole,
- isLoading: creatingReward || creatingRole,
- }
-}
-
-export default useCreateReqBasedTokenReward
diff --git a/src/components/[guild]/AddRewardContext.tsx b/src/components/[guild]/AddRewardContext.tsx
deleted file mode 100644
index 599ff78a7e..0000000000
--- a/src/components/[guild]/AddRewardContext.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { useDisclosure } from "@chakra-ui/react"
-import DiscardAlert from "components/common/DiscardAlert"
-import {
- Dispatch,
- MutableRefObject,
- PropsWithChildren,
- SetStateAction,
- createContext,
- useContext,
- useRef,
- useState,
-} from "react"
-import { PlatformName } from "types"
-
-export enum RoleTypeToAddTo {
- EXISTING_ROLE,
- NEW_ROLE,
-}
-
-const AddRewardContext = createContext<{
- isOpen: boolean
- onOpen: () => void
- onClose: () => void
- modalRef: MutableRefObject
- scrollToTop: () => void
- selection: PlatformName
- setSelection: (newSelection: PlatformName) => void
- step: string
- setStep: (newStep: string) => void
- targetRoleId?: number
- activeTab: RoleTypeToAddTo
- setActiveTab: Dispatch>
- shouldShowCloseAlert: boolean
- setShouldShowCloseAlert: Dispatch>
- isBackButtonDisabled: boolean
- setIsBackButtonDisabled: Dispatch>
-}>(undefined)
-
-const AddRewardProvider = ({
- targetRoleId,
- children,
-}: PropsWithChildren<{ targetRoleId?: number }>) => {
- const modalRef = useRef(null)
- const { isOpen, onOpen, onClose } = useDisclosure()
- const scrollToTop = () => modalRef.current?.scrollTo({ top: 0 })
-
- const [selection, setSelectionOg] = useState()
-
- const [step, setStepOg] = useState()
-
- const setStep = (newStep: string) => {
- setStepOg(newStep)
- scrollToTop()
- }
-
- const [activeTab, setActiveTab] = useState(
- RoleTypeToAddTo.EXISTING_ROLE
- )
-
- const setSelection = (newSelection: PlatformName) => {
- setSelectionOg(newSelection)
- scrollToTop()
- }
-
- const [shouldShowCloseAlert, setShouldShowCloseAlert] = useState(false)
- const {
- isOpen: isDiscardAlertOpen,
- onOpen: onDiscardAlertOpen,
- onClose: onDiscardAlertClose,
- } = useDisclosure()
-
- const [isBackButtonDisabled, setIsBackButtonDisabled] = useState(false)
-
- return (
- {
- setSelection(null)
- setStep("HOME")
- onOpen()
- },
- onClose: () => {
- if (shouldShowCloseAlert) {
- onDiscardAlertOpen()
- return
- }
-
- onClose()
- setShouldShowCloseAlert(false)
- setIsBackButtonDisabled(false)
- },
- scrollToTop,
- selection,
- setSelection,
- step,
- setStep,
- targetRoleId,
- activeTab,
- setActiveTab,
- shouldShowCloseAlert,
- setShouldShowCloseAlert,
- isBackButtonDisabled,
- setIsBackButtonDisabled,
- }}
- >
- {children}
- {
- onClose()
- onDiscardAlertClose()
- setShouldShowCloseAlert(false)
- setIsBackButtonDisabled(false)
- }}
- />
-
- )
-}
-
-const useAddRewardContext = () => useContext(AddRewardContext)
-
-export { AddRewardProvider, useAddRewardContext }
diff --git a/src/components/[guild]/AddSolutionsAndEditGuildButton.tsx b/src/components/[guild]/AddSolutionsAndEditGuildButton.tsx
deleted file mode 100644
index eff47c8324..0000000000
--- a/src/components/[guild]/AddSolutionsAndEditGuildButton.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { StickyAction } from "@/components/StickyAction"
-import { isStickyActionStuckAtom } from "@/components/[guild]/constants"
-import { ButtonGroup, ButtonProps, Divider, useColorMode } from "@chakra-ui/react"
-import { useAtomValue } from "jotai"
-import AddSolutionsButton from "solutions/components/AddSolutionsButton"
-import { useMediaQuery } from "usehooks-ts"
-import EditGuildButton from "./EditGuild"
-import { useThemeContext } from "./ThemeContext"
-
-const AddSolutionsAndEditGuildButton = () => {
- const isStickyActionStuck = useAtomValue(isStickyActionStuckAtom)
- const isMobile = useMediaQuery("(max-width: 640px")
- const { colorMode } = useColorMode()
- const { textColor, buttonColorScheme } = useThemeContext()
-
- const buttonProps = {
- size: { base: "xl", smd: "md" },
- variant: { base: "ghost", smd: "solid" },
- ...(!isStickyActionStuck && !isMobile
- ? {
- color: `${textColor} !important`,
- colorScheme: buttonColorScheme,
- }
- : colorMode === "light"
- ? {
- colorScheme: "gray",
- }
- : undefined),
- } satisfies ButtonProps
-
- return (
-
-
-
-
-
-
-
- )
-}
-
-export { AddSolutionsAndEditGuildButton }
diff --git a/src/components/[guild]/CollapsibleRoleSection.tsx b/src/components/[guild]/CollapsibleRoleSection.tsx
deleted file mode 100644
index c06449e5a7..0000000000
--- a/src/components/[guild]/CollapsibleRoleSection.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Box, Button, Collapse, Icon, Stack, useDisclosure } from "@chakra-ui/react"
-
-import { CaretDown } from "@phosphor-icons/react"
-import { forwardRef } from "react"
-import capitalize from "utils/capitalize"
-
-const CollapsibleRoleSection = forwardRef(
- (
- {
- roleCount,
- label,
- unmountOnExit = false,
- defaultIsOpen = false,
- children,
- ...rest
- }: any,
- ref
- ) => {
- const { isOpen, onToggle } = useDisclosure({ defaultIsOpen })
-
- return (
-
-
- }
- onClick={onToggle}
- >
- {capitalize(
- `${isOpen ? "" : "view "} ${roleCount} ${label} role${
- roleCount > 1 ? "s" : ""
- }`
- )}
-
-
-
- {children}
-
-
-
- )
- }
-)
-
-export default CollapsibleRoleSection
diff --git a/src/components/[guild]/CreateCampaignModal/CreateCampaignModal.tsx b/src/components/[guild]/CreateCampaignModal/CreateCampaignModal.tsx
deleted file mode 100644
index 61dbd4e0fd..0000000000
--- a/src/components/[guild]/CreateCampaignModal/CreateCampaignModal.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import {
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
-} from "@chakra-ui/react"
-import { ArrowRight } from "@phosphor-icons/react"
-import Button from "components/common/Button"
-import { Modal } from "components/common/Modal"
-import usePinata from "hooks/usePinata"
-import useSubmitWithUpload from "hooks/useSubmitWithUpload"
-import { FormProvider, useForm } from "react-hook-form"
-import CampaignForm, { CampaignFormType } from "./components/CampaignForm"
-import useCreateRoleGroup from "./hooks/useCreateRoleGroup"
-
-type Props = { isOpen: boolean; onClose: () => void }
-
-const CreateCampaignModal = (props: Props) => {
- const methods = useForm({ mode: "all" })
- const { handleSubmit } = methods
-
- const iconUploader = usePinata({
- fieldToSetOnSuccess: "imageUrl",
- control: methods.control,
- })
-
- const { onSubmit, isLoading } = useCreateRoleGroup()
-
- const { handleSubmit: handleSubmitWithUpload, isUploadingShown } =
- useSubmitWithUpload(handleSubmit(onSubmit), iconUploader.isUploading)
-
- return (
-
-
-
-
- Create page
-
-
-
-
-
-
-
- }
- onClick={handleSubmitWithUpload}
- isLoading={isUploadingShown || isLoading}
- loadingText="Creating page"
- >
- Create & set roles
-
-
-
-
-
- )
-}
-export default CreateCampaignModal
diff --git a/src/components/[guild]/CreateCampaignModal/components/CampaignForm.tsx b/src/components/[guild]/CreateCampaignModal/components/CampaignForm.tsx
deleted file mode 100644
index ae13396b60..0000000000
--- a/src/components/[guild]/CreateCampaignModal/components/CampaignForm.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import {
- FormControl,
- FormLabel,
- HStack,
- Input,
- Stack,
- Textarea,
-} from "@chakra-ui/react"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import Switch from "components/common/Switch"
-import IconSelector from "components/create-guild/IconSelector"
-import { GUILD_NAME_REGEX } from "components/create-guild/Name"
-import { Uploader } from "hooks/usePinata/usePinata"
-import { ChangeEvent } from "react"
-import { useController, useFormContext } from "react-hook-form"
-
-export type CampaignFormType = {
- imageUrl?: string
- name: string
- description?: string
- hideFromGuildPage?: boolean
-}
-
-type Props = {
- iconUploader: Uploader
-}
-
-const CampaignForm = ({ iconUploader }: Props) => {
- const {
- control,
- register,
- formState: { errors },
- } = useFormContext()
-
- const {
- field: {
- value: hideFromGuildPage,
- onChange: hideFromGuildPageOnChange,
- ...hideFromGuildPageField
- },
- } = useController({ control, name: "hideFromGuildPage" })
-
- return (
-
-
- Logo and title
-
-
-
-
-
- {errors.name?.message}
-
-
-
-
-
- Description
-
-
-
-
- ) =>
- hideFromGuildPageOnChange(!e.target.checked)
- }
- isChecked={!hideFromGuildPage}
- />
-
-
- )
-}
-export default CampaignForm
diff --git a/src/components/[guild]/CreateCampaignModal/hooks/useCreateRoleGroup.ts b/src/components/[guild]/CreateCampaignModal/hooks/useCreateRoleGroup.ts
deleted file mode 100644
index 1425f7a5f0..0000000000
--- a/src/components/[guild]/CreateCampaignModal/hooks/useCreateRoleGroup.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import useJsConfetti from "components/create-guild/hooks/useJsConfetti"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import useToast from "hooks/useToast"
-import { useRouter } from "next/router"
-import { Group } from "types"
-import fetcher from "utils/fetcher"
-
-const useCreateRoleGroup = () => {
- const router = useRouter()
-
- const { id, urlName, mutateGuild } = useGuild()
-
- const toast = useToast()
- const showErrorToast = useShowErrorToast()
-
- const triggerConfetti = useJsConfetti()
-
- const createRoleGroup = (signedValidation: SignedValidation): Promise =>
- fetcher(`/v2/guilds/${id}/groups`, signedValidation)
-
- return useSubmitWithSign(createRoleGroup, {
- onSuccess: (response) => {
- triggerConfetti()
- toast({
- status: "success",
- title: "Successfully created page",
- })
-
- mutateGuild(
- (curr) => ({
- ...curr,
- groups: [...(curr.groups ?? []), response],
- }),
- { revalidate: false }
- )
-
- router.push(`/${urlName}/${response.urlName}`)
- },
- onError: (error) => showErrorToast(error),
- })
-}
-
-export default useCreateRoleGroup
diff --git a/src/components/[guild]/CreateCampaignModal/index.ts b/src/components/[guild]/CreateCampaignModal/index.ts
deleted file mode 100644
index 2cf3eb3255..0000000000
--- a/src/components/[guild]/CreateCampaignModal/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import CreateCampaignModal from "./CreateCampaignModal"
-
-export default CreateCampaignModal
diff --git a/src/components/[guild]/CreateFormModal/components/CreateFormForm.tsx b/src/components/[guild]/CreateFormModal/components/CreateFormForm.tsx
deleted file mode 100644
index 2632896b22..0000000000
--- a/src/components/[guild]/CreateFormModal/components/CreateFormForm.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import {
- Box,
- Divider,
- FormControl,
- FormLabel,
- Input,
- Stack,
- Text,
- Textarea,
-} from "@chakra-ui/react"
-import { CreateForm } from "components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddFormPanel"
-import AddCard from "components/common/AddCard"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import Switch from "components/common/Switch"
-import { LayoutGroup, Reorder, motion } from "framer-motion"
-import { useFieldArray, useFormContext } from "react-hook-form"
-import getFieldIndexesToSwap from "utils/getFieldsToSwap"
-import FormCardEditable from "./FormCardEditable"
-
-const MotionAddCard = motion(AddCard)
-
-const CreateFormForm = () => {
- const {
- control,
- register,
- formState: { errors },
- } = useFormContext()
-
- const { fields, append, remove, update, swap } = useFieldArray({
- control,
- name: "fields",
- })
-
- const onReorder = (newOrder: string[]) => {
- const originalOrder = fields.map((field) => field.id)
- const [indexA, indexB] = getFieldIndexesToSwap(originalOrder, newOrder)
- swap(indexA, indexB)
- }
-
- return (
-
-
- Title
-
- {errors.name?.message}
-
-
-
- Description
-
-
-
-
-
- Add questions
-
-
- field.id)}
- onReorder={onReorder}
- >
- {fields.map((field, index) => (
- update(index, newValue)}
- onRemove={() => remove(index)}
- />
- ))}
-
-
-
- append({
- type: "SHORT_TEXT",
- question: "",
- })
- }
- />
-
- {errors.fields?.message}
-
-
-
-
- )
-}
-export default CreateFormForm
diff --git a/src/components/[guild]/CreateFormModal/components/Display/Choice.tsx b/src/components/[guild]/CreateFormModal/components/Display/Choice.tsx
deleted file mode 100644
index e031f53348..0000000000
--- a/src/components/[guild]/CreateFormModal/components/Display/Choice.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-import {
- Checkbox,
- CheckboxGroup,
- CheckboxGroupProps,
- HStack,
- Input,
- Radio,
- RadioGroup,
- RadioGroupProps,
- Stack,
- forwardRef,
-} from "@chakra-ui/react"
-import { Schemas } from "@guildxyz/types"
-import { CreateForm } from "components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddFormPanel"
-
-type Props = {
- field: Schemas["Field"] | CreateForm["fields"][number]
-}
-
-const SingleChoice = forwardRef, "div">(
- ({ field, value, onChange, ...props }, _ref) => {
- // We probably won't run into this case, but needed to add this line to get valid intellisense
- if (field.type !== "SINGLE_CHOICE") return null
-
- const options = field.options.map((option) =>
- typeof option === "number" || typeof option === "string"
- ? option
- : option.value
- )
-
- const isOtherActive =
- value !== undefined && value !== null && !options.includes(value)
-
- return (
-
-
- {options.map((option) => (
-
- {option}
-
- ))}
-
- {field.allowOther && (
-
-
- {
- onChange(e.target.value)
- }}
- value={isOtherActive ? value : ""}
- {...(!isOtherActive
- ? {
- variant: "unstyled",
- height: "1.5em",
- className: "disabledOtherInput", // so we can handle to keep it's opacity in ResponseModal
- }
- : {})}
- onFocus={() => {
- if (!isOtherActive) onChange("")
- }}
- transition={"padding .15s, height .15s"}
- />
-
- )}
-
-
- )
- }
-)
-
-const MultipleChoice = forwardRef(
- ({ field, value: valuesArray, onChange, ...props }, _ref) => {
- // We probably won't run into this case, but needed to add this line to get valid intellisense
- if (field.type !== "MULTIPLE_CHOICE") return null
-
- const options = field.options.map((option) =>
- typeof option === "number" || typeof option === "string"
- ? option
- : option.value
- )
-
- const otherValue = valuesArray?.find((v) => !options.includes(v))
-
- return (
-
-
- {options.map((option) => (
-
- {option}
-
- ))}
-
- {field.allowOther && (
-
-
- {
- const otherValueIndex = valuesArray.indexOf(otherValue)
- valuesArray[otherValueIndex] = e.target.value
- onChange(valuesArray)
- }}
- onBlur={(e) => {
- if (e.target.value === "")
- onChange(valuesArray.filter((v) => v !== otherValue))
- }}
- value={otherValue || ""}
- {...(otherValue === undefined
- ? {
- variant: "unstyled",
- height: "1.5em",
- className: "disabledOtherInput", // so we can handle to keep it's opacity in ResponseModal
- }
- : {})}
- onFocus={() => {
- if (!otherValue) onChange([...(valuesArray ?? []), ""])
- }}
- transition={"padding .15s, height .15s"}
- />
-
- )}
-
-
- )
- }
-)
-
-export { MultipleChoice, SingleChoice }
diff --git a/src/components/[guild]/CreateFormModal/components/Display/LongText.tsx b/src/components/[guild]/CreateFormModal/components/Display/LongText.tsx
deleted file mode 100644
index e0abab243c..0000000000
--- a/src/components/[guild]/CreateFormModal/components/Display/LongText.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Textarea, TextareaProps, forwardRef } from "@chakra-ui/react"
-
-const LongText = forwardRef((props, ref) => (
-
-))
-
-export default LongText
diff --git a/src/components/[guild]/CreateFormModal/components/Display/Number.tsx b/src/components/[guild]/CreateFormModal/components/Display/Number.tsx
deleted file mode 100644
index 1668efb85c..0000000000
--- a/src/components/[guild]/CreateFormModal/components/Display/Number.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import {
- NumberDecrementStepper,
- NumberIncrementStepper,
- NumberInput,
- NumberInputField,
- NumberInputProps,
- NumberInputStepper,
- forwardRef,
-} from "@chakra-ui/react"
-
-const Number = forwardRef((props, ref) => (
-
-
-
-
-
-
-
-))
-
-export default Number
diff --git a/src/components/[guild]/CreateFormModal/components/Display/Rate.tsx b/src/components/[guild]/CreateFormModal/components/Display/Rate.tsx
deleted file mode 100644
index 3355476914..0000000000
--- a/src/components/[guild]/CreateFormModal/components/Display/Rate.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import {
- Box,
- Collapse,
- Flex,
- HStack,
- RadioGroupProps,
- RadioProps,
- Stack,
- Text,
- UseRadioGroupProps,
- forwardRef,
- useRadio,
- useRadioGroup,
-} from "@chakra-ui/react"
-import { Schemas } from "@guildxyz/types"
-import { CreateForm } from "components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddFormPanel"
-import Button from "components/common/Button"
-
-type Props = {
- field: Schemas["Field"] | CreateForm["fields"][number]
-} & Omit
-
-// TODO: should we pass that ref down? (probably for focus management?)
-const Rate = forwardRef(({ field, ...props }, _ref) => {
- // We probably won't run into this case, but needed to add this line to get valid intellisense
- if (field.type !== "RATE") return null
-
- return (
-
-
-
- ({
- value:
- typeof option === "string" || typeof option === "number"
- ? option.toString()
- : option.value.toString(),
- label:
- typeof option === "string" || typeof option === "number"
- ? option.toString()
- : option.value.toString(),
- }))}
- {...props}
- />
-
-
-
-
- {field.worstLabel}
-
-
-
-
- {field.bestLabel}
-
-
-
-
-
-
-
- )
-})
-
-/**
- * The general RadioButtonGroup component doesn't work properly (it can't handle
- * disabled state?), so I decided to add a really simple radio group component here &
- * we'll refactor it later if needed.
- */
-const RateRadioGroup = ({
- options,
- ...props
-}: {
- options: { label: string; value: string }[]
-} & UseRadioGroupProps) => {
- const { getRootProps, getRadioProps } = useRadioGroup(props)
-
- const group = getRootProps()
-
- return (
-
- {options?.map(({ label, value }) => {
- const radio = getRadioProps({ value })
- return (
-
- {label}
-
- )
- })}
-
- )
-}
-
-const RateRadioButton = (props: RadioProps) => {
- const { getInputProps, getRadioProps } = useRadio(props)
-
- const input = getInputProps()
- const checkbox = getRadioProps()
-
- return (
-
-
-
- {props.children}
-
-
- )
-}
-
-export default Rate
diff --git a/src/components/[guild]/CreateFormModal/components/Display/ShortText.tsx b/src/components/[guild]/CreateFormModal/components/Display/ShortText.tsx
deleted file mode 100644
index 2c657a8a0f..0000000000
--- a/src/components/[guild]/CreateFormModal/components/Display/ShortText.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Input, InputProps, forwardRef } from "@chakra-ui/react"
-
-const ShortText = forwardRef((props, ref) => (
-
-))
-
-export default ShortText
diff --git a/src/components/[guild]/CreateFormModal/components/FormCardEditable/FormCardEditable.tsx b/src/components/[guild]/CreateFormModal/components/FormCardEditable/FormCardEditable.tsx
deleted file mode 100644
index 513659ffde..0000000000
--- a/src/components/[guild]/CreateFormModal/components/FormCardEditable/FormCardEditable.tsx
+++ /dev/null
@@ -1,226 +0,0 @@
-import {
- Divider,
- FormControl,
- FormLabel,
- Grid,
- HStack,
- Icon,
- IconButton,
- Input,
- InputGroup,
- InputLeftElement,
- Stack,
- Switch,
- Tooltip,
-} from "@chakra-ui/react"
-import { Schemas } from "@guildxyz/types"
-import { DotsSixVertical, PencilSimple, Trash } from "@phosphor-icons/react"
-import { CreateForm } from "components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddFormPanel"
-import Button from "components/common/Button"
-import Card from "components/common/Card"
-import CardMotionWrapper from "components/common/CardMotionWrapper"
-import ControlledSelect from "components/common/ControlledSelect"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import { Reorder, useDragControls } from "framer-motion"
-import { useState } from "react"
-import { useController, useFormContext, useWatch } from "react-hook-form"
-import { SelectOption } from "types"
-import { fieldTypes } from "../../formConfig"
-import FormFieldTitle from "./components/FormFieldTitle"
-
-type Props = {
- index: number
- fieldId: string
- onUpdate: (newValue: CreateForm["fields"][number]) => void
- onRemove: () => void
-}
-
-const FormCardEditable = ({ index, fieldId, onUpdate, onRemove }: Props) => {
- const {
- control,
- register,
- formState: { errors },
- } = useFormContext()
- const field = useWatch({ control, name: `fields.${index}` })
- const isEditForm = !!field?.id
- const selectedFieldType = fieldTypes.find((ft) => ft.value === field?.type)
-
- const {
- field: {
- value: isRequiredValue,
- onChange: onIsRequiredChange,
- ...isRequiredControl
- },
- } = useController({
- control,
- name: `fields.${index}.isRequired`,
- })
-
- const [isEditing, setIsEditing] = useState(!isEditForm)
-
- const dragControls = useDragControls()
-
- const isDoneDisabled =
- !!errors.fields?.[index] ||
- !field?.question ||
- ((field?.type === "SINGLE_CHOICE" || field?.type === "MULTIPLE_CHOICE") &&
- (!field.options?.length || field.options.some((option) => !option.value)))
-
- return (
-
-
-
-
- {isEditing ? (
-
-
-
-
- {errors.fields?.[index]?.question?.message}
-
-
-
-
-
- {selectedFieldType?.img}
-
- ) => {
- const isChoice =
- newValue.value === "SINGLE_CHOICE" ||
- newValue.value === "MULTIPLE_CHOICE"
- const isRate = newValue.value === "RATE"
- onUpdate({
- type: field.type,
- question: field.question,
- allowOther: false,
- bestLabel: "",
- worstLabel: "",
- isRequired: false,
- options: isChoice
- ? [
- {
- value: "Option 1",
- },
- ]
- : isRate
- ? [...Array(10)].map((_, i) => ({ value: i + 1 }))
- : [],
- })
- }}
- />
-
-
-
-
- ) : (
-
-
-
- }
- onClick={() => setIsEditing(true)}
- />
-
- dragControls.start(e)}
- />
-
- )}
- {!selectedFieldType ? null : selectedFieldType.SetupComponent &&
- isEditing ? (
-
- ) : (
-
- )}
- {isEditing && (
-
-
-
- Required
-
- onIsRequiredChange(e.target.checked)}
- />
-
-
- }
- rounded="full"
- size="sm"
- variant="ghost"
- onClick={onRemove}
- />
-
-
- setIsEditing(false)}
- >
- Done
-
-
- )}
-
-
-
-
- )
-}
-
-export default FormCardEditable
diff --git a/src/components/[guild]/CreateFormModal/components/FormCardEditable/components/FormFieldTitle.tsx b/src/components/[guild]/CreateFormModal/components/FormCardEditable/components/FormFieldTitle.tsx
deleted file mode 100644
index 7d6ceb30c8..0000000000
--- a/src/components/[guild]/CreateFormModal/components/FormCardEditable/components/FormFieldTitle.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Text, TextProps } from "@chakra-ui/react"
-import { Schemas } from "@guildxyz/types"
-import { CreateForm } from "components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddFormPanel"
-
-type Props = {
- field: Schemas["Field"] | CreateForm["fields"][number]
-} & TextProps
-
-const FormFieldTitle = ({ field, ...textProps }: Props) => (
-
- {field.question}
- {field.isRequired && (
-
- *
-
- )}
-
-)
-
-export default FormFieldTitle
diff --git a/src/components/[guild]/CreateFormModal/components/FormCardEditable/index.ts b/src/components/[guild]/CreateFormModal/components/FormCardEditable/index.ts
deleted file mode 100644
index 85bd7b576d..0000000000
--- a/src/components/[guild]/CreateFormModal/components/FormCardEditable/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import FormCardEditable from "./FormCardEditable"
-
-export default FormCardEditable
diff --git a/src/components/[guild]/CreateFormModal/components/Setup/ChoiceSetup.tsx b/src/components/[guild]/CreateFormModal/components/Setup/ChoiceSetup.tsx
deleted file mode 100644
index f7e4338806..0000000000
--- a/src/components/[guild]/CreateFormModal/components/Setup/ChoiceSetup.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { FormControl, HStack, Input, Stack, Text } from "@chakra-ui/react"
-import { CreateForm } from "components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddFormPanel"
-import Button from "components/common/Button"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import { AnimatePresence, LayoutGroup, Reorder } from "framer-motion"
-import { useRef } from "react"
-import { useFieldArray, useFormContext, useWatch } from "react-hook-form"
-import getFieldIndexesToSwap from "utils/getFieldsToSwap"
-import OptionLayout from "./OptionLayout"
-import RemoveButton from "./RemoveButton"
-
-type Props = {
- index: number
-}
-
-const ChoiceSetup = ({ index }: Props) => {
- const {
- control,
- register,
- setValue,
- formState: { errors },
- } = useFormContext()
- const type = useWatch({ name: `fields.${index}.type` })
- const { fields, append, remove, swap } = useFieldArray({
- control,
- name: `fields.${index}.options`,
- })
-
- const addOptionRef = useRef(null)
-
- const allowOther = useWatch({
- name: `fields.${index}.allowOther`,
- })
-
- const onReorder = (newOrder: string[]) => {
- const originalOrder = fields.map((field) => field.id)
- const [indexA, indexB] = getFieldIndexesToSwap(originalOrder, newOrder)
- swap(indexA, indexB)
- }
-
- return (
-
- field.id)}
- onReorder={onReorder}
- >
-
- {fields.map((field, optionIndex) => (
- 0 && (
- remove(optionIndex)} />
- )
- }
- draggable
- >
-
-
-
- {/* Unfortunately react-hook-form couldn't detect types properly here, so we needed an any cast */}
- {
- (errors.fields?.[index] as any)?.options?.[optionIndex]?.value
- ?.message
- }
-
-
-
- ))}
-
-
-
-
-
-
-
- OR
-
-
- setValue(`fields.${index}.allowOther`, true)}
- >
- Add "Other"...
-
-
- )
- }
- >
- {
- if (!fields.every((field) => !!field.value)) return
- append({
- value: e.target.value,
- })
- addOptionRef.current.value = ""
- }}
- bg="transparent"
- borderStyle="dashed"
- _focus={{ borderStyle: "solid" }}
- />
-
-
- {allowOther && (
- setValue(`fields.${index}.allowOther`, false)}
- />
- }
- mt={-2}
- >
-
-
- )}
-
-
-
- )
-}
-
-export default ChoiceSetup
diff --git a/src/components/[guild]/CreateFormModal/components/Setup/OptionLayout.tsx b/src/components/[guild]/CreateFormModal/components/Setup/OptionLayout.tsx
deleted file mode 100644
index 6fb30a522f..0000000000
--- a/src/components/[guild]/CreateFormModal/components/Setup/OptionLayout.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import {
- Center,
- ChakraProps,
- Flex,
- Grid,
- HStack,
- Icon,
- useColorModeValue,
-} from "@chakra-ui/react"
-import { DotsSixVertical } from "@phosphor-icons/react"
-import { Reorder, useDragControls } from "framer-motion"
-import { PropsWithChildren, ReactNode } from "react"
-import { Rest } from "types"
-
-type Props = {
- action: ReactNode
- type: "SINGLE_CHOICE" | "MULTIPLE_CHOICE"
- draggable?: boolean
- fieldId?: string
-} & Rest
-
-const draggableCenterProps: ChakraProps = {
- cursor: "grab",
- _groupHover: {
- height: 6,
- borderRadius: "md",
- },
- _groupFocusWithin: {
- height: 6,
- borderRadius: "md",
- },
- transition: "height 0.24s ease, border-radius 0.24s ease",
-}
-
-const draggableIconProps: ChakraProps = {
- transition: "opacity 0.24s ease",
- _groupHover: {
- opacity: 1,
- },
- _groupFocusWithin: {
- opacity: 1,
- },
-}
-
-const OptionLayout = ({
- children,
- action,
- type,
- draggable,
- fieldId,
- ...props
-}: PropsWithChildren) => {
- /**
- * Dark color is from blackAlpha.300, but without opacity so it looks great when we
- * reorder the choice inputs
- */
- const inputBgColor = useColorModeValue("white", "#35353A")
-
- const dragControls = useDragControls()
-
- return (
-
-
-
- dragControls.start(e) : undefined}
- >
-
-
- {children}
-
-
- {action}
-
-
- )
-}
-
-export default OptionLayout
diff --git a/src/components/[guild]/CreateFormModal/components/Setup/RateSetup.tsx b/src/components/[guild]/CreateFormModal/components/Setup/RateSetup.tsx
deleted file mode 100644
index 4c161853be..0000000000
--- a/src/components/[guild]/CreateFormModal/components/Setup/RateSetup.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { Flex, Grid, Input, Stack, Text } from "@chakra-ui/react"
-import StyledSelect from "components/common/StyledSelect"
-import { useFormContext, useWatch } from "react-hook-form"
-import { SelectOption } from "types"
-import Rate from "../Display/Rate"
-
-type Props = {
- index: number
-}
-
-const RateSetup = ({ index }: Props) => {
- const field = useWatch({ name: `fields.${index}` })
-
- return (
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-type LabelSetupProps = {
- type: "worst" | "best"
- fieldIndex: number
-}
-
-const baseOptionsArray = [...Array(11)].map((_, i) => ({
- label: i.toString(),
- value: i,
-}))
-
-const labelSetupConfig: Record<
- LabelSetupProps["type"],
- {
- label: string
- options: SelectOption[]
- labelFieldName: string
- }
-> = {
- worst: {
- label: "Start",
- options: baseOptionsArray.slice(0, 2),
- labelFieldName: "worstLabel",
- },
- best: {
- label: "End",
- options: baseOptionsArray.slice(2, baseOptionsArray.length),
- labelFieldName: "bestLabel",
- },
-}
-
-const LabelSetup = ({ fieldIndex, type }: LabelSetupProps) => {
- const { register, getValues, setValue } = useFormContext()
- const optionsFieldName = `fields.${fieldIndex}.options`
-
- return (
- <>
-
-
- {labelSetupConfig[type].label}
-
-
- {
- const currentRateOptions = getValues(optionsFieldName)
- if (type === "worst") {
- setValue(
- optionsFieldName,
- generateOptionsFromValue(value, currentRateOptions.at(-1).value)
- )
- } else {
- setValue(
- optionsFieldName,
- generateOptionsFromValue(currentRateOptions[0].value, value)
- )
- }
- }}
- />
-
- >
- )
-}
-
-const generateOptionsFromValue = (min: number, max: number): number[] => {
- const generatedArray = []
-
- for (let i = min; i <= max; i++) {
- generatedArray.push({ label: i.toString(), value: i })
- }
-
- return generatedArray
-}
-
-export default RateSetup
diff --git a/src/components/[guild]/CreateFormModal/components/Setup/RemoveButton.tsx b/src/components/[guild]/CreateFormModal/components/Setup/RemoveButton.tsx
deleted file mode 100644
index b269a4276d..0000000000
--- a/src/components/[guild]/CreateFormModal/components/Setup/RemoveButton.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { IconButton, IconButtonProps } from "@chakra-ui/react"
-import { X } from "@phosphor-icons/react"
-
-const RemoveButton = (
- props: Omit<
- IconButtonProps,
- "aria-label" | "icon" | "rounded" | "boxSize" | "minW" | "minH" | "variant"
- >
-) => (
- }
- rounded="full"
- boxSize={5}
- minW={5}
- minH={5}
- variant="unstyled"
- display="flex"
- alignItems="center"
- opacity={0.7}
- _hover={{ opacity: 1 }}
- {...props}
- />
-)
-
-export default RemoveButton
diff --git a/src/components/[guild]/CreateFormModal/formConfig.tsx b/src/components/[guild]/CreateFormModal/formConfig.tsx
deleted file mode 100644
index f41973e3a3..0000000000
--- a/src/components/[guild]/CreateFormModal/formConfig.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import { Schemas } from "@guildxyz/types"
-import {
- CheckSquare,
- NumberCircleFive,
- NumberSquareFive,
- RadioButton,
- Textbox,
-} from "@phosphor-icons/react"
-import OptionIcon from "components/common/StyledSelect/components/CustomSelectOption/components/OptionIcon"
-import { ComponentType, ReactNode } from "react"
-import {
- ExpectedMultipleChoiceDisplay,
- ExpectedRateDisplay,
- ExpectedStringDisplay,
-} from "requirements/Form/components/ExpectedAnswerCardDisplay"
-import { ExpectedMultipleChoice } from "requirements/Form/components/ExpectedAnswerRequirements/ExpectedMultipleChoice"
-import ExpectedNumber from "requirements/Form/components/ExpectedAnswerRequirements/ExpectedNumber"
-import ExpectedRate from "requirements/Form/components/ExpectedAnswerRequirements/ExpectedRate"
-import ExpectedSingleChoice from "requirements/Form/components/ExpectedAnswerRequirements/ExpectedSingleChoice"
-import { ExpectedFieldDataProps } from "requirements/Form/components/types"
-import ExpectedString from "../../../requirements/Form/components/ExpectedAnswerRequirements/ExpectedString"
-import { CreateForm } from "../RolePlatforms/components/AddRoleRewardModal/components/AddFormPanel"
-import { MultipleChoice, SingleChoice } from "./components/Display/Choice"
-import LongText from "./components/Display/LongText"
-import Number from "./components/Display/Number"
-import Rate from "./components/Display/Rate"
-import ShortText from "./components/Display/ShortText"
-import ChoiceSetup from "./components/Setup/ChoiceSetup"
-import RateSetup from "./components/Setup/RateSetup"
-
-// TODO: use dynamic imports so end users don't have to download the setup components
-const fieldTypes: {
- label: string
- value: Schemas["Field"]["type"]
- img: ReactNode
- SetupComponent?: ComponentType<{
- index: number
- }>
- DisplayComponent: ComponentType<{
- field: Schemas["Field"] | CreateForm["fields"][number]
- isDisabled?: boolean
- value?: any
- }>
- ExpectedAnswerComponent?: ComponentType<{ field: Schemas["Field"] }>
- ExpectedAnswerDisplayComponent?: ComponentType
-}[] = [
- {
- label: "Short text",
- value: "SHORT_TEXT",
- img: ,
- DisplayComponent: ShortText,
- ExpectedAnswerComponent: ExpectedString,
- ExpectedAnswerDisplayComponent: ExpectedStringDisplay,
- },
- {
- label: "Long text",
- value: "LONG_TEXT",
- img: ,
- DisplayComponent: LongText,
- ExpectedAnswerComponent: ExpectedString,
- ExpectedAnswerDisplayComponent: ExpectedStringDisplay,
- },
- {
- label: "Number",
- value: "NUMBER",
- img: ,
- DisplayComponent: Number,
- ExpectedAnswerComponent: ExpectedNumber,
- ExpectedAnswerDisplayComponent: ExpectedStringDisplay,
- },
- {
- label: "Single choice",
- value: "SINGLE_CHOICE",
- img: ,
- SetupComponent: ChoiceSetup,
- DisplayComponent: SingleChoice,
- ExpectedAnswerComponent: ExpectedSingleChoice,
- ExpectedAnswerDisplayComponent: ExpectedStringDisplay,
- },
- {
- label: "Multiple choice",
- value: "MULTIPLE_CHOICE",
- img: ,
- SetupComponent: ChoiceSetup,
- DisplayComponent: MultipleChoice,
- ExpectedAnswerComponent: ExpectedMultipleChoice,
- ExpectedAnswerDisplayComponent: ExpectedMultipleChoiceDisplay,
- },
- {
- label: "Rate",
- value: "RATE",
- img: ,
- SetupComponent: RateSetup,
- DisplayComponent: Rate,
- ExpectedAnswerComponent: ExpectedRate,
- ExpectedAnswerDisplayComponent: ExpectedRateDisplay,
- },
-]
-
-export { fieldTypes }
diff --git a/src/components/[guild]/CreateFormModal/hooks/useCreateForm.ts b/src/components/[guild]/CreateFormModal/hooks/useCreateForm.ts
deleted file mode 100644
index 6ecdbe5dd0..0000000000
--- a/src/components/[guild]/CreateFormModal/hooks/useCreateForm.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Schemas } from "@guildxyz/types"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useGuildForms from "components/[guild]/hooks/useGuildForms"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import useToast from "hooks/useToast"
-import fetcher from "utils/fetcher"
-
-const useCreateForm = (onSuccess?: (createdForm: Schemas["Form"]) => void) => {
- const { id } = useGuild()
- const { mutate: mutateForms } = useGuildForms()
-
- const toast = useToast()
- const showErrorToast = useShowErrorToast()
-
- return useSubmitWithSign(
- (signedValidation: SignedValidation) =>
- fetcher(`/v2/guilds/${id}/forms`, signedValidation),
- {
- onSuccess: (createdForm) => {
- toast({
- status: "success",
- title: "Successfully created form",
- })
-
- onSuccess?.(createdForm)
-
- mutateForms((prevValue) => [...(prevValue ?? []), createdForm], {
- revalidate: false,
- })
- },
- onError: (error) => showErrorToast(error),
- }
- )
-}
-
-export default useCreateForm
diff --git a/src/components/[guild]/CreateFormModal/schemas.ts b/src/components/[guild]/CreateFormModal/schemas.ts
deleted file mode 100644
index e017d3fc25..0000000000
--- a/src/components/[guild]/CreateFormModal/schemas.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { schemas } from "@guildxyz/types"
-import { z } from "zod"
-
-export const FormCreationSchema = z
- .any()
- .transform((_value) => {
- const value = structuredClone(_value)
- if (Array.isArray(value.fields)) {
- value.fields = value.fields?.map((field) => {
- if (!Array.isArray(field.options)) return field
- return {
- ...field,
- options: field.options?.map((option) => {
- if (typeof option === "string" || typeof option === "number")
- return option
- return option.value
- }),
- }
- })
- }
-
- return value
- })
- .pipe(schemas.FormCreationPayloadSchema)
diff --git a/src/components/[guild]/DeleteButton.tsx b/src/components/[guild]/DeleteButton.tsx
deleted file mode 100644
index 7f1b66bcaf..0000000000
--- a/src/components/[guild]/DeleteButton.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { ButtonProps, Icon, IconButton, Tooltip } from "@chakra-ui/react"
-import { Trash } from "@phosphor-icons/react"
-import { PropsWithChildren } from "react"
-
-type Props = {
- label: string
- onClick: () => void
-} & ButtonProps
-
-const DeleteButton = ({ label, onClick }: PropsWithChildren): JSX.Element => (
-
- }
- colorScheme="red"
- variant={"ghost"}
- minW={"44px"}
- borderRadius={"full"}
- onClick={onClick}
- />
-
-)
-
-export default DeleteButton
diff --git a/src/components/[guild]/DiscordBotPermissionsChecker.tsx b/src/components/[guild]/DiscordBotPermissionsChecker.tsx
deleted file mode 100644
index 020ae5a493..0000000000
--- a/src/components/[guild]/DiscordBotPermissionsChecker.tsx
+++ /dev/null
@@ -1,282 +0,0 @@
-import {
- ListItem,
- ModalBody,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Stack,
- Text,
- ToastId,
- UnorderedList,
- useDisclosure,
-} from "@chakra-ui/react"
-import { ArrowSquareOut, Info } from "@phosphor-icons/react"
-import Button from "components/common/Button"
-import DiscordRoleVideo from "components/common/DiscordRoleVideo"
-import { Modal } from "components/common/Modal"
-import { env } from "env"
-import { ActionToastOptions, useToastWithButton } from "hooks/useToast"
-import { useMemo, useRef, useState } from "react"
-import useSWRImmutable from "swr/immutable"
-import { PlatformType } from "types"
-import fetcher from "utils/fetcher"
-import useGuild from "./hooks/useGuild"
-import useGuildPermission from "./hooks/useGuildPermission"
-
-const MANAGE_ROLES_PERMISSION_NAME = "Manage Roles"
-const MANAGE_SERVER_PERMISSION_NAME = "Manage Server"
-const CREATE_INVITE_PERMISSION_NAME = "Create Invite"
-const GUILD_BOT_ROLE_NAME = "Guild.xyz bot"
-/**
- * If this list changes, make sure to replace the public/discord_permissions.png
- * image
- */
-export const REQUIRED_PERMISSIONS = [
- MANAGE_ROLES_PERMISSION_NAME,
- "View Channels",
- MANAGE_SERVER_PERMISSION_NAME,
- CREATE_INVITE_PERMISSION_NAME,
- "Send Messages",
- "Embed Links",
- "Add Reactions",
- "Use External Emoji",
-] as const
-
-type DiscordPermissions = {
- permissions: Record<
- string,
- {
- name: string
- value: boolean
- }
- >
- roleOrders: {
- discordRoleId: string
- roleName: string
- rolePosition: number
- }[]
-}
-
-type PermissionModalType = "PERMISSIONS" | "NAME" | "ROLE_ORDER"
-
-const MODAL_CONTENT: Record<
- PermissionModalType,
- {
- title: string
- body: JSX.Element
- }
-> = {
- PERMISSIONS: {
- title: "Setup bot permissions",
- body: (
- <>
-
-
- Our bot requires the following permissions in order to function properly:
-
-
- {REQUIRED_PERMISSIONS.map((permission) => (
- {permission}
- ))}
-
-
-
- Your browser does not support the HTML5 video tag.
-
- >
- ),
- },
- ROLE_ORDER: {
- title: "Move the Guild.xyz bot role",
- body: ,
- },
- NAME: {
- title: "Keep the Guild.xyz bot role name",
- body: (
-
- Seems like you've changed our bot role's name on your Discord server. It
- might not work properly unless you keep its original name (
- Guild.xyz bot ).
-
- ),
- },
-}
-
-const DiscordBotPermissionsChecker = () => {
- const { isAdmin } = useGuildPermission()
- const { id, guildPlatforms, roles } = useGuild()
- const discordRewards = useMemo(
- () =>
- guildPlatforms?.filter((gp) => gp.platformId === PlatformType.DISCORD) ?? [],
- [guildPlatforms]
- )
-
- const [errorType, setErrorType] = useState()
- const { isOpen, onOpen, onClose } = useDisclosure()
-
- const fetchDiscordPermissions = () =>
- Promise.all(
- discordRewards?.map((gp) =>
- fetcher(`/v2/discord/servers/${gp.platformGuildId}/permissions`)
- )
- )
-
- const toastWithButton = useToastWithButton()
- const toastIdRef = useRef()
-
- useSWRImmutable(
- discordRewards.length > 0 && isAdmin ? ["discordPermissions", id] : null,
- fetchDiscordPermissions,
- {
- shouldRetryOnError: false,
- onSuccess: (data, _key, _config) => {
- if (!!toastIdRef.current) return
-
- const toastOptions: ActionToastOptions = {
- status: "warning",
- duration: null,
- isClosable: false,
- buttonProps: {
- children: "Fix problem",
- leftIcon: ,
- onClick: onOpen,
- },
- secondButtonProps: {
- children: "Later",
- variant: "ghost",
- },
- }
-
- for (const [index, permissionInfo] of data.entries()) {
- const {
- platformGuildName: serverName,
- platformGuildData: { invite },
- } = discordRewards[index]
-
- const permissionsNotGranted = Object.values(
- permissionInfo.permissions
- ).filter((perm) => !perm.value && perm.name !== "Administrator")
-
- if (
- // We always need the "Manage Roles" permissions
- permissionsNotGranted.find(
- (p) => p.name === MANAGE_ROLES_PERMISSION_NAME
- ) ||
- // We need the Manage Server & Create Invite permissions if there's no custom invite
- (!invite &&
- permissionsNotGranted.find(
- (p) =>
- p.name === CREATE_INVITE_PERMISSION_NAME ||
- p.name === MANAGE_SERVER_PERMISSION_NAME
- ))
- ) {
- toastIdRef.current = toastWithButton({
- title: "Missing permissions",
- description: `${permissionsNotGranted
- .map((perm) => perm.name)
- .join(", ")} permission${
- permissionsNotGranted.length > 1 ? "s are" : " is"
- } missing for the Guild.xyz bot on ${
- serverName ? `the ${serverName}` : "your"
- } Discord server.`,
- ...toastOptions,
- })
- setErrorType("PERMISSIONS")
- return
- }
-
- const discordBotsRole = permissionInfo.roleOrders.find(
- (role) => role.roleName === GUILD_BOT_ROLE_NAME
- )
-
- if (!discordBotsRole) {
- toastIdRef.current = toastWithButton({
- title: "Guild.xyz Discord bot is misconfigured",
- description: `Seems like you've changed our bot's role name on the ${serverName} Discord server. It might not work properly unless you keep its original name.`,
- ...toastOptions,
- })
- setErrorType("NAME")
- return
- }
-
- const discordRewardIds = discordRewards.map((gp) => gp.id)
- const relevantDiscordRoles =
- roles
- ?.filter((role) =>
- role.rolePlatforms.some((rp) =>
- discordRewardIds.includes(rp.guildPlatformId)
- )
- )
- .flatMap((role) => role.rolePlatforms)
- .map((rp) => rp.platformRoleId) ?? []
- const rolesWithInvalidPosition = permissionInfo.roleOrders.filter(
- (r) =>
- relevantDiscordRoles.includes(r.discordRoleId) &&
- r.rolePosition > discordBotsRole.rolePosition
- )
-
- if (rolesWithInvalidPosition.length > 0) {
- toastIdRef.current = toastWithButton({
- title: "Guild.xyz Discord bot is misconfigured",
- description: `${rolesWithInvalidPosition
- .map((r) => r.roleName)
- .join(", ")} ${
- rolesWithInvalidPosition.length > 1 ? "roles" : "role"
- } will not be assigned to your members on the ${serverName} Discord server, since they are above the Guild.xyz bot role.`,
- ...toastOptions,
- })
- setErrorType("ROLE_ORDER")
- }
- }
- },
- onError: () => {
- toastIdRef.current = toastWithButton({
- status: "warning",
- title: "Guild.xyz Discord bot is missing",
- description:
- "The Guild.xyz bot is not a member of your Discord server, so it won't be able to manage your roles.",
- duration: null,
- isClosable: false,
- buttonProps: {
- children: "Invite bot",
- rightIcon: ,
- onClick: () =>
- window.open(
- `https://discord.com/api/oauth2/authorize?client_id=${env.NEXT_PUBLIC_DISCORD_CLIENT_ID}&permissions=268716145&scope=bot%20applications.commands`
- ),
- },
- secondButtonProps: {
- children: "Later",
- variant: "ghost",
- },
- })
- },
- }
- )
-
- return (
-
-
-
-
- {MODAL_CONTENT[errorType]?.title ?? "Discord bot error"}
-
-
-
-
- {MODAL_CONTENT[errorType]?.body ?? Unknown error }
-
-
-
-
-
- Done
-
-
-
-
- )
-}
-
-export default DiscordBotPermissionsChecker
diff --git a/src/components/[guild]/EditGuild/EditGuildButton.tsx b/src/components/[guild]/EditGuild/EditGuildButton.tsx
deleted file mode 100644
index f104a586a9..0000000000
--- a/src/components/[guild]/EditGuild/EditGuildButton.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { ButtonProps, IconButton, Tooltip } from "@chakra-ui/react"
-import { SlidersHorizontal } from "@phosphor-icons/react"
-import { useRouter } from "next/router"
-import useGuild from "../hooks/useGuild"
-
-const EditGuildButton = (props: ButtonProps): JSX.Element => {
- const router = useRouter()
- const { urlName } = useGuild()
-
- return (
-
- }
- aria-label="Edit Guild"
- onClick={() => router.push(`/${urlName}/dashboard`)}
- {...props}
- />
-
- )
-}
-
-export default EditGuildButton
diff --git a/src/components/[guild]/EditGuild/EditGuildFormComponent.tsx b/src/components/[guild]/EditGuild/EditGuildFormComponent.tsx
deleted file mode 100644
index 53b9778e09..0000000000
--- a/src/components/[guild]/EditGuild/EditGuildFormComponent.tsx
+++ /dev/null
@@ -1,273 +0,0 @@
-import {
- Box,
- Divider,
- FormLabel,
- HStack,
- Stack,
- Text,
- VStack,
- useDisclosure,
-} from "@chakra-ui/react"
-import Admins from "components/[guild]/EditGuild/components/Admins/Admins"
-import BackgroundImageUploader from "components/[guild]/EditGuild/components/BackgroundImageUploader"
-import ChangingGuildPinDesignAlert from "components/[guild]/EditGuild/components/ChangingGuildPinDesignAlert"
-import GuildColorPicker from "components/[guild]/EditGuild/components/GuildColorPicker"
-import HideFromExplorerToggle from "components/[guild]/EditGuild/components/HideFromExplorerToggle"
-import SocialLinks from "components/[guild]/EditGuild/components/SocialLinks"
-import TagManager from "components/[guild]/EditGuild/components/TagManager"
-import UrlName from "components/[guild]/EditGuild/components/UrlName"
-import useEditGuild from "components/[guild]/EditGuild/hooks/useEditGuild"
-import useEditTags from "components/[guild]/EditGuild/hooks/useEditTags"
-import { useThemeContext } from "components/[guild]/ThemeContext"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useUser from "components/[guild]/hooks/useUser"
-import Button from "components/common/Button"
-import { FloatingFooter } from "components/common/FloatingFooter"
-import Section from "components/common/Section"
-import ContactInfo from "components/create-guild/BasicInfo/components/ContactInfo"
-import Description from "components/create-guild/Description"
-import DynamicDevTool from "components/create-guild/DynamicDevTool"
-import IconSelector from "components/create-guild/IconSelector"
-import Name from "components/create-guild/Name"
-import { AnimatePresence, motion } from "framer-motion"
-import usePinata from "hooks/usePinata"
-import useSubmitWithUpload from "hooks/useSubmitWithUpload"
-import useToast from "hooks/useToast"
-import useWarnIfUnsavedChanges from "hooks/useWarnIfUnsavedChanges"
-import dynamic from "next/dynamic"
-import { useCallback } from "react"
-import { FormProvider, useForm } from "react-hook-form"
-import handleSubmitDirty from "utils/handleSubmitDirty"
-import { EditGuildForm } from "./types"
-
-const DynamicFeatureFlags = dynamic(
- () => import("components/[guild]/EditGuild/components/FeatureFlags")
-)
-
-const MotionFloatingFooter = motion(FloatingFooter)
-
-const EditGuildFormComponent = () => {
- const {
- name,
- imageUrl,
- description,
- theme,
- showMembers,
- admins,
- urlName,
- hideFromExplorer,
- socialLinks,
- contacts,
- featureFlags,
- tags: savedTags,
- guildPin,
- } = useGuild()
- const { isSuperAdmin } = useUser()
-
- const defaultValues = {
- name,
- imageUrl,
- description,
- theme: theme
- ? {
- backgroundCss: theme?.backgroundCss,
- backgroundImage: theme?.backgroundImage,
- color: theme?.color,
- }
- : {},
- showMembers,
- admins: admins?.map(({ address }) => ({ address })) ?? [],
- urlName,
- hideFromExplorer,
- contacts: contacts || [],
- socialLinks,
- featureFlags: isSuperAdmin ? featureFlags : undefined,
- tags: savedTags,
- } satisfies EditGuildForm
-
- const methods = useForm({
- mode: "all",
- defaultValues,
- })
- const { control, reset, formState } = methods
-
- const { onSubmit: onTagsSubmit } = useEditTags()
-
- const toast = useToast()
-
- const onSuccess = () => {
- toast({
- title: `Guild successfully updated!`,
- status: "success",
- })
- reset(undefined, { keepValues: true })
- }
-
- const { onSubmit, isLoading } = useEditGuild({
- onSuccess,
- })
-
- const { setLocalBackgroundImage } = useThemeContext()
-
- useWarnIfUnsavedChanges(formState.isDirty && !formState.isSubmitted)
-
- const {
- isOpen: isSaveAlertOpen,
- onOpen: onSaveAlertOpen,
- onClose: onSaveAlertClose,
- } = useDisclosure()
-
- const iconUploader = usePinata({
- fieldToSetOnSuccess: "imageUrl",
- fieldToSetOnError: "imageUrl",
- control: methods.control,
- })
-
- const onBackgroundUploadError = useCallback(() => {
- setLocalBackgroundImage(null)
- }, [setLocalBackgroundImage])
-
- const backgroundUploader = usePinata({
- fieldToSetOnSuccess: "theme.backgroundImage",
- onError: onBackgroundUploadError,
- control: methods.control,
- })
-
- const { handleSubmit, isUploadingShown, uploadLoadingText } = useSubmitWithUpload(
- () => {
- handleSubmitDirty(methods)((data) => {
- const { tags, ...dataWithoutTags } = data
- onSubmit(dataWithoutTags)
- if (tags) onTagsSubmit(tags)
- })()
- },
- backgroundUploader.isUploading || iconUploader.isUploading
- )
-
- const loadingText = uploadLoadingText || "Saving data"
-
- const isDirty =
- !!Object.keys(formState.dirtyFields).length ||
- backgroundUploader.isUploading ||
- iconUploader.isUploading
-
- const onSave = (e) => {
- if (
- guildPin?.isActive &&
- (formState.dirtyFields.name ||
- formState.dirtyFields.imageUrl ||
- iconUploader.isUploading ||
- formState.dirtyFields.theme?.color)
- ) {
- onSaveAlertOpen()
- } else {
- handleSubmit(e)
- }
- }
-
- return (
- <>
-
-
-
-
-
-
- Logo and name
-
-
-
-
-
-
-
-
-
- Social links
-
-
-
-
-
-
-
-
-
-
- {savedTags?.includes("VERIFIED") && }
-
-
-
-
-
-
-
- Only visible to the Guild Team to reach you with support and
- partnership initiatives if needed.
-
-
-
-
- {isSuperAdmin && (
- <>
-
-
-
-
-
- Enabled features
-
-
-
- >
- )}
-
-
-
- {isDirty && (
-
-
- You have unsaved changes!
- 0}
- data-test="save-guild-button"
- isLoading={isLoading || isUploadingShown}
- colorScheme="green"
- loadingText={loadingText}
- onClick={onSave}
- maxW="max-content"
- >
- Save changes
-
-
-
- )}
-
-
-
-
-
-
- >
- )
-}
-
-export { EditGuildFormComponent }
diff --git a/src/components/[guild]/EditGuild/components/Admins/Admins.tsx b/src/components/[guild]/EditGuild/components/Admins/Admins.tsx
deleted file mode 100644
index b2ae11e6b8..0000000000
--- a/src/components/[guild]/EditGuild/components/Admins/Admins.tsx
+++ /dev/null
@@ -1,240 +0,0 @@
-import {
- Flex,
- FormControl,
- FormErrorMessage,
- FormLabel,
- Tag,
- useColorMode,
-} from "@chakra-ui/react"
-import { HStack, Icon, forwardRef } from "@chakra-ui/react"
-import { Warning } from "@phosphor-icons/react"
-import {
- CreatableSelect,
- GroupBase,
- MultiValueGenericProps,
- Props,
- chakraComponents,
-} from "chakra-react-select"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useGuildPermission from "components/[guild]/hooks/useGuildPermission"
-import useUser from "components/[guild]/hooks/useUser"
-import CopyableAddress from "components/common/CopyableAddress"
-import GuildAvatar from "components/common/GuildAvatar"
-import StyledSelect from "components/common/StyledSelect"
-import CustomMenuList from "components/common/StyledSelect/components/CustomMenuList"
-import { useMemo } from "react"
-import { PropsWithChildren, useEffect } from "react"
-import { useController, useFormContext, useWatch } from "react-hook-form"
-import { FUEL_ADDRESS_REGEX, SelectOption } from "types"
-import { ADDRESS_REGEX } from "utils/guildCheckout/constants"
-import shortenHex from "utils/shortenHex"
-import { useEnsAddress } from "wagmi"
-import { mainnet } from "wagmi/chains"
-import TransferOwnership from "../TransferOwnership/TransferOwnership"
-
-export const isValidAddress = (address: string) =>
- ADDRESS_REGEX.test(address) || FUEL_ADDRESS_REGEX.test(address)
-
-const validateAdmins = (admins: string[]) =>
- (typeof admins?.[0] === "string"
- ? admins.every((admin) => isValidAddress(admin.trim()))
- : admins.every((addr: any) => isValidAddress(addr?.address.trim()))) ||
- "Every admin should be a valid address"
-
-const Admins = () => {
- const { isSuperAdmin } = useUser()
- const { formState } = useFormContext()
- const { admins: guildAdmins } = useGuild()
- const { isOwner } = useGuildPermission()
- const ownerAddress = useMemo(
- () => guildAdmins?.find((admin) => admin.isOwner)?.address,
- [guildAdmins]
- )
-
- const {
- field: { onChange, ref, value: admins, onBlur },
- } = useController({
- name: "admins",
- rules: { validate: validateAdmins },
- })
-
- const adminOptions = useMemo(() => {
- if (!admins) return []
-
- const ownerOption = {
- value: ownerAddress,
- label: isValidAddress(ownerAddress) ? shortenHex(ownerAddress) : ownerAddress,
- img: ,
- isFixed: true,
- }
-
- const restAdminOptions = admins
- .filter((admin) => admin.address !== ownerAddress)
- .map((admin) => ({
- value: admin.address,
- label: isValidAddress(ownerAddress)
- ? shortenHex(admin.address)
- : admin.address,
- img: ,
- }))
-
- return [ownerOption].concat(restAdminOptions)
- }, [admins, ownerAddress])
-
- const isLoading = !guildAdmins || !adminOptions
-
- return (
- <>
-
-
-
- Admins{" "}
- {!isOwner && !isSuperAdmin && (
- only editable by the Guild owner
- )}
-
- {isOwner || isSuperAdmin ? : null}
-
-
- {
- ref(el)
- if (!el?.inputRef) return
- setTimeout(() => {
- el.inputRef?.addEventListener("paste", (event) => {
- const pastedData = event.clipboardData
- .getData("text")
- ?.trim()
- ?.toLowerCase()
-
- if (!isValidAddress(pastedData)) return
- event.preventDefault()
- if (admins.some(({ address }) => address === pastedData)) return
- onChange([...admins, { address: pastedData }])
- el.inputRef.focus()
- })
- }, 100)
- }}
- value={adminOptions}
- options={adminOptions}
- isMulti
- onBlur={onBlur}
- onChange={(selectedOption: SelectOption[]) => {
- onChange(
- selectedOption?.map((option) => ({
- address: option.value.toLowerCase(),
- }))
- )
- }}
- isLoading={isLoading}
- isClearable={false}
- isDisabled={!isOwner && !isSuperAdmin}
- chakraStyles={{ valueContainer: (base) => ({ ...base, py: 2 }) }}
- />
-
-
- {formState.errors.admins?.message as string}
-
-
- >
- )
-}
-
-const CustomMultiValueContainer = (props) => {
- const { colorMode } = useColorMode()
-
- return (
-
- )
-}
-
-type PropsHelper = MultiValueGenericProps>
-const CustomMultiValueLabel = (props: PropsWithChildren) => {
- const domain = isValidAddress(props.data.value) ? undefined : props.data.value
- const { data: resolvedAddress } = useEnsAddress({
- name: domain,
- chainId: mainnet.id,
- })
-
- const { setError, setValue, control, trigger } = useFormContext()
- const admins = useWatch({ control: control, name: "admins" })
-
- useEffect(() => {
- if (
- domain &&
- resolvedAddress &&
- !admins.includes(resolvedAddress.toLowerCase())
- ) {
- setValue(
- "admins",
- admins.map((admin) =>
- admin.address === domain ? { address: resolvedAddress } : admin
- )
- )
-
- trigger("admins")
- }
-
- if (resolvedAddress === null) {
- setError("admins", {
- message: "Resolving address failed",
- })
- }
- }, [domain, resolvedAddress, admins, setValue, trigger, setError])
-
- return (
-
-
- {resolvedAddress === null ||
- admins.includes(resolvedAddress?.toLowerCase()) ? (
-
- ) : (
- props.data.img
- )}
-
-
-
- )
-}
-
-const AdminSelect = forwardRef((props: Props, ref) => (
- (
-
- ),
- DropdownIndicator: () => null,
- MenuList: (props) => ,
- }}
- openMenuOnClick={false}
- ref={ref}
- {...props}
- chakraStyles={{
- valueContainer: (base) => ({ ...base, py: 2, px: 3 }),
- multiValue: (base) => ({ ...base, minH: "7" }),
- input: (provided) => ({
- ...provided,
- pl: 1,
- minWidth: "14ch",
- }),
- }}
- />
-))
-
-export default Admins
diff --git a/src/components/[guild]/EditGuild/components/BackgroundImageUploader.tsx b/src/components/[guild]/EditGuild/components/BackgroundImageUploader.tsx
deleted file mode 100644
index 776ce75d00..0000000000
--- a/src/components/[guild]/EditGuild/components/BackgroundImageUploader.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { FormControl, FormLabel, Progress, Wrap } from "@chakra-ui/react"
-import { File } from "@phosphor-icons/react"
-import { useThemeContext } from "components/[guild]/ThemeContext"
-import Button from "components/common/Button"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import useDropzone, { ERROR_MESSAGES } from "hooks/useDropzone"
-import { Uploader } from "hooks/usePinata/usePinata"
-import { useState } from "react"
-import RemoveBackgroundImage from "./RemoveBackgroundImage"
-
-type Props = {
- uploader: Uploader
-}
-
-const BackgroundImageUploader = ({
- uploader: { isUploading, onUpload },
-}: Props): JSX.Element => {
- const { localBackgroundImage, setLocalBackgroundImage } = useThemeContext()
- const [progress, setProgress] = useState(0)
-
- const { isDragActive, fileRejections, getRootProps, getInputProps } = useDropzone({
- multiple: false,
- onDrop: (accepted) => {
- if (accepted.length > 0) {
- setLocalBackgroundImage(URL.createObjectURL(accepted[0]))
- onUpload({ data: [accepted[0]], onProgress: setProgress })
- }
- },
- })
-
- return (
-
- Custom background image
-
-
- {isUploading ? (
-
- ) : (
- }
- h="10"
- >
-
- {isDragActive ? "Drop the file here" : "Choose image"}
-
- )}
- {localBackgroundImage && }
-
-
-
- {ERROR_MESSAGES[fileRejections?.[0]?.errors?.[0]?.code] ??
- fileRejections?.[0]?.errors?.[0]?.message}
-
-
- )
-}
-
-export default BackgroundImageUploader
diff --git a/src/components/[guild]/EditGuild/components/ChangingGuildPinDesignAlert.tsx b/src/components/[guild]/EditGuild/components/ChangingGuildPinDesignAlert.tsx
deleted file mode 100644
index bb91eb165c..0000000000
--- a/src/components/[guild]/EditGuild/components/ChangingGuildPinDesignAlert.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import {
- AlertDialogBody,
- AlertDialogContent,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogOverlay,
- AlertDialogProps,
-} from "@chakra-ui/react"
-import Button from "components/common/Button"
-import { Alert } from "components/common/Modal"
-import { useRef } from "react"
-
-type Props = {
- onSave: (e: any) => void
-} & Omit
-
-const ChangingGuildPinDesignAlert = ({
- onClose,
- onSave,
- ...rest
-}: Props): JSX.Element => {
- const cancelRef = useRef()
-
- return (
-
-
-
- Edit Guild Pin design?
-
- Changing the image, name or color of your guild will affect the
- appearance of the mintable Guild Pin too. Users who have already minted
- will see the previous version, while future minters will see the new one.
- Would you like to proceed?
-
-
-
- Cancel
-
- {
- onSave(e)
- onClose()
- }}
- >
- Yes, save changes
-
-
-
-
-
- )
-}
-
-export default ChangingGuildPinDesignAlert
diff --git a/src/components/[guild]/EditGuild/components/DeleteGuildButton/DeleteGuildButton.tsx b/src/components/[guild]/EditGuild/components/DeleteGuildButton/DeleteGuildButton.tsx
deleted file mode 100644
index bafa9d4b4a..0000000000
--- a/src/components/[guild]/EditGuild/components/DeleteGuildButton/DeleteGuildButton.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { useDisclosure } from "@chakra-ui/react"
-import Button from "components/common/Button"
-import ConfirmationAlert from "components/create-guild/Requirements/components/ConfirmationAlert"
-import useDeleteGuild from "./hooks/useDeleteGuild"
-
-const DeleteGuildButton = (): JSX.Element => {
- const { onSubmit, isLoading, isSigning } = useDeleteGuild()
- const { isOpen, onOpen, onClose } = useDisclosure()
-
- return (
- <>
-
- Delete guild
-
-
- onSubmit()}
- title="Delete guild"
- description="Are you sure you want to delete this guild?"
- confirmationText="Delete"
- loadingText={isSigning && "Check your wallet"}
- />
- >
- )
-}
-
-export default DeleteGuildButton
diff --git a/src/components/[guild]/EditGuild/components/DeleteGuildButton/hooks/useDeleteGuild.ts b/src/components/[guild]/EditGuild/components/DeleteGuildButton/hooks/useDeleteGuild.ts
deleted file mode 100644
index db7939b99b..0000000000
--- a/src/components/[guild]/EditGuild/components/DeleteGuildButton/hooks/useDeleteGuild.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { useYourGuilds } from "@/hooks/useYourGuilds"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useMatchMutate from "hooks/useMatchMutate"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import useToast from "hooks/useToast"
-import { useRouter } from "next/router"
-import { GuildBase } from "types"
-import fetcher from "utils/fetcher"
-
-const useDeleteGuild = () => {
- const toast = useToast()
- const showErrorToast = useShowErrorToast()
- const router = useRouter()
-
- const { mutate: mutateYourGuilds } = useYourGuilds()
- const matchMutate = useMatchMutate()
-
- const guild = useGuild()
-
- const submit = async (signedValidation: SignedValidation) =>
- fetcher(`/v2/guilds/${guild.id}`, {
- method: "DELETE",
- ...signedValidation,
- })
-
- return useSubmitWithSign(submit, {
- onSuccess: () => {
- toast({
- title: `Guild deleted!`,
- description: "You're being redirected to the home page",
- status: "success",
- })
-
- mutateYourGuilds((prev) => mutateGuildsCache(prev, guild.id), {
- revalidate: false,
- })
- matchMutate(
- /\/guilds\?order/,
- (prev) => mutateGuildsCache(prev, guild.id),
- { revalidate: false }
- )
-
- router.push("/explorer")
- },
- onError: (error) => showErrorToast(error),
- forcePrompt: true,
- })
-}
-
-const mutateGuildsCache = (prev: GuildBase[], deletedGuildId: number) =>
- prev?.filter((guild) => guild.id !== deletedGuildId)
-
-export default useDeleteGuild
diff --git a/src/components/[guild]/EditGuild/components/DeleteGuildButton/index.ts b/src/components/[guild]/EditGuild/components/DeleteGuildButton/index.ts
deleted file mode 100644
index 596d90d006..0000000000
--- a/src/components/[guild]/EditGuild/components/DeleteGuildButton/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import DeleteGuildButton from "./DeleteGuildButton"
-
-export default DeleteGuildButton
diff --git a/src/components/[guild]/EditGuild/components/FeatureFlags.tsx b/src/components/[guild]/EditGuild/components/FeatureFlags.tsx
deleted file mode 100644
index 5801270ee1..0000000000
--- a/src/components/[guild]/EditGuild/components/FeatureFlags.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { FormControl } from "@chakra-ui/react"
-import StyledSelect from "components/common/StyledSelect"
-import { useController } from "react-hook-form"
-import { SelectOption } from "types"
-import capitalize from "utils/capitalize"
-
-const FEATURE_FLAGS = [
- "TWITTER_EXTRA_REQUIREMENT",
- "GUILD_CREDENTIAL",
- "CRM",
- "GUILD_QUEUES",
- "ERC20",
- "PERIODIC_SYNC",
- "ONGOING_ISSUES",
- "PAYMENT_REQUIREMENT",
-] as const
-export type FeatureFlag = (typeof FEATURE_FLAGS)[number]
-
-const generateLabel = (flag: FeatureFlag) =>
- flag
- .toLowerCase()
- .split("_")
- .map((word) => capitalize(word))
- .join(" ")
-
-const FeatureFlags = (): JSX.Element => {
- const options: SelectOption[] = FEATURE_FLAGS.map((flag) => ({
- label: generateLabel(flag),
- value: flag,
- }))
-
- const {
- field: { ref, name, value, onChange, onBlur },
- } = useController({ name: "featureFlags" })
-
- return (
-
- value?.includes(option.value))}
- isMulti
- options={options}
- onChange={(selectedOption: (SelectOption | string)[]) => {
- onChange(
- selectedOption.map((option) =>
- typeof option === "string" ? option : option.value
- )
- )
- }}
- onBlur={onBlur}
- chakraStyles={{ valueContainer: (base) => ({ ...base, py: 2, px: 3 }) }}
- />
-
- )
-}
-
-export default FeatureFlags
diff --git a/src/components/[guild]/EditGuild/components/GuildColorPicker.tsx b/src/components/[guild]/EditGuild/components/GuildColorPicker.tsx
deleted file mode 100644
index 265e5ddbb5..0000000000
--- a/src/components/[guild]/EditGuild/components/GuildColorPicker.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { ColorPicker } from "@/components/ui/ColorPicker"
-import { FormControl, FormLabel } from "@chakra-ui/react"
-import { useThemeContext } from "components/[guild]/ThemeContext"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import { useFormContext } from "react-hook-form"
-
-type Props = {
- fieldName: string
-}
-
-const GuildColorPicker = ({ fieldName }: Props): JSX.Element => {
- const {
- formState: { errors },
- } = useFormContext()
-
- const { setLocalThemeColor } = useThemeContext()
-
- return (
-
- Main color
-
- {errors[fieldName]?.message as string}
-
- )
-}
-
-export default GuildColorPicker
diff --git a/src/components/[guild]/EditGuild/components/HideFromExplorerToggle.tsx b/src/components/[guild]/EditGuild/components/HideFromExplorerToggle.tsx
deleted file mode 100644
index 8ed6e202e3..0000000000
--- a/src/components/[guild]/EditGuild/components/HideFromExplorerToggle.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { FormControl } from "@chakra-ui/react"
-import Switch from "components/common/Switch"
-import { useFormContext } from "react-hook-form"
-
-const HideFromExplorerToggle = (): JSX.Element => {
- const { register } = useFormContext()
-
- return (
-
-
-
- )
-}
-
-export default HideFromExplorerToggle
diff --git a/src/components/[guild]/EditGuild/components/LeaveGuildButton.tsx b/src/components/[guild]/EditGuild/components/LeaveGuildButton.tsx
deleted file mode 100644
index c1bedfda48..0000000000
--- a/src/components/[guild]/EditGuild/components/LeaveGuildButton.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import {
- AlertDialogBody,
- AlertDialogContent,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogOverlay,
- ButtonProps,
- useDisclosure,
-} from "@chakra-ui/react"
-import useLeaveGuild from "components/[guild]/LeaveButton/hooks/useLeaveGuild"
-import useGuild from "components/[guild]/hooks/useGuild"
-import Button from "components/common/Button"
-import { Alert } from "components/common/Modal"
-import useMembership from "components/explorer/hooks/useMembership"
-import { useRouter } from "next/navigation"
-import { useRef } from "react"
-
-const LeaveGuildButton = (props: ButtonProps) => {
- const { isOpen, onOpen, onClose } = useDisclosure()
- const cancelRef = useRef()
- const router = useRouter()
-
- const { id: guildId } = useGuild()
- const { isMember } = useMembership()
- const { onSubmit, isLoading } = useLeaveGuild(() => router.replace("/explorer"))
-
- if (!isMember) return null
-
- return (
- <>
-
- Leave guild
-
-
-
-
-
-
- Leave guild
-
- Are you sure? You'll lose all your roles and your admin status.
-
-
-
- Cancel
-
- onSubmit({ guildId })}
- isLoading={isLoading}
- data-testid="leave-alert-button"
- >
- Leave guild
-
-
-
-
- >
- )
-}
-
-export default LeaveGuildButton
diff --git a/src/components/[guild]/EditGuild/components/RemoveBackgroundImage.tsx b/src/components/[guild]/EditGuild/components/RemoveBackgroundImage.tsx
deleted file mode 100644
index 338b1c51f1..0000000000
--- a/src/components/[guild]/EditGuild/components/RemoveBackgroundImage.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Icon } from "@chakra-ui/react"
-import { X } from "@phosphor-icons/react"
-import { useThemeContext } from "components/[guild]/ThemeContext"
-import Button from "components/common/Button"
-import { useFormContext } from "react-hook-form"
-
-const RemoveBackgroundImage = () => {
- const { setValue } = useFormContext()
- const { setLocalBackgroundImage } = useThemeContext()
-
- const handleRemoveImage = () => {
- setValue("theme.backgroundImage", "", { shouldDirty: true })
- setLocalBackgroundImage(null)
- }
-
- return (
- }
- colorScheme="red"
- variant="outline"
- borderWidth={1}
- h="10"
- onClick={handleRemoveImage}
- >
- Remove image
-
- )
-}
-
-export default RemoveBackgroundImage
diff --git a/src/components/[guild]/EditGuild/components/SocialLinks.tsx b/src/components/[guild]/EditGuild/components/SocialLinks.tsx
deleted file mode 100644
index e89c1994db..0000000000
--- a/src/components/[guild]/EditGuild/components/SocialLinks.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import {
- CloseButton,
- FormControl,
- GridItem,
- Icon,
- Input,
- InputGroup,
- InputLeftElement,
- InputRightElement,
- SimpleGrid,
-} from "@chakra-ui/react"
-import { SocialLinks as SocialLinksType, consts } from "@guildxyz/types"
-import { Plus } from "@phosphor-icons/react"
-import SocialIcon from "components/[guild]/SocialIcon"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import StyledSelect from "components/common/StyledSelect"
-import { useFormContext, useWatch } from "react-hook-form"
-import { SelectOption, SocialLinkKey } from "types"
-import capitalize from "utils/capitalize"
-import { z } from "zod"
-import { EditGuildForm } from "../types"
-
-const socialLinkUserPaths = {
- TWITTER: "https://x.com/",
- YOUTUBE: "https://youtube.com/",
- SPOTIFY: "https://open.spotify.com/user/",
- MEDIUM: "https://medium.com/",
- GITHUB: "https://github.com/",
- LENS: "",
- MIRROR: "",
- SNAPSHOT: "",
- SOUND: "",
- SUBSTACK: "",
- WARPCAST: "https://warpcast.com/",
- WEBSITE: "",
-} as const satisfies Record
-
-const socialLinkOptions = consts.SocialLinks.map((socialLink) => ({
- label: capitalize(socialLink.toLowerCase()),
- value: socialLink,
- img: ,
-})) satisfies SelectOption[]
-
-const SocialLinks = (): JSX.Element => {
- const {
- control,
- register,
- setValue,
- formState: { errors },
- } = useFormContext()
-
- const definedSocialLinks = useWatch({ control, name: "socialLinks" })
-
- const validateUrl = (input?: string) => {
- const { success } = z.string().url().safeParse(input)
- return success || "Invalid link format."
- }
- return (
-
- {(
- Object.entries(definedSocialLinks ?? {}) as [keyof SocialLinksType, string][]
- )
- .filter(([, value]) => typeof value !== "undefined")
- .map(([key]) => {
- const socialLinkOption = socialLinkOptions.find((sl) => sl.value === key)
-
- if (!socialLinkOption) return null
-
- return (
-
-
-
- {socialLinkOption.img}
-
-
-
- setValue(`socialLinks.${key}`, undefined, {
- shouldDirty: true,
- })
- }
- />
-
-
-
-
- {errors?.socialLinks?.[key]?.message}
-
-
-
- )
- })}
-
- typeof definedSocialLinks?.[sl.value] === "undefined"
- )}
- onChange={(newValue: (typeof socialLinkOptions)[number]) =>
- setValue(
- `socialLinks.${newValue.value}`,
- socialLinkUserPaths[newValue.value] ?? ""
- )
- }
- placeholder="Add more"
- value=""
- components={{
- DropdownIndicator: () => ,
- }}
- size="lg"
- />
-
-
- )
-}
-
-export default SocialLinks
diff --git a/src/components/[guild]/EditGuild/components/TagManager.tsx b/src/components/[guild]/EditGuild/components/TagManager.tsx
deleted file mode 100644
index 98b5647895..0000000000
--- a/src/components/[guild]/EditGuild/components/TagManager.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { HStack } from "@chakra-ui/react"
-import Switch from "components/common/Switch"
-import { useEffect } from "react"
-import { useForm, useFormContext } from "react-hook-form"
-import { GuildTags } from "types"
-
-const TagManager = (): JSX.Element => {
- const { setValue, getValues } = useFormContext()
- const { register, watch } = useForm<{ [k in GuildTags]: boolean }>({
- defaultValues: {
- VERIFIED: getValues("tags").includes("VERIFIED"),
- FEATURED: getValues("tags").includes("FEATURED"),
- },
- })
-
- useEffect(() => {
- const subscription = watch((data) => {
- const newTags: GuildTags[] = Object.keys(data)
- .filter((key) => watch(key as GuildTags))
- .map((key) => key as GuildTags)
-
- setValue("tags", newTags, {
- shouldDirty: true,
- })
- })
- return () => subscription.unsubscribe()
- }, [watch, setValue])
-
- return (
-
-
-
-
- )
-}
-
-export default TagManager
diff --git a/src/components/[guild]/EditGuild/components/TransferOwnership/TransferOwnership.tsx b/src/components/[guild]/EditGuild/components/TransferOwnership/TransferOwnership.tsx
deleted file mode 100644
index 69693c600e..0000000000
--- a/src/components/[guild]/EditGuild/components/TransferOwnership/TransferOwnership.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import {
- FormControl,
- FormLabel,
- HStack,
- Input,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalHeader,
- ModalOverlay,
- Text,
- useDisclosure,
-} from "@chakra-ui/react"
-import useGuild from "components/[guild]/hooks/useGuild"
-import Button from "components/common/Button"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import { Modal } from "components/common/Modal"
-import useToast from "hooks/useToast"
-import { useState } from "react"
-import useTransferOwnership from "./hooks/useTransferOwnership"
-
-const TransferOwnership = () => {
- const { isOpen, onOpen, onClose } = useDisclosure()
-
- return (
- <>
-
- Transfer ownership
-
-
- >
- )
-}
-
-const ADDRESS_REGEX = /^0x[a-f0-9]{40}$/i
-
-const TransferOwnershipModal = ({ isOpen, onClose }) => {
- const [newOwner, setNewOwner] = useState("")
- const { mutateGuild } = useGuild()
- const toast = useToast()
-
- const handleClose = () => {
- setNewOwner("")
- onClose()
- }
-
- const { onSubmit, isLoading } = useTransferOwnership({
- onSuccess: (res) => {
- toast({
- title: "Owner successfully changed!",
- status: "success",
- })
- mutateGuild(
- (oldData) => {
- const newAdmins = oldData.admins.map((admin) => ({
- ...admin,
- isOwner: admin.id === res.userId,
- }))
- if (newAdmins.every((admin) => admin.isOwner === false))
- newAdmins.push({
- id: res.userId,
- address: newOwner.toLowerCase(),
- isOwner: res.isOwner,
- })
- return {
- ...oldData,
- admins: newAdmins,
- }
- },
- { revalidate: false }
- )
- handleClose()
- },
- })
-
- const isValidAddress = ADDRESS_REGEX.test(newOwner)
-
- return (
-
-
-
- Transfer ownership
-
-
-
- Are you sure that you want to hand over your ownership? You'll remain an
- admin, but the new owner will be able to remove you anytime.
-
-
- Address to transfer to
- setNewOwner(e.target.value)}
- />
-
- Please input a 42 characters long, 0x-prefixed hexadecimal address
-
-
-
-
- onSubmit({ to: newOwner })}
- colorScheme="red"
- isLoading={isLoading}
- loadingText={"Loading"}
- isDisabled={!isValidAddress}
- >
- Transfer ownership
-
-
-
-
-
- )
-}
-export default TransferOwnership
diff --git a/src/components/[guild]/EditGuild/components/TransferOwnership/hooks/useTransferOwnership.ts b/src/components/[guild]/EditGuild/components/TransferOwnership/hooks/useTransferOwnership.ts
deleted file mode 100644
index 457632d46b..0000000000
--- a/src/components/[guild]/EditGuild/components/TransferOwnership/hooks/useTransferOwnership.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import useSubmit from "hooks/useSubmit"
-
-type DataType = {
- to: string
-}
-
-type ResponseType = {
- guildId: number
- userId: number
- isOwner: boolean
-}
-
-const useTransferOwnership = ({
- onSuccess,
-}: {
- onSuccess: (res: ResponseType) => void
-}) => {
- const { id } = useGuild()
- const fetcherWithSign = useFetcherWithSign()
-
- const submit = async ({ to }: DataType) =>
- fetcherWithSign([
- `/v2/guilds/${id}/admins/${to}`,
- {
- method: "PUT",
- body: {
- isOwner: true,
- },
- signOptions: {
- forcePrompt: true,
- },
- },
- ])
-
- const showErrorToast = useShowErrorToast()
-
- return useSubmit(submit, {
- onSuccess: (res) => {
- if (onSuccess) onSuccess(res)
- },
- onError: (error) => showErrorToast(error),
- })
-}
-
-export default useTransferOwnership
diff --git a/src/components/[guild]/EditGuild/components/UrlName.tsx b/src/components/[guild]/EditGuild/components/UrlName.tsx
deleted file mode 100644
index fdab609b73..0000000000
--- a/src/components/[guild]/EditGuild/components/UrlName.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import {
- FormControl,
- FormLabel,
- Input,
- InputGroup,
- InputLeftAddon,
-} from "@chakra-ui/react"
-import useGuild from "components/[guild]/hooks/useGuild"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import { useFormContext, useFormState } from "react-hook-form"
-import fetcher from "utils/fetcher"
-import slugify from "utils/slugify"
-
-const checkUrlName = (urlName: string) =>
- fetcher(`/v2/guilds/${urlName}`)
- .then(() => true)
- .catch(() => false)
-
-const UrlName = () => {
- const { errors } = useFormState()
- const { register, setError, clearErrors, getFieldState } = useFormContext()
- const { urlName: currentUrlName } = useGuild()
-
- return (
-
- URL name
-
- guild.xyz/
- {
- const slugified = slugify(value)
- if (value !== slugified)
- return `Invalid URL name, try using "${slugified}" instead`
- },
- })}
- onBlur={(event) => {
- if (getFieldState("urlName").error) return
- checkUrlName(event.target.value).then((alreadyExists) => {
- if (alreadyExists && currentUrlName !== event.target.value) {
- setError("urlName", {
- message: "Sorry, this guild name is already taken",
- })
- } else {
- clearErrors("urlName")
- }
- })
- }}
- />
-
- {errors?.urlName?.message as string}
-
- )
-}
-
-export default UrlName
diff --git a/src/components/[guild]/EditGuild/hooks/useEditGuild.ts b/src/components/[guild]/EditGuild/hooks/useEditGuild.ts
deleted file mode 100644
index 73c125aa1e..0000000000
--- a/src/components/[guild]/EditGuild/hooks/useEditGuild.ts
+++ /dev/null
@@ -1,418 +0,0 @@
-import { useYourGuilds } from "@/hooks/useYourGuilds"
-import useGuild from "components/[guild]/hooks/useGuild"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import useMatchMutate from "hooks/useMatchMutate"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import useSubmit from "hooks/useSubmit"
-import { useRouter } from "next/router"
-import { GuildBase } from "types"
-import replacer from "utils/guildJsonReplacer"
-import { EditGuildForm } from "../types"
-
-type Props = {
- onSuccess?: () => void
- guildId?: string | number
-}
-
-const countFailed = (arr: Record[]) =>
- arr.filter((res) => !!res.error).length
-
-const getCorrelationId = (arr: Record[]) =>
- arr.filter((res) => !!res.error)[0]?.correlationId
-
-const getError = (arr: Record[]) =>
- arr.filter((res) => !!res.error)[0]?.error
-
-const useEditGuild = ({ onSuccess, guildId }: Props = {}) => {
- const guild = useGuild(guildId)
- const { mutate: mutateYourGuilds } = useYourGuilds()
-
- const matchMutate = useMatchMutate()
-
- const showErrorToast = useShowErrorToast()
- const router = useRouter()
- const fetcherWithSign = useFetcherWithSign()
-
- const id = guildId ?? guild?.id
-
- const submit = async (data: Partial) => {
- const existingFeatureFlags = guild?.featureFlags ?? []
- const existingContacts = guild?.contacts ?? []
- const existingAdmins = guild?.admins ?? []
- const { admins, featureFlags, contacts, ...guildData } = data
-
- const adminsToCreate = admins
- ? admins.filter(
- (admin) =>
- !existingAdmins.some(
- (existingAdmin) =>
- existingAdmin.address?.toLowerCase() === admin.address?.toLowerCase()
- )
- )
- : []
-
- const adminsToDelete = admins
- ? existingAdmins.filter(
- (existingAdmin) =>
- !existingAdmin?.isOwner &&
- !admins.some(
- (admin) =>
- existingAdmin.address?.toLowerCase() === admin.address?.toLowerCase()
- )
- )
- : []
-
- const contactsToUpdate = contacts
- ? contacts.filter((contact) => {
- if (!contact.id) return false
- const prevContact = existingContacts?.find((ec) => ec.id === contact.id)
- if (!prevContact) return false
- return (
- !!contact.id &&
- !(
- contact.id === prevContact.id &&
- contact.contact === prevContact.contact &&
- contact.type === prevContact.type
- )
- )
- })
- : []
- const contactsToCreate = contacts
- ? contacts.filter((contact) => !contact.id)
- : []
- const contactsToDelete = contacts
- ? existingContacts.filter(
- (existingContact) =>
- !contacts.some((contact) => contact.id === existingContact.id)
- )
- : []
-
- const featureFlagsToCreate = featureFlags
- ? featureFlags.filter((flag) => !existingFeatureFlags.includes(flag))
- : []
- const featureFlagsToDelete = featureFlags
- ? existingFeatureFlags.filter(
- (existingFlag) => !featureFlags.includes(existingFlag)
- )
- : []
-
- const shouldUpdateBaseGuild = guildData && Object.keys(guildData).length > 0
-
- const adminCreations = Promise.all(
- adminsToCreate.map((adminToCreate) =>
- fetcherWithSign([
- `/v2/guilds/${id}/admins`,
- { method: "POST", body: adminToCreate },
- ]).catch((error) => error)
- )
- )
-
- const adminDeletions = Promise.all(
- adminsToDelete.map((adminToDelete) =>
- fetcherWithSign([
- `/v2/guilds/${id}/admins/${adminToDelete.id}`,
- { method: "DELETE" },
- ])
- .then(() => ({ id: adminToDelete.id }))
- .catch((error) => error)
- )
- )
-
- const contactCreations = Promise.all(
- contactsToCreate.map((contactToCreate) =>
- fetcherWithSign([
- `/v2/guilds/${id}/contacts`,
- { method: "POST", body: contactToCreate },
- ]).catch((error) => error)
- )
- )
-
- const contactUpdates = Promise.all(
- contactsToUpdate.map((contactToUpdate) =>
- fetcherWithSign([
- `/v2/guilds/${id}/contacts/${contactToUpdate.id}`,
- { method: "PUT", body: contactToUpdate },
- ]).catch((error) => error)
- )
- )
-
- const contactDeletions = Promise.all(
- contactsToDelete.map((contactToDelete) =>
- fetcherWithSign([
- `/v2/guilds/${id}/contacts/${contactToDelete.id}`,
- { method: "DELETE" },
- ])
- .then(() => ({ id: contactToDelete.id }))
- .catch((error) => error)
- )
- )
-
- const featureFlagCreations = Promise.all(
- featureFlagsToCreate.map((featureFlagToCreate) =>
- fetcherWithSign([
- `/v2/guilds/${id}/feature-flags`,
- { method: "POST", body: { featureType: featureFlagToCreate } },
- ]).catch((error) => error)
- )
- )
-
- const featureFlagDeletions = Promise.all(
- featureFlagsToDelete.map((featureFlagToDelete) =>
- fetcherWithSign([
- `/v2/guilds/${id}/feature-flags/${featureFlagToDelete}`,
- { method: "DELETE" },
- ])
- .then(() => ({ flagType: featureFlagToDelete }))
- .catch((error) => error)
- )
- )
-
- const baseGuildUpdate = shouldUpdateBaseGuild
- ? fetcherWithSign([
- `/v2/guilds/${id}`,
- { method: "PUT", body: guildData },
- ]).catch((error) => error)
- : new Promise((resolve) => resolve())
-
- const [
- adminCreationResults,
- adminDeleteResults,
- contactCreationResults,
- contactUpdateResults,
- contactDeleteResults,
- featureFlagCreationResults,
- featureFlagDeletionResults,
- guildUpdateResult,
- ] = await Promise.all([
- adminCreations,
- adminDeletions,
- contactCreations,
- contactUpdates,
- contactDeletions,
- featureFlagCreations,
- featureFlagDeletions,
- baseGuildUpdate,
- ])
-
- return {
- admin: {
- creations: {
- success: adminCreationResults.filter((res) => !res.error),
- failedCount: countFailed(adminCreationResults),
- correlationId: getCorrelationId(adminCreationResults),
- error: getError(adminCreationResults),
- },
- deletions: {
- success: adminDeleteResults.filter((res) => !res.error),
- failedCount: countFailed(adminDeleteResults),
- correlationId: getCorrelationId(adminDeleteResults),
- error: getError(adminDeleteResults),
- },
- },
-
- contacts: {
- creations: {
- success: contactCreationResults.filter((res) => !res.error),
- failedCount: countFailed(contactCreationResults),
- correlationId: getCorrelationId(contactCreationResults),
- error: getError(contactCreationResults),
- },
- updates: {
- success: contactUpdateResults.filter((res) => !res.error),
- failedCount: countFailed(contactUpdateResults),
- correlationId: getCorrelationId(contactUpdateResults),
- error: getError(contactUpdateResults),
- },
- deletions: {
- success: contactDeleteResults.filter((res) => !res.error),
- failedCount: countFailed(contactDeleteResults),
- correlationId: getCorrelationId(contactDeleteResults),
- error: getError(contactDeleteResults),
- },
- },
-
- featureFlags: {
- creations: {
- success: featureFlagCreationResults.filter((res) => !res.error),
- failedCount: countFailed(featureFlagCreationResults),
- correlationId: getCorrelationId(featureFlagCreationResults),
- error: getError(featureFlagCreationResults),
- },
- deletions: {
- success: featureFlagDeletionResults.filter((res) => !res.error),
- failedCount: countFailed(featureFlagDeletionResults),
- correlationId: getCorrelationId(featureFlagDeletionResults),
- error: getError(featureFlagCreationResults),
- },
- },
-
- guildUpdateResult,
- } as const
- }
-
- const useSubmitResponse = useSubmit<
- Partial,
- Awaited>
- >(submit, {
- onSuccess: ({ admin, contacts, featureFlags, guildUpdateResult }) => {
- // Show success / error toasts
- if (
- admin.creations.failedCount <= 0 &&
- admin.deletions.failedCount <= 0 &&
- contacts.creations.failedCount <= 0 &&
- contacts.updates.failedCount <= 0 &&
- contacts.deletions.failedCount <= 0 &&
- featureFlags.creations.failedCount <= 0 &&
- featureFlags.deletions.failedCount <= 0 &&
- (!guildUpdateResult || (!!guildUpdateResult && !guildUpdateResult.error))
- ) {
- onSuccess?.()
- } else {
- if (admin.creations.failedCount > 0) {
- showErrorToast({
- error: admin.creations.error || "Failed to create some admins",
- correlationId: admin.creations.correlationId,
- })
- }
- if (admin.deletions.failedCount > 0) {
- showErrorToast({
- error: admin.deletions.error || "Failed to delete some admins",
- correlationId: admin.deletions.correlationId,
- })
- }
-
- if (contacts.creations.failedCount > 0) {
- showErrorToast({
- error: contacts.creations.error || "Failed to create some contacts",
- correlationId: contacts.creations.correlationId,
- })
- }
- if (contacts.updates.failedCount > 0) {
- showErrorToast({
- error: contacts.updates.error || "Failed to update some contacts",
- correlationId: contacts.updates.correlationId,
- })
- }
- if (contacts.deletions.failedCount > 0) {
- showErrorToast({
- error: contacts.deletions.error || "Failed to delete some contacts",
- correlationId: contacts.deletions.correlationId,
- })
- }
-
- if (featureFlags.creations.failedCount > 0) {
- showErrorToast({
- error:
- featureFlags.creations.error || "Failed to create some feature flags",
- correlationId: featureFlags.creations.correlationId,
- })
- }
- if (featureFlags.deletions.failedCount > 0) {
- showErrorToast({
- error:
- featureFlags.deletions.error || "Failed to delete some feature flags",
- correlationId: featureFlags.deletions.correlationId,
- })
- }
-
- if (guildUpdateResult?.error) {
- showErrorToast({
- error: guildUpdateResult.error || "Failed to update guild data",
- correlationId: guildUpdateResult.correlationId,
- })
- }
- }
-
- guild.mutateGuild(
- (prev) => {
- const oldAdminsThatHaventBeenDeleted = (prev?.admins ?? []).filter(
- (prevAdmin) =>
- !admin.deletions.success.some(
- (deletedAdmin) => deletedAdmin.id === prevAdmin.id
- )
- )
-
- const oldContactsThatHaventBeenDeletedNorUpdated = (
- prev?.contacts ?? []
- ).filter(
- (prevContact) =>
- !contacts.deletions.success.some(
- (deletedContact) => deletedContact.id === prevContact.id
- ) &&
- !contacts.updates.success.some(
- (updatedContact) => updatedContact.id === prevContact.id
- )
- )
-
- const oldFeatureFlagsThatHaventBeenDeleted = (
- prev?.featureFlags ?? []
- ).filter(
- (prevFeatureFlag) =>
- !featureFlags.deletions.success.some(
- (deletedFlag) => deletedFlag.featureType === prevFeatureFlag
- )
- )
-
- return {
- ...prev,
- ...(guildUpdateResult ?? {}),
- admins: [...oldAdminsThatHaventBeenDeleted, ...admin.creations.success],
- contacts: [
- ...oldContactsThatHaventBeenDeletedNorUpdated,
- ...contacts.updates.success,
- ...contacts.creations.success,
- ],
- featureFlags: [
- ...oldFeatureFlagsThatHaventBeenDeleted,
- ...featureFlags.creations.success.map(
- (createdFlag) => createdFlag.featureType
- ),
- ],
- }
- },
- { revalidate: false }
- )
-
- if (guildUpdateResult?.id) {
- mutateYourGuilds((prev) => mutateGuildsCache(prev, guildUpdateResult), {
- revalidate: false,
- })
- matchMutate(
- /\/guilds\?order/,
- (prev) => mutateGuildsCache(prev, guildUpdateResult),
- { revalidate: false }
- )
- }
-
- const guildPinCacheKeysRegExp = new RegExp(
- `^/assets/guildPins/image\\?guildId=${id}&guildAction=\\d`
- )
- matchMutate(guildPinCacheKeysRegExp)
-
- if (
- guildUpdateResult?.urlName &&
- guildUpdateResult.urlName !== guild?.urlName
- ) {
- router.push(`/${guildUpdateResult.urlName}/dashboard`)
- }
- },
- onError: (err) => showErrorToast(err),
- })
-
- return {
- ...useSubmitResponse,
- onSubmit: (data: Partial) =>
- useSubmitResponse.onSubmit(JSON.parse(JSON.stringify(data, replacer))),
- }
-}
-
-const mutateGuildsCache = (prev: GuildBase[], editedGuild: GuildBase) =>
- prev?.map((_guild) => {
- if (_guild.id !== editedGuild.id) return _guild
- return {
- ..._guild,
- ...editedGuild,
- }
- })
-
-export default useEditGuild
diff --git a/src/components/[guild]/EditGuild/hooks/useEditTags.ts b/src/components/[guild]/EditGuild/hooks/useEditTags.ts
deleted file mode 100644
index 508e2498f0..0000000000
--- a/src/components/[guild]/EditGuild/hooks/useEditTags.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import useSubmit from "hooks/useSubmit/useSubmit"
-import { GuildTags } from "./../../../../types"
-
-const useEditTags = () => {
- const { tags: defaultTags, id: guildId } = useGuild()
- const showErrorToast = useShowErrorToast()
- const fetcherWithSign = useFetcherWithSign()
-
- const addTag = (tag: GuildTags) =>
- fetcherWithSign([
- `/v2/guilds/${guildId}/tags`,
- {
- method: "POST",
- body: {
- tag,
- },
- },
- ])
-
- const deleteTag = (tag: GuildTags) =>
- fetcherWithSign([
- `/v2/guilds/${guildId}/tags/${tag}`,
- {
- method: "DELETE",
- body: {},
- },
- ])
-
- const submit = async (tags: GuildTags[]) => {
- const tagPromises = []
-
- defaultTags.forEach((tag) => {
- if (!tags.includes(tag)) {
- tagPromises.push(deleteTag(tag))
- }
- })
-
- tags.forEach((tag) => {
- if (!defaultTags.includes(tag)) {
- tagPromises.push(addTag(tag))
- }
- })
-
- return Promise.all(tagPromises)
- }
-
- return useSubmit(submit, {
- onError: (err) => showErrorToast(err),
- })
-}
-
-export default useEditTags
diff --git a/src/components/[guild]/EditGuild/index.ts b/src/components/[guild]/EditGuild/index.ts
deleted file mode 100644
index cf35e235ee..0000000000
--- a/src/components/[guild]/EditGuild/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import EditGuildButton from "./EditGuildButton"
-
-export default EditGuildButton
diff --git a/src/components/[guild]/EditGuild/types.ts b/src/components/[guild]/EditGuild/types.ts
deleted file mode 100644
index a309b0de6c..0000000000
--- a/src/components/[guild]/EditGuild/types.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { GuildContact, Schemas } from "@guildxyz/types"
-import { GuildTags } from "types"
-import { Chain } from "wagmiConfig/chains"
-import { FeatureFlag } from "./components/FeatureFlags"
-
-export type EditGuildForm = Schemas["GuildUpdatePayload"] & {
- admins: { address: string }[]
- contacts: (Omit & { id?: GuildContact["id"] })[]
- guildPin?: {
- chain: Chain | "FUEL"
- isActive: boolean
- }
- // Superadmin-only fields
- featureFlags?: FeatureFlag[]
- tags?: GuildTags[]
-}
diff --git a/src/components/[guild]/GroupPageImageAndName.tsx b/src/components/[guild]/GroupPageImageAndName.tsx
deleted file mode 100644
index 59da902a9f..0000000000
--- a/src/components/[guild]/GroupPageImageAndName.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { LayoutTitle } from "@/components/Layout"
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/Avatar"
-import { Skeleton } from "@/components/ui/Skeleton"
-import { cn } from "@/lib/utils"
-import { useThemeContext } from "./ThemeContext"
-import useRoleGroup from "./hooks/useRoleGroup"
-
-const GroupPageImageAndName = () => {
- const group = useRoleGroup()
- const { avatarBg } = useThemeContext()
-
- return (
- <>
- {group?.imageUrl && (
-
-
-
-
-
-
- )}
-
-
-
- {group?.name ?? "Page"}
-
-
- >
- )
-}
-export { GroupPageImageAndName }
diff --git a/src/components/[guild]/GuildName.tsx b/src/components/[guild]/GuildName.tsx
deleted file mode 100644
index 7d24639737..0000000000
--- a/src/components/[guild]/GuildName.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import VerifiedIcon from "components/common/VerifiedIcon"
-
-const GuildName = ({ name, tags }): any => (
- <>
- {name}
- {tags?.includes("VERIFIED") && (
-
- )}
- >
-)
-
-export default GuildName
diff --git a/src/components/[guild]/GuildPageBanner.tsx b/src/components/[guild]/GuildPageBanner.tsx
deleted file mode 100644
index 5aa4574456..0000000000
--- a/src/components/[guild]/GuildPageBanner.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import Image from "next/image"
-import { useThemeContext } from "./ThemeContext"
-
-const GuildPageBanner = () => {
- const { localThemeColor, localBackgroundImage } = useThemeContext()
-
- return localBackgroundImage ? (
-
- ) : (
-
- )
-}
-
-export { GuildPageBanner }
diff --git a/src/components/[guild]/GuildPageImageAndName.tsx b/src/components/[guild]/GuildPageImageAndName.tsx
deleted file mode 100644
index 452e8fc656..0000000000
--- a/src/components/[guild]/GuildPageImageAndName.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { CheckMark } from "@/components/CheckMark"
-import { LayoutTitle } from "@/components/Layout"
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/Avatar"
-import { Skeleton } from "@/components/ui/Skeleton"
-import { cn } from "@/lib/utils"
-import { MemberCount } from "./RoleCard/components/MemberCount"
-import { useThemeContext } from "./ThemeContext"
-import useGuild from "./hooks/useGuild"
-
-const GuildPageImageAndName = () => {
- const { imageUrl, name, tags, memberCount } = useGuild()
- const { avatarBg, buttonColorSchemeClassName } = useThemeContext()
-
- return (
- <>
- {imageUrl && (
-
-
-
-
-
-
- )}
-
-
-
- {name}
-
- {tags?.includes("VERIFIED") && (
-
- )}
-
-
-
-
- >
- )
-}
-export { GuildPageImageAndName }
diff --git a/src/components/[guild]/JoinButton.tsx b/src/components/[guild]/JoinButton.tsx
deleted file mode 100644
index b78e6a3a9f..0000000000
--- a/src/components/[guild]/JoinButton.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { StickyAction } from "@/components/StickyAction"
-import { Button } from "@/components/ui/Button"
-import { useMediaQuery } from "usehooks-ts"
-import { useOpenJoinModal } from "./JoinModal/JoinModalProvider"
-
-const JoinButton = (): JSX.Element => {
- const openJoinModal = useOpenJoinModal()
- const isMobile = useMediaQuery("(max-width: 640px)")
-
- return (
-
-
- Join Guild
-
-
- )
-}
-
-export { JoinButton }
diff --git a/src/components/[guild]/JoinModal/JoinModal.tsx b/src/components/[guild]/JoinModal/JoinModal.tsx
deleted file mode 100644
index d39d157ad2..0000000000
--- a/src/components/[guild]/JoinModal/JoinModal.tsx
+++ /dev/null
@@ -1,215 +0,0 @@
-import { walletSelectorModalAtom } from "@/components/Providers/atoms"
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import { anchorVariants } from "@/components/ui/Anchor"
-import { Button } from "@/components/ui/Button"
-import { Collapsible, CollapsibleContent } from "@/components/ui/Collapsible"
-import {
- Dialog,
- DialogBody,
- DialogCloseButton,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/Dialog"
-import { Separator } from "@/components/ui/Separator"
-import { useErrorToast } from "@/components/ui/hooks/useErrorToast"
-import { cn } from "@/lib/utils"
-import { ArrowRight } from "@phosphor-icons/react/dist/ssr"
-import useGuild from "components/[guild]/hooks/useGuild"
-import { useAtomValue } from "jotai"
-import dynamic from "next/dynamic"
-import { ComponentType, Fragment } from "react"
-import { FormProvider, useForm } from "react-hook-form"
-import rewards from "rewards"
-import { ConnectPlatform } from "./components/ConnectPlatform"
-import { ShareSocialsCheckbox } from "./components/ShareSocialsCheckbox"
-import { WalletAuthButton } from "./components/WalletAuthButton"
-import { GetRewardsJoinStep } from "./components/progress/GetRewardsJoinStep"
-import { GetRolesJoinStep } from "./components/progress/GetRolesJoinStep"
-import { SatisfyRequirementsJoinStep } from "./components/progress/SatisfyRequirementsJoinStep"
-import { useJoin } from "./hooks/useJoin"
-import { JoinForm, Joinable } from "./types"
-
-const customJoinStep: Partial>> = {
- CAPTCHA: dynamic(() => import("./components/CompleteCaptchaJoinStep")),
- EMAIL: dynamic(() => import("./components/ConnectEmailJoinStep")),
-}
-
-const JoinModal = ({
- isOpen,
- onClose,
-}: {
- isOpen: boolean
- onClose: () => void
-}): JSX.Element => {
- const { isWeb3Connected } = useWeb3ConnectionManager()
- const isWalletSelectorModalOpen = useAtomValue(walletSelectorModalAtom)
- const { name, requiredPlatforms, featureFlags } = useGuild()
-
- const methods = useForm({
- mode: "all",
- defaultValues: {
- shareSocials: featureFlags?.includes("CRM") ? true : undefined,
- },
- })
- const { handleSubmit } = methods
-
- const renderedSteps = (requiredPlatforms ?? []).map((platform, index) => {
- const shouldRenderSeparator = index < (requiredPlatforms?.length ?? 0) - 1
-
- if (platform in customJoinStep) {
- const ConnectComponent = customJoinStep[platform]
- if (!ConnectComponent) return null
- return (
-
-
- {shouldRenderSeparator && }
-
- )
- }
-
- if (!rewards[platform]?.isPlatform) return null
-
- return (
-
-
- {shouldRenderSeparator && }
-
- )
- })
-
- const errorToast = useErrorToast()
-
- const { isLoading, onSubmit, joinProgress, reset } = useJoin({
- onSuccess: () => onClose(),
- onError: (err) => {
- errorToast(err)
- reset()
- },
- })
-
- const onJoin = (data: JoinForm) => {
- onSubmit({
- shareSocials: data?.shareSocials,
- })
- }
-
- const isInDetailedProgressState = !!joinProgress
- ? joinProgress?.state === "MANAGING_ROLES" ||
- joinProgress?.state === "MANAGING_REWARDS" ||
- joinProgress?.state === "FINISHED"
- : false
-
- const hasNoAccess = !!joinProgress && joinProgress.state === "NO_ACCESS"
-
- const { roles } = useGuild()
-
- const onClick = () => {
- onClose()
- window.location.hash = `role-${roles?.[0]?.id}`
- }
-
- return (
- (!newIsOpen ? onClose() : {})}
- >
-
-
- {`Join ${name}`}
-
-
-
-
-
-
-
-
- {renderedSteps?.length > 0 && }
- {renderedSteps}
-
-
-
-
- {/* Wrapper div to apply the margin when the collapse is `display: none` too */}
-
-
-
- {!isInDetailedProgressState && }
-
- {`You're not eligible with your connected accounts. `}
- }
- onClick={onClick}
- >
- See requirements
-
-
- )
- }
- className="gap-2.5"
- />
-
-
-
-
- {isInDetailedProgressState && }
-
-
-
-
-
-
-
-
-
-
-
-
-
- {featureFlags?.includes("CRM") && }
-
-
-
-
- {hasNoAccess ? "Recheck access" : "Check access to join"}
-
-
-
-
-
-
-
- )
-}
-
-export { JoinModal }
diff --git a/src/components/[guild]/JoinModal/JoinModalProvider.tsx b/src/components/[guild]/JoinModal/JoinModalProvider.tsx
deleted file mode 100644
index e5f3049133..0000000000
--- a/src/components/[guild]/JoinModal/JoinModalProvider.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useDisclosure } from "@/hooks/useDisclosure"
-import useMembership from "components/explorer/hooks/useMembership"
-import useClearUrlQuery from "hooks/useClearUrlQuery"
-import { PropsWithChildren, createContext, useContext, useEffect } from "react"
-import { JoinModal } from "./JoinModal"
-
-const JoinModalContext = createContext<() => void>(() => {})
-
-const JoinModalProvider = ({ children }: PropsWithChildren): JSX.Element => {
- const query = useClearUrlQuery()
- const { isOpen, onOpen, onClose } = useDisclosure()
- const { isMember } = useMembership()
-
- useEffect(() => {
- if (typeof query.join === "string") onOpen()
- }, [query.join, onOpen])
-
- // when you're not connected yet but is already a member and use the modal to connect
- useEffect(() => {
- if (isMember) onClose()
- }, [isMember, onClose])
-
- return (
-
- {children}
-
-
- )
-}
-
-const useOpenJoinModal = () => useContext(JoinModalContext)
-
-export { JoinModalProvider, useOpenJoinModal }
diff --git a/src/components/[guild]/JoinModal/components/CompleteCaptchaJoinStep.tsx b/src/components/[guild]/JoinModal/components/CompleteCaptchaJoinStep.tsx
deleted file mode 100644
index b7b753aede..0000000000
--- a/src/components/[guild]/JoinModal/components/CompleteCaptchaJoinStep.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import { useDisclosure } from "@/hooks/useDisclosure"
-import { Robot } from "@phosphor-icons/react/dist/ssr"
-import useSWRWithOptionalAuth from "hooks/useSWRWithOptionalAuth"
-import { CompleteCaptchaDialog } from "requirements/Captcha/components/CompleteCaptcha"
-import { JoinStep } from "./JoinStep"
-
-const CompleteCaptchaJoinStep = (): JSX.Element => {
- const { isWeb3Connected } = useWeb3ConnectionManager()
-
- const {
- data: isDone,
- isLoading,
- mutate,
- } = useSWRWithOptionalAuth(`/v2/util/gate-proof-existence/CAPTCHA`)
-
- const { isOpen, onOpen, setValue } = useDisclosure()
-
- return (
- <>
- ,
- disabled: !isWeb3Connected,
- isLoading,
- // TODO: extract it to a constant, just like we did with PLATFORM_COLORS
- className:
- "bg-cyan-500 hover:bg-cyan-600 hover:dark:bg-cyan-400 active:bg-cyan-700 active:dark:bg-cyan-300 text-white",
- onClick: onOpen,
- children: isDone ? "Completed" : "Complete",
- }}
- />
-
- mutate(() => true, { revalidate: false })}
- />
- >
- )
-}
-
-// biome-ignore lint/style/noDefaultExport: we only load this component dynamically, so it's much more convenient to use a default export here
-export default CompleteCaptchaJoinStep
diff --git a/src/components/[guild]/JoinModal/components/ConnectEmailJoinStep.tsx b/src/components/[guild]/JoinModal/components/ConnectEmailJoinStep.tsx
deleted file mode 100644
index 7459773de5..0000000000
--- a/src/components/[guild]/JoinModal/components/ConnectEmailJoinStep.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { ConnectEmailButton } from "@/components/Account/components/AccountModal/components/EmailAddress"
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/Tooltip"
-import { cn } from "@/lib/utils"
-import { EnvelopeSimple } from "@phosphor-icons/react/dist/ssr"
-import useUser from "components/[guild]/hooks/useUser"
-import { JoinStepUI } from "./JoinStep"
-
-const ConnectEmailJoinStep = (): JSX.Element => {
- const { isWeb3Connected } = useWeb3ConnectionManager()
- const { emails } = useUser()
-
- const isDone = Boolean(emails?.emailAddress && !emails.pending)
-
- return (
-
-
-
- }
- />
-
-
-
- Connect wallet first
-
-
-
- )
-}
-
-// biome-ignore lint/style/noDefaultExport: we only load this component dynamically, so it's much more convenient to use a default export here
-export default ConnectEmailJoinStep
diff --git a/src/components/[guild]/JoinModal/components/ConnectPlatform.tsx b/src/components/[guild]/JoinModal/components/ConnectPlatform.tsx
deleted file mode 100644
index 9412153433..0000000000
--- a/src/components/[guild]/JoinModal/components/ConnectPlatform.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { PLATFORM_COLORS } from "@/components/Account/components/AccountModal/components/SocialAccount"
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import usePlatformsToReconnect from "components/[guild]/hooks/usePlatformsToReconnect"
-import useUser from "components/[guild]/hooks/useUser"
-import { useEffect } from "react"
-import { useFormContext } from "react-hook-form"
-import rewards from "rewards"
-import { PlatformName } from "types"
-import { useConnectPlatform } from "../hooks/useConnectPlatform"
-import { useMembershipUpdate } from "../hooks/useMembershipUpdate"
-import { JoinStep } from "./JoinStep"
-
-type Props = {
- platform: PlatformName
-}
-
-const ConnectPlatform = ({ platform }: Props) => {
- const { isWeb3Connected } = useWeb3ConnectionManager()
- const { platformUsers, isLoading: isLoadingUser } = useUser()
-
- const platformsToReconnect = usePlatformsToReconnect()
- const isReconnect = platformsToReconnect.includes(platform)
-
- // we have the reconnect data from the membership endoint, so we have to mutate that on reconnect success
- const { triggerMembershipUpdate } = useMembershipUpdate()
- const onSuccess = () => isReconnect && triggerMembershipUpdate()
-
- const { onConnect, isLoading, loadingText } = useConnectPlatform(
- platform,
- onSuccess,
- isReconnect
- )
-
- const platformFromDb = platformUsers?.find(
- (platformAccount) => platformAccount.platformName === platform
- )
-
- const { setValue } = useFormContext()
-
- useEffect(() => {
- if (platformFromDb?.platformUserId) setValue(`platforms.${platform}`, null)
- }, [platformFromDb, platform, setValue])
-
- const accountName = `${rewards[platform].name}${
- platform === "TWITTER_V1" ? " (v1)" : ""
- }`
-
- const isDisabled = !isWeb3Connected
- const isConnected = !!platformFromDb
- const buttonLabel =
- platformFromDb?.platformUserData?.username ?? platformFromDb?.platformUserId
-
- const Icon = rewards[platform].icon
-
- return (
- : undefined,
- disabled: isDisabled,
- isLoading: isLoading || (!platformUsers && isLoadingUser),
- loadingText,
- onClick: onConnect,
- className: PLATFORM_COLORS[platform],
- children: isReconnect ? "Reconnect" : isConnected ? buttonLabel : "Connect",
- }}
- />
- )
-}
-
-export { ConnectPlatform }
diff --git a/src/components/[guild]/JoinModal/components/JoinStep.tsx b/src/components/[guild]/JoinModal/components/JoinStep.tsx
deleted file mode 100644
index bf0ce5e3df..0000000000
--- a/src/components/[guild]/JoinModal/components/JoinStep.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { Button, ButtonProps } from "@/components/ui/Button"
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/Tooltip"
-import { cn } from "@/lib/utils"
-import { PropsWithChildren, ReactNode } from "react"
-import { JoinStepIndicator } from "./JoinStepIndicator"
-
-type JoinStepUIProps = {
- title: string
- titleRightElement?: ReactNode
- isRequired?: boolean
- isDone: boolean
-}
-
-type JoinStepProps = {
- disabledText?: string
- buttonProps: ButtonProps
-} & JoinStepUIProps
-
-const JoinStep = ({
- title,
- titleRightElement,
- isRequired,
- isDone,
- disabledText,
- buttonProps,
- children,
-}: PropsWithChildren) => (
-
-
-
-
- {buttonProps.children}
-
-
-
-
- {disabledText}
-
-
-
- {children}
-
-)
-
-const JoinStepUI = ({
- isDone,
- title,
- isRequired,
- titleRightElement,
- children,
-}: PropsWithChildren) => (
-
-
-
-
- {title}
- {isRequired && {` *`} }
-
- {titleRightElement}
-
-
- {children}
-
-)
-
-export { JoinStep, JoinStepUI }
diff --git a/src/components/[guild]/JoinModal/components/JoinStepIndicator.tsx b/src/components/[guild]/JoinModal/components/JoinStepIndicator.tsx
deleted file mode 100644
index f663e74aaa..0000000000
--- a/src/components/[guild]/JoinModal/components/JoinStepIndicator.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { CircularProgressBar } from "@/components/CircularProgressBar"
-import { Check, CircleNotch, X } from "@phosphor-icons/react/dist/ssr"
-
-export type JoinStepIndicatorProps =
- | { status: "INACTIVE" | "DONE" | "NO_ACCESS" | "LOADING" }
- | { status: "PROGRESS"; progress: number }
-
-const JoinStepIndicator = (props: JoinStepIndicatorProps) => {
- switch (props.status) {
- case "DONE": {
- return (
-
-
-
- )
- }
-
- case "NO_ACCESS": {
- return (
-
-
-
- )
- }
-
- case "INACTIVE": {
- return (
-
- )
- }
-
- case "LOADING": {
- return (
-
-
-
- )
- }
-
- case "PROGRESS": {
- return (
-
- )
- }
- }
-}
-
-export { JoinStepIndicator }
diff --git a/src/components/[guild]/JoinModal/components/ShareSocialsCheckbox.tsx b/src/components/[guild]/JoinModal/components/ShareSocialsCheckbox.tsx
deleted file mode 100644
index 87bda44a7f..0000000000
--- a/src/components/[guild]/JoinModal/components/ShareSocialsCheckbox.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { Anchor } from "@/components/ui/Anchor"
-import { Checkbox } from "@/components/ui/Checkbox"
-import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/Form"
-import { useFormContext } from "react-hook-form"
-import { JoinForm } from "../types"
-
-const ShareSocialsCheckbox = (): JSX.Element => {
- const { control } = useFormContext()
-
- return (
- (
-
-
-
-
-
-
- {`I agree to share my profile with the guild, so they can potentially
- reward me for my engagement in the community. `}
-
- Learn more
-
-
-
- )}
- />
- )
-}
-
-export { ShareSocialsCheckbox }
diff --git a/src/components/[guild]/JoinModal/components/WalletAuthButton.tsx b/src/components/[guild]/JoinModal/components/WalletAuthButton.tsx
deleted file mode 100644
index fd13e73402..0000000000
--- a/src/components/[guild]/JoinModal/components/WalletAuthButton.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { walletSelectorModalAtom } from "@/components/Providers/atoms"
-import { useWeb3ConnectionManager } from "@/components/Web3ConnectionManager/hooks/useWeb3ConnectionManager"
-import { SignIn, Wallet } from "@phosphor-icons/react/dist/ssr"
-import { useSetAtom } from "jotai"
-import shortenHex from "utils/shortenHex"
-import { JoinStep } from "./JoinStep"
-
-const WalletAuthButton = (): JSX.Element => {
- const setIsWalletSelectorModalOpen = useSetAtom(walletSelectorModalAtom)
- const { address } = useWeb3ConnectionManager()
-
- return (
- : ,
- onClick: () => setIsWalletSelectorModalOpen(true),
- children: !address ? "Sign in" : shortenHex(address, 3),
- }}
- />
- )
-}
-
-export { WalletAuthButton }
diff --git a/src/components/[guild]/JoinModal/components/progress/GetRewardsJoinStep.tsx b/src/components/[guild]/JoinModal/components/progress/GetRewardsJoinStep.tsx
deleted file mode 100644
index 5a5145fd57..0000000000
--- a/src/components/[guild]/JoinModal/components/progress/GetRewardsJoinStep.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-import { useMemo } from "react"
-import { JoinState } from "../../utils/mapAccessJobState"
-import { ProgressJoinStep } from "./components/ProgressJoinStep"
-
-const GetRewardsJoinStep = ({ joinState }: { joinState: JoinState }) => {
- const status = useMemo(() => {
- if (joinState?.state === "NO_ACCESS") return "NO_ACCESS"
-
- if (joinState?.state === "MANAGING_REWARDS") return "LOADING"
-
- const hasAllRewards =
- !!joinState?.rewards && joinState?.rewards?.granted === joinState?.rewards?.all
-
- if (joinState?.state === "FINISHED" || hasAllRewards) return "DONE"
-
- return "INACTIVE"
- }, [joinState])
-
- /**
- * JoinState doesn't include rewards that we don't have the platform connected to,
- * so we calculate the total count by guild data (we can only do this, until we get
- * all rewards on the guild-page endpoint). But that doesn't include rewards
- * that're secret and we don't see yet (and joinState does), so we just show the
- * larger amount of these two
- */
- const { roles } = useGuild()
- const allRelevantRewardsCount = roles
- .filter((role) => joinState?.roleIds?.includes(role.id))
- .flatMap((role) => role.rolePlatforms)?.length
- const totalRewardsToShow = Math.max(
- joinState?.rewards?.all,
- allRelevantRewardsCount
- )
-
- return (
-
- )
-}
-
-export { GetRewardsJoinStep }
diff --git a/src/components/[guild]/JoinModal/components/progress/GetRolesJoinStep.tsx b/src/components/[guild]/JoinModal/components/progress/GetRolesJoinStep.tsx
deleted file mode 100644
index 5e10721e16..0000000000
--- a/src/components/[guild]/JoinModal/components/progress/GetRolesJoinStep.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { useMemo } from "react"
-import { JoinState } from "../../utils/mapAccessJobState"
-import { ProgressJoinStep } from "./components/ProgressJoinStep"
-
-const GetRolesJoinStep = ({ joinState }: { joinState: JoinState }) => {
- const status = useMemo(() => {
- switch (joinState?.state) {
- case "MANAGING_REWARDS":
- case "FINISHED":
- return "DONE"
-
- case "MANAGING_ROLES":
- return "LOADING"
-
- default:
- return "INACTIVE"
- }
- }, [joinState])
-
- return (
-
- )
-}
-
-export { GetRolesJoinStep }
diff --git a/src/components/[guild]/JoinModal/components/progress/SatisfyRequirementsJoinStep.tsx b/src/components/[guild]/JoinModal/components/progress/SatisfyRequirementsJoinStep.tsx
deleted file mode 100644
index e07c17dce9..0000000000
--- a/src/components/[guild]/JoinModal/components/progress/SatisfyRequirementsJoinStep.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { HTMLAttributes, ReactNode, useMemo } from "react"
-import { JoinState } from "../../utils/mapAccessJobState"
-import { ProgressJoinStep } from "./components/ProgressJoinStep"
-
-const SatisfyRequirementsJoinStep = ({
- joinState,
- fallbackText,
- ...props
-}: {
- joinState: JoinState
- fallbackText?: JSX.Element
- RightComponent?: ReactNode
-} & HTMLAttributes) => {
- const status = useMemo(() => {
- switch (joinState?.state) {
- case "NO_ACCESS":
- return "NO_ACCESS"
-
- case "MANAGING_ROLES":
- case "MANAGING_REWARDS":
- case "FINISHED":
- return "DONE"
-
- case "PREPARING":
- case "CHECKING":
- return "LOADING"
-
- default:
- return "INACTIVE"
- }
- }, [joinState])
-
- return (
- 10
- ? `Oops! ${joinState?.waitingPosition} users ahead of you. Please wait patiently.`
- : "Preparing access check")
- }
- {...props}
- />
- )
-}
-
-export { ProgressJoinStep, SatisfyRequirementsJoinStep }
diff --git a/src/components/[guild]/JoinModal/components/progress/components/ProgressJoinStep.tsx b/src/components/[guild]/JoinModal/components/progress/components/ProgressJoinStep.tsx
deleted file mode 100644
index a66c136e5b..0000000000
--- a/src/components/[guild]/JoinModal/components/progress/components/ProgressJoinStep.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Collapsible, CollapsibleContent } from "@/components/ui/Collapsible"
-import { cn } from "@/lib/utils"
-import { HTMLAttributes, PropsWithChildren, ReactNode } from "react"
-import { JoinStepIndicator } from "../../JoinStepIndicator"
-
-type Props = {
- title: string
- countLabel: string
- fallbackText: string | JSX.Element
- status: "INACTIVE" | "LOADING" | "NO_ACCESS" | "DONE"
- total?: number
- current?: number
- waitingPosition?: number
- RightComponent?: ReactNode
-}
-
-const ProgressJoinStep = ({
- title,
- countLabel,
- fallbackText,
- status,
- total,
- current,
- waitingPosition,
- RightComponent,
- className,
- ...props
-}: PropsWithChildren & HTMLAttributes) => (
-
-
-
-
-
-
-
{title}
-
- {status !== "INACTIVE" &&
- (typeof total === "number" && typeof current === "number" ? (
-
{`${current}/${total} ${countLabel}`}
- ) : (
-
{fallbackText}
- ))}
-
-
-
-
- {`There are a lot of users joining right now, so you have to wait a bit. There are ${waitingPosition} users ahead of you. Feel free to close the site and come back later!`}
-
-
-
-
- {RightComponent}
-
-)
-
-export { ProgressJoinStep }
diff --git a/src/components/[guild]/JoinModal/hooks/useActiveMembershipUpdate.tsx b/src/components/[guild]/JoinModal/hooks/useActiveMembershipUpdate.tsx
deleted file mode 100644
index ce1b5ff699..0000000000
--- a/src/components/[guild]/JoinModal/hooks/useActiveMembershipUpdate.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-import type { AccessCheckJob, JoinJob } from "@guildxyz/types"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useMembership from "components/explorer/hooks/useMembership"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import { useGetKeyForSWRWithOptionalAuth } from "hooks/useGetKeyForSWRWithOptionalAuth"
-import { UseSubmitOptions } from "hooks/useSubmit/types"
-import { atom, useAtom } from "jotai"
-import useSWRImmutable from "swr/immutable"
-import { groupBy } from "../utils/mapAccessJobState"
-
-const SUCCESS_EVENT_NAME = "MEMBERSHIP_UPDATE_SUCCESS"
-const ERROR_EVENT_NAME = "MEMBERSHIP_UPDATE_ERROR"
-
-const isPollingAtom = atom(false)
-
-type Props = UseSubmitOptions & { keepPreviousData?: boolean }
-
-const useActiveMembershipUpdate = ({
- keepPreviousData,
- onSuccess,
- onError,
-}: Props = {}) => {
- const guild = useGuild()
- const fetcherWithSign = useFetcherWithSign()
- const { mutate: mutateMembership } = useMembership()
- const [shouldPoll, setShouldPoll] = useAtom(isPollingAtom)
-
- const getKeyForSWRWithOptionalAuth = useGetKeyForSWRWithOptionalAuth()
-
- const progress = useSWRImmutable(
- shouldPoll ? `/v2/actions/join?guildId=${guild?.id}` : null,
- (key) =>
- fetcherWithSign(getKeyForSWRWithOptionalAuth(key)).then(
- (result: JoinJob[]) =>
- // casting to any until @guildxyz/types contains createdAtTimestamp
- result?.sort((jobA: any, jobB: any) =>
- jobA.createdAtTimestamp > jobB.createdAtTimestamp ? -1 : 1
- )?.[0]
- ),
- {
- onSuccess: (res) => {
- if (!res.done) return
-
- /**
- * "children:access-check:jobs" are the jobs returned from gate, but in some
- * cases the user may get access to additional requirements after the initial
- * access check (e.g. if they get access to "role A" and that role is the
- * requirement in "role B", then this latter will be included only in
- * "res.requirementAccesses"), that's why we use this variable here
- */
- const requirementAccesses = (res as any)
- ?.requirementAccesses as AccessCheckJob["children:access-check:jobs"][]
- const reqJobsByRoleId = groupBy(requirementAccesses ?? [], "roleId")
-
- const newRoles = Object.entries(reqJobsByRoleId).map(
- ([roleIdStr, reqAccesses]: [
- string,
- AccessCheckJob["children:access-check:jobs"],
- ]) => {
- const roleId = +roleIdStr
- return {
- access: res?.roleAccesses?.find(
- (roleAccess) => roleAccess.roleId === +roleId
- )?.access,
- roleId,
- requirements: reqAccesses?.map((reqAccess) => ({
- requirementId: reqAccess.requirementId,
- access: reqAccess.access,
- amount: reqAccess.amount,
- errorMsg:
- reqAccess.userLevelErrors?.[0]?.msg ??
- reqAccess.requirementError?.msg,
- errorType:
- reqAccess.userLevelErrors?.[0]?.errorType ??
- reqAccess.requirementError?.errorType,
- subType:
- reqAccess.userLevelErrors?.[0]?.errorSubType ??
- reqAccess.requirementError?.errorSubType,
- lastCheckedAt: new Date(res.createdAtTimestamp).toISOString(),
- })),
- }
- }
- )
-
- // delaying success a bit so the user has time percieving the last state
- setTimeout(() => {
- mutateMembership(
- (prev) => ({
- guildId: prev?.guildId,
- isAdmin: prev?.isAdmin,
- joinedAt:
- prev?.joinedAt || res?.done ? new Date().toISOString() : null,
- roles: [
- ...(prev?.roles?.filter(
- (role) => !(role.roleId.toString() in reqJobsByRoleId)
- ) ?? []),
- ...newRoles,
- ],
- }),
- { revalidate: false }
- )
-
- /**
- * Instead of calling onSuccess here, we call it in
- * triggerMembershipUpdate, when this event is catched. This is for making
- * sure, the correct onSuccess runs (the same one where we call
- * triggerMembershipUpdate)
- */
- window.postMessage({ type: SUCCESS_EVENT_NAME, res })
- setShouldPoll(false)
- }, 2000)
- },
- onError: (err) => {
- window.postMessage({ type: ERROR_EVENT_NAME, err })
- setShouldPoll(false)
- },
- keepPreviousData,
- refreshInterval: shouldPoll ? 500 : undefined,
- }
- )
-
- return {
- ...progress,
- isValidating: shouldPoll,
- triggerPoll: () => {
- // this doesn't work for some reason, but leaving it here till we investigate
- progress.mutate(null, { revalidate: false })
-
- setShouldPoll(true)
-
- const listener = (event: MessageEvent) => {
- if (event?.data?.type === SUCCESS_EVENT_NAME) {
- try {
- onSuccess?.(event?.data?.res)
- window.removeEventListener("message", listener)
- } catch {}
- }
- if (event?.data?.type === ERROR_EVENT_NAME) {
- try {
- onError?.(event?.data?.err)
- window.removeEventListener("message", listener)
- } catch {}
- }
- }
-
- window.addEventListener("message", listener)
- },
- }
-}
-
-export { useActiveMembershipUpdate }
diff --git a/src/components/[guild]/JoinModal/hooks/useConnectPlatform.ts b/src/components/[guild]/JoinModal/hooks/useConnectPlatform.ts
deleted file mode 100644
index 46552c6bef..0000000000
--- a/src/components/[guild]/JoinModal/hooks/useConnectPlatform.ts
+++ /dev/null
@@ -1,387 +0,0 @@
-import { usePostHogContext } from "@/components/Providers/PostHogProvider"
-import { platformMergeAlertAtom } from "@/components/Providers/atoms"
-import { useErrorToast } from "@/components/ui/hooks/useErrorToast"
-import { useToast } from "@/components/ui/hooks/useToast"
-import { OAuthResultParams } from "app/oauth-result/types"
-import useUser from "components/[guild]/hooks/useUser"
-import { env } from "env"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import { useGetKeyForSWRWithOptionalAuth } from "hooks/useGetKeyForSWRWithOptionalAuth"
-import usePopupWindow from "hooks/usePopupWindow"
-import useSubmit, { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import { UseSubmitOptions } from "hooks/useSubmit/types"
-import { useSetAtom } from "jotai"
-import { useCallback, useMemo } from "react"
-import rewards from "rewards"
-import useSWR from "swr"
-import { PlatformName, PlatformType } from "types"
-import fetcher from "utils/fetcher"
-import { useFetchUserEmail } from "./useFetchUserEmail"
-
-type AuthLevel = "membership" | "creation"
-
-function parseConnectError(error: string):
- | string
- | {
- params: Record
- errors: { msg: string }[]
- } {
- const regex = /^"(\d+)".*params: ({.*}), error: (\[.*\])/
-
- try {
- const [, rawNumber, rawParams, rawErrors] = error.match(regex)
- const number: number = parseInt(rawNumber)
- const params: Record = JSON.parse(rawParams)
- const errors: { msg: string }[] = JSON.parse(rawErrors)
-
- if (
- typeof number !== "number" ||
- isNaN(number) ||
- !params ||
- !Array.isArray(errors)
- )
- return error
-
- return { params: { ...params, code: undefined }, errors }
- } catch {
- return error
- }
-}
-
-function getOAuthURL(
- platformName: string,
- authToken: string,
- scope?: AuthLevel,
- force?: boolean
-) {
- const url = new URL(`../v2/oauth/${platformName}`, env.NEXT_PUBLIC_API)
- url.searchParams.set("path", window.location.pathname)
- url.searchParams.set("token", authToken)
- if (scope) {
- url.searchParams.set("scope", scope)
- }
- if (force) {
- url.searchParams.set("force", "1")
- }
- return url.href
-}
-
-const useConnectPlatform = (
- platformName: PlatformName,
- onSuccess?: () => void,
- isReauth?: boolean, // Temporary, once /connect works without it, we can remove this
- authLevel: AuthLevel = "membership",
- disconnectFromExistingUser?: boolean
-) => {
- const { id, mutate: mutateUser } = useUser()
- const fetchUserEmail = useFetchUserEmail()
- const { toast } = useToast()
- const showPlatformMergeAlert = useSetAtom(platformMergeAlertAtom)
- const { onOpen } = usePopupWindow()
-
- const fetcherWithSign = useFetcherWithSign()
- const getKeyForSWRWithOptionalAuth = useGetKeyForSWRWithOptionalAuth()
-
- const { data: authToken } = useSWR(
- id ? `guild-oauth-token-${id}` : null,
- () =>
- fetcherWithSign(
- getKeyForSWRWithOptionalAuth(`/v2/oauth/${platformName}/token`)
- ).then(({ token }) => token),
- { dedupingInterval: 1000 * 60 * 4, refreshInterval: 1000 * 30 }
- )
-
- const url = useMemo(() => {
- if (authToken) {
- return getOAuthURL(
- platformName,
- authToken,
- authLevel,
- disconnectFromExistingUser
- )
- }
- return null
- }, [platformName, authToken, authLevel, disconnectFromExistingUser])
-
- const listener = useSubmit(
- async () => {
- const channel = new BroadcastChannel(`guild-${platformName}`)
- const messageListener = new Promise((resolve, reject) => {
- channel.onmessage = (event) => {
- if (
- event.isTrusted &&
- event.origin === window.origin &&
- event.data?.type !== "oauth-confirmation"
- ) {
- channel.postMessage({ type: "oauth-confirmation" })
- const result: OAuthResultParams = event.data
-
- if (result.status === "success") {
- fetcherWithSign(
- getKeyForSWRWithOptionalAuth(
- `/v2/users/${id}/platform-users/${PlatformType[platformName]}`
- )
- )
- .then((newPlatformUser) => {
- mutateUser(
- async (prev) => {
- /**
- * For GOOGLE, there is a chance, that the email address got
- * linked as EMAIL as well. Therefore if the user doesn't
- * already have an EMAIL, we revalidate
- */
-
- const hasEmail = !!prev?.emails?.emailAddress
- const isGoogleConnection = platformName === "GOOGLE"
-
- const shouldRefetchEmail = !hasEmail && isGoogleConnection
-
- const email = shouldRefetchEmail
- ? await fetchUserEmail()
- : undefined
-
- return {
- ...prev,
- platformUsers: [
- ...(prev?.platformUsers ?? []).filter(
- ({ platformId }) =>
- platformId !== newPlatformUser.platformId
- ),
- { ...newPlatformUser, platformName },
- ],
- emails: email ?? prev?.emails,
- }
- },
- { revalidate: false }
- )
- })
- .then(() => resolve(true))
- .catch(() => reject("Failed to get new platform connection"))
-
- return
- } else {
- if (result.message?.startsWith("Before connecting your")) {
- const [, addressOrDomain] = result.message.match(
- /^Before connecting your (?:.*?) account, please disconnect it from this address: (.*?)$/
- )
- showPlatformMergeAlert({ addressOrDomain, platformName })
- resolve(false)
- return
- }
- reject(new Error(result.message))
- }
- }
- }
- })
-
- const result = await messageListener.finally(() => {
- channel.close()
- })
- return result
- },
- {
- onSuccess: (isSuccess) => {
- if (isSuccess) {
- onSuccess?.()
- }
- },
- onError: (error) => {
- toast({
- variant: "error",
- title: "Error",
- description:
- error.message ?? `Failed to connect ${rewards[platformName].name}`,
- })
- },
- }
- )
-
- const onClick = useCallback(() => {
- if (!url) return
-
- listener.onSubmit()
-
- /**
- * We can't force Telegram into a standard OAuth behaviour if it is opened in a
- * popup. We can only guarantee a redirect to happen, if we refresh the current
- * window
- */
- if (platformName === "TELEGRAM" || platformName === "WORLD_ID") {
- window.location.href = url
- } else {
- onOpen(url)
- }
- }, [url, listener, platformName, onOpen])
-
- return {
- onConnect: onClick,
- isLoading: listener.isLoading,
- loadingText: "Confirm in the pop-up",
- response: listener.response,
- }
-}
-
-const useConnect = (useSubmitOptions?: UseSubmitOptions, isAutoConnect = false) => {
- const { captureEvent } = usePostHogContext()
- const showErrorToast = useErrorToast()
- const showPlatformMergeAlert = useSetAtom(platformMergeAlertAtom)
-
- const { mutate: mutateUser, id } = useUser()
-
- const fetcherWithSign = useFetcherWithSign()
-
- const submit = ({ signOptions = undefined, ...payload }) => {
- const platformName = payload?.platformName ?? "UNKNOWN_PLATFORM"
-
- const userId =
- id ??
- signOptions?.address?.toLowerCase() ??
- signOptions?.walletClient?.account?.address?.toLowerCase()
-
- return fetcherWithSign([
- `/v2/users/${userId}/platform-users`,
- {
- signOptions,
- method: "POST",
- body: payload,
- },
- ])
- .then((body) => {
- if (body === "rejected") {
- // eslint-disable-next-line @typescript-eslint/no-throw-literal
- throw [platformName, "Something went wrong, connect request rejected."]
- }
-
- if (typeof body === "string") {
- // eslint-disable-next-line @typescript-eslint/no-throw-literal
- throw [platformName, body]
- }
-
- return { ...body, platformName }
- })
- .catch((err) => {
- // eslint-disable-next-line @typescript-eslint/no-throw-literal
- throw [platformName, err]
- })
- }
-
- return useSubmit(submit, {
- onSuccess: (newPlatformUser) => {
- mutateUser(
- (prev) => ({
- ...prev,
- platformUsers: [
- ...(prev?.platformUsers ?? []).filter(
- ({ platformId }) => platformId !== newPlatformUser.platformId
- ),
- newPlatformUser,
- ],
- }),
- { revalidate: false }
- )
-
- useSubmitOptions?.onSuccess?.()
- },
- onError: ([platformName, rawError]) => {
- useSubmitOptions?.onError?.([platformName, rawError])
-
- const errorObject = {
- error: undefined,
- isAutoConnect: undefined,
- platformName,
- }
- let toastError: string
-
- if (isAutoConnect) {
- errorObject.isAutoConnect = true
- }
-
- if (typeof rawError?.error === "string") {
- const parsedError = parseConnectError(rawError.error)
- errorObject.error = parsedError
- toastError =
- typeof parsedError === "string" ? parsedError : parsedError.errors[0].msg
- } else {
- errorObject.error = rawError
- }
-
- captureEvent("Platform connection error", errorObject)
-
- if (toastError?.startsWith("Before connecting your")) {
- const [, addressOrDomain] = toastError.match(
- /^Before connecting your (?:.*?) account, please disconnect it from this address: (.*?)$/
- )
- showPlatformMergeAlert({ addressOrDomain, platformName })
- } else {
- showErrorToast(
- toastError
- ? { error: toastError, correlationId: rawError.correlationId }
- : rawError
- )
- }
- },
- })
-}
-
-type EmailConnectRepsonse = {
- createdAt: Date
- domain: string
- address: string
- emailVerificationCodeId: number
- id: number
- identityId: number
- primary: boolean
-}
-
-const useConnectEmail = ({
- onSuccess,
- onError,
-}: {
- onSuccess?: () => void
- onError?: (error: any) => void
-} = {}) => {
- const { captureEvent } = usePostHogContext()
- const { mutate: mutateUser, id } = useUser()
-
- const submit = (signedValidation: SignedValidation) => {
- const { emailAddress } = JSON.parse(signedValidation?.signedPayload ?? "{}")
-
- return fetcher(`/v2/users/${id}/emails/${emailAddress}/verification`, {
- method: "POST",
- ...signedValidation,
- })
- }
-
- const { onSubmit, ...rest } = useSubmitWithSign(submit, {
- onSuccess: (newPlatformUser = {} as any) => {
- mutateUser(
- (prev) => ({
- ...prev,
- emails: {
- emailAddress: newPlatformUser?.address,
- createdAt: newPlatformUser?.createdAt,
- pending: false,
- },
- }),
- { revalidate: false }
- )
-
- onSuccess?.()
- },
- onError: (error) => {
- captureEvent("Email connection error", error)
- onError?.(error)
- },
- })
-
- return {
- ...rest,
- onSubmit: (params: { authData: { code: string }; emailAddress: string }) =>
- onSubmit({
- ...params,
- identityType: "EMAIL",
- }),
- }
-}
-
-export { useConnectPlatform, useConnect, useConnectEmail }
diff --git a/src/components/[guild]/JoinModal/hooks/useFetchUserEmail.ts b/src/components/[guild]/JoinModal/hooks/useFetchUserEmail.ts
deleted file mode 100644
index 7e90e6eaf3..0000000000
--- a/src/components/[guild]/JoinModal/hooks/useFetchUserEmail.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import useUser from "components/[guild]/hooks/useUser"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import { useGetKeyForSWRWithOptionalAuth } from "hooks/useGetKeyForSWRWithOptionalAuth"
-import { User } from "types"
-
-export function useFetchUserEmail() {
- const { id } = useUser()
- const fetcherWithSign = useFetcherWithSign()
- const getKeyForSWRWithOptionalAuth = useGetKeyForSWRWithOptionalAuth()
-
- return (): Promise =>
- fetcherWithSign(getKeyForSWRWithOptionalAuth(`/v2/users/${id}/emails`))
- .then(([{ address = null, createdAt = null }] = []) => ({
- emailAddress: address,
- pending: false,
- createdAt,
- }))
- .catch(() => null)
-}
diff --git a/src/components/[guild]/JoinModal/hooks/useJoin.tsx b/src/components/[guild]/JoinModal/hooks/useJoin.tsx
deleted file mode 100644
index 94e36fac4a..0000000000
--- a/src/components/[guild]/JoinModal/hooks/useJoin.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { ToastAction } from "@/components/ui/Toast"
-import { useToast } from "@/components/ui/hooks/useToast"
-import { JoinJob } from "@guildxyz/types"
-import { CircleWavyCheck } from "@phosphor-icons/react/dist/ssr"
-import { GUILD_PIN_MAINTENANCE } from "components/[guild]/Requirements/components/GuildCheckout/MintGuildPin/constants"
-import { useMintGuildPinContext } from "components/[guild]/Requirements/components/GuildCheckout/MintGuildPinContext"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useUser from "components/[guild]/hooks/useUser"
-import { UseSubmitOptions } from "hooks/useSubmit/types"
-import { useToastWithShareButtons } from "hooks/useToastWithShareButtons"
-import { useRouter } from "next/router"
-import { useState } from "react"
-import { useMembershipUpdate } from "./useMembershipUpdate"
-
-const useJoin = ({ onSuccess, onError }: UseSubmitOptions) => {
- const guild = useGuild()
- const user = useUser()
- const toastWithShareButtons = useToastWithShareButtons()
- const { toast } = useToast()
- const [isNoAccess, setIsNoAccess] = useState(false)
-
- const mintGuildPinContext = useMintGuildPinContext()
- // Destructuring it separately, since we don't have a MintGuildPinContext on the POAP minting page
- const { onOpen } = mintGuildPinContext ?? {}
- const { pathname } = useRouter()
-
- const { triggerMembershipUpdate, joinProgress, isLoading, reset } =
- useMembershipUpdate({
- onSuccess: (response) => {
- if (
- response?.roleAccesses?.length > 0 &&
- response.roleAccesses.every((role) => !role.access)
- ) {
- setIsNoAccess(true)
- return
- }
-
- if (
- pathname === "/[guild]" &&
- guild.featureFlags.includes("GUILD_CREDENTIAL") &&
- guild.guildPin?.isActive &&
- !GUILD_PIN_MAINTENANCE
- ) {
- toast({
- variant: "success",
-
- title: "Successfully joined guild",
- description: "Let others know as well by minting it onchain",
- action: (
-
-
- Mint Guild Pin
-
- ),
- })
- } else {
- toastWithShareButtons({
- title: "Successfully joined guild",
- shareText: `Just joined the ${guild.name} guild. Continuing my brave quest to explore all corners of web3!
- guild.xyz/${guild.urlName}`,
- })
- }
-
- // mutate user in case they connected new platforms during the join flow
- user?.mutate?.()
-
- onSuccess?.(response)
- },
- onError,
- keepPreviousData: true,
- })
-
- return {
- onSubmit: (data) => {
- setIsNoAccess(false)
- triggerMembershipUpdate(data)
- },
- joinProgress: (isLoading || isNoAccess) && joinProgress,
- isLoading,
- reset,
- }
-}
-
-export { useJoin }
diff --git a/src/components/[guild]/JoinModal/hooks/useMembershipUpdate.tsx b/src/components/[guild]/JoinModal/hooks/useMembershipUpdate.tsx
deleted file mode 100644
index eed3e55bbd..0000000000
--- a/src/components/[guild]/JoinModal/hooks/useMembershipUpdate.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import { usePostHogContext } from "@/components/Providers/PostHogProvider"
-import type { JoinJob } from "@guildxyz/types"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useGuildPermission from "components/[guild]/hooks/useGuildPermission"
-import useMembership from "components/explorer/hooks/useMembership"
-import useCustomPosthogEvents from "hooks/useCustomPosthogEvents"
-import { useFetcherWithSign } from "hooks/useFetcherWithSign"
-import { useGetKeyForSWRWithOptionalAuth } from "hooks/useGetKeyForSWRWithOptionalAuth"
-import useSubmit from "hooks/useSubmit"
-import { UseSubmitOptions } from "hooks/useSubmit/types"
-import { atom, useAtom } from "jotai"
-import useUsersPoints from "rewards/Points/useUsersPoints"
-import { getGuildPlatformsOfRoles } from "../utils/getGuildPlatformsOfRoles"
-import { mapAccessJobState } from "../utils/mapAccessJobState"
-import { useActiveMembershipUpdate } from "./useActiveMembershipUpdate"
-
-export type JoinData = {
- oauthData: any
-}
-
-// syncing useSubmit's isLoading into a global atom
-const isGettingJobAtom = atom(false)
-const currentlyCheckedRoleIdsAtom = atom([])
-
-type Props = UseSubmitOptions & {
- /**
- * We're setting keepPreviousData to true in useJoin, so we can display no access
- * and error states correctly. Would be nice to find a better solution for this
- * (like not parsing NO_ACCESS state and error in mapAccessJobState but storing
- * them separately).
- *
- * - Now when triggering access check after join, it'll start with a finished state
- * (since we're keeping the progress state from join)
- * - The manual progress.mutate(undefined, { revalidate: false }) doesn't work for
- * some reason
- */
- keepPreviousData?: boolean
-}
-
-const useMembershipUpdate = ({
- onSuccess,
- onError,
- keepPreviousData,
-}: Props = {}) => {
- const guild = useGuild()
- const { isAdmin } = useGuildPermission()
- const { mutate: mutateUserPoints } = useUsersPoints()
- const fetcherWithSign = useFetcherWithSign()
- const [isGettingJob, setIsGettingJob] = useAtom(isGettingJobAtom)
- const [currentlyCheckedRoleIds, setCurrentlyCheckedRoleIds] = useAtom(
- currentlyCheckedRoleIdsAtom
- )
- const { captureEvent } = usePostHogContext()
- const { rewardGranted } = useCustomPosthogEvents()
- const posthogOptions = {
- guild: guild.urlName,
- }
-
- const { roleIds: accessedRoleIds } = useMembership()
- const accessedGuildPlatformIds = new Set(
- getGuildPlatformsOfRoles(accessedRoleIds, guild).map(({ id }) => id)
- )
-
- const getKeyForSWRWithOptionalAuth = useGetKeyForSWRWithOptionalAuth()
-
- const submit = async (data?): Promise => {
- setIsGettingJob(true)
-
- const initialPollResult: JoinJob[] = await fetcherWithSign(
- getKeyForSWRWithOptionalAuth(
- `/v2/actions/join?${new URLSearchParams({
- guildId: `${guild?.id}`,
- }).toString()}`
- )
- ).catch(() => null as JoinJob[])
-
- const jobAlreadyInProgress = initialPollResult?.find((job) => !job.done)
-
- if (jobAlreadyInProgress) {
- return jobAlreadyInProgress?.id
- }
-
- const { jobId } = await fetcherWithSign([
- `/v2/actions/join`,
- { method: "POST", body: { guildId: guild?.id, ...(data ?? {}) } },
- ])
- return jobId
- }
-
- const { triggerPoll, ...progress } = useActiveMembershipUpdate({
- onSuccess: (res) => {
- if (res?.failed)
- return onError?.({
- error: res.failedErrorMsg,
- correlationId: res.correlationId,
- })
-
- if (res?.updateMembershipResult?.newMembershipRoleIds?.length > 0) {
- const grantedGuildPlatforms = getGuildPlatformsOfRoles(
- res.updateMembershipResult.newMembershipRoleIds,
- guild
- )
-
- const newGuildPlatforms = grantedGuildPlatforms.filter(
- ({ id }) => !accessedGuildPlatformIds.has(id)
- )
-
- if (newGuildPlatforms.length > 0) {
- newGuildPlatforms.forEach((newGuildPlatform) => {
- rewardGranted(newGuildPlatform.platformId)
- })
- }
- }
-
- if (res?.roleAccesses?.some((role) => !!role.access)) {
- // mutate guild in case the user sees more entities due to visibilities
- if (!isAdmin) guild.mutateGuild()
-
- mutateUserPoints()
- }
-
- setCurrentlyCheckedRoleIds([])
- onSuccess?.(res)
- },
- onError: (error) => {
- onError?.(error)
- setCurrentlyCheckedRoleIds([])
- },
- keepPreviousData,
- })
-
- const useSubmitResponse = useSubmit(submit, {
- onSuccess: () => {
- setIsGettingJob(false)
- triggerPoll()
- },
- onError: (error) => {
- setIsGettingJob(false)
- captureEvent(`Guild join error`, { ...posthogOptions, error })
- onError?.(error)
- },
- })
-
- const isLoading = isGettingJob || progress.isValidating
-
- return {
- isLoading,
- joinProgress: mapAccessJobState(progress.data, isLoading),
- currentlyCheckedRoleIds,
- triggerMembershipUpdate: (data?) => {
- setCurrentlyCheckedRoleIds(data?.roleIds)
- useSubmitResponse.onSubmit(data)
- },
- reset: () => {
- useSubmitResponse.reset()
- progress.mutate(null, { revalidate: false })
- },
- }
-}
-
-export { useMembershipUpdate }
diff --git a/src/components/[guild]/JoinModal/types.ts b/src/components/[guild]/JoinModal/types.ts
deleted file mode 100644
index ab3719dc81..0000000000
--- a/src/components/[guild]/JoinModal/types.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { RequirementType } from "requirements/types"
-import { PlatformName } from "types"
-
-export type JoinForm = { shareSocials?: boolean }
-
-type ExtractPrefix = T extends `${infer Prefix}_${string}` ? Prefix : T
-export type Joinable = PlatformName | ExtractPrefix
diff --git a/src/components/[guild]/JoinModal/utils/getGuildPlatformsOfRoles.ts b/src/components/[guild]/JoinModal/utils/getGuildPlatformsOfRoles.ts
deleted file mode 100644
index 20748e4b2b..0000000000
--- a/src/components/[guild]/JoinModal/utils/getGuildPlatformsOfRoles.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import useGuild from "components/[guild]/hooks/useGuild"
-
-export function getGuildPlatformsOfRoles(
- roleIds: number[],
- guild: ReturnType
-) {
- try {
- const roleIdsSet = new Set(roleIds)
-
- const rolePlatforms = guild.roles
- .filter((role) => roleIdsSet.has(role.id))
- .flatMap((role) => role.rolePlatforms)
-
- const guildPlatformIds = new Set(
- rolePlatforms.map(({ guildPlatformId }) => guildPlatformId)
- )
-
- const guildPlatforms = guild.guildPlatforms.filter((guildPlatform) =>
- guildPlatformIds.has(guildPlatform.id)
- )
-
- return guildPlatforms
- } catch {
- return []
- }
-}
diff --git a/src/components/[guild]/JoinModal/utils/mapAccessJobState.ts b/src/components/[guild]/JoinModal/utils/mapAccessJobState.ts
deleted file mode 100644
index f7d0df3d81..0000000000
--- a/src/components/[guild]/JoinModal/utils/mapAccessJobState.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import type { AccessCheckJob, JoinJob } from "@guildxyz/types"
-
-// TODO: uncomment the properly typed groupBy once we fix our types in the queues package!
-
-// const groupBy = (entities: Entity[], by: By) =>
-// entities.reduce>((grouped, entity) => {
-// const key = `${entity[by]}`
-// // eslint-disable-next-line no-param-reassign
-// grouped[key] ||= []
-// grouped[key].push(entity)
-// return grouped
-// }, {})
-
-const groupBy = (entities: any[], by: string) =>
- entities.reduce>((grouped, entity) => {
- const key = `${entity[by]}`
- // eslint-disable-next-line no-param-reassign
- grouped[key] ||= []
- grouped[key].push(entity)
- return grouped
- }, {})
-
-// explanation of the response statuses: https://discord.com/channels/697041998728659035/1100897398454222982/1197523089441955900
-const mapAccessJobState = (progress: JoinJob, isLoading: boolean) => {
- if (!progress || progress?.failedErrorMsg) {
- if (isLoading) return { state: "PREPARING" }
-
- return {
- state: "INITIAL",
- } as const
- }
-
- const state =
- progress?.roleAccesses?.length > 0 &&
- progress.roleAccesses.every((role) => !role.access)
- ? "NO_ACCESS"
- : progress.done
- ? "FINISHED"
- : ((
- {
- none: "PREPARING",
- "access-preparation": "CHECKING",
- "access-check": "MANAGING_ROLES",
- "access-logic": "MANAGING_ROLES",
- } as const
- )[progress["completed-queue"] ?? "none"] ?? "MANAGING_REWARDS")
-
- const waitingPosition =
- (progress as any).currentQueueState === "waiting"
- ? (progress as any).position
- : null
-
- /**
- * After the "access-check" step, we can use progress.requirementAccesses for live
- * data, since that includes the final results (even if the guild has "chained"
- * roles)
- */
- const isAccessCheckComplete =
- !!progress["completed-queue"] &&
- !["access-preparation", "access-check"].includes(progress["completed-queue"])
- const requirementAccesses = isAccessCheckComplete
- ? ((progress as any)
- .requirementAccesses as AccessCheckJob["children:access-check:jobs"])
- : progress["children:access-check:jobs"]
-
- const requirements = requirementAccesses
- ? {
- all: requirementAccesses?.length,
- satisfied: requirementAccesses?.filter((req) => req?.access)?.length,
- checked: requirementAccesses?.filter((req) => req?.done)?.length,
- }
- : null
-
- const roles =
- progress.roleIds && progress.updateMembershipResult
- ? {
- all: progress.roleIds?.length,
- granted: progress.updateMembershipResult?.membershipRoleIds?.length,
- }
- : null
-
- const rewardsGroupedByPlatforms = progress["children:manage-reward:jobs"]
- ? groupBy(progress["children:manage-reward:jobs"], "flowName")
- : null
-
- const rewards = rewardsGroupedByPlatforms
- ? {
- all: Object.keys(rewardsGroupedByPlatforms).length,
- granted: Object.values(rewardsGroupedByPlatforms).filter((rewardResults) =>
- rewardResults.every((rewardResult) => rewardResult.success)
- ).length,
- }
- : null
-
- return {
- state,
- waitingPosition,
- requirements,
- roles,
- rewards,
- roleIds: progress.roleIds,
- } as const
-}
-
-export type JoinState = ReturnType
-
-export { mapAccessJobState, groupBy }
diff --git a/src/components/[guild]/JoinModal/utils/processConnectorError.ts b/src/components/[guild]/JoinModal/utils/processConnectorError.ts
deleted file mode 100644
index be41e71cde..0000000000
--- a/src/components/[guild]/JoinModal/utils/processConnectorError.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import rewards from "rewards"
-import { PlatformType } from "types"
-import capitalize from "utils/capitalize"
-
-const processConnectorError = (error: string): string | undefined => {
- if (
- typeof error !== "string" ||
- (!error.includes("connector error") && !error.includes("runner error"))
- )
- return undefined
-
- try {
- const [matchedPlatformId] = error.match(/^"\d" /) ?? []
- const platformName = matchedPlatformId
- ? rewards[PlatformType[parseInt(matchedPlatformId.replace('"', "").trim())]]
- .name
- : null
- const cleanError = error.replaceAll("\\", "")
-
- const [matchedMsg] = cleanError.match(/{"msg":"(.*?)"}/m) ?? []
- const [matchedError] = cleanError.match(/error: "(.*?)"/m) ?? []
-
- const parsedError = JSON.parse(
- matchedMsg ??
- (matchedError ? `{${matchedError.replace("error", '"msg"').trim()}}` : "")
- )
- return capitalize(
- parsedError?.msg ? `${parsedError.msg} (${platformName} error)` : ""
- )
- } catch {
- console.error("Unknown error:", error)
- return "Unknown error. Please check the console for more details."
- }
-}
-
-export { processConnectorError }
diff --git a/src/components/[guild]/JoinModal/utils/processDiscordError.ts b/src/components/[guild]/JoinModal/utils/processDiscordError.ts
deleted file mode 100644
index 0ec8cc5104..0000000000
--- a/src/components/[guild]/JoinModal/utils/processDiscordError.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { ErrorInfo } from "@/components/Error"
-import { DiscordError } from "types"
-import capitalize from "utils/capitalize"
-
-const processDiscordError = (error: DiscordError): ErrorInfo => ({
- title: capitalize(error.error.replaceAll("_", " ")),
- description: error.errorDescription,
-})
-
-export { processDiscordError }
diff --git a/src/components/[guild]/LeaveButton/LeaveButton.tsx b/src/components/[guild]/LeaveButton/LeaveButton.tsx
deleted file mode 100644
index b1d6f6ed5a..0000000000
--- a/src/components/[guild]/LeaveButton/LeaveButton.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import {
- AlertDialogBody,
- AlertDialogContent,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogOverlay,
- ButtonProps,
- IconButton,
- Popover,
- PopoverArrow,
- PopoverBody,
- PopoverContent,
- PopoverTrigger,
- useDisclosure,
-} from "@chakra-ui/react"
-import { SignOut } from "@phosphor-icons/react"
-import useGuild from "components/[guild]/hooks/useGuild"
-import Button from "components/common/Button"
-import { Alert } from "components/common/Modal"
-import useMembership from "components/explorer/hooks/useMembership"
-import { useRef } from "react"
-import useLeaveGuild from "./hooks/useLeaveGuild"
-
-const LeaveButton = (props: ButtonProps) => {
- const { isOpen, onOpen, onClose } = useDisclosure()
- const cancelRef = useRef()
-
- const { id: guildId } = useGuild()
- const { isMember } = useMembership()
- const { onSubmit, isLoading } = useLeaveGuild(onClose)
-
- if (!isMember) return null
-
- return (
- <>
-
-
- }
- onClick={onOpen}
- {...props}
- />
-
-
-
-
-
- Leave guild
-
-
-
-
-
-
-
- Leave guild
-
- Are you sure? You'll lose all your roles and can only get them back if
- you still meet all the requirements.
-
-
-
- Cancel
-
- onSubmit({ guildId })}
- isLoading={isLoading}
- data-testid="leave-alert-button"
- >
- Leave guild
-
-
-
-
- >
- )
-}
-
-export default LeaveButton
diff --git a/src/components/[guild]/LeaveButton/hooks/useLeaveGuild.ts b/src/components/[guild]/LeaveButton/hooks/useLeaveGuild.ts
deleted file mode 100644
index cf8f2ff0a4..0000000000
--- a/src/components/[guild]/LeaveButton/hooks/useLeaveGuild.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { useYourGuilds } from "@/hooks/useYourGuilds"
-import useGuild from "components/[guild]/hooks/useGuild"
-import useMembership from "components/explorer/hooks/useMembership"
-import useShowErrorToast from "hooks/useShowErrorToast"
-import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit"
-import useToast from "hooks/useToast"
-import fetcher from "utils/fetcher"
-
-type Response = any
-
-const useLeaveGuild = (onSuccess?: () => void) => {
- const toast = useToast()
- const showErrorToast = useShowErrorToast()
- const { mutate: mutateMembership } = useMembership()
- const { mutate: mutateYourGuilds } = useYourGuilds()
-
- /**
- * Since we have a leave button only on the guild page, it's safe to retrieve the
- * Guild ID from the useGuild hook instead of the params passed to onSubmit
- */
- const { id } = useGuild()
-
- const submit = (signedValidation: SignedValidation): Promise =>
- fetcher(`/user/leaveGuild`, signedValidation)
-
- return useSubmitWithSign(submit, {
- onSuccess: () => {
- toast({
- title: "You've successfully left this guild",
- status: "success",
- })
-
- mutateMembership(undefined, {
- revalidate: false,
- })
- mutateYourGuilds(
- (prevValue) => prevValue?.filter((guild) => guild.id !== id),
- { revalidate: false }
- )
-
- onSuccess?.()
- },
- onError: (error) => showErrorToast(error),
- })
-}
-
-export default useLeaveGuild
diff --git a/src/components/[guild]/LeaveButton/index.ts b/src/components/[guild]/LeaveButton/index.ts
deleted file mode 100644
index dc7b36fe2d..0000000000
--- a/src/components/[guild]/LeaveButton/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import LeaveButton from "./LeaveButton"
-
-export default LeaveButton
diff --git a/src/components/[guild]/LogicDivider.tsx b/src/components/[guild]/LogicDivider.tsx
deleted file mode 100644
index d81184634b..0000000000
--- a/src/components/[guild]/LogicDivider.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Separator } from "@/components/ui/Separator"
-import { cn } from "@/lib/utils"
-import { Logic } from "@guildxyz/types"
-type Props = { logic: Logic; className?: string }
-
-export const formattedLogic: Record = {
- AND: "AND",
- OR: "OR",
- ANY_OF: "OR",
-}
-
-const LogicDivider = ({ logic, className }: Props): JSX.Element => (
-
-
- {/* TODO: Not sure if this custom text color is a good practice or not, we should think about it */}
-
- {formattedLogic[logic]}
-
-
-
-)
-
-export { LogicDivider }
diff --git a/src/components/[guild]/NoPermissionToPageFallback.tsx b/src/components/[guild]/NoPermissionToPageFallback.tsx
deleted file mode 100644
index 2f2ff541a4..0000000000
--- a/src/components/[guild]/NoPermissionToPageFallback.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import {
- accountModalAtom,
- walletSelectorModalAtom,
-} from "@/components/Providers/atoms"
-import {
- Alert,
- AlertDescription,
- AlertIcon,
- Spacer,
- Spinner,
- Stack,
- Text,
-} from "@chakra-ui/react"
-import { SignIn } from "@phosphor-icons/react"
-import Button from "components/common/Button"
-import Card from "components/common/Card"
-import { useSetAtom } from "jotai"
-import useGuildPermission from "./hooks/useGuildPermission"
-import useUser from "./hooks/useUser"
-
-const NoPermissionToPageFallback = ({ children }) => {
- const { id, isLoading } = useUser()
- const { isAdmin } = useGuildPermission()
- const setIsWalletSelectorModalOpen = useSetAtom(walletSelectorModalAtom)
- const setIsAccountModalOpen = useSetAtom(accountModalAtom)
-
- if (isLoading)
- return (
-
-
- Checking permission
-
- )
-
- if (!id)
- return (
-
-
-
-
-
- Sign in to access this page
-
-
-
- }
- colorScheme="white"
- onClick={() => setIsWalletSelectorModalOpen(true)}
- >
- Sign in
-
-
-
- )
-
- if (!isAdmin)
- return (
-
-
-
-
-
- You don't have permission to view this page
-
-
-
- setIsAccountModalOpen(true)}>
- View account
-
-
-
- )
-
- return children
-}
-
-export default NoPermissionToPageFallback
diff --git a/src/components/[guild]/NoRolesAlert.tsx b/src/components/[guild]/NoRolesAlert.tsx
deleted file mode 100644
index 7599ddac7e..0000000000
--- a/src/components/[guild]/NoRolesAlert.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import {
- Alert,
- AlertDescription,
- AlertIcon,
- AlertTitle,
- Stack,
-} from "@chakra-ui/react"
-import Card from "components/common/Card"
-
-type Props = {
- type?: "GUILD" | "GROUP"
- areAllRolesInUse?: boolean
-}
-
-const NoRolesAlert = ({
- type = "GUILD",
- areAllRolesInUse = false,
-}: Props): JSX.Element => {
- const entity = type === "GUILD" ? "guild" : "page"
-
- return (
-
-
-
-
-
- {areAllRolesInUse
- ? "All the roles have this reward already"
- : "No public roles"}
-
-
- {areAllRolesInUse
- ? `It seems like all the roles within this ${entity} have this reward already. You can create a new role, and add this reward to it!`
- : `It seems like this ${entity} doesn't have any public roles. There might be some secret / hidden ones that you can unlock though!`}
-
-
-
-
- )
-}
-
-export default NoRolesAlert
diff --git a/src/components/[guild]/Onboarding/components/SummonMembers/components/EntryChannel.tsx b/src/components/[guild]/Onboarding/components/SummonMembers/components/EntryChannel.tsx
deleted file mode 100644
index de1c91ac18..0000000000
--- a/src/components/[guild]/Onboarding/components/SummonMembers/components/EntryChannel.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { FormControl, FormLabel, Select, Text, Tooltip } from "@chakra-ui/react"
-import { Info } from "@phosphor-icons/react"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import { useEffect } from "react"
-import { useFormContext, useWatch } from "react-hook-form"
-import { Rest } from "types"
-
-export type Channel = {
- id: string
- name: string
-}
-
-type Props = {
- channels: Channel[]
- label: string
- tooltip?: string
- showCreateOption?: boolean
- withAction?: boolean
- fieldName: string
- errorMessage?: string
-} & Rest
-
-const EntryChannel = ({
- channels,
- label,
- tooltip,
- showCreateOption = false,
- withAction,
- fieldName,
- errorMessage,
- ...rest
-}: Props) => {
- const { register, setValue } = useFormContext()
-
- const channelId = useWatch({ name: "channelId" })
-
- useEffect(() => {
- if (!channels?.some(({ id }) => id === channelId)) {
- setValue(fieldName, !showCreateOption ? channels?.[0]?.id : "0")
- }
- }, [channelId, channels, setValue, fieldName, showCreateOption])
-
- return (
-
-
-
- {label}
-
- {/* not focusable so it doesn't automatically open on modal open */}
- {tooltip && (
-
-
-
- )}
-
-
- {showCreateOption && (
-
- Create a new channel for me
-
- )}
- {channels?.map((channel) => (
-
- {channel.name}
-
- ))}
-
- {errorMessage}
-
- )
-}
-
-export default EntryChannel
diff --git a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/PanelBody.tsx b/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/PanelBody.tsx
deleted file mode 100644
index a230c81d67..0000000000
--- a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/PanelBody.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import {
- Box,
- Center,
- Grid,
- HStack,
- Text,
- VStack,
- useColorModeValue,
-} from "@chakra-ui/react"
-import useGuild from "components/[guild]/hooks/useGuild"
-import Image from "next/image"
-import PanelDescription from "./components/PanelDescription"
-import PanelTitle from "./components/PanelTitle"
-
-const GUILD_CASTLE_SIZE = 70
-const GUILD_LOGO_DC_URL = "/img/dc-message.png"
-
-const PanelBody = () => {
- const bg = useColorModeValue("gray.100", "#2F3136")
-
- const { imageUrl, name } = useGuild()
-
- const shouldShowGuildImage = imageUrl.includes("http")
- const guildImageDimension = shouldShowGuildImage ? 30 : 15
-
- return (
-
-
-
-
-
-
-
-
-
- {name}
-
-
-
-
-
-
-
-
-
-
-
-
- Do not share your private keys. We will never ask for your seed phrase.
-
-
- )
-}
-
-export default PanelBody
diff --git a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/components/EditableControls.tsx b/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/components/EditableControls.tsx
deleted file mode 100644
index ffa3a9b181..0000000000
--- a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/components/EditableControls.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import {
- IconButton,
- IconButtonProps,
- useColorModeValue,
- useEditableControls,
-} from "@chakra-ui/react"
-import { Check, PencilSimple } from "@phosphor-icons/react"
-
-const EditableControls = (props: Omit) => {
- const { isEditing, getSubmitButtonProps, getEditButtonProps } =
- useEditableControls()
-
- const conditionalProps = isEditing
- ? { "aria-label": "Edit", icon: , ...getSubmitButtonProps() }
- : { "aria-label": "Save", icon: , ...getEditButtonProps() }
-
- const color = useColorModeValue("gray.600", "white")
- const colorScheme = useColorModeValue("whiteAlpha", undefined)
-
- return (
-
- )
-}
-
-export default EditableControls
diff --git a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/components/PanelDescription.tsx b/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/components/PanelDescription.tsx
deleted file mode 100644
index cd861211e4..0000000000
--- a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/components/PanelDescription.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import {
- Editable,
- EditablePreview,
- EditableTextarea,
- HStack,
-} from "@chakra-ui/react"
-import { useController } from "react-hook-form"
-import EditableControls from "./EditableControls"
-
-const PanelDescription = () => {
- const { field } = useController({
- name: "description",
- rules: { required: true },
- })
-
- return (
-
-
-
-
-
- )
-}
-
-export default PanelDescription
diff --git a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/components/PanelTitle.tsx b/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/components/PanelTitle.tsx
deleted file mode 100644
index ed13b92ae4..0000000000
--- a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/components/PanelTitle.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import {
- Editable,
- EditableInput,
- EditablePreview,
- HStack,
- useColorModeValue,
-} from "@chakra-ui/react"
-import { useController } from "react-hook-form"
-import EditableControls from "./EditableControls"
-
-const PanelTitle = () => {
- const color = useColorModeValue("#2a66d8", "#4EACEE")
-
- const { field } = useController({
- name: "title",
- rules: { required: true },
- })
-
- return (
-
-
-
-
-
- )
-}
-
-export default PanelTitle
diff --git a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/index.ts b/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/index.ts
deleted file mode 100644
index 405fc1a9e3..0000000000
--- a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelBody/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from "./PanelBody"
diff --git a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelButton.tsx b/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelButton.tsx
deleted file mode 100644
index 1f5cda02af..0000000000
--- a/src/components/[guild]/Onboarding/components/SummonMembers/components/PanelButton.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import {
- Box,
- Editable,
- EditableInput,
- EditablePreview,
- HStack,
- Icon,
- Text,
- Wrap,
-} from "@chakra-ui/react"
-import { ArrowSquareOut, Link as LinkIcon } from "@phosphor-icons/react"
-import { useController } from "react-hook-form"
-import EditableControls from "./PanelBody/components/EditableControls"
-
-const PanelButton = () => {
- const { field } = useController({
- name: "button",
- rules: { required: true },
- })
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- Guide
-
-
-
-
- )
-}
-
-export default PanelButton
diff --git a/src/components/[guild]/Onboarding/components/SummonMembers/components/SendDiscordJoinButtonModal.tsx b/src/components/[guild]/Onboarding/components/SummonMembers/components/SendDiscordJoinButtonModal.tsx
deleted file mode 100644
index db3b5566bb..0000000000
--- a/src/components/[guild]/Onboarding/components/SummonMembers/components/SendDiscordJoinButtonModal.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { usePostHogContext } from "@/components/Providers/PostHogProvider"
-import {
- FormControl,
- FormLabel,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Text,
-} from "@chakra-ui/react"
-import useGuild from "components/[guild]/hooks/useGuild"
-import Button from "components/common/Button"
-import FormErrorMessage from "components/common/FormErrorMessage"
-import { Modal } from "components/common/Modal"
-import useServerData from "hooks/useServerData"
-import { FormProvider, useForm } from "react-hook-form"
-import useSendJoin from "../hooks/useSendJoin"
-import EntryChannel from "./EntryChannel"
-import PanelBody from "./PanelBody"
-import PanelButton from "./PanelButton"
-
-export type DiscordEmbedForm = {
- channelId: string
- serverId: string
- title: string
- description: string
- button: string
-}
-
-const SendDiscordJoinButtonModal = ({
- isOpen,
- onClose,
- onSuccess = undefined,
- serverId,
-}) => {
- const { captureEvent } = usePostHogContext()
-
- const { isLoading, onSubmit } = useSendJoin(() => {
- onClose()
- onSuccess?.()
- })
-
- const { description, name } = useGuild()
- const {
- data: { channels },
- } = useServerData(serverId)
-
- const methods = useForm({
- mode: "onSubmit",
- defaultValues: {
- title: "Verify your wallet",
- description: description || "Join this guild and get your role(s)!",
- button: `Join ${name ?? "Guild"}`,
- channelId: "0",
- serverId,
- },
- })
-
- const handleClose = () => {
- methods.reset()
- onClose()
- }
-
- return (
-
-
-
- Send Discord join button
-
-
-
- The bot will send a join button as an entry point for Discord users to
- join your guild. Feel free to customize it below!
-
-
-
-