Compare commits
5 Commits
18fbd17025
...
450c969dfc
Author | SHA1 | Date | |
---|---|---|---|
|
450c969dfc | ||
|
1641b58e55 | ||
|
d7667f7820 | ||
|
76218dccac | ||
|
b82641d1bd |
@ -13,7 +13,13 @@ export async function getCost ({ id, boost = 0, uploadIds }, { me, models }) {
|
|||||||
// or more boost
|
// or more boost
|
||||||
const old = await models.item.findUnique({ where: { id: parseInt(id) } })
|
const old = await models.item.findUnique({ where: { id: parseInt(id) } })
|
||||||
const { totalFeesMsats } = await uploadFees(uploadIds, { models, me })
|
const { totalFeesMsats } = await uploadFees(uploadIds, { models, me })
|
||||||
return BigInt(totalFeesMsats) + satsToMsats(boost - old.boost)
|
const cost = BigInt(totalFeesMsats) + satsToMsats(boost - old.boost)
|
||||||
|
|
||||||
|
if (cost > 0 && old.invoiceActionState && old.invoiceActionState !== 'PAID') {
|
||||||
|
throw new Error('creation invoice not paid')
|
||||||
|
}
|
||||||
|
|
||||||
|
return cost
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function perform (args, context) {
|
export async function perform (args, context) {
|
||||||
|
@ -1328,10 +1328,6 @@ export const updateItem = async (parent, { sub: subName, forward, hash, hmac, ..
|
|||||||
throw new GqlInputError('item is deleted')
|
throw new GqlInputError('item is deleted')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (old.invoiceActionState && old.invoiceActionState !== 'PAID') {
|
|
||||||
throw new GqlInputError('cannot edit unpaid item')
|
|
||||||
}
|
|
||||||
|
|
||||||
// author can edit their own item (except anon)
|
// author can edit their own item (except anon)
|
||||||
const meId = Number(me?.id ?? USER_ID.anon)
|
const meId = Number(me?.id ?? USER_ID.anon)
|
||||||
const authorEdit = !!me && Number(old.userId) === meId
|
const authorEdit = !!me && Number(old.userId) === meId
|
||||||
|
@ -49,7 +49,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const upload = await models.upload.create({ data: { ...fileParams } })
|
const upload = await models.upload.create({ data: { ...fileParams } })
|
||||||
return createPresignedPost({ key: String(upload.id), type, size })
|
|
||||||
|
const extension = type.split('/')[1]
|
||||||
|
const key = `${upload.id}.${extension}`
|
||||||
|
return createPresignedPost({ key, type, size })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { usePaidMutation } from './use-paid-mutation'
|
|||||||
import { ACT_MUTATION } from '@/fragments/paidAction'
|
import { ACT_MUTATION } from '@/fragments/paidAction'
|
||||||
import { meAnonSats } from '@/lib/apollo'
|
import { meAnonSats } from '@/lib/apollo'
|
||||||
import { BoostItemInput } from './adv-post-form'
|
import { BoostItemInput } from './adv-post-form'
|
||||||
|
import { useWallet } from '../wallets'
|
||||||
|
|
||||||
const defaultTips = [100, 1000, 10_000, 100_000]
|
const defaultTips = [100, 1000, 10_000, 100_000]
|
||||||
|
|
||||||
@ -250,12 +251,12 @@ export function useAct ({ query = ACT_MUTATION, ...options } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useZap () {
|
export function useZap () {
|
||||||
|
const wallet = useWallet()
|
||||||
const act = useAct()
|
const act = useAct()
|
||||||
const { me } = useMe()
|
|
||||||
const strike = useLightning()
|
const strike = useLightning()
|
||||||
const toaster = useToast()
|
const toaster = useToast()
|
||||||
|
|
||||||
return useCallback(async ({ item, abortSignal }) => {
|
return useCallback(async ({ item, me, abortSignal }) => {
|
||||||
const meSats = (item?.meSats || 0)
|
const meSats = (item?.meSats || 0)
|
||||||
|
|
||||||
// add current sats to next tip since idempotent zaps use desired total zap not difference
|
// add current sats to next tip since idempotent zaps use desired total zap not difference
|
||||||
@ -267,7 +268,8 @@ export function useZap () {
|
|||||||
try {
|
try {
|
||||||
await abortSignal.pause({ me, amount: sats })
|
await abortSignal.pause({ me, amount: sats })
|
||||||
strike()
|
strike()
|
||||||
const { error } = await act({ variables, optimisticResponse })
|
// batch zaps if wallet is enabled or using fee credits so they can be executed serially in a single request
|
||||||
|
const { error } = await act({ variables, optimisticResponse, context: { batch: !!wallet || me?.privates?.sats > sats } })
|
||||||
if (error) throw error
|
if (error) throw error
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ActCanceledError) {
|
if (error instanceof ActCanceledError) {
|
||||||
@ -277,7 +279,7 @@ export function useZap () {
|
|||||||
const reason = error?.message || error?.toString?.()
|
const reason = error?.message || error?.toString?.()
|
||||||
toaster.danger(reason)
|
toaster.danger(reason)
|
||||||
}
|
}
|
||||||
}, [act, me?.id, strike])
|
}, [act, toaster, strike, !!wallet])
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ActCanceledError extends Error {
|
export class ActCanceledError extends Error {
|
||||||
|
@ -65,6 +65,29 @@ export default function ItemInfo ({
|
|||||||
const meSats = (me ? item.meSats : item.meAnonSats) || 0
|
const meSats = (me ? item.meSats : item.meAnonSats) || 0
|
||||||
|
|
||||||
const EditInfo = () => {
|
const EditInfo = () => {
|
||||||
|
if (canEdit) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span> \ </span>
|
||||||
|
<span
|
||||||
|
className='text-reset pointer fw-bold'
|
||||||
|
onClick={() => onEdit ? onEdit() : router.push(`/items/${item.id}/edit`)}
|
||||||
|
>
|
||||||
|
<span>{editText || 'edit'} </span>
|
||||||
|
{(!item.invoice?.actionState || item.invoice?.actionState === 'PAID') &&
|
||||||
|
<Countdown
|
||||||
|
date={editThreshold}
|
||||||
|
onComplete={() => { setCanEdit(false) }}
|
||||||
|
/>}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const PaymentInfo = () => {
|
||||||
const waitForQrPayment = useQrPayment()
|
const waitForQrPayment = useQrPayment()
|
||||||
if (item.deletedAt) return null
|
if (item.deletedAt) return null
|
||||||
|
|
||||||
@ -90,18 +113,6 @@ export default function ItemInfo ({
|
|||||||
)
|
)
|
||||||
onClick = () => waitForQrPayment({ id: item.invoice?.id }, null, { cancelOnClose: false }).catch(console.error)
|
onClick = () => waitForQrPayment({ id: item.invoice?.id }, null, { cancelOnClose: false }).catch(console.error)
|
||||||
}
|
}
|
||||||
} else if (canEdit) {
|
|
||||||
Component = () => (
|
|
||||||
<>
|
|
||||||
<span>{editText || 'edit'} </span>
|
|
||||||
<Countdown
|
|
||||||
date={editThreshold}
|
|
||||||
onComplete={() => {
|
|
||||||
setCanEdit(false)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>)
|
|
||||||
onClick = () => onEdit ? onEdit() : router.push(`/items/${item.id}/edit`)
|
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -207,6 +218,7 @@ export default function ItemInfo ({
|
|||||||
showActionDropdown &&
|
showActionDropdown &&
|
||||||
<>
|
<>
|
||||||
<EditInfo />
|
<EditInfo />
|
||||||
|
<PaymentInfo />
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<CopyLinkDropdownItem item={item} />
|
<CopyLinkDropdownItem item={item} />
|
||||||
<InfoDropdownItem item={item} />
|
<InfoDropdownItem item={item} />
|
||||||
|
@ -5,6 +5,7 @@ import Link from 'next/link'
|
|||||||
import { LoginButtons, LogoutDropdownItem, NavWalletSummary } from '../common'
|
import { LoginButtons, LogoutDropdownItem, NavWalletSummary } from '../common'
|
||||||
import AnonIcon from '@/svgs/spy-fill.svg'
|
import AnonIcon from '@/svgs/spy-fill.svg'
|
||||||
import styles from './footer.module.css'
|
import styles from './footer.module.css'
|
||||||
|
import canvasStyles from './offcanvas.module.css'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
export default function OffCanvas ({ me, dropNavKey }) {
|
export default function OffCanvas ({ me, dropNavKey }) {
|
||||||
@ -28,7 +29,7 @@ export default function OffCanvas ({ me, dropNavKey }) {
|
|||||||
<>
|
<>
|
||||||
<MeImage onClick={handleShow} />
|
<MeImage onClick={handleShow} />
|
||||||
|
|
||||||
<Offcanvas style={{ maxWidth: '250px', zIndex: '10000' }} show={show} onHide={handleClose} placement='end'>
|
<Offcanvas className={canvasStyles.offcanvas} show={show} onHide={handleClose} placement='end'>
|
||||||
<Offcanvas.Header closeButton>
|
<Offcanvas.Header closeButton>
|
||||||
<Offcanvas.Title><NavWalletSummary /></Offcanvas.Title>
|
<Offcanvas.Title><NavWalletSummary /></Offcanvas.Title>
|
||||||
</Offcanvas.Header>
|
</Offcanvas.Header>
|
||||||
|
15
components/nav/mobile/offcanvas.module.css
Normal file
15
components/nav/mobile/offcanvas.module.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.offcanvas {
|
||||||
|
max-width: 250px;
|
||||||
|
z-index: 10000;
|
||||||
|
right: -100%;
|
||||||
|
animation: slide ease-out 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide {
|
||||||
|
0% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0%)
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,27 @@
|
|||||||
.toastContainer {
|
.toastContainer {
|
||||||
transform: translate3d(0, 0, 0);
|
transform: translate3d(0, 0, 0);
|
||||||
|
display: grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
width: auto;
|
font-size: small;
|
||||||
|
width: fit-content;
|
||||||
|
justify-self: right;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
bottom: -100%;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
animation: slide ease-out 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide {
|
||||||
|
0% {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0%)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.success {
|
.success {
|
||||||
@ -70,9 +84,3 @@
|
|||||||
.toastClose:hover {
|
.toastClose:hover {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 400px) {
|
|
||||||
.toast {
|
|
||||||
width: var(--bs-toast-max-width);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,5 @@
|
|||||||
import { ApolloClient, InMemoryCache, HttpLink, makeVar } from '@apollo/client'
|
import { ApolloClient, InMemoryCache, HttpLink, makeVar, split } from '@apollo/client'
|
||||||
|
import { BatchHttpLink } from '@apollo/client/link/batch-http'
|
||||||
import { decodeCursor, LIMIT } from './cursor'
|
import { decodeCursor, LIMIT } from './cursor'
|
||||||
import { SSR } from './constants'
|
import { SSR } from './constants'
|
||||||
|
|
||||||
@ -28,8 +29,15 @@ export default function getApolloClient () {
|
|||||||
export const meAnonSats = {}
|
export const meAnonSats = {}
|
||||||
|
|
||||||
function getClient (uri) {
|
function getClient (uri) {
|
||||||
|
const link = split(
|
||||||
|
// batch zaps if wallet is enabled so they can be executed serially in a single request
|
||||||
|
operation => operation.operationName === 'act' && operation.variables.act === 'TIP' && operation.getContext().batch,
|
||||||
|
new BatchHttpLink({ uri, batchInterval: 1000, batchDebounce: true, batchMax: 0, batchKey: op => op.variables.id }),
|
||||||
|
new HttpLink({ uri })
|
||||||
|
)
|
||||||
|
|
||||||
return new ApolloClient({
|
return new ApolloClient({
|
||||||
link: new HttpLink({ uri }),
|
link,
|
||||||
ssrMode: SSR,
|
ssrMode: SSR,
|
||||||
connectToDevTools: process.env.NODE_ENV !== 'production',
|
connectToDevTools: process.env.NODE_ENV !== 'production',
|
||||||
cache: new InMemoryCache({
|
cache: new InMemoryCache({
|
||||||
|
@ -16,6 +16,7 @@ const apolloServer = new ApolloServer({
|
|||||||
typeDefs,
|
typeDefs,
|
||||||
resolvers,
|
resolvers,
|
||||||
introspection: true,
|
introspection: true,
|
||||||
|
allowBatchedHttpRequests: true,
|
||||||
plugins: [{
|
plugins: [{
|
||||||
requestDidStart (initialRequestContext) {
|
requestDidStart (initialRequestContext) {
|
||||||
return {
|
return {
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Item_boost_idx" ON "Item"("boost");
|
@ -532,6 +532,7 @@ model Item {
|
|||||||
@@index([invoiceActionState])
|
@@index([invoiceActionState])
|
||||||
@@index([cost])
|
@@index([cost])
|
||||||
@@index([url])
|
@@index([url])
|
||||||
|
@@index([boost])
|
||||||
}
|
}
|
||||||
|
|
||||||
// we use this to denormalize a user's aggregated interactions (zaps) with an item
|
// we use this to denormalize a user's aggregated interactions (zaps) with an item
|
||||||
|
Loading…
x
Reference in New Issue
Block a user