diff --git a/api/resolvers/item.js b/api/resolvers/item.js
index 92f69357..cc4f5fcd 100644
--- a/api/resolvers/item.js
+++ b/api/resolvers/item.js
@@ -643,6 +643,9 @@ export default {
if (Number(old.userId) !== Number(me?.id)) {
throw new GraphQLError('item does not belong to you', { extensions: { code: 'FORBIDDEN' } })
}
+ if (old.bio) {
+ throw new GraphQLError('cannot delete bio', { extensions: { code: 'BAD_INPUT' } })
+ }
return await deleteItemByAuthor({ models, id, item: old })
},
@@ -1171,7 +1174,7 @@ export const SELECT =
"Item"."rootId", "Item".upvotes, "Item".company, "Item".location, "Item".remote, "Item"."deletedAt",
"Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", "Item".boost, "Item".msats,
"Item".ncomments, "Item"."commentMsats", "Item"."lastCommentAt", "Item"."weightedVotes",
- "Item"."weightedDownVotes", "Item".freebie, "Item"."otsHash", "Item"."bountyPaidTo",
+ "Item"."weightedDownVotes", "Item".freebie, "Item".bio, "Item"."otsHash", "Item"."bountyPaidTo",
ltree2text("Item"."path") AS "path", "Item"."weightedComments", "Item"."imgproxyUrls"`
function topOrderByWeightedSats (me, models) {
diff --git a/api/typeDefs/item.js b/api/typeDefs/item.js
index d60c3165..e7dd40b1 100644
--- a/api/typeDefs/item.js
+++ b/api/typeDefs/item.js
@@ -94,6 +94,7 @@ export default gql`
meForward: Boolean
outlawed: Boolean!
freebie: Boolean!
+ bio: Boolean!
paidImgLink: Boolean
ncomments: Int!
comments(sort: String): [Item!]!
diff --git a/components/bounty-form.js b/components/bounty-form.js
index 3a3d8de2..808efb93 100644
--- a/components/bounty-form.js
+++ b/components/bounty-form.js
@@ -3,15 +3,14 @@ import { useRouter } from 'next/router'
import { gql, useApolloClient, useMutation } from '@apollo/client'
import Countdown from './countdown'
import AdvPostForm, { AdvPostInitial } from './adv-post-form'
-import FeeButton from './fee-button'
import InputGroup from 'react-bootstrap/InputGroup'
import { bountySchema } from '../lib/validate'
import { SubSelectInitial } from './sub-select-form'
-import CancelButton from './cancel-button'
import { useCallback } from 'react'
import { normalizeForwards } from '../lib/form'
import { MAX_TITLE_LENGTH } from '../lib/constants'
import { useMe } from './me'
+import { ItemButtonBar } from './post'
export function BountyForm ({
item,
@@ -20,7 +19,6 @@ export function BountyForm ({
titleLabel = 'title',
bountyLabel = 'bounty',
textLabel = 'text',
- buttonText = 'post',
handleSubmit,
children
}) {
@@ -133,13 +131,7 @@ export function BountyForm ({
}
/>
-
-
-
-
+
)
}
diff --git a/components/comment-edit.js b/components/comment-edit.js
index 63250d8e..debfcd5c 100644
--- a/components/comment-edit.js
+++ b/components/comment-edit.js
@@ -1,10 +1,9 @@
import { Form, MarkdownInput } from '../components/form'
import { gql, useMutation } from '@apollo/client'
import styles from './reply.module.css'
-import Button from 'react-bootstrap/Button'
-import Delete from './delete'
import { commentSchema } from '../lib/validate'
-import FeeButton, { FeeButtonProvider } from './fee-button'
+import { FeeButtonProvider } from './fee-button'
+import { ItemButtonBar } from './post'
export default function CommentEdit ({ comment, editThreshold, onSuccess, onCancel }) {
const [upsertComment] = useMutation(
@@ -51,17 +50,7 @@ export default function CommentEdit ({ comment, editThreshold, onSuccess, onCanc
autoFocus
required
/>
-
+
diff --git a/components/discussion-form.js b/components/discussion-form.js
index f94c3985..2cb505a7 100644
--- a/components/discussion-form.js
+++ b/components/discussion-form.js
@@ -3,24 +3,21 @@ import { useRouter } from 'next/router'
import { gql, useApolloClient, useLazyQuery, useMutation } from '@apollo/client'
import Countdown from './countdown'
import AdvPostForm, { AdvPostInitial } from './adv-post-form'
-import FeeButton from './fee-button'
import { ITEM_FIELDS } from '../fragments/items'
import AccordianItem from './accordian-item'
import Item from './item'
-import Delete from './delete'
-import Button from 'react-bootstrap/Button'
import { discussionSchema } from '../lib/validate'
import { SubSelectInitial } from './sub-select-form'
-import CancelButton from './cancel-button'
import { useCallback } from 'react'
import { normalizeForwards } from '../lib/form'
import { MAX_TITLE_LENGTH } from '../lib/constants'
import { useMe } from './me'
import useCrossposter from './use-crossposter'
+import { ItemButtonBar } from './post'
export function DiscussionForm ({
item, sub, editThreshold, titleLabel = 'title',
- textLabel = 'text', buttonText = 'post',
+ textLabel = 'text',
handleSubmit, children
}) {
const router = useRouter()
@@ -133,21 +130,7 @@ export function DiscussionForm ({
: null}
/>
-
-
- {item &&
-
router.push(`/items/${item.id}`)}>
-
- }
-
-
-
-
-
-
+
{!item &&
0 ? '' : 'invisible'}`}>
+ you don't have enough sats, so this one is on us
+
+ - Free items have limited visibility until other stackers zap them.
+ - To get fully visibile right away, fund your account with a few sats or earn some on Stacker News.
+
+ >
+ )
+}
+
export default function FeeButton ({ ChildButton = SubmitButton, variant, text, disabled }) {
const me = useMe()
const { lines, total, disabled: ctxDisabled } = useFeeButton()
+ // freebies: there's only a base cost, it's less than 10, and we have less than 10 sats
+ const free = total === lines.baseCost?.modifier(0) &&
+ total <= FREEBIE_BASE_COST_THRESHOLD &&
+ me?.sats < FREEBIE_BASE_COST_THRESHOLD
+ const feeText = free
+ ? 'free'
+ : total > 1
+ ? numWithUnits(total, { abbreviate: false, format: true })
+ : undefined
return (
- {text}{total > 1 && {numWithUnits(total, { abbreviate: false, format: true })}}
+ {text}{feeText && {feeText}}
{!me &&
}
- {total > 1 &&
-
-
- }
+ {(free &&
) ||
+ (total > 1 &&
)}
)
}
diff --git a/components/item-info.js b/components/item-info.js
index aa796e59..35f170de 100644
--- a/components/item-info.js
+++ b/components/item-info.js
@@ -144,7 +144,7 @@ export default function ItemInfo ({
}
{me && !item.meSats && !item.position &&
!item.mine && !item.deletedAt && }
- {item.mine && !item.position && !item.deletedAt &&
+ {item.mine && !item.position && !item.deletedAt && !item.bio &&
}
{me && !item.mine &&
<>
diff --git a/components/job-form.js b/components/job-form.js
index 8da076e1..55564735 100644
--- a/components/job-form.js
+++ b/components/job-form.js
@@ -15,9 +15,8 @@ import Link from 'next/link'
import { usePrice } from './price'
import Avatar from './avatar'
import { jobSchema } from '../lib/validate'
-import CancelButton from './cancel-button'
import { MAX_TITLE_LENGTH } from '../lib/constants'
-import FeeButton from './fee-button'
+import { ItemButtonBar } from './post'
function satsMin2Mo (minute) {
return minute * 30 * 24 * 60
@@ -155,13 +154,7 @@ export default function JobForm ({ item, sub }) {
/>
{item && }
-
-
-
-
+
>
)
diff --git a/components/link-form.js b/components/link-form.js
index 3262d3f2..381042a9 100644
--- a/components/link-form.js
+++ b/components/link-form.js
@@ -7,16 +7,13 @@ import AdvPostForm, { AdvPostInitial } from './adv-post-form'
import { ITEM_FIELDS } from '../fragments/items'
import Item from './item'
import AccordianItem from './accordian-item'
-import FeeButton from './fee-button'
-import Delete from './delete'
-import Button from 'react-bootstrap/Button'
import { linkSchema } from '../lib/validate'
import Moon from '../svgs/moon-fill.svg'
import { SubSelectInitial } from './sub-select-form'
-import CancelButton from './cancel-button'
import { normalizeForwards } from '../lib/form'
import { MAX_TITLE_LENGTH } from '../lib/constants'
import { useMe } from './me'
+import { ItemButtonBar } from './post'
export function LinkForm ({ item, sub, editThreshold, children }) {
const router = useRouter()
@@ -193,26 +190,13 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
minRows={2}
/>
-
-
- {item
- ? (
-
router.push(`/items/${item.id}`)}>
-
- )
- : dupesLoading &&
-
-
-
searching for dupes
-
}
-
-
-
-
-
-
+
+ {!item && dupesLoading &&
+
+
+
searching for dupes
+
}
+
{!item &&
<>
{dupesData?.dupes?.length > 0 &&
diff --git a/components/poll-form.js b/components/poll-form.js
index cda03afe..0a3353a2 100644
--- a/components/poll-form.js
+++ b/components/poll-form.js
@@ -4,15 +4,12 @@ import { gql, useApolloClient, useMutation } from '@apollo/client'
import Countdown from './countdown'
import AdvPostForm, { AdvPostInitial } from './adv-post-form'
import { MAX_POLL_CHOICE_LENGTH, MAX_POLL_NUM_CHOICES, MAX_TITLE_LENGTH } from '../lib/constants'
-import FeeButton from './fee-button'
-import Delete from './delete'
-import Button from 'react-bootstrap/Button'
import { pollSchema } from '../lib/validate'
import { SubSelectInitial } from './sub-select-form'
-import CancelButton from './cancel-button'
import { useCallback } from 'react'
import { normalizeForwards } from '../lib/form'
import { useMe } from './me'
+import { ItemButtonBar } from './post'
export function PollForm ({ item, sub, editThreshold, children }) {
const router = useRouter()
@@ -98,22 +95,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
maxLength={MAX_POLL_CHOICE_LENGTH}
/>
-
-
-
- {item &&
-
router.push(`/items/${item.id}`)}>
-
- }
-
-
-
-
-
-
-
+
)
}
diff --git a/components/post.js b/components/post.js
index 569ea8a6..3aeda9c7 100644
--- a/components/post.js
+++ b/components/post.js
@@ -10,24 +10,10 @@ import { LinkForm } from './link-form'
import { PollForm } from './poll-form'
import { BountyForm } from './bounty-form'
import SubSelect from './sub-select-form'
-import Info from './info'
import { useCallback, useState } from 'react'
-import { FeeButtonProvider, postCommentBaseLineItems, postCommentUseRemoteLineItems } from './fee-button'
-
-function FreebieDialog () {
- return (
-
- you have no sats, so this one is on us
-
-
- - Free posts have limited visibility and are hidden on the recent tab until other stackers zap them.
- - Free posts will not cover posts that cost more than 1 sat.
- - To get fully visibile and unrestricted posts right away, fund your account with a few sats or earn some on Stacker News.
-
-
-
- )
-}
+import FeeButton, { FeeButtonProvider, postCommentBaseLineItems, postCommentUseRemoteLineItems } from './fee-button'
+import Delete from './delete'
+import CancelButton from './cancel-button'
export function PostForm ({ type, sub, children }) {
const me = useMe()
@@ -49,7 +35,6 @@ export function PostForm ({ type, sub, children }) {
setErrorMessage(undefined)} dismissible>
{errorMessage}
}
- {me?.sats < 1 && }
@@ -121,3 +106,34 @@ export default function Post ({ sub }) {
>
)
}
+
+export function ItemButtonBar ({
+ itemId, canDelete = true, disable,
+ className, children, onDelete, onCancel, hasCancel = true,
+ createText = 'post', editText = 'save'
+}) {
+ const router = useRouter()
+
+ return (
+
+
+ {itemId && canDelete &&
+
router.push(`/items/${itemId}`))}
+ >
+
+ }
+ {children}
+
+ {hasCancel && }
+
+
+
+
+ )
+}
diff --git a/components/reply.js b/components/reply.js
index 2c13f0b9..b3fabe27 100644
--- a/components/reply.js
+++ b/components/reply.js
@@ -5,12 +5,12 @@ import { COMMENTS } from '../fragments/comments'
import { useMe } from './me'
import { forwardRef, useCallback, useEffect, useState, useRef, useImperativeHandle } from 'react'
import Link from 'next/link'
-import FeeButton, { FeeButtonProvider, postCommentBaseLineItems, postCommentUseRemoteLineItems } from './fee-button'
+import { FeeButtonProvider, postCommentBaseLineItems, postCommentUseRemoteLineItems } from './fee-button'
import { commentsViewedAfterComment } from '../lib/new-comments'
import { commentSchema } from '../lib/validate'
-import Info from './info'
import { quote } from '../lib/md'
import { COMMENT_DEPTH_LIMIT } from '../lib/constants'
+import { ItemButtonBar } from './post'
export function ReplyOnAnotherPage ({ item }) {
const path = item.path.split('.')
@@ -28,21 +28,6 @@ export function ReplyOnAnotherPage ({ item }) {
)
}
-function FreebieDialog () {
- return (
-
- you have no sats, so this one is on us
-
-
- - Free comments have limited visibility and are listed at the bottom of the comment section until other stackers zap them.
- - Free comments will not cover comments that cost more than 1 sat.
- - To get fully visibile and unrestricted comments right away, fund your account with a few sats or earn some on Stacker News.
-
-
-
- )
-}
-
export default forwardRef(function Reply ({ item, onSuccess, replyOpen, children, placeholder, contentContainerRef }, ref) {
const [reply, setReply] = useState(replyOpen)
const me = useMe()
@@ -205,18 +190,9 @@ export default forwardRef(function Reply ({ item, onSuccess, replyOpen, children
autoFocus={!replyOpen}
required
placeholder={placeholder}
- hint={me?.sats < 1 && }
innerRef={replyInput}
/>
-
+
}
diff --git a/fragments/items.js b/fragments/items.js
index 6a46065b..48623924 100644
--- a/fragments/items.js
+++ b/fragments/items.js
@@ -33,6 +33,7 @@ export const ITEM_FIELDS = gql`
meForward
outlawed
freebie
+ bio
ncomments
commentSats
lastCommentAt
diff --git a/lib/constants.js b/lib/constants.js
index a6a1dec3..af92d894 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -57,6 +57,7 @@ export const MAX_FORWARDS = 5
export const LNURLP_COMMENT_MAX_LENGTH = 1000
export const RESERVED_MAX_USER_ID = 615
export const GLOBAL_SEED = 616
+export const FREEBIE_BASE_COST_THRESHOLD = 10
export const FOUND_BLURBS = [
'The harsh frontier is no place for the unprepared. This hat will protect you from the sun, dust, and other elements Mother Nature throws your way.',
diff --git a/pages/[name]/index.js b/pages/[name]/index.js
index 01cdc6e6..6c267a0e 100644
--- a/pages/[name]/index.js
+++ b/pages/[name]/index.js
@@ -10,11 +10,11 @@ import { useMe } from '../../components/me'
import { USER_FULL } from '../../fragments/users'
import { ITEM_FIELDS } from '../../fragments/items'
import { getGetServerSideProps } from '../../api/ssrApollo'
-import FeeButton, { FeeButtonProvider } from '../../components/fee-button'
+import { FeeButtonProvider } from '../../components/fee-button'
import { bioSchema } from '../../lib/validate'
-import CancelButton from '../../components/cancel-button'
import { useRouter } from 'next/router'
import PageLoading from '../../components/page-loading'
+import { ItemButtonBar } from '../../components/post'
export const getServerSideProps = getGetServerSideProps({
query: USER_FULL,
@@ -68,12 +68,7 @@ export function BioForm ({ handleDone, bio }) {
name='bio'
minRows={6}
/>
-
-
-
-
-
-
+