From a318727f9cf4f78dea300fb9a62e557c0c932fd7 Mon Sep 17 00:00:00 2001 From: keyan Date: Sun, 19 Nov 2023 14:16:35 -0600 Subject: [PATCH] fix freebie dialog (#460) --- api/resolvers/item.js | 5 +++- api/typeDefs/item.js | 1 + components/bounty-form.js | 12 ++------ components/comment-edit.js | 17 ++---------- components/discussion-form.js | 23 ++-------------- components/fee-button.js | 31 +++++++++++++++++---- components/item-info.js | 2 +- components/job-form.js | 11 ++------ components/link-form.js | 32 ++++++--------------- components/poll-form.js | 22 ++------------- components/post.js | 52 +++++++++++++++++++++++------------ components/reply.js | 30 ++------------------ fragments/items.js | 1 + lib/constants.js | 1 + pages/[name]/index.js | 11 ++------ 15 files changed, 93 insertions(+), 158 deletions(-) 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} /> -
- - - - -
+