stacker.news/components/territory-form.js
mzivil 3328c1daa3 fix clicks on post types label not toggling correct checkbox
Currently, all the post types checkbox are being assigned the
same "postTypes" id, so clicking on any of the post type labels
always toggles the first one. If you inspect the HTML, all the
post type labels have 'for="postTypes"' which is how the HTML
knows which checkbox to toggle.

We have to keep the "name" attribute the same because that's how
the checkbox values are linked to the postTypes field.

To fix, we explicitly assign the id prop for each checkbox so
that the <label>'s "for" attribute is tied to the correct
checkbox input.
2024-02-03 16:59:37 -05:00

217 lines
7.0 KiB
JavaScript

import { Col, InputGroup, Row, Form as BootstrapForm, Badge } from 'react-bootstrap'
import { Checkbox, CheckboxGroup, Form, Input, MarkdownInput } from './form'
import FeeButton, { FeeButtonProvider } from './fee-button'
import { gql, useApolloClient, useMutation } from '@apollo/client'
import { useCallback, useState } from 'react'
import { useRouter } from 'next/router'
import { MAX_TERRITORY_DESC_LENGTH, POST_TYPES, TERRITORY_BILLING_OPTIONS } from '../lib/constants'
import { territorySchema } from '../lib/validate'
import { useMe } from './me'
import Info from './info'
export default function TerritoryForm ({ sub }) {
const router = useRouter()
const client = useApolloClient()
const me = useMe()
const [upsertSub] = useMutation(
gql`
mutation upsertSub($oldName: String, $name: String!, $desc: String, $baseCost: Int!,
$postTypes: [String!]!, $allowFreebies: Boolean!, $billingType: String!,
$billingAutoRenew: Boolean!, $moderated: Boolean!, $hash: String, $hmac: String) {
upsertSub(oldName: $oldName, name: $name, desc: $desc, baseCost: $baseCost,
postTypes: $postTypes, allowFreebies: $allowFreebies, billingType: $billingType,
billingAutoRenew: $billingAutoRenew, moderated: $moderated, hash: $hash, hmac: $hmac) {
name
}
}`
)
const onSubmit = useCallback(
async ({ ...variables }) => {
const { error } = await upsertSub({
variables: { oldName: sub?.name, ...variables }
})
if (error) {
throw new Error({ message: error.toString() })
}
// modify graphql cache to include new sub
client.cache.modify({
fields: {
subs (existing = []) {
const filtered = existing.filter(s => s.name !== variables.name && s.name !== sub?.name)
return [
...filtered,
{ __typename: 'Sub', name: variables.name }]
}
}
})
await router.push(`/~${variables.name}`)
}, [client, upsertSub, router]
)
const [billing, setBilling] = useState((sub?.billingType || 'MONTHLY').toLowerCase())
return (
<FeeButtonProvider baseLineItems={sub ? undefined : { territory: TERRITORY_BILLING_OPTIONS('first')[billing] }}>
<Form
initial={{
name: sub?.name || '',
desc: sub?.desc || '',
baseCost: sub?.baseCost || 10,
postTypes: sub?.postTypes || POST_TYPES,
allowFreebies: typeof sub?.allowFreebies === 'undefined' ? true : sub?.allowFreebies,
billingType: sub?.billingType || 'MONTHLY',
billingAutoRenew: sub?.billingAutoRenew || false,
moderated: sub?.moderated || false
}}
schema={territorySchema({ client, me })}
invoiceable
onSubmit={onSubmit}
className='mb-5'
storageKeyPrefix={sub ? undefined : 'territory'}
> <Input
label='name'
name='name'
required
autoFocus
clear
maxLength={32}
prepend={<InputGroup.Text className='text-monospace'>~</InputGroup.Text>}
/>
<MarkdownInput
label='description'
name='desc'
maxLength={MAX_TERRITORY_DESC_LENGTH}
required
minRows={3}
/>
<Input
label='post cost'
name='baseCost'
type='number'
groupClassName='mb-2'
required
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
/>
<Checkbox
label='allow free posts'
name='allowFreebies'
groupClassName='ms-1'
/>
<CheckboxGroup label='post types' name='postTypes'>
<Row>
<Col xs={4} sm='auto'>
<Checkbox
inline
label='links'
value='LINK'
name='postTypes'
id='links-checkbox'
groupClassName='ms-1 mb-0'
/>
</Col>
<Col xs={4} sm='auto'>
<Checkbox
inline
label='discussions'
value='DISCUSSION'
name='postTypes'
id='discussions-checkbox'
groupClassName='ms-1 mb-0'
/>
</Col>
<Col xs={4} sm='auto'>
<Checkbox
inline
label='bounties'
value='BOUNTY'
name='postTypes'
id='bounties-checkbox'
groupClassName='ms-1 mb-0'
/>
</Col>
<Col xs={4} sm='auto'>
<Checkbox
inline
label='polls'
value='POLL'
name='postTypes'
id='polls-checkbox'
groupClassName='ms-1 mb-0'
/>
</Col>
</Row>
</CheckboxGroup>
<BootstrapForm.Label>moderation</BootstrapForm.Label>
<Checkbox
inline
label={
<div className='d-flex align-items-center'>enable moderation
<Info>
<ol>
<li>Outlaw posts and comments with a click</li>
<li>Your territory will get a <Badge bg='secondary'>moderated</Badge> badge</li>
</ol>
</Info>
</div>
}
name='moderated'
groupClassName='ms-1'
/>
<CheckboxGroup
label='billing'
name='billing'
groupClassName='mb-0'
>
{(!sub?.billingType || sub.billingType === 'MONTHLY') &&
<Checkbox
type='radio'
label='100k sats/month'
value='MONTHLY'
name='billingType'
readOnly={!!sub}
handleChange={checked => checked && setBilling('monthly')}
groupClassName='ms-1 mb-0'
/>}
{(!sub?.billingType || sub.billingType === 'YEARLY') &&
<Checkbox
type='radio'
label='1m sats/year'
value='YEARLY'
name='billingType'
readOnly={!!sub}
handleChange={checked => checked && setBilling('yearly')}
groupClassName='ms-1 mb-0'
/>}
{(!sub?.billingType || sub.billingType === 'ONCE') &&
<Checkbox
type='radio'
label='3m sats once'
value='ONCE'
name='billingType'
readOnly={!!sub}
handleChange={checked => checked && setBilling('once')}
groupClassName='ms-1 mb-0'
/>}
</CheckboxGroup>
{billing !== 'once' &&
<Checkbox
label='auto renew'
name='billingAutoRenew'
groupClassName='ms-1 mt-2'
/>}
<div className='mt-3 d-flex justify-content-end'>
<FeeButton
text={sub ? 'save' : 'found it'}
variant='secondary'
disabled={sub?.status === 'STOPPED'}
/>
</div>
</Form>
</FeeButtonProvider>
)
}