Prompt to attach receive wallet on post (#2059)
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
parent
d3b81e4346
commit
719cb2d507
@ -898,6 +898,14 @@ export default {
|
||||
|
||||
await models.user.update({ where: { id: me.id }, data: { hideWelcomeBanner: true } })
|
||||
return true
|
||||
},
|
||||
hideWalletRecvPrompt: async (parent, data, { me, models }) => {
|
||||
if (!me) {
|
||||
throw new GqlAuthenticationError()
|
||||
}
|
||||
|
||||
await models.user.update({ where: { id: me.id }, data: { hideWalletRecvPrompt: true } })
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -38,6 +38,7 @@ export default gql`
|
||||
unlinkAuth(authType: String!): AuthMethods!
|
||||
linkUnverifiedEmail(email: String!): Boolean
|
||||
hideWelcomeBanner: Boolean
|
||||
hideWalletRecvPrompt: Boolean
|
||||
subscribeUserPosts(id: ID): User
|
||||
subscribeUserComments(id: ID): User
|
||||
toggleMute(id: ID): User
|
||||
@ -141,6 +142,7 @@ export default gql`
|
||||
"""
|
||||
lastCheckedJobs: String
|
||||
hideWelcomeBanner: Boolean!
|
||||
hideWalletRecvPrompt: Boolean!
|
||||
tipPopover: Boolean!
|
||||
upvotePopover: Boolean!
|
||||
hasInvites: Boolean!
|
||||
|
@ -8,6 +8,7 @@ import { RETRY_PAID_ACTION } from '@/fragments/paidAction'
|
||||
import gql from 'graphql-tag'
|
||||
import { USER_ID } from '@/lib/constants'
|
||||
import { useMe } from './me'
|
||||
import { useWalletRecvPrompt, WalletPromptClosed } from '@/wallets/prompt'
|
||||
|
||||
// this is intented to be compatible with upsert item mutations
|
||||
// so that it can be reused for all post types and comments and we don't have
|
||||
@ -22,9 +23,17 @@ export default function useItemSubmit (mutation,
|
||||
const crossposter = useCrossposter()
|
||||
const [upsertItem] = usePaidMutation(mutation)
|
||||
const { me } = useMe()
|
||||
const walletPrompt = useWalletRecvPrompt()
|
||||
|
||||
return useCallback(
|
||||
async ({ boost, crosspost, title, options, bounty, status, ...values }, { resetForm }) => {
|
||||
try {
|
||||
await walletPrompt()
|
||||
} catch (err) {
|
||||
if (err instanceof WalletPromptClosed) return
|
||||
throw err
|
||||
}
|
||||
|
||||
if (options) {
|
||||
// remove existing poll options since else they will be appended as duplicates
|
||||
options = options.slice(item?.poll?.options?.length || 0).filter(o => o.trim().length > 0)
|
||||
@ -93,7 +102,7 @@ export default function useItemSubmit (mutation,
|
||||
}
|
||||
}
|
||||
}, [me, upsertItem, router, crossposter, item, sub, onSuccessfulSubmit,
|
||||
navigateOnSubmit, extraValues, paidMutationOptions]
|
||||
navigateOnSubmit, extraValues, paidMutationOptions, walletPrompt]
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ ${STREAK_FIELDS}
|
||||
hideFromTopUsers
|
||||
hideWalletBalance
|
||||
hideWelcomeBanner
|
||||
hideWalletRecvPrompt
|
||||
imgproxyOnly
|
||||
showImagesAndVideos
|
||||
nostrCrossposting
|
||||
@ -167,6 +168,11 @@ export const USER_SUGGESTIONS = gql`
|
||||
}
|
||||
}`
|
||||
|
||||
export const HIDE_WALLET_RECV_PROMPT_MUTATION = gql`
|
||||
mutation hideWalletRecvPrompt {
|
||||
hideWalletRecvPrompt
|
||||
}`
|
||||
|
||||
export const USER_SEARCH = gql`
|
||||
${STREAK_FIELDS}
|
||||
query searchUsers($q: String!, $limit: Limit, $similarity: Float) {
|
||||
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "hideWalletRecvPrompt" BOOLEAN NOT NULL DEFAULT false;
|
@ -116,6 +116,7 @@ model User {
|
||||
followers UserSubscription[] @relation("follower")
|
||||
followees UserSubscription[] @relation("followee")
|
||||
hideWelcomeBanner Boolean @default(false)
|
||||
hideWalletRecvPrompt Boolean @default(false)
|
||||
diagnostics Boolean @default(false)
|
||||
hideIsContributor Boolean @default(false)
|
||||
lnAddr String?
|
||||
|
@ -125,4 +125,28 @@
|
||||
color: var(--theme-toolbarHover) !important;
|
||||
background-color: var(--theme-toolbarHover) !important;
|
||||
border: 1px solid var(--theme-toolbarActive);
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: var(--theme-grey);
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.separator::before,
|
||||
.separator::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
border-bottom: 1px solid var(--theme-grey);
|
||||
}
|
||||
|
||||
.separator:not(:empty)::before {
|
||||
margin-right: .25em;
|
||||
}
|
||||
|
||||
.separator:not(:empty)::after {
|
||||
margin-left: .25em;
|
||||
}
|
||||
|
151
wallets/prompt.js
Normal file
151
wallets/prompt.js
Normal file
@ -0,0 +1,151 @@
|
||||
import { useCallback } from 'react'
|
||||
import { boolean, object } from 'yup'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { Form, ClientInput, SubmitButton, Checkbox } from '@/components/form'
|
||||
import { useMe } from '@/components/me'
|
||||
import { useShowModal } from '@/components/modal'
|
||||
import Link from 'next/link'
|
||||
import { useWallet } from '@/wallets/index'
|
||||
import { useWalletConfigurator } from '@/wallets/config'
|
||||
import styles from '@/styles/wallet.module.css'
|
||||
import { externalLightningAddressValidator } from '@/lib/validate'
|
||||
import { autowithdrawInitial } from '@/components/autowithdraw-shared'
|
||||
import { useMutation } from '@apollo/client'
|
||||
import { HIDE_WALLET_RECV_PROMPT_MUTATION } from '@/fragments/users'
|
||||
import { useToast } from '@/components/toast'
|
||||
|
||||
export class WalletPromptClosed extends Error {
|
||||
constructor () {
|
||||
super('wallet prompt closed')
|
||||
}
|
||||
}
|
||||
|
||||
export function useWalletRecvPrompt () {
|
||||
const { me } = useMe()
|
||||
const showModal = useShowModal()
|
||||
const toaster = useToast()
|
||||
|
||||
const onAttach = useCallback(({ onClose, resolve }) =>
|
||||
() => {
|
||||
toaster.success('lightning address saved', { persistOnNavigate: true })
|
||||
resolve()
|
||||
onClose()
|
||||
}, [toaster])
|
||||
|
||||
const onSkip = useCallback(({ onClose, resolve }) =>
|
||||
() => {
|
||||
resolve()
|
||||
onClose()
|
||||
}, [])
|
||||
|
||||
return useCallback((e) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// TODO: check if user told us to not show again
|
||||
if (!me || me.optional?.hasRecvWallet || me.privates?.hideWalletRecvPrompt) return resolve()
|
||||
|
||||
showModal(onClose => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<LnAddrForm onAttach={onAttach({ onClose, resolve })} className='mt-3' />
|
||||
<div className={styles.separator}>or</div>
|
||||
<WalletLink />
|
||||
<div className={styles.separator}>or</div>
|
||||
<SkipForm onSkip={onSkip({ onClose, resolve })} />
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}, { keepOpen: true, onClose: () => reject(new WalletPromptClosed()) })
|
||||
})
|
||||
}, [!!me, me?.optional?.hasRecvWallet, me?.privates?.hideWalletRecvPrompt, showModal, onAttach, onSkip])
|
||||
}
|
||||
|
||||
const Header = () => (
|
||||
<div className='fw-bold text-center mb-3'>
|
||||
You need to attach a<br />
|
||||
<span className='fw-bold text-primary fs-1' style={{ fontFamily: 'lightning' }}>lightning wallet</span>
|
||||
<br />
|
||||
to receive sats
|
||||
</div>
|
||||
)
|
||||
|
||||
const LnAddrForm = ({ onAttach }) => {
|
||||
const { me } = useMe()
|
||||
const wallet = useWallet('lightning-address')
|
||||
const { save } = useWalletConfigurator(wallet)
|
||||
|
||||
const schema = object({ lnAddr: externalLightningAddressValidator.required('required') })
|
||||
|
||||
const onSubmit = useCallback(async ({ lnAddr }) => {
|
||||
await save({
|
||||
...autowithdrawInitial({ me }),
|
||||
priority: 0,
|
||||
enabled: true,
|
||||
address: lnAddr
|
||||
}, true)
|
||||
onAttach()
|
||||
}, [save])
|
||||
|
||||
return (
|
||||
<>
|
||||
<span>You can enter a <span className='fw-bold'>lightning address</span>:</span>
|
||||
<Form
|
||||
schema={schema}
|
||||
onSubmit={onSubmit}
|
||||
initial={{ lnAddr: '' }}
|
||||
>
|
||||
<ClientInput
|
||||
name='lnAddr'
|
||||
groupClassName='mt-1 mb-3'
|
||||
append={<SubmitButton variant='primary' size='sm'>save</SubmitButton>}
|
||||
/>
|
||||
</Form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const WalletLink = () => <span>visit <Link href='/wallets'>wallets</Link> to set up a different wallet</span>
|
||||
|
||||
const SkipForm = ({ onSkip }) => {
|
||||
const { me } = useMe()
|
||||
const [hideWalletRecvPrompt] = useMutation(HIDE_WALLET_RECV_PROMPT_MUTATION, {
|
||||
update (cache) {
|
||||
cache.modify({
|
||||
id: `User:${me.id}`,
|
||||
fields: {
|
||||
hideWalletRecvPrompt () {
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const onSubmit = useCallback(({ dontShowAgain }) => {
|
||||
if (dontShowAgain) {
|
||||
// XXX this is not so important to wait for it to complete or make sure it succeeds
|
||||
hideWalletRecvPrompt().catch(err => console.error('hideWalletRecvPrompt error:', err))
|
||||
}
|
||||
onSkip()
|
||||
}, [hideWalletRecvPrompt])
|
||||
|
||||
const schema = object({ dontShowAgain: boolean().required() })
|
||||
return (
|
||||
<Form
|
||||
initial={{ dontShowAgain: false }}
|
||||
className='d-flex justify-content-between align-items-center mt-3'
|
||||
onSubmit={onSubmit}
|
||||
schema={schema}
|
||||
>
|
||||
<Checkbox label="don't show again" name='dontShowAgain' groupClassName='mb-0' />
|
||||
<Button type='submit' variant='secondary' size='sm'>skip</Button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
const Footer = () => (
|
||||
<div className='mt-3 text-center text-muted small'>
|
||||
Stacker News is non-custodial. If you don't attach a wallet, you will receive credits when zapped.
|
||||
See the <Link href='/faq#wallets'>FAQ</Link> for the details.
|
||||
</div>
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user