random zap amounts (#1263)
* add random zapping support adds an option to enable random zap amounts per stacker configurable in settings, you can enable this feature and provide an upper and lower range of your random zap amount * rename github eslint check to lint this has been bothering me since we aren't using eslint for linting * fixup! add random zapping support * fixup! rename github eslint check to lint * fixup! fixup! add random zapping support --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
parent
4964e2c7d1
commit
dc0370ba17
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@ -1,8 +1,8 @@
|
||||
name: Eslint Check
|
||||
name: Lint Check
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
eslint-run:
|
||||
lint-run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
@ -1004,6 +1004,12 @@ export default {
|
||||
})
|
||||
|
||||
return relays?.map(r => r.nostrRelayAddr)
|
||||
},
|
||||
tipRandom: async (user, args, { me }) => {
|
||||
if (!me || me.id !== user.id) {
|
||||
return false
|
||||
}
|
||||
return !!user.tipRandomMin && !!user.tipRandomMax
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -98,6 +98,8 @@ export default gql`
|
||||
noteItemMentions: Boolean!
|
||||
nsfwMode: Boolean!
|
||||
tipDefault: Int!
|
||||
tipRandomMin: Int
|
||||
tipRandomMax: Int
|
||||
turboTipping: Boolean!
|
||||
zapUndos: Int
|
||||
wildWestMode: Boolean!
|
||||
@ -165,6 +167,9 @@ export default gql`
|
||||
noteItemMentions: Boolean!
|
||||
nsfwMode: Boolean!
|
||||
tipDefault: Int!
|
||||
tipRandom: Boolean!
|
||||
tipRandomMin: Int
|
||||
tipRandomMax: Int
|
||||
turboTipping: Boolean!
|
||||
zapUndos: Int
|
||||
wildWestMode: Boolean!
|
||||
|
@ -7,7 +7,7 @@ import UpBolt from '@/svgs/bolt.svg'
|
||||
import { amountSchema } from '@/lib/validate'
|
||||
import { useToast } from './toast'
|
||||
import { useLightning } from './lightning'
|
||||
import { nextTip } from './upvote'
|
||||
import { nextTip, defaultTipIncludingRandom } from './upvote'
|
||||
import { ZAP_UNDO_DELAY_MS } from '@/lib/constants'
|
||||
import { usePaidMutation } from './use-paid-mutation'
|
||||
import { ACT_MUTATION } from '@/fragments/paidAction'
|
||||
@ -101,7 +101,7 @@ export default function ItemAct ({ onClose, item, down, children, abortSignal })
|
||||
return (
|
||||
<Form
|
||||
initial={{
|
||||
amount: me?.privates?.tipDefault || defaultTips[0],
|
||||
amount: defaultTipIncludingRandom(me?.privates) || defaultTips[0],
|
||||
default: false
|
||||
}}
|
||||
schema={amountSchema}
|
||||
|
@ -26,7 +26,7 @@ const UpvotePopover = ({ target, show, handleClose }) => {
|
||||
<button type='button' className='btn-close' onClick={handleClose}><span className='visually-hidden-focusable'>Close alert</span></button>
|
||||
</Popover.Header>
|
||||
<Popover.Body>
|
||||
<div className='mb-2'>Press the bolt again to zap {me?.privates?.tipDefault || 1} more sat{me?.privates?.tipDefault > 1 ? 's' : ''}.</div>
|
||||
<div className='mb-2'>Press the bolt again to zap {me?.privates?.tipRandom ? 'a random amount of' : `${me?.privates?.tipDefault || 1} more`} sat{me?.privates?.tipDefault > 1 ? 's' : ''}.</div>
|
||||
<div>Repeatedly press the bolt to zap more sats.</div>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
@ -67,11 +67,20 @@ export function DropdownItemUpVote ({ item }) {
|
||||
)
|
||||
}
|
||||
|
||||
export const nextTip = (meSats, { tipDefault, turboTipping }) => {
|
||||
// what should our next tip be?
|
||||
if (!turboTipping) return (tipDefault || 1)
|
||||
export const defaultTipIncludingRandom = ({ tipDefault, tipRandom, tipRandomMin, tipRandomMax }) =>
|
||||
tipRandom
|
||||
? Math.floor((Math.random() * (tipRandomMax - tipRandomMin)) + tipRandomMin)
|
||||
: (tipDefault || 1)
|
||||
|
||||
let sats = tipDefault || 1
|
||||
export const nextTip = (meSats, { tipDefault, turboTipping, tipRandom, tipRandomMin, tipRandomMax }) => {
|
||||
// what should our next tip be?
|
||||
const calculatedDefault = defaultTipIncludingRandom({ tipDefault, tipRandom, tipRandomMin, tipRandomMax })
|
||||
|
||||
if (!turboTipping) {
|
||||
return calculatedDefault
|
||||
}
|
||||
|
||||
let sats = calculatedDefault
|
||||
if (turboTipping) {
|
||||
while (meSats >= sats) {
|
||||
sats *= 10
|
||||
@ -138,11 +147,17 @@ export default function UpVote ({ item, className }) {
|
||||
|
||||
// what should our next tip be?
|
||||
const sats = nextTip(meSats, { ...me?.privates })
|
||||
let overlayTextContent
|
||||
if (me) {
|
||||
overlayTextContent = me.privates?.tipRandom ? 'random' : numWithUnits(sats, { abbreviate: false })
|
||||
} else {
|
||||
overlayTextContent = 'zap it'
|
||||
}
|
||||
|
||||
return [
|
||||
meSats, me ? numWithUnits(sats, { abbreviate: false }) : 'zap it',
|
||||
meSats, overlayTextContent,
|
||||
getColor(meSats), getColor(meSats + sats)]
|
||||
}, [item?.meSats, item?.meAnonSats, me?.privates?.tipDefault, me?.privates?.turboDefault])
|
||||
}, [item?.meSats, item?.meAnonSats, me?.privates?.tipDefault, me?.privates?.turboDefault, me?.privates?.tipRandom, me?.privates?.tipRandomMin, me?.privates?.tipRandomMax])
|
||||
|
||||
const handleModalClosed = () => {
|
||||
setHover(false)
|
||||
|
@ -41,6 +41,9 @@ export const ME = gql`
|
||||
noteItemMentions
|
||||
sats
|
||||
tipDefault
|
||||
tipRandom
|
||||
tipRandomMin
|
||||
tipRandomMax
|
||||
tipPopover
|
||||
turboTipping
|
||||
zapUndos
|
||||
@ -66,6 +69,9 @@ export const SETTINGS_FIELDS = gql`
|
||||
fragment SettingsFields on User {
|
||||
privates {
|
||||
tipDefault
|
||||
tipRandom
|
||||
tipRandomMin
|
||||
tipRandomMax
|
||||
turboTipping
|
||||
zapUndos
|
||||
fiatCurrency
|
||||
|
@ -540,8 +540,24 @@ export const actSchema = object({
|
||||
act: string().required('required').oneOf(['TIP', 'DONT_LIKE_THIS'])
|
||||
})
|
||||
|
||||
export const settingsSchema = object({
|
||||
export const settingsSchema = object().shape({
|
||||
tipDefault: intValidator.required('required').positive('must be positive'),
|
||||
tipRandomMin: intValidator.nullable().positive('must be positive')
|
||||
.when(['tipRandomMax'], ([max], schema) => {
|
||||
let res = schema
|
||||
if (max) {
|
||||
res = schema.required('minimum and maximum must either both be omitted or specified').nonNullable()
|
||||
}
|
||||
return res.lessThan(max, 'must be less than maximum')
|
||||
}),
|
||||
tipRandomMax: intValidator.nullable().positive('must be positive')
|
||||
.when(['tipRandomMin'], ([min], schema) => {
|
||||
let res = schema
|
||||
if (min) {
|
||||
res = schema.required('minimum and maximum must either both be omitted or specified').nonNullable()
|
||||
}
|
||||
return res.moreThan(min, 'must be more than minimum')
|
||||
}),
|
||||
fiatCurrency: string().required('required').oneOf(SUPPORTED_CURRENCIES),
|
||||
withdrawMaxFeeDefault: intValidator.required('required').positive('must be positive'),
|
||||
nostrPubkey: string().nullable()
|
||||
@ -561,7 +577,8 @@ export const settingsSchema = object({
|
||||
noReferralLinks: boolean(),
|
||||
hideIsContributor: boolean(),
|
||||
zapUndos: intValidator.nullable().min(0, 'must be greater or equal to 0')
|
||||
})
|
||||
// exclude from cyclic analysis. see https://github.com/jquense/yup/issues/720
|
||||
}, [['tipRandomMax', 'tipRandomMin']])
|
||||
|
||||
const warningMessage = 'If I logout, even accidentally, I will never be able to access my account again'
|
||||
export const lastAuthRemovalSchema = object({
|
||||
|
@ -112,6 +112,9 @@ export default function Settings ({ ssrData }) {
|
||||
<Form
|
||||
initial={{
|
||||
tipDefault: settings?.tipDefault || 21,
|
||||
tipRandom: settings?.tipRandom,
|
||||
tipRandomMin: settings?.tipRandomMin || 1,
|
||||
tipRandomMax: settings?.tipRandomMax || settings?.tipDefault || 21,
|
||||
turboTipping: settings?.turboTipping,
|
||||
zapUndos: settings?.zapUndos || (settings?.tipDefault ? 100 * settings.tipDefault : 2100),
|
||||
zapUndosEnabled: settings?.zapUndos !== null,
|
||||
@ -149,7 +152,7 @@ export default function Settings ({ ssrData }) {
|
||||
noReferralLinks: settings?.noReferralLinks
|
||||
}}
|
||||
schema={settingsSchema}
|
||||
onSubmit={async ({ tipDefault, withdrawMaxFeeDefault, zapUndos, zapUndosEnabled, nostrPubkey, nostrRelays, ...values }) => {
|
||||
onSubmit={async ({ tipDefault, tipRandom, tipRandomMin, tipRandomMax, withdrawMaxFeeDefault, zapUndos, zapUndosEnabled, nostrPubkey, nostrRelays, ...values }) => {
|
||||
if (nostrPubkey.length === 0) {
|
||||
nostrPubkey = null
|
||||
} else {
|
||||
@ -166,6 +169,8 @@ export default function Settings ({ ssrData }) {
|
||||
variables: {
|
||||
settings: {
|
||||
tipDefault: Number(tipDefault),
|
||||
tipRandomMin: tipRandom ? Number(tipRandomMin) : null,
|
||||
tipRandomMax: tipRandom ? Number(tipRandomMax) : null,
|
||||
withdrawMaxFeeDefault: Number(withdrawMaxFeeDefault),
|
||||
zapUndos: zapUndosEnabled ? Number(zapUndos) : null,
|
||||
nostrPubkey,
|
||||
@ -190,7 +195,7 @@ export default function Settings ({ ssrData }) {
|
||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||
hint={<small className='text-muted'>note: you can also press and hold the lightning bolt to zap custom amounts</small>}
|
||||
/>
|
||||
<div className='mb-2'>
|
||||
<div className='pb-4'>
|
||||
<AccordianItem
|
||||
show={settings?.turboTipping}
|
||||
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>advanced</div>}
|
||||
@ -224,6 +229,7 @@ export default function Settings ({ ssrData }) {
|
||||
groupClassName='mb-0'
|
||||
/>
|
||||
<ZapUndosField />
|
||||
<TipRandomField />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
@ -995,31 +1001,79 @@ function ApiKeyDeleteObstacle ({ onClose }) {
|
||||
const ZapUndosField = () => {
|
||||
const [checkboxField] = useField({ name: 'zapUndosEnabled' })
|
||||
return (
|
||||
<div className='d-flex flex-row align-items-center'>
|
||||
<Input
|
||||
name='zapUndos'
|
||||
disabled={!checkboxField.value}
|
||||
<>
|
||||
<Checkbox
|
||||
name='zapUndosEnabled'
|
||||
groupClassName='mb-0'
|
||||
label={
|
||||
<Checkbox
|
||||
name='zapUndosEnabled'
|
||||
groupClassName='mb-0'
|
||||
label={
|
||||
<div className='d-flex align-items-center'>
|
||||
zap undos
|
||||
<Info>
|
||||
<ul className='fw-bold'>
|
||||
<li>After every zap that exceeds or is equal to the threshold, the bolt will pulse</li>
|
||||
<li>You can undo the zap if you click the bolt while it's pulsing</li>
|
||||
<li>The bolt will pulse for {ZAP_UNDO_DELAY_MS / 1000} seconds</li>
|
||||
</ul>
|
||||
</Info>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
}
|
||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||
hint={<small className='text-muted'>threshold at which undo button is shown</small>}
|
||||
<div className='d-flex align-items-center'>
|
||||
zap undos
|
||||
<Info>
|
||||
<ul className='fw-bold'>
|
||||
<li>After every zap that exceeds or is equal to the threshold, the bolt will pulse</li>
|
||||
<li>You can undo the zap if you click the bolt while it's pulsing</li>
|
||||
<li>The bolt will pulse for {ZAP_UNDO_DELAY_MS / 1000} seconds</li>
|
||||
</ul>
|
||||
</Info>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{checkboxField.value &&
|
||||
<Input
|
||||
name='zapUndos'
|
||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||
hint={<small className='text-muted'>threshold at which undo button is shown</small>}
|
||||
/>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const TipRandomField = () => {
|
||||
const [tipRandomField] = useField({ name: 'tipRandom' })
|
||||
const [tipRandomMinField] = useField({ name: 'tipRandomMin' })
|
||||
const [tipRandomMaxField] = useField({ name: 'tipRandomMax' })
|
||||
return (
|
||||
<>
|
||||
<Checkbox
|
||||
name='tipRandom'
|
||||
groupClassName='mb-0'
|
||||
label={
|
||||
<div className='d-flex align-items-center'>
|
||||
Enable random zap values
|
||||
<Info>
|
||||
<ul className='fw-bold'>
|
||||
<li>Set a minimum and maximum zap amount</li>
|
||||
<li>Each time you zap something, a random amount of sats between your minimum and maximum will be zapped</li>
|
||||
<li>If this setting is enabled, it will ignore your default zap amount</li>
|
||||
</ul>
|
||||
</Info>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{tipRandomField.value &&
|
||||
<>
|
||||
<Input
|
||||
type='number'
|
||||
label='minimum random zap'
|
||||
name='tipRandomMin'
|
||||
disabled={!tipRandomField.value}
|
||||
groupClassName='mb-1'
|
||||
required
|
||||
autoFocus
|
||||
max={tipRandomMaxField.value ? tipRandomMaxField.value - 1 : undefined}
|
||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||
/>
|
||||
<Input
|
||||
type='number'
|
||||
label='maximum random zap'
|
||||
name='tipRandomMax'
|
||||
disabled={!tipRandomField.value}
|
||||
required
|
||||
autoFocus
|
||||
min={tipRandomMinField.value ? tipRandomMinField.value + 1 : undefined}
|
||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||
/>
|
||||
</>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "tipRandomMax" INTEGER,
|
||||
ADD COLUMN "tipRandomMin" INTEGER;
|
@ -30,6 +30,8 @@ model User {
|
||||
apiKeyHash String? @unique(map: "users.apikeyhash_unique") @db.Char(64)
|
||||
apiKeyEnabled Boolean @default(false)
|
||||
tipDefault Int @default(100)
|
||||
tipRandomMin Int?
|
||||
tipRandomMax Int?
|
||||
bioId Int?
|
||||
inviteId String?
|
||||
tipPopover Boolean @default(false)
|
||||
|
Loading…
x
Reference in New Issue
Block a user