check for new notifications in context/singleton
This commit is contained in:
parent
bfe5edcfe3
commit
b884bde24d
@ -1,356 +0,0 @@
|
|||||||
import Dropdown from 'react-bootstrap/Dropdown'
|
|
||||||
import Navbar from 'react-bootstrap/Navbar'
|
|
||||||
import Nav from 'react-bootstrap/Nav'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import styles from './header.module.css'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import Button from 'react-bootstrap/Button'
|
|
||||||
import Container from 'react-bootstrap/Container'
|
|
||||||
import Price from './price'
|
|
||||||
import { useMe } from './me'
|
|
||||||
import Head from 'next/head'
|
|
||||||
import { signOut } from 'next-auth/react'
|
|
||||||
import { useCallback, useEffect } from 'react'
|
|
||||||
import { randInRange } from '@/lib/rand'
|
|
||||||
import { abbrNum, msatsToSats } from '@/lib/format'
|
|
||||||
import NoteIcon from '@/svgs/notification-4-fill.svg'
|
|
||||||
import { useQuery } from '@apollo/client'
|
|
||||||
import LightningIcon from '@/svgs/bolt.svg'
|
|
||||||
import SearchIcon from '@/svgs/search-line.svg'
|
|
||||||
import BackArrow from '@/svgs/arrow-left-line.svg'
|
|
||||||
import { BALANCE_LIMIT_MSATS, SSR } from '@/lib/constants'
|
|
||||||
import { useLightning } from './lightning'
|
|
||||||
import { HAS_NOTIFICATIONS } from '@/fragments/notifications'
|
|
||||||
import AnonIcon from '@/svgs/spy-fill.svg'
|
|
||||||
import Hat from './hat'
|
|
||||||
import HiddenWalletSummary from './hidden-wallet-summary'
|
|
||||||
import { clearNotifications } from '@/lib/badge'
|
|
||||||
import { useServiceWorker } from './serviceworker'
|
|
||||||
import SubSelect from './sub-select'
|
|
||||||
|
|
||||||
function WalletSummary ({ me }) {
|
|
||||||
if (!me) return null
|
|
||||||
if (me.privates?.hideWalletBalance) {
|
|
||||||
return <HiddenWalletSummary abbreviate fixedWidth />
|
|
||||||
}
|
|
||||||
return `${abbrNum(me.privates?.sats)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function Back () {
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
return router.asPath !== '/' &&
|
|
||||||
<a
|
|
||||||
role='button' tabIndex='0' className='nav-link standalone p-0' onClick={() => {
|
|
||||||
if (typeof window.navigation === 'undefined' || window.navigation.canGoBack === undefined || window?.navigation.canGoBack) {
|
|
||||||
router.back()
|
|
||||||
} else {
|
|
||||||
router.push('/')
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BackArrow className='theme me-1 me-md-2' width={22} height={22} />
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
|
|
||||||
function NotificationBell () {
|
|
||||||
const { data } = useQuery(HAS_NOTIFICATIONS, SSR
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
pollInterval: 30000,
|
|
||||||
nextFetchPolicy: 'cache-and-network',
|
|
||||||
onCompleted: ({ hasNewNotes }) => {
|
|
||||||
if (!hasNewNotes) {
|
|
||||||
clearNotifications()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<link rel='shortcut icon' href={data?.hasNewNotes ? '/favicon-notify.png' : '/favicon.png'} />
|
|
||||||
</Head>
|
|
||||||
<Link href='/notifications' passHref legacyBehavior>
|
|
||||||
<Nav.Link eventKey='notifications' className='ps-0 position-relative'>
|
|
||||||
<NoteIcon height={22} width={22} className='theme' style={{ marginTop: '-4px' }} />
|
|
||||||
{data?.hasNewNotes &&
|
|
||||||
<span className={styles.notification}>
|
|
||||||
<span className='invisible'>{' '}</span>
|
|
||||||
</span>}
|
|
||||||
</Nav.Link>
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NavProfileMenu ({ me, dropNavKey }) {
|
|
||||||
const { registration: swRegistration, togglePushSubscription } = useServiceWorker()
|
|
||||||
return (
|
|
||||||
<div className='position-relative'>
|
|
||||||
<Dropdown className={styles.dropdown} align='end'>
|
|
||||||
<Dropdown.Toggle className='nav-link nav-item' id='profile' variant='custom'>
|
|
||||||
<Nav.Link eventKey={me.name} as='span' className='p-0'>
|
|
||||||
{`@${me.name}`}<Hat user={me} />
|
|
||||||
</Nav.Link>
|
|
||||||
</Dropdown.Toggle>
|
|
||||||
<Dropdown.Menu>
|
|
||||||
<Link href={'/' + me.name} passHref legacyBehavior>
|
|
||||||
<Dropdown.Item active={me.name === dropNavKey}>
|
|
||||||
profile
|
|
||||||
{me && !me.bioId &&
|
|
||||||
<div className='p-1 d-inline-block bg-secondary ms-1'>
|
|
||||||
<span className='invisible'>{' '}</span>
|
|
||||||
</div>}
|
|
||||||
</Dropdown.Item>
|
|
||||||
</Link>
|
|
||||||
<Link href={'/' + me.name + '/bookmarks'} passHref legacyBehavior>
|
|
||||||
<Dropdown.Item active={me.name + '/bookmarks' === dropNavKey}>bookmarks</Dropdown.Item>
|
|
||||||
</Link>
|
|
||||||
<Link href='/wallet' passHref legacyBehavior>
|
|
||||||
<Dropdown.Item eventKey='wallet'>wallet</Dropdown.Item>
|
|
||||||
</Link>
|
|
||||||
<Link href='/satistics?inc=invoice,withdrawal,stacked,spent' passHref legacyBehavior>
|
|
||||||
<Dropdown.Item eventKey='satistics'>satistics</Dropdown.Item>
|
|
||||||
</Link>
|
|
||||||
<Dropdown.Divider />
|
|
||||||
<Link href='/referrals/month' passHref legacyBehavior>
|
|
||||||
<Dropdown.Item eventKey='referrals'>referrals</Dropdown.Item>
|
|
||||||
</Link>
|
|
||||||
<Dropdown.Divider />
|
|
||||||
<div className='d-flex align-items-center'>
|
|
||||||
<Link href='/settings' passHref legacyBehavior>
|
|
||||||
<Dropdown.Item eventKey='settings'>settings</Dropdown.Item>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<Dropdown.Divider />
|
|
||||||
<Dropdown.Item
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
// order is important because we need to be logged in to delete push subscription on server
|
|
||||||
const pushSubscription = await swRegistration?.pushManager.getSubscription()
|
|
||||||
if (pushSubscription) {
|
|
||||||
await togglePushSubscription()
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// don't prevent signout because of an unsubscription error
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
await signOut({ callbackUrl: '/' })
|
|
||||||
}}
|
|
||||||
>logout
|
|
||||||
</Dropdown.Item>
|
|
||||||
</Dropdown.Menu>
|
|
||||||
</Dropdown>
|
|
||||||
{!me.bioId &&
|
|
||||||
<span className='position-absolute p-1 bg-secondary' style={{ top: '5px', right: '0px' }}>
|
|
||||||
<span className='invisible'>{' '}</span>
|
|
||||||
</span>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function StackerCorner ({ dropNavKey }) {
|
|
||||||
const me = useMe()
|
|
||||||
|
|
||||||
const walletLimitReached = me.privates?.sats >= msatsToSats(BALANCE_LIMIT_MSATS)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='d-flex ms-auto'>
|
|
||||||
<NotificationBell />
|
|
||||||
<NavProfileMenu me={me} dropNavKey={dropNavKey} />
|
|
||||||
<Nav.Item>
|
|
||||||
<Link href='/wallet' passHref legacyBehavior>
|
|
||||||
<Nav.Link eventKey='wallet' className={`${walletLimitReached ? 'text-warning' : 'text-success'} text-monospace px-0 text-nowrap`}>
|
|
||||||
<WalletSummary me={me} />
|
|
||||||
</Nav.Link>
|
|
||||||
</Link>
|
|
||||||
</Nav.Item>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function LurkerCorner ({ path }) {
|
|
||||||
const router = useRouter()
|
|
||||||
const strike = useLightning()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!window.localStorage.getItem('striked')) {
|
|
||||||
const to = setTimeout(() => {
|
|
||||||
strike()
|
|
||||||
window.localStorage.setItem('striked', 'yep')
|
|
||||||
}, randInRange(3000, 10000))
|
|
||||||
return () => clearTimeout(to)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleLogin = useCallback(async pathname => await router.push({
|
|
||||||
pathname,
|
|
||||||
query: { callbackUrl: window.location.origin + router.asPath }
|
|
||||||
}), [router])
|
|
||||||
|
|
||||||
return path !== '/login' && path !== '/signup' && !path.startsWith('/invites') &&
|
|
||||||
<div className='ms-auto'>
|
|
||||||
<Button
|
|
||||||
className='align-items-center px-3 py-1 me-2'
|
|
||||||
id='signup'
|
|
||||||
style={{ borderWidth: '2px' }}
|
|
||||||
variant='outline-grey-darkmode'
|
|
||||||
onClick={() => handleLogin('/login')}
|
|
||||||
>
|
|
||||||
login
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className='align-items-center ps-2 py-1 pe-3'
|
|
||||||
style={{ borderWidth: '2px' }}
|
|
||||||
id='login'
|
|
||||||
onClick={() => handleLogin('/signup')}
|
|
||||||
>
|
|
||||||
<LightningIcon
|
|
||||||
width={17}
|
|
||||||
height={17}
|
|
||||||
className='me-1'
|
|
||||||
/>sign up
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const PREPEND_SUBS = ['home']
|
|
||||||
const APPEND_SUBS = [{ label: '--------', items: ['create'] }]
|
|
||||||
function NavItems ({ className, sub: subName, prefix }) {
|
|
||||||
const sub = subName || 'home'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Nav.Item className={`me-1 ${className}`}>
|
|
||||||
<SubSelect
|
|
||||||
sub={sub} prependSubs={PREPEND_SUBS} appendSubs={APPEND_SUBS} noForm
|
|
||||||
groupClassName='mb-0'
|
|
||||||
/>
|
|
||||||
</Nav.Item>
|
|
||||||
<Nav.Item className={className}>
|
|
||||||
<Link href={prefix + '/'} passHref legacyBehavior>
|
|
||||||
<Nav.Link eventKey='' className={styles.navLink}>hot</Nav.Link>
|
|
||||||
</Link>
|
|
||||||
</Nav.Item>
|
|
||||||
<Nav.Item className={className}>
|
|
||||||
<Link href={prefix + '/recent'} passHref legacyBehavior>
|
|
||||||
<Nav.Link eventKey='recent' className={styles.navLink}>recent</Nav.Link>
|
|
||||||
</Link>
|
|
||||||
</Nav.Item>
|
|
||||||
{sub !== 'jobs' &&
|
|
||||||
<Nav.Item className={className}>
|
|
||||||
<Link
|
|
||||||
href={{
|
|
||||||
pathname: '/~/top/[type]/[when]',
|
|
||||||
query: { type: 'posts', when: 'day', sub: subName }
|
|
||||||
}} as={prefix + '/top/posts/day'} passHref legacyBehavior
|
|
||||||
>
|
|
||||||
<Nav.Link eventKey='top' className={styles.navLink}>top</Nav.Link>
|
|
||||||
</Link>
|
|
||||||
</Nav.Item>}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PostItem ({ className, prefix }) {
|
|
||||||
const me = useMe()
|
|
||||||
|
|
||||||
if (me) {
|
|
||||||
return (
|
|
||||||
<Link href={prefix + '/post'} className={`${className} btn btn-md btn-primary px-3 py-1 `}>
|
|
||||||
post
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
href={prefix + '/post'}
|
|
||||||
className={`${className} ${styles.postAnon} btn btn-md btn-outline-grey-darkmode d-flex align-items-center px-3 py-0 py-lg-1`}
|
|
||||||
>
|
|
||||||
<AnonIcon className='me-1 fill-secondary' width={16} height={16} /> post
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Header ({ sub }) {
|
|
||||||
const router = useRouter()
|
|
||||||
const path = router.asPath.split('?')[0]
|
|
||||||
const prefix = sub ? `/~${sub}` : ''
|
|
||||||
const topNavKey = path.split('/')[sub ? 2 : 1] ?? ''
|
|
||||||
const dropNavKey = path.split('/').slice(sub ? 2 : 1).join('/')
|
|
||||||
const me = useMe()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container as='header' className='px-sm-0'>
|
|
||||||
<Navbar className='pb-0 pb-lg-2'>
|
|
||||||
<Nav
|
|
||||||
className={styles.navbarNav}
|
|
||||||
activeKey={topNavKey}
|
|
||||||
>
|
|
||||||
<div className='d-flex align-items-center'>
|
|
||||||
<Back />
|
|
||||||
<Link href='/' passHref legacyBehavior>
|
|
||||||
<Navbar.Brand className={`${styles.brand} d-flex me-0 me-md-2`}>
|
|
||||||
SN
|
|
||||||
</Navbar.Brand>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<NavItems className='d-none d-lg-flex mx-2' prefix={prefix} sub={sub} />
|
|
||||||
<PostItem className='d-none d-lg-flex mx-2' prefix={prefix} />
|
|
||||||
<Link href={prefix + '/search'} passHref legacyBehavior>
|
|
||||||
<Nav.Link eventKey='search' className='position-relative d-none d-lg-flex align-items-center pe-0 ms-2'>
|
|
||||||
<SearchIcon className='theme' width={22} height={22} />
|
|
||||||
</Nav.Link>
|
|
||||||
</Link>
|
|
||||||
<Nav.Item className={`${styles.price} ms-auto align-items-center ${me?.name.length > 10 ? 'd-none d-lg-flex' : ''}`}>
|
|
||||||
<Price className='nav-link text-monospace' />
|
|
||||||
</Nav.Item>
|
|
||||||
{me ? <StackerCorner dropNavKey={dropNavKey} /> : <LurkerCorner path={path} />}
|
|
||||||
</Nav>
|
|
||||||
</Navbar>
|
|
||||||
<Navbar className='pt-0 pb-2 d-lg-none'>
|
|
||||||
<Nav
|
|
||||||
className={styles.navbarNav}
|
|
||||||
activeKey={topNavKey}
|
|
||||||
>
|
|
||||||
<NavItems prefix={prefix} sub={sub} />
|
|
||||||
<Link href={prefix + '/search'} passHref legacyBehavior>
|
|
||||||
<Nav.Link eventKey='search' className='position-relative me-auto ms-auto me-sm-1 d-flex'>
|
|
||||||
<SearchIcon className='theme' width={22} height={22} />
|
|
||||||
</Nav.Link>
|
|
||||||
</Link>
|
|
||||||
<PostItem className='me-0' prefix={prefix} />
|
|
||||||
</Nav>
|
|
||||||
</Navbar>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HeaderStatic () {
|
|
||||||
return (
|
|
||||||
<Container as='header' className='px-sm-0'>
|
|
||||||
<Navbar className='pb-0 pb-lg-1'>
|
|
||||||
<Nav
|
|
||||||
className={styles.navbarNav}
|
|
||||||
>
|
|
||||||
<div className='d-flex align-items-center'>
|
|
||||||
<Back />
|
|
||||||
<Link href='/' passHref legacyBehavior>
|
|
||||||
<Navbar.Brand className={styles.brand}>
|
|
||||||
SN
|
|
||||||
</Navbar.Brand>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link href='/search' passHref legacyBehavior>
|
|
||||||
<Nav.Link eventKey='search' className='position-relative d-flex align-items-center mx-2'>
|
|
||||||
<SearchIcon className='theme' width={22} height={22} />
|
|
||||||
</Nav.Link>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</Nav>
|
|
||||||
</Navbar>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
@ -6,10 +6,7 @@ import BackArrow from '../../svgs/arrow-left-line.svg'
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import Price from '../price'
|
import Price from '../price'
|
||||||
import SubSelect from '../sub-select'
|
import SubSelect from '../sub-select'
|
||||||
import { useQuery } from '@apollo/client'
|
import { ANON_USER_ID, BALANCE_LIMIT_MSATS } from '../../lib/constants'
|
||||||
import { HAS_NOTIFICATIONS } from '../../fragments/notifications'
|
|
||||||
import { ANON_USER_ID, BALANCE_LIMIT_MSATS, SSR } from '../../lib/constants'
|
|
||||||
import { clearNotifications } from '../../lib/badge'
|
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import NoteIcon from '../../svgs/notification-4-fill.svg'
|
import NoteIcon from '../../svgs/notification-4-fill.svg'
|
||||||
import { useMe } from '../me'
|
import { useMe } from '../me'
|
||||||
@ -24,6 +21,7 @@ import LightningIcon from '../../svgs/bolt.svg'
|
|||||||
import SearchIcon from '../../svgs/search-line.svg'
|
import SearchIcon from '../../svgs/search-line.svg'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import SnIcon from '@/svgs/sn.svg'
|
import SnIcon from '@/svgs/sn.svg'
|
||||||
|
import { useHasNewNotes } from '../use-has-new-notes'
|
||||||
|
|
||||||
export function Brand ({ className }) {
|
export function Brand ({ className }) {
|
||||||
return (
|
return (
|
||||||
@ -100,7 +98,7 @@ export function BackOrBrand ({ className }) {
|
|||||||
|
|
||||||
export function SearchItem ({ prefix, className }) {
|
export function SearchItem ({ prefix, className }) {
|
||||||
return (
|
return (
|
||||||
<Link href={prefix + '/search'} passHref legacyBehavior>
|
<Link href='/search' passHref legacyBehavior>
|
||||||
<Nav.Link eventKey='search' className={className}>
|
<Nav.Link eventKey='search' className={className}>
|
||||||
<SearchIcon className='theme' width={22} height={28} />
|
<SearchIcon className='theme' width={22} height={28} />
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
@ -132,27 +130,17 @@ export function NavSelect ({ sub: subName, className, size }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function NavNotifications ({ className }) {
|
export function NavNotifications ({ className }) {
|
||||||
const { data } = useQuery(HAS_NOTIFICATIONS, SSR
|
const hasNewNotes = useHasNewNotes()
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
pollInterval: 30000,
|
|
||||||
nextFetchPolicy: 'cache-and-network',
|
|
||||||
onCompleted: ({ hasNewNotes }) => {
|
|
||||||
if (!hasNewNotes) {
|
|
||||||
clearNotifications()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<link rel='shortcut icon' href={data?.hasNewNotes ? '/favicon-notify.png' : '/favicon.png'} />
|
<link rel='shortcut icon' href={hasNewNotes ? '/favicon-notify.png' : '/favicon.png'} />
|
||||||
</Head>
|
</Head>
|
||||||
<Link href='/notifications' passHref legacyBehavior>
|
<Link href='/notifications' passHref legacyBehavior>
|
||||||
<Nav.Link eventKey='notifications' className={classNames('position-relative', className)}>
|
<Nav.Link eventKey='notifications' className={classNames('position-relative', className)}>
|
||||||
<NoteIcon height={28} width={20} className='theme' />
|
<NoteIcon height={28} width={20} className='theme' />
|
||||||
{data?.hasNewNotes &&
|
{hasNewNotes &&
|
||||||
<span className={styles.notification}>
|
<span className={styles.notification}>
|
||||||
<span className='invisible'>{' '}</span>
|
<span className='invisible'>{' '}</span>
|
||||||
</span>}
|
</span>}
|
||||||
|
32
components/use-has-new-notes.js
Normal file
32
components/use-has-new-notes.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { HAS_NOTIFICATIONS } from '@/fragments/notifications'
|
||||||
|
import { clearNotifications } from '@/lib/badge'
|
||||||
|
import { SSR } from '@/lib/constants'
|
||||||
|
import { useQuery } from '@apollo/client'
|
||||||
|
import React, { useContext } from 'react'
|
||||||
|
|
||||||
|
export const HasNewNotesContext = React.createContext(false)
|
||||||
|
|
||||||
|
export function HasNewNotesProvider ({ me, children }) {
|
||||||
|
const { data } = useQuery(HAS_NOTIFICATIONS,
|
||||||
|
SSR
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
pollInterval: 30000,
|
||||||
|
nextFetchPolicy: 'cache-and-network',
|
||||||
|
onCompleted: ({ hasNewNotes }) => {
|
||||||
|
if (!hasNewNotes) {
|
||||||
|
clearNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HasNewNotesContext.Provider value={!!data?.hasNewNotes}>
|
||||||
|
{children}
|
||||||
|
</HasNewNotesContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useHasNewNotes () {
|
||||||
|
return useContext(HasNewNotesContext)
|
||||||
|
}
|
@ -20,6 +20,7 @@ import { LoggerProvider } from '@/components/logger'
|
|||||||
import { ChainFeeProvider } from '@/components/chain-fee.js'
|
import { ChainFeeProvider } from '@/components/chain-fee.js'
|
||||||
import { WebLNProvider } from '@/components/webln'
|
import { WebLNProvider } from '@/components/webln'
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
|
import { HasNewNotesProvider } from '@/components/use-has-new-notes'
|
||||||
|
|
||||||
const PWAPrompt = dynamic(() => import('react-ios-pwa-prompt'), { ssr: false })
|
const PWAPrompt = dynamic(() => import('react-ios-pwa-prompt'), { ssr: false })
|
||||||
|
|
||||||
@ -102,28 +103,30 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
|
|||||||
<PlausibleProvider domain='stacker.news' trackOutboundLinks>
|
<PlausibleProvider domain='stacker.news' trackOutboundLinks>
|
||||||
<ApolloProvider client={client}>
|
<ApolloProvider client={client}>
|
||||||
<MeProvider me={me}>
|
<MeProvider me={me}>
|
||||||
<LoggerProvider>
|
<HasNewNotesProvider>
|
||||||
<ServiceWorkerProvider>
|
<LoggerProvider>
|
||||||
<PriceProvider price={price}>
|
<ServiceWorkerProvider>
|
||||||
<LightningProvider>
|
<PriceProvider price={price}>
|
||||||
<ToastProvider>
|
<LightningProvider>
|
||||||
<WebLNProvider>
|
<ToastProvider>
|
||||||
<ShowModalProvider>
|
<WebLNProvider>
|
||||||
<BlockHeightProvider blockHeight={blockHeight}>
|
<ShowModalProvider>
|
||||||
<ChainFeeProvider chainFee={chainFee}>
|
<BlockHeightProvider blockHeight={blockHeight}>
|
||||||
<ErrorBoundary>
|
<ChainFeeProvider chainFee={chainFee}>
|
||||||
<Component ssrData={ssrData} {...otherProps} />
|
<ErrorBoundary>
|
||||||
{!router?.query?.disablePrompt && <PWAPrompt copyBody='This website has app functionality. Add it to your home screen to use it in fullscreen and receive notifications. In Safari:' promptOnVisit={2} />}
|
<Component ssrData={ssrData} {...otherProps} />
|
||||||
</ErrorBoundary>
|
{!router?.query?.disablePrompt && <PWAPrompt copyBody='This website has app functionality. Add it to your home screen to use it in fullscreen and receive notifications. In Safari:' promptOnVisit={2} />}
|
||||||
</ChainFeeProvider>
|
</ErrorBoundary>
|
||||||
</BlockHeightProvider>
|
</ChainFeeProvider>
|
||||||
</ShowModalProvider>
|
</BlockHeightProvider>
|
||||||
</WebLNProvider>
|
</ShowModalProvider>
|
||||||
</ToastProvider>
|
</WebLNProvider>
|
||||||
</LightningProvider>
|
</ToastProvider>
|
||||||
</PriceProvider>
|
</LightningProvider>
|
||||||
</ServiceWorkerProvider>
|
</PriceProvider>
|
||||||
</LoggerProvider>
|
</ServiceWorkerProvider>
|
||||||
|
</LoggerProvider>
|
||||||
|
</HasNewNotesProvider>
|
||||||
</MeProvider>
|
</MeProvider>
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
</PlausibleProvider>
|
</PlausibleProvider>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user