refine tipping
This commit is contained in:
parent
650ad03de5
commit
e4c1c2f1e1
|
@ -209,7 +209,7 @@ export default {
|
||||||
|
|
||||||
return await updateItem(parent, { id, data: { text } }, { me, models })
|
return await updateItem(parent, { id, data: { text } }, { me, models })
|
||||||
},
|
},
|
||||||
act: async (parent, { id, act, sats }, { me, models }) => {
|
act: async (parent, { id, act, sats, tipDefault }, { me, models }) => {
|
||||||
// need to make sure we are logged in
|
// need to make sure we are logged in
|
||||||
if (!me) {
|
if (!me) {
|
||||||
throw new AuthenticationError('you must be logged in')
|
throw new AuthenticationError('you must be logged in')
|
||||||
|
@ -228,6 +228,10 @@ export default {
|
||||||
if (item) {
|
if (item) {
|
||||||
throw new UserInputError('cannot tip your self')
|
throw new UserInputError('cannot tip your self')
|
||||||
}
|
}
|
||||||
|
// if tipDefault, set on user
|
||||||
|
if (tipDefault) {
|
||||||
|
await models.user.update({ where: { id: me.id }, data: { tipDefault: sats } })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await serialize(models, models.$queryRaw`SELECT item_act(${Number(id)}, ${me.id}, ${act}, ${Number(sats)})`)
|
await serialize(models, models.$queryRaw`SELECT item_act(${Number(id)}, ${me.id}, ${act}, ${Number(sats)})`)
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default gql`
|
||||||
updateDiscussion(id: ID!, title: String!, text: String): Item!
|
updateDiscussion(id: ID!, title: String!, text: String): Item!
|
||||||
createComment(text: String!, parentId: ID!): Item!
|
createComment(text: String!, parentId: ID!): Item!
|
||||||
updateComment(id: ID!, text: String!): Item!
|
updateComment(id: ID!, text: String!): Item!
|
||||||
act(id: ID!, act: ItemAct!, sats: Int): ItemActResult!
|
act(id: ID!, act: ItemAct!, sats: Int, tipDefault: Boolean): ItemActResult!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Items {
|
type Items {
|
||||||
|
|
|
@ -21,6 +21,7 @@ export default gql`
|
||||||
freePosts: Int!
|
freePosts: Int!
|
||||||
freeComments: Int!
|
freeComments: Int!
|
||||||
hasNewNotes: Boolean!
|
hasNewNotes: Boolean!
|
||||||
|
tipDefault: Int!
|
||||||
sats: Int!
|
sats: Int!
|
||||||
msats: Int!
|
msats: Int!
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import { useFormikContext } from 'formik'
|
import { useFormikContext } from 'formik'
|
||||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||||
|
|
||||||
export default function ActionTooltip ({ children, notForm, overlayText }) {
|
export default function ActionTooltip ({ children, notForm, disable, overlayText }) {
|
||||||
// if we're in a form, we want to hide tooltip on submit
|
// if we're in a form, we want to hide tooltip on submit
|
||||||
let formik
|
let formik
|
||||||
if (!notForm) {
|
if (!notForm) {
|
||||||
formik = useFormikContext()
|
formik = useFormikContext()
|
||||||
}
|
}
|
||||||
|
if (disable) {
|
||||||
|
return children
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
placement='bottom'
|
placement='bottom'
|
||||||
|
|
|
@ -15,12 +15,12 @@ export const AdvPostInitial = {
|
||||||
export default function AdvPostForm () {
|
export default function AdvPostForm () {
|
||||||
return (
|
return (
|
||||||
<AccordianItem
|
<AccordianItem
|
||||||
header={<div className='font-weight-bold'>advanced</div>}
|
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>options</div>}
|
||||||
body={
|
body={
|
||||||
<Input
|
<Input
|
||||||
label='boost'
|
label='boost'
|
||||||
name='boost'
|
name='boost'
|
||||||
hint={<span className='text-muted'>boost ranks posts higher temporarily depending on the amount</span>}
|
hint={<span className='text-muted'>boost ranks posts higher temporarily based on the amount</span>}
|
||||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,11 +82,9 @@ export function DiscussionForm ({ item, editThreshold }) {
|
||||||
: null}
|
: null}
|
||||||
/>
|
/>
|
||||||
{!item && <AdvPostForm />}
|
{!item && <AdvPostForm />}
|
||||||
<div className='d-flex'>
|
<ActionTooltip>
|
||||||
<ActionTooltip>
|
<SubmitButton variant='secondary' className='mt-3'>{item ? 'save' : 'post'}</SubmitButton>
|
||||||
<SubmitButton variant='secondary' className='mt-2 ml-auto'>{item ? 'save' : 'post'}</SubmitButton>
|
</ActionTooltip>
|
||||||
</ActionTooltip>
|
|
||||||
</div>
|
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,6 +175,36 @@ export function Input ({ label, groupClassName, ...props }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Checkbox ({ children, label, extra, handleChange, ...props }) {
|
||||||
|
// React treats radios and checkbox inputs differently other input types, select, and textarea.
|
||||||
|
// Formik does this too! When you specify `type` to useField(), it will
|
||||||
|
// return the correct bag of props for you
|
||||||
|
const [field, { value }] = useField({ ...props, type: 'checkbox' })
|
||||||
|
return (
|
||||||
|
<div className={value ? styles.checkboxChecked : styles.checkboxUnchecked}>
|
||||||
|
<BootstrapForm.Check
|
||||||
|
custom
|
||||||
|
id={props.id || props.name}
|
||||||
|
>
|
||||||
|
<BootstrapForm.Check.Input
|
||||||
|
{...field} {...props} type='checkbox' onChange={(e) => {
|
||||||
|
field.onChange(e)
|
||||||
|
handleChange && handleChange(e.target.checked)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<BootstrapForm.Check.Label className='d-flex'>
|
||||||
|
<div className='flex-grow-1'>{label}</div>
|
||||||
|
{extra &&
|
||||||
|
<div className={styles.checkboxExtra}>
|
||||||
|
{extra}
|
||||||
|
</div>}
|
||||||
|
</BootstrapForm.Check.Label>
|
||||||
|
</BootstrapForm.Check>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function Form ({
|
export function Form ({
|
||||||
initial, schema, onSubmit, children, initialError, validateImmediately, ...props
|
initial, schema, onSubmit, children, initialError, validateImmediately, ...props
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -9,4 +9,4 @@
|
||||||
.markdownInput .text {
|
.markdownInput .text {
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { InputGroup, Modal } from 'react-bootstrap'
|
import { InputGroup, Modal } from 'react-bootstrap'
|
||||||
import React, { useState, useCallback, useContext, useRef, useEffect } from 'react'
|
import React, { useState, useCallback, useContext, useRef, useEffect } from 'react'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { Form, Input, SubmitButton } from './form'
|
import { Checkbox, Form, Input, SubmitButton } from './form'
|
||||||
|
import { useMe } from './me'
|
||||||
|
|
||||||
export const ItemActContext = React.createContext({
|
export const ItemActContext = React.createContext({
|
||||||
item: null,
|
item: null,
|
||||||
|
@ -36,6 +37,7 @@ export const ActSchema = Yup.object({
|
||||||
export function ItemActModal () {
|
export function ItemActModal () {
|
||||||
const { item, setItem } = useItemAct()
|
const { item, setItem } = useItemAct()
|
||||||
const inputRef = useRef(null)
|
const inputRef = useRef(null)
|
||||||
|
const me = useMe()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
|
@ -51,11 +53,19 @@ export function ItemActModal () {
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Form
|
<Form
|
||||||
initial={{
|
initial={{
|
||||||
amount: 21
|
amount: me?.tipDefault || 21,
|
||||||
|
default: false
|
||||||
}}
|
}}
|
||||||
schema={ActSchema}
|
schema={ActSchema}
|
||||||
onSubmit={async ({ amount, submit }) => {
|
onSubmit={async ({ amount, tipDefault, submit }) => {
|
||||||
await item.act({ variables: { id: item.itemId, act: submit, sats: Number(amount) } })
|
await item.act({
|
||||||
|
variables: {
|
||||||
|
id: item.itemId,
|
||||||
|
act: submit,
|
||||||
|
sats: Number(amount),
|
||||||
|
tipDefault
|
||||||
|
}
|
||||||
|
})
|
||||||
await item.strike()
|
await item.strike()
|
||||||
setItem(null)
|
setItem(null)
|
||||||
}}
|
}}
|
||||||
|
@ -68,6 +78,12 @@ export function ItemActModal () {
|
||||||
autoFocus
|
autoFocus
|
||||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||||
/>
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='set as default'
|
||||||
|
name='tipDefault'
|
||||||
|
required
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
<div className='d-flex'>
|
<div className='d-flex'>
|
||||||
<SubmitButton variant='success' className='ml-auto mt-1 px-4' value='TIP'>tip</SubmitButton>
|
<SubmitButton variant='success' className='ml-auto mt-1 px-4' value='TIP'>tip</SubmitButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -109,11 +109,9 @@ export function LinkForm ({ item, editThreshold }) {
|
||||||
/>
|
/>
|
||||||
{!item && <AdvPostForm />}
|
{!item && <AdvPostForm />}
|
||||||
|
|
||||||
<div className='d-flex'>
|
<ActionTooltip>
|
||||||
<ActionTooltip>
|
<SubmitButton variant='secondary' className='mt-3'>{item ? 'save' : 'post'}</SubmitButton>
|
||||||
<SubmitButton variant='secondary' className='mt-2 ml-auto'>{item ? 'save' : 'post'}</SubmitButton>
|
</ActionTooltip>
|
||||||
</ActionTooltip>
|
|
||||||
</div>
|
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ export function MeProvider ({ children }) {
|
||||||
freePosts
|
freePosts
|
||||||
freeComments
|
freeComments
|
||||||
hasNewNotes
|
hasNewNotes
|
||||||
|
tipDefault
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
const { data } = useQuery(query, { pollInterval: 1000 })
|
const { data } = useQuery(query, { pollInterval: 1000 })
|
||||||
|
|
|
@ -7,15 +7,17 @@ import { useFundError } from './fund-error'
|
||||||
import ActionTooltip from './action-tooltip'
|
import ActionTooltip from './action-tooltip'
|
||||||
import { useItemAct } from './item-act'
|
import { useItemAct } from './item-act'
|
||||||
import Window from '../svgs/window-2-fill.svg'
|
import Window from '../svgs/window-2-fill.svg'
|
||||||
|
import { useMe } from './me'
|
||||||
|
|
||||||
export default function UpVote ({ item, className }) {
|
export default function UpVote ({ item, className }) {
|
||||||
const [session] = useSession()
|
const [session] = useSession()
|
||||||
const { setError } = useFundError()
|
const { setError } = useFundError()
|
||||||
const { setItem } = useItemAct()
|
const { setItem } = useItemAct()
|
||||||
|
const me = useMe()
|
||||||
const [act] = useMutation(
|
const [act] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
mutation act($id: ID!, $act: ItemAct! $sats: Int!) {
|
mutation act($id: ID!, $act: ItemAct! $sats: Int!, $tipDefault: Boolean) {
|
||||||
act(id: $id, act: $act, sats: $sats) {
|
act(id: $id, act: $act, sats: $sats, tipDefault: $tipDefault) {
|
||||||
act,
|
act,
|
||||||
sats
|
sats
|
||||||
}
|
}
|
||||||
|
@ -68,29 +70,58 @@ export default function UpVote ({ item, className }) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const overlayText = () => {
|
||||||
|
if (item?.meVote) {
|
||||||
|
if (me?.tipDefault) {
|
||||||
|
return `tip ${me.tipDefault}`
|
||||||
|
}
|
||||||
|
return <Window style={{ fill: '#fff' }} width={18} height={18} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return '1 sat'
|
||||||
|
}
|
||||||
|
|
||||||
|
const noSelfTips = item?.meVote && item?.user?.id === me?.id
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LightningConsumer>
|
<LightningConsumer>
|
||||||
{({ strike }) =>
|
{({ strike }) =>
|
||||||
<ActionTooltip notForm overlayText={item?.meVote ? <Window style={{ fill: '#fff' }} /> : '1 sat'}>
|
<ActionTooltip notForm disable={noSelfTips} overlayText={overlayText()}>
|
||||||
<UpArrow
|
<UpArrow
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className={
|
className={
|
||||||
`${styles.upvote}
|
`${styles.upvote}
|
||||||
${className || ''}
|
${className || ''}
|
||||||
|
${noSelfTips ? styles.noSelfTips : ''}
|
||||||
${item?.meVote ? styles.voted : ''}`
|
${item?.meVote ? styles.voted : ''}`
|
||||||
}
|
}
|
||||||
onClick={
|
onClick={
|
||||||
session
|
session
|
||||||
? async (e) => {
|
? async (e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
if (!item) return
|
||||||
|
|
||||||
|
// we can't tip ourselves
|
||||||
|
if (noSelfTips) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (item?.meVote) {
|
if (item?.meVote) {
|
||||||
|
if (me?.tipDefault) {
|
||||||
|
try {
|
||||||
|
await act({ variables: { id: item.id, act: 'TIP', sats: me.tipDefault } })
|
||||||
|
strike()
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
setItem({ itemId: item.id, act, strike })
|
setItem({ itemId: item.id, act, strike })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strike()
|
strike()
|
||||||
if (!item) return
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await act({ variables: { id: item.id, act: 'VOTE', sats: 1 } })
|
await act({ variables: { id: item.id, act: 'VOTE', sats: 1 } })
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.noSelfTips:hover {
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|
||||||
.upvote.voted {
|
.upvote.voted {
|
||||||
fill: #F6911D;
|
fill: #F6911D;
|
||||||
filter: drop-shadow(0 0 7px #F6911D);
|
filter: drop-shadow(0 0 7px #F6911D);
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ItemAct" ALTER COLUMN "sats" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "tipDefault" INTEGER NOT NULL DEFAULT 0;
|
|
@ -28,6 +28,7 @@ model User {
|
||||||
freeComments Int @default(5)
|
freeComments Int @default(5)
|
||||||
freePosts Int @default(2)
|
freePosts Int @default(2)
|
||||||
checkedNotesAt DateTime?
|
checkedNotesAt DateTime?
|
||||||
|
tipDefault Int @default(0)
|
||||||
pubkey String? @unique
|
pubkey String? @unique
|
||||||
|
|
||||||
@@map(name: "users")
|
@@map(name: "users")
|
||||||
|
|
Loading…
Reference in New Issue