This commit is contained in:
keyan 2023-01-12 17:53:09 -06:00
parent ed153b5199
commit 10ff3fa1c3
18 changed files with 249 additions and 71 deletions

View File

@ -159,7 +159,7 @@ export default {
${SELECT} ${SELECT}
FROM "Item" FROM "Item"
WHERE "parentId" IS NULL AND "Item".created_at <= $1 WHERE "parentId" IS NULL AND "Item".created_at <= $1
AND "pinId" IS NULL AND "pinId" IS NULL AND "deletedAt" IS NULL
${topClause(when)} ${topClause(when)}
${await filterClause(me, models)} ${await filterClause(me, models)}
${await topOrderClause(sort, me, models)} ${await topOrderClause(sort, me, models)}
@ -176,7 +176,7 @@ export default {
${SELECT} ${SELECT}
FROM "Item" FROM "Item"
WHERE "parentId" IS NOT NULL WHERE "parentId" IS NOT NULL
AND "Item".created_at <= $1 AND "Item".created_at <= $1 AND "deletedAt" IS NULL
${topClause(when)} ${topClause(when)}
${await filterClause(me, models)} ${await filterClause(me, models)}
${await topOrderClause(sort, me, models)} ${await topOrderClause(sort, me, models)}
@ -239,7 +239,7 @@ export default {
${SELECT} ${SELECT}
FROM "Item" FROM "Item"
WHERE "parentId" IS NULL AND "Item".created_at <= $1 WHERE "parentId" IS NULL AND "Item".created_at <= $1
AND "pinId" IS NULL AND "pinId" IS NULL AND "deletedAt" IS NULL
${topClause(within)} ${topClause(within)}
${await filterClause(me, models)} ${await filterClause(me, models)}
${await topOrderByWeightedSats(me, models)} ${await topOrderByWeightedSats(me, models)}
@ -288,7 +288,7 @@ export default {
${SELECT} ${SELECT}
FROM "Item" FROM "Item"
WHERE "parentId" IS NULL AND "Item".created_at <= $1 AND "Item".created_at > $3 WHERE "parentId" IS NULL AND "Item".created_at <= $1 AND "Item".created_at > $3
AND "pinId" IS NULL AND NOT bio AND "pinId" IS NULL AND NOT bio AND "deletedAt" IS NULL
${subClause(4)} ${subClause(4)}
${await filterClause(me, models)} ${await filterClause(me, models)}
${await newTimedOrderByWeightedSats(me, models, 1)} ${await newTimedOrderByWeightedSats(me, models, 1)}
@ -301,7 +301,7 @@ export default {
${SELECT} ${SELECT}
FROM "Item" FROM "Item"
WHERE "parentId" IS NULL AND "Item".created_at <= $1 WHERE "parentId" IS NULL AND "Item".created_at <= $1
AND "pinId" IS NULL AND NOT bio AND "pinId" IS NULL AND NOT bio AND "deletedAt" IS NULL
${subClause(3)} ${subClause(3)}
${await filterClause(me, models)} ${await filterClause(me, models)}
${await newTimedOrderByWeightedSats(me, models, 1)} ${await newTimedOrderByWeightedSats(me, models, 1)}
@ -438,7 +438,7 @@ export default {
comments = await models.$queryRaw(` comments = await models.$queryRaw(`
${SELECT} ${SELECT}
FROM "Item" FROM "Item"
WHERE "parentId" IS NOT NULL WHERE "parentId" IS NOT NULL AND "deletedAt" IS NULL
AND "Item".created_at <= $1 AND "Item".created_at <= $1
${topClause(within)} ${topClause(within)}
${await filterClause(me, models)} ${await filterClause(me, models)}
@ -548,6 +548,28 @@ export default {
}, },
Mutation: { Mutation: {
deleteItem: async (parent, { id }, { me, models }) => {
const old = await models.item.findUnique({ where: { id: Number(id) } })
if (Number(old.userId) !== Number(me?.id)) {
throw new AuthenticationError('item does not belong to you')
}
const data = { deletedAt: new Date() }
if (old.text) {
data.text = '*deleted by author*'
}
if (old.title) {
data.title = 'deleted by author'
}
if (old.url) {
data.url = null
}
if (old.pollCost) {
data.pollCost = null
}
return await models.item.update({ where: { id: Number(id) }, data })
},
upsertLink: async (parent, args, { me, models }) => { upsertLink: async (parent, args, { me, models }) => {
const { id, ...data } = args const { id, ...data } = args
data.url = ensureProtocol(data.url) data.url = ensureProtocol(data.url)
@ -1040,7 +1062,7 @@ function nestComments (flat, parentId) {
export const SELECT = export const SELECT =
`SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title, `SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title,
"Item".text, "Item".url, "Item"."userId", "Item"."fwdUserId", "Item"."parentId", "Item"."pinId", "Item"."maxBid", "Item".text, "Item".url, "Item"."userId", "Item"."fwdUserId", "Item"."parentId", "Item"."pinId", "Item"."maxBid",
"Item".company, "Item".location, "Item".remote, "Item".company, "Item".location, "Item".remote, "Item"."deletedAt",
"Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", "Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost",
"Item".msats, "Item".ncomments, "Item"."commentMsats", "Item"."lastCommentAt", "Item"."weightedVotes", "Item".msats, "Item".ncomments, "Item"."commentMsats", "Item"."lastCommentAt", "Item"."weightedVotes",
"Item"."weightedDownVotes", "Item".freebie, ltree2text("Item"."path") AS "path"` "Item"."weightedDownVotes", "Item".freebie, ltree2text("Item"."path") AS "path"`

View File

@ -31,6 +31,7 @@ export default gql`
} }
extend type Mutation { extend type Mutation {
deleteItem(id: ID): Item
upsertLink(id: ID, title: String!, url: String!, boost: Int, forward: String): Item! upsertLink(id: ID, title: String!, url: String!, boost: Int, forward: String): Item!
upsertDiscussion(id: ID, title: String!, text: String, boost: Int, forward: String): Item! upsertDiscussion(id: ID, title: String!, text: String, boost: Int, forward: String): Item!
upsertJob(id: ID, sub: ID!, title: String!, company: String!, location: String, remote: Boolean, upsertJob(id: ID, sub: ID!, title: String!, company: String!, location: String, remote: Boolean,
@ -71,6 +72,7 @@ export default gql`
id: ID! id: ID!
createdAt: String! createdAt: String!
updatedAt: String! updatedAt: String!
deletedAt: String
title: String title: String
searchTitle: String searchTitle: String
url: String url: String

View File

@ -4,6 +4,8 @@ import { gql, useMutation } from '@apollo/client'
import styles from './reply.module.css' import styles from './reply.module.css'
import TextareaAutosize from 'react-textarea-autosize' import TextareaAutosize from 'react-textarea-autosize'
import { EditFeeButton } from './fee-button' import { EditFeeButton } from './fee-button'
import { Button } from 'react-bootstrap'
import Delete from './delete'
export const CommentSchema = Yup.object({ export const CommentSchema = Yup.object({
text: Yup.string().required('required').trim() text: Yup.string().required('required').trim()
@ -54,10 +56,15 @@ export default function CommentEdit ({ comment, editThreshold, onSuccess, onCanc
autoFocus autoFocus
required required
/> />
<EditFeeButton <div className='d-flex justify-content-between'>
paidSats={comment.meSats} <Delete itemId={comment.id} onDelete={onSuccess}>
parentId={comment.parentId} text='save' ChildButton={SubmitButton} variant='secondary' <Button variant='grey-medium'>delete</Button>
/> </Delete>
<EditFeeButton
paidSats={comment.meSats}
parentId={comment.parentId} text='save' ChildButton={SubmitButton} variant='secondary'
/>
</div>
</Form> </Form>
</div> </div>
) )

View File

@ -19,6 +19,7 @@ import Flag from '../svgs/flag-fill.svg'
import { Badge } from 'react-bootstrap' import { Badge } from 'react-bootstrap'
import { abbrNum } from '../lib/format' import { abbrNum } from '../lib/format'
import Share from './share' import Share from './share'
import { DeleteDropdown } from './delete'
function Parent ({ item, rootText }) { function Parent ({ item, rootText }) {
const ParentFrag = () => ( const ParentFrag = () => (
@ -138,7 +139,7 @@ export default function Comment ({
{me && !item.meSats && !item.meDontLike && !item.mine && <DontLikeThis id={item.id} />} {me && !item.meSats && !item.meDontLike && !item.mine && <DontLikeThis id={item.id} />}
{(item.outlawed && <Link href='/outlawed'><a>{' '}<Badge className={itemStyles.newComment} variant={null}>OUTLAWED</Badge></a></Link>) || {(item.outlawed && <Link href='/outlawed'><a>{' '}<Badge className={itemStyles.newComment} variant={null}>OUTLAWED</Badge></a></Link>) ||
(item.freebie && !item.mine && (me?.greeterMode) && <Link href='/freebie'><a>{' '}<Badge className={itemStyles.newComment} variant={null}>FREEBIE</Badge></a></Link>)} (item.freebie && !item.mine && (me?.greeterMode) && <Link href='/freebie'><a>{' '}<Badge className={itemStyles.newComment} variant={null}>FREEBIE</Badge></a></Link>)}
{canEdit && {canEdit && !item.deletedAt &&
<> <>
<span> \ </span> <span> \ </span>
<div <div
@ -156,6 +157,7 @@ export default function Comment ({
/> />
</div> </div>
</>} </>}
{mine && !canEdit && !item.deletedAt && <DeleteDropdown itemId={item.id} />}
</div> </div>
{!includeParent && (collapse {!includeParent && (collapse
? <Eye ? <Eye

103
components/delete.js Normal file
View File

@ -0,0 +1,103 @@
import { useMutation } from '@apollo/client'
import { gql } from 'apollo-server-micro'
import { useState } from 'react'
import { Alert, Button, Dropdown } from 'react-bootstrap'
import { useShowModal } from './modal'
import MoreIcon from '../svgs/more-fill.svg'
export default function Delete ({ itemId, children, onDelete }) {
const showModal = useShowModal()
const [deleteItem] = useMutation(
gql`
mutation deleteItem($id: ID!) {
deleteItem(id: $id) {
text
title
url
pollCost
deletedAt
}
}`, {
update (cache, { data: { deleteItem } }) {
console.log(deleteItem)
cache.modify({
id: `Item:${itemId}`,
fields: {
text: () => deleteItem.text,
title: () => deleteItem.title,
url: () => deleteItem.url,
pollCost: () => deleteItem.pollCost,
deletedAt: () => deleteItem.deletedAt
}
})
}
}
)
return (
<span
className='pointer' onClick={() => {
showModal(onClose => {
return (
<DeleteConfirm
onConfirm={async () => {
const { error } = await deleteItem({ variables: { id: itemId } })
if (error) {
throw new Error({ message: error.toString() })
}
if (onDelete) {
onDelete()
}
onClose()
}}
/>
)
})
}}
>{children}
</span>
)
}
function DeleteConfirm ({ onConfirm }) {
const [error, setError] = useState()
return (
<>
{error && <Alert variant='danger' onClose={() => setError(undefined)} dismissible>{error}</Alert>}
<p className='font-weight-bolder'>Are you sure? This is a gone forever kind of delete.</p>
<div className='d-flex justify-content-end'>
<Button
variant='danger' onClick={async () => {
try {
await onConfirm()
} catch (e) {
setError(e.message || e)
}
}}
>delete
</Button>
</div>
</>
)
}
export function DeleteDropdown (props) {
return (
<Dropdown className='pointer' as='span'>
<Dropdown.Toggle variant='success' id='dropdown-basic' as='a'>
<MoreIcon className='fill-grey ml-1' height={16} width={16} />
</Dropdown.Toggle>
<Dropdown.Menu>
<Delete {...props}>
<Dropdown.Item
className='text-center'
>
delete
</Dropdown.Item>
</Delete>
</Dropdown.Menu>
</Dropdown>
)
}

View File

@ -10,6 +10,8 @@ import FeeButton, { EditFeeButton } from './fee-button'
import { ITEM_FIELDS } from '../fragments/items' import { ITEM_FIELDS } from '../fragments/items'
import AccordianItem from './accordian-item' import AccordianItem from './accordian-item'
import Item from './item' import Item from './item'
import Delete from './delete'
import { Button } from 'react-bootstrap'
export function DiscussionForm ({ export function DiscussionForm ({
item, editThreshold, titleLabel = 'title', item, editThreshold, titleLabel = 'title',
@ -103,27 +105,34 @@ export function DiscussionForm ({
{adv && <AdvPostForm edit={!!item} />} {adv && <AdvPostForm edit={!!item} />}
<div className='mt-3'> <div className='mt-3'>
{item {item
? <EditFeeButton ? (
paidSats={item.meSats} <div className='d-flex justify-content-between'>
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary' <Delete itemId={item.id} onDelete={() => router.push(`/items/${item.id}`)}>
/> <Button variant='grey-medium'>delete</Button>
</Delete>
<EditFeeButton
paidSats={item.meSats}
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
/>
</div>)
: <FeeButton : <FeeButton
baseFee={1} parentId={null} text={buttonText} baseFee={1} parentId={null} text={buttonText}
ChildButton={SubmitButton} variant='secondary' ChildButton={SubmitButton} variant='secondary'
/>} />}
</div> </div>
<div className={`mt-3 ${related.length > 0 ? '' : 'invisible'}`}> {!item &&
<AccordianItem <div className={`mt-3 ${related.length > 0 ? '' : 'invisible'}`}>
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>similar</div>} <AccordianItem
body={ header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>similar</div>}
<div> body={
{related.map((item, i) => ( <div>
<Item item={item} key={item.id} /> {related.map((item, i) => (
))} <Item item={item} key={item.id} />
</div> ))}
</div>
} }
/> />
</div> </div>}
</Form> </Form>
) )
} }

View File

@ -16,6 +16,7 @@ import DontLikeThis from './dont-link-this'
import Flag from '../svgs/flag-fill.svg' import Flag from '../svgs/flag-fill.svg'
import Share from './share' import Share from './share'
import { abbrNum } from '../lib/format' import { abbrNum } from '../lib/format'
import { DeleteDropdown } from './delete'
export function SearchTitle ({ title }) { export function SearchTitle ({ title }) {
return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => { return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
@ -123,7 +124,7 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
</Link> </Link>
</>} </>}
</span> </span>
{canEdit && {canEdit && !item.deletedAt &&
<> <>
<span> \ </span> <span> \ </span>
<Link href={`/items/${item.id}/edit`} passHref> <Link href={`/items/${item.id}/edit`} passHref>
@ -139,6 +140,8 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
</a> </a>
</Link> </Link>
</>} </>}
{mine && !canEdit && !item.position && !item.deletedAt &&
<DeleteDropdown itemId={item.id} />}
</div> </div>
{showFwdUser && item.fwdUser && <FwdUser user={item.fwdUser} />} {showFwdUser && item.fwdUser && <FwdUser user={item.fwdUser} />}
</div> </div>

View File

@ -10,6 +10,8 @@ import AccordianItem from './accordian-item'
import { MAX_TITLE_LENGTH } from '../lib/constants' import { MAX_TITLE_LENGTH } from '../lib/constants'
import { URL_REGEXP } from '../lib/url' import { URL_REGEXP } from '../lib/url'
import FeeButton, { EditFeeButton } from './fee-button' import FeeButton, { EditFeeButton } from './fee-button'
import Delete from './delete'
import { Button } from 'react-bootstrap'
export function LinkForm ({ item, editThreshold }) { export function LinkForm ({ item, editThreshold }) {
const router = useRouter() const router = useRouter()
@ -138,42 +140,51 @@ export function LinkForm ({ item, editThreshold }) {
<AdvPostForm edit={!!item} /> <AdvPostForm edit={!!item} />
<div className='mt-3'> <div className='mt-3'>
{item {item
? <EditFeeButton ? (
paidSats={item.meSats} <div className='d-flex justify-content-between'>
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary' <Delete itemId={item.id} onDelete={() => router.push(`/items/${item.id}`)}>
/> <Button variant='grey-medium'>delete</Button>
</Delete>
<EditFeeButton
paidSats={item.meSats}
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
/>
</div>)
: <FeeButton : <FeeButton
baseFee={1} parentId={null} text='post' baseFee={1} parentId={null} text='post'
ChildButton={SubmitButton} variant='secondary' ChildButton={SubmitButton} variant='secondary'
/>} />}
</div> </div>
{dupesData?.dupes?.length > 0 && {!item &&
<div className='mt-3'> <>
<AccordianItem {dupesData?.dupes?.length > 0 &&
show <div className='mt-3'>
headerColor='#c03221' <AccordianItem
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>dupes</div>} show
body={ headerColor='#c03221'
<div> header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>dupes</div>}
{dupesData.dupes.map((item, i) => ( body={
<Item item={item} key={item.id} /> <div>
))} {dupesData.dupes.map((item, i) => (
</div> <Item item={item} key={item.id} />
))}
</div>
} }
/> />
</div>} </div>}
<div className={`mt-3 ${related.length > 0 ? '' : 'invisible'}`}> <div className={`mt-3 ${related.length > 0 ? '' : 'invisible'}`}>
<AccordianItem <AccordianItem
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>similar</div>} header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>similar</div>}
body={ body={
<div> <div>
{related.map((item, i) => ( {related.map((item, i) => (
<Item item={item} key={item.id} /> <Item item={item} key={item.id} />
))} ))}
</div> </div>
} }
/> />
</div> </div>
</>}
</Form> </Form>
) )
} }

View File

@ -83,7 +83,7 @@ function Notification ({ n }) {
{n.sources.tips > 0 && <span>{(n.sources.comments > 0 || n.sources.posts > 0) && ' \\ '}{n.sources.tips} sats for tipping top content early</span>} {n.sources.tips > 0 && <span>{(n.sources.comments > 0 || n.sources.posts > 0) && ' \\ '}{n.sources.tips} sats for tipping top content early</span>}
</div>} </div>}
<div className='pb-1' style={{ lineHeight: '140%' }}> <div className='pb-1' style={{ lineHeight: '140%' }}>
SN distributes the sats it earns back to its best users daily. These sats come from <Link href='/~jobs' passHref><a>jobs</a></Link>, boosts, posting fees, and donations. You can see the daily rewards pool and make a donation <Link href='/rewards' passHref><a>here</a></Link>. SN distributes the sats it earns back to its best users daily. These sats come from <Link href='/~jobs' passHref><a>jobs</a></Link>, boosts, posting fees, and donations. You can see the daily rewards pool and make a donation <Link href='/rewards' passHref><a>here</a></Link>.
</div> </div>
</div> </div>
</div> </div>

View File

@ -7,6 +7,8 @@ import AdvPostForm, { AdvPostInitial, AdvPostSchema } from './adv-post-form'
import { MAX_TITLE_LENGTH, MAX_POLL_CHOICE_LENGTH, MAX_POLL_NUM_CHOICES } from '../lib/constants' import { MAX_TITLE_LENGTH, MAX_POLL_CHOICE_LENGTH, MAX_POLL_NUM_CHOICES } from '../lib/constants'
import TextareaAutosize from 'react-textarea-autosize' import TextareaAutosize from 'react-textarea-autosize'
import FeeButton, { EditFeeButton } from './fee-button' import FeeButton, { EditFeeButton } from './fee-button'
import Delete from './delete'
import { Button } from 'react-bootstrap'
export function PollForm ({ item, editThreshold }) { export function PollForm ({ item, editThreshold }) {
const router = useRouter() const router = useRouter()
@ -94,10 +96,16 @@ export function PollForm ({ item, editThreshold }) {
<AdvPostForm edit={!!item} /> <AdvPostForm edit={!!item} />
<div className='mt-3'> <div className='mt-3'>
{item {item
? <EditFeeButton ? (
paidSats={item.meSats} <div className='d-flex justify-content-between'>
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary' <Delete itemId={item.id} onDelete={() => router.push(`/items/${item.id}`)}>
/> <Button variant='grey-medium'>delete</Button>
</Delete>
<EditFeeButton
paidSats={item.meSats}
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
/>
</div>)
: <FeeButton : <FeeButton
baseFee={1} parentId={null} text='post' baseFee={1} parentId={null} text='post'
ChildButton={SubmitButton} variant='secondary' ChildButton={SubmitButton} variant='secondary'

View File

@ -171,6 +171,8 @@ export default function UpVote ({ item, className }) {
return `${sats} sat${sats > 1 ? 's' : ''}` return `${sats} sat${sats > 1 ? 's' : ''}`
} }
const disabled = item?.mine || fwd2me || item?.deletedAt
const color = getColor(item?.meSats) const color = getColor(item?.meSats)
return ( return (
<LightningConsumer> <LightningConsumer>
@ -182,7 +184,7 @@ export default function UpVote ({ item, className }) {
if (!item) return if (!item) return
// we can't tip ourselves // we can't tip ourselves
if (item?.mine || fwd2me) { if (disabled) {
return return
} }
@ -197,7 +199,7 @@ export default function UpVote ({ item, className }) {
if (!item) return if (!item) return
// we can't tip ourselves // we can't tip ourselves
if (item?.mine || fwd2me) { if (disabled) {
return return
} }
@ -234,9 +236,9 @@ export default function UpVote ({ item, className }) {
}) })
} }
> >
<ActionTooltip notForm disable={item?.mine || fwd2me} overlayText={overlayText()}> <ActionTooltip notForm disable={disabled} overlayText={overlayText()}>
<div <div
className={`${item?.mine || fwd2me ? styles.noSelfTips : ''} className={`${disabled ? styles.noSelfTips : ''}
${styles.upvoteWrapper}`} ${styles.upvoteWrapper}`}
> >
<UpBolt <UpBolt
@ -245,7 +247,7 @@ export default function UpVote ({ item, className }) {
className={ className={
`${styles.upvote} `${styles.upvote}
${className || ''} ${className || ''}
${item?.mine || fwd2me ? styles.noSelfTips : ''} ${disabled ? styles.noSelfTips : ''}
${item?.meSats ? styles.voted : ''}` ${item?.meSats ? styles.voted : ''}`
} }
style={item?.meSats style={item?.meSats

View File

@ -5,6 +5,7 @@ export const COMMENT_FIELDS = gql`
id id
parentId parentId
createdAt createdAt
deletedAt
text text
user { user {
name name

View File

@ -6,6 +6,7 @@ export const ITEM_FIELDS = gql`
id id
parentId parentId
createdAt createdAt
deletedAt
title title
url url
user { user {

View File

@ -1,9 +1,9 @@
export function ignoreClick (e) { export function ignoreClick (e) {
return e.target.onclick || // the target has a click handler return e.target.onclick || // the target has a click handler
// the target has an interactive parent // the target has an interactive parent
e.target.matches(':where(.upvoteParent, form, textarea, button, a, input) :scope') || e.target.matches(':where(.upvoteParent, .pointer, form, textarea, button, a, input) :scope') ||
// the target is an interactive element // the target is an interactive element
['TEXTAREA', 'BUTTON', 'A', 'INPUT', 'FORM'].includes(e.target.tagName.toUpperCase()) || ['TEXTAREA', 'BUTTON', 'A', 'INPUT', 'FORM'].includes(e.target.tagName.toUpperCase()) ||
// the target is an interactive element // the target is an interactive element
e.target.class === 'upvoteParent' e.target.class === 'upvoteParent' || e.target.class === 'pointer'
} }

View File

@ -15,6 +15,7 @@ import { useRouter } from 'next/router'
import Item from '../components/item' import Item from '../components/item'
import Comment from '../components/comment' import Comment from '../components/comment'
import React from 'react' import React from 'react'
import ItemJob from '../components/item-job'
export const getServerSideProps = getGetServerSideProps(WALLET_HISTORY) export const getServerSideProps = getGetServerSideProps(WALLET_HISTORY)
@ -92,7 +93,7 @@ function Detail ({ fact }) {
return ( return (
<> <>
<div className={satusClass(fact.status)}> <div className={satusClass(fact.status)}>
SN distributes the sats it earns back to its best users daily. These sats come from <Link href='/~jobs' passHref><a>jobs</a></Link>, boosts, posting fees, and donations. You can see the daily rewards pool and make a donation <Link href='/rewards' passHref><a>here</a></Link>. SN distributes the sats it earns back to its best users daily. These sats come from <Link href='/~jobs' passHref><a>jobs</a></Link>, boosts, posting fees, and donations. You can see the daily rewards pool and make a donation <Link href='/rewards' passHref><a>here</a></Link>.
</div> </div>
</> </>
) )
@ -128,6 +129,9 @@ function Detail ({ fact }) {
} }
if (fact.item.title) { if (fact.item.title) {
if (fact.item.isJob) {
return <ItemJob className={styles.itemWrapper} item={fact.item} />
}
return <div className={styles.itemWrapper}><Item item={fact.item} /></div> return <div className={styles.itemWrapper}><Item item={fact.item} /></div>
} }

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Item" ADD COLUMN "deletedAt" TIMESTAMP(3);

View File

@ -210,6 +210,7 @@ model Item {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map(name: "created_at") createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @updatedAt @map(name: "updated_at") updatedAt DateTime @default(now()) @updatedAt @map(name: "updated_at")
deletedAt DateTime?
title String? title String?
text String? text String?
url String? url String?

View File

@ -53,7 +53,7 @@ function earn ({ models }) {
FROM FROM
"Item" "Item"
WHERE created_at >= now_utc() - interval '36 hours' WHERE created_at >= now_utc() - interval '36 hours'
AND "weightedVotes" > 0 AND "weightedVotes" > 0 AND "deletedAt" IS NULL AND NOT bio
) x ) x
WHERE x.percentile <= ${TOP_PERCENTILE} WHERE x.percentile <= ${TOP_PERCENTILE}
), ),