(null);\n const { x } = useScroll(scrollRef);\n const { gridView, setGridView, filters } = useQuestboardContext();\n const questboard = useQuestBoard(filters);\n const didMount = useDidMount();\n\n const scrollRight = useCallback(() => {\n if (scrollRef.current) {\n scrollRef.current.scrollBy({ left: 300, behavior: 'smooth' });\n }\n }, [scrollRef]);\n\n const scrollLeft = useCallback(() => {\n if (scrollRef.current) {\n scrollRef.current.scrollBy({ left: -300, behavior: 'smooth' });\n }\n }, [scrollRef]);\n\n return (\n \n {x !== 0 && (\n
}\n />\n )}\n
\n {questboard.isLoading ? : }\n
\n {x + 15 < Number(scrollRef.current?.scrollWidth) - Number(scrollRef.current?.clientWidth) && (\n
}\n />\n )}\n\n
\n \n\n {didMount && (\n \n }\n tooltip={t('grid')}\n variant=\"muted\"\n className={clsx(\n 'rounded-r-none',\n !gridView ? '' : 'bg-component-quaternary-press border-component-quaternary-press',\n )}\n size=\"sm\"\n mutedText={!gridView}\n onClick={() => setGridView(true)}\n />\n }\n tooltip={t('section')}\n variant=\"muted\"\n className={clsx(\n 'rounded-l-none',\n gridView ? '' : 'bg-component-quaternary-press border-component-quaternary-press',\n )}\n size=\"sm\"\n mutedText={gridView}\n onClick={() => setGridView(false)}\n />\n
\n )}\n \n
\n );\n};\n","export * from './BoardHeader';\n","'use client';\n\n/*\n From Datadog documentation: https://docs.datadoghq.com/real_user_monitoring/guide/monitor-your-nextjs-app-with-rum/?tab=npm\n Because the RUM SDK needs to run on the client to collect telemetry data, the file where it is initialized through the NPM package must be a client component.\n*/\n\n/*\n Insert this component everywhere you want to record user session.\n*/\nimport { useEffect } from 'react';\nimport { datadogRum } from '@datadog/browser-rum';\n\nimport { envConfig } from '#app/config';\n\nexport const useSessionRecorder = () =>\n useEffect(() => {\n if (typeof window !== 'undefined' && envConfig.env === 'production') {\n datadogRum.startSessionReplayRecording();\n }\n }, []);\n","'use client';\n\nimport React from 'react';\n\nimport { canUseDOM, GetQuestboardOutput } from '@zealy/utils';\n\nimport { Module } from './Module';\n\nexport const GridView = ({\n questboard,\n isSubscriptionLimitReached,\n}: {\n questboard: GetQuestboardOutput;\n isSubscriptionLimitReached?: boolean;\n}) => {\n const hasOneColumn = canUseDOM() && window.matchMedia('(max-width: 1280px)').matches;\n\n const showInFirstColumn = (index: number) => {\n return hasOneColumn ? index < Number(questboard?.length) / 2 : !(index % 2);\n };\n\n return (\n \n
\n {questboard && (\n <>\n {questboard.map((category, i) => {\n if (!showInFirstColumn(i)) return null;\n\n return (\n \n );\n })}\n >\n )}\n
\n
\n {questboard && (\n <>\n {questboard.map((category, i) => {\n if (showInFirstColumn(i)) return null;\n\n return (\n \n );\n })}\n >\n )}\n
\n
\n );\n};\n","import type { CSSProperties } from 'react';\nimport React, { useMemo, useRef } from 'react';\nimport clsx from 'clsx';\nimport { useTranslations } from 'next-intl';\nimport { useParams, usePathname } from 'next/navigation';\n\nimport type { GetQuestboardOutput } from '@zealy/utils';\nimport { Badge, ProgressBar } from '@zealy/design-system';\nimport { CheckCircleFilled, SnowflakeLine } from '@zealy/icons';\n\nimport { QuestCard } from '#components/QuestCard';\nimport { DEFAULT_THEME } from '#constants/questboard';\nimport { cn } from '#utils/utils';\n\nimport { hexToRgbString } from '../GridView/ModuleCard/ModuleCard.utils';\n\nconst moduleHeaderStyle = {\n backgroundImage: `linear-gradient(to bottom left, #00000000, var(--bg) 80%), var(--cover)`,\n minHeight: '238px',\n};\n\nconst fallbackHeaderStyle = {\n backgroundImage: 'var(--cover)',\n};\n\nconst ModuleHeader = ({\n module,\n defaultTheme,\n description,\n isSubscriptionLimitReached,\n}: {\n module: GetQuestboardOutput[number];\n description?: string;\n isSubscriptionLimitReached?: boolean;\n defaultTheme?: boolean;\n}) => {\n const t = useTranslations('questboard.badge.statuses');\n\n return (\n \n
\n
\n {module.title} \n {module.status === 'completed' && (\n }>\n {t('completed')}\n \n )}\n {module.status !== 'completed' && isSubscriptionLimitReached && (\n } variant=\"info\">\n {t('limit-reached')}\n \n )}\n
\n\n {description &&
{description}
}\n\n {module.status !== 'completed' && module.totalQuestCount > 0 && (\n
\n )}\n
\n
\n );\n};\n\nexport const ModuleSection = ({\n module,\n isSubscriptionLimitReached,\n}: {\n module: GetQuestboardOutput[number];\n isSubscriptionLimitReached?: boolean;\n}) => {\n const isXl = useRef(\n typeof window !== 'undefined' && window.matchMedia('(min-width: 1280px)').matches,\n );\n const pathname = usePathname();\n\n const baseUrl = `${pathname.substring(0, pathname.indexOf('/questboard'))}/questboard`;\n\n const shouldUseFallback = !module.coverUrl && !module.color;\n\n const fallbackTheme = shouldUseFallback\n ? DEFAULT_THEME[module.position % DEFAULT_THEME.length]\n : undefined;\n\n const coverUrl = module.coverUrl\n ? module.coverUrl\n : isXl.current\n ? fallbackTheme?.coverUrlXL.src\n : fallbackTheme?.coverUrl.src;\n\n const style = useMemo(\n () =>\n ({\n '--bg':\n !!module.color || !!module.coverUrl ? module.color || 'var(--color-bg-tertiary)' : '',\n '--module-color': module.color\n ? hexToRgbString(module.color)\n : fallbackTheme?.color ?? '96,47,214',\n '--cover': `url(${coverUrl})`,\n } as CSSProperties),\n [module.color, module.coverUrl, coverUrl, fallbackTheme?.color],\n );\n\n return (\n \n
\n {module.status !== 'completed' && (\n
\n {module.quests.map(quest => (\n \n ))}\n
\n )}\n
\n );\n};\n","'use client';\n\nimport React from 'react';\nimport { useTranslations } from 'next-intl';\n\nimport type { GetQuestboardOutput } from '@zealy/utils';\nimport { Badge } from '@zealy/design-system';\n\nimport { ModuleSection } from './ModuleSection';\n\nexport const SectionView = ({\n questboard,\n isSubscriptionLimitReached,\n}: {\n questboard: GetQuestboardOutput;\n isSubscriptionLimitReached?: boolean;\n}) => {\n const t = useTranslations('questboard');\n\n const indexLockedStart = questboard?.findIndex(module => module.status === 'locked');\n const indexCompletedStart = questboard?.findIndex(module => module.status === 'completed');\n\n return (\n \n {questboard?.map((module, index) => (\n \n {indexCompletedStart === index && (\n \n {t('modules-section', {\n status: t('badge.statuses.completed'),\n })}\n \n )}\n {indexLockedStart === index && (\n \n {t('modules-section', {\n status: t('badge.statuses.lock'),\n })}\n \n )}\n \n \n ))}\n
\n );\n};\n","'use client';\n\nimport React from 'react';\n\nimport type { GetQuestboardOutput, QuestContributorFilters } from '@zealy/utils';\nimport { useSubscriptionStatus } from '@zealy/queries';\n\nimport Skeletons from '#components/Skeletons';\nimport { useDidMount } from '#hooks/useDidMount';\nimport { useSessionRecorder } from '#hooks/useSessionRecorder';\n\nimport { useFilteredQuestboard } from './Board.hooks';\nimport { EmptyState } from './EmptyState';\nimport { GridView } from './GridView/GridView';\nimport { useQuestboardContext } from './QuestboardContext';\nimport { SectionView } from './SectionView/SectionView';\n\nconst questFilters = {\n byId: (allowedIds: string[]) => (quest: GetQuestboardOutput[number]['quests'][number]) =>\n allowedIds.includes(quest.id),\n};\n\nexport const useQuestboardFilters = (\n board?: GetQuestboardOutput,\n categoryIds?: string[],\n questIds?: string[],\n) => {\n return React.useMemo(\n () =>\n (board ?? []).reduce(\n (acc, curr) => {\n const quests = questIds ? curr.quests.filter(questFilters.byId(questIds)) : curr.quests;\n\n const currentModule = {\n ...curr,\n quests,\n };\n\n const shouldIncludeModule =\n quests.length && (!categoryIds?.length || categoryIds.includes(curr.id));\n\n const modules = shouldIncludeModule ? [...acc.modules, currentModule] : acc.modules;\n\n return {\n totalQuestCount: acc.totalQuestCount + curr.totalQuestCount,\n modules,\n };\n },\n {\n modules: [] as GetQuestboardOutput,\n totalQuestCount: 0,\n },\n ),\n [board, categoryIds, questIds],\n );\n};\n\nconst BoardSkeleton = ({ gridView = false }) =>\n gridView ? (\n \n ) : (\n \n );\n\nexport interface BoardViewProps {\n filters?: Array;\n}\n\nexport const BoardView = ({ filters }: BoardViewProps) => {\n useSessionRecorder();\n const didMount = useDidMount();\n const { gridView } = useQuestboardContext();\n const { data: subscriptionData } = useSubscriptionStatus();\n\n const {\n isLoading,\n modules: filteredQuestboard,\n totalQuestCount,\n } = useFilteredQuestboard({\n customFilters: filters,\n });\n\n if (!didMount) return null;\n\n if (isLoading) return ;\n\n if (filteredQuestboard?.length === 0) return ;\n\n return (\n \n {gridView ? (\n \n ) : (\n \n )}\n
\n );\n};\n","'use client';\n\nimport React from 'react';\nimport { useTranslations } from 'next-intl';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport { useParams } from 'next/navigation';\n\nimport { Button } from '@zealy/design-system';\n\nimport { useCommunityURL } from '#hooks/useCommunityURL';\nimport { useNextQuest } from '#hooks/useNextQuest';\nimport { cn } from '#utils/utils';\n\nimport { useQuestboardContext } from './QuestboardContext';\n\nexport const EmptyState = ({\n view = 'board',\n className = '',\n totalQuestCount,\n}: {\n view?: 'board' | 'module';\n className?: string;\n totalQuestCount?: number;\n}) => {\n const t = useTranslations();\n const tS = useTranslations('questboard.search');\n\n const { moduleId, questId } = useParams<{ moduleId: string; questId: string }>();\n\n const nextQuest = useNextQuest(moduleId, questId);\n const baseURL = useCommunityURL();\n const nextQuestURL = `${baseURL}/questboard/${nextQuest?.categoryId}/${nextQuest?.id}`;\n\n const { setSelectedCategories, clearFilters } = useQuestboardContext();\n\n const clearAllFilters = () => {\n setSelectedCategories([]);\n clearFilters();\n };\n\n return (\n \n
\n
\n {t(`questboard.empty.${view}`)}\n
\n\n {nextQuest && (\n
\n {t('common.next-quest')}\n \n )}\n\n {!!totalQuestCount && (\n
\n
\n {tS.rich('hidden-quests', {\n b: children => {children} ,\n count: totalQuestCount,\n })}\n
\n
\n {tS('clear')}\n \n
\n )}\n
\n );\n};\n","'use client';\n\nimport React from 'react';\nimport { useTranslations } from 'next-intl';\n\nimport { TabItem, Tabs } from '@zealy/design-system';\nimport { Timer2Line } from '@zealy/icons';\nimport { useCurrentSprint } from '@zealy/queries';\n\nimport { useIsMobile } from '#hooks/useIsMobile';\n\nimport { useQuestboardContext } from './QuestboardContext';\n\nexport const MobileTabs = () => {\n const t = useTranslations();\n const isMobile = useIsMobile();\n const sprint = useCurrentSprint();\n const { sprintView, setSprintView } = useQuestboardContext();\n\n if (!isMobile || !sprint.data) return null;\n\n return (\n \n setSprintView(false)}\n />\n\n }\n value=\"sprint\"\n as=\"button\"\n selected={sprintView}\n label={t('sidebar.items.sprints')}\n onClick={() => setSprintView(true)}\n />\n \n );\n};\n","'use client';\n\nimport React from 'react';\nimport { useTranslations } from 'next-intl';\nimport Link from 'next/link';\n\nimport { Button, Icon } from '@zealy/design-system';\nimport { InfoLine } from '@zealy/icons';\nimport { CommunityMember, useAuthenticatedUser, useSubscriptionStatus } from '@zealy/queries';\nimport { dayjs, roleIsAtLeast } from '@zealy/utils';\n\nexport const SubscriptionLimitReachedBanner = ({ subdomain }: { subdomain: string }) => {\n const user = useAuthenticatedUser();\n const t = useTranslations('questboard.limit-reached');\n\n const subscriptionStatus = useSubscriptionStatus({\n enabled: !!user.data?.role,\n });\n\n const baseHref = `/cw/${subdomain}/settings/plans`;\n\n // When there is no role, we show the join community banner already\n if (!user.data?.role || !subscriptionStatus.data || !subscriptionStatus.data.isLimitReached)\n return null;\n\n return (\n \n
\n
} className=\"invisible lg:visible\" />\n
\n {roleIsAtLeast(user.data.role, 'admin')\n ? t('admin.label')\n : t('user.label', {\n days: dayjs(subscriptionStatus.data?.resetAt).diff(dayjs(), 'days'),\n })}\n
\n
\n
\n {roleIsAtLeast(user.data.role, 'admin') && (\n \n {t('admin.upgrade')}\n \n // Add the notify CTA once the feature is ready\n )}\n
\n
\n );\n};\n","'use client';\n\nimport React from 'react';\n\nimport { useNavigationHistory } from '#context/NavigationHistory';\nimport { usePersistedScrollPosition } from '#hooks/usePersistedScrollPosition';\n\nimport { BoardHeader } from './_components/BoardHeader';\nimport { StatusFilters } from './_components/BoardHeader/StatusFilters';\nimport { BoardView } from './_components/BoardView';\nimport { MobileTabs } from './_components/MobileTabs';\nimport { SubscriptionLimitReachedBanner } from './admin/_components/SubscriptionLimitReachedBanner';\nimport { AdminPreviewBanner } from './AdminPreviewBanner';\n\ntype CommunityLayoutProps = {\n params: {\n subdomain: string;\n };\n};\n\nexport default function QuestBoardPage({ params: { subdomain } }: CommunityLayoutProps) {\n const { previousRoute } = useNavigationHistory();\n\n const shouldRecoverScroll =\n previousRoute?.includes(`/cw/${subdomain}/questboard/`) && !previousRoute?.includes('/admin');\n\n const { setScrollRef } = usePersistedScrollPosition(\n 'questboard:scrollTop',\n shouldRecoverScroll,\n );\n\n return (\n \n
\n\n
\n\n
\n
\n \n \n \n
\n
\n );\n}\n","'use client';\n\nimport React, { useEffect } from 'react';\nimport { usePrevious } from 'react-use';\nimport { usePathname, useSearchParams } from 'next/navigation';\n\nimport { createContext } from '../createContext';\n\ntype NavigationHistoryContext = { previousRoute: string | null };\n\nconst defaultValue = { previousRoute: null };\n\nconst [NavigationHistoryContextProvider, useNavigationHistory] =\n createContext({\n name: 'NavigationHistoryProvider',\n hookName: 'useNavigationHistory',\n defaultValue,\n });\n\nexport const NavigationHistoryProvider = ({ children }: { children: React.ReactNode }) => {\n const [currentRoute, setCurrentRoute] = React.useState(null);\n const previousRoute = usePrevious(currentRoute) ?? null;\n\n const pathname = usePathname();\n const searchParams = useSearchParams().toString();\n\n useEffect(() => {\n const route = `${pathname}${searchParams ? `?${searchParams}` : ''}`;\n setCurrentRoute(route);\n }, [pathname, searchParams, previousRoute]);\n\n return (\n \n {children}\n \n );\n};\n\nexport { useNavigationHistory };\n","export * from './NavigationHistory';\n","'use client';\n\nimport { useCallback, useMemo } from 'react';\n\nimport { useQuestBoard } from '@zealy/queries';\n\nimport { getQuestStatus } from '#components/QuestCard/components/QuestStatus/QuestStatus.utils';\n\nexport const useNextQuest = (moduleId?: string, questId?: string) => {\n const { data } = useQuestBoard();\n\n const getNextQuest = useCallback(\n (moduleIndex: number, questPosition: number) => {\n const questModule = data?.at(moduleIndex);\n if (!questModule) {\n return undefined;\n }\n\n const nextQuest = questModule.quests?.find(q => q.position > questPosition);\n if (!nextQuest) {\n return getNextQuest(moduleIndex + 1, 0);\n }\n\n const status = getQuestStatus(nextQuest, false);\n\n if (!status.canClaim) {\n return getNextQuest(moduleIndex, nextQuest.position);\n }\n\n return nextQuest;\n },\n [data],\n );\n\n return useMemo(() => {\n if (!moduleId || !data) return undefined;\n\n const currentModuleIndex = data?.findIndex(m => m.id === moduleId) ?? -1;\n\n if (currentModuleIndex === -1) return undefined;\n\n const currentQuest = data.at(currentModuleIndex)?.quests.find(q => q.id === questId);\n\n const currentQuestPosition = currentQuest?.position ?? 0;\n\n return getNextQuest(currentModuleIndex, currentQuestPosition);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [moduleId, questId, getNextQuest]);\n};\n","import { useSessionStorageValue } from '@react-hookz/web';\nimport { useCallback, useEffect, useState } from 'react';\nimport debounce from 'lodash.debounce';\n\nexport const usePersistedScrollPosition = (key: string, recover = true) => {\n const {\n value: scrollPosition,\n set: setScrollPosition,\n remove: removeScrollPosition,\n } = useSessionStorageValue(key);\n\n const [initialScrollPosition] = useState(scrollPosition);\n\n const [scrollElement, setScrollElement] = useState(null);\n\n const setScrollRef = useCallback((node: T) => {\n if (node !== null) {\n setScrollElement(node);\n }\n }, []);\n\n useEffect(() => {\n if (!scrollElement) return;\n\n if (recover) {\n scrollElement.scrollTop = initialScrollPosition ?? 0;\n } else {\n removeScrollPosition();\n }\n\n const scrollHandler = debounce(() => {\n setScrollPosition(scrollElement.scrollTop ?? 0);\n }, 200);\n\n scrollElement.addEventListener('scroll', scrollHandler);\n\n return () => {\n scrollElement.removeEventListener('scroll', scrollHandler);\n };\n }, [scrollElement, recover, initialScrollPosition, removeScrollPosition, setScrollPosition]);\n\n return { setScrollRef };\n};\n"],"names":["Promise","resolve","then","__webpack_require__","bind","NAN","reTrim","reIsBadHex","reIsBinary","reIsOctal","freeParseInt","parseInt","freeGlobal","g","Object","freeSelf","self","root","Function","objectToString","objectProto","prototype","toString","nativeMax","Math","max","nativeMin","min","now","Date","isObject","value","type","toNumber","isObjectLike","call","other","valueOf","replace","isBinary","test","slice","module","exports","func","wait","options","lastArgs","lastThis","maxWait","result","timerId","lastCallTime","lastInvokeTime","leading","maxing","trailing","invokeFunc","time","args","thisArg","undefined","apply","shouldInvoke","timeSinceLastCall","timeSinceLastInvoke","timerExpired","trailingEdge","setTimeout","debounced","isInvoking","arguments","cancel","clearTimeout","flush","usePrevious","state","ref","useRef","useEffect","current","esm_useEffectOnce","effect","esm_useUnmount","fn","fnRef","useEffectOnce","esm_useRafState","initialState","frame","_a","useState","setState","setRafState","useCallback","cancelAnimationFrame","requestAnimationFrame","useUnmount","esm_useScroll","useRafState","x","y","handler","scrollLeft","scrollTop","on","capture","passive","off","Elements5Filled","withBaseIcon","_jsx","width","height","viewBox","fill","role","children","d","props","iconName","displayName","Grid04Filled","fillRule","clipRule","FilterSkeleton","jsx_runtime","jsx","Fragment","Array","map","_","i","Skeleton","className","BoardHeader","scrollRef","t","useTranslations","useScroll","gridView","setGridView","filters","useQuestboardContext","questboard","useQuestBoard","didMount","useDidMount","scrollRight","scrollBy","left","behavior","jsxs","div","Button","onClick","aria-label","variant","onlyIcon","size","leftIcon","ArrowLeftLine","isLoading","CategoryFilters","Number","scrollWidth","clientWidth","ArrowRightLine","TooltipProvider","StatusFilters","IconButton","icon","tooltip","clsx","mutedText","useSessionRecorder","envConfig","env","datadogRum","startSessionReplayRecording","GridView","isSubscriptionLimitReached","param","hasOneColumn","canUseDOM","window","matchMedia","matches","showInFirstColumn","index","length","category","Module","id","moduleHeaderStyle","backgroundImage","minHeight","fallbackHeaderStyle","ModuleHeader","defaultTheme","description","style","backgroundPosition","concat","coverVerticalPosition","cn","p","span","title","status","Badge","CheckCircleFilled","SnowflakeLine","totalQuestCount","ProgressBar","claimedQuestCount","fillShadowBg","ModuleSection","isXl","pathname","usePathname","baseUrl","substring","indexOf","shouldUseFallback","coverUrl","color","fallbackTheme","DEFAULT_THEME","position","coverUrlXL","src","useMemo","hexToRgbString","quests","quest","QuestCard","href","SectionView","indexLockedStart","findIndex","indexCompletedStart","React","BoardSkeleton","Skeletons","ModuleList","totalOfSkeletons","BoardView","data","subscriptionData","useSubscriptionStatus","modules","filteredQuestboard","useFilteredQuestboard","customFilters","EmptyState","isLimitReached","view","tS","moduleId","questId","useParams","nextQuest","useNextQuest","baseURL","useCommunityURL","nextQuestURL","categoryId","setSelectedCategories","clearFilters","react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__","Image","alt","as","Link","rich","b","count","MobileTabs","isMobile","useIsMobile","sprint","useCurrentSprint","sprintView","setSprintView","Tabs","orientation","TabItem","selected","label","Timer2Line","SubscriptionLimitReachedBanner","user","subscriptionStatus","subdomain","useAuthenticatedUser","enabled","Icon","InfoLine","roleIsAtLeast","days","dayjs","resetAt","diff","QuestBoardPage","params","previousRoute","useNavigationHistory","shouldRecoverScroll","includes","setScrollRef","usePersistedScrollPosition","AdminPreviewBanner","NavigationHistoryContextProvider","createContext","name","hookName","defaultValue","NavigationHistoryProvider","currentRoute","setCurrentRoute","searchParams","useSearchParams","getNextQuest","moduleIndex","questPosition","questModule","at","find","q","getQuestStatus","canClaim","currentQuest","currentModuleIndex","m","key","recover","scrollPosition","set","setScrollPosition","remove","removeScrollPosition","useSessionStorageValue","initialScrollPosition","scrollElement","setScrollElement","node","scrollHandler","debounce","addEventListener","removeEventListener"],"sourceRoot":""}