import { useEffect, useContext, createContext, useState, useCallback, useMemo } from 'react' import Table from 'react-bootstrap/Table' import ActionTooltip from './action-tooltip' import Info from './info' import styles from './fee-button.module.css' import { gql, useQuery } from '@apollo/client' import { ANON_FEE_MULTIPLIER, SSR } from '@/lib/constants' import { numWithUnits } from '@/lib/format' import { useMe } from './me' import AnonIcon from '@/svgs/spy-fill.svg' import { useShowModal } from './modal' import Link from 'next/link' import { SubmitButton } from './form' const FeeButtonContext = createContext() export function postCommentBaseLineItems ({ baseCost = 1, comment = false, allowFreebies = true, me }) { const anonCharge = me ? {} : { anonCharge: { term: `x ${ANON_FEE_MULTIPLIER}`, label: 'anon mult', modifier: (cost) => cost * ANON_FEE_MULTIPLIER } } return { baseCost: { term: baseCost, label: `${comment ? 'comment' : 'post'} cost`, modifier: (cost) => cost + baseCost, allowFreebies }, ...anonCharge } } export function postCommentUseRemoteLineItems ({ parentId } = {}) { const query = parentId ? gql`{ itemRepetition(parentId: "${parentId}") }` : gql`{ itemRepetition }` return function useRemoteLineItems () { const [line, setLine] = useState({}) const { data } = useQuery(query, SSR ? {} : { pollInterval: 1000, nextFetchPolicy: 'cache-and-network' }) useEffect(() => { const repetition = data?.itemRepetition if (!repetition) return setLine({}) setLine({ itemRepetition: { term: <>x 10<sup>{repetition}</sup></>, label: <>{repetition} {parentId ? 'repeat or self replies' : 'posts'} in 10m</>, modifier: (cost) => cost * Math.pow(10, repetition) } }) }, [data?.itemRepetition]) return line } } export function FeeButtonProvider ({ baseLineItems = {}, useRemoteLineItems = () => null, children }) { const [lineItems, setLineItems] = useState({}) const [disabled, setDisabled] = useState(false) const remoteLineItems = useRemoteLineItems() const mergeLineItems = useCallback((newLineItems) => { setLineItems(lineItems => ({ ...lineItems, ...newLineItems })) }, [setLineItems]) const value = useMemo(() => { const lines = { ...baseLineItems, ...lineItems, ...remoteLineItems } return { lines, merge: mergeLineItems, total: Object.values(lines).reduce((acc, { modifier }) => modifier(acc), 0), disabled, setDisabled } }, [baseLineItems, lineItems, remoteLineItems, mergeLineItems, disabled, setDisabled]) return ( <FeeButtonContext.Provider value={value}> {children} </FeeButtonContext.Provider> ) } export function useFeeButton () { const context = useContext(FeeButtonContext) return context } function FreebieDialog () { return ( <> <div className='fw-bold'>you don't have enough sats, so this one is on us</div> <ul className='mt-2'> <li>Free items have limited visibility until other stackers zap them.</li> <li>To get fully visible right away, fund your account with a few sats or earn some on Stacker News.</li> </ul> </> ) } 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 and we don't have enough sats const free = total === lines.baseCost?.modifier(0) && lines.baseCost?.allowFreebies && me?.privates?.sats < total const feeText = free ? 'free' : total > 1 ? numWithUnits(total, { abbreviate: false, format: true }) : undefined disabled ||= ctxDisabled return ( <div className={styles.feeButton}> <ActionTooltip overlayText={feeText}> <ChildButton variant={variant} disabled={disabled} nonDisabledText={feeText}>{text}</ChildButton> </ActionTooltip> {!me && <AnonInfo />} {(free && <Info><FreebieDialog /></Info>) || (total > 1 && <Info><Receipt lines={lines} total={total} /></Info>)} </div> ) } function Receipt ({ lines, total }) { return ( <Table className={styles.receipt} borderless size='sm'> <tbody> {Object.entries(lines).map(([key, { term, label, omit }]) => ( !omit && <tr key={key}> <td>{term}</td> <td align='right' className='font-weight-light'>{label}</td> </tr>))} </tbody> <tfoot> <tr> <td className='fw-bold'>{numWithUnits(total, { abbreviate: false, format: true })}</td> <td align='right' className='font-weight-light'>total fee</td> </tr> </tfoot> </Table> ) } function AnonInfo () { const showModal = useShowModal() return ( <AnonIcon className='ms-2 fill-theme-color' height={22} width={22} onClick={ (e) => showModal(onClose => <div><div className='fw-bold text-center'>You are posting without an account</div> <ol className='my-3'> <li>You'll pay by invoice</li> <li>Your content will be content-joined (get it?!) under the <Link href='/anon' target='_blank'>@anon</Link> account</li> <li>Any sats your content earns will go toward <Link href='/rewards' target='_blank'>rewards</Link></li> <li>We won't be able to notify you when you receive replies</li> </ol> <small className='text-center fst-italic text-muted'>btw if you don't need to be anonymous, posting is cheaper with an account</small> </div>) } /> ) }