Compare commits
5 Commits
967b5b74fb
...
8329da1f56
Author | SHA1 | Date | |
---|---|---|---|
|
8329da1f56 | ||
|
b1f850ee0e | ||
|
8a19fc0905 | ||
|
286f53f2b3 | ||
|
cbcae1d128 |
@ -720,7 +720,7 @@ export default {
|
|||||||
} else if (authType === 'nostr') {
|
} else if (authType === 'nostr') {
|
||||||
user = await models.user.update({ where: { id: me.id }, data: { hideNostr: true, nostrAuthPubkey: null } })
|
user = await models.user.update({ where: { id: me.id }, data: { hideNostr: true, nostrAuthPubkey: null } })
|
||||||
} else if (authType === 'email') {
|
} else if (authType === 'email') {
|
||||||
user = await models.user.update({ where: { id: me.id }, data: { email: null, emailVerified: null } })
|
user = await models.user.update({ where: { id: me.id }, data: { email: null, emailVerified: null, emailHash: null } })
|
||||||
} else {
|
} else {
|
||||||
throw new GraphQLError('no such account', { extensions: { code: 'BAD_INPUT' } })
|
throw new GraphQLError('no such account', { extensions: { code: 'BAD_INPUT' } })
|
||||||
}
|
}
|
||||||
|
@ -108,3 +108,5 @@ tsmith123,pr,#1207,#837,easy,high,1,,180k,stickymarch60@walletofsatoshi.com,2024
|
|||||||
SatsAllDay,pr,#1214,#1199,good-first-issue,,,,20k,weareallsatoshi@getalby.com,2024-06-03
|
SatsAllDay,pr,#1214,#1199,good-first-issue,,,,20k,weareallsatoshi@getalby.com,2024-06-03
|
||||||
SatsAllDay,pr,#1197,#1192,medium,,,,250k,weareallsatoshi@getalby.com,2024-06-03
|
SatsAllDay,pr,#1197,#1192,medium,,,,250k,weareallsatoshi@getalby.com,2024-06-03
|
||||||
tsmith123,pr,#1216,#1213,easy,,1,,90k,stickymarch60@walletofsatoshi.com,2024-06-03
|
tsmith123,pr,#1216,#1213,easy,,1,,90k,stickymarch60@walletofsatoshi.com,2024-06-03
|
||||||
|
tsmith123,pr,#1231,#1230,good-first-issue,,,,20k,stickymarch60@walletofsatoshi.com,2024-06-13
|
||||||
|
felipebueno,issue,#1231,#1230,good-first-issue,,,,2k,felipebueno@getalby.com,2024-06-13
|
||||||
|
|
@ -138,3 +138,11 @@ export function WalletSecurityBanner () {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function AuthBanner () {
|
||||||
|
return (
|
||||||
|
<Alert className={`${styles.banner} mt-0`} key='info' variant='danger'>
|
||||||
|
Please add a second auth method to avoid losing access to your account.
|
||||||
|
</Alert>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -25,7 +25,7 @@ import Skull from '@/svgs/death-skull.svg'
|
|||||||
import { commentSubTreeRootId } from '@/lib/item'
|
import { commentSubTreeRootId } from '@/lib/item'
|
||||||
import Pin from '@/svgs/pushpin-fill.svg'
|
import Pin from '@/svgs/pushpin-fill.svg'
|
||||||
import LinkToContext from './link-to-context'
|
import LinkToContext from './link-to-context'
|
||||||
import { ItemContextProvider } from './item'
|
import { ItemContextProvider, useItemContext } from './item'
|
||||||
|
|
||||||
function Parent ({ item, rootText }) {
|
function Parent ({ item, rootText }) {
|
||||||
const root = useRoot()
|
const root = useRoot()
|
||||||
@ -144,11 +144,7 @@ export default function Comment ({
|
|||||||
onTouchStart={() => ref.current.classList.add('outline-new-comment-unset')}
|
onTouchStart={() => ref.current.classList.add('outline-new-comment-unset')}
|
||||||
>
|
>
|
||||||
<div className={`${itemStyles.item} ${styles.item}`}>
|
<div className={`${itemStyles.item} ${styles.item}`}>
|
||||||
{item.outlawed && !me?.privates?.wildWestMode
|
<ZapIcon item={item} pin={pin} me={me} />
|
||||||
? <Skull className={styles.dontLike} width={24} height={24} />
|
|
||||||
: item.meDontLikeSats > item.meSats
|
|
||||||
? <DownZap width={24} height={24} className={styles.dontLike} item={item} />
|
|
||||||
: pin ? <Pin width={22} height={22} className={styles.pin} /> : <UpVote item={item} className={styles.upvote} />}
|
|
||||||
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
|
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
|
||||||
<div className='d-flex align-items-center'>
|
<div className='d-flex align-items-center'>
|
||||||
{item.user?.meMute && !includeParent && collapse === 'yep'
|
{item.user?.meMute && !includeParent && collapse === 'yep'
|
||||||
@ -250,6 +246,20 @@ export default function Comment ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ZapIcon ({ item, pin }) {
|
||||||
|
const me = useMe()
|
||||||
|
const { pendingSats, pendingDownSats } = useItemContext()
|
||||||
|
|
||||||
|
const meSats = item.meSats + pendingSats
|
||||||
|
const downSats = item.meDontLikeSats + pendingDownSats
|
||||||
|
|
||||||
|
return item.outlawed && !me?.privates?.wildWestMode
|
||||||
|
? <Skull className={styles.dontLike} width={24} height={24} />
|
||||||
|
: downSats > meSats
|
||||||
|
? <DownZap width={24} height={24} className={styles.dontLike} item={item} />
|
||||||
|
: pin ? <Pin width={22} height={22} className={styles.pin} /> : <UpVote item={item} className={styles.upvote} />
|
||||||
|
}
|
||||||
|
|
||||||
export function CommentSkeleton ({ skeletonChildren }) {
|
export function CommentSkeleton ({ skeletonChildren }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.comment}>
|
<div className={styles.comment}>
|
||||||
|
@ -4,18 +4,24 @@ import { useToast } from './toast'
|
|||||||
import ItemAct from './item-act'
|
import ItemAct from './item-act'
|
||||||
import AccordianItem from './accordian-item'
|
import AccordianItem from './accordian-item'
|
||||||
import Flag from '@/svgs/flag-fill.svg'
|
import Flag from '@/svgs/flag-fill.svg'
|
||||||
import { useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
import getColor from '@/lib/rainbow'
|
import getColor from '@/lib/rainbow'
|
||||||
import { gql, useMutation } from '@apollo/client'
|
import { gql, useMutation } from '@apollo/client'
|
||||||
|
import { useItemContext } from './item'
|
||||||
|
import { useLightning } from './lightning'
|
||||||
|
|
||||||
export function DownZap ({ item, ...props }) {
|
export function DownZap ({ item, ...props }) {
|
||||||
|
const { pendingDownSats } = useItemContext()
|
||||||
const { meDontLikeSats } = item
|
const { meDontLikeSats } = item
|
||||||
const style = useMemo(() => (meDontLikeSats
|
|
||||||
|
const downSats = meDontLikeSats + pendingDownSats
|
||||||
|
|
||||||
|
const style = useMemo(() => (downSats
|
||||||
? {
|
? {
|
||||||
fill: getColor(meDontLikeSats),
|
fill: getColor(downSats),
|
||||||
filter: `drop-shadow(0 0 6px ${getColor(meDontLikeSats)}90)`
|
filter: `drop-shadow(0 0 6px ${getColor(downSats)}90)`
|
||||||
}
|
}
|
||||||
: undefined), [meDontLikeSats])
|
: undefined), [downSats])
|
||||||
return (
|
return (
|
||||||
<DownZapper item={item} As={({ ...oprops }) => <Flag {...props} {...oprops} style={style} />} />
|
<DownZapper item={item} As={({ ...oprops }) => <Flag {...props} {...oprops} style={style} />} />
|
||||||
)
|
)
|
||||||
@ -24,6 +30,17 @@ export function DownZap ({ item, ...props }) {
|
|||||||
function DownZapper ({ item, As, children }) {
|
function DownZapper ({ item, As, children }) {
|
||||||
const toaster = useToast()
|
const toaster = useToast()
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
|
const strike = useLightning()
|
||||||
|
const { setPendingDownSats } = useItemContext()
|
||||||
|
|
||||||
|
const optimisticUpdate = useCallback((sats, { onClose } = {}) => {
|
||||||
|
setPendingDownSats(pendingSats => pendingSats + sats)
|
||||||
|
strike()
|
||||||
|
onClose?.()
|
||||||
|
return () => {
|
||||||
|
setPendingDownSats(pendingSats => pendingSats - sats)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<As
|
<As
|
||||||
@ -31,7 +48,7 @@ function DownZapper ({ item, As, children }) {
|
|||||||
try {
|
try {
|
||||||
showModal(onClose =>
|
showModal(onClose =>
|
||||||
<ItemAct
|
<ItemAct
|
||||||
onClose={onClose} item={item} down
|
onClose={onClose} item={item} down optimisticUpdate={optimisticUpdate}
|
||||||
>
|
>
|
||||||
<AccordianItem
|
<AccordianItem
|
||||||
header='what is a downzap?' body={
|
header='what is a downzap?' body={
|
||||||
|
@ -37,7 +37,7 @@ export default function ItemInfo ({
|
|||||||
const [hasNewComments, setHasNewComments] = useState(false)
|
const [hasNewComments, setHasNewComments] = useState(false)
|
||||||
const [meTotalSats, setMeTotalSats] = useState(0)
|
const [meTotalSats, setMeTotalSats] = useState(0)
|
||||||
const root = useRoot()
|
const root = useRoot()
|
||||||
const { pendingSats, pendingCommentSats } = useItemContext()
|
const { pendingSats, pendingCommentSats, pendingDownSats } = useItemContext()
|
||||||
const sub = item?.sub || root?.sub
|
const sub = item?.sub || root?.sub
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -58,6 +58,8 @@ export default function ItemInfo ({
|
|||||||
const rootReply = item.path.split('.').length === 2
|
const rootReply = item.path.split('.').length === 2
|
||||||
const canPin = (isPost && mySub) || (myPost && rootReply)
|
const canPin = (isPost && mySub) || (myPost && rootReply)
|
||||||
|
|
||||||
|
const downSats = item.meDontLikeSats + pendingDownSats
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className || `${styles.other}`}>
|
<div className={className || `${styles.other}`}>
|
||||||
{!(item.position && (pinnable || !item.subName)) && !(!item.parentId && Number(item.user?.id) === USER_ID.ad) &&
|
{!(item.position && (pinnable || !item.subName)) && !(!item.parentId && Number(item.user?.id) === USER_ID.ad) &&
|
||||||
@ -68,8 +70,8 @@ export default function ItemInfo ({
|
|||||||
unitPlural: 'stackers'
|
unitPlural: 'stackers'
|
||||||
})} ${item.mine
|
})} ${item.mine
|
||||||
? `\\ ${numWithUnits(item.meSats, { abbreviate: false })} to post`
|
? `\\ ${numWithUnits(item.meSats, { abbreviate: false })} to post`
|
||||||
: `(${numWithUnits(meTotalSats, { abbreviate: false })}${item.meDontLikeSats
|
: `(${numWithUnits(meTotalSats, { abbreviate: false })}${downSats
|
||||||
? ` & ${numWithUnits(item.meDontLikeSats, { abbreviate: false, unitSingular: 'downsat', unitPlural: 'downsats' })}`
|
? ` & ${numWithUnits(downSats, { abbreviate: false, unitSingular: 'downsat', unitPlural: 'downsats' })}`
|
||||||
: ''} from me)`} `}
|
: ''} from me)`} `}
|
||||||
>
|
>
|
||||||
{numWithUnits(item.sats + pendingSats)}
|
{numWithUnits(item.sats + pendingSats)}
|
||||||
@ -179,7 +181,7 @@ export default function ItemInfo ({
|
|||||||
<CrosspostDropdownItem item={item} />}
|
<CrosspostDropdownItem item={item} />}
|
||||||
{me && !item.position &&
|
{me && !item.position &&
|
||||||
!item.mine && !item.deletedAt &&
|
!item.mine && !item.deletedAt &&
|
||||||
(item.meDontLikeSats > meTotalSats
|
(downSats > meTotalSats
|
||||||
? <DropdownItemUpVote item={item} />
|
? <DropdownItemUpVote item={item} />
|
||||||
: <DontLikeThisDropdownItem item={item} />)}
|
: <DontLikeThisDropdownItem item={item} />)}
|
||||||
{me && sub && !item.mine && !item.outlawed && Number(me.id) === Number(sub.userId) && sub.moderated &&
|
{me && sub && !item.mine && !item.outlawed && Number(me.id) === Number(sub.userId) && sub.moderated &&
|
||||||
|
@ -49,7 +49,9 @@ const ItemContext = createContext({
|
|||||||
pendingSats: 0,
|
pendingSats: 0,
|
||||||
setPendingSats: undefined,
|
setPendingSats: undefined,
|
||||||
pendingVote: undefined,
|
pendingVote: undefined,
|
||||||
setPendingVote: undefined
|
setPendingVote: undefined,
|
||||||
|
pendingDownSats: 0,
|
||||||
|
setPendingDownSats: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
export const ItemContextProvider = ({ children }) => {
|
export const ItemContextProvider = ({ children }) => {
|
||||||
@ -57,6 +59,7 @@ export const ItemContextProvider = ({ children }) => {
|
|||||||
const [pendingSats, innerSetPendingSats] = useState(0)
|
const [pendingSats, innerSetPendingSats] = useState(0)
|
||||||
const [pendingCommentSats, innerSetPendingCommentSats] = useState(0)
|
const [pendingCommentSats, innerSetPendingCommentSats] = useState(0)
|
||||||
const [pendingVote, setPendingVote] = useState()
|
const [pendingVote, setPendingVote] = useState()
|
||||||
|
const [pendingDownSats, setPendingDownSats] = useState(0)
|
||||||
|
|
||||||
// cascade comment sats up to root context
|
// cascade comment sats up to root context
|
||||||
const setPendingSats = useCallback((sats) => {
|
const setPendingSats = useCallback((sats) => {
|
||||||
@ -76,9 +79,11 @@ export const ItemContextProvider = ({ children }) => {
|
|||||||
pendingCommentSats,
|
pendingCommentSats,
|
||||||
setPendingCommentSats,
|
setPendingCommentSats,
|
||||||
pendingVote,
|
pendingVote,
|
||||||
setPendingVote
|
setPendingVote,
|
||||||
|
pendingDownSats,
|
||||||
|
setPendingDownSats
|
||||||
}),
|
}),
|
||||||
[pendingSats, setPendingSats, pendingCommentSats, setPendingCommentSats, pendingVote, setPendingVote])
|
[pendingSats, setPendingSats, pendingCommentSats, setPendingCommentSats, pendingVote, setPendingVote, pendingDownSats, setPendingDownSats])
|
||||||
return <ItemContext.Provider value={value}>{children}</ItemContext.Provider>
|
return <ItemContext.Provider value={value}>{children}</ItemContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,13 +106,7 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s
|
|||||||
</div>)
|
</div>)
|
||||||
: <div />}
|
: <div />}
|
||||||
<div className={`${styles.item} ${siblingComments ? 'pt-3' : ''}`}>
|
<div className={`${styles.item} ${siblingComments ? 'pt-3' : ''}`}>
|
||||||
{item.position && (pinnable || !item.subName)
|
<ZapIcon item={item} pinnable={pinnable} />
|
||||||
? <Pin width={24} height={24} className={styles.pin} />
|
|
||||||
: item.meDontLikeSats > item.meSats
|
|
||||||
? <DownZap width={24} height={24} className={styles.dontLike} item={item} />
|
|
||||||
: Number(item.user?.id) === USER_ID.ad
|
|
||||||
? <AdIcon width={24} height={24} className={styles.ad} />
|
|
||||||
: <UpVote item={item} className={styles.upvote} />}
|
|
||||||
<div className={styles.hunk}>
|
<div className={styles.hunk}>
|
||||||
<div className={`${styles.main} flex-wrap`}>
|
<div className={`${styles.main} flex-wrap`}>
|
||||||
<Link
|
<Link
|
||||||
@ -229,6 +228,21 @@ export function ItemSkeleton ({ rank, children, showUpvote = true }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ZapIcon ({ item, pinnable }) {
|
||||||
|
const { pendingSats, pendingDownSats } = useItemContext()
|
||||||
|
|
||||||
|
const meSats = item.meSats + pendingSats
|
||||||
|
const downSats = item.meDontLikeSats + pendingDownSats
|
||||||
|
|
||||||
|
return item.position && (pinnable || !item.subName)
|
||||||
|
? <Pin width={24} height={24} className={styles.pin} />
|
||||||
|
: downSats > meSats
|
||||||
|
? <DownZap width={24} height={24} className={styles.dontLike} item={item} />
|
||||||
|
: Number(item.user?.id) === USER_ID.ad
|
||||||
|
? <AdIcon width={24} height={24} className={styles.ad} />
|
||||||
|
: <UpVote item={item} className={styles.upvote} />
|
||||||
|
}
|
||||||
|
|
||||||
function PollIndicator ({ item }) {
|
function PollIndicator ({ item }) {
|
||||||
const hasExpiration = !!item.pollExpiresAt
|
const hasExpiration = !!item.pollExpiresAt
|
||||||
const timeRemaining = timeLeft(new Date(item.pollExpiresAt))
|
const timeRemaining = timeLeft(new Date(item.pollExpiresAt))
|
||||||
|
@ -56,12 +56,23 @@ const TipPopover = ({ target, show, handleClose }) => (
|
|||||||
|
|
||||||
export function DropdownItemUpVote ({ item }) {
|
export function DropdownItemUpVote ({ item }) {
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
|
const { setPendingSats } = useItemContext()
|
||||||
|
const strike = useLightning()
|
||||||
|
|
||||||
|
const optimisticUpdate = useCallback((sats, { onClose } = {}) => {
|
||||||
|
setPendingSats(pendingSats => pendingSats + sats)
|
||||||
|
strike()
|
||||||
|
onClose?.()
|
||||||
|
return () => {
|
||||||
|
setPendingSats(pendingSats => pendingSats - sats)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
showModal(onClose =>
|
showModal(onClose =>
|
||||||
<ItemAct onClose={onClose} item={item} />)
|
<ItemAct onClose={onClose} item={item} optimisticUpdate={optimisticUpdate} />)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className='text-success'>zap</span>
|
<span className='text-success'>zap</span>
|
||||||
|
@ -30,6 +30,7 @@ import { INVOICE_RETENTION_DAYS, ZAP_UNDO_DELAY_MS } from '@/lib/constants'
|
|||||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||||
import { useField } from 'formik'
|
import { useField } from 'formik'
|
||||||
import styles from './settings.module.css'
|
import styles from './settings.module.css'
|
||||||
|
import { AuthBanner } from '@/components/banners'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps({ query: SETTINGS, authRequired: true })
|
export const getServerSideProps = getGetServerSideProps({ query: SETTINGS, authRequired: true })
|
||||||
|
|
||||||
@ -106,7 +107,7 @@ export default function Settings ({ ssrData }) {
|
|||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className='pb-3 w-100 mt-2' style={{ maxWidth: '600px' }}>
|
<div className='pb-3 w-100 mt-2' style={{ maxWidth: '600px' }}>
|
||||||
{hasOnlyOneAuthMethod(settings?.authMethods) && <div className={styles.alert}>Please add a second auth method to avoid losing access to your account.</div>}
|
{hasOnlyOneAuthMethod(settings?.authMethods) && <AuthBanner />}
|
||||||
<SettingsHeader />
|
<SettingsHeader />
|
||||||
<Form
|
<Form
|
||||||
initial={{
|
initial={{
|
||||||
@ -1007,10 +1008,9 @@ const ZapUndosField = () => {
|
|||||||
zap undos
|
zap undos
|
||||||
<Info>
|
<Info>
|
||||||
<ul className='fw-bold'>
|
<ul className='fw-bold'>
|
||||||
<li>An undo button is shown after every zap that exceeds or is equal to the threshold</li>
|
<li>After every zap that exceeds or is equal to the threshold, the bolt will pulse</li>
|
||||||
<li>The button is shown for {ZAP_UNDO_DELAY_MS / 1000} seconds</li>
|
<li>You can undo the zap if you click the bolt while it's pulsing</li>
|
||||||
<li>The button is only shown for zaps from the custodial wallet</li>
|
<li>The bolt will pulse for {ZAP_UNDO_DELAY_MS / 1000} seconds</li>
|
||||||
<li>Use a budget or manual approval with attached wallets</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</Info>
|
</Info>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user