mango-v4-ui/components/Layout.tsx

288 lines
8.6 KiB
TypeScript
Raw Normal View History

2022-08-02 19:15:17 -07:00
import SideNav from './SideNav'
2023-03-26 17:42:50 -07:00
import {
Fragment,
ReactNode,
useCallback,
useEffect,
useMemo,
useState,
} from 'react'
2023-03-06 03:48:24 -08:00
import { ArrowPathIcon, ChevronRightIcon } from '@heroicons/react/20/solid'
import { nftThemeMeta } from '../utils/theme'
2022-09-12 08:53:57 -07:00
import mangoStore from '@store/mangoStore'
2022-07-26 19:45:27 -07:00
import BottomBar from './mobile/BottomBar'
import TopBar from './TopBar'
import useLocalStorageState from '../hooks/useLocalStorageState'
2023-07-20 17:20:59 -07:00
import {
ACCEPT_TERMS_KEY,
NON_RESTRICTED_JURISDICTION_KEY,
2023-07-20 17:20:59 -07:00
SECONDS,
SIDEBAR_COLLAPSE_KEY,
SLOTS_WARNING_KEY,
2023-07-20 17:20:59 -07:00
} from '../utils/constants'
2022-11-15 20:12:51 -08:00
import { useWallet } from '@solana/wallet-adapter-react'
2023-01-02 04:19:30 -08:00
import SuccessParticles from './shared/SuccessParticles'
2022-11-21 19:23:54 -08:00
import { tsParticles } from 'tsparticles-engine'
import { loadFull } from 'tsparticles'
2023-03-06 03:48:24 -08:00
import useInterval from './shared/useInterval'
import { Transition } from '@headlessui/react'
import { useTranslation } from 'next-i18next'
import TermsOfUseModal from './modals/TermsOfUseModal'
import { useTheme } from 'next-themes'
2023-06-12 22:33:15 -07:00
import PromoBanner from './rewards/PromoBanner'
import { useRouter } from 'next/router'
2023-08-06 22:57:02 -07:00
import StatusBar from './StatusBar'
import WarningBanner from './shared/WarningBanner'
import useMangoAccountAccounts from 'hooks/useMangoAccountAccounts'
import TokenSlotsWarningModal, {
WARNING_LEVEL,
} from './modals/TokenSlotsWarningModal'
import useMangoAccount from 'hooks/useMangoAccount'
import useUnownedAccount from 'hooks/useUnownedAccount'
2023-12-12 19:59:06 -08:00
import NewListingBanner from './NewListingBanner'
import useIpAddress from 'hooks/useIpAddress'
import RestrictedCountryModal from './modals/RestrictedCountryModal'
2022-07-26 19:45:27 -07:00
2023-06-06 05:47:42 -07:00
export const sideBarAnimationDuration = 300
const termsLastUpdated = 1679441610978
2022-09-21 18:23:29 -07:00
2022-07-26 19:45:27 -07:00
const Layout = ({ children }: { children: ReactNode }) => {
2023-06-06 22:00:59 -07:00
const themeData = mangoStore((s) => s.themeData)
const { theme } = useTheme()
const [isCollapsed, setIsCollapsed] = useLocalStorageState(
SIDEBAR_COLLAPSE_KEY,
2023-07-21 11:47:53 -07:00
false,
)
const [hasSeenSlotsWarning, setHasSeenSlotsWarning] = useLocalStorageState(
SLOTS_WARNING_KEY,
undefined,
)
2023-06-12 22:33:15 -07:00
const { asPath } = useRouter()
const { usedTokens, totalTokens } = useMangoAccountAccounts()
const { mangoAccountAddress } = useMangoAccount()
const { isUnownedAccount } = useUnownedAccount()
const showSlotsNearlyFullWarning = useMemo(() => {
const slotsAvailable = totalTokens.length - usedTokens.length
if (
hasSeenSlotsWarning === 0 ||
!slotsAvailable ||
slotsAvailable > 1 ||
isUnownedAccount
)
return false
return true
}, [hasSeenSlotsWarning, usedTokens, totalTokens])
const showSlotsFullWarning = useMemo(() => {
const slotsAvailable = totalTokens.length - usedTokens.length
if (
hasSeenSlotsWarning === 1 ||
slotsAvailable ||
!mangoAccountAddress ||
isUnownedAccount
)
return false
return true
}, [hasSeenSlotsWarning, usedTokens, totalTokens, mangoAccountAddress])
2022-08-13 04:29:48 -07:00
2022-09-21 18:23:29 -07:00
useEffect(() => {
2022-09-29 21:21:23 -07:00
const animationFrames = 15
2022-09-21 18:23:29 -07:00
for (let x = 1; x <= animationFrames; x++) {
2023-07-22 13:44:43 -07:00
setTimeout(
() => {
window.dispatchEvent(new Event('resize'))
},
(sideBarAnimationDuration / animationFrames) * x,
)
2022-09-21 18:23:29 -07:00
}
}, [isCollapsed])
2022-07-26 19:45:27 -07:00
const handleToggleSidebar = () => {
setIsCollapsed(!isCollapsed)
}
2022-11-21 19:23:54 -08:00
const particlesInit = useCallback(async () => {
await loadFull(tsParticles)
}, [])
useEffect(() => {
particlesInit()
}, [particlesInit])
2022-11-21 19:23:54 -08:00
useEffect(() => {
const set = mangoStore.getState().set
2023-07-23 04:06:29 -07:00
if (theme && nftThemeMeta[theme]) {
set((s) => {
s.themeData = nftThemeMeta[theme]
})
} else {
set((s) => {
s.themeData = nftThemeMeta.default
})
}
}, [theme])
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
if (!mounted) return null
2022-07-26 19:45:27 -07:00
return (
2023-06-07 19:30:56 -07:00
<main
2023-06-08 03:41:23 -07:00
className={`${themeData.fonts.body.variable} ${themeData.fonts.display.variable} ${themeData.fonts.mono.variable} font-sans`}
2023-06-07 19:30:56 -07:00
>
2022-11-21 19:23:54 -08:00
<div className="fixed z-30">
2023-01-02 04:19:30 -08:00
<SuccessParticles />
2022-11-21 19:23:54 -08:00
</div>
2023-06-01 23:05:08 -07:00
<div
className={`min-h-screen grow ${
2023-06-06 22:00:59 -07:00
!themeData.useGradientBg
2023-06-01 23:05:08 -07:00
? 'bg-th-bkg-1'
: 'bg-gradient-to-b from-th-bkg-1 to-th-bkg-2'
} text-th-fgd-2 transition-all`}
>
2022-12-19 23:00:54 -08:00
<div className="fixed bottom-0 left-0 z-20 w-full md:hidden">
<BottomBar />
</div>
2023-01-11 20:45:04 -08:00
<div className="fixed z-20 hidden h-screen md:block">
2022-12-19 23:00:54 -08:00
<button
className="absolute right-0 top-1/2 z-20 hidden h-8 w-3 -translate-y-1/2 rounded-none rounded-l bg-th-bkg-3 hover:bg-th-bkg-4 focus:outline-none focus-visible:bg-th-bkg-4 lg:flex lg:items-center lg:justify-center"
2022-12-19 23:00:54 -08:00
onClick={handleToggleSidebar}
>
<ChevronRightIcon
2023-10-17 04:28:43 -07:00
className={`h-4 w-4 shrink-0 ${!isCollapsed ? 'rotate-180' : ''}`}
2022-12-19 23:00:54 -08:00
/>
</button>
<div
2023-08-17 19:25:09 -07:00
className={`hide-scroll h-full ${
!isCollapsed ? 'overflow-y-auto' : ''
}`}
>
2022-12-19 23:00:54 -08:00
<SideNav collapsed={isCollapsed} />
</div>
2022-12-19 23:00:54 -08:00
</div>
<div
2023-11-06 16:49:12 -08:00
className={`w-full transition-all duration-${sideBarAnimationDuration} ease-in-out ${
isCollapsed ? 'md:pl-[64px]' : 'pl-[200px]'
2022-12-19 23:00:54 -08:00
}`}
>
2023-08-08 22:24:09 -07:00
<TopBar />
2023-12-12 19:59:06 -08:00
<NewListingBanner />
2023-08-08 22:24:09 -07:00
{asPath !== '/rewards' ? <PromoBanner /> : null}
2024-01-02 18:28:26 -08:00
<div className="pb-12 md:pb-[27px]">
{children}
<WarningBanner />
</div>
2023-08-08 22:24:09 -07:00
<StatusBar collapsed={isCollapsed} />
2022-07-26 19:45:27 -07:00
</div>
2023-03-06 03:48:24 -08:00
<DeployRefreshManager />
<TermsOfUse />
<RestrictedCountryCheck />
{showSlotsNearlyFullWarning ? (
<TokenSlotsWarningModal
isOpen={showSlotsNearlyFullWarning}
onClose={() => setHasSeenSlotsWarning(WARNING_LEVEL.NEARLY_FULL)}
warningLevel={WARNING_LEVEL.NEARLY_FULL}
/>
) : null}
{showSlotsFullWarning ? (
<TokenSlotsWarningModal
isOpen={showSlotsFullWarning}
onClose={() => setHasSeenSlotsWarning(WARNING_LEVEL.FULL)}
warningLevel={WARNING_LEVEL.FULL}
/>
) : null}
2022-07-26 19:45:27 -07:00
</div>
</main>
)
}
export default Layout
const TermsOfUse = () => {
const { connected } = useWallet()
const [acceptTerms, setAcceptTerms] = useLocalStorageState(
ACCEPT_TERMS_KEY,
2023-07-21 11:47:53 -07:00
'',
)
const showTermsOfUse = useMemo(() => {
return (!acceptTerms || acceptTerms < termsLastUpdated) && connected
}, [acceptTerms, connected])
return (
<>
{showTermsOfUse ? (
<TermsOfUseModal
isOpen={showTermsOfUse}
onClose={() => setAcceptTerms(Date.now())}
/>
) : null}
</>
2022-07-26 19:45:27 -07:00
)
}
2024-01-04 02:29:20 -08:00
// this will only show if the ip api doesn't return the country
const RestrictedCountryCheck = () => {
const { ipCountry, loadingIpCountry } = useIpAddress()
2024-01-16 19:59:40 -08:00
const groupLoaded = mangoStore((s) => s.groupLoaded)
const [confirmedCountry, setConfirmedCountry] = useLocalStorageState(
NON_RESTRICTED_JURISDICTION_KEY,
false,
)
2024-01-06 02:54:56 -08:00
2024-01-06 03:52:31 -08:00
const showModal = useMemo(() => {
2024-01-16 19:59:40 -08:00
return !confirmedCountry && !ipCountry && !loadingIpCountry && groupLoaded
}, [confirmedCountry, ipCountry, loadingIpCountry, groupLoaded])
return showModal ? (
<RestrictedCountryModal
isOpen={showModal}
2024-01-06 03:54:46 -08:00
onClose={() => {
setConfirmedCountry(true)
}}
/>
) : null
}
2023-03-06 03:48:24 -08:00
function DeployRefreshManager(): JSX.Element | null {
const { t } = useTranslation('common')
const [newBuildAvailable, setNewBuildAvailable] = useState(false)
2023-03-06 03:48:24 -08:00
useInterval(async () => {
const response = await fetch('/api/build-id')
const { buildId } = await response.json()
if (buildId && process.env.BUILD_ID && buildId !== process.env.BUILD_ID) {
// There's a new version deployed that we need to load
setNewBuildAvailable(true)
}
2023-07-20 17:20:59 -07:00
}, 300 * SECONDS)
2023-03-06 03:48:24 -08:00
return (
<Transition
appear={true}
show={newBuildAvailable}
as={Fragment}
enter="transition ease-in duration-300"
enterFrom="translate-y-0"
enterTo="-translate-y-[130px] md:-translate-y-20"
leave="transition ease-out"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<button
className="fixed -bottom-[46px] left-1/2 z-50 flex -translate-x-1/2 items-center rounded-full border border-th-bkg-4 bg-th-bkg-3 px-4 py-3 shadow-md focus:outline-none md:hover:bg-th-bkg-4 md:hover:shadow-none"
2023-03-06 03:48:24 -08:00
onClick={() => window.location.reload()}
>
<p className="mr-2 whitespace-nowrap text-th-fgd-1">
{t('new-version')}
</p>
<ArrowPathIcon className="h-5 w-5" />
</button>
</Transition>
)
}