stacker.news/components/territory-transfer.js
ekzyis b379e7467f
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 13:56:02 -06:00

101 lines
2.8 KiB
JavaScript

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>
)
}