= e => {\n eventTracker.analytics.track(\n 'Community Card Clicked',\n {\n subdomain: community.subdomain,\n communityId: community.id,\n collection,\n },\n () => {\n if (e.ctrlKey || e.metaKey) return window.open(getCommunityPath(community), '_blank');\n router.push(getCommunityPath(community));\n },\n );\n };\n\n return (\n \n \n \n \n {showRank && community.rank && (\n \n {community.rank}\n \n )}\n \n \n {community.name}
\n \n {launchDate && launchDate.getTime() > now.getTime() && (\n \n {format.relativeTime(launchDate, now)}\n \n )}\n
\n \n {community.description}\n
\n \n {!!community.website && (\n e.stopPropagation()}\n leftIcon={}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n />\n )}\n }>\n {community.quests}\n \n {!!community.totalMembers && (\n }>\n {format.number(Number(community.totalMembers), {\n notation: 'compact',\n })}\n \n )}\n {community.twitter && (\n }\n as={Link}\n href={`https://twitter.com/${community.twitter}`}\n onClick={e => e.stopPropagation()}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {!!community.twitterFollowers &&\n format.number(Number(community.twitterFollowers), {\n notation: 'compact',\n })}\n \n )}\n {community.discord && (\n }\n as={Link}\n href={community.discord}\n onClick={e => e.stopPropagation()}\n target=\"_blank\"\n >\n {!!community.totalDiscordMembers &&\n format.number(Number(community.totalDiscordMembers), {\n notation: 'compact',\n })}\n \n )}\n
\n \n );\n};\n","export * from './CommunityCard';\nexport * from './CommunityCard.types';\n","'use client';\n\nimport { useMemo } from 'react';\nimport { useCopyToClipboard } from 'react-use';\nimport { useTranslations } from 'next-intl';\nimport { useParams, useRouter, useSearchParams } from 'next/navigation';\n\nimport type { Community, CommunityMember } from '@zealy/queries';\nimport { Button } from '@zealy/design-system';\nimport { CheckLine } from '@zealy/icons';\nimport {\n communityKeys,\n joinCommunity,\n useInvitation,\n useReferralLink,\n usersKeys,\n} from '@zealy/queries';\nimport { createInviteURL, roleIsAtLeast } from '@zealy/utils';\n\nimport { queryClient } from '#app/QueryProvider';\nimport { ConnectButton } from '#components/JoinCommunityBar/ConnectButton';\nimport { revalidateTags } from '#utils/serverAction';\n\nexport type CommunityActionsProps = {\n community: Community;\n user?: CommunityMember;\n};\n\nexport const CommunityActions = ({ user, community }: CommunityActionsProps) => {\n const t = useTranslations('common');\n\n const { invitationId } = useParams<{ invitationId: string }>();\n const searchParams = useSearchParams();\n const inviteId = searchParams.get('invitationId') ?? invitationId;\n const { data: invite } = useInvitation({ id: inviteId });\n const validInvite = invite?.status === 'valid' && !roleIsAtLeast(user?.role, invite?.role);\n\n const isPrivate = community?.visibility === 'private' && !validInvite;\n\n const isBanned = user?.role === 'banned';\n\n const router = useRouter();\n\n const { data: referral } = useReferralLink(community.subdomain);\n const [copyState, copy] = useCopyToClipboard();\n\n const isLinkCopied = !!copyState.value;\n\n const handleCta = async () => {\n if (user) {\n try {\n await joinCommunity(community.subdomain, inviteId);\n } catch (error) {\n console.error((error as any).response);\n } finally {\n // Doing it here because acceptInvitation can throw and still join\n revalidateTags([`user:${user.id}`]);\n await queryClient.invalidateQueries({ queryKey: usersKeys.user('me') });\n await queryClient.invalidateQueries({ queryKey: communityKeys.userCommunities() });\n }\n } else router.push(`/login${inviteId ? `?invitationId=${inviteId}` : ''}`);\n };\n\n const isJoined = Boolean(user?.role) && !validInvite;\n\n const ctaVariant = isJoined ? 'ghost' : 'filled';\n const ctaLabel = isJoined ? t('joined') : t('join-community', { communityName: community.name });\n\n const label = isLinkCopied ? t('link-copied') : t('invite-friends');\n\n const inviteLink = useMemo(() => createInviteURL(referral?.id ?? ''), [referral?.id]);\n\n const description = isBanned ? (\n <>\n {t('banned')}
\n {!!user.banReason && t('ban-reason', { reason: user?.banReason })}\n >\n ) : isPrivate ? (\n t('private')\n ) : undefined;\n\n const handleCopy = () => {\n copy(inviteLink);\n };\n\n const canJoin = !isJoined && !isBanned && !isPrivate;\n\n return (\n <>\n \n {user ? (\n \n ) : (\n \n {t('connect-to', {\n platform: t('brandName'),\n })}\n \n )}\n : undefined}\n >\n {label}\n \n
\n {description && (\n {description}
\n )}\n >\n );\n};\n","import clsx from 'clsx';\nimport { useTranslations } from 'next-intl';\n\nimport { Tag } from '@zealy/design-system';\nimport { DiscordBrand, Link2Line, MapViewFilled, TwitterFilled, UserFilled } from '@zealy/icons';\nimport { Community } from '@zealy/queries';\n\nimport { CommunityImage } from '#components/CommunityCard';\nimport { getTwitterAccountURL } from '#utils/urls';\n\nconst extractHost = (url: string) => {\n try {\n const urlObject = new URL(url);\n\n return urlObject.hostname.replace('www.', '');\n } catch (e) {\n return url;\n }\n};\n\nexport const CommunityProfile = ({\n community,\n className,\n}: {\n community: Community;\n className?: string;\n}) => {\n const t = useTranslations('onboarding.steps.launch');\n const intl = new Intl.NumberFormat('en-US', { notation: 'compact' });\n\n return (\n \n
\n {community.image && }\n {community.name}\n
\n {community.description && (\n
\n {community.description}\n \n )}\n
\n {community.website && (\n }\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n as=\"a\"\n href={community.website}\n >\n {extractHost(community.website)}\n \n )}\n {community.discord && (\n }\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n as=\"a\"\n href={community.discord}\n size=\"sm\"\n >\n {intl.format(Number(community.totalDiscordMembers))}\n \n )}\n {community.twitter && (\n }\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n as=\"a\"\n href={getTwitterAccountURL(community.twitter)}\n size=\"sm\"\n >\n {intl.format(Number(community.twitterFollowers))}\n \n )}\n {community.totalMembers && (\n } size=\"sm\">\n {intl.format(Number(community.totalMembers))}\n \n )}\n {community.quests && (\n } size=\"sm\">\n {intl.format(Number(community.quests))}\n \n )}\n
\n
\n );\n};\n","export * from './CommunityProfile';\n","'use client';\n\nimport Base from 'react-countdown';\nimport clsx from 'clsx';\n\nimport { Badge } from '@zealy/design-system';\n\nimport { useDidMount } from '#hooks/useDidMount';\n\nexport const Countdown = ({\n date,\n label,\n className,\n}: {\n date: string | Date;\n label?: string;\n className?: string;\n}) => {\n const didMount = useDidMount();\n if (!didMount) return null;\n return (\n \n {!!label && {label}}\n {\n return (\n \n \n {formatted.days}\n d\n \n \n {formatted.hours}\n h\n \n \n {formatted.minutes}\n m\n \n \n {formatted.seconds}\n s\n \n \n );\n }}\n />\n
\n );\n};\n","export * from './Countdown';\n","'use client';\n\nimport { useSessionStorageValue } from '@react-hookz/web';\nimport React from 'react';\nimport { useTranslations } from 'next-intl';\nimport { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation';\n\nimport type { ButtonProps } from '@zealy/design-system';\nimport { Button } from '@zealy/design-system';\n\nexport const ConnectButton = (props: ButtonProps) => {\n const t = useTranslations('common');\n\n const { invitationId, subdomain } = useParams<{ invitationId: string; subdomain?: string }>();\n const searchParams = useSearchParams();\n const inviteId = searchParams.get('invitationId') ?? invitationId;\n const pathname = usePathname();\n const { set: setRedirectUrl } = useSessionStorageValue('redirectUrl');\n const router = useRouter();\n\n const handleClick = () => {\n setRedirectUrl(pathname);\n\n const href = pathname?.includes('/embed/c/')\n ? '/embed/signup'\n : `/signup?${inviteId ? `invitationId=${inviteId}&` : ''}${\n subdomain ? `subdomain=${subdomain}` : ''\n }`;\n\n router.push(href);\n };\n\n return (\n \n );\n};\n","'use client';\n\nimport * as Dialog from '@radix-ui/react-dialog';\nimport React from 'react';\n\nimport { Button } from '@zealy/design-system';\nimport { CrossLine } from '@zealy/icons';\n\nimport { cn } from '#utils/utils';\n\nexport interface ModalProps extends Dialog.DialogProps {\n title?: string;\n description?: React.ReactNode;\n trigger?: React.ReactElement;\n className?: string;\n hideCloseButton?: boolean;\n}\n\nexport const Modal = ({\n title,\n description,\n trigger,\n children,\n className,\n hideCloseButton,\n ...props\n}: ModalProps) => {\n return (\n \n {trigger && {trigger}}\n \n \n \n {(title || description) && (\n \n {title && (\n \n {title}\n \n )}\n {description && (\n \n {description}\n \n )}\n
\n )}\n {children}\n {!hideCloseButton && (\n \n }\n />\n \n )}\n \n \n \n );\n};\n","export * from './Modal';\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { withBaseIcon } from '../src/Icons.utils';\nexport const MegaphoneFilled = (props) => withBaseIcon(_jsx(\"svg\", { width: 24, height: 24, viewBox: \"0 0 24 24\", fill: \"none\", role: \"img\", children: _jsx(\"path\", { d: \"M20.75 4.62164C20.75 2.70623 20.75 1.74852 20.3654 1.22163 19.9971.717142 19.4143.413686 18.7898.401276 18.1375.388314 17.353.937523 15.7838 2.03594L12.115 4.60413C11.6153 4.95387 11.3655 5.12874 11.1849 5.3557 11.0251 5.55666 10.9054 5.78654 10.8324 6.03277 10.75 6.31085 10.75 6.61579 10.75 7.22567L10.75 14.7745C10.75 15.3843 10.75 15.6893 10.8324 15.9674 10.9054 16.2136 11.0251 16.4435 11.1849 16.6444 11.3655 16.8714 11.6153 17.0463 12.115 17.396L15.7837 19.9641C17.3529 21.0626 18.1376 21.6118 18.7898 21.5988 19.4143 21.5864 19.9971 21.283 20.3654 20.7785 20.75 20.2516 20.75 19.2939 20.75 17.3784V14.3005C20.75 14.1128 20.8438 13.9375 21 13.8334L21.752 13.3321C22.5317 12.8122 23 11.9371 23 11V11C23 10.0629 22.5317 9.18777 21.7519 8.66795L21 8.16672C20.8438 8.0626 20.75 7.88731 20.75 7.6996V4.62164ZM9.00761 6.25006C9.14149 6.25006 9.25003 6.3586 9.25003 6.49249V19.8751C9.25003 20.9106 8.41057 21.7501 7.37503 21.7501V21.7501C6.3395 21.7501 5.50003 20.9106 5.50003 19.8751V15.7501 15.7501C3.15282 15.7501 1.25003 13.8473 1.25003 11.5001L1.25003 11.0001C1.25003 8.37671 3.37668 6.25006 6.00003 6.25006H9.00761Z\", fill: \"currentColor\" }) }), props);\nMegaphoneFilled.iconName = 'megaphone';\nMegaphoneFilled.displayName = 'MegaphoneFilled';\nexport default MegaphoneFilled;\n","import {\n Button,\n Calendar,\n EmbedURL,\n Link,\n LinkCopyGroup,\n Popover,\n PopoverContent,\n PopoverTrigger,\n TimePicker,\n} from '@zealy/design-system';\nimport { useCommunity, useReferralLink, useUpdateCommunity } from '@zealy/queries';\n\nimport type { Community } from '@zealy/utils';\nimport { Countdown } from '#components/Countdown';\nimport Image from 'next/image';\nimport { MegaphoneFilled } from '@zealy/icons';\nimport React from 'react';\nimport { UpgradeModal } from '#components/UpgradeModal';\nimport dayjs from 'dayjs';\nimport { envConfig } from '#app/config';\nimport { getCommunityPath } from '@zealy/utils';\nimport { toast } from '#components/Toaster';\nimport { useDidMount } from '#hooks/useDidMount';\nimport { useTranslations } from 'next-intl';\n\nconst ScheduleLaunch = ({\n handleLaunch,\n initialValue,\n}: {\n handleLaunch: (launchDate?: Date) => void;\n initialValue: Date | undefined;\n}) => {\n const t = useTranslations('onboarding.steps.launch');\n const [selectedDay, setSelectedDay] = React.useState(initialValue);\n\n return (\n \n \n setSelectedDay(\n dayjs(date)\n .set('hour', selectedDay?.getHours() ?? 12)\n .set('minutes', selectedDay?.getMinutes() ?? 0)\n .startOf('hour')\n .utc()\n .toDate(),\n )\n }\n />\n\n {\n setSelectedDay(\n dayjs(selectedDay).set('hour', hours).set('minutes', minutes).utc().toDate(),\n );\n }}\n size=\"sm\"\n />\n\n \n \n \n
\n \n );\n};\n\nexport const LaunchButton = () => {\n const t = useTranslations('onboarding.steps.launch');\n\n return (\n \n \n \n
{t('description')}
\n
{t('label')}
\n
\n \n );\n};\n\nconst LaunchCountdown = ({ launchDate }: { launchDate: string }) => {\n const t = useTranslations();\n\n return (\n \n
\n
\n {t('onboarding.steps.launch.date.label')}\n
\n
\n \n {t('common.edit', {\n entity: '',\n })}\n \n \n
\n
\n {t('onboarding.steps.launch.date.formatted', {\n date: new Date(launchDate),\n })}\n
\n
\n
\n );\n};\n\nconst ShareLaunch = ({\n community,\n undoLaunch,\n}: {\n community?: Community;\n undoLaunch: () => void;\n}) => {\n const t = useTranslations();\n const { data: invite } = useReferralLink();\n\n if (!community || !invite?.id) return null;\n\n const communityPath = getCommunityPath(community);\n\n const inviteLink = `${envConfig.appUrl}${communityPath}/invite/${invite?.id}`;\n\n return (\n \n {community.image && (\n \n )}\n \n \n \n );\n};\n\nexport const LaunchCommunity = ({\n children,\n launchDate,\n}: {\n children?: React.ReactNode;\n launchDate?: string | null;\n}) => {\n const didMount = useDidMount();\n const [open, setOpen] = React.useState(false);\n const [isUpgradeModalOpen, setOpenUpgradeModal] = React.useState(false);\n const t = useTranslations('onboarding.promote-launch');\n const tCommon = useTranslations('common');\n\n const closeUpgradeModal = () => setOpenUpgradeModal(false);\n const openUpgradeModal = () => setOpenUpgradeModal(true);\n\n const community = useCommunity();\n\n const updateCommunity = useUpdateCommunity();\n\n const hasNotScheduledLaunch = community.data && !community.data.launchDate;\n const hasScheduledLaunch = community.data && !!community.data.launchDate;\n const isPayingCommunity = !!community.data?.planId;\n\n const handleLaunch = (selectedDay?: Date) => {\n updateCommunity.mutate(\n {\n launchDate: selectedDay\n ? selectedDay.toISOString()\n : dayjs().add(3, 'minutes').toISOString(),\n requiredFields: {\n fillEmail: true,\n fillWallet: false,\n linkWallet: false,\n ...(community.data?.requiredFields ?? {}),\n linkTwitter: true,\n linkDiscord: true,\n },\n },\n {\n onError: () => toast.error(tCommon('toast-error')),\n },\n );\n };\n\n const handleUndoLaunch = () => {\n updateCommunity.mutate(\n {\n launchDate: null,\n },\n {\n onSuccess: () => setOpen(false),\n onError: () => toast.error(tCommon('toast-error')),\n },\n );\n };\n\n if (!community.data || !didMount) return null;\n\n return (\n \n {community.data?.launchDate ? (\n \n ) : (\n children\n )}\n {hasScheduledLaunch && !isPayingCommunity && (\n <>\n \n }\n >\n {t('cta')}\n \n
\n \n \n >\n )}\n\n {hasNotScheduledLaunch ? (\n \n ) : (\n \n )}\n \n );\n};\n","import { DialogClose } from '@radix-ui/react-dialog';\nimport React from 'react';\nimport { useTranslations } from 'next-intl';\n\nimport {\n Button,\n Drawer,\n DrawerClose,\n DrawerContent,\n DrawerTrigger,\n Popover,\n PopoverClose,\n PopoverContent,\n PopoverTrigger,\n} from '@zealy/design-system';\n\nimport type { ModalProps } from '#components/Modal';\nimport { Modal } from '#components/Modal';\nimport { useDebouncedValue } from '#hooks/useDebouncedValue';\nimport { useDidMount } from '#hooks/useDidMount';\nimport { useIsMobile } from '#hooks/useIsMobile';\n\nexport interface ResponsivePopoverProps extends ModalProps {\n trigger?: React.ReactElement;\n popoverProps?: React.ComponentProps;\n asModal?: boolean;\n onSave?: () => void;\n onCancel?: () => void;\n showActions?: boolean;\n drawerClassName?: string;\n saveLabel?: React.ReactNode;\n isLoading?: boolean;\n}\n\nexport const ResponsivePopover = ({\n children,\n trigger,\n popoverProps,\n asModal,\n onSave,\n onCancel,\n showActions,\n drawerClassName,\n saveLabel,\n isLoading,\n ...props\n}: ResponsivePopoverProps) => {\n const t = useTranslations('common');\n const isMobile = useIsMobile();\n\n const isMobileDebounced = useDebouncedValue(isMobile, 3000);\n\n const didMount = useDidMount();\n\n if (!didMount) return null;\n\n if (isMobileDebounced)\n return (\n \n {trigger && {trigger}}\n\n \n {showActions && (\n \n \n \n \n\n \n
\n )}\n\n {children}\n \n \n );\n\n if (asModal)\n return (\n \n {children}\n\n {showActions && (\n \n \n \n \n \n
\n )}\n \n );\n\n return (\n \n {trigger}\n \n {children}\n\n {showActions && (\n \n
\n \n \n
\n
\n )}\n \n \n );\n};\n","export * from './ResponsivePopover';\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { withBaseIcon } from '../src/Icons.utils';\nexport const InfinityLine = (props) => withBaseIcon(_jsx(\"svg\", { width: 24, height: 24, viewBox: \"0 0 24 24\", fill: \"none\", role: \"img\", children: _jsx(\"path\", { d: \"M10 16.0004C9.16434 16.6281 8.12561 17 7 17C4.23858 17 2 14.7614 2 12C2 9.23858 4.23858 7 7 7C9.76142 7 12 9.23858 12 12C12 14.7614 14.2386 17 17 17C19.7614 17 22 14.7614 22 12C22 9.23858 19.7614 7 17 7C15.8742 7 14.8353 7.37209 13.9995 8\", stroke: \"currentColor\", strokeWidth: 2, strokeLinecap: \"round\", strokeLinejoin: \"round\" }) }), props);\nInfinityLine.iconName = 'infinity';\nInfinityLine.displayName = 'InfinityLine';\nexport default InfinityLine;\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nimport { withBaseIcon } from '../src/Icons.utils';\nexport const ChartBarFilled = (props) => withBaseIcon(_jsx(\"svg\", { width: 24, height: 24, viewBox: \"0 0 24 24\", fill: \"none\", role: \"img\", children: _jsx(\"path\", { fillRule: \"evenodd\", clipRule: \"evenodd\", d: \"M3.98005 1.79497C5.04961 1.25 6.44974 1.25 9.25 1.25H14.75C17.5503 1.25 18.9504 1.25 20.02 1.79497C20.9608 2.27433 21.7257 3.03924 22.205 3.98005C22.75 5.04961 22.75 6.44974 22.75 9.25V14.75C22.75 17.5503 22.75 18.9504 22.205 20.02C21.7257 20.9608 20.9608 21.7257 20.02 22.205C18.9504 22.75 17.5503 22.75 14.75 22.75H9.25C6.44974 22.75 5.04961 22.75 3.98005 22.205C3.03924 21.7257 2.27433 20.9608 1.79497 20.02C1.25 18.9504 1.25 17.5503 1.25 14.75V9.25C1.25 6.44974 1.25 5.04961 1.79497 3.98005C2.27433 3.03924 3.03924 2.27433 3.98005 1.79497ZM7.99982 6.25C7.5856 6.25 7.24982 6.58579 7.24982 7C7.24982 7.41421 7.5856 7.75 7.99982 7.75H13.9998C14.414 7.75 14.7498 7.41421 14.7498 7C14.7498 6.58579 14.414 6.25 13.9998 6.25H7.99982ZM7.99982 11.25C7.5856 11.25 7.24982 11.5858 7.24982 12C7.24982 12.4142 7.5856 12.75 7.99982 12.75H15.9998C16.414 12.75 16.7498 12.4142 16.7498 12C16.7498 11.5858 16.414 11.25 15.9998 11.25H7.99982ZM7.99982 16.25C7.5856 16.25 7.24982 16.5858 7.24982 17C7.24982 17.4142 7.5856 17.75 7.99982 17.75H11.9998C12.414 17.75 12.7498 17.4142 12.7498 17C12.7498 16.5858 12.414 16.25 11.9998 16.25H7.99982Z\", fill: \"currentColor\" }) }), props);\nChartBarFilled.iconName = 'chart-bar';\nChartBarFilled.displayName = 'ChartBarFilled';\nexport default ChartBarFilled;\n","'use client';\n\nimport React, { useEffect } from 'react';\nimport clsx from 'clsx';\nimport { useTranslations } from 'next-intl';\nimport Link from 'next/link';\nimport { useParams } from 'next/navigation';\n\nimport type { CommunityMember } from '@zealy/queries';\nimport { Badge, Button } from '@zealy/design-system';\nimport {\n ChartBarFilled,\n ExportLine,\n GamesFilled,\n InfinityLine,\n TrendUpLine,\n UsersFilled,\n} from '@zealy/icons';\nimport { useAuthenticatedUser, useCommunity } from '@zealy/queries';\n\nimport { useEventTracker } from '#context';\n\nimport type { ModalProps } from './Modal';\nimport { Modal } from './Modal';\n\nconst BENEFITS = [\n 'increased-limits',\n 'visibility',\n 'crm',\n 'analytics',\n 'export',\n 'co-marketing',\n] as const;\n\nconst BENEFIT_ICONS = {\n 'increased-limits': InfinityLine,\n visibility: TrendUpLine,\n crm: UsersFilled,\n analytics: ChartBarFilled,\n export: ExportLine,\n 'co-marketing': GamesFilled,\n};\n\nconst COLORS = [\n 'bg-task-type-poll',\n 'bg-task-type-file-upload',\n 'bg-task-type-api',\n 'bg-task-type-url',\n 'bg-task-type-visit-link',\n 'bg-task-type-text',\n];\n\nexport const UpgradeModal = (props: ModalProps) => {\n const { subdomain } = useParams<{ subdomain: string }>();\n const t = useTranslations('subscriptions.upgrade-modal');\n const { analytics } = useEventTracker();\n const { data: community } = useCommunity();\n\n const user = useAuthenticatedUser();\n\n const canUpgrade = user.data?.role === 'admin';\n\n useEffect(() => {\n if (props.open) {\n analytics.track('Viewed Upgrade Modal', {\n communityId: community?.id,\n });\n\n analytics.track_links('#upgrade-cta', 'Clicked Upgrade Modal Link', {\n communityId: community?.id,\n });\n }\n }, [props.open, analytics, community?.id]);\n\n return (\n \n \n
\n {t.rich('title', {\n pink: text => {text},\n })}\n
\n
\n
\n
{t('description')}
\n {canUpgrade ? (\n
\n ) : (\n <>\n
\n
{t('admin-access-required')}
\n >\n )}\n
\n
\n \n );\n};\n","'use client';\n\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useEffect, useState } from 'react';\nimport Cookies from 'js-cookie';\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation';\n\nimport type { IAuthError } from '@zealy/utils';\nimport { AuthError } from '@zealy/utils';\n\nconst AuthErrorContext = createContext<{\n authError: IAuthError | undefined;\n setAuthError: (authError?: IAuthError) => void;\n}>({\n authError: undefined,\n setAuthError: () => {},\n});\n\nconst safeParse = (json: string) => {\n try {\n return JSON.parse(json);\n } catch (e) {\n return undefined;\n }\n};\n\nexport const AuthErrorProvider = ({ children }: { children: ReactNode }) => {\n const pathname = usePathname();\n const search = useSearchParams();\n const router = useRouter();\n\n const authError = search.get('error');\n\n const parsedAuthError = authError ? safeParse(authError) : undefined;\n\n const instantiatedAuthError = parsedAuthError ? new AuthError(parsedAuthError) : undefined;\n\n const [error, setError] = useState(instantiatedAuthError);\n\n const isConnected = !!Cookies.get('user_metadata');\n\n useEffect(() => {\n if (!pathname.includes('settings') && isConnected && authError)\n router.push(`/cw/_/settings/linked-accounts?error=${authError}`);\n }, [pathname, isConnected, router, authError]);\n\n return (\n \n {children}\n \n );\n};\n\nexport const useAuthError = () => {\n const result = useContext(AuthErrorContext);\n if (!result) {\n throw new Error('Context used outside of its Provider!');\n }\n return result;\n};\n","'use client';\n\nimport { FlagsmithProvider, useFlags } from 'flagsmith/react';\nimport React from 'react';\n\nimport { getMe } from '@zealy/queries';\n\nimport { createContext } from '../createContext';\nimport { communitiesFlag } from './communitiesFlag';\n\nexport type CommunitiesFlagContext = typeof communitiesFlag;\n\nexport const [CommunitiesFlagsContextProvider, useCommunitiesFlagContext] =\n createContext({\n name: 'CommunitiesFlagsProvider',\n hookName: 'useCommunitiesFlag',\n defaultValue: communitiesFlag,\n });\n\nexport function CommunitiesFlagProvider({ children }: { children: React.ReactNode }) {\n return (\n \n <>{children}>\n \n );\n}\n\nexport function useCommunitiesFlags(key: T) {\n return useFlags(key);\n}\n\n/**\n * Helper for parsing array of user ids from flag value (JSON), for emulating user-related flag\n */\nexport function useUserFlag(key: string) {\n const flag = useFlags([key])?.[key];\n if (!flag) {\n return { enabled: false };\n }\n if (flag.enabled) {\n return { enabled: true };\n }\n const userIds: string[] = flag.value ? JSON.parse(flag.value as string) : [];\n const myUserId = getMe();\n if (!myUserId) {\n return { enabled: false };\n }\n return { enabled: userIds.includes(myUserId) };\n}\n","'use client';\n\nimport React from 'react';\n\nimport { CommunitiesFlagProvider, useCommunitiesFlags, useUserFlag } from './CommunitiesFlags';\n\nexport function FeatureFlagsProvider({ children }: { children: React.ReactNode }) {\n return {children};\n}\n\nexport { useCommunitiesFlags, useUserFlag };\n","import type React from 'react';\nimport { createContext as createReactContext, useContext as useReactContext } from 'react';\n\nexport interface CreateContextOptions {\n strict?: boolean;\n hookName?: string;\n providerName?: string;\n errorMessage?: string;\n name?: string;\n defaultValue?: T;\n}\n\nexport type CreateContextReturn = [React.Provider, () => T, React.Context];\n\nfunction getErrorMessage(hook: string, provider: string) {\n return `${hook} returned \\`undefined\\`. Seems you forgot to wrap component within ${provider}`;\n}\n\nexport function createContext(options: CreateContextOptions = {}) {\n const {\n name,\n strict = true,\n hookName = 'useContext',\n providerName = 'Provider',\n errorMessage,\n defaultValue,\n } = options;\n\n const Context = createReactContext(defaultValue);\n\n Context.displayName = name;\n\n function useContext() {\n const context = useReactContext(Context);\n\n if (!context && strict) {\n const error = new Error(errorMessage ?? getErrorMessage(hookName, providerName));\n error.name = 'ContextError';\n Error.captureStackTrace?.(error, useContext);\n throw error;\n }\n\n return context;\n }\n\n return [Context.Provider, useContext, Context] as CreateContextReturn;\n}\n","'use client';\n\nimport React, { useCallback } from 'react';\n\nimport { envConfig } from '#app/config';\n\ntype Popup = {\n id: string;\n priority: number;\n};\n\nexport interface PopupState {\n queue: Popup[];\n}\n\nexport interface PopupContextState {\n queue: Popup[];\n currentPopupId: string | null;\n}\n\nexport const PopupContext = React.createContext<{\n state: PopupContextState;\n dispatch: React.Dispatch;\n}>({\n state: { currentPopupId: null, queue: [] },\n dispatch: () => {},\n});\n\nexport enum PopupAction {\n ADD_POPUP = 'ADD_POPUP',\n REMOVE_POPUP = 'REMOVE_POPUP',\n}\n\nexport interface PopupProviderAction {\n type: PopupAction;\n payload: Popup;\n}\n\nfunction popupReducer(state: PopupState, action: PopupProviderAction) {\n switch (action.type) {\n case 'ADD_POPUP': {\n const isInQueue = state.queue.some(popup => popup.id === action.payload.id);\n if (isInQueue) return state;\n\n return {\n queue: [...state.queue, action.payload].sort((a, b) => a.priority - b.priority),\n };\n }\n case 'REMOVE_POPUP': {\n return {\n queue: state.queue.filter(popup => popup.id !== action.payload.id),\n };\n }\n default:\n throw new Error(`Unhandled action type: ${action.type}`);\n }\n}\n\n/**\n * All popups that are not triggered by the user should be added using this\n * provider to ensure that they are not shown at the same time.\n */\nexport const PopupProvider = ({ children }: { children: React.ReactNode }) => {\n const [state, dispatch] = React.useReducer(popupReducer, { queue: [] });\n\n return (\n \n {children}\n \n );\n};\n\nexport const usePopup = ({ id, priority }: { id: string; priority: number }) => {\n const context = React.useContext(PopupContext);\n if (!context) {\n throw new Error('usePopup must be used within a PopupProvider');\n }\n\n const onOpenChange = useCallback(\n (open: boolean) => {\n if (!open) {\n context.dispatch({\n type: PopupAction.REMOVE_POPUP,\n payload: { id, priority },\n });\n } else {\n context.dispatch({\n type: PopupAction.ADD_POPUP,\n payload: { id, priority },\n });\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [id, priority],\n );\n\n return {\n isOpen: context.state.currentPopupId === id,\n onOpenChange,\n };\n};\n","import { useEffect } from 'react';\nimport { useBeforeUnload as _useBeforeUnload } from 'react-use';\n\n// https://github.com/vercel/next.js/discussions/32231#discussioncomment-7284386\nexport const useBeforeUnload = (\n isConfirm = true,\n message = 'Are you sure want to leave this page?',\n rootUrl = '',\n) => {\n // check when page is about to be reloaded\n _useBeforeUnload(isConfirm, message);\n\n // check when page is about to be changed\n useEffect(() => {\n function isAnchorOfCurrentUrl(currentUrl: string, newUrl: string) {\n const currentUrlObj = new URL(currentUrl);\n const newUrlObj = new URL(newUrl);\n // Compare hostname, pathname, and search parameters\n if (rootUrl) {\n return currentUrl.startsWith(rootUrl) === newUrl.startsWith(rootUrl);\n }\n if (\n currentUrlObj.hostname === newUrlObj.hostname &&\n currentUrlObj.pathname === newUrlObj.pathname &&\n currentUrlObj.search === newUrlObj.search\n ) {\n // Check if the new URL is just an anchor of the current URL page\n const currentHash = currentUrlObj.hash;\n const newHash = newUrlObj.hash;\n return (\n currentHash !== newHash &&\n currentUrlObj.href.replace(currentHash, '') === newUrlObj.href.replace(newHash, '')\n );\n }\n return false;\n }\n\n function findClosestAnchor(element: HTMLElement | null): HTMLAnchorElement | null {\n while (element && element.tagName.toLowerCase() !== 'a') {\n element = element.parentElement;\n }\n return element as HTMLAnchorElement;\n }\n function handleClick(event: MouseEvent) {\n try {\n const target = event.target as HTMLElement;\n const anchor = findClosestAnchor(target);\n if (anchor) {\n const currentUrl = window.location.href;\n const newUrl = (anchor as HTMLAnchorElement).href;\n if (!newUrl || !currentUrl) return;\n const isAnchor = isAnchorOfCurrentUrl(currentUrl, newUrl);\n const isDownloadLink = (anchor as HTMLAnchorElement).download !== '';\n\n const isPageLeaving = !(newUrl === currentUrl || isAnchor || isDownloadLink);\n\n if (isPageLeaving && isConfirm && !window.confirm(message)) {\n // Cancel the route change\n event.preventDefault();\n event.stopPropagation();\n }\n }\n } catch (err) {\n alert(err);\n }\n }\n\n // Add the global click event listener\n document.addEventListener('click', handleClick, true);\n\n // Clean up the global click event listener when the component is unmounted\n return () => {\n document.removeEventListener('click', handleClick, true);\n };\n }, [isConfirm, message, rootUrl]);\n};\n","'use client';\n\nimport { Content, Description, Overlay, Portal, Root, Title } from '@radix-ui/react-dialog';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { useTranslations } from 'next-intl';\nimport { useReCaptcha as useReCaptchaLib } from 'next-recaptcha-v3';\nimport Script from 'next/script';\n\nimport { envConfig } from '#app/config';\nimport { useCommunitiesFlags } from '#context/FeatureFlags';\n\ndeclare global {\n interface Window {\n turnstile: {\n render: (container: HTMLElement, options: any) => string;\n reset: (widgetId: string) => void;\n remove: (widgetId: string) => void;\n };\n }\n}\n\nexport type CaptchaToken =\n | {\n turnstileToken: string;\n }\n | {\n reCaptchaToken: string;\n }\n | undefined;\n\nconst isCypressTest = typeof window !== 'undefined' && window.CYPRESS_TEST_ENV === true;\n\n/**\n * A simplified captcha hook that uses Turnstile in interaction-only mode,\n * or falls back to reCAPTCHA if Turnstile is not enabled.\n *\n * Usage:\n * const { CaptchaWidget, getToken } = useCaptcha('myAction');\n *\n * Call getToken() to trigger rendering of the widget and retrieve a token.\n * It returns { reCaptchaToken: string } when using reCAPTCHA or\n * { turnstileToken: string } when using Turnstile.\n * Remove console.log once Turnstile is fully deployed and working.\n */\nexport const useCaptcha = (action: string) => {\n const t = useTranslations('captcha');\n\n const { turnstile } = useCommunitiesFlags(['turnstile']);\n const useTurnstile =\n turnstile.enabled && (turnstile.value as undefined | string)?.includes(action);\n\n // reCAPTCHA fallback\n const { executeRecaptcha, loaded: recaptchaLoaded } = useReCaptchaLib();\n\n const turnstileScriptLoadedRef = useRef(false);\n const [widgetId, setWidgetId] = useState(null);\n const [shouldRenderWidget, setShouldRenderWidget] = useState(false);\n const [dialogOpen, setDialogOpen] = useState(false);\n\n const containerRef = useRef(null);\n const tokenPromiseRef = useRef<{\n resolve: (token: string) => void;\n reject: (error: Error) => void;\n } | null>(null);\n\n useEffect(() => {\n if (window.turnstile && !turnstileScriptLoadedRef.current) {\n console.log('๐ Detected global turnstile object, script already loaded');\n turnstileScriptLoadedRef.current = true;\n }\n }, []);\n\n const cleanupWidget = useCallback(() => {\n if (window.turnstile && widgetId) {\n try {\n window.turnstile.remove(widgetId);\n setWidgetId(null);\n } catch (e) {\n console.error('Failed to remove turnstile widget:', e);\n setWidgetId(null);\n }\n }\n }, [widgetId]);\n\n const handleTurnstileSuccess = useCallback(\n (token: string) => {\n console.log('๐ handleTurnstileSuccess called with token:', token?.slice(0, 10) + '...');\n\n if (tokenPromiseRef.current) {\n tokenPromiseRef.current.resolve(token);\n tokenPromiseRef.current = null;\n }\n\n setDialogOpen(false);\n\n // Use a timeout to ensure the dialog animation completes before cleanup\n setTimeout(() => {\n setShouldRenderWidget(false);\n cleanupWidget();\n }, 300);\n },\n [cleanupWidget],\n );\n\n // Define renderWidget first (moved up)\n const renderWidget = useCallback(() => {\n console.log('๐ฏ renderWidget called', {\n hasTurnstile: !!window.turnstile,\n hasContainer: !!containerRef.current,\n currentWidgetId: widgetId,\n });\n\n cleanupWidget();\n\n if (window.turnstile && containerRef.current) {\n const id = window.turnstile.render(containerRef.current, {\n sitekey: envConfig.turnstile.siteKey,\n action,\n callback: handleTurnstileSuccess,\n size: 'normal',\n appearance: 'interaction-only',\n 'before-interactive-callback': () => {\n console.log('๐ฌ before-interactive-callback triggered');\n setDialogOpen(true);\n },\n });\n console.log('๐ New widget rendered with ID:', id);\n setWidgetId(id);\n }\n }, [action, handleTurnstileSuccess, cleanupWidget, widgetId]);\n\n const forceRender = useCallback(() => {\n console.log('๐ Force render triggered');\n // Use a small timeout to ensure DOM has time to update\n setTimeout(() => {\n if (containerRef.current && window.turnstile && !widgetId && shouldRenderWidget) {\n console.log('๐ Container is available, rendering widget');\n renderWidget();\n } else {\n console.log('โ ๏ธ Container not ready:', {\n hasContainer: !!containerRef.current,\n hasTurnstile: !!window.turnstile,\n widgetId,\n shouldRender: shouldRenderWidget,\n });\n }\n }, 50);\n }, [renderWidget, widgetId, shouldRenderWidget]);\n\n // Effect to render the widget when the script is loaded and the widget is requested.\n useEffect(() => {\n console.log('๐ Render effect triggered:', {\n useTurnstile,\n turnstileScriptLoaded: turnstileScriptLoadedRef.current,\n shouldRenderWidget,\n hasContainer: !!containerRef.current,\n widgetId,\n });\n\n if (\n useTurnstile &&\n turnstileScriptLoadedRef.current &&\n shouldRenderWidget &&\n containerRef.current &&\n !widgetId\n ) {\n console.log('โจ Conditions met, calling renderWidget');\n renderWidget();\n }\n }, [useTurnstile, shouldRenderWidget, widgetId, renderWidget]);\n\n // Effect to check container availability when script loads\n useEffect(() => {\n if (turnstileScriptLoadedRef.current && shouldRenderWidget) {\n forceRender();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [turnstileScriptLoadedRef.current, shouldRenderWidget, forceRender]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n cleanupWidget();\n };\n }, [cleanupWidget]);\n\n /**\n * getToken: Returns a promise that resolves with the captcha token.\n * - For reCAPTCHA, it executes the recaptcha function and returns { reCaptchaToken: string }.\n * - For Turnstile, it triggers widget rendering (if not already rendered), resets the widget\n * for a fresh verification, and returns a promise that resolves to { turnstileToken: string }.\n * If verification fails, it may return undefined.\n */\n const getToken = useCallback((): Promise => {\n console.log('๐ซ getToken called:', {\n useTurnstile,\n widgetId,\n shouldRenderWidget,\n hasTurnstileGlobal: !!window.turnstile,\n });\n\n if (isCypressTest) {\n console.log('๐งช Cypress test environment detected, returning dummy token');\n return Promise.resolve(\n useTurnstile\n ? { turnstileToken: 'cypress_dummy_turnstile_token' }\n : { reCaptchaToken: 'cypress_dummy_recaptcha_token' },\n );\n }\n\n if (window.turnstile && !turnstileScriptLoadedRef.current) {\n console.log('๐ Detected global turnstile object during getToken');\n turnstileScriptLoadedRef.current = true;\n }\n\n if (!useTurnstile) {\n if (!recaptchaLoaded) {\n return Promise.reject(new Error('reCAPTCHA not loaded'));\n }\n return executeRecaptcha(action).then(token =>\n token ? { reCaptchaToken: token } : undefined,\n );\n }\n\n if (widgetId) {\n cleanupWidget();\n }\n\n // Use a small timeout to avoid state update conflicts\n setTimeout(() => {\n setShouldRenderWidget(true);\n }, 0);\n\n return new Promise((resolve, reject) => {\n console.log('โณ Setting up new promise');\n tokenPromiseRef.current = {\n resolve: (token: string) => resolve(token ? { turnstileToken: token } : undefined),\n reject,\n };\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [useTurnstile, executeRecaptcha, action, recaptchaLoaded, cleanupWidget, widgetId]);\n\n // The CaptchaWidget component now renders a Radix UI Dialog with the Turnstile widget inside\n const CaptchaWidget =\n useTurnstile && shouldRenderWidget ? (\n <>\n {!turnstileScriptLoadedRef.current && !window.turnstile && (\n