diff --git a/.env.example b/.env.example index 9deb1c62c..bb01b4c2d 100644 --- a/.env.example +++ b/.env.example @@ -48,6 +48,10 @@ NEXT_PUBLIC_OTHER_TAGS=["Coindesk"] NEXT_PUBLIC_COIN_GECKO_API_KEY= +# sanity cms config NEXT_PUBLIC_SANITY_PROJECT_ID="4wbnjof1" NEXT_PUBLIC_SANITY_VERSION="2022-03-07" +# enable topics +NEXT_PUBLIC_SHOW_TOPICS=true + diff --git a/components/front-page/HeroBanner.tsx b/components/front-page/HeroBanner.tsx index c1c59a038..97280a5bc 100644 --- a/components/front-page/HeroBanner.tsx +++ b/components/front-page/HeroBanner.tsx @@ -24,7 +24,7 @@ export const HeroBanner = ({ const prctChange = ((latestPrice - firstPrice) / firstPrice) * 100; return ( -
+

diff --git a/components/front-page/Topics.tsx b/components/front-page/Topics.tsx new file mode 100644 index 000000000..4b0653684 --- /dev/null +++ b/components/front-page/Topics.tsx @@ -0,0 +1,108 @@ +import Carousel from "components/ui/Carousel"; +import { CmsTopicHeader } from "lib/cms/topics"; +import { chunk, isObject, isString } from "lodash-es"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/router"; + +export const Topics = ({ + topics, + imagePlaceholders, + selectedTopic, + onClick, +}: { + topics: CmsTopicHeader[]; + imagePlaceholders: string[]; + selectedTopic?: CmsTopicHeader | string; + onClick?: (topic: CmsTopicHeader) => void; +}) => { + const router = useRouter(); + + return ( + <> +
+ {topics.map((topic, index) => ( + { + if (onClick) { + e.preventDefault(); + onClick(topic); + } else { + router.push(`/topics/${topic.slug}`); + } + }} + > +
+ {`Image +
+
+

{topic.title}

+
+ + ))} +
+
+ ( +
+ {topics.map((topic, index) => ( + +
+ {`Image +
+
+

+ {topic.title} +

+
+ + ))} +
+ ))} + /> +
+ + ); +}; diff --git a/components/markets/MarketsList.tsx b/components/markets/MarketsList.tsx index ecaf26212..425207cad 100644 --- a/components/markets/MarketsList.tsx +++ b/components/markets/MarketsList.tsx @@ -14,9 +14,13 @@ import useMarketsUrlQuery from "lib/hooks/useMarketsUrlQuery"; import { filterTypes } from "lib/constants/market-filter"; import { ZTG } from "lib/constants"; import { useMarketsStats } from "lib/hooks/queries/useMarketsStats"; +import { CmsTopicHeader } from "lib/cms/topics"; +import { Topics } from "components/front-page/Topics"; export type MarketsListProps = { className?: string; + cmsTopics: CmsTopicHeader[]; + cmsTopicPlaceholders: string[]; }; const useChangeQuery = ( @@ -55,7 +59,11 @@ const useChangeQuery = ( }, [withLiquidityOnly]); }; -const MarketsList = ({ className = "" }: MarketsListProps) => { +const MarketsList = ({ + className = "", + cmsTopics, + cmsTopicPlaceholders, +}: MarketsListProps) => { const [filters, setFilters] = useState(); const [orderBy, setOrderBy] = useState(); const [withLiquidityOnly, setWithLiquidityOnly] = useState(); @@ -96,11 +104,18 @@ const MarketsList = ({ className = "" }: MarketsListProps) => { data-testid="marketsList" id={"market-list"} > + {process.env.NEXT_PUBLIC_SHOW_TOPICS === "true" && ( +
+ +
+ )} + +
{markets?.map((market) => { const volume = market.volume; diff --git a/components/markets/market-card/index.tsx b/components/markets/market-card/index.tsx index 206c787c6..65d8f6fca 100644 --- a/components/markets/market-card/index.tsx +++ b/components/markets/market-card/index.tsx @@ -242,7 +242,7 @@ export const MarketCard = ({
; + options?: EmblaOptionsType; +}; + +const Carousel: React.FC = (props) => { + const { slides, options } = props; + const [emblaRef, emblaApi] = useEmblaCarousel(options); + + const { + prevBtnDisabled, + nextBtnDisabled, + onPrevButtonClick, + onNextButtonClick, + } = usePrevNextButtons(emblaApi); + + return ( +
+
+ + +
+ +
+
+
+ {slides.map((slide, index) => ( +
+
+ {slide} +
+
+ ))} +
+
+
+
+ ); +}; + +type UsePrevNextButtonsType = { + prevBtnDisabled: boolean; + nextBtnDisabled: boolean; + onPrevButtonClick: () => void; + onNextButtonClick: () => void; +}; + +export const usePrevNextButtons = ( + emblaApi: EmblaCarouselType | undefined, +): UsePrevNextButtonsType => { + const [prevBtnDisabled, setPrevBtnDisabled] = useState(true); + const [nextBtnDisabled, setNextBtnDisabled] = useState(true); + + const onPrevButtonClick = useCallback(() => { + if (!emblaApi) return; + emblaApi.scrollPrev(); + }, [emblaApi]); + + const onNextButtonClick = useCallback(() => { + if (!emblaApi) return; + emblaApi.scrollNext(); + }, [emblaApi]); + + const onSelect = useCallback((emblaApi: EmblaCarouselType) => { + setPrevBtnDisabled(!emblaApi.canScrollPrev()); + setNextBtnDisabled(!emblaApi.canScrollNext()); + }, []); + + useEffect(() => { + if (!emblaApi) return; + + onSelect(emblaApi); + emblaApi.on("reInit", onSelect); + emblaApi.on("select", onSelect); + }, [emblaApi, onSelect]); + + return { + prevBtnDisabled, + nextBtnDisabled, + onPrevButtonClick, + onNextButtonClick, + }; +}; + +export default Carousel; diff --git a/layouts/DefaultLayout.tsx b/layouts/DefaultLayout.tsx index 15ade3505..ce5169e1d 100644 --- a/layouts/DefaultLayout.tsx +++ b/layouts/DefaultLayout.tsx @@ -29,6 +29,7 @@ const greyBackgroundPageRoutes = [ "/markets", "/create-account", "/deposit", + "/topics", ]; const DefaultLayout: FC = ({ children }) => { @@ -47,7 +48,8 @@ const DefaultLayout: FC = ({ children }) => { return (
url, + "marketIds": markets[].marketId, +}`; + +export const getCmsTopicHeaders = async (): Promise => { + const data = await sanity.fetch( + groq`*[_type == "topic"] | order(orderRank) ${topicHeaderFields}`, + ); + + return data; +}; + +const topicFullFields = groq`{ + title, + description, + "slug": slug.current, + "thumbnail": thumbnail.asset->url, + "banner": banner, + "bannerBlurData": banner.asset->metadata.lqip, + "marketIds": markets[].marketId +}`; + +export const getCmsFullTopic = async (slug: string): Promise => { + const data = await sanity.fetch( + groq`*[_type == "topic" && slug.current == "${slug}"] | order(orderRank) ${topicFullFields}`, + ); + + return data[0]; +}; + +export const marketsForTopic = async ( + topic: CmsTopicFull | CmsTopicHeader, + indexer: ZeitgeistIndexer, + opts?: { + limit?: number; + }, +) => { + const { markets } = await indexer.markets({ + where: { + marketId_in: topic.marketIds, + }, + limit: opts?.limit, + }); + + const stats = await getMarketsStats( + indexer.client, + markets.map((m) => m.marketId), + ); + + const marketCardsData = markets + .map((market) => { + if (!market || !market.categories) return; + + const marketCategories: MarketOutcomes = market.categories + .map((category, index) => { + const asset = market.assets[index]; + + if (!asset) return; + + const marketCategory: MarketOutcome = { + name: category.name ?? "", + assetId: market.outcomeAssets[index], + price: asset.price, + }; + + return marketCategory; + }) + .filter(isNotNull); + + const prediction = getCurrentPrediction(market.assets, market); + + const marketCardData: IndexedMarketCardData = { + marketId: market.marketId, + question: market.question ?? "", + creation: market.creation, + img: market.img ?? "", + prediction: prediction, + creator: market.creator, + volume: Number(new Decimal(market?.volume ?? 0).div(ZTG).toFixed(0)), + baseAsset: market.baseAsset, + outcomes: marketCategories, + pool: market.pool ?? null, + neoPool: market.neoPool, + marketType: market.marketType as any, + tags: market.tags?.filter(isNotNull), + status: market.status, + scalarType: (market.scalarType ?? null) as "number" | "date" | null, + endDate: market.period.end, + numParticipants: stats.find((s) => s.marketId === market.marketId) + ?.participants, + liquidity: stats.find((s) => s.marketId === market.marketId)?.liquidity, + }; + + return marketCardData; + }) + .filter(isNotNull); + + marketCardsData.sort((a, b) => { + return ( + topic.marketIds?.findIndex((m) => m === a.marketId) - + topic.marketIds?.findIndex((m) => m === b.marketId) + ); + }); + + return marketCardsData; +}; diff --git a/lib/util/assets.ts b/lib/util/assets.ts index f6a3046ca..737009f4e 100644 --- a/lib/util/assets.ts +++ b/lib/util/assets.ts @@ -14,6 +14,10 @@ export const getCurrentPrediction = ( ): { name: string; price: number; percentage: number } => { const totalPrice = assets.reduce((acc, asset) => acc + asset.price, 0); + if (assets?.length < 2) { + return { name: "N/A", price: 0, percentage: 0 }; + } + if (market?.marketType?.categorical) { let [highestPrice, highestPriceIndex] = [0, 0]; assets.sort( diff --git a/lib/util/getPlaiceHolders.ts b/lib/util/getPlaiceHolders.ts new file mode 100644 index 000000000..f6a46dc61 --- /dev/null +++ b/lib/util/getPlaiceHolders.ts @@ -0,0 +1,12 @@ +import { + IGetPlaiceholderOptions, + IGetPlaiceholderReturn, + getPlaiceholder, +} from "plaiceholder"; + +export const getPlaiceholders = ( + paths: string[], + options?: IGetPlaiceholderOptions, +): Promise => { + return Promise.all(paths.map((path) => getPlaiceholder(path, options))); +}; diff --git a/package.json b/package.json index 0aa673dd2..dc6331319 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "boring-avatars": "^1.6.1", "decimal.js": "^10.4.3", "dotenv": "^9.0.2", + "embla-carousel": "^8.0.0-rc19", + "embla-carousel-react": "^8.0.0-rc19", "fathom-client": "^3.2.0", "flexsearch": "^0.7.21", "font-color-contrast": "^11.1.0", @@ -66,6 +68,7 @@ "next": "^13.4.19", "next-absolute-url": "^1.2.2", "next-qrcode": "^2.5.1", + "next-sanity-image": "^6.1.1", "object-hash": "^2.2.0", "plaiceholder": "^2.5.0", "pure-react-carousel": "^1.27.8", diff --git a/pages/index.tsx b/pages/index.tsx index dd9e87284..24f06003d 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,3 +1,4 @@ +import { Disclosure } from "@headlessui/react"; import { GenericChainProperties } from "@polkadot/types"; import { dehydrate, QueryClient } from "@tanstack/react-query"; import { create, ZeitgeistIpfs } from "@zeitgeistpm/sdk"; @@ -10,12 +11,20 @@ import { NewsSection } from "components/front-page/News"; import PopularCategories, { CATEGORIES, } from "components/front-page/PopularCategories"; +import { Topics } from "components/front-page/Topics"; import WatchHow from "components/front-page/WatchHow"; -import { IndexedMarketCardData } from "components/markets/market-card"; +import MarketCard, { + IndexedMarketCardData, +} from "components/markets/market-card"; import MarketScroll from "components/markets/MarketScroll"; import { GraphQLClient } from "graphql-request"; import { getCmsMarketMetadataForAllMarkets } from "lib/cms/markets"; import { getCmsNews, CmsNews } from "lib/cms/news"; +import { + CmsTopicHeader, + getCmsTopicHeaders, + marketsForTopic, +} from "lib/cms/topics"; import { endpointOptions, environment, graphQlEndpoint } from "lib/constants"; import getFeaturedMarkets from "lib/gql/featured-markets"; import { getNetworkStats } from "lib/gql/get-network-stats"; @@ -27,21 +36,18 @@ import { ZtgPriceHistory, } from "lib/hooks/queries/useAssetUsdPrice"; import { categoryCountsKey } from "lib/hooks/queries/useCategoryCounts"; +import { getPlaiceholders } from "lib/util/getPlaiceHolders"; import { NextPage } from "next"; +import Image from "next/image"; import Link from "next/link"; +import { useRouter } from "next/router"; import path from "path"; import { getPlaiceholder, IGetPlaiceholderOptions, IGetPlaiceholderReturn, } from "plaiceholder"; - -const getPlaiceholders = ( - paths: string[], - options?: IGetPlaiceholderOptions, -): Promise => { - return Promise.all(paths.map((path) => getPlaiceholder(path, options))); -}; +import { useState } from "react"; export async function getStaticProps() { const client = new GraphQLClient(graphQlEndpoint); @@ -51,7 +57,10 @@ export async function getStaticProps() { storage: ZeitgeistIpfs(), }); - const news = await getCmsNews(); + const [news, cmsTopics] = await Promise.all([ + getCmsNews(), + getCmsTopicHeaders(), + ]); const [ featuredMarkets, @@ -59,10 +68,12 @@ export async function getStaticProps() { bannerPlaceholder, categoryPlaceholders, newsImagePlaceholders, + topicImagePlaceholders, stats, ztgHistory, chainProperties, marketsCmsData, + topicsMarkets, ] = await Promise.all([ getFeaturedMarkets(client, sdk), getTrendingMarkets(client, sdk), @@ -74,10 +85,22 @@ export async function getStaticProps() { news.map((slide) => slide.image ?? ""), { size: 16 }, ), + getPlaiceholders( + cmsTopics.map((topic) => topic.thumbnail ?? ""), + { size: 16 }, + ), getNetworkStats(sdk), getZTGHistory(), sdk.api.rpc.system.properties(), getCmsMarketMetadataForAllMarkets(), + Promise.all( + cmsTopics.map((topic) => + marketsForTopic(topic, sdk.indexer, { limit: 3 }).then((markets) => ({ + topic, + markets, + })), + ), + ), ]); const queryClient = new QueryClient(); @@ -110,9 +133,12 @@ export async function getStaticProps() { bannerPlaceholder: bannerPlaceholder.base64 ?? "", categoryPlaceholders: categoryPlaceholders.map((c) => c.base64) ?? [], newsImagePlaceholders: newsImagePlaceholders.map((c) => c.base64) ?? [], + topicImagePlaceholders: topicImagePlaceholders.map((c) => c.base64) ?? [], stats, ztgHistory, chainProperties: chainProperties.toPrimitive(), + cmsTopics, + topicsMarkets, }, revalidate: environment === "production" @@ -127,10 +153,16 @@ const IndexPage: NextPage<{ trendingMarkets: IndexedMarketCardData[]; categoryPlaceholders: string[]; newsImagePlaceholders: string[]; + topicImagePlaceholders: string[]; bannerPlaceholder: string; stats: { marketCount: number; tradersCount: number; volumeUsd: number }; ztgHistory: ZtgPriceHistory; chainProperties: GenericChainProperties; + cmsTopics: CmsTopicHeader[]; + topicsMarkets: { + topic: CmsTopicHeader; + markets: IndexedMarketCardData[]; + }[]; }> = ({ news, trendingMarkets, @@ -138,10 +170,17 @@ const IndexPage: NextPage<{ bannerPlaceholder, categoryPlaceholders, newsImagePlaceholders, + topicImagePlaceholders, stats, ztgHistory, chainProperties, + cmsTopics, + topicsMarkets, }) => { + const router = useRouter(); + const topicSlug = (router.query?.topic as string) ?? cmsTopics[0]?.slug; + const topic = topicsMarkets.find((t) => t.topic.slug === topicSlug); + return ( <>
-
- -
+ {process.env.NEXT_PUBLIC_SHOW_TOPICS === "true" && ( +
+
+ { + router.push( + { query: { ...router.query, topic: topic.slug } }, + undefined, + { shallow: true }, + ); + }} + imagePlaceholders={topicImagePlaceholders} + /> +
+ + {topic && topic.topic.marketIds && ( + <> +
+ {topic.markets.map((market) => ( + + ))} +
+ +
+ Go to {topic.topic.title}{" "} + Markets ({topic.topic.marketIds?.length ?? 0}) +
+ + + )} +
+ )} {featuredMarkets.length > 0 && (
diff --git a/pages/markets/index.tsx b/pages/markets/index.tsx index 7273138a1..4aa32462a 100644 --- a/pages/markets/index.tsx +++ b/pages/markets/index.tsx @@ -4,16 +4,38 @@ import { QueryClient, dehydrate } from "@tanstack/query-core"; import { getCmsMarketMetadataForAllMarkets } from "lib/cms/markets"; import { marketCmsDatakeyForMarket } from "lib/hooks/queries/cms/useMarketCmsMetadata"; import { environment } from "lib/constants"; +import { CmsTopicHeader, getCmsTopicHeaders } from "lib/cms/topics"; +import { getPlaiceholders } from "lib/util/getPlaiceHolders"; -const MarketsPage: NextPage = () => { - return ; +const MarketsPage: NextPage = ({ + cmsTopics, + cmsTopicPlaceholders, +}: { + cmsTopics: CmsTopicHeader[]; + cmsTopicPlaceholders: string[]; +}) => { + return ( + + ); }; export async function getStaticProps() { const queryClient = new QueryClient(); - const cmsData = await getCmsMarketMetadataForAllMarkets(); - for (const marketCmsData of cmsData) { + const [cmsMarketMetaData, cmsTopics] = await Promise.all([ + getCmsMarketMetadataForAllMarkets(), + getCmsTopicHeaders(), + ]); + + const cmsTopicPlaceholders = await getPlaiceholders( + cmsTopics.map((topic) => topic.thumbnail ?? ""), + { size: 16 }, + ).then((plh) => plh.map((c) => c.base64) ?? []); + + for (const marketCmsData of cmsMarketMetaData) { if (marketCmsData.marketId) { queryClient.setQueryData( marketCmsDatakeyForMarket(marketCmsData.marketId), @@ -25,6 +47,8 @@ export async function getStaticProps() { return { props: { dehydratedState: dehydrate(queryClient), + cmsTopics, + cmsTopicPlaceholders, }, revalidate: environment === "production" diff --git a/pages/topics/[topic].tsx b/pages/topics/[topic].tsx new file mode 100644 index 000000000..a6620fe6c --- /dev/null +++ b/pages/topics/[topic].tsx @@ -0,0 +1,147 @@ +import { PortableText } from "@portabletext/react"; +import { ZeitgeistIpfs, create } from "@zeitgeistpm/sdk"; +import MarketCard, { + IndexedMarketCardData, +} from "components/markets/market-card"; +import { sanityImageBuilder } from "lib/cms/sanity"; +import { + CmsTopicFull, + getCmsFullTopic, + getCmsTopicHeaders, + marketsForTopic, +} from "lib/cms/topics"; +import { endpointOptions, graphQlEndpoint } from "lib/constants"; +import { NextPage } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import NotFoundPage from "pages/404"; + +export async function getStaticPaths() { + const cmsTopics = await getCmsTopicHeaders(); + + const paths = cmsTopics.map((topic) => ({ + params: { topic: topic.slug }, + })); + + return { paths, fallback: "blocking" }; +} + +export async function getStaticProps({ + params, +}: { + params: { topic: string }; +}) { + const sdk = await create({ + provider: endpointOptions.map((e) => e.value), + indexer: graphQlEndpoint, + storage: ZeitgeistIpfs(), + }); + + const cmsTopic = await getCmsFullTopic(params.topic); + const marketCardsData = await marketsForTopic(cmsTopic, sdk.indexer); + + return { + props: { + cmsTopic: cmsTopic ?? null, + markets: marketCardsData, + }, + }; +} + +const TopicPage: NextPage<{ + cmsTopic: CmsTopicFull; + markets: IndexedMarketCardData[]; +}> = ({ cmsTopic, markets }) => { + if (process.env.NEXT_PUBLIC_SHOW_TOPICS !== "true") { + return ; + } + + const [marketOne, marketTwo, marketThree, marketFour, ...restMarkets] = + markets; + + const banner = cmsTopic.banner + ? sanityImageBuilder.image(cmsTopic.banner) + : undefined; + + let blur = {}; + + if (cmsTopic.banner && cmsTopic.bannerBlurData) { + blur = { + placeholder: "blur", + blurDataURL: cmsTopic.bannerBlurData, + }; + } + + return ( +
+ {banner && ( +
+ +
+ )} + +
+

{cmsTopic.title}

+ +
+ All Markets +
+ +
+ + {cmsTopic.description && ( +
+ +
+ )} + + {markets.length > 4 ? ( + <> +
+
+
+ + +
+
+ + +
+
+
+ {restMarkets.map((market) => ( + + ))} +
+
+ +
+ {markets.map((market) => ( + + ))} +
+ + ) : ( + <> +
+ {markets.map((market) => ( + + ))} +
+ + )} +
+ ); +}; + +export default TopicPage; diff --git a/styles/index.css b/styles/index.css index 26dc1d8d1..005753616 100644 --- a/styles/index.css +++ b/styles/index.css @@ -360,7 +360,7 @@ tr td:first-child { } .ztg-transition { - @apply transition duration-200 ease-in-out; + @apply transition duration-300 ease-[cubic-bezier(.35,.29,0,1.31)]; } @keyframes rotation { @@ -801,3 +801,24 @@ tr td:first-child { input:-webkit-autofill { -webkit-box-shadow: 0 0 0 30px theme("colors.nyanza-base") inset !important; } + +.embla { + --slide-spacing: 1rem; + --slide-size: 100%; + --slide-height: 19rem; +} +.embla__viewport { + overflow: hidden; +} +.embla__container { + backface-visibility: hidden; + display: flex; + touch-action: pan-y; + margin-left: calc(var(--slide-spacing) * -1); +} +.embla__slide { + flex: 0 0 var(--slide-size); + min-width: 0; + padding-left: var(--slide-spacing); + position: relative; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b13c2ad04..4e95a7528 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2447,6 +2447,13 @@ __metadata: languageName: node linkType: hard +"@sanity/image-url@npm:^1.0.2": + version: 1.0.2 + resolution: "@sanity/image-url@npm:1.0.2" + checksum: fbd2a6e7bd1e9b053c5a8d953c35a17f5d00dcfd792ce115fd7dfb237cbff03c7830fdf64e1ae8c6e00b58c8cabc2673f546d587454359ee02be2e2f8a39bd1e + languageName: node + linkType: hard + "@scure/base@npm:1.1.1": version: 1.1.1 resolution: "@scure/base@npm:1.1.1" @@ -4363,6 +4370,8 @@ __metadata: cross-env: ^7.0.3 decimal.js: ^10.4.3 dotenv: ^9.0.2 + embla-carousel: ^8.0.0-rc19 + embla-carousel-react: ^8.0.0-rc19 fathom-client: ^3.2.0 flexsearch: ^0.7.21 font-color-contrast: ^11.1.0 @@ -4379,6 +4388,7 @@ __metadata: next: ^13.4.19 next-absolute-url: ^1.2.2 next-qrcode: ^2.5.1 + next-sanity-image: ^6.1.1 next-transpile-modules: ^9.1.0 object-hash: ^2.2.0 plaiceholder: ^2.5.0 @@ -6197,6 +6207,34 @@ __metadata: languageName: node linkType: hard +"embla-carousel-react@npm:^8.0.0-rc19": + version: 8.0.0-rc19 + resolution: "embla-carousel-react@npm:8.0.0-rc19" + dependencies: + embla-carousel: 8.0.0-rc19 + embla-carousel-reactive-utils: 8.0.0-rc19 + peerDependencies: + react: ^16.8.0 || ^17.0.1 || ^18.0.0 + checksum: b870dad1180ea741d61e905d188a7c197716d7b90abcfa7adbd7072a6763051acd7511ca10e3a17c9a2c66482281e6d824a0af507c0f70eee14b9b5d600bb7ab + languageName: node + linkType: hard + +"embla-carousel-reactive-utils@npm:8.0.0-rc19": + version: 8.0.0-rc19 + resolution: "embla-carousel-reactive-utils@npm:8.0.0-rc19" + peerDependencies: + embla-carousel: 8.0.0-rc19 + checksum: b66c1601e75c8482af70a7b8476ac98b60995f6b759b9c76ae70a47192c0b14021aa2b4eadadb207d20485c16ef53f51f10148e8c5d8dfbf34538b62154e294a + languageName: node + linkType: hard + +"embla-carousel@npm:8.0.0-rc19, embla-carousel@npm:^8.0.0-rc19": + version: 8.0.0-rc19 + resolution: "embla-carousel@npm:8.0.0-rc19" + checksum: de531482ef3fe8b71e9f114cbed9066f0c3bcf261cdfdfb3686961409c02765b65ad137ab262f690bc1f5b4837e066f6bf9e749e19ef24555769559f01bbba5f + languageName: node + linkType: hard + "emoji-regex@npm:^10.2.1": version: 10.3.0 resolution: "emoji-regex@npm:10.3.0" @@ -9617,6 +9655,19 @@ __metadata: languageName: node linkType: hard +"next-sanity-image@npm:^6.1.1": + version: 6.1.1 + resolution: "next-sanity-image@npm:6.1.1" + dependencies: + "@sanity/image-url": ^1.0.2 + peerDependencies: + "@sanity/client": ^5.0.0 || ^6.0.0 + next: ^13.0.0 || ^14.0.0 + react: ^18.0.0 + checksum: b0a4a3400a46bb70cef121ed6ad29bb060cf187508ffaff75a7a98a143f9cc188ea221916ab63cf730e5cd0fd24b51f37fcb833d936e57c19be006c64a6b1785 + languageName: node + linkType: hard + "next-tick@npm:^1.1.0": version: 1.1.0 resolution: "next-tick@npm:1.1.0"