stacker.news/components/territory-transfer.js

101 lines
2.8 KiB
JavaScript
Raw Normal View History

Territory transfers (#878) * Allow founders to transfer territories * Log territory transfers in new AuditLog table * Add territory transfer notifications * Use polymorphic AuditEvent table * Add setting for territory transfer notifications * Add push notification * Rename label from user to stacker * More space between cancel and confirm button * Remove AuditEvent table The audit table is not necessary for territory transfers and only adds complexity and unrelated discussion to this PR. Thinking about a future-proof schema for territory transfers and how/what to audit at the same time made my head spin. Some thoughts I had: 1. Maybe using polymorphism for an audit log / audit events is not a good idea Using polymorphism as is currently used in the code base (user wallets) means that every generic event must map to exactly one specialized event. Is this a good requirement/assumption? It already didn't work well for naive auditing of territory transfers since we want events to be indexable by user (no array column) so every event needs to point to a single user but a territory transfer involves multiple users. This made me wonder: Do we even need a table? Maybe the audit log for a user can be implemented using a view? This would also mean no data denormalization. 2. What to audit and how and why? Most actions are already tracked in some way by necessity: zaps, items, mutes, payments, ... In that case: what is the benefit of tracking these things individually in a separate table? Denormalize simply for convenience or performance? Why no view (see previous point)? Use case needs to be more clearly defined before speccing out a schema. * Fix territory transfer notification id conflict * Use include instead of two separate queries * Drop territory transfer setting * Remove trigger usage * Prevent transfers to yourself
2024-03-05 19:56:02 +00:00
import { gql, useApolloClient, useMutation } from '@apollo/client'
import { useShowModal } from './modal'
import { useToast } from './toast'
import { Button, Dropdown, InputGroup } from 'react-bootstrap'
import { Form, InputUserSuggest, SubmitButton } from './form'
import { territoryTransferSchema } from '../lib/validate'
import { useCallback } from 'react'
import Link from 'next/link'
import { useMe } from './me'
function TransferObstacle ({ sub, onClose, userName }) {
const toaster = useToast()
const [transfer] = useMutation(
gql`
mutation transferTerritory($subName: String!, $userName: String!) {
transferTerritory(subName: $subName, userName: $userName) {
name
user {
id
}
}
}
`
)
return (
<div className='text-center'>
Do you really want to transfer your territory
<div>
<Link href={`/~${sub.name}`}>~{sub.name}</Link>
{' '}to{' '}
<Link href={`/${userName}`}>@{userName}</Link>?
</div>
<div className='d-flex justify-center align-items-center mt-3 mx-auto'>
<Button className='d-flex ms-auto mx-3' variant='danger' onClick={onClose}>cancel</Button>
<Button
className='d-flex me-auto mx-3' variant='success'
onClick={
async () => {
try {
await transfer({ variables: { subName: sub.name, userName } })
onClose()
toaster.success('transfer successful')
} catch (err) {
console.error(err)
toaster.danger('failed to transfer')
}
}
}
>confirm
</Button>
</div>
</div>
)
}
function TerritoryTransferForm ({ sub, onClose }) {
const showModal = useShowModal()
const client = useApolloClient()
const me = useMe()
const schema = territoryTransferSchema({ me, client })
const onSubmit = useCallback(async (values) => {
showModal(onClose => <TransferObstacle sub={sub} onClose={onClose} {...values} />)
}, [])
return (
<Form
initial={{
userName: ''
}}
schema={schema}
onSubmit={onSubmit}
>
<h2 className='text-center'>transfer territory</h2>
<div className='d-flex align-items-center mb-2'>
<InputUserSuggest
label='stacker'
name='userName'
prepend={<InputGroup.Text>@</InputGroup.Text>}
showValid
autoFocus
/>
</div>
<SubmitButton variant='success'>transfer</SubmitButton>
</Form>
)
}
export function TerritoryTransferDropdownItem ({ sub }) {
const showModal = useShowModal()
return (
<Dropdown.Item onClick={async () =>
showModal(onClose =>
<TerritoryTransferForm sub={sub} onClose={onClose} />)}
>
transfer
</Dropdown.Item>
)
}