Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/web/src/pages/wallet-page/WalletPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useIsMobile } from 'hooks/useIsMobile'
import { WalletCoinsList } from 'pages/wallet-page/components/WalletCoinsList'

import { LinkedWallets } from './components/LinkedWallets'
import { SecureWalletReminderModal } from './components/SecureWalletReminderModal'

const messages = {
title: 'Wallet'
Expand Down Expand Up @@ -56,6 +57,7 @@ export const WalletPage = () => {
<AccountBalance />
<WalletCoinsList />
<LinkedWallets />
<SecureWalletReminderModal />
</Flex>
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { useCallback, useEffect, useState } from 'react'

import { useAudioBalance } from '@audius/common/api'
import { AUDIO } from '@audius/fixed-decimal'
import {
Button,
Modal,
ModalContent,
ModalContentText,
ModalHeader,
ModalTitle,
ModalFooter,
IconShieldCheck
} from '@audius/harmony'

import { localStorage } from 'services/local-storage'

const FAQ_URL =
'https://help.audius.co/product/crypto-wallet-faq-for-audius-users'
const DISMISS_STORAGE_KEY = 'secureWalletReminderDismissedAt'
const DISMISS_DURATION_MS = 14 * 24 * 60 * 60 * 1000 // 2 weeks
const BALANCE_THRESHOLD = AUDIO(1000).value

const messages = {
title: 'Protect your $AUDIO',
body1:
"You're holding 1000 $AUDIO or more. To keep your funds safe, we recommend moving your balance to an external wallet that only you control.",
body2:
"Self-custody means your $AUDIO isn't tied to a single account: you hold the keys, so you're protected even if your password is compromised. Any external wallet can be connected to your Audius account to keep badges and access.",
body3: 'Read the FAQ to learn how to transfer your balance securely.',
dismiss: 'Dismiss',
readFaq: 'Read the FAQ'
}

const wasRecentlyDismissed = async () => {
const dismissedAt = await localStorage.getItem(DISMISS_STORAGE_KEY)
if (!dismissedAt) return false
const timestamp = Number(dismissedAt)
if (Number.isNaN(timestamp)) return false
return Date.now() - timestamp < DISMISS_DURATION_MS
}

export const SecureWalletReminderModal = () => {
const [isOpen, setIsOpen] = useState(false)
const { accountBalance, isLoading } = useAudioBalance()

useEffect(() => {
if (isLoading || isOpen) return
if (accountBalance < BALANCE_THRESHOLD) return

let cancelled = false
const maybeOpen = async () => {
const dismissed = await wasRecentlyDismissed()
if (!cancelled && !dismissed) {
setIsOpen(true)
}
}
maybeOpen()
return () => {
cancelled = true
}
}, [accountBalance, isLoading, isOpen])

const handleDismiss = useCallback(() => {
localStorage.setItem(DISMISS_STORAGE_KEY, String(Date.now()))
setIsOpen(false)
}, [])

const handleReadFaq = useCallback(() => {
window.open(FAQ_URL, '_blank', 'noopener,noreferrer')
handleDismiss()
}, [handleDismiss])

return (
<Modal isOpen={isOpen} onClose={handleDismiss} size='small'>
<ModalHeader>
<ModalTitle icon={<IconShieldCheck />} title={messages.title} />
</ModalHeader>
<ModalContent>
<ModalContentText>{messages.body1}</ModalContentText>
<ModalContentText>{messages.body2}</ModalContentText>
<ModalContentText>{messages.body3}</ModalContentText>
</ModalContent>
<ModalFooter>
<Button fullWidth variant='secondary' onClick={handleDismiss}>
{messages.dismiss}
</Button>
<Button fullWidth variant='primary' onClick={handleReadFaq}>
{messages.readFaq}
</Button>
</ModalFooter>
</Modal>
)
}
Loading