stacker.news/components/job-form.js

271 lines
8.6 KiB
JavaScript
Raw Normal View History

2022-02-26 16:41:30 +00:00
import { Checkbox, Form, Input, MarkdownInput, SubmitButton } from './form'
2023-07-24 18:35:05 +00:00
import Row from 'react-bootstrap/Row'
import Col from 'react-bootstrap/Col'
import InputGroup from 'react-bootstrap/InputGroup'
import Image from 'react-bootstrap/Image'
import BootstrapForm from 'react-bootstrap/Form'
import Alert from 'react-bootstrap/Alert'
2022-02-17 17:23:43 +00:00
import { useEffect, useState } from 'react'
2022-04-18 16:08:58 +00:00
import Info from './info'
2022-02-17 17:23:43 +00:00
import AccordianItem from './accordian-item'
import styles from '../styles/post.module.css'
import { useLazyQuery, gql, useMutation } from '@apollo/client'
import { useRouter } from 'next/router'
2022-02-26 16:41:30 +00:00
import Link from 'next/link'
import { usePrice } from './price'
2022-07-21 22:55:05 +00:00
import Avatar from './avatar'
2022-11-29 17:28:57 +00:00
import ActionTooltip from './action-tooltip'
2023-02-08 19:38:04 +00:00
import { jobSchema } from '../lib/validate'
2023-06-12 19:34:10 +00:00
import CancelButton from './cancel-button'
2022-02-17 17:23:43 +00:00
2022-03-07 19:30:21 +00:00
function satsMin2Mo (minute) {
return minute * 30 * 24 * 60
}
function PriceHint ({ monthly }) {
const { price, fiatSymbol } = usePrice()
2022-09-12 23:42:09 +00:00
2022-09-29 20:42:33 +00:00
if (!price || !monthly) {
2022-03-07 19:30:21 +00:00
return null
}
const fixed = (n, f) => Number.parseFloat(n).toFixed(f)
const fiat = fixed((price / 100000000) * monthly, 0)
2022-09-12 23:42:09 +00:00
return <span className='text-muted'>{monthly} sats/mo which is {fiatSymbol}{fiat}/mo</span>
2022-02-17 17:23:43 +00:00
}
// need to recent list items
export default function JobForm ({ item, sub }) {
const storageKeyPrefix = item ? undefined : `${sub.name}-job`
const router = useRouter()
2022-07-21 22:55:05 +00:00
const [logoId, setLogoId] = useState(item?.uploadId)
2022-02-17 17:23:43 +00:00
const [upsertJob] = useMutation(gql`
2023-05-08 19:14:32 +00:00
mutation upsertJob($sub: String!, $id: ID, $title: String!, $company: String!, $location: String,
2022-07-21 22:55:05 +00:00
$remote: Boolean, $text: String!, $url: String!, $maxBid: Int!, $status: String, $logo: Int) {
2023-05-08 19:14:32 +00:00
upsertJob(sub: $sub, id: $id, title: $title, company: $company,
2022-03-07 21:50:13 +00:00
location: $location, remote: $remote, text: $text,
2022-07-21 22:55:05 +00:00
url: $url, maxBid: $maxBid, status: $status, logo: $logo) {
2022-02-17 17:23:43 +00:00
id
}
}`
)
return (
<>
<Form
2023-05-11 00:26:07 +00:00
className='pb-5 pt-3'
2022-02-17 17:23:43 +00:00
initial={{
title: item?.title || '',
2022-03-07 21:50:13 +00:00
company: item?.company || '',
location: item?.location || '',
remote: item?.remote || false,
2022-02-17 17:23:43 +00:00
text: item?.text || '',
url: item?.url || '',
2022-09-29 20:42:33 +00:00
maxBid: item?.maxBid || 0,
2022-02-26 16:41:30 +00:00
stop: false,
start: false
2022-02-17 17:23:43 +00:00
}}
2023-02-08 19:38:04 +00:00
schema={jobSchema}
2022-02-17 17:23:43 +00:00
storageKeyPrefix={storageKeyPrefix}
2022-02-26 16:41:30 +00:00
onSubmit={(async ({ maxBid, stop, start, ...values }) => {
let status
if (start) {
status = 'ACTIVE'
} else if (stop) {
status = 'STOPPED'
}
const { error } = await upsertJob({
variables: {
id: item?.id,
2023-05-08 19:14:32 +00:00
sub: item?.subName || sub?.name,
maxBid: Number(maxBid),
status,
2022-07-21 22:55:05 +00:00
logo: Number(logoId),
...values
}
})
2022-02-17 17:23:43 +00:00
if (error) {
throw new Error({ message: error.toString() })
}
if (item) {
2022-03-10 18:25:16 +00:00
await router.push(`/items/${item.id}`)
2022-02-17 17:23:43 +00:00
} else {
2022-03-10 18:25:16 +00:00
await router.push(`/~${sub.name}/recent`)
2022-02-17 17:23:43 +00:00
}
})}
>
2022-07-21 22:55:05 +00:00
<div className='form-group'>
<label className='form-label'>logo</label>
<div className='position-relative' style={{ width: 'fit-content' }}>
<Image
src={logoId ? `https://${process.env.NEXT_PUBLIC_AWS_UPLOAD_BUCKET}.s3.amazonaws.com/${logoId}` : '/jobs-default.png'} width='135' height='135' roundedCircle
/>
<Avatar onSuccess={setLogoId} />
</div>
</div>
2022-02-17 17:23:43 +00:00
<Input
2022-03-07 21:50:13 +00:00
label='job title'
2022-02-17 17:23:43 +00:00
name='title'
required
autoFocus
2022-08-25 18:46:07 +00:00
clear
2022-02-17 17:23:43 +00:00
/>
2022-03-07 21:50:13 +00:00
<Input
label='company'
name='company'
required
2022-08-25 18:46:07 +00:00
clear
2022-03-07 21:50:13 +00:00
/>
2023-07-24 18:35:05 +00:00
<Row className='me-0'>
2022-03-07 21:50:13 +00:00
<Col>
<Input
label='location'
name='location'
2022-08-25 18:46:07 +00:00
clear
2022-03-07 21:50:13 +00:00
/>
</Col>
2023-07-27 00:18:42 +00:00
<Col className='d-flex ps-0' xs='auto'>
<Checkbox
label={<div className='fw-bold'>remote</div>} name='remote' hiddenLabel
groupClassName={styles.inlineCheckGroup}
/>
</Col>
2023-07-24 18:35:05 +00:00
</Row>
2022-02-17 17:23:43 +00:00
<MarkdownInput
topLevel
2022-02-17 17:23:43 +00:00
label='description'
name='text'
minRows={6}
required
/>
<Input
2023-07-24 18:35:05 +00:00
label={<>how to apply <small className='text-muted ms-2'>url or email address</small></>}
2022-02-17 17:23:43 +00:00
name='url'
required
2022-08-25 18:46:07 +00:00
clear
2022-02-17 17:23:43 +00:00
/>
<PromoteJob item={item} sub={sub} />
2022-02-26 16:41:30 +00:00
{item && <StatusControl item={item} />}
2023-06-12 19:34:10 +00:00
<div className='d-flex align-items-center justify-content-end mt-3'>
2022-12-01 22:22:13 +00:00
{item
? (
<div className='d-flex'>
2023-06-12 19:34:10 +00:00
<CancelButton />
<SubmitButton variant='secondary'>save</SubmitButton>
</div>
)
2022-12-01 22:22:13 +00:00
: (
<ActionTooltip overlayText='1000 sats'>
<SubmitButton variant='secondary'>post <small> 1000 sats</small></SubmitButton>
</ActionTooltip>
)}
2022-11-29 17:28:57 +00:00
</div>
2022-02-17 17:23:43 +00:00
</Form>
</>
)
}
2022-02-26 16:41:30 +00:00
function PromoteJob ({ item, sub }) {
2022-09-29 20:42:33 +00:00
const [monthly, setMonthly] = useState(satsMin2Mo(item?.maxBid || 0))
const [getAuctionPosition, { data }] = useLazyQuery(gql`
query AuctionPosition($id: ID, $bid: Int!) {
2023-05-08 19:14:32 +00:00
auctionPosition(sub: "${item?.subName || sub?.name}", id: $id, bid: $bid)
2022-09-29 20:42:33 +00:00
}`,
{ fetchPolicy: 'cache-and-network' })
2022-09-29 20:42:33 +00:00
const position = data?.auctionPosition
useEffect(() => {
const initialMaxBid = Number(item?.maxBid) || 0
2022-09-29 20:42:33 +00:00
getAuctionPosition({ variables: { id: item?.id, bid: initialMaxBid } })
setMonthly(satsMin2Mo(initialMaxBid))
}, [])
return (
<AccordianItem
show={item?.maxBid > 0}
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>promote</div>}
body={
<>
<Input
label={
<div className='d-flex align-items-center'>bid
<Info>
2023-07-24 18:35:05 +00:00
<ol className='fw-bold'>
2022-09-29 20:42:33 +00:00
<li>The higher your bid the higher your job will rank</li>
<li>You can increase, decrease, or remove your bid at anytime</li>
<li>You can edit or stop your job at anytime</li>
<li>If you run out of sats, your job will stop being promoted until you fill your wallet again</li>
</ol>
</Info>
2023-07-24 18:35:05 +00:00
<small className='text-muted ms-2'>optional</small>
2022-09-29 20:42:33 +00:00
</div>
}
name='maxBid'
onChange={async (formik, e) => {
if (e.target.value >= 0 && e.target.value <= 100000000) {
setMonthly(satsMin2Mo(e.target.value))
getAuctionPosition({ variables: { id: item?.id, bid: Number(e.target.value) } })
} else {
setMonthly(satsMin2Mo(0))
}
}}
append={<InputGroup.Text className='text-monospace'>sats/min</InputGroup.Text>}
hint={<PriceHint monthly={monthly} />}
/>
2023-07-24 18:35:05 +00:00
<><div className='fw-bold text-muted'>This bid puts your job in position: {position}</div></>
2022-09-29 20:42:33 +00:00
</>
}
/>
)
}
2022-02-26 16:41:30 +00:00
function StatusControl ({ item }) {
let StatusComp
2022-02-28 20:09:21 +00:00
if (item.status !== 'STOPPED') {
2022-02-26 16:41:30 +00:00
StatusComp = () => {
return (
2022-02-28 20:09:21 +00:00
<>
<AccordianItem
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>I want to stop my job</div>}
2023-07-24 18:35:05 +00:00
headerColor='var(--bs-danger)'
2022-02-28 20:09:21 +00:00
body={
<Checkbox
2023-07-24 18:35:05 +00:00
label={<div className='fw-bold text-danger'>stop my job</div>} name='stop' inline
2022-02-28 20:09:21 +00:00
/>
2022-02-26 16:41:30 +00:00
}
2022-02-28 20:09:21 +00:00
/>
</>
2022-02-26 16:41:30 +00:00
)
}
2022-09-29 20:42:33 +00:00
} else if (item.status === 'STOPPED') {
2022-02-26 16:41:30 +00:00
StatusComp = () => {
return (
<AccordianItem
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>I want to resume my job</div>}
2023-07-24 18:35:05 +00:00
headerColor='var(--bs-success)'
2022-02-26 16:41:30 +00:00
body={
<Checkbox
2023-07-24 18:35:05 +00:00
label={<div className='fw-bold text-success'>resume my job</div>} name='start' inline
2022-02-26 16:41:30 +00:00
/>
}
/>
)
}
}
return (
2022-09-29 20:42:33 +00:00
<div className='my-3 border border-3 rounded'>
<div className='p-3'>
<BootstrapForm.Label>job control</BootstrapForm.Label>
{item.status === 'NOSATS' &&
<Alert variant='warning'>your promotion ran out of sats. <Link href='/wallet?type=fund' className='text-reset text-underline'>fund your wallet</Link> or reduce bid to continue promoting your job</Alert>}
2022-09-29 20:42:33 +00:00
<StatusComp />
</div>
2022-02-26 16:41:30 +00:00
</div>
)
}