Use module path aliases (#938)

* Use module path aliases

* fix broken refactor

* path mapping for svgs, style, and remaining places (bonus: lose babel dep)

---------

Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
This commit is contained in:
ekzyis 2024-03-20 01:37:31 +01:00 committed by GitHub
parent 22ff832efb
commit d237861ff5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
171 changed files with 1663 additions and 927 deletions

View File

@ -1,20 +0,0 @@
{
"presets": [
"next/babel"
],
"plugins": [
[
"inline-react-svg",
{
"svgo": {
"plugins": [
{
"name": "removeViewBox",
"active": false
}
]
}
}
]
]
}

View File

@ -1,5 +1,5 @@
import lndService from 'ln-service'
import lnd from '../lnd'
import lnd from '@/api/lnd'
const cache = new Map()
const expiresIn = 1000 * 30 // 30 seconds in milliseconds

View File

@ -1,5 +1,5 @@
import lndService from 'ln-service'
import lnd from '../lnd'
import lnd from '@/api/lnd'
const cache = new Map()
const expiresIn = 1000 * 30 // 30 seconds in milliseconds

View File

@ -1,4 +1,4 @@
import { timeUnitForRange, whenRange } from '../../lib/time'
import { timeUnitForRange, whenRange } from '@/lib/time'
export function withClause (range) {
const unit = timeUnitForRange(range)

View File

@ -1,5 +1,5 @@
import { ANON_USER_ID, AWS_S3_URL_REGEXP } from '../../lib/constants'
import { msatsToSats } from '../../lib/format'
import { ANON_USER_ID, AWS_S3_URL_REGEXP } from '@/lib/constants'
import { msatsToSats } from '@/lib/format'
export default {
Query: {

View File

@ -1,6 +1,6 @@
import { GraphQLError } from 'graphql'
import { inviteSchema, ssValidate } from '../../lib/validate'
import { msatsToSats } from '../../lib/format'
import { inviteSchema, ssValidate } from '@/lib/validate'
import { msatsToSats } from '@/lib/format'
export default {
Query: {

View File

@ -1,23 +1,23 @@
import { GraphQLError } from 'graphql'
import { ensureProtocol, removeTracking, stripTrailingSlash } from '../../lib/url'
import { ensureProtocol, removeTracking, stripTrailingSlash } from '@/lib/url'
import serialize, { serializeInvoicable } from './serial'
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor'
import { getMetadata, metadataRuleSets } from 'page-metadata-parser'
import { ruleSet as publicationDateRuleSet } from '../../lib/timedate-scraper'
import { ruleSet as publicationDateRuleSet } from '@/lib/timedate-scraper'
import domino from 'domino'
import {
ITEM_SPAM_INTERVAL, ITEM_FILTER_THRESHOLD,
COMMENT_DEPTH_LIMIT, COMMENT_TYPE_QUERY,
ANON_USER_ID, ANON_ITEM_SPAM_INTERVAL, POLL_COST,
ITEM_ALLOW_EDITS, GLOBAL_SEED, ANON_FEE_MULTIPLIER, NOFOLLOW_LIMIT, UNKNOWN_LINK_REL
} from '../../lib/constants'
import { msatsToSats } from '../../lib/format'
} from '@/lib/constants'
import { msatsToSats } from '@/lib/format'
import { parse } from 'tldts'
import uu from 'url-unshort'
import { actSchema, advSchema, bountySchema, commentSchema, discussionSchema, jobSchema, linkSchema, pollSchema, ssValidate } from '../../lib/validate'
import { notifyItemParents, notifyUserSubscribers, notifyZapped, notifyTerritorySubscribers, notifyMention } from '../../lib/webPush'
import { defaultCommentSort, isJob, deleteItemByAuthor, getDeleteCommand, hasDeleteCommand } from '../../lib/item'
import { datePivot, whenRange } from '../../lib/time'
import { actSchema, advSchema, bountySchema, commentSchema, discussionSchema, jobSchema, linkSchema, pollSchema, ssValidate } from '@/lib/validate'
import { notifyItemParents, notifyUserSubscribers, notifyZapped, notifyTerritorySubscribers, notifyMention } from '@/lib/webPush'
import { defaultCommentSort, isJob, deleteItemByAuthor, getDeleteCommand, hasDeleteCommand } from '@/lib/item'
import { datePivot, whenRange } from '@/lib/time'
import { imageFeesInfo, uploadIdsFromText } from './image'
import assertGofacYourself from './ofac'

View File

@ -1,9 +1,9 @@
import { GraphQLError } from 'graphql'
import { decodeCursor, LIMIT, nextNoteCursorEncoded } from '../../lib/cursor'
import { decodeCursor, LIMIT, nextNoteCursorEncoded } from '@/lib/cursor'
import { getItem, filterClause, whereClause, muteClause } from './item'
import { getInvoice } from './wallet'
import { pushSubscriptionSchema, ssValidate } from '../../lib/validate'
import { replyToSubscription } from '../../lib/webPush'
import { pushSubscriptionSchema, ssValidate } from '@/lib/validate'
import { replyToSubscription } from '@/lib/webPush'
import { getSub } from './sub'
export default {

View File

@ -1,6 +1,6 @@
import { GraphQLError } from 'graphql'
import { withClause, intervalClause } from './growth'
import { timeUnitForRange, whenRange } from '../../lib/time'
import { timeUnitForRange, whenRange } from '@/lib/time'
export default {
Query: {

View File

@ -1,7 +1,7 @@
import { GraphQLError } from 'graphql'
import { amountSchema, ssValidate } from '../../lib/validate'
import { amountSchema, ssValidate } from '@/lib/validate'
import { serializeInvoicable } from './serial'
import { ANON_USER_ID } from '../../lib/constants'
import { ANON_USER_ID } from '@/lib/constants'
import { getItem } from './item'
import { topUsers } from './user'

View File

@ -1,5 +1,5 @@
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
import { whenToFrom } from '../../lib/time'
import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor'
import { whenToFrom } from '@/lib/time'
import { getItem, itemQueryWithMeta, SELECT } from './item'
function queryParts (q) {

View File

@ -3,8 +3,8 @@ import retry from 'async-retry'
import Prisma from '@prisma/client'
import { settleHodlInvoice } from 'ln-service'
import { createHmac } from './wallet'
import { msatsToSats, numWithUnits } from '../../lib/format'
import { BALANCE_LIMIT_MSATS } from '../../lib/constants'
import { msatsToSats, numWithUnits } from '@/lib/format'
import { BALANCE_LIMIT_MSATS } from '@/lib/constants'
export default async function serialize (models, ...calls) {
return await retry(async bail => {

View File

@ -1,13 +1,12 @@
import { GraphQLError } from 'graphql'
import { serializeInvoicable } from './serial'
import { TERRITORY_COST_MONTHLY, TERRITORY_COST_ONCE, TERRITORY_COST_YEARLY, TERRITORY_PERIOD_COST } from '../../lib/constants'
import { datePivot, whenRange } from '../../lib/time'
import { ssValidate, territorySchema } from '../../lib/validate'
import { nextBilling, proratedBillingCost } from '../../lib/territory'
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
import { TERRITORY_COST_MONTHLY, TERRITORY_COST_ONCE, TERRITORY_COST_YEARLY, TERRITORY_PERIOD_COST } from '@/lib/constants'
import { datePivot, whenRange } from '@/lib/time'
import { ssValidate, territorySchema } from '@/lib/validate'
import { nextBilling, proratedBillingCost } from '@/lib/territory'
import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor'
import { subViewGroup } from './growth'
import { notifyTerritoryTransfer } from '../../lib/webPush'
import { notifyTerritoryTransfer } from '@/lib/webPush'
export function paySubQueries (sub, models) {
if (sub.billingType === 'ONCE') {
return []

View File

@ -1,6 +1,6 @@
import { GraphQLError } from 'graphql'
import { ANON_USER_ID, IMAGE_PIXELS_MAX, UPLOAD_SIZE_MAX, UPLOAD_SIZE_MAX_AVATAR, UPLOAD_TYPES_ALLOW } from '../../lib/constants'
import { createPresignedPost } from '../s3'
import { ANON_USER_ID, IMAGE_PIXELS_MAX, UPLOAD_SIZE_MAX, UPLOAD_SIZE_MAX_AVATAR, UPLOAD_TYPES_ALLOW } from '@/lib/constants'
import { createPresignedPost } from '@/api/s3'
export default {
Mutation: {

View File

@ -1,13 +1,13 @@
import { readFile } from 'fs/promises'
import { join, resolve } from 'path'
import { GraphQLError } from 'graphql'
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
import { msatsToSats } from '../../lib/format'
import { bioSchema, emailSchema, settingsSchema, ssValidate, userSchema } from '../../lib/validate'
import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor'
import { msatsToSats } from '@/lib/format'
import { bioSchema, emailSchema, settingsSchema, ssValidate, userSchema } from '@/lib/validate'
import { getItem, updateItem, filterClause, createItem, whereClause, muteClause } from './item'
import { ANON_USER_ID, DELETE_USER_ID, RESERVED_MAX_USER_ID, SN_USER_IDS } from '../../lib/constants'
import { ANON_USER_ID, DELETE_USER_ID, RESERVED_MAX_USER_ID, SN_USER_IDS } from '@/lib/constants'
import { viewGroup } from './growth'
import { whenRange } from '../../lib/time'
import { whenRange } from '@/lib/time'
const contributors = new Set()

View File

@ -2,14 +2,14 @@ import { getIdentity, createHodlInvoice, createInvoice, decodePaymentRequest, pa
import { GraphQLError } from 'graphql'
import crypto from 'crypto'
import serialize from './serial'
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor'
import lnpr from 'bolt11'
import { SELECT } from './item'
import { lnAddrOptions } from '../../lib/lnurl'
import { msatsToSats, msatsToSatsDecimal, ensureB64 } from '../../lib/format'
import { LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '../../lib/validate'
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, ANON_USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT } from '../../lib/constants'
import { datePivot } from '../../lib/time'
import { lnAddrOptions } from '@/lib/lnurl'
import { msatsToSats, msatsToSatsDecimal, ensureB64 } from '@/lib/format'
import { LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '@/lib/validate'
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, ANON_USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT } from '@/lib/constants'
import { datePivot } from '@/lib/time'
import assertGofacYourself from './ofac'
export async function getInvoice (parent, { id }, { me, models, lnd }) {

View File

@ -1,5 +1,5 @@
import AWS from 'aws-sdk'
import { MEDIA_URL } from '../../lib/constants'
import { MEDIA_URL } from '@/lib/constants'
const bucketRegion = 'us-east-1'
const Bucket = process.env.NEXT_PUBLIC_AWS_UPLOAD_BUCKET

View File

@ -7,12 +7,12 @@ import models from './models'
import { print } from 'graphql'
import lnd from './lnd'
import search from './search'
import { ME } from '../fragments/users'
import { PRICE } from '../fragments/price'
import { BLOCK_HEIGHT } from '../fragments/blockHeight'
import { CHAIN_FEE } from '../fragments/chainFee'
import { ME } from '@/fragments/users'
import { PRICE } from '@/fragments/price'
import { BLOCK_HEIGHT } from '@/fragments/blockHeight'
import { CHAIN_FEE } from '@/fragments/chainFee'
import { getServerSession } from 'next-auth/next'
import { getAuthOptions } from '../pages/api/auth/[...nextauth]'
import { getAuthOptions } from '@/pages/api/auth/[...nextauth]'
export default async function getSSRApolloClient ({ req, res, me = null }) {
const session = req && await getServerSession(req, res, getAuthOptions(req))

114
api/webPush/index.js Normal file
View File

@ -0,0 +1,114 @@
import webPush from 'web-push'
import models from '@/api/models'
import { COMMENT_DEPTH_LIMIT } from '@/lib/constants'
import removeMd from 'remove-markdown'
const webPushEnabled = process.env.NODE_ENV === 'production' ||
(process.env.VAPID_MAILTO && process.env.NEXT_PUBLIC_VAPID_PUBKEY && process.env.VAPID_PRIVKEY)
if (webPushEnabled) {
webPush.setVapidDetails(
process.env.VAPID_MAILTO,
process.env.NEXT_PUBLIC_VAPID_PUBKEY,
process.env.VAPID_PRIVKEY
)
} else {
console.warn('VAPID_* env vars not set, skipping webPush setup')
}
const createPayload = (notification) => {
// https://web.dev/push-notifications-display-a-notification/#visual-options
let { title, body, ...options } = notification
if (body) body = removeMd(body)
return JSON.stringify({
title,
options: {
body,
timestamp: Date.now(),
icon: '/icons/icon_x96.png',
...options
}
})
}
const createUserFilter = (tag) => {
// filter users by notification settings
const tagMap = {
REPLY: 'noteAllDescendants',
MENTION: 'noteMentions',
TIP: 'noteItemSats',
FORWARDEDTIP: 'noteForwardedSats',
REFERRAL: 'noteInvites',
INVITE: 'noteInvites',
EARN: 'noteEarning',
DEPOSIT: 'noteDeposits',
STREAK: 'noteCowboyHat'
}
const key = tagMap[tag.split('-')[0]]
return key ? { user: { [key]: true } } : undefined
}
const createItemUrl = async ({ id }) => {
const [rootItem] = await models.$queryRawUnsafe(
'SELECT subpath(path, -LEAST(nlevel(path), $1::INTEGER), 1)::text AS id FROM "Item" WHERE id = $2::INTEGER',
COMMENT_DEPTH_LIMIT + 1, Number(id)
)
return `/items/${rootItem.id}` + (rootItem.id !== id ? `?commentId=${id}` : '')
}
const sendNotification = (subscription, payload) => {
if (!webPushEnabled) {
console.warn('webPush not configured. skipping notification')
return
}
const { id, endpoint, p256dh, auth } = subscription
return webPush.sendNotification({ endpoint, keys: { p256dh, auth } }, payload)
.catch(async (err) => {
if (err.statusCode === 400) {
console.log('[webPush] invalid request: ', err)
} else if ([401, 403].includes(err.statusCode)) {
console.log('[webPush] auth error: ', err)
} else if (err.statusCode === 404 || err.statusCode === 410) {
console.log('[webPush] subscription has expired or is no longer valid: ', err)
const deletedSubscripton = await models.pushSubscription.delete({ where: { id } })
console.log(`[webPush] deleted subscription ${id} of user ${deletedSubscripton.userId} due to push error`)
} else if (err.statusCode === 413) {
console.log('[webPush] payload too large: ', err)
} else if (err.statusCode === 429) {
console.log('[webPush] too many requests: ', err)
} else {
console.log('[webPush] error: ', err)
}
})
}
export async function sendUserNotification (userId, notification) {
try {
notification.data ??= {}
if (notification.item) {
notification.data.url ??= await createItemUrl(notification.item)
notification.data.itemId ??= notification.item.id
delete notification.item
}
const userFilter = createUserFilter(notification.tag)
const payload = createPayload(notification)
const subscriptions = await models.pushSubscription.findMany({
where: { userId, ...userFilter }
})
await Promise.allSettled(
subscriptions.map(subscription => sendNotification(subscription, payload))
)
} catch (err) {
console.log('[webPush] error sending user notification: ', err)
}
}
export async function replyToSubscription (subscriptionId, notification) {
try {
const payload = createPayload(notification)
const subscription = await models.pushSubscription.findUnique({ where: { id: subscriptionId } })
await sendNotification(subscription, payload)
} catch (err) {
console.log('[webPush] error sending subscription reply: ', err)
}
}

View File

@ -1,8 +1,8 @@
import Accordion from 'react-bootstrap/Accordion'
import AccordionContext from 'react-bootstrap/AccordionContext'
import { useAccordionButton } from 'react-bootstrap/AccordionButton'
import ArrowRight from '../svgs/arrow-right-s-fill.svg'
import ArrowDown from '../svgs/arrow-down-s-fill.svg'
import ArrowRight from '@/svgs/arrow-right-s-fill.svg'
import ArrowDown from '@/svgs/arrow-down-s-fill.svg'
import { useContext } from 'react'
function ContextAwareToggle ({ children, headerColor = 'var(--theme-grey)', eventKey }) {

View File

@ -1,6 +1,6 @@
import Dropdown from 'react-bootstrap/Dropdown'
import styles from './item.module.css'
import MoreIcon from '../svgs/more-fill.svg'
import MoreIcon from '@/svgs/more-fill.svg'
export default function ActionDropdown ({ children }) {
if (!children) {

View File

@ -2,10 +2,10 @@ import { useState, useEffect } from 'react'
import AccordianItem from './accordian-item'
import { Input, InputUserSuggest, VariableInput, Checkbox } from './form'
import InputGroup from 'react-bootstrap/InputGroup'
import { BOOST_MIN, BOOST_MULT, MAX_FORWARDS } from '../lib/constants'
import { DEFAULT_CROSSPOSTING_RELAYS } from '../lib/nostr'
import { BOOST_MIN, BOOST_MULT, MAX_FORWARDS } from '@/lib/constants'
import { DEFAULT_CROSSPOSTING_RELAYS } from '@/lib/nostr'
import Info from './info'
import { numWithUnits } from '../lib/format'
import { numWithUnits } from '@/lib/format'
import styles from './adv-post-form.module.css'
import { useMe } from './me'
import { useFeeButton } from './fee-button'

View File

@ -3,7 +3,7 @@ import { Checkbox, Input } from './form'
import { useMe } from './me'
import { useEffect, useState } from 'react'
import { isNumber } from 'mathjs'
import { numWithUnits } from '../lib/format'
import { numWithUnits } from '@/lib/format'
function autoWithdrawThreshold ({ me }) {
return isNumber(me?.privates?.autoWithdrawThreshold) ? me?.privates?.autoWithdrawThreshold : 10000

View File

@ -2,8 +2,8 @@ import { useRef, useState } from 'react'
import AvatarEditor from 'react-avatar-editor'
import Button from 'react-bootstrap/Button'
import BootstrapForm from 'react-bootstrap/Form'
import EditImage from '../svgs/image-edit-fill.svg'
import Moon from '../svgs/moon-fill.svg'
import EditImage from '@/svgs/image-edit-fill.svg'
import Moon from '@/svgs/moon-fill.svg'
import { useShowModal } from './modal'
import { ImageUpload } from './image'

View File

@ -1,12 +1,12 @@
import Alert from 'react-bootstrap/Alert'
import styles from './banners.module.css'
import { useEffect, useState } from 'react'
import { useMe } from '../components/me'
import { useMe } from '@/components/me'
import { useMutation } from '@apollo/client'
import { WELCOME_BANNER_MUTATION } from '../fragments/users'
import { useToast } from '../components/toast'
import { BALANCE_LIMIT_MSATS } from '../lib/constants'
import { msatsToSats, numWithUnits } from '../lib/format'
import { WELCOME_BANNER_MUTATION } from '@/fragments/users'
import { useToast } from '@/components/toast'
import { BALANCE_LIMIT_MSATS } from '@/lib/constants'
import { msatsToSats, numWithUnits } from '@/lib/format'
export function WelcomeBanner ({ Banner }) {
const me = useMe()

View File

@ -1,7 +1,7 @@
import { createContext, useContext, useMemo } from 'react'
import { useQuery } from '@apollo/client'
import { SSR } from '../lib/constants'
import { BLOCK_HEIGHT } from '../fragments/blockHeight'
import { SSR } from '@/lib/constants'
import { BLOCK_HEIGHT } from '@/fragments/blockHeight'
export const BlockHeightContext = createContext({
height: 0

View File

@ -1,15 +1,15 @@
import { Form, Input, MarkdownInput } from '../components/form'
import { Form, Input, MarkdownInput } from '@/components/form'
import { useRouter } from 'next/router'
import { gql, useApolloClient, useMutation } from '@apollo/client'
import Countdown from './countdown'
import AdvPostForm, { AdvPostInitial } from './adv-post-form'
import InputGroup from 'react-bootstrap/InputGroup'
import useCrossposter from './use-crossposter'
import { bountySchema } from '../lib/validate'
import { bountySchema } from '@/lib/validate'
import { SubSelectInitial } from './sub-select'
import { useCallback } from 'react'
import { normalizeForwards, toastDeleteScheduled } from '../lib/form'
import { MAX_TITLE_LENGTH } from '../lib/constants'
import { normalizeForwards, toastDeleteScheduled } from '@/lib/form'
import { MAX_TITLE_LENGTH } from '@/lib/constants'
import { useMe } from './me'
import { useToast } from './toast'
import { ItemButtonBar } from './post'

View File

@ -1,7 +1,7 @@
import { createContext, useContext, useMemo } from 'react'
import { useQuery } from '@apollo/client'
import { SSR } from '../lib/constants'
import { CHAIN_FEE } from '../fragments/chainFee'
import { SSR } from '@/lib/constants'
import { CHAIN_FEE } from '@/fragments/chainFee'
export const ChainFeeContext = createContext({
fee: 0

View File

@ -12,9 +12,9 @@ import { ResponsiveContainer } from 'recharts/lib/component/ResponsiveContainer'
import { PieChart } from 'recharts/lib/chart/PieChart'
import { Cell } from 'recharts/lib/component/Cell'
import { Pie } from 'recharts/lib/polar/Pie'
import { abbrNum } from '../lib/format'
import { abbrNum } from '@/lib/format'
import { useRouter } from 'next/router'
import { timeUnitForRange } from '../lib/time'
import { timeUnitForRange } from '@/lib/time'
const dateFormatter = (when, from, to) => {
const unit = xAxisName(when, from, to)

View File

@ -1,9 +1,9 @@
import { Form, MarkdownInput } from '../components/form'
import { Form, MarkdownInput } from '@/components/form'
import { gql, useMutation } from '@apollo/client'
import styles from './reply.module.css'
import { commentSchema } from '../lib/validate'
import { commentSchema } from '@/lib/validate'
import { useToast } from './toast'
import { toastDeleteScheduled } from '../lib/form'
import { toastDeleteScheduled } from '@/lib/form'
import { FeeButtonProvider } from './fee-button'
import { ItemButtonBar } from './post'

View File

@ -5,16 +5,16 @@ import Link from 'next/link'
import Reply, { ReplyOnAnotherPage } from './reply'
import { useEffect, useMemo, useRef, useState } from 'react'
import UpVote from './upvote'
import Eye from '../svgs/eye-fill.svg'
import EyeClose from '../svgs/eye-close-line.svg'
import Eye from '@/svgs/eye-fill.svg'
import EyeClose from '@/svgs/eye-close-line.svg'
import { useRouter } from 'next/router'
import CommentEdit from './comment-edit'
import { ANON_USER_ID, COMMENT_DEPTH_LIMIT, UNKNOWN_LINK_REL } from '../lib/constants'
import { ignoreClick } from '../lib/clicks'
import { ANON_USER_ID, COMMENT_DEPTH_LIMIT, UNKNOWN_LINK_REL } from '@/lib/constants'
import { ignoreClick } from '@/lib/clicks'
import PayBounty from './pay-bounty'
import BountyIcon from '../svgs/bounty-bag.svg'
import BountyIcon from '@/svgs/bounty-bag.svg'
import ActionTooltip from './action-tooltip'
import { numWithUnits } from '../lib/format'
import { numWithUnits } from '@/lib/format'
import Share from './share'
import ItemInfo from './item-info'
import Badge from 'react-bootstrap/Badge'
@ -22,9 +22,9 @@ import { RootProvider, useRoot } from './root'
import { useMe } from './me'
import { useQuoteReply } from './use-quote-reply'
import { DownZap } from './dont-link-this'
import Skull from '../svgs/death-skull.svg'
import { commentSubTreeRootId } from '../lib/item'
import Pin from '../svgs/pushpin-fill.svg'
import Skull from '@/svgs/death-skull.svg'
import { commentSubTreeRootId } from '@/lib/item'
import Pin from '@/svgs/pushpin-fill.svg'
function Parent ({ item, rootText }) {
const root = useRoot()

View File

@ -3,8 +3,8 @@ import Comment, { CommentSkeleton } from './comment'
import styles from './header.module.css'
import Nav from 'react-bootstrap/Nav'
import Navbar from 'react-bootstrap/Navbar'
import { numWithUnits } from '../lib/format'
import { defaultCommentSort } from '../lib/item'
import { numWithUnits } from '@/lib/format'
import { defaultCommentSort } from '@/lib/item'
import { useRouter } from 'next/router'
export function CommentsHeader ({ handleSort, pinned, bio, parentCreatedAt, commentSats }) {

View File

@ -1,16 +1,16 @@
import { Form, Input, MarkdownInput } from '../components/form'
import { Form, Input, MarkdownInput } from '@/components/form'
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 { ITEM_FIELDS } from '../fragments/items'
import { ITEM_FIELDS } from '@/fragments/items'
import AccordianItem from './accordian-item'
import Item from './item'
import { discussionSchema } from '../lib/validate'
import { discussionSchema } from '@/lib/validate'
import { SubSelectInitial } from './sub-select'
import { useCallback } from 'react'
import { normalizeForwards, toastDeleteScheduled } from '../lib/form'
import { MAX_TITLE_LENGTH } from '../lib/constants'
import { normalizeForwards, toastDeleteScheduled } from '@/lib/form'
import { MAX_TITLE_LENGTH } from '@/lib/constants'
import { useMe } from './me'
import useCrossposter from './use-crossposter'
import { useToast } from './toast'

View File

@ -3,9 +3,9 @@ import { useShowModal } from './modal'
import { useToast } from './toast'
import ItemAct from './item-act'
import AccordianItem from './accordian-item'
import Flag from '../svgs/flag-fill.svg'
import Flag from '@/svgs/flag-fill.svg'
import { useMemo } from 'react'
import getColor from '../lib/rainbow'
import getColor from '@/lib/rainbow'
import { gql, useMutation } from '@apollo/client'
import { useMe } from './me'

View File

@ -1,6 +1,6 @@
import { Component } from 'react'
import { StaticLayout } from './layout'
import styles from '../styles/error.module.css'
import styles from '@/styles/error.module.css'
import Image from 'react-bootstrap/Image'
import copy from 'clipboard-copy'
import { LoggerContext } from './logger'

View File

@ -4,10 +4,10 @@ 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 { ANON_FEE_MULTIPLIER, SSR } from '@/lib/constants'
import { numWithUnits } from '@/lib/format'
import { useMe } from './me'
import AnonIcon from '../svgs/spy-fill.svg'
import AnonIcon from '@/svgs/spy-fill.svg'
import { useShowModal } from './modal'
import Link from 'next/link'
import { SubmitButton } from './form'

View File

@ -1,7 +1,7 @@
import { gql, useQuery } from '@apollo/client'
import Link from 'next/link'
import { RewardLine } from '../pages/rewards'
import { SSR } from '../lib/constants'
import { RewardLine } from '@/pages/rewards'
import { SSR } from '@/lib/constants'
const REWARDS = gql`
{

View File

@ -3,15 +3,15 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
import Popover from 'react-bootstrap/Popover'
import { CopyInput } from './form'
import styles from './footer.module.css'
import Texas from '../svgs/texas.svg'
import Github from '../svgs/github-fill.svg'
import Texas from '@/svgs/texas.svg'
import Github from '@/svgs/github-fill.svg'
import Link from 'next/link'
import Sun from '../svgs/sun-fill.svg'
import Moon from '../svgs/moon-fill.svg'
import No from '../svgs/no.svg'
import Bolt from '../svgs/bolt.svg'
import Amboss from '../svgs/amboss.svg'
import Mempool from '../svgs/bimi.svg'
import Sun from '@/svgs/sun-fill.svg'
import Moon from '@/svgs/moon-fill.svg'
import No from '@/svgs/no.svg'
import Bolt from '@/svgs/bolt.svg'
import Amboss from '@/svgs/amboss.svg'
import Mempool from '@/svgs/bimi.svg'
import { useEffect, useState } from 'react'
import Rewards from './footer-rewards'
import useDarkMode from './dark-mode'

View File

@ -8,27 +8,27 @@ import Col from 'react-bootstrap/Col'
import Dropdown from 'react-bootstrap/Dropdown'
import Nav from 'react-bootstrap/Nav'
import Row from 'react-bootstrap/Row'
import Markdown from '../svgs/markdown-line.svg'
import AddImageIcon from '../svgs/image-add-line.svg'
import Markdown from '@/svgs/markdown-line.svg'
import AddImageIcon from '@/svgs/image-add-line.svg'
import styles from './form.module.css'
import Text from '../components/text'
import AddIcon from '../svgs/add-fill.svg'
import CloseIcon from '../svgs/close-line.svg'
import Text from '@/components/text'
import AddIcon from '@/svgs/add-fill.svg'
import CloseIcon from '@/svgs/close-line.svg'
import { gql, useLazyQuery } from '@apollo/client'
import { USER_SUGGESTIONS } from '../fragments/users'
import { USER_SUGGESTIONS } from '@/fragments/users'
import TextareaAutosize from 'react-textarea-autosize'
import { useToast } from './toast'
import { useInvoiceable } from './invoice'
import { numWithUnits } from '../lib/format'
import { numWithUnits } from '@/lib/format'
import textAreaCaret from 'textarea-caret'
import ReactDatePicker from 'react-datepicker'
import 'react-datepicker/dist/react-datepicker.css'
import useDebounceCallback, { debounce } from './use-debounce-callback'
import { ImageUpload } from './image'
import { AWS_S3_URL_REGEXP } from '../lib/constants'
import { whenRange } from '../lib/time'
import { AWS_S3_URL_REGEXP } from '@/lib/constants'
import { whenRange } from '@/lib/time'
import { useFeeButton } from './fee-button'
import Thumb from '../svgs/thumb-up-fill.svg'
import Thumb from '@/svgs/thumb-up-fill.svg'
import Info from './info'
export function SubmitButton ({

View File

@ -1,10 +1,10 @@
import Badge from 'react-bootstrap/Badge'
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
import Tooltip from 'react-bootstrap/Tooltip'
import CowboyHatIcon from '../svgs/cowboy.svg'
import AnonIcon from '../svgs/spy-fill.svg'
import { numWithUnits } from '../lib/format'
import { AD_USER_ID, ANON_USER_ID } from '../lib/constants'
import CowboyHatIcon from '@/svgs/cowboy.svg'
import AnonIcon from '@/svgs/spy-fill.svg'
import { numWithUnits } from '@/lib/format'
import { AD_USER_ID, ANON_USER_ID } from '@/lib/constants'
export default function Hat ({ user, badge, className = 'ms-1', height = 16, width = 16 }) {
if (!user || Number(user.id) === AD_USER_ID) return null

View File

@ -11,20 +11,20 @@ import { useMe } from './me'
import Head from 'next/head'
import { signOut } from 'next-auth/react'
import { useCallback, useEffect } from 'react'
import { randInRange } from '../lib/rand'
import { abbrNum, msatsToSats } from '../lib/format'
import NoteIcon from '../svgs/notification-4-fill.svg'
import { randInRange } from '@/lib/rand'
import { abbrNum, msatsToSats } from '@/lib/format'
import NoteIcon from '@/svgs/notification-4-fill.svg'
import { useQuery } from '@apollo/client'
import LightningIcon from '../svgs/bolt.svg'
import SearchIcon from '../svgs/search-line.svg'
import BackArrow from '../svgs/arrow-left-line.svg'
import { BALANCE_LIMIT_MSATS, SSR } from '../lib/constants'
import LightningIcon from '@/svgs/bolt.svg'
import SearchIcon from '@/svgs/search-line.svg'
import BackArrow from '@/svgs/arrow-left-line.svg'
import { BALANCE_LIMIT_MSATS, SSR } from '@/lib/constants'
import { useLightning } from './lightning'
import { HAS_NOTIFICATIONS } from '../fragments/notifications'
import AnonIcon from '../svgs/spy-fill.svg'
import { HAS_NOTIFICATIONS } from '@/fragments/notifications'
import AnonIcon from '@/svgs/spy-fill.svg'
import Hat from './hat'
import HiddenWalletSummary from './hidden-wallet-summary'
import { clearNotifications } from '../lib/badge'
import { clearNotifications } from '@/lib/badge'
import { useServiceWorker } from './serviceworker'
import SubSelect from './sub-select'

View File

@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from 'react'
import { abbrNum, numWithUnits } from '../lib/format'
import { abbrNum, numWithUnits } from '@/lib/format'
import { useMe } from './me'
export default function HiddenWalletSummary ({ abbreviate, fixedWidth }) {

View File

@ -1,10 +1,10 @@
import styles from './text.module.css'
import { Fragment, useState, useEffect, useMemo, useCallback, forwardRef, useRef, memo } from 'react'
import { IMGPROXY_URL_REGEXP } from '../lib/url'
import { IMGPROXY_URL_REGEXP } from '@/lib/url'
import { useShowModal } from './modal'
import { useMe } from './me'
import { Dropdown } from 'react-bootstrap'
import { UNKNOWN_LINK_REL, UPLOAD_TYPES_ALLOW, MEDIA_URL } from '../lib/constants'
import { UNKNOWN_LINK_REL, UPLOAD_TYPES_ALLOW, MEDIA_URL } from '@/lib/constants'
import { useToast } from './toast'
import gql from 'graphql-tag'
import { useMutation } from '@apollo/client'

View File

@ -1,5 +1,5 @@
import React from 'react'
import InfoIcon from '../svgs/information-fill.svg'
import InfoIcon from '@/svgs/information-fill.svg'
import { useShowModal } from './modal'
export default function Info ({ children, label, iconClassName = 'fill-theme-color' }) {

View File

@ -1,7 +1,7 @@
import { CopyInput } from './form'
import { gql, useMutation } from '@apollo/client'
import { INVITE_FIELDS } from '../fragments/invites'
import styles from '../styles/invites.module.css'
import { INVITE_FIELDS } from '@/fragments/invites'
import styles from '@/styles/invites.module.css'
export default function Invite ({ invite, active }) {
const [revokeInvite] = useMutation(

View File

@ -1,6 +1,6 @@
import Moon from '../svgs/moon-fill.svg'
import Check from '../svgs/check-double-line.svg'
import ThumbDown from '../svgs/thumb-down-fill.svg'
import Moon from '@/svgs/moon-fill.svg'
import Check from '@/svgs/check-double-line.svg'
import ThumbDown from '@/svgs/thumb-down-fill.svg'
function InvoiceDefaultStatus ({ status }) {
return (

View File

@ -2,10 +2,10 @@ import { useState, useCallback, useEffect } from 'react'
import { useApolloClient, useMutation, useQuery } from '@apollo/client'
import { Button } from 'react-bootstrap'
import { gql } from 'graphql-tag'
import { numWithUnits } from '../lib/format'
import { numWithUnits } from '@/lib/format'
import AccordianItem from './accordian-item'
import Qr, { QrSkeleton } from './qr'
import { INVOICE } from '../fragments/wallet'
import { INVOICE } from '@/fragments/wallet'
import InvoiceStatus from './invoice-status'
import { useMe } from './me'
import { useShowModal } from './modal'

View File

@ -3,8 +3,8 @@ import InputGroup from 'react-bootstrap/InputGroup'
import React, { useState, useRef, useEffect, useCallback } from 'react'
import { Form, Input, SubmitButton } from './form'
import { useMe } from './me'
import UpBolt from '../svgs/bolt.svg'
import { amountSchema } from '../lib/validate'
import UpBolt from '@/svgs/bolt.svg'
import { amountSchema } from '@/lib/validate'
import { gql, useApolloClient, useMutation } from '@apollo/client'
import { payOrLoginError, useInvoiceModal } from './invoice'
import { TOAST_DEFAULT_DELAY_MS, useToast, withToastFlow } from './toast'

View File

@ -5,7 +5,7 @@ import Comment from './comment'
import Text, { SearchText } from './text'
import ZoomableImage from './image'
import Comments from './comments'
import styles from '../styles/item.module.css'
import styles from '@/styles/item.module.css'
import itemStyles from './item.module.css'
import { useMe } from './me'
import Button from 'react-bootstrap/Button'
@ -14,18 +14,18 @@ import YouTube from 'react-youtube'
import useDarkMode from './dark-mode'
import { useEffect, useState } from 'react'
import Poll from './poll'
import { commentsViewed } from '../lib/new-comments'
import { commentsViewed } from '@/lib/new-comments'
import Related from './related'
import PastBounties from './past-bounties'
import Check from '../svgs/check-double-line.svg'
import Check from '@/svgs/check-double-line.svg'
import Share from './share'
import Toc from './table-of-contents'
import Link from 'next/link'
import { RootProvider } from './root'
import { IMGPROXY_URL_REGEXP } from '../lib/url'
import { numWithUnits } from '../lib/format'
import { IMGPROXY_URL_REGEXP } from '@/lib/url'
import { numWithUnits } from '@/lib/format'
import { useQuoteReply } from './use-quote-reply'
import { UNKNOWN_LINK_REL } from '../lib/constants'
import { UNKNOWN_LINK_REL } from '@/lib/constants'
function BioItem ({ item, handleClick }) {
const me = useMe()

View File

@ -4,9 +4,9 @@ import { useEffect, useState } from 'react'
import Badge from 'react-bootstrap/Badge'
import Dropdown from 'react-bootstrap/Dropdown'
import Countdown from './countdown'
import { abbrNum, numWithUnits } from '../lib/format'
import { newComments, commentsViewedAt } from '../lib/new-comments'
import { timeSince } from '../lib/time'
import { abbrNum, numWithUnits } from '@/lib/format'
import { newComments, commentsViewedAt } from '@/lib/new-comments'
import { timeSince } from '@/lib/time'
import { DeleteDropdownItem } from './delete'
import styles from './item.module.css'
import { useMe } from './me'
@ -15,7 +15,7 @@ import BookmarkDropdownItem from './bookmark'
import SubscribeDropdownItem from './subscribe'
import { CopyLinkDropdownItem, CrosspostDropdownItem } from './share'
import Hat from './hat'
import { AD_USER_ID } from '../lib/constants'
import { AD_USER_ID } from '@/lib/constants'
import ActionDropdown from './action-dropdown'
import MuteDropdownItem from './mute'
import { DropdownItemUpVote } from './upvote'

View File

@ -6,11 +6,11 @@ import Image from 'react-bootstrap/Image'
import { SearchTitle } from './item'
import styles from './item.module.css'
import Link from 'next/link'
import { timeSince } from '../lib/time'
import EmailIcon from '../svgs/mail-open-line.svg'
import { timeSince } from '@/lib/time'
import EmailIcon from '@/svgs/mail-open-line.svg'
import Share from './share'
import Hat from './hat'
import { MEDIA_URL } from '../lib/constants'
import { MEDIA_URL } from '@/lib/constants'
export default function ItemJob ({ item, toc, rank, children }) {
const isEmail = string().email().isValidSync(item.url)

View File

@ -2,22 +2,22 @@ import Link from 'next/link'
import styles from './item.module.css'
import UpVote from './upvote'
import { useRef } from 'react'
import { AD_USER_ID, UNKNOWN_LINK_REL } from '../lib/constants'
import Pin from '../svgs/pushpin-fill.svg'
import { AD_USER_ID, UNKNOWN_LINK_REL } from '@/lib/constants'
import Pin from '@/svgs/pushpin-fill.svg'
import reactStringReplace from 'react-string-replace'
import PollIcon from '../svgs/bar-chart-horizontal-fill.svg'
import BountyIcon from '../svgs/bounty-bag.svg'
import PollIcon from '@/svgs/bar-chart-horizontal-fill.svg'
import BountyIcon from '@/svgs/bounty-bag.svg'
import ActionTooltip from './action-tooltip'
import ImageIcon from '../svgs/image-fill.svg'
import { numWithUnits } from '../lib/format'
import ImageIcon from '@/svgs/image-fill.svg'
import { numWithUnits } from '@/lib/format'
import ItemInfo from './item-info'
import Prism from '../svgs/prism.svg'
import { commentsViewedAt } from '../lib/new-comments'
import Prism from '@/svgs/prism.svg'
import { commentsViewedAt } from '@/lib/new-comments'
import { useRouter } from 'next/router'
import { Badge } from 'react-bootstrap'
import AdIcon from '../svgs/advertisement-fill.svg'
import AdIcon from '@/svgs/advertisement-fill.svg'
import { DownZap } from './dont-link-this'
import { timeLeft } from '../lib/time'
import { timeLeft } from '@/lib/time'
export function SearchTitle ({ title }) {
return reactStringReplace(title, /\*\*\*([^*]+)\*\*\*/g, (match, i) => {

View File

@ -5,8 +5,8 @@ import styles from './items.module.css'
import MoreFooter from './more-footer'
import { Fragment, useCallback, useMemo } from 'react'
import { CommentFlat } from './comment'
import { SUB_ITEMS } from '../fragments/subs'
import { LIMIT } from '../lib/cursor'
import { SUB_ITEMS } from '@/fragments/subs'
import { LIMIT } from '@/lib/cursor'
import ItemFull from './item-full'
import { useData } from './use-data'

View File

@ -8,16 +8,16 @@ import Alert from 'react-bootstrap/Alert'
import { useCallback, useEffect, useState } from 'react'
import Info from './info'
import AccordianItem from './accordian-item'
import styles from '../styles/post.module.css'
import styles from '@/styles/post.module.css'
import { useLazyQuery, gql, useMutation } from '@apollo/client'
import { useRouter } from 'next/router'
import Link from 'next/link'
import { usePrice } from './price'
import Avatar from './avatar'
import { jobSchema } from '../lib/validate'
import { MAX_TITLE_LENGTH, MEDIA_URL } from '../lib/constants'
import { jobSchema } from '@/lib/validate'
import { MAX_TITLE_LENGTH, MEDIA_URL } from '@/lib/constants'
import { useToast } from './toast'
import { toastDeleteScheduled } from '../lib/form'
import { toastDeleteScheduled } from '@/lib/form'
import { ItemButtonBar } from './post'
function satsMin2Mo (minute) {

View File

@ -7,9 +7,9 @@ import Row from 'react-bootstrap/Row'
import AccordianItem from './accordian-item'
import Qr, { QrSkeleton } from './qr'
import styles from './lightning-auth.module.css'
import BackIcon from '../svgs/arrow-left-line.svg'
import BackIcon from '@/svgs/arrow-left-line.svg'
import { useRouter } from 'next/router'
import { SSR } from '../lib/constants'
import { SSR } from '@/lib/constants'
function QrAuth ({ k1, encodedUrl, callbackUrl }) {
const query = gql`

View File

@ -1,5 +1,5 @@
import React, { useRef, useEffect, useContext } from 'react'
import { randInRange } from '../lib/rand'
import { randInRange } from '@/lib/rand'
export const LightningContext = React.createContext(() => {})

View File

@ -1,18 +1,18 @@
import { useState, useEffect, useCallback } from 'react'
import { Form, Input, MarkdownInput } from '../components/form'
import { Form, Input, MarkdownInput } from '@/components/form'
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 { ITEM_FIELDS } from '../fragments/items'
import { ITEM_FIELDS } from '@/fragments/items'
import Item from './item'
import AccordianItem from './accordian-item'
import { linkSchema } from '../lib/validate'
import Moon from '../svgs/moon-fill.svg'
import { normalizeForwards, toastDeleteScheduled } from '../lib/form'
import { linkSchema } from '@/lib/validate'
import Moon from '@/svgs/moon-fill.svg'
import { normalizeForwards, toastDeleteScheduled } from '@/lib/form'
import { useToast } from './toast'
import { SubSelectInitial } from './sub-select'
import { MAX_TITLE_LENGTH } from '../lib/constants'
import { MAX_TITLE_LENGTH } from '@/lib/constants'
import useCrossposter from './use-crossposter'
import { useMe } from './me'
import { ItemButtonBar } from './post'

View File

@ -1,6 +1,6 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { useMe } from './me'
import fancyNames from '../lib/fancy-names.json'
import fancyNames from '@/lib/fancy-names.json'
const generateFancyName = () => {
// 100 adjectives * 100 nouns * 10000 = 100M possible names

View File

@ -1,7 +1,7 @@
import GithubIcon from '../svgs/github-fill.svg'
import TwitterIcon from '../svgs/twitter-fill.svg'
import LightningIcon from '../svgs/bolt.svg'
import NostrIcon from '../svgs/nostr.svg'
import GithubIcon from '@/svgs/github-fill.svg'
import TwitterIcon from '@/svgs/twitter-fill.svg'
import LightningIcon from '@/svgs/bolt.svg'
import NostrIcon from '@/svgs/nostr.svg'
import Button from 'react-bootstrap/Button'
export default function LoginButton ({ text, type, className, onClick }) {

View File

@ -1,13 +1,13 @@
import { signIn } from 'next-auth/react'
import styles from './login.module.css'
import { Form, Input, SubmitButton } from '../components/form'
import { Form, Input, SubmitButton } from '@/components/form'
import { useState } from 'react'
import Alert from 'react-bootstrap/Alert'
import { useRouter } from 'next/router'
import { LightningAuthWithExplainer } from './lightning-auth'
import NostrAuth from './nostr-auth'
import LoginButton from './login-button'
import { emailSchema } from '../lib/validate'
import { emailSchema } from '@/lib/validate'
export function EmailLoginForm ({ text, callbackUrl }) {
return (

View File

@ -1,7 +1,7 @@
import React, { useContext } from 'react'
import { useQuery } from '@apollo/client'
import { ME } from '../fragments/users'
import { SSR } from '../lib/constants'
import { ME } from '@/fragments/users'
import { SSR } from '@/lib/constants'
export const MeContext = React.createContext({
me: null

View File

@ -1,6 +1,6 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import Modal from 'react-bootstrap/Modal'
import BackArrow from '../svgs/arrow-left-line.svg'
import BackArrow from '@/svgs/arrow-left-line.svg'
import { useRouter } from 'next/router'
import ActionDropdown from './action-dropdown'

View File

@ -6,9 +6,9 @@ import Col from 'react-bootstrap/Col'
import Row from 'react-bootstrap/Row'
import { useRouter } from 'next/router'
import AccordianItem from './accordian-item'
import BackIcon from '../svgs/arrow-left-line.svg'
import BackIcon from '@/svgs/arrow-left-line.svg'
import styles from './lightning-auth.module.css'
import { callWithTimeout } from '../lib/nostr'
import { callWithTimeout } from '@/lib/nostr'
function ExtensionError ({ message, details }) {
return (

View File

@ -3,17 +3,17 @@ import { useQuery } from '@apollo/client'
import Comment, { CommentSkeleton } from './comment'
import Item from './item'
import ItemJob from './item-job'
import { NOTIFICATIONS } from '../fragments/notifications'
import { NOTIFICATIONS } from '@/fragments/notifications'
import MoreFooter from './more-footer'
import Invite from './invite'
import { ignoreClick } from '../lib/clicks'
import { dayMonthYear, timeSince } from '../lib/time'
import { ignoreClick } from '@/lib/clicks'
import { dayMonthYear, timeSince } from '@/lib/time'
import Link from 'next/link'
import Check from '../svgs/check-double-line.svg'
import HandCoin from '../svgs/hand-coin-fill.svg'
import { LOST_BLURBS, FOUND_BLURBS, UNKNOWN_LINK_REL } from '../lib/constants'
import CowboyHatIcon from '../svgs/cowboy.svg'
import BaldIcon from '../svgs/bald.svg'
import Check from '@/svgs/check-double-line.svg'
import HandCoin from '@/svgs/hand-coin-fill.svg'
import { LOST_BLURBS, FOUND_BLURBS, UNKNOWN_LINK_REL } from '@/lib/constants'
import CowboyHatIcon from '@/svgs/cowboy.svg'
import BaldIcon from '@/svgs/bald.svg'
import { RootProvider } from './root'
import Alert from 'react-bootstrap/Alert'
import styles from './notifications.module.css'
@ -21,14 +21,14 @@ import { useServiceWorker } from './serviceworker'
import { Checkbox, Form } from './form'
import { useRouter } from 'next/router'
import { useData } from './use-data'
import { nostrZapDetails } from '../lib/nostr'
import { nostrZapDetails } from '@/lib/nostr'
import Text from './text'
import NostrIcon from '../svgs/nostr.svg'
import { numWithUnits } from '../lib/format'
import BountyIcon from '../svgs/bounty-bag.svg'
import NostrIcon from '@/svgs/nostr.svg'
import { numWithUnits } from '@/lib/format'
import BountyIcon from '@/svgs/bounty-bag.svg'
import { LongCountdown } from './countdown'
import { nextBillingWithGrace } from '../lib/territory'
import { commentSubTreeRootId } from '../lib/item'
import { nextBillingWithGrace } from '@/lib/territory'
import { commentSubTreeRootId } from '@/lib/item'
function Notification ({ n, fresh }) {
const type = n.__typename

View File

@ -1,4 +1,4 @@
import Moon from '../svgs/moon-fill.svg'
import Moon from '@/svgs/moon-fill.svg'
export default function PageLoading () {
return (

View File

@ -3,7 +3,7 @@ import Button from 'react-bootstrap/Button'
import styles from './pay-bounty.module.css'
import ActionTooltip from './action-tooltip'
import { useMe } from './me'
import { numWithUnits } from '../lib/format'
import { numWithUnits } from '@/lib/format'
import { useShowModal } from './modal'
import { useRoot } from './root'
import { payOrLoginError, useInvoiceModal } from './invoice'

View File

@ -1,14 +1,14 @@
import { DateTimeInput, Form, Input, MarkdownInput, VariableInput } from '../components/form'
import { DateTimeInput, Form, Input, MarkdownInput, VariableInput } from '@/components/form'
import { useRouter } from 'next/router'
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 { datePivot } from '../lib/time'
import { pollSchema } from '../lib/validate'
import { MAX_POLL_CHOICE_LENGTH, MAX_POLL_NUM_CHOICES, MAX_TITLE_LENGTH } from '@/lib/constants'
import { datePivot } from '@/lib/time'
import { pollSchema } from '@/lib/validate'
import { SubSelectInitial } from './sub-select'
import { useCallback } from 'react'
import { normalizeForwards, toastDeleteScheduled } from '../lib/form'
import { normalizeForwards, toastDeleteScheduled } from '@/lib/form'
import useCrossposter from './use-crossposter'
import { useMe } from './me'
import { useToast } from './toast'

View File

@ -1,13 +1,13 @@
import { gql, useMutation } from '@apollo/client'
import Button from 'react-bootstrap/Button'
import { fixedDecimal, numWithUnits } from '../lib/format'
import { timeLeft } from '../lib/time'
import { fixedDecimal, numWithUnits } from '@/lib/format'
import { timeLeft } from '@/lib/time'
import { useMe } from './me'
import styles from './poll.module.css'
import Check from '../svgs/checkbox-circle-fill.svg'
import Check from '@/svgs/checkbox-circle-fill.svg'
import { signIn } from 'next-auth/react'
import ActionTooltip from './action-tooltip'
import { POLL_COST } from '../lib/constants'
import { POLL_COST } from '@/lib/constants'
import { payOrLoginError, useInvoiceModal } from './invoice'
export default function Poll ({ item }) {

View File

@ -1,10 +1,10 @@
import React, { useContext, useEffect, useMemo, useState } from 'react'
import { useQuery } from '@apollo/client'
import { fixedDecimal } from '../lib/format'
import { fixedDecimal } from '@/lib/format'
import { useMe } from './me'
import { PRICE } from '../fragments/price'
import { CURRENCY_SYMBOLS } from '../lib/currency'
import { SSR } from '../lib/constants'
import { PRICE } from '@/fragments/price'
import { CURRENCY_SYMBOLS } from '@/lib/currency'
import { SSR } from '@/lib/constants'
import { useBlockHeight } from './block-height'
import { useChainFee } from './chain-fee'

View File

@ -1,4 +1,4 @@
import { ITEM_TYPES, ITEM_TYPES_UNIVERSAL } from '../lib/constants'
import { ITEM_TYPES, ITEM_TYPES_UNIVERSAL } from '@/lib/constants'
import { Select } from './form'
import { useRouter } from 'next/router'

View File

@ -1,4 +1,4 @@
import { RELATED_ITEMS } from '../fragments/items'
import { RELATED_ITEMS } from '@/fragments/items'
import AccordianItem from './accordian-item'
import Items from './items'
import { NavigateFooter } from './more-footer'

View File

@ -1,20 +1,20 @@
import { Form, MarkdownInput } from '../components/form'
import { Form, MarkdownInput } from '@/components/form'
import { gql, useMutation } from '@apollo/client'
import styles from './reply.module.css'
import { COMMENTS } from '../fragments/comments'
import { COMMENTS } from '@/fragments/comments'
import { useMe } from './me'
import { forwardRef, useCallback, useEffect, useState, useRef } from 'react'
import Link from 'next/link'
import { FeeButtonProvider, postCommentBaseLineItems, postCommentUseRemoteLineItems } from './fee-button'
import { commentsViewedAfterComment } from '../lib/new-comments'
import { commentSchema } from '../lib/validate'
import { commentsViewedAfterComment } from '@/lib/new-comments'
import { commentSchema } from '@/lib/validate'
import { useToast } from './toast'
import { toastDeleteScheduled } from '../lib/form'
import { toastDeleteScheduled } from '@/lib/form'
import { ItemButtonBar } from './post'
import { useShowModal } from './modal'
import { Button } from 'react-bootstrap'
import { useRoot } from './root'
import { commentSubTreeRootId } from '../lib/item'
import { commentSubTreeRootId } from '@/lib/item'
export function ReplyOnAnotherPage ({ item }) {
const rootId = commentSubTreeRootId(item)

View File

@ -1,10 +1,10 @@
import Container from 'react-bootstrap/Container'
import styles from './search.module.css'
import SearchIcon from '../svgs/search-line.svg'
import SearchIcon from '@/svgs/search-line.svg'
import { useEffect, useRef, useState } from 'react'
import { Form, Input, Select, DatePicker, SubmitButton } from './form'
import { useRouter } from 'next/router'
import { whenToFrom } from '../lib/time'
import { whenToFrom } from '@/lib/time'
export default function Search ({ sub }) {
const router = useRouter()

View File

@ -1,7 +1,7 @@
import { NextSeo } from 'next-seo'
import { useRouter } from 'next/router'
import removeMd from 'remove-markdown'
import { numWithUnits } from '../lib/format'
import { numWithUnits } from '@/lib/format'
export function SeoSearch ({ sub }) {
const router = useRouter()

View File

@ -1,11 +1,11 @@
import Dropdown from 'react-bootstrap/Dropdown'
import ShareIcon from '../svgs/share-fill.svg'
import ShareIcon from '@/svgs/share-fill.svg'
import copy from 'clipboard-copy'
import useCrossposter from './use-crossposter'
import { useMe } from './me'
import { useToast } from './toast'
import { SSR } from '../lib/constants'
import { commentSubTreeRootId } from '../lib/item'
import { SSR } from '@/lib/constants'
import { commentSubTreeRootId } from '@/lib/item'
import { useRouter } from 'next/router'
const referrurl = (ipath, me) => {

View File

@ -1,8 +1,8 @@
import Alert from 'react-bootstrap/Alert'
import YouTube from '../svgs/youtube-line.svg'
import YouTube from '@/svgs/youtube-line.svg'
import { useEffect, useState } from 'react'
import { gql, useQuery } from '@apollo/client'
import { datePivot } from '../lib/time'
import { datePivot } from '@/lib/time'
export default function Snl ({ ignorePreference }) {
const [show, setShow] = useState()

View File

@ -1,5 +1,5 @@
import React, { useCallback, useContext, useState } from 'react'
import { randInRange } from '../lib/rand'
import { randInRange } from '@/lib/rand'
export const SnowContext = React.createContext(() => {})

View File

@ -1,7 +1,7 @@
import { useRouter } from 'next/router'
import { Select } from './form'
import { SSR } from '../lib/constants'
import { SUBS } from '../fragments/subs'
import { SSR } from '@/lib/constants'
import { SUBS } from '@/fragments/subs'
import { useQuery } from '@apollo/client'
import { useEffect, useState } from 'react'
import styles from './sub-select.module.css'

View File

@ -1,7 +1,7 @@
import React, { useMemo, useState } from 'react'
import Dropdown from 'react-bootstrap/Dropdown'
import FormControl from 'react-bootstrap/FormControl'
import TocIcon from '../svgs/list-unordered.svg'
import TocIcon from '@/svgs/list-unordered.svg'
import { fromMarkdown } from 'mdast-util-from-markdown'
import { visit } from 'unist-util-visit'
import { toString } from 'mdast-util-to-string'

View File

@ -5,13 +5,13 @@ import FeeButton, { FeeButtonProvider } from './fee-button'
import { gql, useApolloClient, useLazyQuery, useMutation } from '@apollo/client'
import { useCallback, useMemo, useState } from 'react'
import { useRouter } from 'next/router'
import { MAX_TERRITORY_DESC_LENGTH, POST_TYPES, TERRITORY_BILLING_OPTIONS, TERRITORY_PERIOD_COST } from '../lib/constants'
import { territorySchema } from '../lib/validate'
import { MAX_TERRITORY_DESC_LENGTH, POST_TYPES, TERRITORY_BILLING_OPTIONS, TERRITORY_PERIOD_COST } from '@/lib/constants'
import { territorySchema } from '@/lib/validate'
import { useMe } from './me'
import Info from './info'
import { abbrNum } from '../lib/format'
import { purchasedType } from '../lib/territory'
import { SUB } from '../fragments/subs'
import { abbrNum } from '@/lib/format'
import { purchasedType } from '@/lib/territory'
import { SUB } from '@/fragments/subs'
export default function TerritoryForm ({ sub }) {
const router = useRouter()

View File

@ -3,7 +3,7 @@ import { AccordianCard } from './accordian-item'
import TerritoryPaymentDue, { TerritoryBillingLine } from './territory-payment-due'
import Link from 'next/link'
import Text from './text'
import { numWithUnits } from '../lib/format'
import { numWithUnits } from '@/lib/format'
import styles from './item.module.css'
import Hat from './hat'
import { useMe } from './me'

View File

@ -1,5 +1,5 @@
import Link from 'next/link'
import { abbrNum, numWithUnits } from '../lib/format'
import { abbrNum, numWithUnits } from '@/lib/format'
import styles from './item.module.css'
import React, { useEffect, useMemo, useState } from 'react'
import { useQuery } from '@apollo/client'

View File

@ -1,14 +1,14 @@
import { Alert } from 'react-bootstrap'
import { useMe } from './me'
import FeeButton, { FeeButtonProvider } from './fee-button'
import { TERRITORY_BILLING_OPTIONS } from '../lib/constants'
import { TERRITORY_BILLING_OPTIONS } from '@/lib/constants'
import { Form } from './form'
import { timeSince } from '../lib/time'
import { timeSince } from '@/lib/time'
import { LongCountdown } from './countdown'
import { useCallback } from 'react'
import { useApolloClient, useMutation } from '@apollo/client'
import { SUB_PAY } from '../fragments/subs'
import { nextBillingWithGrace } from '../lib/territory'
import { SUB_PAY } from '@/fragments/subs'
import { nextBillingWithGrace } from '@/lib/territory'
export default function TerritoryPaymentDue ({ sub }) {
const me = useMe()

View File

@ -3,7 +3,7 @@ import { useShowModal } from './modal'
import { useToast } from './toast'
import { Button, Dropdown, InputGroup } from 'react-bootstrap'
import { Form, InputUserSuggest, SubmitButton } from './form'
import { territoryTransferSchema } from '../lib/validate'
import { territoryTransferSchema } from '@/lib/validate'
import { useCallback } from 'react'
import Link from 'next/link'
import { useMe } from './me'

View File

@ -4,22 +4,22 @@ import YouTube from 'react-youtube'
import gfm from 'remark-gfm'
import { LightAsync as SyntaxHighlighter } from 'react-syntax-highlighter'
import atomDark from 'react-syntax-highlighter/dist/cjs/styles/prism/atom-dark'
import mention from '../lib/remark-mention'
import sub from '../lib/remark-sub'
import mention from '@/lib/remark-mention'
import sub from '@/lib/remark-sub'
import React, { useState, memo, useRef, useCallback, useMemo, useEffect } from 'react'
import GithubSlugger from 'github-slugger'
import LinkIcon from '../svgs/link.svg'
import Thumb from '../svgs/thumb-up-fill.svg'
import LinkIcon from '@/svgs/link.svg'
import Thumb from '@/svgs/thumb-up-fill.svg'
import { toString } from 'mdast-util-to-string'
import copy from 'clipboard-copy'
import ZoomableImage, { decodeOriginalUrl } from './image'
import { IMGPROXY_URL_REGEXP, parseInternalLinks } from '../lib/url'
import { IMGPROXY_URL_REGEXP, parseInternalLinks } from '@/lib/url'
import reactStringReplace from 'react-string-replace'
import { rehypeInlineCodeProperty } from '../lib/md'
import { rehypeInlineCodeProperty } from '@/lib/md'
import { Button } from 'react-bootstrap'
import { useRouter } from 'next/router'
import Link from 'next/link'
import { UNKNOWN_LINK_REL } from '../lib/constants'
import { UNKNOWN_LINK_REL } from '@/lib/constants'
import isEqual from 'lodash/isEqual'
export function SearchText ({ text }) {

View File

@ -1,7 +1,7 @@
import { useRouter } from 'next/router'
import { Form, Select, DatePicker } from './form'
import { ITEM_SORTS, SUB_SORTS, USER_SORTS, WHENS } from '../lib/constants'
import { whenToFrom } from '../lib/time'
import { ITEM_SORTS, SUB_SORTS, USER_SORTS, WHENS } from '@/lib/constants'
import { whenToFrom } from '@/lib/time'
export default function TopHeader ({ sub, cat }) {
const router = useRouter()

View File

@ -1,16 +1,16 @@
import UpBolt from '../svgs/bolt.svg'
import UpBolt from '@/svgs/bolt.svg'
import styles from './upvote.module.css'
import { gql, useMutation } from '@apollo/client'
import ActionTooltip from './action-tooltip'
import ItemAct, { useAct, useZap } from './item-act'
import { useMe } from './me'
import getColor from '../lib/rainbow'
import getColor from '@/lib/rainbow'
import { useCallback, useMemo, useRef, useState } from 'react'
import LongPressable from 'react-longpressable'
import Overlay from 'react-bootstrap/Overlay'
import Popover from 'react-bootstrap/Popover'
import { useShowModal } from './modal'
import { numWithUnits } from '../lib/format'
import { numWithUnits } from '@/lib/format'
import { Dropdown } from 'react-bootstrap'
const UpvotePopover = ({ target, show, handleClose }) => {

View File

@ -1,7 +1,7 @@
import { useRouter } from 'next/router'
import { Select, DatePicker } from './form'
import { WHENS } from '../lib/constants'
import { whenToFrom } from '../lib/time'
import { WHENS } from '@/lib/constants'
import { whenToFrom } from '@/lib/time'
export function UsageHeader () {
const router = useRouter()

View File

@ -1,10 +1,10 @@
import { useCallback } from 'react'
import { useToast } from './toast'
import { Button } from 'react-bootstrap'
import { DEFAULT_CROSSPOSTING_RELAYS, crosspost, callWithTimeout } from '../lib/nostr'
import { DEFAULT_CROSSPOSTING_RELAYS, crosspost, callWithTimeout } from '@/lib/nostr'
import { gql, useMutation, useQuery, useLazyQuery } from '@apollo/client'
import { SETTINGS } from '../fragments/users'
import { ITEM_FULL_FIELDS, POLL_FIELDS } from '../fragments/items'
import { SETTINGS } from '@/fragments/users'
import { ITEM_FULL_FIELDS, POLL_FIELDS } from '@/fragments/items'
async function discussionToEvent (item) {
const createdAt = Math.floor(Date.now() / 1000)

View File

@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { quote as quoteMd } from '../lib/md'
import { quote as quoteMd } from '@/lib/md'
export function useQuoteReply ({ text }) {
const ref = useRef(null)

View File

@ -9,26 +9,26 @@ import { Form, Input, SubmitButton } from './form'
import { gql, useApolloClient, useMutation } from '@apollo/client'
import styles from './user-header.module.css'
import { useMe } from './me'
import { NAME_MUTATION } from '../fragments/users'
import { NAME_MUTATION } from '@/fragments/users'
import QRCode from 'qrcode.react'
import LightningIcon from '../svgs/bolt.svg'
import { encodeLNUrl } from '../lib/lnurl'
import LightningIcon from '@/svgs/bolt.svg'
import { encodeLNUrl } from '@/lib/lnurl'
import Avatar from './avatar'
import { userSchema } from '../lib/validate'
import { userSchema } from '@/lib/validate'
import { useShowModal } from './modal'
import { numWithUnits } from '../lib/format'
import { numWithUnits } from '@/lib/format'
import Hat from './hat'
import SubscribeUserDropdownItem from './subscribeUser'
import ActionDropdown from './action-dropdown'
import CodeIcon from '../svgs/terminal-box-fill.svg'
import CodeIcon from '@/svgs/terminal-box-fill.svg'
import MuteDropdownItem from './mute'
import copy from 'clipboard-copy'
import { useToast } from './toast'
import { hexToBech32 } from '../lib/nostr'
import NostrIcon from '../svgs/nostr.svg'
import GithubIcon from '../svgs/github-fill.svg'
import TwitterIcon from '../svgs/twitter-fill.svg'
import { UNKNOWN_LINK_REL, MEDIA_URL } from '../lib/constants'
import { hexToBech32 } from '@/lib/nostr'
import NostrIcon from '@/svgs/nostr.svg'
import GithubIcon from '@/svgs/github-fill.svg'
import TwitterIcon from '@/svgs/twitter-fill.svg'
import { UNKNOWN_LINK_REL, MEDIA_URL } from '@/lib/constants'
export default function UserHeader ({ user }) {
const router = useRouter()

View File

@ -1,6 +1,6 @@
import Link from 'next/link'
import Image from 'react-bootstrap/Image'
import { abbrNum, numWithUnits } from '../lib/format'
import { abbrNum, numWithUnits } from '@/lib/format'
import styles from './item.module.css'
import userStyles from './user-header.module.css'
import React, { useEffect, useMemo, useState } from 'react'
@ -9,7 +9,7 @@ import MoreFooter from './more-footer'
import { useData } from './use-data'
import Hat from './hat'
import { useMe } from './me'
import { MEDIA_URL } from '../lib/constants'
import { MEDIA_URL } from '@/lib/constants'
// all of this nonsense is to show the stat we are sorting by first
const Stacked = ({ user }) => (user.optional.stacked !== null && <span>{abbrNum(user.optional.stacked)} stacked</span>)

View File

@ -1,7 +1,7 @@
import { Badge, Button, Card } from 'react-bootstrap'
import styles from '../styles/wallet.module.css'
import Plug from '../svgs/plug.svg'
import Gear from '../svgs/settings-5-fill.svg'
import styles from '@/styles/wallet.module.css'
import Plug from '@/svgs/plug.svg'
import Gear from '@/svgs/settings-5-fill.svg'
import Link from 'next/link'
import CancelButton from './cancel-button'
import { SubmitButton } from './form'

View File

@ -1,7 +1,7 @@
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
import { LNbitsProvider, useLNbits } from './lnbits'
import { NWCProvider, useNWC } from './nwc'
import { useToast, withToastFlow } from '../toast'
import { useToast, withToastFlow } from '@/components/toast'
import { gql, useMutation } from '@apollo/client'
const WebLNContext = createContext({})

View File

@ -2,7 +2,7 @@
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
import { Relay, finalizeEvent, nip04 } from 'nostr-tools'
import { parseNwcUrl } from '../../lib/url'
import { parseNwcUrl } from '@/lib/url'
const NWCContext = createContext()

28
jsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/api/*": [
"api/*"
],
"@/lib/*": [
"lib/*"
],
"@/fragments/*": [
"fragments/*"
],
"@/pages/*": [
"pages/*"
],
"@/components/*": [
"components/*"
],
"@/styles/*": [
"styles/*"
],
"@/svgs/*": [
"svgs/*"
],
}
}
}

143
lib/push-notifications.js Normal file
View File

@ -0,0 +1,143 @@
import { sendUserNotification } from '@/api/webPush'
import { ANON_USER_ID } from '@/lib/constants'
import { msatsToSats, numWithUnits } from '@/lib/format'
export const notifyUserSubscribers = async ({ models, item }) => {
try {
const isPost = !!item.title
const userSubs = await models.userSubscription.findMany({
where: {
followeeId: Number(item.userId),
[isPost ? 'postsSubscribedAt' : 'commentsSubscribedAt']: { not: null }
},
include: {
followee: true
}
})
const subType = isPost ? 'POST' : 'COMMENT'
const tag = `FOLLOW-${item.userId}-${subType}`
await Promise.allSettled(userSubs.map(({ followerId, followee }) => sendUserNotification(followerId, {
title: `@${followee.name} ${isPost ? 'created a post' : 'replied to a post'}`,
body: isPost ? item.title : item.text,
item,
data: { followeeName: followee.name, subType },
tag
})))
} catch (err) {
console.error(err)
}
}
export const notifyTerritorySubscribers = async ({ models, item }) => {
try {
const isPost = !!item.title
const { subName } = item
// only notify on posts in subs
if (!isPost || !subName) return
const territorySubs = await models.subSubscription.findMany({
where: {
subName
}
})
const author = await models.user.findUnique({ where: { id: item.userId } })
const tag = `TERRITORY_POST-${subName}`
await Promise.allSettled(
territorySubs
// don't send push notification to author itself
.filter(({ userId }) => userId !== author.id)
.map(({ userId }) =>
sendUserNotification(userId, {
title: `@${author.name} created a post in ~${subName}`,
body: item.title,
item,
data: { subName },
tag
})))
} catch (err) {
console.error(err)
}
}
export const notifyItemParents = async ({ models, item, me }) => {
try {
const user = await models.user.findUnique({ where: { id: me?.id || ANON_USER_ID } })
const parents = await models.$queryRawUnsafe(
'SELECT DISTINCT p."userId" FROM "Item" i JOIN "Item" p ON p.path @> i.path WHERE i.id = $1 and p."userId" <> $2 ' +
'AND NOT EXISTS (SELECT 1 FROM "Mute" m WHERE m."muterId" = p."userId" AND m."mutedId" = $2)',
Number(item.parentId), Number(user.id))
Promise.allSettled(
parents.map(({ userId }) => sendUserNotification(userId, {
title: `@${user.name} replied to you`,
body: item.text,
item,
tag: 'REPLY'
}))
)
} catch (err) {
console.error(err)
}
}
export const notifyZapped = async ({ models, id }) => {
try {
const updatedItem = await models.item.findUnique({ where: { id: Number(id) } })
const forwards = await models.itemForward.findMany({ where: { itemId: Number(id) } })
const userPromises = forwards.map(fwd => models.user.findUnique({ where: { id: fwd.userId } }))
const userResults = await Promise.allSettled(userPromises)
const mappedForwards = forwards.map((fwd, index) => ({ ...fwd, user: userResults[index].value ?? null }))
let forwardedSats = 0
let forwardedUsers = ''
if (mappedForwards.length) {
forwardedSats = Math.floor(msatsToSats(updatedItem.msats) * mappedForwards.map(fwd => fwd.pct).reduce((sum, cur) => sum + cur) / 100)
forwardedUsers = mappedForwards.map(fwd => `@${fwd.user.name}`).join(', ')
}
let notificationTitle
if (updatedItem.title) {
if (forwards.length > 0) {
notificationTitle = `your post forwarded ${numWithUnits(forwardedSats)} to ${forwardedUsers}`
} else {
notificationTitle = `your post stacked ${numWithUnits(msatsToSats(updatedItem.msats))}`
}
} else {
if (forwards.length > 0) {
// I don't think this case is possible
notificationTitle = `your reply forwarded ${numWithUnits(forwardedSats)} to ${forwardedUsers}`
} else {
notificationTitle = `your reply stacked ${numWithUnits(msatsToSats(updatedItem.msats))}`
}
}
await sendUserNotification(updatedItem.userId, {
title: notificationTitle,
body: updatedItem.title ? updatedItem.title : updatedItem.text,
item: updatedItem,
tag: `TIP-${updatedItem.id}`
})
// send push notifications to forwarded recipients
if (mappedForwards.length) {
await Promise.allSettled(mappedForwards.map(forward => sendUserNotification(forward.user.id, {
title: `you were forwarded ${numWithUnits(msatsToSats(updatedItem.msats) * forward.pct / 100)}`,
body: updatedItem.title ?? updatedItem.text,
item: updatedItem,
tag: `FORWARDEDTIP-${updatedItem.id}`
})))
}
} catch (err) {
console.error(err)
}
}
export const notifyTerritoryTransfer = async ({ models, sub, to }) => {
try {
await sendUserNotification(to.id, {
title: `~${sub.name} was transferred to you`,
tag: `TERRITORY_TRANSFER-${sub.name}`
})
} catch (err) {
console.error(err)
}
}

View File

@ -1,4 +1,4 @@
import getSSRApolloClient from '../api/ssrApollo'
import getSSRApolloClient from '@/api/ssrApollo'
const SITE_URL = 'https://stacker.news'
const SITE_TITLE = 'stacker news'

View File

@ -7,8 +7,8 @@ import {
import { SUPPORTED_CURRENCIES } from './currency'
import { NOSTR_MAX_RELAY_NUM, NOSTR_PUBKEY_BECH32, NOSTR_PUBKEY_HEX } from './nostr'
import { msatsToSats, numWithUnits, abbrNum, ensureB64 } from './format'
import * as usersFragments from '../fragments/users'
import * as subsFragments from '../fragments/subs'
import * as usersFragments from '@/fragments/users'
import * as subsFragments from '@/fragments/subs'
import { isInvoicableMacaroon, isInvoiceMacaroon } from './macaroon'
import { parseNwcUrl } from './url'
import { datePivot } from './time'

View File

@ -2,7 +2,7 @@ import webPush from 'web-push'
import removeMd from 'remove-markdown'
import { ANON_USER_ID, COMMENT_DEPTH_LIMIT, FOUND_BLURBS, LOST_BLURBS } from './constants'
import { msatsToSats, numWithUnits } from './format'
import models from '../api/models'
import models from '@/api/models'
const webPushEnabled = process.env.NODE_ENV === 'production' ||
(process.env.VAPID_MAILTO && process.env.NEXT_PUBLIC_VAPID_PUBKEY && process.env.VAPID_PRIVKEY)
@ -250,7 +250,7 @@ export const notifyMention = async (userId, item) => {
body: item.text,
item,
tag: 'MENTION'
}).catch(console.error)
})
} catch (err) {
console.error(err)
}
@ -258,7 +258,15 @@ export const notifyMention = async (userId, item) => {
export const notifyReferral = async (userId) => {
try {
await sendUserNotification(userId, { title: 'someone joined via one of your referral links', tag: 'REFERRAL' }).catch(console.error)
await sendUserNotification(userId, { title: 'someone joined via one of your referral links', tag: 'REFERRAL' })
} catch (err) {
console.error(err)
}
}
export const notifyInvite = async (userId) => {
try {
await sendUserNotification(userId, { title: 'your invite has been redeemed', tag: 'INVITE' })
} catch (err) {
console.error(err)
}
@ -315,7 +323,7 @@ export async function notifyNewStreak (userId, streak) {
title: 'you found a cowboy hat',
body: blurb,
tag: 'STREAK-FOUND'
}).catch(console.error)
})
} catch (err) {
console.error(err)
}

Some files were not shown because too many files have changed in this diff Show More