Merge pull request #393 from blockworks-foundation/cms-announcements

add announcements to overview
This commit is contained in:
saml33 2024-02-26 14:49:17 +11:00 committed by GitHub
commit bd468bbe80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 365 additions and 6 deletions

View File

@ -15,6 +15,7 @@ import ConnectEmptyState from '@components/shared/ConnectEmptyState'
import CreateAccountModal from '@components/modals/CreateAccountModal'
import { FaceSmileIcon } from '@heroicons/react/20/solid'
import Button from '@components/shared/Button'
import Announcements from './Announcements'
const EMPTY_STATE_WRAPPER_CLASSES =
'flex h-[180px] flex-col justify-center pb-4 md:h-full'
@ -116,6 +117,7 @@ const AccountOverview = () => {
<AccountHeroStats accountValue={accountValue} />
</div>
</div>
<Announcements />
<Explore />
{showCreateAccountModal ? (
<CreateAccountModal

View File

@ -0,0 +1,184 @@
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/20/solid'
import { useViewport } from 'hooks/useViewport'
import { ReactNode, useRef } from 'react'
import { breakpoints } from 'utils/theme'
import Slider from 'react-slick'
import Image from 'next/image'
import { usePlausible } from 'next-plausible'
import Link from 'next/link'
import { useQuery } from '@tanstack/react-query'
import { AppAnnouncement, fetchCMSAnnounements } from 'utils/contentful'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { SHOW_ANNOUNCEMENTS_KEY } from 'utils/constants'
import { LinkButton } from '@components/shared/Button'
import { useTranslation } from 'react-i18next'
const Announcements = () => {
const { width } = useViewport()
const { t } = useTranslation('account')
const [showAnnouncements, setShowAnnouncements] = useLocalStorageState(
SHOW_ANNOUNCEMENTS_KEY,
true,
)
const { data: announcements } = useQuery(
['announcements-data'],
() => fetchCMSAnnounements(),
{
cacheTime: 1000 * 60 * 15,
staleTime: 1000 * 60 * 5,
retry: 3,
refetchOnWindowFocus: false,
},
)
const sliderSettings = {
arrows: false,
dots: false,
infinite: true,
slidesToShow: 3,
slidesToScroll: 1,
cssEase: 'linear',
responsive: [
{
breakpoint: breakpoints.xl,
settings: {
slidesToShow: 2,
},
},
{
breakpoint: breakpoints.lg,
settings: {
slidesToShow: 1,
},
},
],
}
const sliderRef = useRef<Slider | null>(null)
const nextSlide = () => {
if (sliderRef.current) {
sliderRef.current.slickNext()
}
}
const prevSlide = () => {
if (sliderRef.current) {
sliderRef.current.slickPrev()
}
}
const slides = width >= breakpoints.xl ? 3 : width >= breakpoints.lg ? 2 : 1
const showArrows = announcements?.length
? slides < announcements.length
: false
return announcements?.length && showAnnouncements ? (
<div className="px-2 pt-10 md:px-4">
<div className="flex items-center justify-center">
{showArrows ? (
<button
className="mr-4 flex h-8 w-8 items-center justify-center rounded-full border-2 border-th-bkg-4"
onClick={prevSlide}
>
<ChevronLeftIcon className="h-5 w-5 text-th-fgd-1" />
</button>
) : null}
<div className={` ${showArrows ? 'w-[calc(100%-120px)]' : 'w-full'}`}>
<Slider ref={sliderRef} {...sliderSettings}>
{announcements.map((announcement, i) => (
<div className="px-2" key={announcement.title + i}>
<Announcement data={announcement} />
</div>
))}
</Slider>
</div>
{showArrows ? (
<button
className="ml-4 flex h-8 w-8 items-center justify-center rounded-full border-2 border-th-bkg-4"
onClick={nextSlide}
>
<ChevronRightIcon className="h-5 w-5 text-th-fgd-1" />
</button>
) : null}
</div>
<div className="mt-2 flex justify-center px-2 md:justify-end">
<LinkButton
className="text-xs font-normal text-th-fgd-3"
onClick={() => setShowAnnouncements(false)}
>
{t('hide-announcements')}
</LinkButton>
</div>
</div>
) : null
}
export default Announcements
const classNames =
'bg-th-bkg-2 p-4 rounded-lg block w-full md:hover:bg-th-bkg-3'
const AnnouncementWrapper = ({
children,
isExternal,
path,
}: {
children: ReactNode
isExternal: boolean
path: string
}) => {
const telemetry = usePlausible()
const trackClick = () => {
telemetry('announcement', {
props: {
path: path,
},
})
}
return isExternal ? (
<a
className={classNames}
href={path}
rel="noopener noreferrer"
target="_blank"
onClick={trackClick}
>
{children}
</a>
) : (
<Link className={classNames} href={path} onClick={trackClick} shallow>
{children}
</Link>
)
}
const Announcement = ({ data }: { data: AppAnnouncement }) => {
const { linkPath, description, image, title } = data
const imageSrc = image?.src
const imageAlt = image?.alt || 'CTA Image'
const isExtenalLink = linkPath.includes('http')
return (
<AnnouncementWrapper isExternal={isExtenalLink} path={linkPath}>
<span className="flex items-center space-x-3">
{imageSrc ? (
<Image
className="shrink-0 rounded-full"
src={`https:${imageSrc}`}
alt={imageAlt}
height={48}
width={48}
/>
) : null}
<div>
<p className="block font-display text-sm text-th-fgd-1">{title}</p>
<p className="block text-sm text-th-fgd-3">{description}</p>
</div>
</span>
{/* <ChevronRightIcon className="ml-3 h-6 w-6 text-th-fgd-4 flex-shrink-0" /> */}
</AnnouncementWrapper>
)
}

View File

@ -1,6 +1,5 @@
import { useEffect, useMemo, useState } from 'react'
import PerpMarketsTable from './PerpMarketsTable'
import { useTranslation } from 'react-i18next'
import mangoStore from '@store/mangoStore'
import RecentGainersLosers from './RecentGainersLosers'
import Spot from './Spot'
@ -8,11 +7,13 @@ import useBanks from 'hooks/useBanks'
import TabsText from '@components/shared/TabsText'
import useFollowedAccounts from 'hooks/useFollowedAccounts'
import FollowedAccounts from './FollowedAccounts'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { SHOW_ANNOUNCEMENTS_KEY } from 'utils/constants'
const Explore = () => {
const { t } = useTranslation(['common'])
const { banks } = useBanks()
const { data: followedAccounts } = useFollowedAccounts()
const [showAnnouncements] = useLocalStorageState(SHOW_ANNOUNCEMENTS_KEY, true)
const perpStats = mangoStore((s) => s.perpStats.data)
const [activeTab, setActiveTab] = useState('tokens')
@ -37,12 +38,12 @@ const Explore = () => {
}, [banks, followedAccounts])
return (
<>
<div className="px-4 pt-10 md:px-6">
<div className={showAnnouncements ? 'pt-4' : 'pt-10'}>
{/* <div className="px-4 pt-10 md:px-6">
<h2 className="mb-4 text-center text-lg md:text-left">
{t('explore')}
</h2>
</div>
</div> */}
<RecentGainersLosers />
<div className="z-10 w-max px-4 pt-8 md:px-6">
<div
@ -62,7 +63,7 @@ const Explore = () => {
<div className="pb-20 md:pb-0">
<TabContent activeTab={activeTab} />
</div>
</>
</div>
)
}

View File

@ -18,6 +18,7 @@ import { useCallback } from 'react'
import { useRouter } from 'next/router'
import {
NOTIFICATION_POSITION_KEY,
SHOW_ANNOUNCEMENTS_KEY,
SIZE_INPUT_UI_KEY,
TRADE_CHART_UI_KEY,
TRADE_LAYOUT_KEY,
@ -25,6 +26,7 @@ import {
import mangoStore from '@store/mangoStore'
import { CUSTOM_SKINS } from 'utils/theme'
import { SETTINGS_BUTTON_TITLE_CLASSES } from './AccountSettings'
import Switch from '@components/forms/Switch'
const NOTIFICATION_POSITIONS = [
'bottom-left',
@ -89,6 +91,11 @@ const DisplaySettings = () => {
const [, setTradeLayout] = useLocalStorageState(TRADE_LAYOUT_KEY, 'chartLeft')
const [showAnnouncements, setShowAnnouncements] = useLocalStorageState(
SHOW_ANNOUNCEMENTS_KEY,
true,
)
// add nft skins to theme selection list
useEffect(() => {
if (nfts.length) {
@ -236,6 +243,13 @@ const DisplaySettings = () => {
/>
</div>
</div>
<div className="flex items-center justify-between border-t border-th-bkg-3 p-4">
<p className={SETTINGS_BUTTON_TITLE_CLASSES}>{t('announcements')}</p>
<Switch
checked={showAnnouncements}
onChange={() => setShowAnnouncements(!showAnnouncements)}
/>
</div>
</div>
)
}

View File

@ -11,6 +11,7 @@ const nextConfig = {
'arweave.net',
'www.dual.finance',
'shdw-drive.genesysgo.net',
'images.ctfassets.net',
],
},
reactStrictMode: true,

View File

@ -79,9 +79,11 @@
"react-nice-dates": "3.1.0",
"react-number-format": "4.9.2",
"react-responsive-pagination": "2.2.3",
"react-slick": "0.30.2",
"react-tsparticles": "2.2.4",
"recharts": "2.5.0",
"remark-gfm": "4.0.0",
"slick-carousel": "1.8.1",
"three": "^0.155.0",
"tsparticles": "2.2.4",
"walktour": "5.1.1",
@ -106,6 +108,7 @@
"@types/react": "18.0.3",
"@types/react-dom": "18.0.0",
"@types/react-grid-layout": "1.3.2",
"@types/react-slick": "0.23.13",
"@types/recharts": "1.8.24",
"@typescript-eslint/eslint-plugin": "5.43.0",
"autoprefixer": "10.4.13",

View File

@ -2,6 +2,8 @@ import '../styles/globals.css'
import 'react-nice-dates/build/style.css'
import '../styles/datepicker.css'
import 'driver.js/dist/driver.css'
import 'slick-carousel/slick/slick.css'
import 'slick-carousel/slick/slick-theme.css'
import type { AppProps } from 'next/app'
import { useCallback, useMemo } from 'react'
import {

View File

@ -14,6 +14,7 @@
"follow": "Follow",
"followed-accounts": "Followed Accounts",
"funding-chart": "Funding Chart",
"hide-announcements": "Hide Announcements",
"init-health": "Init Health",
"maint-health": "Maint Health",
"health-contributions": "Health Contributions",

View File

@ -19,6 +19,7 @@
"all": "All",
"amount": "Amount",
"amount-owed": "Amount Owed",
"announcements": "Announcements",
"asked-sign-transaction": "You'll be asked to sign a transaction",
"asset-liability-weight": "Asset/Liability Weights",
"asset-liability-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral. Liability weight does the opposite (adds to the value of the liability in your health calculation).",

View File

@ -14,6 +14,7 @@
"follow": "Follow",
"followed-accounts": "Followed Accounts",
"funding-chart": "Funding Chart",
"hide-announcements": "Hide Announcements",
"init-health": "Init Health",
"maint-health": "Maint Health",
"health-contributions": "Health Contributions",

View File

@ -19,6 +19,7 @@
"all": "All",
"amount": "Amount",
"amount-owed": "Amount Owed",
"announcements": "Announcements",
"asked-sign-transaction": "You'll be asked to sign a transaction",
"asset-liability-weight": "Asset/Liability Weights",
"asset-liability-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral. Liability weight does the opposite (adds to the value of the liability in your health calculation).",

View File

@ -14,6 +14,7 @@
"follow": "Seguir",
"followed-accounts": "Contas Seguidas",
"funding-chart": "Gráfico de Financiamento",
"hide-announcements": "Hide Announcements",
"init-health": "Saúde Inicial",
"maint-health": "Saúde de Manutenção",
"health-contributions": "Contribuições para a Saúde",

View File

@ -19,6 +19,7 @@
"all": "Todos",
"amount": "Quantia",
"amount-owed": "Quantia Devida",
"announcements": "Announcements",
"asked-sign-transaction": "Você será solicitado a assinar uma transação",
"asset-liability-weight": "Pesos de Ativo/Passivo",
"asset-liability-weight-desc": "O peso do ativo aplica um desconto ao valor do colateral no cálculo da saúde da sua conta. Quanto menor o peso do ativo, menos o ativo conta como colateral. O peso do passivo faz o oposto (adiciona ao valor do passivo no cálculo da saúde).",

View File

@ -14,6 +14,7 @@
"follow": "Follow",
"followed-accounts": "Followed Accounts",
"funding-chart": "Funding Chart",
"hide-announcements": "Hide Announcements",
"init-health": "Init Health",
"maint-health": "Maint Health",
"health-contributions": "Health Contributions",

View File

@ -19,6 +19,7 @@
"all": "All",
"amount": "Amount",
"amount-owed": "Amount Owed",
"announcements": "Announcements",
"asked-sign-transaction": "You'll be asked to sign a transaction",
"asset-liability-weight": "Asset/Liability Weights",
"asset-liability-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral. Liability weight does the opposite (adds to the value of the liability in your health calculation).",

View File

@ -14,6 +14,7 @@
"follow": "关注",
"followed-accounts": "你关注的帐户",
"funding-chart": "资金费图表",
"hide-announcements": "Hide Announcements",
"health-contributions": "健康度贡献",
"init-health": "初始健康度",
"init-health-contribution": "初始健康贡献",

View File

@ -19,6 +19,7 @@
"all": "全部",
"amount": "数量",
"amount-owed": "欠款",
"announcements": "Announcements",
"asked-sign-transaction": "你会被要求签署交易",
"asset-liability-weight": "资产/债务权重",
"asset-liability-weight-desc": "资产权重在账户健康计算中对质押品价值进行扣减。资产权重越低,资产对质押品的影响越小。债务权重恰恰相反(在健康计算中增加债务价值)。",

View File

@ -14,6 +14,7 @@
"follow": "關注",
"followed-accounts": "你關注的帳戶",
"funding-chart": "資金費圖表",
"hide-announcements": "Hide Announcements",
"health-contributions": "健康度貢獻",
"init-health": "初始健康度",
"init-health-contribution": "初始健康貢獻",

View File

@ -19,6 +19,7 @@
"all": "全部",
"amount": "數量",
"amount-owed": "欠款",
"announcements": "Announcements",
"asked-sign-transaction": "你會被要求簽署交易",
"asset-liability-weight": "資產/債務權重",
"asset-liability-weight-desc": "資產權重在賬戶健康計算中對質押品價值進行扣減。資產權重越低,資產對質押品的影響越小。債務權重恰恰相反(在健康計算中增加債務價值)。",

View File

@ -88,6 +88,8 @@ export const NON_RESTRICTED_JURISDICTION_KEY = 'non-restricted-jurisdiction-0.1'
export const FILTER_ORDERS_FOR_MARKET_KEY = 'filterOrdersForMarket-0.1'
export const FILTER_HISTORY_FOR_MARKET_KEY = 'filterHistoryForMarket-0.1'
export const SHOW_ANNOUNCEMENTS_KEY = 'showAnnouncements-0.1'
// Unused
export const PROFILE_CATEGORIES = [
'borrower',

View File

@ -1,4 +1,6 @@
import {
Asset,
AssetLink,
createClient,
Entry,
EntryFieldTypes,
@ -66,6 +68,25 @@ export interface TokenPage {
erc20TokenDecimals: number | undefined
}
function parseContentfulContentImage(
asset?: Asset<undefined, string> | { sys: AssetLink },
): ContentImage | null {
if (!asset) {
return null
}
if (!('fields' in asset)) {
return null
}
return {
src: asset.fields.file?.url || '',
alt: asset.fields.description || '',
width: asset.fields.file?.details.image?.width || 0,
height: asset.fields.file?.details.image?.height || 0,
}
}
function parseContentfulTokenPage(
tokenPageEntry?: TokenPageEntry,
): TokenPage | null {
@ -93,6 +114,53 @@ function parseContentfulTokenPage(
}
}
function parseContentfulAppAnnouncement(
homePageAnnouncementEntry?: AppAnnouncementEntry,
): AppAnnouncement | null {
if (!homePageAnnouncementEntry) {
return null
}
return {
title: homePageAnnouncementEntry.fields.title || '',
description: homePageAnnouncementEntry.fields.description || '',
linkPath: homePageAnnouncementEntry.fields.linkPath || '',
image: parseContentfulContentImage(homePageAnnouncementEntry.fields.image),
}
}
export interface ContentImage {
src: string
alt: string
width: number
height: number
}
export interface AppAnnouncement {
title: string
description?: string
image: ContentImage | null
linkPath: string
}
export interface TypeAppAnnouncementFields {
title: EntryFieldTypes.Symbol
description?: EntryFieldTypes.Symbol
linkPath: EntryFieldTypes.Symbol
image?: EntryFieldTypes.AssetLink
}
export type TypeAppAnnouncementSkeleton = EntrySkeletonType<
TypeAppAnnouncementFields,
'appAnnouncement'
>
type AppAnnouncementEntry = Entry<
TypeAppAnnouncementSkeleton,
undefined,
string
>
export async function fetchCMSTokenPage(
symbol: string | undefined,
): Promise<TokenPage[]> {
@ -115,3 +183,23 @@ export async function fetchCMSTokenPage(
return parsedTokenPages
}
export async function fetchCMSAnnounements(): Promise<AppAnnouncement[]> {
const client = createClient({
space: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID!,
accessToken: process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN!,
})
const announcementsResult =
await client.getEntries<TypeAppAnnouncementSkeleton>({
content_type: 'appAnnouncement',
include: 2,
order: ['-sys.createdAt'],
limit: 3,
})
return announcementsResult.items.map(
(appAnnouncementEntry) =>
parseContentfulAppAnnouncement(appAnnouncementEntry) as AppAnnouncement,
)
}

View File

@ -3627,6 +3627,13 @@
dependencies:
"@types/react" "*"
"@types/react-slick@0.23.13":
version "0.23.13"
resolved "https://registry.yarnpkg.com/@types/react-slick/-/react-slick-0.23.13.tgz#037434e73a58063047b121e08565f7185d811f36"
integrity sha512-bNZfDhe/L8t5OQzIyhrRhBr/61pfBcWaYJoq6UDqFtv5LMwfg4NsVDD2J8N01JqdAdxLjOt66OZEp6PX+dGs/A==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.4.9":
version "4.4.9"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.9.tgz#12a1a1b5b8791067198149867b0823fbace31579"
@ -6160,6 +6167,11 @@ enhanced-resolve@^5.12.0:
graceful-fs "^4.2.4"
tapable "^2.2.0"
enquire.js@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/enquire.js/-/enquire.js-2.1.6.tgz#3e8780c9b8b835084c3f60e166dbc3c2a3c89814"
integrity sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw==
entities@^4.2.0, entities@^4.3.0, entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
@ -8551,6 +8563,13 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
json2mq@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a"
integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==
dependencies:
string-convert "^0.2.0"
json5@1.0.2, json5@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
@ -8717,6 +8736,11 @@ lodash.clonedeep@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
lodash.isequal@4.5.0, lodash.isequal@^4.0.0, lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
@ -10548,6 +10572,17 @@ react-simple-animate@3.0.2:
resolved "https://registry.yarnpkg.com/react-simple-animate/-/react-simple-animate-3.0.2.tgz#67f29b0c64155d2dfd540c1c74f634ef521536a8"
integrity sha512-GDpznDMpIHHdH6L6AZo9nNry6Xsq7forKmd6rwigMSuxe4FS9ihK0HSC1odB4WWKbA87S8VL7EVR9JeTRVkbQA==
react-slick@0.30.2:
version "0.30.2"
resolved "https://registry.yarnpkg.com/react-slick/-/react-slick-0.30.2.tgz#b28e992f9c519bb516a0af8d37e82cb59fee08ce"
integrity sha512-XvQJi7mRHuiU3b9irsqS9SGIgftIfdV5/tNcURTb5LdIokRA5kIIx3l4rlq2XYHfxcSntXapoRg/GxaVOM1yfg==
dependencies:
classnames "^2.2.5"
enquire.js "^2.1.6"
json2mq "^0.2.0"
lodash.debounce "^4.0.8"
resize-observer-polyfill "^1.5.0"
react-smooth@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-2.0.4.tgz#95187126265970a1490e2aea5690365203ee555f"
@ -10822,6 +10857,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
resize-observer-polyfill@^1.5.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@ -11192,6 +11232,11 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
slick-carousel@1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/slick-carousel/-/slick-carousel-1.8.1.tgz#a4bfb29014887bb66ce528b90bd0cda262cc8f8d"
integrity sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA==
smart-buffer@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
@ -11354,6 +11399,11 @@ strict-uri-encode@^2.0.0:
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==
string-convert@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==
string-length@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"