add announcements to overview

This commit is contained in:
saml33 2024-02-21 13:08:12 +11:00
parent 2910d04086
commit f31d74eb61
13 changed files with 329 additions and 2 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,12 @@ const AccountOverview = () => {
<AccountHeroStats accountValue={accountValue} />
</div>
</div>
<div className="px-4 pt-10 md:px-6">
<h2 className="mb-4 text-center text-lg md:text-left">
{t('announcements')}
</h2>
<Announcements />
</div>
<Explore />
{showCreateAccountModal ? (
<CreateAccountModal

View File

@ -0,0 +1,170 @@
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'
const Announcements = () => {
const { width } = useViewport()
const { data: announcements } = useQuery(
['announcements-data'],
() => fetchCMSAnnounements(),
{
cacheTime: 1000 * 60 * 15,
staleTime: 1000 * 60 * 5,
retry: 3,
refetchOnWindowFocus: false,
},
)
console.log(announcements)
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 ? (
<div className="flex items-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%-88px)]' : 'w-full'}`}>
<Slider ref={sliderRef} {...sliderSettings}>
{announcements.map((announcement, i) => (
<div
className={i !== announcements.length - 1 ? 'pr-3' : 'pr-[1px]'}
key={announcement.title + i}
>
<Announcement data={announcement} />
</div>
))}
</Slider>
</div>
{showArrows ? (
<button
className="ml-1 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>
) : null
}
export default Announcements
const classNames =
'border border-th-bkg-4 py-3 px-4 rounded-lg flex items-center justify-between 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"
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="mb-1 text-xs leading-none text-th-active">{category}</p> */}
<p className="block font-display text-sm text-th-fgd-2">{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

@ -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

@ -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

@ -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

@ -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

@ -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

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

View File

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

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

@ -2558,7 +2558,7 @@
dependencies:
"@solana/wallet-adapter-base" "^0.9.23"
"@solana/wallet-adapter-solflare@0.6.27":
"@solana/wallet-adapter-solflare@0.6.27", "@solana/wallet-adapter-solflare@^0.6.28":
version "0.6.27"
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.27.tgz#49ba2dfecca4bee048e65d302216d1b732d7e39e"
integrity sha512-MBBx9B1pI8ChCT70sgxrmeib1S7G9tRQzfMHqJPdGQ2jGtukY0Puzma2OBsIAsH5Aw9rUUUFZUK+8pzaE+mgAg==
@ -2569,7 +2569,7 @@
"@solflare-wallet/sdk" "^1.3.0"
"@wallet-standard/wallet" "^1.0.1"
"@solana/wallet-adapter-solflare@0.6.28", "@solana/wallet-adapter-solflare@^0.6.28":
"@solana/wallet-adapter-solflare@0.6.28":
version "0.6.28"
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.28.tgz#3de42a43220cca361050ebd1755078012a5b0fe2"
integrity sha512-iiUQtuXp8p4OdruDawsm1dRRnzUCcsu+lKo8OezESskHtbmZw2Ifej0P99AbJbBAcBw7q4GPI6987Vh05Si5rw==
@ -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"