Skip to content

Commit

Permalink
random zap amounts (#1263)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
SatsAllDay and huumn authored Jul 27, 2024
1 parent 4964e2c commit dc0370b
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 37 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 6 additions & 0 deletions api/resolvers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
},

Expand Down
5 changes: 5 additions & 0 deletions api/typeDefs/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export default gql`
noteItemMentions: Boolean!
nsfwMode: Boolean!
tipDefault: Int!
tipRandomMin: Int
tipRandomMax: Int
turboTipping: Boolean!
zapUndos: Int
wildWestMode: Boolean!
Expand Down Expand Up @@ -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!
Expand Down
4 changes: 2 additions & 2 deletions components/item-act.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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}
Expand Down
27 changes: 21 additions & 6 deletions components/upvote.js
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -67,11 +67,20 @@ export function DropdownItemUpVote ({ item }) {
)
}

export const nextTip = (meSats, { tipDefault, turboTipping }) => {
export const defaultTipIncludingRandom = ({ tipDefault, tipRandom, tipRandomMin, tipRandomMax }) =>
tipRandom
? Math.floor((Math.random() * (tipRandomMax - tipRandomMin)) + tipRandomMin)
: (tipDefault || 1)

export const nextTip = (meSats, { tipDefault, turboTipping, tipRandom, tipRandomMin, tipRandomMax }) => {
// what should our next tip be?
if (!turboTipping) return (tipDefault || 1)
const calculatedDefault = defaultTipIncludingRandom({ tipDefault, tipRandom, tipRandomMin, tipRandomMax })

if (!turboTipping) {
return calculatedDefault
}

let sats = tipDefault || 1
let sats = calculatedDefault
if (turboTipping) {
while (meSats >= sats) {
sats *= 10
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions fragments/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export const ME = gql`
noteItemMentions
sats
tipDefault
tipRandom
tipRandomMin
tipRandomMax
tipPopover
turboTipping
zapUndos
Expand All @@ -66,6 +69,9 @@ export const SETTINGS_FIELDS = gql`
fragment SettingsFields on User {
privates {
tipDefault
tipRandom
tipRandomMin
tipRandomMax
turboTipping
zapUndos
fiatCurrency
Expand Down
21 changes: 19 additions & 2 deletions lib/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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({
Expand Down
104 changes: 79 additions & 25 deletions pages/settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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>}
Expand Down Expand Up @@ -224,6 +229,7 @@ export default function Settings ({ ssrData }) {
groupClassName='mb-0'
/>
<ZapUndosField />
<TipRandomField />
</>
}
/>
Expand Down Expand Up @@ -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>
}
/>
<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>
}
/>
{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>
}
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
hint={<small className='text-muted'>threshold at which undo button is shown</small>}
/>
</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>}
/>
</>}
</>
)
}
3 changes: 3 additions & 0 deletions prisma/migrations/20240710143024_zap_random/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "tipRandomMax" INTEGER,
ADD COLUMN "tipRandomMin" INTEGER;
2 changes: 2 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit dc0370b

Please sign in to comment.