├── .cursor └── rules │ └── zeitgeist.mdc ├── .env.development ├── .env.example ├── .env.production ├── .github └── workflows │ ├── codeql.yml │ ├── playwright.yml │ ├── sync-staging-branches.yml │ └── test.yml ├── .gitignore ├── .prettierrc.js ├── .vscode └── settings.json ├── .yarn └── releases │ ├── yarn-3.2.1.cjs │ └── yarn-3.2.2.cjs ├── .yarnrc.yml ├── CLAUDE.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── breakpoints.js ├── components ├── account │ ├── Account.tsx │ ├── AccountButton.tsx │ ├── AccountModalContent.tsx │ ├── AccountModalHead.tsx │ ├── AccountSelect.tsx │ ├── AccountSelectOption.tsx │ ├── AccountSelectValue.tsx │ ├── OnboardingModal.tsx │ ├── WalletIcon.tsx │ └── WalletSelect.tsx ├── assets │ └── AssetActionButtons │ │ ├── AssetTradingButtons.tsx │ │ ├── DisputeButton.tsx │ │ ├── PoolShareButtons.tsx │ │ ├── RedeemButton.tsx │ │ ├── ReportButton.tsx │ │ └── index.tsx ├── confirmation │ └── ConfirmationProvider.tsx ├── context │ └── ContentDimensionsContext.tsx ├── court │ ├── CourtAppealForm.tsx │ ├── CourtCasesTable.tsx │ ├── CourtExitButton.tsx │ ├── CourtReassignForm.tsx │ ├── CourtStageTimer.tsx │ ├── CourtUnstakeButton.tsx │ ├── CourtVoteForm.tsx │ ├── CourtVoteRevealForm.tsx │ ├── DelegateButton.tsx │ ├── JoinCourtAsJurorButton.tsx │ ├── JurorsTable.tsx │ ├── ManageDelegationButton.tsx │ ├── ManageDelegationsForm.tsx │ ├── SelectedDrawsTable.tsx │ └── learn │ │ └── CourtDocsArticle.tsx ├── create │ └── editor │ │ ├── Editor.tsx │ │ ├── ErrorMessage.tsx │ │ ├── MarketFormSection.tsx │ │ ├── Publishing.tsx │ │ ├── ResetButton.tsx │ │ ├── Summary.tsx │ │ ├── inputs │ │ ├── BlockPeriod.tsx │ │ ├── Category.tsx │ │ ├── Currency.tsx │ │ ├── DateTime.tsx │ │ ├── FeeSelect.tsx │ │ ├── Liquidity.tsx │ │ ├── Moderation.tsx │ │ ├── Oracle.tsx │ │ ├── TimezoneSelect.tsx │ │ └── answers │ │ │ ├── Categorical.tsx │ │ │ ├── Scalar.tsx │ │ │ └── index.tsx │ │ └── types.ts ├── devtools.tsx ├── front-page │ ├── BgBallFx.tsx │ ├── GettingStartedSection.tsx │ ├── HeroBanner.tsx │ ├── LatestTrades.tsx │ ├── LatestTradesCompact.tsx │ ├── NetworkStats.tsx │ ├── News.tsx │ ├── PopularCategories.tsx │ ├── Topics.tsx │ ├── TrendingMarketsCompact.tsx │ └── WatchHow.tsx ├── grillchat │ └── index.tsx ├── hero-slider │ ├── HeroControls.tsx │ ├── HeroSlide.tsx │ ├── HeroSlider.module.css │ └── HeroSlider.tsx ├── icons │ ├── DiscordIcon.tsx │ ├── SubIdIcon.tsx │ ├── SubScanIcon.tsx │ ├── TwitterIcon.tsx │ ├── ZeitgeistIcon.tsx │ └── ZeitgeistIconDark.tsx ├── liquidity │ ├── ExitPoolFormAmm2.tsx │ ├── JoinPoolFormAmm2.tsx │ ├── LiquidityModalAmm2.tsx │ ├── MarketLiquiditySection.tsx │ ├── PoolFeesSelect.tsx │ ├── PoolSettings.tsx │ ├── PoolSettingsAMM2.tsx │ └── PoolTable.tsx ├── markets │ ├── BuyFullSetForm.tsx │ ├── BuySellFullSetsButton.tsx │ ├── DisputeResult.tsx │ ├── FavoriteMarketsList.tsx │ ├── MarketAddresses.tsx │ ├── MarketAssetDetails.tsx │ ├── MarketChart.tsx │ ├── MarketContextActionOutcomeSelector.tsx │ ├── MarketDescription.tsx │ ├── MarketFavoriteToggle.tsx │ ├── MarketHeader.tsx │ ├── MarketScroll.tsx │ ├── MarketSearch.tsx │ ├── MarketTimer.tsx │ ├── MarketsList.tsx │ ├── PoolDeployer.tsx │ ├── PromotionCallout.tsx │ ├── ReportResult.tsx │ ├── ScalarPriceRange.tsx │ ├── SellFullSetForm.tsx │ ├── SimilarMarketsSection.tsx │ ├── TradeResult.tsx │ ├── market-card │ │ ├── context.ts │ │ └── index.tsx │ └── market-filter │ │ ├── ClearAllButton.tsx │ │ ├── DropDownSelect.tsx │ │ ├── MarketActiveFilters.tsx │ │ ├── MarketFiltersCheckboxes.tsx │ │ ├── MarketFiltersContainer.tsx │ │ ├── MarketFiltersDropdowns.tsx │ │ ├── MarketFiltersSort.tsx │ │ ├── index.tsx │ │ └── mobile-dialog │ │ ├── FilterDetails.tsx │ │ ├── FiltersList.tsx │ │ └── index.tsx ├── meta │ ├── MarketMeta.tsx │ └── OgHead.tsx ├── onboarding │ ├── DisclaimerModal.tsx │ ├── DisclaimerTerms.tsx │ └── Onboarding.tsx ├── orderbook │ └── OrdersTable.tsx ├── outcomes │ ├── CategoricalDisputeBox.tsx │ ├── CategoricalReportBox.tsx │ ├── ScalarDisputeBox.tsx │ └── ScalarReportBox.tsx ├── portfolio │ ├── AccountPoolsTable.tsx │ ├── BondsTable.tsx │ ├── Breakdown.tsx │ ├── CourtRewardsTable.tsx │ ├── CourtTabGroup.tsx │ ├── CreatorFeePayouts.tsx │ ├── CurrenciesTable.tsx │ ├── DepositButton.tsx │ ├── EmptyPortfolio.tsx │ ├── HistoryTabGroup.tsx │ ├── MarketPositionHeader.tsx │ ├── MarketPositions.tsx │ ├── PortfolioIdentity.tsx │ ├── TradeHistoryTable.tsx │ ├── TransactionHistoryTable.tsx │ ├── Transfer.tsx │ ├── TransferButton.tsx │ └── WithdrawButton.tsx ├── settings │ ├── AccountSettingsForm.tsx │ ├── FeePayingAssetSelect.tsx │ ├── OtherSettingsForm.tsx │ └── SettingsModal.tsx ├── top-bar │ ├── Alerts.tsx │ ├── MenuItem.tsx │ ├── MenuLogo.tsx │ ├── Navigation.tsx │ ├── index.tsx │ └── navigation-items.ts ├── trade-form │ ├── Amm2TradeForm.tsx │ ├── BuyForm.tsx │ ├── LimitOrderForm.tsx │ ├── SellForm.tsx │ ├── TradeTab.tsx │ └── index.tsx ├── twitch │ └── TwitchPlayer.tsx ├── ui │ ├── AddressInput.tsx │ ├── AssetInput.tsx │ ├── AssetSelect.tsx │ ├── Avatar.tsx │ ├── Carousel.tsx │ ├── CopyIcon.tsx │ ├── Footer.tsx │ ├── FormTransactionButton.tsx │ ├── HorizontalScroll.tsx │ ├── InfoPopover.tsx │ ├── Input.tsx │ ├── Loader.tsx │ ├── MarketImage.tsx │ ├── Modal.tsx │ ├── NotificationCenter.tsx │ ├── Paginator.tsx │ ├── PercentageChange.tsx │ ├── Pill.tsx │ ├── QrCode.tsx │ ├── QuillEditor.tsx │ ├── QuillViewer.tsx │ ├── RangeInput.tsx │ ├── ReferendumSummary.tsx │ ├── SecondaryButton.tsx │ ├── Skeleton.tsx │ ├── SubTabsList.tsx │ ├── TabGroup.tsx │ ├── Table.tsx │ ├── TableChart.tsx │ ├── TimeFilters.tsx │ ├── TimeSeriesChart.tsx │ ├── Toggle.tsx │ ├── TransactionButton.tsx │ ├── TruncatedText.tsx │ ├── TypingIndicator.tsx │ ├── actionable │ │ ├── ActionableCard.tsx │ │ └── cards │ │ │ ├── CreateAccount.tsx │ │ │ ├── Deposit.tsx │ │ │ └── StartTrading.tsx │ └── inputs.tsx ├── web3wallet │ └── index.tsx └── wizard │ ├── WizardStepper.tsx │ └── types.ts ├── declarations.d.ts ├── docker-compose.yml ├── e2e ├── errors.spec.ts ├── index.spec.ts ├── index.spec.ts-snapshots │ └── learnSection-chromium-linux.png └── lib │ ├── index.page.ts │ └── test.ts ├── layouts ├── DefaultLayout.tsx ├── PortfolioLayout.tsx └── types.ts ├── lib ├── cms │ ├── featured-markets.ts │ ├── get-promoted-markets.ts │ ├── markets.ts │ ├── news.ts │ ├── sanity │ │ └── index.ts │ └── topics.ts ├── constants │ ├── breakpoints.ts │ ├── category-images.ts │ ├── chains.ts │ ├── foreign-asset.ts │ ├── index.ts │ ├── market-filter.ts │ ├── markets.ts │ ├── supported-currencies.ts │ └── whitelisted-trusted-creators.ts ├── gql │ ├── constants.ts │ ├── display-name.ts │ ├── featured-markets.ts │ ├── get-network-stats.ts │ ├── historical-prices.ts │ ├── market-header.ts │ ├── market-history.ts │ ├── markets-stats.ts │ ├── markets.ts │ ├── popular-categories.ts │ ├── resolution-date.ts │ └── trending-markets.ts ├── hooks │ ├── animation │ │ ├── useParallax.ts │ │ └── useTypedText.ts │ ├── events │ │ ├── useGlobalKeyPress.ts │ │ ├── useHasMounted.ts │ │ ├── useHover.ts │ │ ├── useRelativeMousePosition.ts │ │ └── useWindowSize.ts │ ├── index.ts │ ├── queries │ │ ├── amm2 │ │ │ └── useAmm2Pool.ts │ │ ├── cms │ │ │ └── useMarketCmsMetadata.ts │ │ ├── constants.ts │ │ ├── court │ │ │ ├── useCaseMarketId.ts │ │ │ ├── useConnectedCourtParticipant.ts │ │ │ ├── useCourtCases.ts │ │ │ ├── useCourtParticipants.ts │ │ │ ├── useCourtStakeSharePercentage.ts │ │ │ ├── useCourtTotalStakedAmount.ts │ │ │ ├── useCourtVoteDraws.ts │ │ │ ├── useCourtYearlyInflation.ts │ │ │ └── useMarketCaseId.ts │ │ ├── orderbook │ │ │ ├── useConnectedAddressOrders.ts │ │ │ ├── useOrders.ts │ │ │ └── useRpcOrders.ts │ │ ├── polkadot │ │ │ └── usePolkadotReferendumVotes.ts │ │ ├── useAccountAmm2Pools.ts │ │ ├── useAccountAssetBalances.ts │ │ ├── useAccountBonds.ts │ │ ├── useAccountPoolAssetBalances.ts │ │ ├── useAccountTokenPositions.ts │ │ ├── useAmm2MarketSpotPrices.ts │ │ ├── useAssetMetadata.ts │ │ ├── useAssetUsdPrice.ts │ │ ├── useBalance.ts │ │ ├── useBalances.ts │ │ ├── useCategoryCounts.ts │ │ ├── useChainConstants.ts │ │ ├── useCourtNextPayout.ts │ │ ├── useCourtReassignments.ts │ │ ├── useCreatorFeePayouts.ts │ │ ├── useCurrencyBalances.ts │ │ ├── useExtrinsicFee.ts │ │ ├── useFavoriteMarkets.ts │ │ ├── useFeePayingAsset.ts │ │ ├── useForeignAssetBalances.ts │ │ ├── useIdentities.ts │ │ ├── useIdentity.ts │ │ ├── useInfiniteMarkets.ts │ │ ├── useLatestTrades.tsx │ │ ├── useMarket.ts │ │ ├── useMarket24hrPriceChanges.ts │ │ ├── useMarketDeadlineConstants.ts │ │ ├── useMarketDisputes.ts │ │ ├── useMarketEventHistory.ts │ │ ├── useMarketIsTradingEnabled.ts │ │ ├── useMarketPoolId.ts │ │ ├── useMarketPriceHistory.ts │ │ ├── useMarketSearch.ts │ │ ├── useMarketSpotPrices.ts │ │ ├── useMarketStage.ts │ │ ├── useMarketsByIds.ts │ │ ├── useMarketsStats.ts │ │ ├── useMintedInCourt.ts │ │ ├── usePool.ts │ │ ├── usePoolAccountIds.ts │ │ ├── usePoolBaseBalance.ts │ │ ├── usePoolLiquidity.ts │ │ ├── usePoolsByIds.ts │ │ ├── usePortfolioPositions.ts │ │ ├── useReadyToReportMarkets.ts │ │ ├── useRecommendedMarkets.ts │ │ ├── useRedeemableMarkets.ts │ │ ├── useRpcMarket.ts │ │ ├── useSaturatedMarket.ts │ │ ├── useTotalIssuanceForPools.ts │ │ ├── useTradeHistory.ts │ │ ├── useTradeItemState.ts │ │ ├── useTransactionHistory.ts │ │ ├── useZtgBalance.ts │ │ └── useZtgPrice.ts │ ├── slides.tsx │ ├── trade.tsx │ ├── useCrossChainExtrinsic.ts │ ├── useExtrinsic.ts │ ├── useLocalStorage.ts │ ├── useMarketImage.ts │ ├── useMarketsUrlQuery.ts │ ├── usePrevious.ts │ ├── useQueryParamState.ts │ ├── useSdkv2.ts │ ├── useSubscribeBlockEvents.ts │ ├── useUserLocation.ts │ └── useWeb3Wallet.ts ├── math.spec.ts ├── math.ts ├── query-client.ts ├── state │ ├── account.tsx │ ├── alerts │ │ ├── index.ts │ │ ├── types.ts │ │ └── useAlerts.ts │ ├── chaintime.ts │ ├── confirm-modal │ │ └── useConfirmation.ts │ ├── court │ │ ├── CourtCaseJurorCompositeId.ts │ │ ├── CourtSaltPhraseStorage.ts │ │ ├── get-stage.ts │ │ ├── types.ts │ │ ├── useCourtBacklog.ts │ │ ├── useCourtCommitmentHash.ts │ │ ├── useCourtSalt.tsx │ │ ├── useCourtStage.ts │ │ ├── useOutcomeMatchingCommitmentHash.ts │ │ └── useVoteOutcome.ts │ ├── cross-chain.ts │ ├── delay-queue.ts │ ├── disclaimer.ts │ ├── favorites │ │ └── index.ts │ ├── fee-paying-asset.ts │ ├── market-creation │ │ ├── constants │ │ │ ├── currency.ts │ │ │ ├── deadline-options.ts │ │ │ └── swap-fee.ts │ │ ├── editor.ts │ │ ├── types │ │ │ ├── draft.ts │ │ │ ├── fieldstate.ts │ │ │ ├── form.ts │ │ │ ├── step.ts │ │ │ ├── timeline.ts │ │ │ └── validation.ts │ │ └── util │ │ │ └── tickers.ts │ ├── notifications.ts │ ├── onboarding.ts │ ├── polkadot-api.ts │ ├── promotions.ts │ ├── util │ │ ├── persistent-atom.ts │ │ └── web3auth-config.ts │ ├── wallet-connect.ts │ └── wallet.tsx ├── twitch │ └── index.ts ├── types │ ├── create-market.ts │ ├── deep-partial.ts │ ├── deep-readonly.ts │ ├── index.ts │ ├── market-filter.ts │ ├── markets.ts │ ├── union.ts │ └── user-identity.ts └── util │ ├── amm2.spec.ts │ ├── amm2.ts │ ├── assets-are-equal.ts │ ├── assets.ts │ ├── await-indexer.ts │ ├── calc-free-balance.spec.ts │ ├── calc-free-balance.ts │ ├── calc-price-history-start.ts │ ├── calc-resolved-market-prices.ts │ ├── calc-scalar-winnings.spec.ts │ ├── calc-scalar-winnings.ts │ ├── calculate-restrictive-pool-asset.spec.ts │ ├── calculate-restrictive-pool-asset.ts │ ├── color-calc.spec.ts │ ├── color-calc.ts │ ├── convert-decimals.spec.ts │ ├── convert-decimals.ts │ ├── count-decimals.ts │ ├── court │ └── calculateSlashableStake.ts │ ├── create-vote-commitment-hash.spec.ts │ ├── create-vote-commitment-hash.ts │ ├── delay.ts │ ├── download.ts │ ├── estimate-market-resolution.ts │ ├── fetch-all-pages.spec.ts │ ├── fetch-all-pages.ts │ ├── fonts.ts │ ├── format-compact.ts │ ├── format-scalar-outcome.ts │ ├── generate-guid.ts │ ├── get-api-at.ts │ ├── get-query-params.ts │ ├── getPlaiceHolders.ts │ ├── hasDatePassed.ts │ ├── index.ts │ ├── is-amm2-market.ts │ ├── is-current-origin.ts │ ├── lookup-price.ts │ ├── market-filter.ts │ ├── market-status-details.ts │ ├── market.spec.ts │ ├── market.ts │ ├── order-selection.spec.ts │ ├── order-selection.ts │ ├── parse-asset-id.ts │ ├── perbill-to-number.ts │ ├── poll.spec.ts │ ├── poll.ts │ ├── tx.ts │ ├── unsub-or-warns.ts │ ├── wallet-connect-signer.ts │ ├── weight-math.spec.ts │ └── weight-math.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── 404.tsx ├── _app.tsx ├── _document.tsx ├── activity.tsx ├── api │ ├── cms │ │ └── market-metadata │ │ │ └── batch │ │ │ └── index.ts │ ├── ipfs │ │ ├── index.ts │ │ └── types.ts │ ├── location.ts │ ├── og │ │ ├── [marketId].ts │ │ └── generate.tsx │ ├── onboardUser.ts │ ├── revalidate.ts │ └── usd-price.ts ├── avatar │ ├── [address].tsx │ └── index.tsx ├── claim.tsx ├── court │ ├── [caseid].tsx │ └── index.tsx ├── create-account.tsx ├── create.tsx ├── deposit.tsx ├── index.tsx ├── latest-trades.tsx ├── leaderboard │ └── [period].tsx ├── liquidity │ └── [poolid].tsx ├── markets │ ├── [marketid].tsx │ ├── await │ │ └── [marketid].tsx │ ├── favorites.tsx │ └── index.tsx ├── portfolio │ ├── [address].tsx │ └── index.tsx ├── search.tsx └── topics │ └── [topic].tsx ├── playwright.config.ts ├── postcss.config.js ├── public ├── Leaderboard-banner.png ├── Revised_Logo.svg ├── Zeitgeist-trans.png ├── airdrop.json ├── airdrop.svg ├── android-chrome-192x192.png ├── android-chrome-256x256.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── avatar_preview.jpeg ├── banner.png ├── browserconfig.xml ├── carousel │ ├── banner.png │ └── intro_zeitgeist_avatar.png ├── categories │ ├── crypto │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ ├── dotsama │ │ └── 1.png │ ├── entertainment │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ ├── esports │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ ├── finance │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ ├── news │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ ├── politics │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ ├── science │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ ├── sports │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ ├── tech │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ └── zeitgeist │ │ └── 1.png ├── category │ ├── crypto.png │ ├── dotsama.png │ ├── entertainment.png │ ├── finance.png │ ├── news.png │ ├── politics.png │ ├── science.png │ ├── sports.png │ ├── technology.png │ └── zeitgeist.png ├── court.png ├── court_banner.png ├── court_gnomes.png ├── crypto_wizard.png ├── currencies │ ├── assethub.svg │ ├── ausd.jpg │ ├── dot.png │ ├── dot_filled.png │ ├── dot_filled_black.png │ ├── moonbeam.png │ ├── rococo.png │ ├── rococo.svg │ ├── usdc.svg │ ├── usdt.png │ ├── ztg.jpg │ ├── ztg.png │ ├── ztg.svg │ └── ztg_neue.png ├── dark-404.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── featured │ ├── Kanaria_NFT.png │ ├── Kusama.png │ └── Polkadot.png ├── fonts │ └── inter │ │ ├── static │ │ ├── Inter-Black.ttf │ │ ├── Inter-Bold.ttf │ │ ├── Inter-ExtraBold.ttf │ │ ├── Inter-ExtraLight.ttf │ │ ├── Inter-Light.ttf │ │ ├── Inter-Medium.ttf │ │ ├── Inter-Regular.ttf │ │ ├── Inter-SemiBold.ttf │ │ └── Inter-Thin.ttf │ │ └── variable.ttf ├── horse_rider.svg ├── icons │ ├── ZTG.svg │ ├── acc-balance.svg │ ├── court.svg │ ├── default-market.png │ ├── discord.svg │ ├── facebook-f.svg │ ├── google-g.svg │ ├── lock.svg │ ├── new-moon.svg │ ├── nova.png │ ├── polkadot-js.png │ ├── polkassembly.svg │ ├── singular.svg │ ├── subwallet.png │ ├── talisman.png │ ├── telegram.svg │ ├── unlock.svg │ ├── usdc-icon.svg │ ├── verified-icon.svg │ ├── walletconnect-icon.svg │ └── x-logo.svg ├── learn │ ├── create_account.png │ ├── deposit.png │ ├── learn-1.png │ ├── learn-2.png │ ├── learn-3.png │ └── start_trading.png ├── light-404.png ├── misc │ └── portal_gate.png ├── moon.svg ├── mstile-150x150.png ├── nft │ ├── circles-background.png │ └── ellipse-background.png ├── og │ ├── bg1.png │ └── zeitgeist_badge.png ├── polkadot_icon.png ├── prices │ ├── dot.json │ ├── usdt.json │ └── ztg.json ├── safari-pinned-tab.svg ├── singular.png ├── site.webmanifest ├── sun.svg ├── support.png ├── web3auth.svg └── ztg_8.svg ├── scripts ├── extractMarketImages.ts ├── mts │ ├── getSpotPrices.mts │ └── tsconfig.json └── tsconfig.json ├── styles ├── card.css ├── date-picker.css ├── drawer.css ├── index.css ├── kusama-derby.css ├── quill.css └── range-component.css ├── tailwind.config.js ├── tsconfig.json ├── vitest.config.ts ├── wsx-build.sh ├── yarn.lock └── ztg-build.sh /.env.production: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_FATHOM_SITE_ID=FATHOM_ANALYTICS_SITE_ID 2 | NEXT_PUBLIC_HOTJAR_SITE_ID=HOTJAR_SITE_ID 3 | NEXT_PUBLIC_MARKET_IMAGE_MAX_KB=100 4 | NEXT_PUBLIC_BLOCK_TIME=12 5 | NEXT_PUBLIC_MARKET_POLL_INTERVAL_MS=120000 6 | 7 | NEXT_PUBLIC_SHOW_COURT=false 8 | 9 | NEXT_PUBLIC_NOT_ALLOWED_COUNTRIES=["US","KP","SY","CU","IR","VE","PR"] 10 | 11 | #NEXT_PUBLIC_NOTIFICATION_MESSAGE="App is currently under maintenance. Please return at 12:00pm UTC." 12 | #NEXT_PUBLIC_FEATURED_MARKET_IDS="[126,128,77]" 13 | 14 | # markets that will not be shown in the app 15 | #NEXT_PUBLIC_HIDDEN_MARKET_IDS=[] 16 | 17 | NEXT_PUBLIC_IPFS_NODE="http://ipfs.zeitgeist.pm:5001" 18 | 19 | NEXT_PUBLIC_VERCEL_ENV=production 20 | NEXT_PUBLIC_SITE_URL=https://app.zeitgeist.pm 21 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: E2E Tests 2 | on: 3 | deployment_status: 4 | jobs: 5 | test-e2e: 6 | name: Playwright tests 7 | if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' 8 | timeout-minutes: 15 9 | runs-on: ubuntu-latest 10 | container: 11 | # Use image version that matches your Playwright version 12 | # (Check version in package.json) 13 | image: mcr.microsoft.com/playwright:v1.39.0-jammy 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: 16 19 | - name: Install dependencies 20 | run: yarn 21 | - name: Run Playwright tests 22 | run: yarn playwright test 23 | env: 24 | PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }} 25 | - uses: actions/upload-artifact@v4 26 | if: always() 27 | with: 28 | name: playwright-report 29 | path: playwright-report/ 30 | retention-days: 30 31 | -------------------------------------------------------------------------------- /.github/workflows/sync-staging-branches.yml: -------------------------------------------------------------------------------- 1 | name: Sync staging to test-staging 2 | 3 | on: 4 | push: 5 | branches: 6 | - staging 7 | 8 | jobs: 9 | sync-branches: 10 | runs-on: ubuntu-latest 11 | name: Syncing branches 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: open-pr 15 | id: open-pr 16 | uses: repo-sync/pull-request@v2 17 | with: 18 | source_branch: "staging" 19 | destination_branch: "test-staging" 20 | pr_title: "Pulling ${{ github.ref }} into test-staging" 21 | pr_label: "automerge" 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | - name: automerge 24 | uses: pascalgn/automerge-action@v0.15.5 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | PULL_REQUEST: ${{ steps.open-pr.outputs.pr_number }} 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | on: 3 | push: 4 | branches: [staging] 5 | pull_request: 6 | branches: [staging] 7 | jobs: 8 | test: 9 | name: Jest 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Install dependencies 14 | run: yarn 15 | - name: Run tests 16 | run: yarn test 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | .yarn/cache 38 | .yarn/install-state.gz 39 | .yarnrc 40 | /test-results/ 41 | /playwright-report/ 42 | /playwright/.cache/ 43 | 44 | #reference 45 | /zeitgeist-subsquid 46 | /zeitgeist-runtime 47 | /zeitgeist-sdk 48 | /reference -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: "all", 3 | tabWidth: 2, 4 | singleQuote: false, 5 | semi: true, 6 | plugins: ["prettier-plugin-tailwindcss"], 7 | }; 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "prettier.configPath": ".prettierrc.js", 4 | "cSpell.enabled": true, 5 | "cSpell.words": ["Excecution"] 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.2.2.cjs 4 | checksumBehavior: update 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | WORKDIR /ui 3 | COPY . . 4 | RUN yarn install 5 | RUN yarn build 6 | EXPOSE 3000 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zeitgeist prediction markets 2 | 3 | ## Install dependencies 4 | 5 | `yarn install` 6 | 7 | ## Setup environment variables 8 | 9 | Some features require environment variables. To test locally create `.env.local` file from `.env.example`. 10 | 11 | `cp .env.example .env.local` 12 | 13 | ## Run development environment 14 | 15 | `yarn dev` 16 | -------------------------------------------------------------------------------- /breakpoints.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sm: 600, 3 | md: 905, 4 | lg: 1240, 5 | xl: 1440, 6 | "2xl": 1728, 7 | }; 8 | -------------------------------------------------------------------------------- /components/account/Account.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog } from "@headlessui/react"; 2 | import Modal from "components/ui/Modal"; 3 | import { useAccountModals } from "lib/state/account"; 4 | import AccountModalContent from "./AccountModalContent"; 5 | import AccountModalHead from "./AccountModalHead"; 6 | import WalletSelect from "./WalletSelect"; 7 | 8 | export const Account = () => { 9 | const { 10 | accountSelectModalOpen, 11 | walletSelectModalOpen, 12 | closeAccountSelect, 13 | closeWalletSelect, 14 | } = useAccountModals(); 15 | 16 | return ( 17 | <> 18 | 19 | 20 |
21 | 22 |
23 | 24 |
25 |
26 | 27 | 28 | 32 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /components/account/AccountModalHead.tsx: -------------------------------------------------------------------------------- 1 | import { useAccountModals } from "lib/state/account"; 2 | 3 | const AccountModalHead = () => { 4 | const accountModals = useAccountModals(); 5 | 6 | const switchExtension = () => { 7 | accountModals.openWalletSelect(); 8 | }; 9 | 10 | return ( 11 |
12 |
Account
13 |
{ 16 | switchExtension(); 17 | }} 18 | > 19 | Switch wallet extension 20 |
21 |
22 | ); 23 | }; 24 | 25 | export default AccountModalHead; 26 | -------------------------------------------------------------------------------- /components/account/AccountSelectOption.tsx: -------------------------------------------------------------------------------- 1 | import Avatar from "components/ui/Avatar"; 2 | import { shortenAddress } from "lib/util"; 3 | 4 | import React, { FC } from "react"; 5 | 6 | export interface AccountSelectRowProps { 7 | name: string; 8 | address: string; 9 | } 10 | 11 | const AccountSelectOption: FC = ({ name, address }) => { 12 | return ( 13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
{name}
21 |
22 | {shortenAddress(address, 12, 12)} 23 |
24 |
25 | {address} 26 |
27 |
28 |
29 | ); 30 | }; 31 | export default AccountSelectOption; 32 | -------------------------------------------------------------------------------- /components/account/WalletIcon.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | interface WalletIconProps { 4 | extensionName: string; 5 | logoAlt: string; 6 | logoSrc: string; 7 | onClick?: () => void; 8 | hasError?: boolean; 9 | error?: any; 10 | className?: string; 11 | } 12 | 13 | const WalletIcon = ({ 14 | logoAlt, 15 | logoSrc, 16 | extensionName, 17 | onClick, 18 | hasError, 19 | error, 20 | className, 21 | }: WalletIconProps) => { 22 | return ( 23 | 44 | ); 45 | }; 46 | 47 | export default WalletIcon; 48 | -------------------------------------------------------------------------------- /components/confirmation/ConfirmationProvider.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog } from "@headlessui/react"; 2 | import Modal from "components/ui/Modal"; 3 | import { useConfirmation } from "lib/state/confirm-modal/useConfirmation"; 4 | 5 | export const ConfirmationProvider = () => { 6 | const { confirmations, confirm, dismiss } = useConfirmation(); 7 | return ( 8 | <> 9 | {Object.entries(confirmations).map(([id, value]) => ( 10 | dismiss(id)}> 11 | 12 |

{value.title}

13 |

{value.description}

14 |
15 | 21 | 27 |
28 |
29 |
30 | ))} 31 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /components/context/ContentDimensionsContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, FC, PropsWithChildren, useContext } from "react"; 2 | 3 | export type ContentDimensions = { 4 | scrollTop?: number; 5 | scrollTo?: (scrollTop: number) => void; 6 | height?: number; 7 | width?: number; 8 | }; 9 | 10 | export const ContentDimensionsContext = createContext( 11 | null, 12 | ); 13 | 14 | export const ContentDimensionsProvider: FC< 15 | PropsWithChildren 16 | > = ({ width, height, scrollTop, scrollTo, children }) => ( 17 | 20 | {children} 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /components/create/editor/ErrorMessage.tsx: -------------------------------------------------------------------------------- 1 | import { Transition } from "@headlessui/react"; 2 | import { FieldState } from "lib/state/market-creation/types/fieldstate"; 3 | 4 | /** 5 | * Displayes a error message when a field has an error and 6 | * has been touched by the user. 7 | * 8 | * @param props.field - the field to display the error message for. 9 | */ 10 | export const ErrorMessage = ({ field }: { field: FieldState }) => { 11 | return ( 12 | 21 | {field?.errors?.[0]?.message} 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /components/create/editor/inputs/TimezoneSelect.tsx: -------------------------------------------------------------------------------- 1 | import moment from "moment-timezone"; 2 | import React from "react"; 3 | import { FormEvent } from "../types"; 4 | 5 | type TimezoneSelectProps = { 6 | name: string; 7 | value?: string; 8 | onChange: (event: FormEvent) => void; 9 | onBlur: (event: FormEvent) => void; 10 | }; 11 | 12 | const defaultTimezone = moment.tz.guess(); 13 | const allTimezones = moment.tz.names(); 14 | 15 | const TimezoneSelect: React.FC = (props) => { 16 | return ( 17 |
18 | 40 |
41 | ); 42 | }; 43 | 44 | export default TimezoneSelect; 45 | -------------------------------------------------------------------------------- /components/create/editor/types.ts: -------------------------------------------------------------------------------- 1 | export type FormEvent = { 2 | target: { name: string; value: T }; 3 | type: "blur" | "change" | "focusout" | string; 4 | }; 5 | -------------------------------------------------------------------------------- /components/devtools.tsx: -------------------------------------------------------------------------------- 1 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 2 | import BatshitDevtools from "@yornaath/batshit-devtools-react"; 3 | import dynamic from "next/dynamic"; 4 | import { Suspense } from "react"; 5 | 6 | const DevTools = () => { 7 | return ( 8 | <> 9 | {process.env.NEXT_PUBLIC_REACT_QUERY_DEVTOOLS === "true" && 10 | typeof window === "object" ? ( 11 | }> 12 | 13 | 14 | 15 | ) : ( 16 | <> 17 | )} 18 | 19 | ); 20 | }; 21 | 22 | export default dynamic(() => Promise.resolve(DevTools), { 23 | ssr: false, 24 | }); 25 | -------------------------------------------------------------------------------- /components/front-page/GettingStartedSection.tsx: -------------------------------------------------------------------------------- 1 | import { CreateAccountActionableCard } from "components/ui/actionable/cards/CreateAccount"; 2 | import { DepositActionableCard } from "components/ui/actionable/cards/Deposit"; 3 | import { StartTradingActionableCard } from "components/ui/actionable/cards/StartTrading"; 4 | 5 | const GettingStartedSection = () => { 6 | return ( 7 | <> 8 |
9 |

Getting Started

10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 | 23 | ); 24 | }; 25 | 26 | export default GettingStartedSection; 27 | -------------------------------------------------------------------------------- /components/front-page/NetworkStats.tsx: -------------------------------------------------------------------------------- 1 | const StatCard = ({ title, value }: { title: string; value: string }) => { 2 | return ( 3 |
4 |
5 |
6 | {value} 7 |
8 |
{title}
9 |
10 |
11 | ); 12 | }; 13 | 14 | const NetworkStats = ({ 15 | tradersCount, 16 | marketCount, 17 | totalVolumeUsd, 18 | }: { 19 | tradersCount: number; 20 | marketCount: number; 21 | totalVolumeUsd: number; 22 | }) => { 23 | return ( 24 |
25 | 31 | 32 | 33 |
34 | ); 35 | }; 36 | 37 | export default NetworkStats; 38 | -------------------------------------------------------------------------------- /components/hero-slider/HeroSlider.module.css: -------------------------------------------------------------------------------- 1 | /* hero slider animation */ 2 | .blurInOut { 3 | animation: blurInOut 333ms; 4 | } 5 | 6 | @keyframes blurInOut { 7 | 0% { 8 | -webkit-filter: blur(0px); 9 | opacity: 0; 10 | } 11 | 50% { 12 | -webkit-filter: blur(5px); 13 | opacity: 1; 14 | } 15 | 100% { 16 | -webkit-filter: blur(0px); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /components/icons/SubIdIcon.tsx: -------------------------------------------------------------------------------- 1 | const SubIdIcon = () => { 2 | return ( 3 | 10 | 16 | 17 | ); 18 | }; 19 | 20 | export default SubIdIcon; 21 | -------------------------------------------------------------------------------- /components/icons/TwitterIcon.tsx: -------------------------------------------------------------------------------- 1 | const TwitterIcon = ({ fill = "black" }: { fill?: string }) => { 2 | return ( 3 | 10 | 14 | 15 | ); 16 | }; 17 | 18 | export default TwitterIcon; 19 | -------------------------------------------------------------------------------- /components/icons/ZeitgeistIconDark.tsx: -------------------------------------------------------------------------------- 1 | const ZeitgeistIconDark = ({ width = 35, height = 35 }) => { 2 | return ( 3 | 10 | 14 | 15 | ); 16 | }; 17 | 18 | export default ZeitgeistIconDark; 19 | -------------------------------------------------------------------------------- /components/markets/DisputeResult.tsx: -------------------------------------------------------------------------------- 1 | import { FullMarketFragment } from "@zeitgeistpm/indexer"; 2 | import { ScalarRangeType } from "@zeitgeistpm/sdk"; 3 | import { TwitterBird } from "components/markets/TradeResult"; 4 | import { 5 | MarketCategoricalOutcome, 6 | MarketScalarOutcome, 7 | displayOutcome, 8 | } from "lib/types"; 9 | import { AiOutlineFileDone } from "react-icons/ai"; 10 | 11 | export const DisputeResult = ({ market }: { market: FullMarketFragment }) => { 12 | const marketUrl = `https://app.zeitgeist.pm/markets/${market.marketId}`; 13 | 14 | const twitterBaseUrl = "https://twitter.com/intent/tweet?text="; 15 | const tweetUrl = `${twitterBaseUrl}I just disputed the outcome of %40ZeitgeistPM market: "${market.question}"%0A%0ACheck out the market here%3A%0A&url=${marketUrl}`; 16 | 17 | return ( 18 |
19 |
20 | 21 |
22 |

Successfully disputed!

23 | 24 | 30 | 31 | 32 |
33 | ); 34 | }; 35 | 36 | export default DisputeResult; 37 | -------------------------------------------------------------------------------- /components/markets/MarketDescription.tsx: -------------------------------------------------------------------------------- 1 | import { PortableText } from "@portabletext/react"; 2 | import { useMarketCmsMetadata } from "lib/hooks/queries/cms/useMarketCmsMetadata"; 3 | import { isArray, isString } from "lodash-es"; 4 | import { FullMarketFragment } from "@zeitgeistpm/indexer"; 5 | import { MarketPageIndexedData } from "lib/gql/markets"; 6 | import dynamic from "next/dynamic"; 7 | 8 | const QuillViewer = dynamic(() => import("components/ui/QuillViewer"), { 9 | ssr: false, 10 | }); 11 | 12 | export const MarketDescription = ({ 13 | market, 14 | }: { 15 | market: FullMarketFragment | MarketPageIndexedData | MarketPageIndexedData; 16 | }) => { 17 | const { data: marketCmsMetadata } = useMarketCmsMetadata(market.marketId); 18 | 19 | const description = marketCmsMetadata?.description ?? market.description; 20 | 21 | return ( 22 | <> 23 | {isArray(description) && description.length ? ( 24 | <> 25 |

About Market

26 | 27 | 28 | ) : ( 29 | isString(description) && 30 | description?.length > 0 && ( 31 | <> 32 |

About Market

33 | 34 | 35 | ) 36 | )} 37 | 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /components/markets/MarketFavoriteToggle.tsx: -------------------------------------------------------------------------------- 1 | import { useFavoriteMarketsStorage } from "lib/state/favorites"; 2 | import { MdFavorite, MdFavoriteBorder } from "react-icons/md"; 3 | 4 | export const MarketFavoriteToggle = ({ 5 | marketId, 6 | size, 7 | }: { 8 | marketId: number; 9 | size?: number; 10 | }) => { 11 | const { add, remove, isFavorite } = useFavoriteMarketsStorage(); 12 | 13 | return ( 14 |
{ 17 | e.preventDefault(); 18 | e.stopPropagation(); 19 | isFavorite(marketId) ? remove(marketId) : add(marketId); 20 | }} 21 | > 22 | {isFavorite(marketId) ? ( 23 | 24 | ) : ( 25 | 26 | )} 27 |
28 | ); 29 | }; 30 | 31 | export default MarketFavoriteToggle; 32 | -------------------------------------------------------------------------------- /components/markets/market-card/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from "react"; 2 | 3 | const MarketCardContext = createContext<{ baseAsset: string }>({ 4 | baseAsset: "", 5 | }); 6 | 7 | export default MarketCardContext; 8 | -------------------------------------------------------------------------------- /components/markets/market-filter/ClearAllButton.tsx: -------------------------------------------------------------------------------- 1 | const ClearAllButton = ({ clear }) => { 2 | return ( 3 | 9 | ); 10 | }; 11 | 12 | export default ClearAllButton; 13 | -------------------------------------------------------------------------------- /components/markets/market-filter/MarketFiltersCheckboxes.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useMarketFiltersContext } from "./MarketFiltersContainer"; 3 | import Input from "components/ui/Input"; 4 | 5 | type MarketFiltersCheckboxesProps = { 6 | className?: string; 7 | }; 8 | 9 | const MarketFiltersCheckboxes: React.FC = ({ 10 | className = "", 11 | }) => { 12 | const { withLiquidityOnly, setWithLiquidityOnly } = useMarketFiltersContext(); 13 | return withLiquidityOnly != null ? ( 14 | 23 | ) : ( 24 | <> 25 | ); 26 | }; 27 | 28 | export default MarketFiltersCheckboxes; 29 | -------------------------------------------------------------------------------- /components/meta/MarketMeta.tsx: -------------------------------------------------------------------------------- 1 | import { IndexerContext, Market } from "@zeitgeistpm/sdk"; 2 | import { MarketPageIndexedData } from "lib/gql/markets"; 3 | import { OgHead } from "./OgHead"; 4 | 5 | export const MarketMeta = ({ 6 | market, 7 | }: { 8 | market: Market | MarketPageIndexedData; 9 | }) => { 10 | return ( 11 | <> 12 | 24 | 25 | ); 26 | }; 27 | 28 | export default MarketMeta; 29 | -------------------------------------------------------------------------------- /components/onboarding/Onboarding.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useState } from "react"; 2 | import { useOnboarding } from "lib/state/onboarding"; 3 | import { DesktopOnboardingModal } from "components/account/OnboardingModal"; 4 | import Modal from "components/ui/Modal"; 5 | 6 | export const Onboarding = () => { 7 | const onboarding = useOnboarding(); 8 | 9 | const step = onboarding.hasWallet ? 4 : 1; 10 | 11 | const walletInstallJustConfirmed = useMemo( 12 | () => onboarding.walletInstallJustConfirmed, 13 | [], 14 | ); 15 | 16 | const [closed, setClosed] = useState(false); 17 | 18 | return ( 19 | { 22 | setClosed(true); 23 | onboarding.setWalletInstallConfirmed(false); 24 | }} 25 | > 26 | 34 | 35 | ); 36 | }; 37 | 38 | export default Onboarding; 39 | -------------------------------------------------------------------------------- /components/portfolio/CourtTabGroup.tsx: -------------------------------------------------------------------------------- 1 | import { Tab } from "@headlessui/react"; 2 | import SubTabsList from "components/ui/SubTabsList"; 3 | import { useQueryParamState } from "lib/hooks/useQueryParamState"; 4 | import CourtRewardsTable from "./CourtRewardsTable"; 5 | 6 | type CourtGroupItem = "Rewards"; 7 | const courtTabItems: CourtGroupItem[] = ["Rewards"]; 8 | 9 | const CourtTabGroup = ({ address }: { address: string }) => { 10 | const [historyTabSelection, setHistoryTabSelection] = 11 | useQueryParamState("courtTab"); 12 | 13 | const courtTabIndex = courtTabItems.indexOf(historyTabSelection); 14 | const selectedIndex = courtTabIndex !== -1 ? courtTabIndex : 0; 15 | 16 | return ( 17 | setHistoryTabSelection(courtTabItems[index])} 21 | > 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default CourtTabGroup; 33 | -------------------------------------------------------------------------------- /components/portfolio/EmptyPortfolio.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Frown } from "react-feather"; 3 | 4 | const EmptyPortfolio = ({ 5 | headerText, 6 | bodyText, 7 | buttonText, 8 | buttonLink, 9 | }: { 10 | headerText: string; 11 | bodyText: string; 12 | buttonText?: string; 13 | buttonLink?: string; 14 | }) => { 15 | return ( 16 |
17 | 18 |
{headerText}
19 |
{bodyText}
20 | {buttonText && buttonLink && ( 21 | 25 | {buttonText} 26 | 27 | )} 28 |
29 | ); 30 | }; 31 | 32 | export default EmptyPortfolio; 33 | -------------------------------------------------------------------------------- /components/portfolio/HistoryTabGroup.tsx: -------------------------------------------------------------------------------- 1 | import { Tab } from "@headlessui/react"; 2 | import SubTabsList from "components/ui/SubTabsList"; 3 | import TradeHistoryTable from "./TradeHistoryTable"; 4 | import TransactionHistoryTable from "./TransactionHistoryTable"; 5 | import { useQueryParamState } from "lib/hooks/useQueryParamState"; 6 | 7 | type HistoryTabItem = "Trades" | "Other Transactions"; 8 | const historyTabItems: HistoryTabItem[] = ["Trades", "Other Transactions"]; 9 | 10 | const HistoryTabGroup = ({ address }: { address: string }) => { 11 | const [historyTabSelection, setHistoryTabSelection] = 12 | useQueryParamState("historyTab"); 13 | 14 | const historyTabIndex = historyTabItems.indexOf(historyTabSelection); 15 | const selectedIndex = historyTabIndex !== -1 ? historyTabIndex : 0; 16 | 17 | return ( 18 | setHistoryTabSelection(historyTabItems[index])} 22 | > 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default HistoryTabGroup; 37 | -------------------------------------------------------------------------------- /components/portfolio/MarketPositionHeader.tsx: -------------------------------------------------------------------------------- 1 | import { IOBaseAssetId, IOForeignAssetId } from "@zeitgeistpm/sdk"; 2 | import { lookupAssetImagePath } from "lib/constants/foreign-asset"; 3 | import { parseAssetIdString } from "lib/util/parse-asset-id"; 4 | import Image from "next/image"; 5 | import Link from "next/link"; 6 | 7 | const MarketPositionHeader = ({ 8 | marketId, 9 | question, 10 | baseAsset, 11 | }: { 12 | marketId: number; 13 | question?: string; 14 | baseAsset: string; 15 | }) => { 16 | const baseAssetId = parseAssetIdString(baseAsset); 17 | const imagePath = lookupAssetImagePath(baseAssetId); 18 | 19 | return ( 20 |

21 | Currency token logo 28 | {question} 29 |

30 | ); 31 | }; 32 | 33 | export default MarketPositionHeader; 34 | -------------------------------------------------------------------------------- /components/portfolio/Transfer.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowRight } from "react-feather"; 2 | import Image from "next/image"; 3 | import { CHAIN_IMAGES } from "lib/constants/chains"; 4 | 5 | const Transfer = ({ 6 | sourceChain, 7 | destinationChain, 8 | }: { 9 | sourceChain: string; 10 | destinationChain: string; 11 | }) => { 12 | return ( 13 |
14 |
15 | {sourceChain} 22 |
{sourceChain}
23 |
24 | 25 |
26 | {destinationChain} 33 |
{destinationChain}
34 |
35 |
36 | ); 37 | }; 38 | 39 | export default Transfer; 40 | -------------------------------------------------------------------------------- /components/top-bar/MenuLogo.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import Logo from "../icons/ZeitgeistIcon"; 3 | 4 | const MenuLogo: FC<{}> = () => { 5 | return ( 6 |
7 | 8 | <> 9 |
10 |

Zeitgeist

11 |
12 | 13 |
14 | ); 15 | }; 16 | 17 | export default MenuLogo; 18 | -------------------------------------------------------------------------------- /components/top-bar/navigation-items.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BarChart2, 3 | Box, 4 | Briefcase, 5 | PlusSquare, 6 | User, 7 | Users, 8 | } from "react-feather"; 9 | 10 | export const NAVIGATION_ITEMS = { 11 | markets: { 12 | label: "Markets", 13 | href: "/markets", 14 | IconComponent: BarChart2, 15 | }, 16 | create: { 17 | label: "Create Market", 18 | IconComponent: PlusSquare, 19 | href: "/create", 20 | }, 21 | portfolio: { 22 | label: "Portfolio", 23 | href: "/portfolio", 24 | IconComponent: Briefcase, 25 | }, 26 | avatar: { 27 | label: "Avatar and Badges", 28 | href: "/avatar", 29 | IconComponent: User, 30 | }, 31 | activity: { 32 | label: "Activity Feed", 33 | IconComponent: Box, 34 | href: "/activity", 35 | }, 36 | court: { 37 | label: "Court", 38 | IconComponent: Users, 39 | href: "/court", 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /components/trade-form/TradeTab.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, PropsWithChildren } from "react"; 2 | 3 | const TradeTab: FC< 4 | PropsWithChildren<{ selected: boolean; className: string }> 5 | > = React.forwardRef( 6 | ( 7 | { children, selected, className, ...rest }, 8 | ref: React.ForwardedRef, 9 | ) => { 10 | const classes = `block font-medium cursor-pointer h-full center w-1/2 outline-0 text-ztg-18-150 transition-all ${ 11 | className ?? "" 12 | } ${ 13 | selected 14 | ? "bg-ztg-blue font-bold text-white" 15 | : "text-pastel-blue bg-[#CCE0F4]" 16 | }`; 17 | return ( 18 |
19 | {children} 20 |
21 | ); 22 | }, 23 | ); 24 | 25 | export enum TradeTabType { 26 | Buy = 0, 27 | Sell = 1, 28 | } 29 | 30 | export default TradeTab; 31 | -------------------------------------------------------------------------------- /components/twitch/TwitchPlayer.tsx: -------------------------------------------------------------------------------- 1 | import { TwitchEmbed, TwitchEmbedProps } from "react-twitch-embed"; 2 | 3 | export const TwitchPlayer = (props: TwitchEmbedProps) => { 4 | return ; 5 | }; 6 | 7 | export default TwitchPlayer; 8 | -------------------------------------------------------------------------------- /components/ui/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import { decodeAddress } from "@polkadot/keyring"; 2 | import BoringAvatar from "boring-avatars"; 3 | 4 | const blues = ["#0001fe", "#a000ff", "#70f8ff"]; 5 | const reds = ["#fb7ce8", "#FF0054", "#FAB400"]; 6 | 7 | const Avatar = ({ 8 | address, 9 | zoomed = false, 10 | size = 30, 11 | deps, 12 | copy = true, 13 | }: { 14 | address: string; 15 | zoomed?: boolean; 16 | size?: number; 17 | deps?: any[]; 18 | copy?: boolean; 19 | }) => { 20 | if (address === "") { 21 | return null; 22 | } 23 | 24 | const decodedAddressArray = Array.from(decodeAddress(address)); 25 | const blue = blues[decodedAddressArray[5] % blues.length]; 26 | const red = reds[decodedAddressArray[6] % reds.length]; 27 | const blueFirst = decodedAddressArray[10] % 2; 28 | 29 | return ( 30 |
39 | 45 |
46 | ); 47 | }; 48 | 49 | export default Avatar; 50 | -------------------------------------------------------------------------------- /components/ui/FormTransactionButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | import TransactionButton from "./TransactionButton"; 3 | 4 | interface TransactionButtonProps { 5 | disabled?: boolean; 6 | className?: string; 7 | dataTest?: string; 8 | disableFeeCheck?: boolean; 9 | type?: "button" | "submit" | "reset"; 10 | loading: boolean | undefined; 11 | } 12 | 13 | const FormTransactionButton: FC> = ({ 14 | disabled = false, 15 | className = "", 16 | dataTest = "", 17 | disableFeeCheck = false, 18 | type = "submit", 19 | children, 20 | loading = undefined, 21 | }) => { 22 | return ( 23 | 31 | {children} 32 | 33 | ); 34 | }; 35 | 36 | export default FormTransactionButton; 37 | -------------------------------------------------------------------------------- /components/ui/Paginator.tsx: -------------------------------------------------------------------------------- 1 | import { Plus } from "react-feather"; 2 | 3 | type PaginatorProps = { 4 | disabled?: boolean; 5 | onPlusClicked?: () => void; 6 | }; 7 | 8 | const Paginator = ({ 9 | disabled = false, 10 | onPlusClicked = () => {}, 11 | }: PaginatorProps) => { 12 | const handlePlusClicked = () => { 13 | onPlusClicked(); 14 | }; 15 | return ( 16 |
17 |
18 |
19 | { 21 | if (!disabled) { 22 | handlePlusClicked(); 23 | } 24 | }} 25 | size={24} 26 | className={ 27 | !disabled ? "cursor-pointer" : "cursor-default opacity-20" 28 | } 29 | /> 30 |
31 |
32 |
38 | Load more 39 |
40 |
41 |
42 |
43 | ); 44 | }; 45 | 46 | export default Paginator; 47 | -------------------------------------------------------------------------------- /components/ui/PercentageChange.tsx: -------------------------------------------------------------------------------- 1 | const PercentageChange = ({ change }: { change: string }) => { 2 | return ( 3 |
4 | {Number(change) > 0 ? ( 5 | 12 | 13 | 14 | ) : ( 15 | <> 16 | )} 17 | 18 | {Number(change) < 0 ? ( 19 | 27 | 28 | 29 | ) : ( 30 | <> 31 | )} 32 | 33 | 34 | {change}% 35 | 36 |
37 | ); 38 | }; 39 | 40 | export default PercentageChange; 41 | -------------------------------------------------------------------------------- /components/ui/Pill.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | 3 | const Pill: FC> = ({ 4 | title, 5 | value, 6 | children, 7 | }) => { 8 | return ( 9 |
13 | {title}: 14 | {value} 15 | {children} 16 |
17 | ); 18 | }; 19 | 20 | export default Pill; 21 | -------------------------------------------------------------------------------- /components/ui/QrCode.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useQRCode } from "next-qrcode"; 3 | 4 | type QrCodeProps = { 5 | width?: number; 6 | text: string; 7 | }; 8 | 9 | const QrCode: React.FC = ({ width, text }) => { 10 | const { Canvas } = useQRCode(); 11 | return ( 12 | 18 | ); 19 | }; 20 | 21 | export default QrCode; 22 | -------------------------------------------------------------------------------- /components/ui/QuillViewer.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import ReactQuill from "react-quill"; 3 | import "react-quill/dist/quill.bubble.css"; 4 | 5 | const QuillViewer = ({ value }: { value: string }) => { 6 | const formats = ["bold", "underline", "italic", "link", "list"]; 7 | 8 | const parsedValue = useMemo(() => { 9 | const maxLength = 30; 10 | const regex = /(.*?)<\/a>/g; 11 | const matches = value.matchAll(regex); 12 | 13 | for (const match of matches) { 14 | const [fullLink, linkText] = match; 15 | 16 | const linkContainSpaces = linkText.includes(" "); 17 | 18 | if (linkContainSpaces === false) { 19 | const newLinkText = 20 | linkText.length > maxLength 21 | ? linkText.substring(0, maxLength) + "..." 22 | : linkText; 23 | 24 | value = value.replace(`>${linkText}<`, `>${newLinkText}<`); 25 | } 26 | } 27 | return value; 28 | }, [value]); 29 | 30 | return ( 31 | 37 | ); 38 | }; 39 | 40 | export default QuillViewer; 41 | -------------------------------------------------------------------------------- /components/ui/SecondaryButton.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, FC } from "react"; 2 | 3 | const SecondaryButton: FC< 4 | PropsWithChildren<{ 5 | onClick: () => void; 6 | disabled?: boolean; 7 | className?: string; 8 | }> 9 | > = ({ onClick, disabled, className = "", children }) => { 10 | return ( 11 | 18 | ); 19 | }; 20 | 21 | export default SecondaryButton; 22 | -------------------------------------------------------------------------------- /components/ui/Skeleton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export type SkeletonProps = { 4 | className?: string; 5 | width?: string | number; 6 | height?: string | number; 7 | }; 8 | 9 | const Skeleton: React.FC = ({ 10 | className = "", 11 | width, 12 | height, 13 | }) => { 14 | width = typeof width === "number" ? `${width}px` : width; 15 | height = typeof height === "number" ? `${height}px` : height; 16 | return ( 17 |
21 | ); 22 | }; 23 | 24 | export default Skeleton; 25 | -------------------------------------------------------------------------------- /components/ui/SubTabsList.tsx: -------------------------------------------------------------------------------- 1 | import { Tab } from "@headlessui/react"; 2 | 3 | const SubTabsList = ({ titles }: { titles: string[] }) => { 4 | return ( 5 | 6 | {titles.map((title, index) => ( 7 | 8 | {({ selected }) => ( 9 |
16 | {title} 17 |
18 | )} 19 |
20 | ))} 21 |
22 | ); 23 | }; 24 | 25 | export default SubTabsList; 26 | -------------------------------------------------------------------------------- /components/ui/TableChart.tsx: -------------------------------------------------------------------------------- 1 | import { Line, LineChart, ResponsiveContainer, YAxis } from "recharts"; 2 | 3 | export const getColour = (startValue: number, endValue: number) => { 4 | if (startValue > endValue) { 5 | return "#FF0054"; 6 | } else if (startValue < endValue) { 7 | return "#70C703"; 8 | } else { 9 | return "#0001FE"; 10 | } 11 | }; 12 | 13 | const TableChart = ({ data }: { data: { v: number; t: number }[] }) => { 14 | return ( 15 | <> 16 | {data?.length > 0 ? ( 17 | 18 | 19 | 26 | 27 | 28 | 29 | ) : ( 30 | <> 31 | )} 32 | 33 | ); 34 | }; 35 | export default TableChart; 36 | -------------------------------------------------------------------------------- /components/ui/TruncatedText.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | const TruncatedText = (props: { 4 | length: number; 5 | text: string; 6 | children: (text: string) => ReactNode; 7 | }) => { 8 | let isCutOff = props.text?.length > props.length; 9 | let cuttofText = `${props.text?.slice(0, props.length)}${ 10 | isCutOff ? "..." : "" 11 | }`; 12 | 13 | return {props.children(cuttofText)}; 14 | }; 15 | 16 | export default TruncatedText; 17 | -------------------------------------------------------------------------------- /components/ui/actionable/cards/CreateAccount.tsx: -------------------------------------------------------------------------------- 1 | import { ActionableCard, ActionableCardProps } from "../ActionableCard"; 2 | 3 | export const CreateAccountActionableCard = ({ 4 | animationVariant, 5 | }: { 6 | animationVariant?: ActionableCardProps["animationVariant"]; 7 | }) => ( 8 | 17 | ); 18 | -------------------------------------------------------------------------------- /components/ui/actionable/cards/Deposit.tsx: -------------------------------------------------------------------------------- 1 | import { ActionableCard, ActionableCardProps } from "../ActionableCard"; 2 | 3 | export const DepositActionableCard = ({ 4 | animationVariant, 5 | }: { 6 | animationVariant?: ActionableCardProps["animationVariant"]; 7 | }) => ( 8 | 17 | ); 18 | -------------------------------------------------------------------------------- /components/ui/actionable/cards/StartTrading.tsx: -------------------------------------------------------------------------------- 1 | import { ActionableCard, ActionableCardProps } from "../ActionableCard"; 2 | 3 | export const StartTradingActionableCard = ({ 4 | animationVariant, 5 | }: { 6 | animationVariant?: ActionableCardProps["animationVariant"]; 7 | }) => ( 8 | 17 | ); 18 | -------------------------------------------------------------------------------- /declarations.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | declare module "framer-motion" { 4 | export interface AnimatePresenceProps { 5 | children?: React.ReactNode; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | build: . 5 | ports: 6 | - "3000:3000" 7 | command: yarn start 8 | -------------------------------------------------------------------------------- /e2e/errors.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import test from "./lib/test"; 3 | 4 | test.describe("pages open without errors", () => { 5 | const testRoutes = [ 6 | { path: "/", testId: "indexPage" }, 7 | // { path: "/markets", testId: "marketCard" }, 8 | ]; 9 | 10 | for (const route of testRoutes) { 11 | test(`route "${route.path}"`, async ({ page, consoleErrors }) => { 12 | await page.goto(route.path); 13 | 14 | const element = page.locator(`[data-testid^=${route.testId}]`).first(); 15 | await element.waitFor(); 16 | await page.waitForLoadState(); 17 | 18 | expect( 19 | consoleErrors.length, 20 | `There were errors: ${consoleErrors.join(", ")}`, 21 | ).toBe(0); 22 | }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /e2e/index.spec.ts-snapshots/learnSection-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/e2e/index.spec.ts-snapshots/learnSection-chromium-linux.png -------------------------------------------------------------------------------- /e2e/lib/index.page.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "@playwright/test"; 2 | 3 | export class IndexPage { 4 | readonly learnSection: Locator; 5 | readonly popularCategories: Locator; 6 | readonly heroSlider: Locator; 7 | 8 | constructor(public readonly page: Page) { 9 | this.learnSection = page.getByTestId("learnSection"); 10 | this.popularCategories = page.getByTestId("popularCategories"); 11 | this.heroSlider = page.getByTestId("HeroSlider__container"); 12 | } 13 | 14 | async goto() { 15 | await this.page.goto("/"); 16 | } 17 | 18 | getLearnSectionButtons(): Locator { 19 | return this.learnSection.locator("a"); 20 | } 21 | 22 | async getActiveSlideIndex(): Promise { 23 | const images = await this.heroSlider.locator("> img").all(); 24 | for (const [index, image] of images.entries()) { 25 | const opacity = await image.evaluate((node) => { 26 | return node.style.opacity; 27 | }); 28 | const isVisible = opacity === "1"; 29 | if (isVisible) { 30 | return index; 31 | } 32 | } 33 | return -1; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /e2e/lib/test.ts: -------------------------------------------------------------------------------- 1 | import { test as base } from "@playwright/test"; 2 | 3 | const IGNORED_MESSAGES = [ 4 | "Failed to load resource", 5 | "Loading initial props cancelled", 6 | ]; 7 | 8 | const test = base.extend<{ consoleErrors: string[] }>({ 9 | consoleErrors: async ({ page }, use) => { 10 | const logs: string[] = []; 11 | 12 | page.on("pageerror", (error) => { 13 | for (const ignoredMessage of IGNORED_MESSAGES) { 14 | if (error.message.includes(ignoredMessage)) { 15 | return; 16 | } 17 | } 18 | logs.push(error.message); 19 | }); 20 | 21 | page.on("console", (consoleMessage) => { 22 | if (consoleMessage.type() === "error") { 23 | const text = consoleMessage.text(); 24 | for (const ignoredMessage of IGNORED_MESSAGES) { 25 | if (text.includes(ignoredMessage)) { 26 | return; 27 | } 28 | } 29 | logs.push(text); 30 | } 31 | }); 32 | 33 | await use(logs); 34 | }, 35 | }); 36 | 37 | export default test; 38 | -------------------------------------------------------------------------------- /layouts/types.ts: -------------------------------------------------------------------------------- 1 | import { NextPage } from "next"; 2 | import { FC } from "react"; 3 | 4 | export type NextPageWithLayout

= NextPage & { 5 | Layout?: FC | (() => JSX.Element); 6 | }; 7 | -------------------------------------------------------------------------------- /lib/cms/featured-markets.ts: -------------------------------------------------------------------------------- 1 | import groq from "groq"; 2 | import { sanity } from "./sanity"; 3 | 4 | export type CmsFeaturedMarkets = { 5 | marketIds: number[]; 6 | }; 7 | 8 | const fields = groq`{ 9 | "marketIds": markets[].marketId, 10 | }`; 11 | 12 | export const getCmsFeaturedMarkets = async () => { 13 | // Short circuit to use default if NOTION_API_KEY doesn't exist. 14 | const data = await sanity.fetch( 15 | groq`*[_type == "featuredMarkets"]${fields}`, 16 | ); 17 | 18 | return data?.[0]; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/cms/news.ts: -------------------------------------------------------------------------------- 1 | import groq from "groq"; 2 | import { sanity } from "./sanity"; 3 | 4 | export type CmsNews = { 5 | title: string; 6 | subtitle: string; 7 | image: string; 8 | link: 9 | | { isMarket: true; market: { marketId: number } } 10 | | { isMarket: false | undefined; url: string }; 11 | }; 12 | 13 | const fields = groq`{ 14 | title, 15 | subtitle, 16 | "image": image.asset->url, 17 | link, 18 | }`; 19 | 20 | export const getCmsNews = async (): Promise => { 21 | const data = await sanity.fetch( 22 | groq`*[_type == "news"] | order(_createdAt desc) ${fields}`, 23 | ); 24 | 25 | return data; 26 | }; 27 | -------------------------------------------------------------------------------- /lib/cms/sanity/index.ts: -------------------------------------------------------------------------------- 1 | import { createClient, type ClientConfig } from "@sanity/client"; 2 | import { environment } from "lib/constants"; 3 | 4 | const PROJECT_ID = process.env["NEXT_PUBLIC_SANITY_PROJECT_ID"]; 5 | const VERSION = process.env["NEXT_PUBLIC_SANITY_VERSION"]; 6 | 7 | const DATASET = environment === "production" ? "mainnet" : "bsr"; 8 | 9 | const config: ClientConfig = { 10 | projectId: PROJECT_ID, 11 | dataset: DATASET, 12 | useCdn: true, // set to `false` to bypass the edge cache 13 | apiVersion: VERSION, // use current date (YYYY-MM-DD) to target the latest API version 14 | }; 15 | 16 | export const sanity = createClient(config); 17 | 18 | import imageUrlBuilder from "@sanity/image-url"; 19 | 20 | export const sanityImageBuilder = imageUrlBuilder(sanity); 21 | -------------------------------------------------------------------------------- /lib/constants/breakpoints.ts: -------------------------------------------------------------------------------- 1 | export const BREAKPOINTS = { 2 | sm: 600, 3 | md: 905, 4 | lg: 1240, 5 | xl: 1440, 6 | "2xl": 1728, 7 | }; 8 | -------------------------------------------------------------------------------- /lib/constants/category-images.ts: -------------------------------------------------------------------------------- 1 | import { union } from "lib/types/union"; 2 | import { prodTags } from "./markets"; 3 | 4 | const tags = [...prodTags, "untagged"] as const; 5 | 6 | const generateImagePaths = (basePath: string, imageCount: number) => { 7 | return Array(imageCount) 8 | .fill(0) 9 | .map((_, index) => `${basePath}/${index + 1}.png`); 10 | }; 11 | export const CATEGORY_IMAGES = union<(typeof tags)[number]>().exhaustAsRecord({ 12 | Sports: generateImagePaths("/categories/sports", 5), 13 | Politics: generateImagePaths("/categories/politics", 4), 14 | Technology: generateImagePaths("/categories/tech", 4), 15 | Crypto: generateImagePaths("/categories/crypto", 4), 16 | Science: generateImagePaths("/categories/science", 4), 17 | News: generateImagePaths("/categories/news", 5), 18 | Dotsama: generateImagePaths("/categories/dotsama", 1), 19 | Zeitgeist: generateImagePaths("/categories/zeitgeist", 1), 20 | Finance: generateImagePaths("/categories/finance", 4), 21 | Entertainment: generateImagePaths("/categories/entertainment", 4), 22 | untagged: generateImagePaths("/categories/zeitgeist", 1), 23 | }); 24 | -------------------------------------------------------------------------------- /lib/constants/markets.ts: -------------------------------------------------------------------------------- 1 | import { EMarketStatus, MarketStatus } from "lib/types/markets"; 2 | 3 | export const prodTags = [ 4 | "Politics", 5 | "Crypto", 6 | "Dotsama", 7 | "Zeitgeist", 8 | "Technology", 9 | "Science", 10 | "News", 11 | "Sports", 12 | "Entertainment", 13 | "Finance", 14 | ] as const; 15 | 16 | const otherTags = process.env.NEXT_PUBLIC_OTHER_TAGS 17 | ? JSON.parse(process.env.NEXT_PUBLIC_OTHER_TAGS) 18 | : []; 19 | 20 | export const defaultTags = [...prodTags, ...otherTags] as const; 21 | 22 | export type SupportedTag = (typeof defaultTags)[number]; 23 | 24 | export const marketStatuses = Object.keys(EMarketStatus) as MarketStatus[]; 25 | 26 | export const hiddenMarketIds = 27 | process.env.NEXT_PUBLIC_HIDDEN_MARKET_IDS ?? "[]"; 28 | -------------------------------------------------------------------------------- /lib/constants/whitelisted-trusted-creators.ts: -------------------------------------------------------------------------------- 1 | import { tryCatch } from "@zeitgeistpm/utility/dist/either"; 2 | 3 | export const WHITELISTED_TRUSTED_CREATORS: string[] = tryCatch(() => 4 | JSON.parse(process.env.NEXT_PUBLIC_WHITELISTED_TRUSTED_CREATORS as string), 5 | ).unwrapOr([]); 6 | -------------------------------------------------------------------------------- /lib/gql/constants.ts: -------------------------------------------------------------------------------- 1 | import { isWSX, wsxID } from "lib/constants"; 2 | 3 | export const marketMetaFilter = isWSX 4 | ? `question_not_eq: "", question_isNull: false, categories_isNull: false, hasValidMetaCategories_eq: true, scoringRule_not_eq: Parimutuel, baseAsset_eq: "{\\"foreignAsset\\":${wsxID}}"` 5 | : `question_not_eq: "", question_isNull: false, categories_isNull: false, hasValidMetaCategories_eq: true, scoringRule_not_eq: Parimutuel, baseAsset_not_eq: "{\\"foreignAsset\\":${wsxID}}"`; 6 | -------------------------------------------------------------------------------- /lib/gql/display-name.ts: -------------------------------------------------------------------------------- 1 | import { FullContext, Sdk } from "@zeitgeistpm/sdk"; 2 | 3 | export const getDisplayName = async ( 4 | sdk: Sdk, 5 | addresses: string[], 6 | ) => { 7 | const identities = await Promise.all( 8 | addresses.map((address) => sdk.api.query.identity.identityOf(address)), 9 | ); 10 | 11 | const textDecoder = new TextDecoder(); 12 | 13 | const names: (string | null)[] = identities.map((identity) => 14 | identity.isNone === false 15 | ? textDecoder.decode( 16 | (identity.value.get("info") as any).get("display").value, 17 | ) 18 | : null, 19 | ); 20 | 21 | return names; 22 | }; 23 | -------------------------------------------------------------------------------- /lib/gql/featured-markets.ts: -------------------------------------------------------------------------------- 1 | import { FullMarketFragment } from "@zeitgeistpm/indexer"; 2 | import { FullContext, Sdk } from "@zeitgeistpm/sdk"; 3 | import { GraphQLClient } from "graphql-request"; 4 | import { getCmsFeaturedMarkets } from "lib/cms/featured-markets"; 5 | 6 | const getFeaturedMarkets = async ( 7 | client: GraphQLClient, 8 | sdk: Sdk, 9 | ): Promise => { 10 | const cmsFeaturedMarkets = await getCmsFeaturedMarkets(); 11 | 12 | const { markets } = await sdk.indexer.markets({ 13 | where: { 14 | marketId_in: cmsFeaturedMarkets.marketIds ?? [], 15 | }, 16 | }); 17 | 18 | markets.sort((a, b) => { 19 | return ( 20 | cmsFeaturedMarkets.marketIds?.findIndex((m) => m === a.marketId) - 21 | cmsFeaturedMarkets.marketIds?.findIndex((m) => m === b.marketId) 22 | ); 23 | }); 24 | 25 | return markets; 26 | }; 27 | 28 | export default getFeaturedMarkets; 29 | -------------------------------------------------------------------------------- /lib/gql/market-header.ts: -------------------------------------------------------------------------------- 1 | import { gql } from "graphql-request"; 2 | import { marketMetaFilter } from "./constants"; 3 | import { IndexerContext, Sdk } from "@zeitgeistpm/sdk"; 4 | 5 | const marketHeaderQuery = gql` 6 | query MarketTransactionHeader($marketIds: [Int!]) { 7 | markets( 8 | where: { 9 | marketId_in: $marketIds 10 | ${marketMetaFilter} 11 | } 12 | orderBy: marketId_ASC 13 | ) { 14 | marketId 15 | question 16 | categories { 17 | name 18 | } 19 | } 20 | } 21 | `; 22 | 23 | export type MarketHeader = { 24 | marketId: number; 25 | question: string; 26 | categories: { name: string }[]; 27 | }; 28 | 29 | export const getMarketHeaders = async ( 30 | sdk: Sdk, 31 | marketIds: number[], 32 | ) => { 33 | const { markets } = await sdk.indexer.client.request<{ 34 | markets: MarketHeader[]; 35 | }>(marketHeaderQuery, { 36 | marketIds: marketIds, 37 | }); 38 | 39 | return markets; 40 | }; 41 | -------------------------------------------------------------------------------- /lib/gql/market-history.ts: -------------------------------------------------------------------------------- 1 | import { gql, GraphQLClient } from "graphql-request"; 2 | import { OutcomeReport } from "@zeitgeistpm/indexer"; 3 | 4 | const historicalMarketQuery = gql` 5 | query HistoricalMarkets($marketId: Int) { 6 | historicalMarkets(where: { market: { marketId_eq: $marketId } }) { 7 | by 8 | blockNumber 9 | event 10 | outcome { 11 | categorical 12 | scalar 13 | } 14 | resolvedOutcome 15 | timestamp 16 | } 17 | } 18 | `; 19 | 20 | export type MarketEventStatus = 21 | | "MarketCreated" 22 | | "MarketClosed" 23 | | "MarketDisputed" 24 | | "MarketResolved" 25 | | "MarketReported"; 26 | 27 | export type MarketEvent = { 28 | blockNumber: number; 29 | by: string; 30 | event: MarketEventStatus; 31 | outcome: OutcomeReport; 32 | resolvedOutcome: string; 33 | timestamp: number; 34 | }; 35 | 36 | export const getMarketHistory = async ( 37 | client: GraphQLClient, 38 | marketId: number, 39 | ): Promise => { 40 | const response = await client.request<{ 41 | historicalMarkets: MarketEvent[]; 42 | }>(historicalMarketQuery, { marketId }); 43 | return response.historicalMarkets; 44 | }; 45 | -------------------------------------------------------------------------------- /lib/gql/markets-stats.ts: -------------------------------------------------------------------------------- 1 | import { gql, GraphQLClient } from "graphql-request"; 2 | 3 | const query = gql` 4 | query MarketStats($ids: [Int!]!) { 5 | marketStats(marketId: $ids) { 6 | participants 7 | liquidity 8 | marketId 9 | } 10 | } 11 | `; 12 | 13 | export type MarketStats = { 14 | participants: number; 15 | liquidity: string; 16 | marketId: number; 17 | }; 18 | 19 | export const getMarketsStats = async ( 20 | client: GraphQLClient, 21 | ids: number[], 22 | ): Promise => { 23 | if (ids.length === 0) return []; 24 | const { marketStats } = await client.request<{ 25 | marketStats: MarketStats[]; 26 | }>(query, { ids }); 27 | return marketStats; 28 | }; 29 | -------------------------------------------------------------------------------- /lib/gql/popular-categories.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLClient } from "graphql-request"; 2 | import { hiddenMarketIds } from "lib/constants/markets"; 3 | import { marketMetaFilter } from "./constants"; 4 | 5 | export const getCategoryCounts = async ( 6 | client: GraphQLClient, 7 | categoryNames: string[], 8 | ): Promise => { 9 | const queries = categoryNames.map( 10 | (name, index) => `tag${index}: marketsConnection(where: { 11 | status_eq: Active, 12 | tags_containsAny: "${name}", 13 | marketId_not_in: ${hiddenMarketIds}, 14 | ${marketMetaFilter} 15 | }, orderBy: marketId_ASC) { 16 | totalCount 17 | }`, 18 | ); 19 | 20 | const response = await client.request<{ 21 | [key: string]: { 22 | totalCount: number; 23 | }; 24 | }>(`query CategoryCounts {${queries.join(" ")}}`); 25 | 26 | const counts = Object.values(response).map((a) => a.totalCount); 27 | 28 | return counts; 29 | }; 30 | -------------------------------------------------------------------------------- /lib/gql/resolution-date.ts: -------------------------------------------------------------------------------- 1 | import { gql, GraphQLClient } from "graphql-request"; 2 | 3 | const resolutionQuery = gql` 4 | query MarketResolutionDate($marketId: Int) { 5 | historicalMarkets( 6 | where: { event_eq: MarketResolved, market: { marketId_eq: $marketId } } 7 | ) { 8 | timestamp 9 | blockNumber 10 | } 11 | } 12 | `; 13 | 14 | //returns ISO string timestamp 15 | export const getResolutionTimestamp = async ( 16 | client: GraphQLClient, 17 | marketId: number, 18 | ) => { 19 | const response = await client.request<{ 20 | historicalMarkets: { timestamp: string; blockNumber: number }[]; 21 | }>(resolutionQuery, { 22 | marketId, 23 | }); 24 | 25 | return { 26 | timestamp: 27 | response.historicalMarkets?.length > 0 28 | ? response.historicalMarkets[0].timestamp 29 | : null, 30 | blockNumber: 31 | response.historicalMarkets?.length > 0 32 | ? response.historicalMarkets[0].blockNumber 33 | : null, 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /lib/hooks/animation/useParallax.ts: -------------------------------------------------------------------------------- 1 | import { MotionValue, useTransform } from "framer-motion"; 2 | 3 | export const useParallax = (value: MotionValue, distance: number) => { 4 | return useTransform(value, [0, 1], [0, distance]); 5 | }; 6 | -------------------------------------------------------------------------------- /lib/hooks/animation/useTypedText.ts: -------------------------------------------------------------------------------- 1 | import { random } from "lodash-es"; 2 | import { useEffect, useRef, useState } from "react"; 3 | 4 | export const useTypedText = (fullText: string) => { 5 | const chars = useRef(fullText.split("")); 6 | const [text, setStext] = useState(""); 7 | 8 | const [animationState, setAnimationState] = useState< 9 | "waiting" | "playing" | "finished" 10 | >("waiting"); 11 | 12 | useEffect(() => { 13 | if (animationState === "playing") { 14 | setTimeout( 15 | () => { 16 | if (chars.current.length > 0) { 17 | setStext(text + chars.current.shift()); 18 | } else { 19 | setAnimationState("finished"); 20 | } 21 | }, 22 | random(15, 55), 23 | ); 24 | } 25 | }, [animationState, text]); 26 | 27 | const play = () => { 28 | setAnimationState("playing"); 29 | }; 30 | 31 | return { 32 | text, 33 | animationState, 34 | play, 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /lib/hooks/events/useGlobalKeyPress.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | /** 4 | * Listen for document wide key press. 5 | * Subscribes when component mounts, unsubscribes when component unmounts. 6 | * 7 | * @param key string - the key to listen for 8 | * @param cb - callback function to run when key is pressed 9 | */ 10 | export const useGlobalKeyPress = (key: string, cb: () => void) => { 11 | useEffect(() => { 12 | const handleKeyPress = (event: KeyboardEvent) => { 13 | if (event.key === key) { 14 | cb(); 15 | } 16 | }; 17 | document.addEventListener("keydown", handleKeyPress); 18 | 19 | return () => { 20 | document.removeEventListener("keydown", handleKeyPress); 21 | }; 22 | }, [key, cb]); 23 | }; 24 | -------------------------------------------------------------------------------- /lib/hooks/events/useHasMounted.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | /** 4 | * Returns true if the component has mounted. 5 | * Useful for two pass rendering where props like className or style changes on the client 6 | * based on values that are only available on the client like window width etc. 7 | * 8 | * @link https://github.com/vercel/next.js/issues/17463#issuecomment-701472340 9 | * @link https://joshwcomeau.com/react/the-perils-of-rehydration/#abstractions 10 | */ 11 | export const useHasMounted = () => { 12 | const [hasMounted, setHasMounted] = useState(false); 13 | 14 | useEffect(() => { 15 | setHasMounted(true); 16 | }, []); 17 | 18 | return hasMounted; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/hooks/events/useHover.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export const useHover = () => { 4 | const [hovered, setHovered] = useState(false); 5 | 6 | const onMouseEnter = () => { 7 | setHovered(true); 8 | }; 9 | 10 | const onMouseLeave = () => { 11 | setHovered(false); 12 | }; 13 | 14 | return { 15 | hovered, 16 | register: () => ({ onMouseEnter, onMouseLeave }), 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/hooks/events/useRelativeMousePosition.ts: -------------------------------------------------------------------------------- 1 | import { useMotionValue } from "framer-motion"; 2 | import { MutableRefObject, RefObject, useEffect } from "react"; 3 | 4 | export const useRelativeMousePosition = (ref: RefObject) => { 5 | const x = useMotionValue(0); 6 | const y = useMotionValue(0); 7 | 8 | useEffect(() => { 9 | const updateMousePosition = (ev: MouseEvent) => { 10 | x.updateAndNotify( 11 | ev.clientX - 12 | (ref.current?.clientWidth ?? 0) / 2 + 13 | (ref.current?.clientLeft ?? 0), 14 | ); 15 | y.updateAndNotify( 16 | ev.clientY - 17 | (ref.current?.clientHeight ?? 0) / 2 + 18 | (ref.current?.clientTop ?? 0), 19 | ); 20 | }; 21 | 22 | window.addEventListener("mousemove", updateMousePosition); 23 | 24 | return () => window.removeEventListener("mousemove", updateMousePosition); 25 | }, []); 26 | 27 | return { x, y }; 28 | }; 29 | -------------------------------------------------------------------------------- /lib/hooks/events/useWindowSize.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export const useWindowSize = () => { 4 | const [windowSize, setWindowSize] = useState({ 5 | width: globalThis.innerWidth, 6 | height: globalThis.innerHeight, 7 | }); 8 | 9 | useEffect(() => { 10 | const handleResize = () => { 11 | setWindowSize({ 12 | width: window.innerWidth, 13 | height: window.innerHeight, 14 | }); 15 | }; 16 | window.addEventListener("resize", handleResize); 17 | handleResize(); 18 | return () => window.removeEventListener("resize", handleResize); 19 | }, []); 20 | return windowSize; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | import { fromEvent, Subscription } from "rxjs"; 3 | import { debounceTime } from "rxjs/operators"; 4 | 5 | export const useEvent = ( 6 | target: EventTarget | undefined, 7 | eventName: string, 8 | debounceMs: number = 0, 9 | ) => { 10 | const eventSub = useRef(null); 11 | const [event, setEvent] = useState(); 12 | 13 | useEffect(() => { 14 | if (eventSub.current) { 15 | eventSub.current.unsubscribe(); 16 | } 17 | if (!target) { 18 | return; 19 | } 20 | eventSub.current = fromEvent(target, eventName) 21 | .pipe(debounceTime(debounceMs)) 22 | .subscribe((e: Event) => setEvent(e)); 23 | return () => eventSub.current?.unsubscribe(); 24 | }, [target]); 25 | 26 | return event; 27 | }; 28 | -------------------------------------------------------------------------------- /lib/hooks/queries/cms/useMarketCmsMetadata.ts: -------------------------------------------------------------------------------- 1 | import { useQuery, useQueryClient } from "@tanstack/react-query"; 2 | import * as batshit from "@yornaath/batshit"; 3 | import { FullCmsMarketMetadata } from "lib/cms/markets"; 4 | 5 | export const marketCmsDataRootKey = ["cms", "market-metadata"]; 6 | 7 | export const marketCmsDatakeyForMarket = (marketId: string | number) => [ 8 | ...marketCmsDataRootKey, 9 | Number(marketId), 10 | ]; 11 | 12 | export const useMarketCmsMetadata = (marketId: string | number) => { 13 | return useQuery( 14 | marketCmsDatakeyForMarket(marketId), 15 | async () => { 16 | return batcher.fetch(Number(marketId)); 17 | }, 18 | { staleTime: 100_000 }, 19 | ); 20 | }; 21 | 22 | const batcher = batshit.create({ 23 | fetcher: async (marketIds: number[]) => { 24 | const res = await fetch( 25 | `/api/cms/market-metadata/batch?marketIds=${JSON.stringify(marketIds)}`, 26 | ); 27 | return (await res.json()) as FullCmsMarketMetadata[]; 28 | }, 29 | scheduler: batshit.windowScheduler(10), 30 | resolver: (items, query) => 31 | items.find((meta) => Number(meta.marketId) === Number(query)) ?? null, 32 | }); 33 | -------------------------------------------------------------------------------- /lib/hooks/queries/constants.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MarketWhereInput, 3 | HistoricalSwapWhereInput, 4 | } from "@zeitgeistpm/indexer"; 5 | import { isWSX, wsxAssetIdString, wsxID } from "lib/constants"; 6 | 7 | export const marketMetaFilter: MarketWhereInput = { 8 | question_isNull: false, 9 | question_not_eq: "", 10 | categories_isNull: false, 11 | hasValidMetaCategories_eq: true, 12 | ...(isWSX ? { baseAsset_eq: wsxAssetIdString } : {}), 13 | ...(!isWSX ? { baseAsset_not_eq: wsxAssetIdString } : {}), 14 | }; 15 | 16 | export const swapsMetaFilter: HistoricalSwapWhereInput = { 17 | ...(isWSX 18 | ? { 19 | assetIn_eq: wsxAssetIdString, 20 | assetOut_eq: wsxAssetIdString, 21 | } 22 | : { 23 | assetIn_not_eq: wsxAssetIdString, 24 | assetOut_not_eq: wsxAssetIdString, 25 | }), 26 | }; 27 | -------------------------------------------------------------------------------- /lib/hooks/queries/court/useConnectedCourtParticipant.ts: -------------------------------------------------------------------------------- 1 | import { useWallet } from "lib/state/wallet"; 2 | import { useCourtParticipants } from "./useCourtParticipants"; 3 | 4 | export const useConnectedCourtParticipant = () => { 5 | const { data: participants } = useCourtParticipants(); 6 | const wallet = useWallet(); 7 | 8 | const participant = participants?.find( 9 | (p) => p.address === wallet.realAddress, 10 | ); 11 | 12 | return participant; 13 | }; 14 | -------------------------------------------------------------------------------- /lib/hooks/queries/court/useCourtStakeSharePercentage.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { useConnectedCourtParticipant } from "./useConnectedCourtParticipant"; 3 | import { useCourtTotalStakedAmount } from "./useCourtTotalStakedAmount"; 4 | import Decimal from "decimal.js"; 5 | import { ZTG } from "@zeitgeistpm/sdk"; 6 | 7 | export const useCourtStakeSharePercentage = () => { 8 | const totalStake = useCourtTotalStakedAmount(); 9 | const connectedParticipant = useConnectedCourtParticipant(); 10 | 11 | return useMemo(() => { 12 | if (totalStake && connectedParticipant) { 13 | return new Decimal(connectedParticipant.stake) 14 | .div(ZTG) 15 | .div(totalStake.all) 16 | .mul(100); 17 | } 18 | return new Decimal(0); 19 | }, [totalStake, connectedParticipant]); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/hooks/queries/court/useCourtTotalStakedAmount.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { useCourtParticipants } from "./useCourtParticipants"; 3 | import Decimal from "decimal.js"; 4 | import { ZTG } from "@zeitgeistpm/sdk"; 5 | 6 | export const useCourtTotalStakedAmount = () => { 7 | const { data: participants } = useCourtParticipants(); 8 | 9 | return useMemo(() => { 10 | if (!participants) 11 | return { 12 | jurorTotal: new Decimal(0), 13 | delegatorTotal: new Decimal(0), 14 | all: new Decimal(0), 15 | }; 16 | 17 | const jurorTotal = participants 18 | .filter((p) => p.type === "Juror") 19 | .reduce((acc, participant) => { 20 | return acc.add(participant.stake); 21 | }, new Decimal(0)) 22 | .div(ZTG); 23 | 24 | const delegatorTotal = participants 25 | .filter((p) => p.type === "Delegator") 26 | .reduce((acc, participant) => { 27 | return acc.add(participant.stake); 28 | }, new Decimal(0)) 29 | .div(ZTG); 30 | 31 | const all = jurorTotal.add(delegatorTotal); 32 | 33 | return { 34 | jurorTotal, 35 | delegatorTotal, 36 | all, 37 | }; 38 | }, [participants]); 39 | }; 40 | -------------------------------------------------------------------------------- /lib/hooks/queries/court/useMarketCaseId.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { isRpcSdk } from "@zeitgeistpm/sdk"; 3 | import { useSdkv2 } from "lib/hooks/useSdkv2"; 4 | 5 | export const marketCaseIdRootKey = "market-case-id"; 6 | 7 | export const useMarketCaseId = (marketId?: number) => { 8 | const [sdk, id] = useSdkv2(); 9 | 10 | const enabled = !!sdk && marketId != null && isRpcSdk(sdk); 11 | const query = useQuery( 12 | [id, marketCaseIdRootKey, marketId], 13 | async () => { 14 | if (!enabled) return null; 15 | const res = await sdk.api.query.court.marketIdToCourtId(marketId); 16 | return res.unwrapOr(null)?.toNumber() ?? null; 17 | }, 18 | { 19 | enabled: enabled, 20 | staleTime: Infinity, 21 | }, 22 | ); 23 | 24 | return query; 25 | }; 26 | -------------------------------------------------------------------------------- /lib/hooks/queries/orderbook/useConnectedAddressOrders.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { AssetId, isRpcSdk } from "@zeitgeistpm/sdk"; 3 | import Decimal from "decimal.js"; 4 | import { useSdkv2 } from "lib/hooks/useSdkv2"; 5 | import { useWallet } from "lib/state/wallet"; 6 | import { useOrders } from "./useOrders"; 7 | 8 | export const userOrdersRootKey = "user-orders"; 9 | 10 | export type MarketOrder = { 11 | id: number; 12 | makerAddress: string; 13 | makerAmount: Decimal; 14 | makerAsset: AssetId; 15 | marketId: number; 16 | takerAmount: Decimal; 17 | takerAsset: AssetId; 18 | }; 19 | 20 | export const useUserOrders = () => { 21 | const [sdk, id] = useSdkv2(); 22 | 23 | const { realAddress } = useWallet(); 24 | const { data: orders } = useOrders(); 25 | 26 | const query = useQuery( 27 | [id, userOrdersRootKey, orders?.length], 28 | async () => { 29 | return orders?.filter((order) => order.makerAddress === realAddress); 30 | }, 31 | { 32 | enabled: Boolean(sdk && isRpcSdk(sdk)), 33 | staleTime: 10_000, 34 | }, 35 | ); 36 | 37 | return query; 38 | }; 39 | -------------------------------------------------------------------------------- /lib/hooks/queries/polkadot/usePolkadotReferendumVotes.ts: -------------------------------------------------------------------------------- 1 | import "@polkadot/api-augment"; 2 | import { useQuery } from "@tanstack/react-query"; 3 | import Decimal from "decimal.js"; 4 | import { usePolkadotApi } from "lib/state/polkadot-api"; 5 | 6 | export const polkadotReferendumVotesRootKey = "polkadot-referendum-votes"; 7 | 8 | export const usePolkadotReferendumVotes = (referendumIndex: number) => { 9 | const { api } = usePolkadotApi(); 10 | 11 | const enabled = !!api; 12 | const query = useQuery( 13 | [polkadotReferendumVotesRootKey, referendumIndex], 14 | async () => { 15 | if (enabled) { 16 | const referendum = 17 | await api.query.referenda.referendumInfoFor(referendumIndex); 18 | 19 | const votes = referendum.unwrapOr(null)?.isOngoing 20 | ? referendum.unwrap().asOngoing.tally 21 | : null; 22 | 23 | if (!votes) return null; 24 | 25 | const ayes = new Decimal(votes.ayes.toString()); 26 | const nays = new Decimal(votes.nays.toString()); 27 | const total = ayes.plus(nays); 28 | return { 29 | ayes, 30 | nays, 31 | ayePercentage: ayes.div(total), 32 | nayPercentage: nays.div(total), 33 | }; 34 | } 35 | }, 36 | { 37 | enabled: enabled, 38 | staleTime: Infinity, 39 | refetchInterval: 60_000, 40 | }, 41 | ); 42 | 43 | return query; 44 | }; 45 | -------------------------------------------------------------------------------- /lib/hooks/queries/useAccountTokenPositions.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { isIndexedSdk } from "@zeitgeistpm/sdk"; 3 | import { parseAssetIdString } from "lib/util/parse-asset-id"; 4 | import { useSdkv2 } from "../useSdkv2"; 5 | 6 | export const positionsRootKey = "account-token-positions"; 7 | 8 | export const useAccountTokenPositions = (address?: string) => { 9 | const [sdk, id] = useSdkv2(); 10 | 11 | return useQuery( 12 | [id, positionsRootKey, address], 13 | async () => { 14 | if (sdk && isIndexedSdk(sdk) && address) { 15 | const { accountBalances } = await sdk.indexer.accountBalances({ 16 | where: { 17 | account: { 18 | accountId_eq: address, 19 | }, 20 | balance_gt: 0, 21 | }, 22 | }); 23 | 24 | return accountBalances.map(({ assetId, balance }) => ({ 25 | assetId: parseAssetIdString(assetId)!, 26 | balance, 27 | })); 28 | } 29 | return null; 30 | }, 31 | { 32 | keepPreviousData: true, 33 | enabled: Boolean(sdk && isIndexedSdk(sdk) && address), 34 | staleTime: 10_000, 35 | }, 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/hooks/queries/useBalances.ts: -------------------------------------------------------------------------------- 1 | import { useQueries } from "@tanstack/react-query"; 2 | import { AssetId, isRpcSdk } from "@zeitgeistpm/sdk"; 3 | import { getApiAtBlock } from "lib/util/get-api-at"; 4 | import { useSdkv2 } from "../useSdkv2"; 5 | import { balanceRootKey, fetchAssetBalance } from "./useBalance"; 6 | 7 | export const useBalances = ( 8 | assetIds: AssetId[], 9 | address?: string, 10 | blockNumber?: number, 11 | ) => { 12 | const [sdk, id] = useSdkv2(); 13 | 14 | const queries = useQueries({ 15 | queries: assetIds.map((assetId) => { 16 | return { 17 | queryKey: [id, balanceRootKey, address, assetId, blockNumber], 18 | queryFn: async () => { 19 | if (address && assetId && isRpcSdk(sdk)) { 20 | const api = await getApiAtBlock(sdk.api, blockNumber); 21 | 22 | return fetchAssetBalance(api, address, assetId); 23 | } 24 | }, 25 | enabled: Boolean(sdk && address && isRpcSdk(sdk) && assetId), 26 | keepPreviousData: true, 27 | }; 28 | }), 29 | }); 30 | 31 | return queries; 32 | }; 33 | -------------------------------------------------------------------------------- /lib/hooks/queries/useCategoryCounts.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { isIndexedSdk } from "@zeitgeistpm/sdk"; 3 | import { CATEGORIES } from "components/front-page/PopularCategories"; 4 | import { getCategoryCounts } from "lib/gql/popular-categories"; 5 | import { useSdkv2 } from "../useSdkv2"; 6 | 7 | export const categoryCountsKey = "category-counts"; 8 | 9 | export const useCategoryCounts = () => { 10 | const [sdk] = useSdkv2(); 11 | 12 | const query = useQuery( 13 | [categoryCountsKey], 14 | async () => { 15 | if (isIndexedSdk(sdk)) { 16 | const categoryCounts = await getCategoryCounts( 17 | sdk.indexer.client, 18 | CATEGORIES.map((c) => c.name), 19 | ); 20 | 21 | return categoryCounts; 22 | } 23 | }, 24 | { 25 | enabled: Boolean(sdk && isIndexedSdk(sdk)), 26 | keepPreviousData: true, 27 | staleTime: 100_000, 28 | }, 29 | ); 30 | 31 | return query; 32 | }; 33 | -------------------------------------------------------------------------------- /lib/hooks/queries/useCreatorFeePayouts.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { HistoricalAccountBalanceOrderByInput } from "@zeitgeistpm/indexer"; 3 | import { isIndexedSdk } from "@zeitgeistpm/sdk"; 4 | import Decimal from "decimal.js"; 5 | import { useSdkv2 } from "../useSdkv2"; 6 | 7 | export const creatorFeePayoutsRootKey = "creator-fee-payouts"; 8 | 9 | export const useCreatorFeePayouts = (address?: string) => { 10 | const [sdk, id] = useSdkv2(); 11 | 12 | const query = useQuery( 13 | [id, creatorFeePayoutsRootKey, address], 14 | async () => { 15 | if (isIndexedSdk(sdk) && address) { 16 | const { historicalAccountBalances: events } = 17 | await sdk.indexer.historicalAccountBalances({ 18 | where: { 19 | event_contains: "MarketCreatorFeesPaid", 20 | accountId_eq: address, 21 | }, 22 | order: HistoricalAccountBalanceOrderByInput.BlockNumberDesc, 23 | }); 24 | 25 | // filter out balances leaving the account 26 | return events.filter((event) => new Decimal(event.dBalance).gt(0)); 27 | } 28 | }, 29 | { 30 | enabled: Boolean(sdk && address && isIndexedSdk(sdk)), 31 | staleTime: 10_000, 32 | }, 33 | ); 34 | 35 | return query; 36 | }; 37 | -------------------------------------------------------------------------------- /lib/hooks/queries/useMarketIsTradingEnabled.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { 3 | Context, 4 | hasPool, 5 | isIndexedData, 6 | Market, 7 | MetadataStorage, 8 | } from "@zeitgeistpm/sdk"; 9 | import { useSdkv2 } from "../useSdkv2"; 10 | 11 | export const rootKey = "market-enabled"; 12 | 13 | export const useMarketIsTradingEnabled = ( 14 | market?: Market>, 15 | ) => { 16 | const [sdk, id] = useSdkv2(); 17 | 18 | const enabled = !!sdk && !!market; 19 | const { data: isEnabled } = useQuery( 20 | [id, rootKey, market?.marketId], 21 | async () => { 22 | if (!enabled) return; 23 | const status = isIndexedData(market) 24 | ? market.status 25 | : market?.status.toString(); 26 | return ( 27 | status?.toLowerCase() === "active" && 28 | (await hasPool(sdk, market)) 29 | ); 30 | }, 31 | { 32 | enabled: Boolean(sdk && market), 33 | initialData: false, 34 | }, 35 | ); 36 | 37 | return isEnabled; 38 | }; 39 | -------------------------------------------------------------------------------- /lib/hooks/queries/useMarketPoolId.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { isRpcSdk } from "@zeitgeistpm/sdk"; 3 | import { useSdkv2 } from "../useSdkv2"; 4 | 5 | export const marketPoolIdRootKey = "market-pool-Id"; 6 | 7 | export const useMarketPoolId = (marketId: number) => { 8 | const [sdk, id] = useSdkv2(); 9 | 10 | const query = useQuery( 11 | [id, marketPoolIdRootKey, marketId], 12 | async () => { 13 | if (isRpcSdk(sdk)) { 14 | const poolId = await sdk.api.query.marketCommons.marketPool(marketId); 15 | return poolId.isSome ? poolId.unwrap().toNumber() : null; 16 | } 17 | }, 18 | { 19 | enabled: Boolean(sdk && marketId != null && isRpcSdk(sdk)), 20 | }, 21 | ); 22 | 23 | return query; 24 | }; 25 | -------------------------------------------------------------------------------- /lib/hooks/queries/useMarketStage.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { Context, isRpcSdk, Market } from "@zeitgeistpm/sdk"; 3 | import { useSdkv2 } from "../useSdkv2"; 4 | import { marketsRootQuery } from "./useMarket"; 5 | import { useChainTime } from "lib/state/chaintime"; 6 | 7 | export const marketStageRootKey = "market-stage"; 8 | 9 | /** 10 | * Get the current stage of a market. 11 | * 12 | * @param market Market 13 | * @returns useQuery 14 | */ 15 | export const useMarketStage = (market?: Market) => { 16 | const [sdk, id] = useSdkv2(); 17 | 18 | const now = useChainTime(); 19 | 20 | return useQuery( 21 | [id, marketsRootQuery, market?.marketId, marketStageRootKey], 22 | async () => { 23 | if (sdk && isRpcSdk(sdk) && market && now) { 24 | return await sdk.model.markets.getStage(market, now); 25 | } 26 | return null; 27 | }, 28 | { 29 | enabled: Boolean(sdk && isRpcSdk(sdk) && market && now), 30 | staleTime: 10_000, 31 | }, 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /lib/hooks/queries/useMarketsStats.ts: -------------------------------------------------------------------------------- 1 | import { useQuery, UseQueryResult } from "@tanstack/react-query"; 2 | import { isIndexedSdk } from "@zeitgeistpm/sdk"; 3 | import { getMarketsStats, MarketStats } from "lib/gql/markets-stats"; 4 | import { useSdkv2 } from "../useSdkv2"; 5 | 6 | export const marketsStatsRootQuery = "markets-stats"; 7 | 8 | export const useMarketsStats = ( 9 | marketIds: number[], 10 | ): UseQueryResult => { 11 | const [sdk, id] = useSdkv2(); 12 | 13 | return useQuery( 14 | [marketsStatsRootQuery, id, marketIds], 15 | async () => { 16 | if (!isIndexedSdk(sdk)) return []; 17 | const poolStats = await getMarketsStats(sdk.indexer.client, marketIds); 18 | 19 | return poolStats; 20 | }, 21 | { 22 | enabled: sdk != null && isIndexedSdk(sdk) && marketIds.length > 0, 23 | keepPreviousData: true, 24 | staleTime: 100_000, 25 | }, 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /lib/hooks/queries/useMintedInCourt.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import type { HistoricalAccountBalanceWhereInput } from "@zeitgeistpm/indexer"; 3 | import { HistoricalAccountBalanceOrderByInput } from "@zeitgeistpm/indexer"; 4 | import { isIndexedSdk } from "@zeitgeistpm/sdk"; 5 | import { useSdkv2 } from "../useSdkv2"; 6 | 7 | export const mintedInCourtRootKey = "minted-in-court"; 8 | 9 | export const useMintedInCourt = ( 10 | filter: Partial<{ 11 | account: string; 12 | limit: number; 13 | offset: number; 14 | }>, 15 | ) => { 16 | const [sdk, id] = useSdkv2(); 17 | 18 | const enabled = sdk && isIndexedSdk(sdk) && filter.account; 19 | 20 | const query = useQuery( 21 | [id, mintedInCourtRootKey, filter], 22 | async () => { 23 | if (enabled) { 24 | const response = await sdk.indexer.historicalAccountBalances({ 25 | where: { 26 | event_eq: "MintedInCourt", 27 | accountId_eq: filter.account, 28 | }, 29 | order: HistoricalAccountBalanceOrderByInput.TimestampDesc, 30 | limit: filter.limit, 31 | offset: filter.offset, 32 | }); 33 | 34 | return response.historicalAccountBalances; 35 | } 36 | }, 37 | { 38 | enabled: Boolean(enabled), 39 | staleTime: 30_000, 40 | }, 41 | ); 42 | return query; 43 | }; 44 | -------------------------------------------------------------------------------- /lib/hooks/queries/usePool.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { isIndexedSdk, PoolGetQuery } from "@zeitgeistpm/sdk"; 3 | import { useSdkv2 } from "../useSdkv2"; 4 | 5 | export const poolsRootKey = "pools"; 6 | 7 | export const usePool = (getPoolQuery?: PoolGetQuery) => { 8 | const [sdk, id] = useSdkv2(); 9 | 10 | const query = useQuery( 11 | [id, poolsRootKey, getPoolQuery], 12 | async () => { 13 | if (isIndexedSdk(sdk) && getPoolQuery) { 14 | const pool = await sdk.model.swaps.getPool(getPoolQuery); 15 | return pool.unwrapOr(undefined); 16 | } 17 | }, 18 | { 19 | enabled: Boolean(sdk && getPoolQuery && isIndexedSdk(sdk)), 20 | staleTime: 10_000, 21 | }, 22 | ); 23 | 24 | return query; 25 | }; 26 | -------------------------------------------------------------------------------- /lib/hooks/queries/usePoolAccountIds.ts: -------------------------------------------------------------------------------- 1 | import { useQueries } from "@tanstack/react-query"; 2 | import { Context, isRpcSdk, Pool } from "@zeitgeistpm/sdk"; 3 | import { useSdkv2 } from "../useSdkv2"; 4 | 5 | export const rootKey = "pool-account-ids"; 6 | 7 | export const usePoolAccountIds = (pools?: Pool[]) => { 8 | const [sdk, id] = useSdkv2(); 9 | 10 | const queries = useQueries({ 11 | queries: 12 | pools?.map((pool) => { 13 | return { 14 | enabled: Boolean(sdk && isRpcSdk(sdk) && pool), 15 | queryKey: [id, rootKey, pool?.poolId], 16 | keepPreviousData: true, 17 | queryFn: async () => { 18 | if (sdk && isRpcSdk(sdk)) { 19 | return { 20 | poolId: pool.poolId, 21 | accountId: ( 22 | await sdk.api.rpc.swaps.poolAccountId(pool.poolId) 23 | ).toString(), 24 | }; 25 | } 26 | }, 27 | }; 28 | }) ?? [], 29 | }); 30 | 31 | return queries.reduce((index, query) => { 32 | if (!query.data) return index; 33 | return { 34 | ...index, 35 | [query.data.poolId]: query.data.accountId, 36 | }; 37 | }, {}); 38 | }; 39 | -------------------------------------------------------------------------------- /lib/hooks/queries/usePoolBaseBalance.ts: -------------------------------------------------------------------------------- 1 | import { parseAssetId } from "@zeitgeistpm/sdk"; 2 | import { useBalance } from "./useBalance"; 3 | import { usePool } from "./usePool"; 4 | 5 | export const usePoolBaseBalance = (poolId?: number, blockNumber?: number) => { 6 | const { data: pool } = usePool(poolId != null ? { poolId } : undefined); 7 | 8 | const balanceQuery = useBalance( 9 | pool?.account.accountId, 10 | pool?.baseAsset ? parseAssetId(pool.baseAsset).unwrap() : undefined, 11 | blockNumber, 12 | ); 13 | 14 | return balanceQuery; 15 | }; 16 | -------------------------------------------------------------------------------- /lib/hooks/queries/useRpcMarket.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { isRpcSdk } from "@zeitgeistpm/sdk"; 3 | import { useSdkv2 } from "../useSdkv2"; 4 | 5 | export const rpcMarketRootKey = "rpc-market"; 6 | 7 | export const useRpcMarket = (marketId: number) => { 8 | const [sdk, id] = useSdkv2(); 9 | 10 | const query = useQuery( 11 | [id, rpcMarketRootKey, marketId], 12 | async () => { 13 | if (isRpcSdk(sdk)) { 14 | const market = await sdk.asRpc().model.markets.get(marketId); 15 | return market.unwrap(); 16 | } 17 | }, 18 | { 19 | enabled: Boolean(sdk && isRpcSdk(sdk) && marketId != null), 20 | }, 21 | ); 22 | 23 | return query; 24 | }; 25 | -------------------------------------------------------------------------------- /lib/hooks/queries/useSaturatedMarket.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { Context, isRpcData, Market } from "@zeitgeistpm/sdk"; 3 | import { useSdkv2 } from "../useSdkv2"; 4 | 5 | export const rootKey = "market:saturated"; 6 | 7 | export const useSaturatedMarket = (market?: Market) => { 8 | const [sdk, id] = useSdkv2(); 9 | 10 | const query = useQuery( 11 | [id, rootKey, market?.marketId], 12 | async () => { 13 | return isRpcData(market) ? await market.saturate() : market; 14 | }, 15 | { 16 | enabled: Boolean(sdk && market), 17 | }, 18 | ); 19 | 20 | return query; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/hooks/queries/useZtgBalance.ts: -------------------------------------------------------------------------------- 1 | import { useBalance } from "./useBalance"; 2 | 3 | export const useZtgBalance = (address?: string, blockNumber?: number) => { 4 | return useBalance(address, { Ztg: null }, blockNumber); 5 | }; 6 | -------------------------------------------------------------------------------- /lib/hooks/queries/useZtgPrice.ts: -------------------------------------------------------------------------------- 1 | import { UseQueryResult } from "@tanstack/react-query"; 2 | import Decimal from "decimal.js"; 3 | import { useAssetUsdPrice } from "./useAssetUsdPrice"; 4 | 5 | export const useZtgPrice = (): UseQueryResult => { 6 | return useAssetUsdPrice({ Ztg: null }); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/hooks/useLocalStorage.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export const useLocalStorage = (key: string, initialValue: T) => { 4 | const [storedValue, setStoredValue] = useState(() => { 5 | if (typeof window === "undefined") { 6 | return initialValue; 7 | } 8 | try { 9 | const item = window.localStorage.getItem(key); 10 | 11 | return item ? JSON.parse(item) : initialValue; 12 | } catch (error) { 13 | console.log(error); 14 | return initialValue; 15 | } 16 | }); 17 | 18 | const setValue = (value: T | ((value: T) => T)) => { 19 | try { 20 | const valueToStore = 21 | value instanceof Function ? value(storedValue) : value; 22 | 23 | setStoredValue(valueToStore); 24 | 25 | if (typeof window !== "undefined") { 26 | window.localStorage.setItem(key, JSON.stringify(valueToStore)); 27 | } 28 | } catch (error) { 29 | console.log(error); 30 | } 31 | }; 32 | return [storedValue, setValue]; 33 | }; 34 | -------------------------------------------------------------------------------- /lib/hooks/useMarketImage.ts: -------------------------------------------------------------------------------- 1 | import { CATEGORY_IMAGES } from "lib/constants/category-images"; 2 | import { useMarketCmsMetadata } from "./queries/cms/useMarketCmsMetadata"; 3 | import { FullMarketFragment } from "@zeitgeistpm/indexer"; 4 | 5 | export const useMarketImage = ( 6 | market: FullMarketFragment | { marketId: number; tags?: string[] }, 7 | opts?: { 8 | fallback?: string; 9 | }, 10 | ) => { 11 | const cmsQuery = useMarketCmsMetadata(market.marketId); 12 | const fallback = getFallbackImage(market.tags, market.marketId); 13 | 14 | return { 15 | ...cmsQuery, 16 | data: cmsQuery.data?.imageUrl ?? opts?.fallback ?? fallback, 17 | }; 18 | }; 19 | 20 | export const getFallbackImage = ( 21 | marketTags: FullMarketFragment["tags"], 22 | marketId: number, 23 | ) => { 24 | const tagIndex = marketTags ? marketId % marketTags.length : 0; 25 | const pickedTag = marketTags?.[tagIndex]; 26 | 27 | const tag = ( 28 | pickedTag && pickedTag in CATEGORY_IMAGES ? pickedTag : "untagged" 29 | ) as keyof typeof CATEGORY_IMAGES; 30 | 31 | const category = CATEGORY_IMAGES[tag]; 32 | 33 | const fallback = category[marketId % category.length]; 34 | return fallback; 35 | }; 36 | -------------------------------------------------------------------------------- /lib/hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | export const usePrevious = (value: T) => { 4 | const ref = useRef(value); 5 | 6 | useEffect(() => { 7 | ref.current = value; 8 | }, [value]); 9 | 10 | return ref.current; 11 | }; 12 | -------------------------------------------------------------------------------- /lib/hooks/useUserLocation.ts: -------------------------------------------------------------------------------- 1 | import { useAtom } from "jotai"; 2 | import { atomsWithQuery } from "jotai-tanstack-query"; 3 | 4 | export type UserLocation = { 5 | locationAllowed: boolean; 6 | }; 7 | 8 | export const userLocationKey = "user-location"; 9 | 10 | export const [userLocationDataAtom, userLocationStatusAtom] = 11 | atomsWithQuery(() => ({ 12 | queryKey: [userLocationKey], 13 | initialData: () => ({ 14 | locationAllowed: true, 15 | }), 16 | keepPreviousData: true, 17 | queryFn: async () => { 18 | const response = await fetch(`/api/location`); 19 | const json = await response.json(); 20 | 21 | const notAllowedCountries: string[] = JSON.parse( 22 | process.env.NEXT_PUBLIC_NOT_ALLOWED_COUNTRIES ?? "[]", 23 | ); 24 | 25 | const userCountry: string = json.body.country; 26 | const locationAllowed = !notAllowedCountries.includes(userCountry); 27 | 28 | console.log("Location:", userCountry); 29 | console.log("Allowed:", locationAllowed); 30 | 31 | return { locationAllowed }; 32 | }, 33 | })); 34 | 35 | export const useUserLocation = () => { 36 | const [data] = useAtom(userLocationDataAtom); 37 | const [status] = useAtom(userLocationStatusAtom); 38 | return { ...data, ...status }; 39 | }; 40 | -------------------------------------------------------------------------------- /lib/query-client.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/react-query"; 2 | 3 | export const appQueryClient = new QueryClient(); 4 | -------------------------------------------------------------------------------- /lib/state/account.tsx: -------------------------------------------------------------------------------- 1 | import { atom, useAtom } from "jotai"; 2 | import { useDisclaimer } from "./disclaimer"; 3 | 4 | const accountsAtom = atom({ 5 | accountSelectModalOpen: false, 6 | walletSelectModalOpen: false, 7 | }); 8 | 9 | export const useAccountModals = () => { 10 | const [state, setState] = useAtom(accountsAtom); 11 | 12 | const { showDisclaimer, disclaimerAccepted } = useDisclaimer(() => { 13 | setState({ ...state, walletSelectModalOpen: true }); 14 | }); 15 | 16 | return { 17 | ...state, 18 | openAccountSelect: () => { 19 | setState({ 20 | ...state, 21 | accountSelectModalOpen: true, 22 | }); 23 | }, 24 | openWalletSelect: () => { 25 | if (disclaimerAccepted) { 26 | setState({ ...state, walletSelectModalOpen: true }); 27 | } else { 28 | showDisclaimer(); 29 | } 30 | }, 31 | closeAccountSelect: () => { 32 | setState({ ...state, accountSelectModalOpen: false }); 33 | }, 34 | closeWalletSelect: () => { 35 | setState({ ...state, walletSelectModalOpen: false }); 36 | }, 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /lib/state/alerts/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./useAlerts"; 2 | export * from "./types"; 3 | -------------------------------------------------------------------------------- /lib/state/chaintime.ts: -------------------------------------------------------------------------------- 1 | import { isRpcSdk } from "@zeitgeistpm/sdk"; 2 | import { ChainTime } from "@zeitgeistpm/utility/dist/time"; 3 | import { atom, getDefaultStore, useAtom } from "jotai"; 4 | import { sdkAtom } from "lib/hooks/useSdkv2"; 5 | import { Subscription } from "rxjs"; 6 | 7 | export const chainTimeAtom = atom(false); 8 | 9 | const store = getDefaultStore(); 10 | 11 | let sub: Subscription; 12 | 13 | const onSdkChange = () => { 14 | const sdk = store.get(sdkAtom); 15 | if (sub) sub.unsubscribe(); 16 | if (isRpcSdk(sdk)) { 17 | sub = sdk.model.time.now.$().subscribe((time) => { 18 | store.set(chainTimeAtom, time); 19 | }); 20 | } 21 | }; 22 | 23 | /** 24 | * In dev the subscription is sometimes not set up on first render. 25 | * So we need to check if the sdk is already set up and if so update the chaintime atom. 26 | */ 27 | const sdk = store.get(sdkAtom); 28 | if (sdk) onSdkChange(); 29 | 30 | store.sub(sdkAtom, onSdkChange); 31 | 32 | export const useChainTime = (): ChainTime | null => { 33 | const [chainTime] = useAtom(chainTimeAtom); 34 | return chainTime || null; 35 | }; 36 | -------------------------------------------------------------------------------- /lib/state/court/CourtCaseJurorCompositeId.ts: -------------------------------------------------------------------------------- 1 | import Opaque, { create } from "ts-opaque"; 2 | import { CourtAppealRound } from "./types"; 3 | import { useCourtCase } from "lib/hooks/queries/court/useCourtCases"; 4 | 5 | export type CourtCaseJurorCompositeId = Opaque< 6 | string, 7 | "CourtCaseJurorCompositeId" 8 | >; 9 | 10 | /** 11 | * This is a composite id that is used to identify a juror's vote for a particular case and the round. 12 | * This will be unique for each juror, case, and round of voting so that we can carry related state like 13 | * the committed vote, generated phrase, download of backup state etc.. for each individual round of voting. 14 | */ 15 | export const courtCaseJurorCompositeId = (params: { 16 | marketId: number; 17 | caseId: number; 18 | juror: string; 19 | }): CourtCaseJurorCompositeId => { 20 | const { data: courtCase } = useCourtCase(params.caseId); 21 | 22 | const round = courtCase 23 | ? (courtCase.appeals.length as CourtAppealRound) 24 | : undefined; 25 | 26 | const roundIdentifier = round ? `-appeal-round[${round}]` : ""; 27 | 28 | return create( 29 | `${params.marketId}-${params.caseId}${roundIdentifier}-${params.juror}`, 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/state/court/CourtSaltPhraseStorage.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | export type CourtSaltPhraseStorage = z.TypeOf; 4 | 5 | export const IOCourtSaltPhraseStorage = z.object({ 6 | juror: z.string(), 7 | caseId: z.number(), 8 | marketId: z.number(), 9 | phrase: z.string(), 10 | createdAt: z.number(), 11 | }); 12 | -------------------------------------------------------------------------------- /lib/state/court/types.ts: -------------------------------------------------------------------------------- 1 | export type CourtAppealRound = 1 | 2 | 3; 2 | -------------------------------------------------------------------------------- /lib/state/court/useCourtCommitmentHash.ts: -------------------------------------------------------------------------------- 1 | import { CategoricalAssetId, isRpcSdk } from "@zeitgeistpm/sdk"; 2 | import { useSdkv2 } from "lib/hooks/useSdkv2"; 3 | import { createCourtCommitmentHash } from "lib/util/create-vote-commitment-hash"; 4 | import { useMemo } from "react"; 5 | import { useWallet } from "../wallet"; 6 | import { CourtSalt } from "./useCourtSalt"; 7 | 8 | export type UseCourtCommitmentHash = { 9 | commitmentHash?: Uint8Array; 10 | }; 11 | 12 | export type UseCourtCommitmentHashParams = { 13 | salt: CourtSalt; 14 | selectedOutcome?: CategoricalAssetId; 15 | }; 16 | 17 | export const useCourtCommitmentHash = ({ 18 | salt, 19 | selectedOutcome, 20 | }: UseCourtCommitmentHashParams): UseCourtCommitmentHash => { 21 | const [sdk] = useSdkv2(); 22 | const wallet = useWallet(); 23 | 24 | const commitmentHash = useMemo(() => { 25 | if (isRpcSdk(sdk) && selectedOutcome && wallet.realAddress) { 26 | return createCourtCommitmentHash( 27 | sdk, 28 | wallet.realAddress!, 29 | selectedOutcome, 30 | salt, 31 | ); 32 | } 33 | }, [salt, selectedOutcome, wallet.realAddress]); 34 | 35 | return { 36 | commitmentHash, 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /lib/state/court/useCourtStage.ts: -------------------------------------------------------------------------------- 1 | import { useMarket } from "lib/hooks/queries/useMarket"; 2 | import { useChainTime } from "../chaintime"; 3 | import { useMemo } from "react"; 4 | import { getCourtStage } from "./get-stage"; 5 | import { useCourtCase } from "lib/hooks/queries/court/useCourtCases"; 6 | 7 | export const useCourtStage = ({ 8 | marketId, 9 | caseId, 10 | }: { 11 | marketId?: number | string; 12 | caseId?: number | string; 13 | }) => { 14 | const chainTime = useChainTime(); 15 | const { data: market } = useMarket({ marketId: Number(marketId) }); 16 | const { data: courtCase } = useCourtCase(Number(caseId)); 17 | 18 | const stage = useMemo(() => { 19 | if (courtCase && market && chainTime) { 20 | return getCourtStage(chainTime, market, courtCase); 21 | } 22 | }, [chainTime, market, courtCase]); 23 | 24 | return stage; 25 | }; 26 | -------------------------------------------------------------------------------- /lib/state/court/useOutcomeMatchingCommitmentHash.ts: -------------------------------------------------------------------------------- 1 | import { u8aToHex } from "@polkadot/util"; 2 | import { HexString } from "@polkadot/util/types"; 3 | import { CategoricalAssetId, isRpcSdk } from "@zeitgeistpm/sdk"; 4 | import { useSdkv2 } from "lib/hooks/useSdkv2"; 5 | import { CourtSalt } from "lib/state/court/useCourtSalt"; 6 | import { useWallet } from "lib/state/wallet"; 7 | import { createCourtCommitmentHash } from "lib/util/create-vote-commitment-hash"; 8 | 9 | /** 10 | * Find a matching outcome for a commitment hash. 11 | * Helpful so that we can restore state from backup or partially missing local state. 12 | */ 13 | export const useOutcomeMatchingCommitmentHash = ( 14 | salt: CourtSalt, 15 | commitmentHash: HexString, 16 | outcomeAssets: CategoricalAssetId[], 17 | ) => { 18 | const [sdk] = useSdkv2(); 19 | const wallet = useWallet(); 20 | if (isRpcSdk(sdk) && wallet.realAddress) { 21 | const outcome = outcomeAssets.find((assetId) => { 22 | const assetHash = createCourtCommitmentHash( 23 | sdk, 24 | wallet.realAddress!, 25 | assetId, 26 | salt, 27 | ); 28 | return u8aToHex(assetHash) === commitmentHash; 29 | }); 30 | 31 | return outcome; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /lib/state/delay-queue.ts: -------------------------------------------------------------------------------- 1 | import { AssetId } from "@zeitgeistpm/sdk"; 2 | import { atom, getDefaultStore, useAtom } from "jotai"; 3 | 4 | type QueueItem = { 5 | id: number; 6 | lifetimeMS: number; 7 | metadata?: Metadata; 8 | onItemRemoved: () => void; 9 | }; 10 | 11 | type Metadata = { address?: string; assetId: AssetId }; 12 | 13 | const delayQueueAtom = atom([]); 14 | 15 | const store = getDefaultStore(); 16 | 17 | export const useDelayQueue = () => { 18 | const atom = useAtom(delayQueueAtom); 19 | 20 | const addItem = (lifetimeMS: number, metadata?: Metadata) => { 21 | const items = store.get(delayQueueAtom); 22 | const id = items.length + 1; 23 | const onItemRemoved = () => {}; 24 | 25 | store.set(delayQueueAtom, [ 26 | ...items, 27 | { id: id, lifetimeMS, metadata, onItemRemoved }, 28 | ]); 29 | 30 | setTimeout(() => { 31 | const items = store.get(delayQueueAtom); 32 | const remainingItems = items.filter((item) => item.id !== id); 33 | 34 | store.set(delayQueueAtom, remainingItems); 35 | }, lifetimeMS); 36 | }; 37 | 38 | return { 39 | addItem, 40 | items: atom[0], 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /lib/state/disclaimer.ts: -------------------------------------------------------------------------------- 1 | import { atom, useAtom } from "jotai"; 2 | import { persistentAtom } from "./util/persistent-atom"; 3 | import { useEffect, useMemo } from "react"; 4 | 5 | const disclaimerAcceptanceStateAtom = persistentAtom<{ 6 | disclaimerStatus: boolean; 7 | }>({ 8 | key: "disclaimer-acceptance", 9 | defaultValue: { 10 | disclaimerStatus: false, 11 | }, 12 | }); 13 | 14 | const disclaimerDisplayed = atom(false); 15 | 16 | export const useDisclaimer = (onAccept?: () => void) => { 17 | const [modalOpen, setModalOpen] = useAtom(disclaimerDisplayed); 18 | const [{ disclaimerStatus }, setDisclaimerStatus] = useAtom( 19 | disclaimerAcceptanceStateAtom, 20 | ); 21 | 22 | const initialDisclaimerStatus = useMemo(() => { 23 | return disclaimerStatus; 24 | }, []); 25 | 26 | useEffect(() => { 27 | if (!initialDisclaimerStatus && disclaimerStatus) { 28 | onAccept?.(); 29 | } 30 | }, [disclaimerStatus]); 31 | 32 | const setDisclaimerAccepted = () => { 33 | setDisclaimerStatus({ disclaimerStatus: true }); 34 | }; 35 | 36 | const showDisclaimer = () => { 37 | setModalOpen(true); 38 | }; 39 | const hideDisclaimer = () => { 40 | setModalOpen(false); 41 | }; 42 | 43 | return { 44 | modalOpen, 45 | hideDisclaimer, 46 | showDisclaimer, 47 | disclaimerAccepted: disclaimerStatus, 48 | setDisclaimerAccepted, 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /lib/state/fee-paying-asset.ts: -------------------------------------------------------------------------------- 1 | import { useAtom } from "jotai"; 2 | import { persistentAtom } from "./util/persistent-atom"; 3 | import { AssetOption } from "components/ui/AssetSelect"; 4 | 5 | type SelectedFeeAsset = AssetOption; 6 | 7 | const feePayingAssetStateAtom = persistentAtom({ 8 | key: "fee-paying-asset", 9 | defaultValue: { 10 | label: "Default", 11 | additionalText: "Uses first available asset", 12 | }, 13 | }); 14 | 15 | const useFeePayingAssetSelection = () => { 16 | const [state, setState] = useAtom(feePayingAssetStateAtom); 17 | 18 | const setAsset = (selection: SelectedFeeAsset) => { 19 | setState(selection); 20 | }; 21 | 22 | return { assetSelection: state, setAsset }; 23 | }; 24 | 25 | export default useFeePayingAssetSelection; 26 | -------------------------------------------------------------------------------- /lib/state/market-creation/constants/currency.ts: -------------------------------------------------------------------------------- 1 | import { SupportedCurrencyTag } from "lib/constants/supported-currencies"; 2 | 3 | /** 4 | * A map of the minimum liquidity required for a market creation 5 | * pr supported currency. 6 | */ 7 | 8 | export const minBaseLiquidity: Record = { 9 | ZTG: 200, 10 | DOT: 10, 11 | "USDC.wh": 50, 12 | }; 13 | -------------------------------------------------------------------------------- /lib/state/market-creation/constants/deadline-options.ts: -------------------------------------------------------------------------------- 1 | import { BlockPeriodPickerOptions } from "components/create/editor/inputs/BlockPeriod"; 2 | import { DeepReadonly } from "lib/types/deep-readonly"; 3 | 4 | /** 5 | * Options for the block period pickers. 6 | * Used in the time period step of the market creation process. 7 | */ 8 | 9 | export const gracePeriodOptions = [ 10 | { type: "duration", preset: "None", unit: "hours", value: 0 }, 11 | { type: "custom-date" }, 12 | ] as const satisfies DeepReadonly; 13 | 14 | export const reportingPeriodOptions = [ 15 | { 16 | type: "duration", 17 | preset: "1 Day", 18 | unit: "days", 19 | value: 1, 20 | }, 21 | { 22 | type: "duration", 23 | preset: "4 Days", 24 | unit: "days", 25 | value: 4, 26 | }, 27 | { type: "custom-duration" }, 28 | ] as const satisfies DeepReadonly; 29 | 30 | export const disputePeriodOptions = [ 31 | { 32 | type: "duration", 33 | preset: "1 Day", 34 | unit: "days", 35 | value: 1, 36 | }, 37 | { 38 | type: "duration", 39 | preset: "2 Days", 40 | unit: "days", 41 | value: 2, 42 | }, 43 | { type: "custom-duration" }, 44 | ] as const satisfies DeepReadonly; 45 | -------------------------------------------------------------------------------- /lib/state/market-creation/constants/swap-fee.ts: -------------------------------------------------------------------------------- 1 | import { Liquidity } from "../types/form"; 2 | 3 | export const swapFeePresets: Liquidity["swapFee"][] = [ 4 | { 5 | type: "preset", 6 | value: 1, 7 | }, 8 | { 9 | type: "preset", 10 | value: 3, 11 | }, 12 | ]; 13 | -------------------------------------------------------------------------------- /lib/state/polkadot-api.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise, WsProvider } from "@polkadot/api"; 2 | import { atom, useAtom } from "jotai"; 3 | import { loadable } from "jotai/utils"; 4 | import { environment } from "lib/constants"; 5 | 6 | const endpoints = 7 | environment === "production" 8 | ? [ 9 | "wss://rpc.polkadot.io", 10 | "wss://polkadot-rpc.dwellir.com", 11 | "wss://polkadot.public.curie.radiumblock.co/ws", 12 | "wss://1rpc.io/dot", 13 | "wss://rpc-polkadot.luckyfriday.io", 14 | ] 15 | : ["wss://rococo-rpc.polkadot.io"]; 16 | 17 | const polkadotApiAtom = loadable( 18 | atom(async () => { 19 | const wsProvider = new WsProvider(endpoints); 20 | const api = await ApiPromise.create({ provider: wsProvider }); 21 | 22 | return api; 23 | }), 24 | ); 25 | 26 | export const usePolkadotApi = () => { 27 | const [value] = useAtom(polkadotApiAtom); 28 | 29 | if (value.state === "hasData") { 30 | return { api: value.data, isLoading: false }; 31 | } else { 32 | return { api: null, isLoading: value.state === "loading" }; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /lib/twitch/index.ts: -------------------------------------------------------------------------------- 1 | import { isAbsoluteUrl } from "next/dist/shared/lib/utils"; 2 | 3 | export const isLive = async (channelNameOrUrl: string) => { 4 | const res = await fetch( 5 | `https://decapi.me/twitch/uptime/${extractChannelName(channelNameOrUrl)}`, 6 | ); 7 | const data = await res.text(); 8 | return res.ok && !data.match(/is offline|error/i); 9 | }; 10 | 11 | export const extractChannelName = (url?: string) => { 12 | if (!url) return null; 13 | if (isAbsoluteUrl(url)) { 14 | return new URL(url ?? "").pathname.replace("/", ""); 15 | } else { 16 | return url; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /lib/types/create-market.ts: -------------------------------------------------------------------------------- 1 | export interface MultipleOutcomeEntry { 2 | name: string; 3 | ticker: string; 4 | color: string; 5 | } 6 | 7 | export type MarketImageCid = string; 8 | export type MarketImageBase64Encoded = string; 9 | 10 | export type MarketImageString = MarketImageCid | MarketImageBase64Encoded; 11 | 12 | export const isMarketImageBase64Encoded = ( 13 | image: string, 14 | ): image is MarketImageBase64Encoded => { 15 | return image.startsWith("data:image"); 16 | }; 17 | -------------------------------------------------------------------------------- /lib/types/deep-partial.ts: -------------------------------------------------------------------------------- 1 | export type DeepPartial = T extends object 2 | ? { 3 | [P in keyof T]?: DeepPartial; 4 | } 5 | : T; 6 | -------------------------------------------------------------------------------- /lib/types/deep-readonly.ts: -------------------------------------------------------------------------------- 1 | export type DeepReadonly = T extends (infer R)[] 2 | ? DeepReadonlyArray 3 | : T extends Function 4 | ? T 5 | : T extends object 6 | ? DeepReadonlyObject 7 | : T; 8 | 9 | export interface DeepReadonlyArray extends ReadonlyArray> {} 10 | 11 | export type DeepReadonlyObject = { 12 | readonly [P in keyof T]: DeepReadonly; 13 | }; 14 | -------------------------------------------------------------------------------- /lib/types/market-filter.ts: -------------------------------------------------------------------------------- 1 | import { filterTypes } from "lib/constants/market-filter"; 2 | import { MarketStatus } from "./markets"; 3 | import { CATEGORIES } from "components/front-page/PopularCategories"; 4 | 5 | export enum MarketsOrderBy { 6 | Newest = "Newest", 7 | Oldest = "Oldest", 8 | MostVolume = "Most Volume", 9 | LeastVolume = "Least Volume", 10 | } 11 | 12 | export type MarketOrderByOption = { 13 | label: MarketsOrderBy; 14 | value: MarketsOrderBy; 15 | }; 16 | 17 | export type MarketFilterTagLabel = (typeof CATEGORIES)[number]["name"] | string; 18 | 19 | export type MarketFilterStatusLabel = MarketStatus; 20 | 21 | export type MarketFilterType = (typeof filterTypes)[number]; 22 | 23 | export type MarketFilter = { 24 | type: MarketFilterType; 25 | value: string; 26 | label: string; 27 | imageUrl?: string; 28 | }; 29 | 30 | export interface MarketStatusFilter extends MarketFilter { 31 | type: "status"; 32 | value: MarketFilterStatusLabel; 33 | } 34 | 35 | export interface MarketTagFilter extends MarketFilter { 36 | type: "tag"; 37 | value: MarketFilterTagLabel; 38 | } 39 | 40 | export interface MarketCurrencyFilter extends MarketFilter { 41 | type: "currency"; 42 | value: string; 43 | } 44 | 45 | export type MarketsListFiltersQuery = { 46 | [key in MarketFilterType]?: string[]; 47 | }; 48 | 49 | export type MarketsListQuery = { 50 | filters: MarketsListFiltersQuery; 51 | ordering: MarketsOrderBy; 52 | liquidityOnly: boolean; 53 | }; 54 | -------------------------------------------------------------------------------- /lib/types/markets.ts: -------------------------------------------------------------------------------- 1 | import { OutcomeReport } from "@zeitgeistpm/indexer"; 2 | 3 | export type MarketOutcome = { 4 | name: string; 5 | color?: string; 6 | price: number; 7 | assetId?: string; 8 | amountInPool?: string; 9 | }; 10 | export type MarketOutcomes = MarketOutcome[]; 11 | 12 | export enum EMarketStatus { 13 | Proposed = "Proposed", 14 | Active = "Active", 15 | Closed = "Closed", 16 | Reported = "Reported", 17 | Disputed = "Disputed", 18 | Resolved = "Resolved", 19 | } 20 | 21 | export type MarketStatus = keyof typeof EMarketStatus; 22 | 23 | export type MarketDispute = { 24 | at: number; 25 | by: string; 26 | }; 27 | 28 | export type MarketTypeOf = 29 | | { 30 | categorical: number; 31 | } 32 | | { 33 | scalar: string[]; 34 | }; 35 | -------------------------------------------------------------------------------- /lib/types/user-identity.ts: -------------------------------------------------------------------------------- 1 | export type Judgement = 2 | | "Unknown" 3 | | "FeePaid" 4 | | "Reasonable" 5 | | "KnownGood" 6 | | "OutOfDate" 7 | | "LowQuality" 8 | | "Erroneous"; 9 | 10 | export interface UserIdentity { 11 | displayName: string; 12 | discord?: string; 13 | twitter?: string; 14 | judgement?: Judgement; 15 | } 16 | -------------------------------------------------------------------------------- /lib/util/assets-are-equal.ts: -------------------------------------------------------------------------------- 1 | import { AssetId } from "@zeitgeistpm/sdk"; 2 | 3 | export const assetsAreEqual = (asset1?: AssetId, asset2?: AssetId) => { 4 | return JSON.stringify(asset1) === JSON.stringify(asset2); 5 | }; 6 | -------------------------------------------------------------------------------- /lib/util/await-indexer.ts: -------------------------------------------------------------------------------- 1 | const INDEXER_DELAY_MS = 40 * 1000; 2 | 3 | export const awaitIndexer = async (callback: () => void) => { 4 | setTimeout(() => { 5 | callback(); 6 | }, INDEXER_DELAY_MS); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/util/calc-free-balance.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "vitest"; 2 | import { calculateFreeBalance } from "./calc-free-balance"; 3 | 4 | describe("calculateFreeBalance", () => { 5 | test("should return free when nothing is frozen", () => { 6 | const free = calculateFreeBalance("100", "0", "0"); 7 | expect(free.toString()).toEqual("100"); 8 | }); 9 | 10 | test("should negate the max of misc and fee frozen", () => { 11 | const free = calculateFreeBalance("100", "20", "10"); 12 | expect(free.toString()).toEqual("80"); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /lib/util/calc-free-balance.ts: -------------------------------------------------------------------------------- 1 | import Decimal from "decimal.js"; 2 | 3 | export const calculateFreeBalance = ( 4 | free: string, 5 | miscFrozen: string, 6 | feeFrozen: string, 7 | ) => { 8 | const maxFrozen = Decimal.max( 9 | miscFrozen ? miscFrozen : 0, 10 | feeFrozen ? feeFrozen : 0, 11 | ); 12 | return new Decimal(free).minus(maxFrozen); 13 | }; 14 | -------------------------------------------------------------------------------- /lib/util/calc-price-history-start.ts: -------------------------------------------------------------------------------- 1 | import { MarketStatus } from "@zeitgeistpm/sdk"; 2 | import { TimeFilter } from "components/ui/TimeFilters"; 3 | 4 | export const calcPriceHistoryStartDate = ( 5 | marketStatus: MarketStatus, 6 | chartFilter: TimeFilter, 7 | poolCreationDate: Date, 8 | resolutionDate: Date, 9 | ) => { 10 | if (chartFilter.label === "All") return poolCreationDate; 11 | if (marketStatus === "Resolved" && resolutionDate) { 12 | const startDate = 13 | resolutionDate.getTime() - (chartFilter?.timePeriodMS ?? 0); 14 | 15 | return startDate > poolCreationDate.getTime() 16 | ? new Date(startDate) 17 | : poolCreationDate; 18 | } else { 19 | const now = new Date(); 20 | 21 | const startDate = now.getTime() - (chartFilter?.timePeriodMS ?? 0); 22 | 23 | return startDate > poolCreationDate.getTime() 24 | ? new Date(startDate) 25 | : poolCreationDate; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /lib/util/calculate-restrictive-pool-asset.ts: -------------------------------------------------------------------------------- 1 | import Decimal from "decimal.js"; 2 | 3 | export const calculateRestrictivePoolAsset = ( 4 | poolBalances: Decimal[], 5 | userBalances: Decimal[], 6 | ) => { 7 | let restrictiveBalanceIndex: number | undefined; 8 | userBalances.forEach((balance, index) => { 9 | const poolBalance = poolBalances[index]; 10 | const ratio = balance.div(poolBalance); 11 | 12 | let isRestrictive = true; 13 | userBalances.forEach((otherBalance, otherIndex) => { 14 | if (index !== otherIndex) { 15 | const balanceNeeded = otherBalance.mul(ratio); 16 | if (balanceNeeded.greaterThanOrEqualTo(otherBalance)) { 17 | isRestrictive = false; 18 | } 19 | } 20 | }); 21 | 22 | if (isRestrictive === true) restrictiveBalanceIndex = index; 23 | }); 24 | 25 | return restrictiveBalanceIndex ?? 0; 26 | }; 27 | -------------------------------------------------------------------------------- /lib/util/color-calc.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "vitest"; 2 | import { calcMarketColors, hslToHex } from "./color-calc"; 3 | 4 | describe("color calculation", () => { 5 | describe("calcMarketColors", () => { 6 | test("should create evenly distibuted colors", () => { 7 | const colors = calcMarketColors(0, 2); 8 | 9 | expect(colors.length).toEqual(2); 10 | expect(colors).toStrictEqual(["#ff0000", "#00ffff"]); // blue and red 11 | }); 12 | test("should work with large markets", () => { 13 | const colors = calcMarketColors(1230, 64); 14 | 15 | expect(colors.length).toEqual(64); 16 | expect(colors[0]).toEqual("#00ff80"); //green 17 | expect(colors[32]).toEqual("#ff0080"); //pink 18 | expect(colors.at(-1)).toEqual("#00ff68"); //green 19 | }); 20 | }); 21 | 22 | describe("hslToHex", () => { 23 | test("should convert hsl to hex", () => { 24 | const hex = hslToHex(181, 100, 50); 25 | 26 | expect(hex).toEqual("#00fbff"); 27 | }); 28 | 29 | test("should convert hsl to hex (black)", () => { 30 | const hex = hslToHex(0, 0, 0); 31 | 32 | expect(hex).toEqual("#000000"); 33 | }); 34 | 35 | test("should work for hues above 360", () => { 36 | const hex = hslToHex(700, 100, 50); 37 | 38 | expect(hex).toEqual("#ff0055"); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /lib/util/color-calc.ts: -------------------------------------------------------------------------------- 1 | export const calcMarketColors = (marketId: number, assetsLength: number) => { 2 | return Array.from({ length: assetsLength }, (_, i) => 3 | calcColor(marketId, assetsLength, i), 4 | ); 5 | }; 6 | 7 | export const calcColor = ( 8 | marketId: number, 9 | assetsLength: number, 10 | assetIndex: number, 11 | ) => { 12 | const startingPoint = marketId % 360; 13 | const assetSpacing = 360 / assetsLength; 14 | const hue = startingPoint + assetSpacing * assetIndex; 15 | 16 | const saturation = 100; 17 | const lightness = 50; 18 | 19 | return hslToHex(hue, saturation, lightness); 20 | }; 21 | 22 | export const hslToHex = (h: number, s: number, l: number) => { 23 | l /= 100; 24 | const a = (s * Math.min(l, 1 - l)) / 100; 25 | const f = (n) => { 26 | const k = (n + h / 30) % 12; 27 | const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); 28 | return Math.round(255 * color) 29 | .toString(16) 30 | .padStart(2, "0"); // convert to Hex and prefix "0" if needed 31 | }; 32 | return `#${f(0)}${f(8)}${f(4)}`; 33 | }; 34 | -------------------------------------------------------------------------------- /lib/util/convert-decimals.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "vitest"; 2 | import Decimal from "decimal.js"; 3 | import { convertDecimals } from "./convert-decimals"; 4 | 5 | describe("convertDecimals", () => { 6 | test("should scale number down", () => { 7 | const amount = convertDecimals(new Decimal(1000), 12, 10); 8 | 9 | expect(amount.toString()).toEqual("10"); 10 | }); 11 | 12 | test("should scale number up", () => { 13 | const amount = convertDecimals(new Decimal(1000), 10, 12); 14 | 15 | expect(amount.toString()).toEqual("100000"); 16 | }); 17 | 18 | test("should do nothing", () => { 19 | const amount = convertDecimals(new Decimal(1000), 10, 10); 20 | 21 | expect(amount.toString()).toEqual("1000"); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /lib/util/convert-decimals.ts: -------------------------------------------------------------------------------- 1 | import Decimal from "decimal.js"; 2 | 3 | export const convertDecimals = ( 4 | amount: Decimal, 5 | fromDecimals: number, 6 | toDecimals: number, 7 | ) => { 8 | const multiplier = new Decimal(10).pow(toDecimals - fromDecimals); 9 | 10 | return amount.mul(multiplier); 11 | }; 12 | -------------------------------------------------------------------------------- /lib/util/count-decimals.ts: -------------------------------------------------------------------------------- 1 | export const countDecimals = (val: number) => { 2 | if (Math.floor(val.valueOf()) === val.valueOf()) return 0; 3 | // handle very small numbers 4 | try { 5 | return val.toString().split(".")[1].length || 0; 6 | } catch { 7 | return 0; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /lib/util/court/calculateSlashableStake.ts: -------------------------------------------------------------------------------- 1 | export const calculateSlashableStake = ( 2 | round: number, 3 | minJurorStake: number, 4 | ) => { 5 | const requestedVoteWeight = Math.pow(2, round) * 31 + Math.pow(2, round) - 1; 6 | const totalSlashableStake = requestedVoteWeight * minJurorStake; 7 | return totalSlashableStake; 8 | }; 9 | -------------------------------------------------------------------------------- /lib/util/create-vote-commitment-hash.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "vitest"; 2 | import { MarketId, batterystationRpc, create } from "@zeitgeistpm/sdk"; 3 | import { blake2AsU8a } from "@polkadot/util-crypto"; 4 | import { u8aToHex } from "@polkadot/util"; 5 | import { createCourtCommitmentHash } from "./create-vote-commitment-hash"; 6 | 7 | describe("createCourtCommitmentHash", () => { 8 | test("should produce the correct commitment hash provided the", async () => { 9 | const sdk = await create(batterystationRpc()); 10 | const phrase = 11 | "purity home goddess equal grant squirrel page cause domain hope throw wink"; 12 | const salt = blake2AsU8a(phrase); 13 | 14 | const yesHash = createCourtCommitmentHash( 15 | sdk, 16 | "dDyXkkoewJvnksMEirA7k6K76STzJz78bxYtG1Y1V2LCHJyMA", 17 | { 18 | CategoricalOutcome: [754 as MarketId, 0], 19 | }, 20 | salt, 21 | ); 22 | 23 | const noHash = createCourtCommitmentHash( 24 | sdk, 25 | "dDyXkkoewJvnksMEirA7k6K76STzJz78bxYtG1Y1V2LCHJyMA", 26 | { 27 | CategoricalOutcome: [754 as MarketId, 1], 28 | }, 29 | salt, 30 | ); 31 | 32 | expect(u8aToHex(yesHash)).toEqual( 33 | "0x77d99bba0142ab7ad7e148a9cdb246ca362c03eef834481678766f639d383061", 34 | ); 35 | expect(u8aToHex(noHash)).toEqual( 36 | "0x407fe12e135068fa36728063a30a1ce8e19c2b18ad3b471eeea2f20686e01426", 37 | ); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /lib/util/create-vote-commitment-hash.ts: -------------------------------------------------------------------------------- 1 | import { blake2AsU8a } from "@polkadot/util-crypto"; 2 | import { u8aConcat } from "@polkadot/util/u8a"; 3 | import { CategoricalAssetId, RpcContext, Sdk } from "@zeitgeistpm/sdk"; 4 | 5 | export const createCourtCommitmentHash = ( 6 | sdk: Sdk, 7 | address: string, 8 | selectedOutcome: CategoricalAssetId, 9 | salt: Uint8Array, 10 | ) => { 11 | const accountId = sdk.api.createType("AccountId32", address); 12 | const voteItem = sdk.api.createType("ZrmlCourtVoteItem", { 13 | Outcome: sdk.api.createType("ZeitgeistPrimitivesOutcomeReport", { 14 | Categorical: selectedOutcome.CategoricalOutcome[1], 15 | }), 16 | }); 17 | 18 | const hash = blake2AsU8a(u8aConcat(accountId, voteItem.toU8a(), salt), 256); 19 | 20 | return hash; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/util/delay.ts: -------------------------------------------------------------------------------- 1 | export const delay = async (ms: number) => { 2 | return new Promise((resolve) => { 3 | setTimeout(resolve, ms); 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /lib/util/download.ts: -------------------------------------------------------------------------------- 1 | export const downloadText = (filename: string, text: string) => { 2 | var element = document.createElement("a"); 3 | element.setAttribute( 4 | "href", 5 | "data:text/plain;charset=utf-8," + encodeURIComponent(text), 6 | ); 7 | element.setAttribute("download", filename); 8 | 9 | element.style.display = "none"; 10 | document.body.appendChild(element); 11 | 12 | element.click(); 13 | 14 | document.body.removeChild(element); 15 | }; 16 | -------------------------------------------------------------------------------- /lib/util/estimate-market-resolution.ts: -------------------------------------------------------------------------------- 1 | export const estimateMarketResolutionDate = ( 2 | endDate: Date, 3 | blockTimeSeconds: number, 4 | gracePeriodBlocks: number = 0, 5 | reportPeriodBlocks: number = 0, 6 | disputePeriodBlocks: number = 0, 7 | ) => { 8 | const estimatedTimeAfterEnd = 9 | blockTimeSeconds * 10 | (gracePeriodBlocks + reportPeriodBlocks / 2 + disputePeriodBlocks); 11 | 12 | return new Date(endDate.getTime() + estimatedTimeAfterEnd * 1000); 13 | }; 14 | -------------------------------------------------------------------------------- /lib/util/fetch-all-pages.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "vitest"; 2 | import { fetchAllPages } from "./fetch-all-pages"; 3 | 4 | describe("fetchAllPages", () => { 5 | test("should fetch all when there is a single page", async () => { 6 | const fetcher = async (pageNumber: number, limit: number) => { 7 | if (pageNumber === 0) { 8 | return Array.from({ length: 10 }, (_, i) => i); 9 | } 10 | 11 | return []; 12 | }; 13 | 14 | const pages = await fetchAllPages(fetcher); 15 | 16 | expect(pages.length).toEqual(10); 17 | }); 18 | 19 | test("should fetch all when there is multiple pages", async () => { 20 | const fetcher = async (pageNumber: number, limit) => { 21 | if (pageNumber === 0) { 22 | return Array.from({ length: limit }, (_, i) => i); 23 | } 24 | if (pageNumber === 1) { 25 | return Array.from({ length: 10 }, (_, i) => i); 26 | } 27 | 28 | return []; 29 | }; 30 | 31 | const pages = await fetchAllPages(fetcher); 32 | 33 | expect(pages.length).toEqual(5010); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /lib/util/fetch-all-pages.ts: -------------------------------------------------------------------------------- 1 | export const fetchAllPages = async ( 2 | fetcher: (pageNumber: number, limit: number) => Promise, 3 | ) => { 4 | const MAX_RECORDS = 5000; 5 | 6 | const records: T[] = []; 7 | let lastBatchLength; 8 | let pageNumber = 0; 9 | 10 | while (lastBatchLength == null || lastBatchLength === MAX_RECORDS) { 11 | const batch = await fetcher(pageNumber, MAX_RECORDS); 12 | records.push(...batch); 13 | pageNumber++; 14 | lastBatchLength = batch.length; 15 | } 16 | 17 | return records; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/util/fonts.ts: -------------------------------------------------------------------------------- 1 | //font optimization from @next/font 2 | import { Inter } from "next/font/google"; 3 | import { Kanit } from "next/font/google"; 4 | import { Roboto_Mono } from "next/font/google"; 5 | 6 | export const inter = Inter({ 7 | subsets: [ 8 | "cyrillic", 9 | "cyrillic-ext", 10 | "greek", 11 | "greek-ext", 12 | "latin", 13 | "latin-ext", 14 | "vietnamese", 15 | ], 16 | variable: "--font-inter", 17 | }); 18 | 19 | export const kanit = Kanit({ 20 | subsets: ["latin"], 21 | weight: "700", 22 | variable: "--font-kanit", 23 | }); 24 | 25 | export const roboto_mono = Roboto_Mono({ 26 | subsets: ["latin", "latin-ext"], 27 | weight: ["400", "700"], 28 | variable: "--font-roboto-mono", 29 | }); 30 | -------------------------------------------------------------------------------- /lib/util/format-compact.ts: -------------------------------------------------------------------------------- 1 | export const formatNumberCompact = ( 2 | num: number | bigint, 3 | maximumSignificantDigits = 3, 4 | ) => { 5 | // Ensure displaying absolute zeros are unsigned(-), because javascript sucks sometimes. 6 | if (num === 0 || num === 0n) num = 0; 7 | 8 | return new Intl.NumberFormat("en-US", { 9 | maximumSignificantDigits: maximumSignificantDigits, 10 | notation: "compact", 11 | }).format(num); 12 | }; 13 | -------------------------------------------------------------------------------- /lib/util/format-scalar-outcome.ts: -------------------------------------------------------------------------------- 1 | import { Decimal } from "decimal.js"; 2 | import { ZTG } from "../constants"; 3 | import type { ScalarRangeType } from "@zeitgeistpm/sdk"; 4 | 5 | export const formatScalarOutcome = ( 6 | outcome: string | number, 7 | scalarType: ScalarRangeType, 8 | ) => { 9 | return scalarType === "date" 10 | ? new Intl.DateTimeFormat("default", { 11 | dateStyle: "medium", 12 | }).format(new Decimal(outcome).div(ZTG).toNumber()) 13 | : new Intl.NumberFormat("default", { 14 | maximumSignificantDigits: 3, 15 | notation: "compact", 16 | }).format(new Decimal(outcome).div(ZTG).toNumber()); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/util/generate-guid.ts: -------------------------------------------------------------------------------- 1 | export const generateGUID = () => { 2 | return ( 3 | S4() + 4 | S4() + 5 | "-" + 6 | S4() + 7 | "-" + 8 | S4() + 9 | "-" + 10 | S4() + 11 | "-" + 12 | S4() + 13 | S4() + 14 | S4() 15 | ); 16 | }; 17 | 18 | const S4 = () => 19 | (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); 20 | -------------------------------------------------------------------------------- /lib/util/get-api-at.ts: -------------------------------------------------------------------------------- 1 | import type { ApiPromise } from "@polkadot/api"; 2 | 3 | export const getApiAtBlock = async ( 4 | api: ApiPromise, 5 | blockNumber?: number, 6 | ): Promise => { 7 | if (blockNumber != null) { 8 | const blockHash = await api.rpc.chain.getBlockHash(blockNumber); 9 | const apiAt = (await api.at(blockHash)) as ApiPromise; 10 | return apiAt; 11 | } else { 12 | return api; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /lib/util/get-query-params.ts: -------------------------------------------------------------------------------- 1 | import { parse as parseUri } from "uri-js"; 2 | 3 | /** 4 | 5 | Extracts query parameters from the given path and returns them as an object. 6 | Router from next.js does not provide a way to get only the query parameters from router, so this function is used to extract them. 7 | @param path - The path containing the query parameters. 8 | @returns An object representing the extracted query parameters. 9 | */ 10 | export const getQueryParams = ( 11 | path: string, 12 | ): { [key: string]: T } => { 13 | const url = parseUri(path); 14 | let queryParams = {}; 15 | const queryParamsArr = [...Array.from(new URLSearchParams(url.query))]; 16 | for (const pair of queryParamsArr) { 17 | queryParams[pair[0]] = pair[1]; 18 | } 19 | return queryParams; 20 | }; 21 | -------------------------------------------------------------------------------- /lib/util/getPlaiceHolders.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IGetPlaiceholderOptions, 3 | IGetPlaiceholderReturn, 4 | getPlaiceholder, 5 | } from "plaiceholder"; 6 | 7 | export const getPlaiceholders = ( 8 | paths: string[], 9 | options?: IGetPlaiceholderOptions, 10 | ): Promise => { 11 | return Promise.all(paths.map((path) => getPlaiceholder(path, options))); 12 | }; 13 | -------------------------------------------------------------------------------- /lib/util/hasDatePassed.ts: -------------------------------------------------------------------------------- 1 | //endTime must be in milliseconds from getTime() format 2 | export const hasDatePassed = (endTime: number) => { 3 | const currentTime = new Date(); 4 | const diff = endTime - currentTime.getTime(); 5 | return diff < 0; 6 | }; 7 | -------------------------------------------------------------------------------- /lib/util/index.ts: -------------------------------------------------------------------------------- 1 | import { decodeAddress, encodeAddress } from "@polkadot/keyring"; 2 | import { hexToU8a, isHex } from "@polkadot/util"; 3 | 4 | export const shortenAddress = ( 5 | address: string, 6 | sliceStart: number = 6, 7 | sliceEnd: number = 4, 8 | ) => { 9 | return `${address.slice(0, sliceStart)}...${address.slice(-sliceEnd)}`; 10 | }; 11 | 12 | const hexChars = [ 13 | "0", 14 | "1", 15 | "2", 16 | "3", 17 | "4", 18 | "5", 19 | "6", 20 | "7", 21 | "8", 22 | "9", 23 | "A", 24 | "B", 25 | "C", 26 | "D", 27 | "E", 28 | "F", 29 | ]; 30 | 31 | export const formatNumberLocalized = ( 32 | num: number | bigint, 33 | locale: string = "en-US", 34 | maximumFractionDigits: number = 2, 35 | ) => { 36 | // Ensure displaying absolute zeros are unsigned(-), because javascript sucks sometimes. 37 | if (num === 0 || num === 0n) num = 0; 38 | 39 | return new Intl.NumberFormat(locale, { maximumFractionDigits }).format(num); 40 | }; 41 | 42 | export const isValidPolkadotAddress = (address: string) => { 43 | try { 44 | encodeAddress(isHex(address) ? hexToU8a(address) : decodeAddress(address)); 45 | return true; 46 | } catch { 47 | return false; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /lib/util/is-amm2-market.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/lib/util/is-amm2-market.ts -------------------------------------------------------------------------------- /lib/util/is-current-origin.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Check if a url is same origin as the current page. 4 | * 5 | * @param url url to test 6 | * @returns boolean 7 | */ 8 | export const isCurrentOrigin = (url: string) => { 9 | if (!process.env.NEXT_PUBLIC_SITE_URL) return false; 10 | 11 | const currentOrigin = new URL( 12 | process.env.NEXT_PUBLIC_SITE_URL.match("vercel.app") 13 | ? `https://${process.env.NEXT_PUBLIC_SITE_URL}` 14 | : process.env.NEXT_PUBLIC_SITE_URL, 15 | ).origin; 16 | 17 | return new URL(currentOrigin).origin === new URL(url, currentOrigin).origin; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/util/lookup-price.ts: -------------------------------------------------------------------------------- 1 | import { IOForeignAssetId, parseAssetId } from "@zeitgeistpm/sdk"; 2 | import Decimal from "decimal.js"; 3 | import { ForeignAssetPrices } from "lib/hooks/queries/useAssetUsdPrice"; 4 | 5 | export const lookUpAssetPrice = ( 6 | baseAsset: string, 7 | foreignAssetPrices: ForeignAssetPrices, 8 | ztgPrice: Decimal, 9 | ) => { 10 | const assetId = parseAssetId(baseAsset).unwrap(); 11 | 12 | return IOForeignAssetId.is(assetId) 13 | ? foreignAssetPrices[assetId.ForeignAsset.toString()] 14 | : ztgPrice; 15 | }; 16 | -------------------------------------------------------------------------------- /lib/util/market-filter.ts: -------------------------------------------------------------------------------- 1 | import { MarketFilter } from "lib/types/market-filter"; 2 | 3 | /** 4 | * Return true if `a` and `b` filters are same, else return false 5 | */ 6 | export const compareMarketFilters = ( 7 | a: MarketFilter, 8 | b: MarketFilter, 9 | ): boolean => { 10 | return a.value === b.value && a.type === b.type; 11 | }; 12 | 13 | /** 14 | * Returns the index of the `searchItem` in the array and -1 otherwise. 15 | */ 16 | export const findFilterIndex = ( 17 | filters: MarketFilter[], 18 | searchItem: MarketFilter, 19 | ) => { 20 | return filters.findIndex((item) => { 21 | return compareMarketFilters(searchItem, item); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /lib/util/parse-asset-id.ts: -------------------------------------------------------------------------------- 1 | import { AssetId, parseAssetId } from "@zeitgeistpm/sdk"; 2 | 3 | export const parseAssetIdString = ( 4 | assetId?: string | AssetId, 5 | ): AssetId | undefined => { 6 | return assetId ? parseAssetId(assetId).unrightOr(undefined) : undefined; 7 | }; 8 | -------------------------------------------------------------------------------- /lib/util/perbill-to-number.ts: -------------------------------------------------------------------------------- 1 | import Decimal from "decimal.js"; 2 | 3 | export const ONE_BILLION = 10 ** 9; 4 | 5 | export function perbillToNumber(perbill: number): number; 6 | export function perbillToNumber(perbill: Decimal): Decimal; 7 | export function perbillToNumber(perbill: Decimal | number): Decimal | number { 8 | if (typeof perbill === "number") { 9 | return perbill / ONE_BILLION; 10 | } else { 11 | return perbill.div(ONE_BILLION); 12 | } 13 | } 14 | 15 | export function perbillToPrct(perbill: number): number; 16 | export function perbillToPrct(perbill: Decimal): Decimal; 17 | export function perbillToPrct(perbill: Decimal | number): Decimal | number { 18 | if (typeof perbill === "number") { 19 | return perbillToNumber(perbill) * 100; 20 | } else { 21 | return perbillToNumber(perbill).mul(100); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/util/poll.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test, vi } from "vitest"; 2 | import { poll, PollingTimeout } from "./poll"; 3 | 4 | describe("poll", () => { 5 | test("should only call input function once if it succeeds", async () => { 6 | const testFn = vi.fn(async () => { 7 | return new Promise((resolve) => { 8 | resolve("foo"); 9 | }); 10 | }); 11 | 12 | const res = await poll(testFn, { 13 | interval: 50, 14 | timeout: 1000, 15 | }); 16 | 17 | expect(res).toEqual("foo"); 18 | expect(testFn).toHaveBeenCalledTimes(1); 19 | }); 20 | 21 | test("should call function at least twice and timeout", async () => { 22 | const testFn = vi.fn(async () => { 23 | throw new Error("1) What"); 24 | }); 25 | 26 | const res = await poll(testFn, { 27 | interval: 1, 28 | timeout: 100, 29 | }); 30 | 31 | expect(res).toEqual(PollingTimeout); 32 | expect(testFn).toHaveBeenNthCalledWith(1); 33 | expect(testFn).toHaveBeenNthCalledWith(2); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /lib/util/unsub-or-warns.ts: -------------------------------------------------------------------------------- 1 | export const unsubOrWarns = (unsub: () => void) => { 2 | if (unsub) { 3 | unsub(); 4 | } else { 5 | console.warn( 6 | "Failing to unsubscribe from subscriptions could lead to memory bloat", 7 | ); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const { withPlaiceholder } = require("@plaiceholder/next"); 2 | 3 | module.exports = withPlaiceholder({ 4 | reactStrictMode: true, 5 | experimental: { 6 | esmExternals: true, 7 | scrollRestoration: true, 8 | }, 9 | images: { 10 | minimumCacheTTL: 10800, 11 | domains: [ 12 | "ipfs-gateway.zeitgeist.pm", 13 | "cdn.discordapp.com", 14 | "images.unsplash.com", 15 | "cdn.sanity.io", 16 | ], 17 | }, 18 | staticPageGenerationTimeout: 300, //5 mins 19 | }); 20 | -------------------------------------------------------------------------------- /pages/404.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { useRouter } from "next/router"; 3 | 4 | const NotFoundPage = ({ 5 | backText, 6 | backLink, 7 | }: { 8 | backText?: string; 9 | backLink?: string; 10 | }) => { 11 | const router = useRouter(); 12 | const src = "/light-404.png"; 13 | 14 | const handleClick = () => { 15 | if (backLink) router.push(backLink); 16 | }; 17 | 18 | return ( 19 | <> 20 | 404 Page 21 | {backText && backLink ? ( 22 |

23 | 29 |
30 | ) : ( 31 | <> 32 | )} 33 | 34 | ); 35 | }; 36 | 37 | export default NotFoundPage; 38 | -------------------------------------------------------------------------------- /pages/api/cms/market-metadata/batch/index.ts: -------------------------------------------------------------------------------- 1 | import { getCmsFullMarketMetadataForMarkets } from "lib/cms/markets"; 2 | import { PageConfig } from "next"; 3 | import { NextRequest } from "next/server"; 4 | 5 | export const config: PageConfig = { 6 | runtime: "edge", 7 | }; 8 | 9 | export default async function handler(request: NextRequest) { 10 | try { 11 | const marketIdsRaw = new URL(request.url).searchParams.get("marketIds"); 12 | const marketIds = marketIdsRaw ? JSON.parse(marketIdsRaw) : null; 13 | 14 | if (!marketIds) { 15 | return new Response(`Request needs market ids in params`, { 16 | status: 400, 17 | }); 18 | } 19 | 20 | if (!Array.isArray(marketIds)) { 21 | return new Response(`Request market ids needs to be an array`, { 22 | status: 400, 23 | }); 24 | } 25 | 26 | const metadata = await getCmsFullMarketMetadataForMarkets( 27 | marketIds.map((m) => Number(m)), 28 | ); 29 | 30 | return new Response(JSON.stringify(metadata), { 31 | headers: { 32 | "Cache-Control": "public, s-maxage=180, stale-while-revalidate=21600", 33 | }, 34 | }); 35 | } catch (error) { 36 | console.error(error); 37 | return new Response(JSON.stringify({ error: error.message }), { 38 | status: 500, 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pages/api/ipfs/types.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | export const IOMarketMetadata = z.object({ 4 | question: z.string(), 5 | description: z.optional(z.string()), 6 | tags: z.optional(z.array(z.string())), 7 | slug: z.optional(z.string()), 8 | categories: z.optional( 9 | z.array( 10 | z.object({ 11 | name: z.string(), 12 | ticker: z.optional(z.string()), 13 | img: z.optional(z.string()), 14 | color: z.optional(z.string()), 15 | }), 16 | ), 17 | ), 18 | }); 19 | -------------------------------------------------------------------------------- /pages/api/location.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | 3 | export default function getIPLocation( 4 | request: NextApiRequest, 5 | response: NextApiResponse, 6 | ) { 7 | const country = request.headers["x-vercel-ip-country"]; 8 | const region = request.headers["x-vercel-ip-country-region"]; 9 | const city = request.headers["x-vercel-ip-city"]; 10 | const ip = request.headers["x-forwarded-for"]; 11 | 12 | response.status(200).json({ 13 | body: { 14 | country: country, 15 | region: region, 16 | city: city, 17 | ip: ip, 18 | }, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /pages/api/revalidate.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | 3 | export default async function handler( 4 | request: NextApiRequest, 5 | response: NextApiResponse, 6 | ) { 7 | if (request.query.token !== process.env.REVALIDATION_TOKEN) { 8 | return response.status(401).json({ message: "Invalid token" }); 9 | } 10 | 11 | await response.revalidate("/"); 12 | return response.json({ success: true, revalidated: true }); 13 | } 14 | -------------------------------------------------------------------------------- /pages/avatar/index.tsx: -------------------------------------------------------------------------------- 1 | import { useWallet } from "lib/state/wallet"; 2 | import { useRouter } from "next/router"; 3 | import NotFoundPage from "pages/404"; 4 | import * as React from "react"; 5 | 6 | const Avatar = () => { 7 | const router = useRouter(); 8 | const wallet = useWallet(); 9 | 10 | if (wallet.activeAccount) { 11 | router.replace(`/avatar/${wallet.activeAccount?.address}`); 12 | return <>; 13 | } 14 | 15 | return ; 16 | }; 17 | 18 | export default Avatar; 19 | -------------------------------------------------------------------------------- /pages/create.tsx: -------------------------------------------------------------------------------- 1 | import MarketEditor from "components/create/editor/Editor"; 2 | import { NextPage } from "next"; 3 | 4 | const CreateMarketPage: NextPage = () => { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | }; 11 | 12 | export default CreateMarketPage; 13 | -------------------------------------------------------------------------------- /pages/latest-trades.tsx: -------------------------------------------------------------------------------- 1 | import LatestTrades from "components/front-page/LatestTrades"; 2 | import { NextPage } from "next"; 3 | import { useRouter } from "next/router"; 4 | 5 | const LatestTradesPage: NextPage = () => { 6 | const { query } = useRouter(); 7 | const marketId = query["marketId"]; 8 | 9 | return ( 10 |
11 |

12 | Latest Trades 13 |

14 | 18 |
19 | ); 20 | }; 21 | 22 | export default LatestTradesPage; 23 | -------------------------------------------------------------------------------- /pages/markets/favorites.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, dehydrate } from "@tanstack/query-core"; 2 | import FavoriteMarketsList from "components/markets/FavoriteMarketsList"; 3 | import { getCmsMarketCardMetadataForAllMarkets } from "lib/cms/markets"; 4 | import { environment } from "lib/constants"; 5 | import { marketCmsDatakeyForMarket } from "lib/hooks/queries/cms/useMarketCmsMetadata"; 6 | import { NextPage } from "next"; 7 | 8 | const FavoriteMarketsPage: NextPage = () => { 9 | return ; 10 | }; 11 | 12 | export async function getStaticProps() { 13 | const queryClient = new QueryClient(); 14 | const cmsData = await getCmsMarketCardMetadataForAllMarkets(); 15 | 16 | for (const marketCmsData of cmsData) { 17 | if (marketCmsData.marketId) { 18 | queryClient.setQueryData( 19 | marketCmsDatakeyForMarket(marketCmsData.marketId), 20 | marketCmsData, 21 | ); 22 | } 23 | } 24 | 25 | return { 26 | props: { 27 | dehydratedState: dehydrate(queryClient), 28 | }, 29 | revalidate: 30 | environment === "production" 31 | ? 3 * 60 //3 min 32 | : 60 * 60, 33 | }; 34 | } 35 | 36 | export default FavoriteMarketsPage; 37 | -------------------------------------------------------------------------------- /pages/markets/index.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, dehydrate } from "@tanstack/query-core"; 2 | import MarketsList from "components/markets/MarketsList"; 3 | import { getCmsMarketCardMetadataForAllMarkets } from "lib/cms/markets"; 4 | import { environment } from "lib/constants"; 5 | import { marketCmsDatakeyForMarket } from "lib/hooks/queries/cms/useMarketCmsMetadata"; 6 | import { NextPage } from "next"; 7 | 8 | const MarketsPage: NextPage = ({ 9 | cmsTopicPlaceholders, 10 | }: { 11 | cmsTopicPlaceholders: string[]; 12 | }) => { 13 | return ; 14 | }; 15 | 16 | export async function getStaticProps() { 17 | const queryClient = new QueryClient(); 18 | 19 | const [cmsMarketMetaData] = await Promise.all([ 20 | getCmsMarketCardMetadataForAllMarkets(), 21 | ]); 22 | 23 | for (const marketCmsData of cmsMarketMetaData) { 24 | if (marketCmsData.marketId) { 25 | queryClient.setQueryData( 26 | marketCmsDatakeyForMarket(marketCmsData.marketId), 27 | marketCmsData, 28 | ); 29 | } 30 | } 31 | 32 | return { 33 | props: { 34 | dehydratedState: dehydrate(queryClient), 35 | }, 36 | revalidate: 37 | environment === "production" 38 | ? 3 * 60 //3 min 39 | : 60 * 60, 40 | }; 41 | } 42 | 43 | export default MarketsPage; 44 | -------------------------------------------------------------------------------- /pages/portfolio/index.tsx: -------------------------------------------------------------------------------- 1 | import PortfolioLayout from "layouts/PortfolioLayout"; 2 | import { NextPageWithLayout } from "layouts/types"; 3 | 4 | const PortfolioIndex: NextPageWithLayout = () => { 5 | return <>; 6 | }; 7 | 8 | PortfolioIndex.Layout = PortfolioLayout; 9 | 10 | export default PortfolioIndex; 11 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/Leaderboard-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/Leaderboard-banner.png -------------------------------------------------------------------------------- /public/Revised_Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/Zeitgeist-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/Zeitgeist-trans.png -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/android-chrome-256x256.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/avatar_preview.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/avatar_preview.jpeg -------------------------------------------------------------------------------- /public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/banner.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/carousel/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/carousel/banner.png -------------------------------------------------------------------------------- /public/carousel/intro_zeitgeist_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/carousel/intro_zeitgeist_avatar.png -------------------------------------------------------------------------------- /public/categories/crypto/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/crypto/1.png -------------------------------------------------------------------------------- /public/categories/crypto/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/crypto/2.png -------------------------------------------------------------------------------- /public/categories/crypto/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/crypto/3.png -------------------------------------------------------------------------------- /public/categories/crypto/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/crypto/4.png -------------------------------------------------------------------------------- /public/categories/dotsama/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/dotsama/1.png -------------------------------------------------------------------------------- /public/categories/entertainment/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/entertainment/1.png -------------------------------------------------------------------------------- /public/categories/entertainment/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/entertainment/2.png -------------------------------------------------------------------------------- /public/categories/entertainment/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/entertainment/3.png -------------------------------------------------------------------------------- /public/categories/entertainment/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/entertainment/4.png -------------------------------------------------------------------------------- /public/categories/esports/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/esports/1.png -------------------------------------------------------------------------------- /public/categories/esports/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/esports/2.png -------------------------------------------------------------------------------- /public/categories/esports/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/esports/3.png -------------------------------------------------------------------------------- /public/categories/esports/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/esports/4.png -------------------------------------------------------------------------------- /public/categories/esports/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/esports/5.png -------------------------------------------------------------------------------- /public/categories/finance/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/finance/1.png -------------------------------------------------------------------------------- /public/categories/finance/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/finance/2.png -------------------------------------------------------------------------------- /public/categories/finance/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/finance/3.png -------------------------------------------------------------------------------- /public/categories/finance/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/finance/4.png -------------------------------------------------------------------------------- /public/categories/news/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/news/1.png -------------------------------------------------------------------------------- /public/categories/news/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/news/2.png -------------------------------------------------------------------------------- /public/categories/news/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/news/3.png -------------------------------------------------------------------------------- /public/categories/news/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/news/4.png -------------------------------------------------------------------------------- /public/categories/news/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/news/5.png -------------------------------------------------------------------------------- /public/categories/politics/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/politics/1.png -------------------------------------------------------------------------------- /public/categories/politics/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/politics/2.png -------------------------------------------------------------------------------- /public/categories/politics/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/politics/3.png -------------------------------------------------------------------------------- /public/categories/politics/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/politics/4.png -------------------------------------------------------------------------------- /public/categories/science/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/science/1.png -------------------------------------------------------------------------------- /public/categories/science/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/science/2.png -------------------------------------------------------------------------------- /public/categories/science/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/science/3.png -------------------------------------------------------------------------------- /public/categories/science/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/science/4.png -------------------------------------------------------------------------------- /public/categories/sports/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/sports/1.png -------------------------------------------------------------------------------- /public/categories/sports/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/sports/2.png -------------------------------------------------------------------------------- /public/categories/sports/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/sports/3.png -------------------------------------------------------------------------------- /public/categories/sports/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/sports/4.png -------------------------------------------------------------------------------- /public/categories/sports/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/sports/5.png -------------------------------------------------------------------------------- /public/categories/tech/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/tech/1.png -------------------------------------------------------------------------------- /public/categories/tech/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/tech/2.png -------------------------------------------------------------------------------- /public/categories/tech/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/tech/3.png -------------------------------------------------------------------------------- /public/categories/tech/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/tech/4.png -------------------------------------------------------------------------------- /public/categories/zeitgeist/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/categories/zeitgeist/1.png -------------------------------------------------------------------------------- /public/category/crypto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/category/crypto.png -------------------------------------------------------------------------------- /public/category/dotsama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/category/dotsama.png -------------------------------------------------------------------------------- /public/category/entertainment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/category/entertainment.png -------------------------------------------------------------------------------- /public/category/finance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/category/finance.png -------------------------------------------------------------------------------- /public/category/news.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/category/news.png -------------------------------------------------------------------------------- /public/category/politics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/category/politics.png -------------------------------------------------------------------------------- /public/category/science.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/category/science.png -------------------------------------------------------------------------------- /public/category/sports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/category/sports.png -------------------------------------------------------------------------------- /public/category/technology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/category/technology.png -------------------------------------------------------------------------------- /public/category/zeitgeist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/category/zeitgeist.png -------------------------------------------------------------------------------- /public/court.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/court.png -------------------------------------------------------------------------------- /public/court_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/court_banner.png -------------------------------------------------------------------------------- /public/court_gnomes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/court_gnomes.png -------------------------------------------------------------------------------- /public/crypto_wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/crypto_wizard.png -------------------------------------------------------------------------------- /public/currencies/assethub.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/currencies/ausd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/currencies/ausd.jpg -------------------------------------------------------------------------------- /public/currencies/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/currencies/dot.png -------------------------------------------------------------------------------- /public/currencies/dot_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/currencies/dot_filled.png -------------------------------------------------------------------------------- /public/currencies/dot_filled_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/currencies/dot_filled_black.png -------------------------------------------------------------------------------- /public/currencies/moonbeam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/currencies/moonbeam.png -------------------------------------------------------------------------------- /public/currencies/rococo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/currencies/rococo.png -------------------------------------------------------------------------------- /public/currencies/usdt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/currencies/usdt.png -------------------------------------------------------------------------------- /public/currencies/ztg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/currencies/ztg.jpg -------------------------------------------------------------------------------- /public/currencies/ztg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/currencies/ztg.png -------------------------------------------------------------------------------- /public/currencies/ztg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/currencies/ztg_neue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/currencies/ztg_neue.png -------------------------------------------------------------------------------- /public/dark-404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/dark-404.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/favicon.ico -------------------------------------------------------------------------------- /public/featured/Kanaria_NFT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/featured/Kanaria_NFT.png -------------------------------------------------------------------------------- /public/featured/Kusama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/featured/Kusama.png -------------------------------------------------------------------------------- /public/featured/Polkadot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/featured/Polkadot.png -------------------------------------------------------------------------------- /public/fonts/inter/static/Inter-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/fonts/inter/static/Inter-Black.ttf -------------------------------------------------------------------------------- /public/fonts/inter/static/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/fonts/inter/static/Inter-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/inter/static/Inter-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/fonts/inter/static/Inter-ExtraBold.ttf -------------------------------------------------------------------------------- /public/fonts/inter/static/Inter-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/fonts/inter/static/Inter-ExtraLight.ttf -------------------------------------------------------------------------------- /public/fonts/inter/static/Inter-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/fonts/inter/static/Inter-Light.ttf -------------------------------------------------------------------------------- /public/fonts/inter/static/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/fonts/inter/static/Inter-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/inter/static/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/fonts/inter/static/Inter-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/inter/static/Inter-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/fonts/inter/static/Inter-SemiBold.ttf -------------------------------------------------------------------------------- /public/fonts/inter/static/Inter-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/fonts/inter/static/Inter-Thin.ttf -------------------------------------------------------------------------------- /public/fonts/inter/variable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/fonts/inter/variable.ttf -------------------------------------------------------------------------------- /public/icons/ZTG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/icons/acc-balance.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/icons/default-market.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/icons/default-market.png -------------------------------------------------------------------------------- /public/icons/discord.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/facebook-f.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/icons/google-g.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/new-moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/nova.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/icons/nova.png -------------------------------------------------------------------------------- /public/icons/polkadot-js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/icons/polkadot-js.png -------------------------------------------------------------------------------- /public/icons/singular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/subwallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/icons/subwallet.png -------------------------------------------------------------------------------- /public/icons/talisman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/icons/talisman.png -------------------------------------------------------------------------------- /public/icons/telegram.svg: -------------------------------------------------------------------------------- 1 | Telegram_logo -------------------------------------------------------------------------------- /public/icons/unlock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/icons/verified-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/walletconnect-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/x-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/learn/create_account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/learn/create_account.png -------------------------------------------------------------------------------- /public/learn/deposit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/learn/deposit.png -------------------------------------------------------------------------------- /public/learn/learn-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/learn/learn-1.png -------------------------------------------------------------------------------- /public/learn/learn-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/learn/learn-2.png -------------------------------------------------------------------------------- /public/learn/learn-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/learn/learn-3.png -------------------------------------------------------------------------------- /public/learn/start_trading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/learn/start_trading.png -------------------------------------------------------------------------------- /public/light-404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/light-404.png -------------------------------------------------------------------------------- /public/misc/portal_gate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/misc/portal_gate.png -------------------------------------------------------------------------------- /public/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/nft/circles-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/nft/circles-background.png -------------------------------------------------------------------------------- /public/nft/ellipse-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/nft/ellipse-background.png -------------------------------------------------------------------------------- /public/og/bg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/og/bg1.png -------------------------------------------------------------------------------- /public/og/zeitgeist_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/og/zeitgeist_badge.png -------------------------------------------------------------------------------- /public/polkadot_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/polkadot_icon.png -------------------------------------------------------------------------------- /public/singular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/singular.png -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /public/support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeitgeistpm/ui/9bd3cbd5c1bf3ec95e6b3fea3c740cf42dcd69ce/public/support.png -------------------------------------------------------------------------------- /public/ztg_8.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/mts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es6", 5 | "lib": ["esnext", "dom"], 6 | "allowJs": false, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "ESNext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true 14 | }, 15 | "include": ["*.mts"], 16 | "exclude": [] 17 | } 18 | -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es6", 5 | "lib": ["esnext", "dom"], 6 | "allowJs": false, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "CommonJS", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true 14 | }, 15 | "include": ["*.ts"], 16 | "exclude": [] 17 | } 18 | -------------------------------------------------------------------------------- /styles/card.css: -------------------------------------------------------------------------------- 1 | div[class^="card-exp-col"] { 2 | @apply mr-ztg-16 flex-shrink-0; 3 | } 4 | div[class^="card-exp-col"]:last-child { 5 | @apply mr-ztg-0; 6 | } 7 | 8 | .card-exp-col-1 { 9 | @apply flex-ztg-basis-80; 10 | } 11 | .card-exp-col-2 { 12 | @apply flex-ztg-basis-54 text-right; 13 | } 14 | .card-exp-col-3 { 15 | @apply flex-ztg-basis-85 text-right; 16 | } 17 | .card-exp-col-4 { 18 | @apply flex-ztg-basis-115 text-right; 19 | } 20 | .card-exp-col-5 { 21 | @apply flex-ztg-basis-66; 22 | } 23 | .card-exp-col-6 { 24 | @apply flex-ztg-basis-100; 25 | } 26 | -------------------------------------------------------------------------------- /styles/date-picker.css: -------------------------------------------------------------------------------- 1 | .dark > * .rdtPicker { 2 | background-color: black; 3 | } 4 | 5 | .rdtPicker { 6 | @apply text-sky-600; 7 | } 8 | 9 | .dark > * .rdtPicker > * td:hover { 10 | @apply bg-sky-900 !important; 11 | } 12 | .dark > * .rdtPicker > * th:hover { 13 | @apply bg-sky-900 !important; 14 | } 15 | .dark > * .rdtPicker > * span:hover { 16 | @apply bg-sky-900 !important; 17 | border: 0; 18 | } 19 | -------------------------------------------------------------------------------- /styles/drawer.css: -------------------------------------------------------------------------------- 1 | .drawer.left .arrow-container { 2 | @apply right-0; 3 | transform: translateX(50%); 4 | } 5 | 6 | .drawer.right .arrow-container { 7 | @apply left-0; 8 | @apply -translate-x-1/2 transform; 9 | } 10 | 11 | .drawer.right.closed .arrow-container { 12 | transform: translateX(calc(-100% - 4px)); 13 | } 14 | -------------------------------------------------------------------------------- /styles/kusama-derby.css: -------------------------------------------------------------------------------- 1 | .list-kusama-rectangle { 2 | list-style-image: url("/icons/rectangle-2.svg"); 3 | } 4 | .list-kusama-donut { 5 | list-style-image: url("/icons/donut.svg"); 6 | } 7 | 8 | ul#howList li { 9 | margin-left: -25px; 10 | } 11 | 12 | #slotsBox h3 { 13 | mix-blend-mode: exclusion; 14 | } 15 | #slotsBox p { 16 | @apply transition-colors; 17 | @apply duration-500; 18 | 19 | /* mix-blend-mode: exclusion; */ 20 | } 21 | 22 | #slotsBox p > span { 23 | mix-blend-mode: exclusion; 24 | } 25 | 26 | #slotBox1:hover p { 27 | @apply text-green-1; 28 | } 29 | 30 | #slotBox2:hover p { 31 | @apply text-orange-1; 32 | } 33 | 34 | #slotBox3:hover p { 35 | @apply text-purple-1; 36 | } 37 | 38 | .leader-indicator { 39 | background: radial-gradient( 40 | 99.83% 99.83% at 50% 0%, 41 | #ff8001 0%, 42 | #ffd100 100% 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /styles/range-component.css: -------------------------------------------------------------------------------- 1 | input[type="range"] { 2 | -webkit-appearance: none; 3 | height: 2px; 4 | border-radius: 5px; 5 | background: black; 6 | background-size: 70% 100%; 7 | background-repeat: no-repeat; 8 | } 9 | 10 | input[type="range"]::-webkit-slider-thumb { 11 | -webkit-appearance: none; 12 | display: block; 13 | height: 20px; 14 | width: 40px; 15 | @apply rounded-[30px]; 16 | @apply border-[2px] border-solid border-black; 17 | background: white; 18 | cursor: ew-resize; 19 | box-shadow: 0 0 2px 0 #555; 20 | } 21 | 22 | input[type="range"]::-moz-range-thumb { 23 | display: block; 24 | height: 20px; 25 | width: 40px; 26 | @apply rounded-[30px]; 27 | @apply border-[2px] border-solid border-black; 28 | background: white; 29 | cursor: ew-resize; 30 | box-shadow: 0 0 2px 0 #555; 31 | } 32 | 33 | input[type="range"]::-webkit-slider-runnable-track { 34 | -webkit-appearance: none; 35 | box-shadow: none; 36 | border: none; 37 | background: transparent; 38 | } 39 | 40 | input[type="range"]::-moz-range-track { 41 | box-shadow: none; 42 | border: none; 43 | background: transparent; 44 | } 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "strictNullChecks": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "noEmit": true, 15 | "esModuleInterop": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "jsx": "preserve", 21 | "experimentalDecorators": true, 22 | "useDefineForClassFields": true, 23 | "baseUrl": "./", 24 | "incremental": true, 25 | "downlevelIteration": true, 26 | "plugins": [ 27 | { 28 | "name": "next" 29 | } 30 | ] 31 | }, 32 | "include": [ 33 | "next-env.d.ts", 34 | "declarations.d.ts", 35 | "**/*.ts", 36 | "**/*.tsx", 37 | "**/*.jsx", 38 | "**/*.js", 39 | ".next/types/**/*.ts" 40 | ], 41 | "filesGlobs": [], 42 | "exclude": [ 43 | "node_modules", 44 | "cypress", 45 | "scripts" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, defaultExclude } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: ["**/*.{test,spec}.?(c|m)[jt]s?(x)"], 6 | exclude: [...defaultExclude, "e2e/**"], 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /wsx-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "VERCEL_GIT_COMMIT_REF: $VERCEL_GIT_COMMIT_REF" 4 | 5 | if [[ "$VERCEL_GIT_COMMIT_REF" == "wsx" || "$VERCEL_GIT_COMMIT_REF" == "wsx-staging" ]] ; then 6 | # Proceed with the build 7 | echo "✅ - Build can proceed" 8 | exit 1; 9 | 10 | else 11 | # Don't build 12 | echo "🛑 - Build cancelled" 13 | exit 0; 14 | fi -------------------------------------------------------------------------------- /ztg-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "VERCEL_GIT_COMMIT_REF: $VERCEL_GIT_COMMIT_REF" 4 | 5 | if [[ "$VERCEL_GIT_COMMIT_REF" == "wsx" || "$VERCEL_GIT_COMMIT_REF" == "wsx-staging" ]] ; then 6 | # Don't build 7 | echo "🛑 - Build cancelled" 8 | exit 0; 9 | 10 | else 11 | # Proceed with the build 12 | echo "✅ - Build can proceed" 13 | exit 1; 14 | fi --------------------------------------------------------------------------------