WIP tips
This commit is contained in:
parent
3f8b5894cb
commit
0a20f2ea23
@ -11,13 +11,19 @@ import Markdown from '../svgs/markdown-line.svg'
|
|||||||
import styles from './form.module.css'
|
import styles from './form.module.css'
|
||||||
import Text from '../components/text'
|
import Text from '../components/text'
|
||||||
|
|
||||||
export function SubmitButton ({ children, variant, ...props }) {
|
export function SubmitButton ({ children, variant, value, onClick, ...props }) {
|
||||||
const { isSubmitting } = useFormikContext()
|
const { isSubmitting, setFieldValue } = useFormikContext()
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant={variant || 'main'}
|
variant={variant || 'main'}
|
||||||
type='submit'
|
type='submit'
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
|
onClick={value
|
||||||
|
? e => {
|
||||||
|
setFieldValue('submit', value)
|
||||||
|
onClick && onClick(e)
|
||||||
|
}
|
||||||
|
: onClick}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@ -108,7 +114,7 @@ function FormGroup ({ className, label, children }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputInner ({ prepend, append, hint, showValid, onChange, overrideValue, ...props }) {
|
function InputInner ({ prepend, append, hint, showValid, onChange, overrideValue, innerRef, ...props }) {
|
||||||
const [field, meta, helpers] = props.readOnly ? [{}, {}, {}] : useField(props)
|
const [field, meta, helpers] = props.readOnly ? [{}, {}, {}] : useField(props)
|
||||||
const formik = props.readOnly ? null : useFormikContext()
|
const formik = props.readOnly ? null : useFormikContext()
|
||||||
|
|
||||||
@ -132,6 +138,7 @@ function InputInner ({ prepend, append, hint, showValid, onChange, overrideValue
|
|||||||
formik?.submitForm()
|
formik?.submitForm()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
ref={innerRef}
|
||||||
{...field} {...props}
|
{...field} {...props}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
field.onChange(e)
|
field.onChange(e)
|
||||||
|
102
components/item-act.js
Normal file
102
components/item-act.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { Accordion, InputGroup, Modal } from 'react-bootstrap'
|
||||||
|
import React, { useState, useCallback, useContext, useRef, useEffect } from 'react'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { Form, Input, SubmitButton } from './form'
|
||||||
|
import ArrowRight from '../svgs/arrow-right-s-fill.svg'
|
||||||
|
import ArrowDown from '../svgs/arrow-down-s-fill.svg'
|
||||||
|
|
||||||
|
export const ItemActContext = React.createContext({
|
||||||
|
item: null,
|
||||||
|
setItem: () => {}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function ItemActProvider ({ children }) {
|
||||||
|
const [item, setItem] = useState(null)
|
||||||
|
|
||||||
|
const contextValue = {
|
||||||
|
item,
|
||||||
|
setItem: useCallback(i => setItem(i), [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemActContext.Provider value={contextValue}>
|
||||||
|
{children}
|
||||||
|
</ItemActContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useItemAct () {
|
||||||
|
const { item, setItem } = useContext(ItemActContext)
|
||||||
|
return { item, setItem }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ActSchema = Yup.object({
|
||||||
|
amount: Yup.number().typeError('must be a number').required('required')
|
||||||
|
.positive('must be positive').integer('must be whole')
|
||||||
|
})
|
||||||
|
|
||||||
|
export function ItemActModal () {
|
||||||
|
const { item, setItem } = useItemAct()
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const inputRef = useRef(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
inputRef.current?.focus()
|
||||||
|
}, [item])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
show={!!item}
|
||||||
|
onHide={() => {
|
||||||
|
setItem(null)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Modal.Body>
|
||||||
|
<Form
|
||||||
|
initial={{
|
||||||
|
amount: 21
|
||||||
|
}}
|
||||||
|
schema={ActSchema}
|
||||||
|
onSubmit={async ({ amount, submit }) => {
|
||||||
|
await item.act({ variables: { id: item.itemId, act: submit, sats: Number(amount) } })
|
||||||
|
await item.strike()
|
||||||
|
setOpen(false)
|
||||||
|
setItem(null)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
label='amount'
|
||||||
|
name='amount'
|
||||||
|
innerRef={inputRef}
|
||||||
|
required
|
||||||
|
autoFocus
|
||||||
|
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||||
|
/>
|
||||||
|
<div className='d-flex justify-content-between'>
|
||||||
|
<SubmitButton variant='boost' className='mt-1' value='BOOST'>boost</SubmitButton>
|
||||||
|
<SubmitButton variant='success' className='mt-1 px-4' value='TIP'>tip</SubmitButton>
|
||||||
|
</div>
|
||||||
|
<Accordion className='pt-3'>
|
||||||
|
<Accordion.Toggle
|
||||||
|
as={props => <div {...props} />}
|
||||||
|
eventKey='0'
|
||||||
|
style={{ cursor: 'pointer', display: 'flex', justifyContent: 'center', alignItems: 'center' }}
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
>
|
||||||
|
{open
|
||||||
|
? <ArrowDown className='fill-grey' height={16} width={16} />
|
||||||
|
: <ArrowRight className='fill-grey' height={16} width={16} />}
|
||||||
|
<small className='text-muted text-underline'>I'm confused</small>
|
||||||
|
</Accordion.Toggle>
|
||||||
|
<Accordion.Collapse eventKey='0' className='mt-2'>
|
||||||
|
<span>Tips go directly to the poster or commenter. Boosts boost the rank
|
||||||
|
of the post or comment for a limited time, and the sats go to the site.
|
||||||
|
</span>
|
||||||
|
</Accordion.Collapse>
|
||||||
|
</Accordion>
|
||||||
|
</Form>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
@ -39,8 +39,11 @@ export default function Item ({ item, rank, children }) {
|
|||||||
<div className={`${styles.other}`}>
|
<div className={`${styles.other}`}>
|
||||||
<span>{item.sats} sats</span>
|
<span>{item.sats} sats</span>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
<span>{item.boost} boost</span>
|
{item.boost > 0 &&
|
||||||
<span> \ </span>
|
<>
|
||||||
|
<span>{item.boost} boost</span>
|
||||||
|
<span> \ </span>
|
||||||
|
</>}
|
||||||
<Link href={`/items/${item.id}`} passHref>
|
<Link href={`/items/${item.id}`} passHref>
|
||||||
<a className='text-reset'>{item.ncomments} comments</a>
|
<a className='text-reset'>{item.ncomments} comments</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -5,14 +5,16 @@ import { gql, useMutation } from '@apollo/client'
|
|||||||
import { signIn, useSession } from 'next-auth/client'
|
import { signIn, useSession } from 'next-auth/client'
|
||||||
import { useFundError } from './fund-error'
|
import { useFundError } from './fund-error'
|
||||||
import ActionTooltip from './action-tooltip'
|
import ActionTooltip from './action-tooltip'
|
||||||
|
import { useItemAct } from './item-act'
|
||||||
|
|
||||||
export default function UpVote ({ itemId, meSats, className }) {
|
export default function UpVote ({ itemId, meSats, className }) {
|
||||||
const [session] = useSession()
|
const [session] = useSession()
|
||||||
const { setError } = useFundError()
|
const { setError } = useFundError()
|
||||||
|
const { setItem } = useItemAct()
|
||||||
const [act] = useMutation(
|
const [act] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
mutation act($id: ID!, $sats: Int!) {
|
mutation act($id: ID!, $act: ItemAct! $sats: Int!) {
|
||||||
act(id: $id, act: VOTE, sats: $sats)
|
act(id: $id, act: $act, sats: $sats)
|
||||||
}`, {
|
}`, {
|
||||||
update (cache, { data: { act } }) {
|
update (cache, { data: { act } }) {
|
||||||
// read in the cached object so we don't use meSats prop
|
// read in the cached object so we don't use meSats prop
|
||||||
@ -59,10 +61,16 @@ export default function UpVote ({ itemId, meSats, className }) {
|
|||||||
session
|
session
|
||||||
? async (e) => {
|
? async (e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
if (meSats >= 1) {
|
||||||
|
setItem({ itemId, act, strike })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
strike()
|
strike()
|
||||||
if (!itemId) return
|
if (!itemId) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await act({ variables: { id: itemId, sats: 1 } })
|
await act({ variables: { id: itemId, act: 'VOTE', sats: 1 } })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.toString().includes('insufficient funds')) {
|
if (error.toString().includes('insufficient funds')) {
|
||||||
setError(true)
|
setError(true)
|
||||||
|
@ -8,6 +8,7 @@ import { LightningProvider } from '../components/lightning'
|
|||||||
import apolloClient from '../lib/apollo'
|
import apolloClient from '../lib/apollo'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
import { ItemActModal, ItemActProvider } from '../components/item-act'
|
||||||
|
|
||||||
function MyApp ({ Component, pageProps }) {
|
function MyApp ({ Component, pageProps }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -30,7 +31,10 @@ function MyApp ({ Component, pageProps }) {
|
|||||||
<LightningProvider>
|
<LightningProvider>
|
||||||
<FundErrorProvider>
|
<FundErrorProvider>
|
||||||
<FundErrorModal />
|
<FundErrorModal />
|
||||||
<Component {...pageProps} />
|
<ItemActProvider>
|
||||||
|
<ItemActModal />
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</ItemActProvider>
|
||||||
</FundErrorProvider>
|
</FundErrorProvider>
|
||||||
</LightningProvider>
|
</LightningProvider>
|
||||||
</MeProvider>
|
</MeProvider>
|
||||||
|
@ -5,7 +5,7 @@ $theme-colors: (
|
|||||||
"info" : #007cbe,
|
"info" : #007cbe,
|
||||||
"success" : #5c8001,
|
"success" : #5c8001,
|
||||||
"twitter" : #1da1f2,
|
"twitter" : #1da1f2,
|
||||||
"boost" : #7A0CE9,
|
"boost" : #8c25f4
|
||||||
);
|
);
|
||||||
|
|
||||||
$body-bg: #f5f5f5;
|
$body-bg: #f5f5f5;
|
||||||
@ -171,6 +171,10 @@ footer {
|
|||||||
fill: #c03221;
|
fill: #c03221;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes flash {
|
@keyframes flash {
|
||||||
from { filter: brightness(1);}
|
from { filter: brightness(1);}
|
||||||
2% { filter: brightness(2.3); }
|
2% { filter: brightness(2.3); }
|
||||||
|
1
svgs/arrow-down-s-fill.svg
Normal file
1
svgs/arrow-down-s-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 16l-6-6h12z"/></svg>
|
After Width: | Height: | Size: 153 B |
1
svgs/arrow-right-s-fill.svg
Normal file
1
svgs/arrow-right-s-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16 12l-6 6V6z"/></svg>
|
After Width: | Height: | Size: 152 B |
Loading…
x
Reference in New Issue
Block a user