2024-02-10 02:35:32 +00:00
|
|
|
import AccordianItem from './accordian-item'
|
2023-12-23 20:26:16 +00:00
|
|
|
import { Col, InputGroup, Row, Form as BootstrapForm, Badge } from 'react-bootstrap'
|
2023-11-21 23:32:22 +00:00
|
|
|
import { Checkbox, CheckboxGroup, Form, Input, MarkdownInput } from './form'
|
|
|
|
import FeeButton, { FeeButtonProvider } from './fee-button'
|
2024-03-19 22:23:59 +00:00
|
|
|
import { gql, useApolloClient, useLazyQuery, useMutation } from '@apollo/client'
|
2024-02-16 18:25:12 +00:00
|
|
|
import { useCallback, useMemo, useState } from 'react'
|
2023-11-21 23:32:22 +00:00
|
|
|
import { useRouter } from 'next/router'
|
2024-03-20 00:37:31 +00:00
|
|
|
import { MAX_TERRITORY_DESC_LENGTH, POST_TYPES, TERRITORY_BILLING_OPTIONS, TERRITORY_PERIOD_COST } from '@/lib/constants'
|
|
|
|
import { territorySchema } from '@/lib/validate'
|
2023-11-21 23:32:22 +00:00
|
|
|
import { useMe } from './me'
|
2023-12-23 20:26:16 +00:00
|
|
|
import Info from './info'
|
2024-03-20 00:37:31 +00:00
|
|
|
import { abbrNum } from '@/lib/format'
|
|
|
|
import { purchasedType } from '@/lib/territory'
|
|
|
|
import { SUB } from '@/fragments/subs'
|
2023-11-21 23:32:22 +00:00
|
|
|
|
|
|
|
export default function TerritoryForm ({ sub }) {
|
|
|
|
const router = useRouter()
|
|
|
|
const client = useApolloClient()
|
|
|
|
const me = useMe()
|
|
|
|
const [upsertSub] = useMutation(
|
|
|
|
gql`
|
2024-01-11 23:45:17 +00:00
|
|
|
mutation upsertSub($oldName: String, $name: String!, $desc: String, $baseCost: Int!,
|
2023-12-10 21:41:20 +00:00
|
|
|
$postTypes: [String!]!, $allowFreebies: Boolean!, $billingType: String!,
|
2024-02-10 02:35:32 +00:00
|
|
|
$billingAutoRenew: Boolean!, $moderated: Boolean!, $hash: String, $hmac: String, $nsfw: Boolean!) {
|
2024-01-11 23:45:17 +00:00
|
|
|
upsertSub(oldName: $oldName, name: $name, desc: $desc, baseCost: $baseCost,
|
2023-12-10 21:41:20 +00:00
|
|
|
postTypes: $postTypes, allowFreebies: $allowFreebies, billingType: $billingType,
|
2024-02-10 02:35:32 +00:00
|
|
|
billingAutoRenew: $billingAutoRenew, moderated: $moderated, hash: $hash, hmac: $hmac, nsfw: $nsfw) {
|
2023-11-21 23:32:22 +00:00
|
|
|
name
|
|
|
|
}
|
|
|
|
}`
|
|
|
|
)
|
2024-03-19 22:23:59 +00:00
|
|
|
const [unarchiveTerritory] = useMutation(
|
|
|
|
gql`
|
|
|
|
mutation unarchiveTerritory($name: String!, $desc: String, $baseCost: Int!,
|
|
|
|
$postTypes: [String!]!, $allowFreebies: Boolean!, $billingType: String!,
|
|
|
|
$billingAutoRenew: Boolean!, $moderated: Boolean!, $hash: String, $hmac: String, $nsfw: Boolean!) {
|
|
|
|
unarchiveTerritory(name: $name, desc: $desc, baseCost: $baseCost,
|
|
|
|
postTypes: $postTypes, allowFreebies: $allowFreebies, billingType: $billingType,
|
|
|
|
billingAutoRenew: $billingAutoRenew, moderated: $moderated, hash: $hash, hmac: $hmac, nsfw: $nsfw) {
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}`
|
|
|
|
)
|
|
|
|
|
|
|
|
const schema = territorySchema({ client, me, sub })
|
|
|
|
|
|
|
|
const [fetchSub] = useLazyQuery(SUB)
|
|
|
|
const [archived, setArchived] = useState(false)
|
|
|
|
const onNameChange = useCallback(async (formik, e) => {
|
|
|
|
// never show "territory archived" warning during edits
|
|
|
|
if (sub) return
|
|
|
|
const name = e.target.value
|
|
|
|
const { data } = await fetchSub({ variables: { sub: name } })
|
|
|
|
setArchived(data?.sub?.status === 'STOPPED')
|
|
|
|
}, [fetchSub, setArchived])
|
2023-11-21 23:32:22 +00:00
|
|
|
|
|
|
|
const onSubmit = useCallback(
|
|
|
|
async ({ ...variables }) => {
|
2024-03-19 22:23:59 +00:00
|
|
|
const { error } = archived
|
|
|
|
? await unarchiveTerritory({ variables })
|
|
|
|
: await upsertSub({ variables: { oldName: sub?.name, ...variables } })
|
2023-11-21 23:32:22 +00:00
|
|
|
|
|
|
|
if (error) {
|
|
|
|
throw new Error({ message: error.toString() })
|
|
|
|
}
|
|
|
|
|
|
|
|
// modify graphql cache to include new sub
|
|
|
|
client.cache.modify({
|
|
|
|
fields: {
|
|
|
|
subs (existing = []) {
|
2024-01-11 23:45:17 +00:00
|
|
|
const filtered = existing.filter(s => s.name !== variables.name && s.name !== sub?.name)
|
2023-11-21 23:32:22 +00:00
|
|
|
return [
|
2024-01-11 23:45:17 +00:00
|
|
|
...filtered,
|
2023-11-21 23:32:22 +00:00
|
|
|
{ __typename: 'Sub', name: variables.name }]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
await router.push(`/~${variables.name}`)
|
2024-03-19 22:23:59 +00:00
|
|
|
}, [client, upsertSub, unarchiveTerritory, router, archived]
|
2023-11-21 23:32:22 +00:00
|
|
|
)
|
|
|
|
|
2023-12-09 01:11:25 +00:00
|
|
|
const [billing, setBilling] = useState((sub?.billingType || 'MONTHLY').toLowerCase())
|
2024-02-16 18:25:12 +00:00
|
|
|
const lineItems = useMemo(() => {
|
|
|
|
const lines = { territory: TERRITORY_BILLING_OPTIONS('first')[billing] }
|
|
|
|
if (!sub) return lines
|
|
|
|
|
|
|
|
// we are changing billing type so prorate the change
|
|
|
|
if (sub?.billingType?.toLowerCase() !== billing) {
|
|
|
|
const alreadyBilled = TERRITORY_PERIOD_COST(purchasedType(sub))
|
|
|
|
lines.paid = {
|
|
|
|
term: `- ${abbrNum(alreadyBilled)} sats`,
|
|
|
|
label: 'already paid',
|
|
|
|
modifier: cost => cost - alreadyBilled
|
|
|
|
}
|
|
|
|
return lines
|
|
|
|
}
|
|
|
|
}, [sub, billing])
|
2023-11-21 23:32:22 +00:00
|
|
|
|
|
|
|
return (
|
2024-02-16 18:25:12 +00:00
|
|
|
<FeeButtonProvider baseLineItems={lineItems}>
|
2023-11-21 23:32:22 +00:00
|
|
|
<Form
|
|
|
|
initial={{
|
|
|
|
name: sub?.name || '',
|
|
|
|
desc: sub?.desc || '',
|
|
|
|
baseCost: sub?.baseCost || 10,
|
|
|
|
postTypes: sub?.postTypes || POST_TYPES,
|
2023-12-10 21:41:20 +00:00
|
|
|
allowFreebies: typeof sub?.allowFreebies === 'undefined' ? true : sub?.allowFreebies,
|
2023-12-08 20:02:00 +00:00
|
|
|
billingType: sub?.billingType || 'MONTHLY',
|
2023-12-23 20:26:16 +00:00
|
|
|
billingAutoRenew: sub?.billingAutoRenew || false,
|
2024-02-10 02:35:32 +00:00
|
|
|
moderated: sub?.moderated || false,
|
|
|
|
nsfw: sub?.nsfw || false
|
2023-11-21 23:32:22 +00:00
|
|
|
}}
|
2024-03-19 22:23:59 +00:00
|
|
|
schema={schema}
|
2023-11-21 23:32:22 +00:00
|
|
|
invoiceable
|
|
|
|
onSubmit={onSubmit}
|
|
|
|
className='mb-5'
|
|
|
|
storageKeyPrefix={sub ? undefined : 'territory'}
|
2024-02-16 18:25:12 +00:00
|
|
|
>
|
|
|
|
<Input
|
|
|
|
label='name'
|
|
|
|
name='name'
|
|
|
|
required
|
|
|
|
autoFocus
|
|
|
|
clear
|
|
|
|
maxLength={32}
|
|
|
|
prepend={<InputGroup.Text className='text-monospace'>~</InputGroup.Text>}
|
2024-03-19 22:23:59 +00:00
|
|
|
onChange={onNameChange}
|
|
|
|
warn={archived && (
|
|
|
|
<div className='d-flex align-items-center'>this territory is archived
|
|
|
|
<Info>
|
|
|
|
<ul className='fw-bold'>
|
|
|
|
<li>This territory got archived because the previous founder did not pay for the upkeep</li>
|
|
|
|
<li>You can proceed but will inherit the old content</li>
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
)}
|
2024-01-11 23:45:17 +00:00
|
|
|
/>
|
2023-11-21 23:32:22 +00:00
|
|
|
<MarkdownInput
|
|
|
|
label='description'
|
|
|
|
name='desc'
|
|
|
|
maxLength={MAX_TERRITORY_DESC_LENGTH}
|
|
|
|
required
|
|
|
|
minRows={3}
|
|
|
|
/>
|
|
|
|
<Input
|
|
|
|
label='post cost'
|
|
|
|
name='baseCost'
|
|
|
|
type='number'
|
2023-12-10 21:41:20 +00:00
|
|
|
groupClassName='mb-2'
|
2023-11-21 23:32:22 +00:00
|
|
|
required
|
|
|
|
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
|
|
|
/>
|
2023-12-10 21:41:20 +00:00
|
|
|
<Checkbox
|
|
|
|
label='allow free posts'
|
|
|
|
name='allowFreebies'
|
|
|
|
groupClassName='ms-1'
|
|
|
|
/>
|
2023-11-21 23:32:22 +00:00
|
|
|
<CheckboxGroup label='post types' name='postTypes'>
|
|
|
|
<Row>
|
|
|
|
<Col xs={4} sm='auto'>
|
|
|
|
<Checkbox
|
|
|
|
inline
|
|
|
|
label='links'
|
|
|
|
value='LINK'
|
|
|
|
name='postTypes'
|
2024-02-03 21:31:22 +00:00
|
|
|
id='links-checkbox'
|
2023-11-21 23:32:22 +00:00
|
|
|
groupClassName='ms-1 mb-0'
|
|
|
|
/>
|
|
|
|
</Col>
|
|
|
|
<Col xs={4} sm='auto'>
|
|
|
|
<Checkbox
|
|
|
|
inline
|
|
|
|
label='discussions'
|
|
|
|
value='DISCUSSION'
|
|
|
|
name='postTypes'
|
2024-02-03 21:31:22 +00:00
|
|
|
id='discussions-checkbox'
|
2023-11-21 23:32:22 +00:00
|
|
|
groupClassName='ms-1 mb-0'
|
|
|
|
/>
|
|
|
|
</Col>
|
|
|
|
<Col xs={4} sm='auto'>
|
|
|
|
<Checkbox
|
|
|
|
inline
|
|
|
|
label='bounties'
|
|
|
|
value='BOUNTY'
|
|
|
|
name='postTypes'
|
2024-02-03 21:31:22 +00:00
|
|
|
id='bounties-checkbox'
|
2023-11-21 23:32:22 +00:00
|
|
|
groupClassName='ms-1 mb-0'
|
|
|
|
/>
|
|
|
|
</Col>
|
|
|
|
<Col xs={4} sm='auto'>
|
|
|
|
<Checkbox
|
|
|
|
inline
|
|
|
|
label='polls'
|
|
|
|
value='POLL'
|
|
|
|
name='postTypes'
|
2024-02-03 21:31:22 +00:00
|
|
|
id='polls-checkbox'
|
2023-11-21 23:32:22 +00:00
|
|
|
groupClassName='ms-1 mb-0'
|
|
|
|
/>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
</CheckboxGroup>
|
2024-02-16 18:25:12 +00:00
|
|
|
{sub?.billingType !== 'ONCE' &&
|
|
|
|
<>
|
|
|
|
<CheckboxGroup
|
|
|
|
label={
|
|
|
|
<span className='d-flex align-items-center'>billing
|
|
|
|
{sub && sub.billingType !== 'ONCE' &&
|
|
|
|
<Info>
|
|
|
|
You will be credited what you paid for your current billing period when you change your billing period to a longer duration.
|
|
|
|
If you change from yearly to monthly, when your year ends, your monthly billing will begin.
|
|
|
|
</Info>}
|
|
|
|
</span>
|
|
|
|
}
|
|
|
|
name='billing'
|
|
|
|
groupClassName={billing !== 'once' ? 'mb-0' : ''}
|
|
|
|
>
|
|
|
|
<Checkbox
|
|
|
|
type='radio'
|
|
|
|
label='100k sats/month'
|
|
|
|
value='MONTHLY'
|
|
|
|
name='billingType'
|
|
|
|
id='monthly-checkbox'
|
|
|
|
handleChange={checked => checked && setBilling('monthly')}
|
|
|
|
groupClassName='ms-1 mb-0'
|
|
|
|
/>
|
|
|
|
<Checkbox
|
|
|
|
type='radio'
|
|
|
|
label='1m sats/year'
|
|
|
|
value='YEARLY'
|
|
|
|
name='billingType'
|
|
|
|
id='yearly-checkbox'
|
|
|
|
handleChange={checked => checked && setBilling('yearly')}
|
|
|
|
groupClassName='ms-1 mb-0'
|
|
|
|
/>
|
|
|
|
<Checkbox
|
|
|
|
type='radio'
|
|
|
|
label='3m sats once'
|
|
|
|
value='ONCE'
|
|
|
|
name='billingType'
|
|
|
|
id='once-checkbox'
|
|
|
|
handleChange={checked => checked && setBilling('once')}
|
|
|
|
groupClassName='ms-1 mb-0'
|
|
|
|
/>
|
|
|
|
</CheckboxGroup>
|
|
|
|
{billing !== 'once' &&
|
|
|
|
<Checkbox
|
|
|
|
label='auto-renew'
|
|
|
|
name='billingAutoRenew'
|
|
|
|
groupClassName='ms-1 mt-2'
|
|
|
|
/>}
|
|
|
|
</>}
|
2024-02-10 02:35:32 +00:00
|
|
|
<AccordianItem
|
|
|
|
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>options</div>}
|
|
|
|
body={
|
|
|
|
<>
|
|
|
|
<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'
|
|
|
|
/>
|
|
|
|
<BootstrapForm.Label>nsfw</BootstrapForm.Label>
|
|
|
|
<Checkbox
|
|
|
|
inline
|
|
|
|
label={
|
|
|
|
<div className='d-flex align-items-center'>mark as nsfw
|
|
|
|
<Info>
|
|
|
|
<ol>
|
|
|
|
<li>Let stackers know that your territory may contain explicit content</li>
|
|
|
|
<li>Your territory will get a <Badge bg='secondary'>nsfw</Badge> badge</li>
|
|
|
|
</ol>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
name='nsfw'
|
|
|
|
groupClassName='ms-1'
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
|
|
|
|
}
|
|
|
|
/>
|
2023-11-21 23:32:22 +00:00
|
|
|
<div className='mt-3 d-flex justify-content-end'>
|
|
|
|
<FeeButton
|
|
|
|
text={sub ? 'save' : 'found it'}
|
|
|
|
variant='secondary'
|
|
|
|
disabled={sub?.status === 'STOPPED'}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</Form>
|
|
|
|
</FeeButtonProvider>
|
|
|
|
)
|
|
|
|
}
|