Force post options open when dirty or on errors (#1129)

* wip submit will open options

* fix: options show on error discussions

* lint

* feat: all types check for dirty or errors

* lint

* fix ordering

* dirty and error useEffects

* use formik context

* update dirty checks on forms

* revert dirty logic

* simplify handle error functions

* lint

* add myself to contributors and update awards

* use Formik context in adv-post-form

* move all logic into accordian item

* move logic up to adv-post-form

* lint

* errors open options every time

* refine dirty form accordians

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
This commit is contained in:
Ben Allen 2024-05-12 17:17:41 -04:00 committed by GitHub
parent 77c87dae80
commit 57fbab31b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 80 additions and 16 deletions

View File

@ -79,5 +79,6 @@ felipebueno,pr,#1094,,,,2,,80k,felipebueno@getalby.com,2024-05-06
benalleng,helpfulness,#1127,#927,good-first-issue,,,,2k,benalleng@mutiny.plus,2024-05-04
itsrealfake,pr,#1135,#1016,good-first-issue,,,nonideal solution,10k,itsrealfake2@stacker.news,2024-05-06
SatsAllDay,issue,#1135,#1016,good-first-issue,,,,1k,weareallsatoshi@getalby.com,2024-05-04
s373nZ,issue,#1136,#1107,medium,high,,,50k,???,???
SatsAllDay,pr,#1145,#717,medium,,,,250k,weareallsatoshi@zeuspay.com,???
s373nZ,issue,#1136,#1107,medium,high,,,50k,se7enz@minibits.cash,2024-05-05
benalleng,pr,#1129,#1045,good-first-issue,,,paid for advice out of band,20k,benalleng@mutiny.plus,???
benalleng,pr,#1129,#491,good-first-issue,,,,20k,benalleng@mutiny.plus,???

1 name type pr id issue ids difficulty priority changes requested notes amount receive method date paid
79 benalleng helpfulness #1127 #927 good-first-issue 2k benalleng@mutiny.plus 2024-05-04
80 itsrealfake pr #1135 #1016 good-first-issue nonideal solution 10k itsrealfake2@stacker.news 2024-05-06
81 SatsAllDay issue #1135 #1016 good-first-issue 1k weareallsatoshi@getalby.com 2024-05-04
82 s373nZ issue #1136 #1107 medium high 50k ??? se7enz@minibits.cash ??? 2024-05-05
83 SatsAllDay benalleng pr #1145 #1129 #717 #1045 medium good-first-issue paid for advice out of band 250k 20k weareallsatoshi@zeuspay.com benalleng@mutiny.plus ???
84 benalleng pr #1129 #491 good-first-issue 20k benalleng@mutiny.plus ???

View File

@ -3,12 +3,19 @@ import AccordionContext from 'react-bootstrap/AccordionContext'
import { useAccordionButton } from 'react-bootstrap/AccordionButton'
import ArrowRight from '@/svgs/arrow-right-s-fill.svg'
import ArrowDown from '@/svgs/arrow-down-s-fill.svg'
import { useContext } from 'react'
import { useContext, useEffect } from 'react'
function ContextAwareToggle ({ children, headerColor = 'var(--theme-grey)', eventKey }) {
function ContextAwareToggle ({ children, headerColor = 'var(--theme-grey)', eventKey, show }) {
const { activeEventKey } = useContext(AccordionContext)
const decoratedOnClick = useAccordionButton(eventKey)
useEffect(() => {
// if we want to show the accordian and it's not open, open it
if (show && activeEventKey !== eventKey) {
decoratedOnClick()
}
}, [show])
const isCurrentEventKey = activeEventKey === eventKey
return (
@ -24,7 +31,7 @@ function ContextAwareToggle ({ children, headerColor = 'var(--theme-grey)', even
export default function AccordianItem ({ header, body, headerColor = 'var(--theme-grey)', show }) {
return (
<Accordion defaultActiveKey={show ? '0' : undefined}>
<ContextAwareToggle eventKey='0'><div style={{ color: headerColor }}>{header}</div></ContextAwareToggle>
<ContextAwareToggle show={show} eventKey='0'><div style={{ color: headerColor }}>{header}</div></ContextAwareToggle>
<Accordion.Collapse eventKey='0' className='mt-2'>
<div>{body}</div>
</Accordion.Collapse>

View File

@ -10,6 +10,7 @@ import styles from './adv-post-form.module.css'
import { useMe } from './me'
import { useFeeButton } from './fee-button'
import { useRouter } from 'next/router'
import { useFormikContext } from 'formik'
const EMPTY_FORWARD = { nym: '', pct: '' }
@ -20,11 +21,43 @@ export function AdvPostInitial ({ forward, boost }) {
}
}
export default function AdvPostForm ({ children, item }) {
export default function AdvPostForm ({ children, item, storageKeyPrefix }) {
const me = useMe()
const { merge } = useFeeButton()
const router = useRouter()
const [itemType, setItemType] = useState()
const formik = useFormikContext()
const [show, setShow] = useState(undefined)
useEffect(() => {
const isDirty = formik?.values.forward?.[0].nym !== '' || formik?.values.forward?.[0].pct !== '' ||
formik?.values.boost !== '' || (router.query?.type === 'link' && formik?.values.text !== '')
// if the adv post form is dirty on first render, show the accordian
if (isDirty) {
setShow(true)
}
// HACK ... TODO: we should generically handle this kind of local storage stuff
// in the form component, overriding the initial values
if (storageKeyPrefix) {
for (let i = 0; i < MAX_FORWARDS; i++) {
['nym', 'pct'].forEach(key => {
const value = window.localStorage.getItem(`${storageKeyPrefix}-forward[${i}].${key}`)
if (value) {
formik?.setFieldValue(`forward[${i}].${key}`, value)
}
})
}
}
}, [formik?.values, storageKeyPrefix])
useEffect(() => {
// force show the accordian if there is an error and the form is submitting
const hasError = !!formik?.errors?.boost || formik?.errors?.forward?.length > 0
// if it's open we don't want to collapse on submit
setShow(show => show || (hasError && formik?.isSubmitting))
}, [formik?.isSubmitting])
useEffect(() => {
const determineItemType = () => {
@ -69,6 +102,7 @@ export default function AdvPostForm ({ children, item }) {
return (
<AccordianItem
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>options</div>}
show={show}
body={
<>
{children}

View File

@ -93,6 +93,8 @@ export function BountyForm ({
}, [upsertBounty, router]
)
const storageKeyPrefix = item ? undefined : 'bounty'
return (
<Form
initial={{
@ -109,7 +111,7 @@ export function BountyForm ({
handleSubmit ||
onSubmit
}
storageKeyPrefix={item ? undefined : 'bounty'}
storageKeyPrefix={storageKeyPrefix}
>
{children}
<Input
@ -143,7 +145,7 @@ export function BountyForm ({
: null
}
/>
<AdvPostForm edit={!!item} item={item} />
<AdvPostForm storageKeyPrefix={storageKeyPrefix} item={item} />
<ItemButtonBar itemId={item?.id} canDelete={false} />
</Form>
)

View File

@ -84,7 +84,7 @@ export function DiscussionForm ({
}`)
const related = relatedData?.related?.items || []
const storageKeyPrefix = item ? undefined : 'discussion'
return (
<Form
initial={{
@ -97,7 +97,7 @@ export function DiscussionForm ({
schema={schema}
invoiceable
onSubmit={handleSubmit || onSubmit}
storageKeyPrefix={item ? undefined : 'discussion'}
storageKeyPrefix={storageKeyPrefix}
>
{children}
<Input
@ -124,7 +124,7 @@ export function DiscussionForm ({
? <div className='text-muted fw-bold'><Countdown date={editThreshold} /></div>
: null}
/>
<AdvPostForm edit={!!item} item={item} />
<AdvPostForm storageKeyPrefix={storageKeyPrefix} item={item} />
<ItemButtonBar itemId={item?.id} />
{!item &&
<div className={`mt-3 ${related.length > 0 ? '' : 'invisible'}`}>

View File

@ -19,6 +19,7 @@ import { MAX_TITLE_LENGTH, MEDIA_URL } from '@/lib/constants'
import { useToast } from './toast'
import { toastDeleteScheduled } from '@/lib/form'
import { ItemButtonBar } from './post'
import { useFormikContext } from 'formik'
function satsMin2Mo (minute) {
return minute * 30 * 24 * 60
@ -166,6 +167,8 @@ export default function JobForm ({ item, sub }) {
}
function PromoteJob ({ item, sub }) {
const formik = useFormikContext()
const [show, setShow] = useState(undefined)
const [monthly, setMonthly] = useState(satsMin2Mo(item?.maxBid || 0))
const [getAuctionPosition, { data }] = useLazyQuery(gql`
query AuctionPosition($id: ID, $bid: Int!) {
@ -180,9 +183,21 @@ function PromoteJob ({ item, sub }) {
setMonthly(satsMin2Mo(initialMaxBid))
}, [])
useEffect(() => {
if (formik?.values?.maxBid !== 0) {
setShow(true)
}
}, [formik?.values])
useEffect(() => {
const hasMaxBidError = !!formik?.errors?.maxBid
// if it's open we don't want to collapse on submit
setShow(show => show || (hasMaxBidError && formik?.isSubmitting))
}, [formik?.isSubmitting])
return (
<AccordianItem
show={item?.maxBid > 0}
show={show}
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>promote</div>}
body={
<>

View File

@ -129,6 +129,8 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
const [postDisabled, setPostDisabled] = useState(false)
const [titleOverride, setTitleOverride] = useState()
const storageKeyPrefix = item ? undefined : 'link'
return (
<Form
initial={{
@ -142,7 +144,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
schema={schema}
invoiceable
onSubmit={onSubmit}
storageKeyPrefix={item ? undefined : 'link'}
storageKeyPrefix={storageKeyPrefix}
>
{children}
<Input
@ -196,7 +198,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
}
}}
/>
<AdvPostForm edit={!!item} item={item}>
<AdvPostForm storageKeyPrefix={storageKeyPrefix} item={item}>
<MarkdownInput
label='context'
name='text'

View File

@ -71,6 +71,8 @@ export function PollForm ({ item, sub, editThreshold, children }) {
const initialOptions = item?.poll?.options.map(i => i.option)
const storageKeyPrefix = item ? undefined : 'poll'
return (
<Form
initial={{
@ -85,7 +87,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
schema={schema}
invoiceable
onSubmit={onSubmit}
storageKeyPrefix={item ? undefined : 'poll'}
storageKeyPrefix={storageKeyPrefix}
>
{children}
<Input
@ -111,7 +113,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
: null}
maxLength={MAX_POLL_CHOICE_LENGTH}
/>
<AdvPostForm edit={!!item} item={item}>
<AdvPostForm storageKeyPrefix={storageKeyPrefix} item={item}>
<DateTimeInput
isClearable
label='poll expiration'

View File

@ -9,3 +9,4 @@ stargut
mz
btcbagehot
felipe
benalleng