2024-03-20 00:37:31 +00:00
import { Checkbox , Form , Input , SubmitButton , Select , VariableInput , CopyInput } from '@/components/form'
2023-07-24 18:35:05 +00:00
import Alert from 'react-bootstrap/Alert'
import Button from 'react-bootstrap/Button'
import InputGroup from 'react-bootstrap/InputGroup'
2024-03-20 00:37:31 +00:00
import { CenterLayout } from '@/components/layout'
2024-02-14 19:33:31 +00:00
import { useState , useMemo } from 'react'
2022-06-02 22:55:23 +00:00
import { gql , useMutation , useQuery } from '@apollo/client'
2024-03-20 00:37:31 +00:00
import { getGetServerSideProps } from '@/api/ssrApollo'
import LoginButton from '@/components/login-button'
2023-07-29 19:38:20 +00:00
import { signIn } from 'next-auth/react'
2024-03-20 00:37:31 +00:00
import { LightningAuth } from '@/components/lightning-auth'
import { SETTINGS , SET _SETTINGS } from '@/fragments/users'
2022-06-02 22:55:23 +00:00
import { useRouter } from 'next/router'
2024-03-20 00:37:31 +00:00
import Info from '@/components/info'
2022-12-01 21:31:04 +00:00
import Link from 'next/link'
2024-03-20 00:37:31 +00:00
import AccordianItem from '@/components/accordian-item'
2023-02-01 15:54:08 +00:00
import { bech32 } from 'bech32'
2024-03-20 00:37:31 +00:00
import { NOSTR _MAX _RELAY _NUM , NOSTR _PUBKEY _BECH32 , DEFAULT _CROSSPOSTING _RELAYS } from '@/lib/nostr'
import { emailSchema , lastAuthRemovalSchema , settingsSchema } from '@/lib/validate'
import { SUPPORTED _CURRENCIES } from '@/lib/currency'
import PageLoading from '@/components/page-loading'
import { useShowModal } from '@/components/modal'
import { authErrorMessage } from '@/components/login'
import { NostrAuth } from '@/components/nostr-auth'
import { useToast } from '@/components/toast'
import { useLogger } from '@/components/logger'
import { useMe } from '@/components/me'
import { INVOICE _RETENTION _DAYS } from '@/lib/constants'
2024-03-14 20:32:34 +00:00
import { OverlayTrigger , Tooltip } from 'react-bootstrap'
2024-03-20 00:37:31 +00:00
import DeleteIcon from '@/svgs/delete-bin-line.svg'
2024-03-25 00:46:12 +00:00
import { useField } from 'formik'
2022-04-21 22:50:02 +00:00
2023-08-28 17:52:15 +00:00
export const getServerSideProps = getGetServerSideProps ( { query : SETTINGS , authRequired : true } )
2021-10-30 16:20:11 +00:00
2023-02-01 15:54:08 +00:00
function bech32encode ( hexString ) {
return bech32 . encode ( 'npub' , bech32 . toWords ( Buffer . from ( hexString , 'hex' ) ) )
}
2023-07-23 15:08:43 +00:00
export default function Settings ( { ssrData } ) {
2023-08-25 23:21:51 +00:00
const toaster = useToast ( )
2023-09-18 18:57:02 +00:00
const me = useMe ( )
2022-09-06 13:01:49 +00:00
const [ setSettings ] = useMutation ( SET _SETTINGS , {
update ( cache , { data : { setSettings } } ) {
cache . modify ( {
id : 'ROOT_QUERY' ,
fields : {
settings ( ) {
return setSettings
}
}
} )
}
}
2021-10-30 16:20:11 +00:00
)
2023-09-18 23:00:16 +00:00
const logger = useLogger ( )
2021-10-30 16:20:11 +00:00
2022-06-02 22:55:23 +00:00
const { data } = useQuery ( SETTINGS )
2024-02-14 19:33:31 +00:00
const { settings : { privates : settings } } = useMemo ( ( ) => data ? ? ssrData , [ data , ssrData ] )
2023-07-23 15:08:43 +00:00
if ( ! data && ! ssrData ) return < PageLoading / >
2022-06-02 22:55:23 +00:00
2021-10-30 16:20:11 +00:00
return (
2023-07-23 15:08:43 +00:00
< CenterLayout >
2022-06-02 22:55:23 +00:00
< div className = 'py-3 w-100' >
2023-08-13 19:12:18 +00:00
< h2 className = 'mb-2 text-start' > settings < / h 2 >
2022-06-02 22:55:23 +00:00
< Form
initial = { {
tipDefault : settings ? . tipDefault || 21 ,
2022-12-09 19:25:38 +00:00
turboTipping : settings ? . turboTipping ,
2024-03-26 16:57:20 +00:00
zapUndos : settings ? . zapUndos || ( settings ? . tipDefault ? 100 * settings . tipDefault : 2100 ) ,
2024-03-25 08:47:50 +00:00
zapUndosEnabled : settings ? . zapUndos !== null ,
2022-09-12 23:18:23 +00:00
fiatCurrency : settings ? . fiatCurrency || 'USD' ,
2023-10-21 00:09:41 +00:00
withdrawMaxFeeDefault : settings ? . withdrawMaxFeeDefault ,
2022-06-02 22:55:23 +00:00
noteItemSats : settings ? . noteItemSats ,
noteEarning : settings ? . noteEarning ,
noteAllDescendants : settings ? . noteAllDescendants ,
noteMentions : settings ? . noteMentions ,
noteDeposits : settings ? . noteDeposits ,
2024-03-25 20:20:11 +00:00
noteWithdrawals : settings ? . noteWithdrawals ,
2022-06-02 22:55:23 +00:00
noteInvites : settings ? . noteInvites ,
2022-08-30 21:50:47 +00:00
noteJobIndicator : settings ? . noteJobIndicator ,
2023-02-01 14:44:35 +00:00
noteCowboyHat : settings ? . noteCowboyHat ,
2023-09-12 15:31:46 +00:00
noteForwardedSats : settings ? . noteForwardedSats ,
2022-09-21 19:57:36 +00:00
hideInvoiceDesc : settings ? . hideInvoiceDesc ,
2023-11-09 17:50:43 +00:00
autoDropBolt11s : settings ? . autoDropBolt11s ,
2022-12-01 21:31:04 +00:00
hideFromTopUsers : settings ? . hideFromTopUsers ,
2023-05-01 21:49:47 +00:00
hideCowboyHat : settings ? . hideCowboyHat ,
2024-02-14 19:33:31 +00:00
hideGithub : settings ? . hideGithub ,
hideNostr : settings ? . hideNostr ,
hideTwitter : settings ? . hideTwitter ,
2023-10-03 18:05:04 +00:00
imgproxyOnly : settings ? . imgproxyOnly ,
2022-09-27 21:19:15 +00:00
wildWestMode : settings ? . wildWestMode ,
2023-01-07 00:53:09 +00:00
greeterMode : settings ? . greeterMode ,
2024-02-10 02:35:32 +00:00
nsfwMode : settings ? . nsfwMode ,
2023-02-01 15:54:08 +00:00
nostrPubkey : settings ? . nostrPubkey ? bech32encode ( settings . nostrPubkey ) : '' ,
2023-10-04 18:47:09 +00:00
nostrCrossposting : settings ? . nostrCrossposting ,
2023-08-23 20:29:55 +00:00
nostrRelays : settings ? . nostrRelays ? . length ? settings ? . nostrRelays : [ '' ] ,
2023-09-12 17:19:26 +00:00
hideBookmarks : settings ? . hideBookmarks ,
2023-09-18 18:57:02 +00:00
hideWalletBalance : settings ? . hideWalletBalance ,
2023-09-18 23:00:16 +00:00
diagnostics : settings ? . diagnostics ,
2024-03-17 14:57:50 +00:00
hideIsContributor : settings ? . hideIsContributor ,
noReferralLinks : settings ? . noReferralLinks
2022-06-02 22:55:23 +00:00
} }
2023-02-08 19:38:04 +00:00
schema = { settingsSchema }
2024-03-25 00:46:12 +00:00
onSubmit = { async ( { tipDefault , withdrawMaxFeeDefault , zapUndos , zapUndosEnabled , nostrPubkey , nostrRelays , ... values } ) => {
2023-01-07 00:53:09 +00:00
if ( nostrPubkey . length === 0 ) {
nostrPubkey = null
2023-02-01 15:54:08 +00:00
} else {
2023-02-08 19:38:04 +00:00
if ( NOSTR _PUBKEY _BECH32 . test ( nostrPubkey ) ) {
2023-02-01 15:54:08 +00:00
const { words } = bech32 . decode ( nostrPubkey )
nostrPubkey = Buffer . from ( bech32 . fromWords ( words ) ) . toString ( 'hex' )
}
2023-01-07 00:53:09 +00:00
}
const nostrRelaysFiltered = nostrRelays ? . filter ( word => word . trim ( ) . length > 0 )
2023-08-25 23:21:51 +00:00
try {
await setSettings ( {
variables : {
2023-11-10 01:05:35 +00:00
settings : {
tipDefault : Number ( tipDefault ) ,
withdrawMaxFeeDefault : Number ( withdrawMaxFeeDefault ) ,
2024-03-25 00:46:12 +00:00
zapUndos : zapUndosEnabled ? Number ( zapUndos ) : null ,
2023-11-10 01:05:35 +00:00
nostrPubkey ,
nostrRelays : nostrRelaysFiltered ,
... values
}
2023-08-25 23:21:51 +00:00
}
} )
toaster . success ( 'saved settings' )
} catch ( err ) {
console . error ( err )
toaster . danger ( 'failed to save settings' )
}
2022-06-02 22:55:23 +00:00
} }
>
< Input
2023-06-19 18:21:55 +00:00
label = 'zap default'
2022-06-02 22:55:23 +00:00
name = 'tipDefault'
2022-12-09 19:25:38 +00:00
groupClassName = 'mb-0'
2022-06-02 22:55:23 +00:00
required
autoFocus
append = { < InputGroup . Text className = 'text-monospace' > sats < / I n p u t G r o u p . T e x t > }
2023-06-19 18:21:55 +00:00
hint = { < small className = 'text-muted' > note : you can also press and hold the lightning bolt to zap custom amounts < / s m a l l > }
2022-06-02 22:55:23 +00:00
/ >
2022-12-09 19:25:38 +00:00
< div className = 'mb-2' >
< AccordianItem
show = { settings ? . turboTipping }
header = { < div style = { { fontWeight : 'bold' , fontSize : '92%' } } > advanced < / d i v > }
2024-02-22 00:48:42 +00:00
body = {
< >
< Checkbox
name = 'turboTipping'
label = {
< div className = 'd-flex align-items-center' > turbo zapping
< Info >
< ul className = 'fw-bold' >
< li > Makes every additional bolt click raise your total zap to another 10 x multiple of your default zap < / l i >
< li > e . g . if your zap default is 10 sats
< ul >
< li > 1 st click : 10 sats total zapped < / l i >
< li > 2 nd click : 100 sats total zapped < / l i >
< li > 3 rd click : 1000 sats total zapped < / l i >
< li > 4 th click : 10000 sats total zapped < / l i >
< li > and so on ... < / l i >
< / u l >
< / l i >
< li > You can still custom zap via long press
< ul >
< li > the next bolt click rounds up to the next greatest 10 x multiple of your default < / l i >
< / u l >
< / l i >
2022-12-09 19:25:38 +00:00
< / u l >
2024-02-22 00:48:42 +00:00
< / I n f o >
< / d i v >
}
groupClassName = 'mb-0'
/ >
2024-03-25 08:53:02 +00:00
< ZapUndosField / >
2024-02-22 00:48:42 +00:00
< / >
}
2022-12-09 19:25:38 +00:00
/ >
< / d i v >
2022-10-04 21:21:42 +00:00
< Select
2022-09-12 23:18:23 +00:00
label = 'fiat currency'
name = 'fiatCurrency'
2022-10-04 21:21:42 +00:00
size = 'sm'
2023-02-08 19:38:04 +00:00
items = { SUPPORTED _CURRENCIES }
2022-09-18 01:45:21 +00:00
required
2022-09-12 23:18:23 +00:00
/ >
2023-10-21 00:09:41 +00:00
< Input
label = 'default max fee for withdrawals'
name = 'withdrawMaxFeeDefault'
required
append = { < InputGroup . Text className = 'text-monospace' > sats < / I n p u t G r o u p . T e x t > }
/ >
2023-06-12 20:37:12 +00:00
< div className = 'form-label' > notify me when ... < / d i v >
2022-06-02 22:55:23 +00:00
< Checkbox
label = 'I stack sats from posts and comments'
name = 'noteItemSats'
groupClassName = 'mb-0'
/ >
2023-09-12 15:31:46 +00:00
< Checkbox
label = 'I get forwarded sats from a post'
name = 'noteForwardedSats'
groupClassName = 'mb-0'
/ >
2022-06-02 22:55:23 +00:00
< Checkbox
label = 'I get a daily airdrop'
name = 'noteEarning'
groupClassName = 'mb-0'
/ >
< Checkbox
label = 'someone replies to someone who replied to me'
name = 'noteAllDescendants'
groupClassName = 'mb-0'
/ >
< Checkbox
2022-12-19 22:27:52 +00:00
label = 'someone joins using my invite or referral links'
2022-06-02 22:55:23 +00:00
name = 'noteInvites'
groupClassName = 'mb-0'
/ >
< Checkbox
label = 'sats are deposited in my account'
name = 'noteDeposits'
groupClassName = 'mb-0'
/ >
2024-03-25 20:20:11 +00:00
< Checkbox
label = 'sats are withdrawn from my account'
name = 'noteWithdrawals'
groupClassName = 'mb-0'
/ >
2022-06-02 22:55:23 +00:00
< Checkbox
label = 'someone mentions me'
name = 'noteMentions'
groupClassName = 'mb-0'
/ >
< Checkbox
label = 'there is a new job'
name = 'noteJobIndicator'
2023-02-01 14:44:35 +00:00
groupClassName = 'mb-0'
/ >
< Checkbox
label = 'I find or lose a cowboy hat'
name = 'noteCowboyHat'
2022-06-02 22:55:23 +00:00
/ >
2022-08-30 21:50:47 +00:00
< div className = 'form-label' > privacy < / d i v >
< Checkbox
label = {
2022-09-21 19:57:36 +00:00
< div className = 'd-flex align-items-center' > hide invoice descriptions
2022-08-30 21:50:47 +00:00
< Info >
2023-07-24 18:35:05 +00:00
< ul className = 'fw-bold' >
2022-08-30 21:50:47 +00:00
< li > Use this if you don ' t want funding sources to be linkable to your SN identity . < / l i >
< li > It makes your invoice descriptions blank . < / l i >
2022-09-02 16:58:16 +00:00
< li > This only applies to invoices you create
2022-08-30 21:50:47 +00:00
< ul >
2022-09-02 16:58:16 +00:00
< li > lnurl - pay and lightning addresses still reference your nym < / l i >
2022-08-30 21:50:47 +00:00
< / u l >
< / l i >
< / u l >
< / I n f o >
2022-09-21 19:57:36 +00:00
< / d i v >
2022-08-30 21:50:47 +00:00
}
name = 'hideInvoiceDesc'
2022-12-01 21:31:04 +00:00
groupClassName = 'mb-0'
/ >
2023-11-09 17:50:43 +00:00
< DropBolt11sCheckbox
ssrData = { ssrData }
label = {
< div className = 'd-flex align-items-center' > autodelete withdrawal invoices
< Info >
< ul className = 'fw-bold' >
< li > use this to protect receiver privacy < / l i >
< li > applies retroactively , cannot be reversed < / l i >
< li > withdrawal invoices are kept at least { INVOICE _RETENTION _DAYS } days for security and debugging purposes < / l i >
< li > autodeletions are run a daily basis at night < / l i >
< / u l >
< / I n f o >
< / d i v >
}
name = 'autoDropBolt11s'
groupClassName = 'mb-0'
/ >
2022-12-01 21:31:04 +00:00
< Checkbox
2023-07-23 15:08:43 +00:00
label = { < > hide me from < Link href = '/top/stackers/day' > top stackers < /Link></ > }
2022-12-01 21:31:04 +00:00
name = 'hideFromTopUsers'
2023-05-01 21:49:47 +00:00
groupClassName = 'mb-0'
/ >
< Checkbox
label = { < > hide my cowboy hat < / > }
name = 'hideCowboyHat'
2023-08-15 17:55:16 +00:00
groupClassName = 'mb-0'
2023-09-12 17:19:26 +00:00
/ >
< Checkbox
label = { < > hide my wallet balance < / > }
name = 'hideWalletBalance'
groupClassName = 'mb-0'
2023-08-15 17:55:16 +00:00
/ >
< Checkbox
2023-09-18 23:44:30 +00:00
label = { < > hide my bookmarks from other stackers < / > }
name = 'hideBookmarks'
2023-08-23 20:29:55 +00:00
groupClassName = 'mb-0'
/ >
2024-02-14 19:33:31 +00:00
< Checkbox
disabled = { me . optional . githubId === null }
label = {
< div className = 'd-flex align-items-center' > hide my linked github profile
< Info >
< ul className = 'fw-bold' >
< li > Linked accounts are hidden from your profile by default < / l i >
< li > uncheck this to display your github on your profile < / l i >
{ me . optional . githubId === null &&
< div className = 'my-2' >
< li > < i > You don ' t seem to have a linked github account < / i > < / l i >
< ul > < li > If this is wrong , try unlinking / relinking < / l i > < / u l >
< / d i v > }
< / u l >
< / I n f o >
< / d i v >
}
name = 'hideGithub'
groupClassName = 'mb-0'
/ >
< Checkbox
disabled = { me . optional . nostrAuthPubkey === null }
label = {
< div className = 'd-flex align-items-center' > hide my linked nostr profile
< Info >
< ul className = 'fw-bold' >
< li > Linked accounts are hidden from your profile by default < / l i >
< li > Uncheck this to display your npub on your profile < / l i >
{ me . optional . nostrAuthPubkey === null &&
< div className = 'my-2' >
< li > You don ' t seem to have a linked nostr account < / l i >
< ul > < li > If this is wrong , try unlinking / relinking < / l i > < / u l >
< / d i v > }
< / u l >
< / I n f o >
< / d i v >
}
name = 'hideNostr'
groupClassName = 'mb-0'
/ >
< Checkbox
disabled = { me . optional . twitterId === null }
label = {
< div className = 'd-flex align-items-center' > hide my linked twitter profile
< Info >
< ul className = 'fw-bold' >
< li > Linked accounts are hidden from your profile by default < / l i >
< li > Uncheck this to display your twitter on your profile < / l i >
{ me . optional . twitterId === null &&
< div className = 'my-2' >
< i > You don ' t seem to have a linked twitter account < / i >
< ul > < li > If this is wrong , try unlinking / relinking < / l i > < / u l >
< / d i v > }
< / u l >
< / I n f o >
< / d i v >
}
name = 'hideTwitter'
groupClassName = 'mb-0'
/ >
2023-11-20 15:04:38 +00:00
{ me . optional ? . isContributor &&
2023-09-18 18:57:02 +00:00
< Checkbox
label = { < > hide that I ' m a stacker . news contributor < / > }
name = 'hideIsContributor'
groupClassName = 'mb-0'
/ > }
2023-08-23 20:29:55 +00:00
< Checkbox
2023-10-03 18:05:04 +00:00
label = {
< div className = 'd-flex align-items-center' > only load images from proxy
< Info >
< ul className = 'fw-bold' >
< li > only load images from our image proxy automatically < / l i >
< li > this prevents IP address leaks to arbitrary sites < / l i >
< li > if we fail to load an image , the raw link will be shown < / l i >
< / u l >
< / I n f o >
< / d i v >
}
name = 'imgproxyOnly'
2023-09-18 23:00:16 +00:00
groupClassName = 'mb-0'
/ >
< Checkbox
label = {
2023-09-18 23:44:30 +00:00
< div className = 'd-flex align-items-center' > allow anonymous diagnostics
2023-09-18 23:00:16 +00:00
< Info >
< ul className = 'fw-bold' >
2023-09-18 23:44:30 +00:00
< li > collect and send back anonymous diagnostics data < / l i >
< li > this information is used to fix bugs < / l i >
2023-09-18 23:00:16 +00:00
< li > this information includes :
< ul > < li > timestamps < / l i > < / u l >
< ul > < li > a randomly generated fancy name < / l i > < / u l >
< ul > < li > your user agent < / l i > < / u l >
< ul > < li > your operating system < / l i > < / u l >
< / l i >
< li > this information can not be traced back to you without your fancy name < / l i >
< li > fancy names are generated in your browser < / l i >
< / u l >
< div className = 'text-muted fst-italic' > your fancy name : { logger . name } < / d i v >
< / I n f o >
< / d i v >
}
name = 'diagnostics'
2024-03-17 14:57:50 +00:00
groupClassName = 'mb-0'
/ >
< Checkbox
label = { < > don ' t create referral links on copy < / > }
name = 'noReferralLinks'
2022-08-30 21:50:47 +00:00
/ >
2022-09-21 19:57:36 +00:00
< div className = 'form-label' > content < / d i v >
< Checkbox
label = {
< div className = 'd-flex align-items-center' > wild west mode
< Info >
2023-07-24 18:35:05 +00:00
< ul className = 'fw-bold' >
2022-09-27 21:19:15 +00:00
< li > don ' t hide flagged content < / l i >
< li > don ' t down rank flagged content < / l i >
2022-09-21 19:57:36 +00:00
< / u l >
< / I n f o >
< / d i v >
}
name = 'wildWestMode'
2022-09-27 21:19:15 +00:00
groupClassName = 'mb-0'
/ >
< Checkbox
label = {
< div className = 'd-flex align-items-center' > greeter mode
< Info >
2023-07-24 18:35:05 +00:00
< ul className = 'fw-bold' >
2022-09-27 21:19:15 +00:00
< li > see and screen free posts and comments < / l i >
2023-07-09 17:37:12 +00:00
< li > help onboard new stackers to SN and Lightning < / l i >
2022-09-27 21:19:15 +00:00
< li > you might be subject to more spam < / l i >
< / u l >
< / I n f o >
< / d i v >
}
name = 'greeterMode'
2024-02-10 02:35:32 +00:00
groupClassName = 'mb-0'
/ >
< Checkbox
label = {
< div className = 'd-flex align-items-center' > nsfw mode
< Info >
< ul className = 'fw-bold' >
< li > see posts from nsfw territories < / l i >
< / u l >
< / I n f o >
< / d i v >
}
name = 'nsfwMode'
2022-09-21 19:57:36 +00:00
/ >
2023-10-04 18:47:09 +00:00
< h4 > nostr < / h 4 >
< Checkbox
label = {
< div className = 'd-flex align-items-center' > crosspost to nostr
< Info >
< ul className = 'fw-bold' >
Nostr crossposting all item types (#779)
* crosspost-item
* crosspost old items, update with nEventId
* Updating noteId encoding, cleaning up a little
* Fixing item-info condition, cleaning up
* Linting
* Add createdAt variable back
* Change instances of eventId to noteId
* Adding upsertNoteId mutation
* Cleaning up updateItem, using toasts to communivate success/failure in crosspost-item
* Linting
* Move crosspost to share button, make sure only OP can crosspost
* Lint
* Simplify conditions
* user might have no nostr extension installed
Co-authored-by: ekzyis <27162016+ekzyis@users.noreply.github.com>
* change upsertNoteId to updateNoteID for resolver and mutations, change isOp to mine, remove unused noteId params
* Basic setup for crossposting poll / link items
* post rebase fixes and Bounty and job crossposts
* Job crossposting working
* adding back accidentally removed import
* Lint / rebase
* Outsource as much crossposting logic from discussion-form into use-crossposter as possible
* Fix incorrect property for user relays, fix itemId param in updateNoteId
* Fix toast messages / error cases in use-crossposter
* Update item forms to for updated use-crossposter hook
* CrosspostDropdownItem in share updated to accomodate use-crossposter update
* Encode paramaterized replacable event id's in naddress format with nostr-tools, bounty to follw nip-99 spec
* Increase timeout on relay connection / cleaning up
* No longer crossposting job
* Add blastr, fix crosspost button in item-info for polls/discussions, finish removing job crosspostr code
* Fix toaster error, create reusable crossposterror function to surface toaster
* Cleaning up / comments / linting
* Update copy
* Simplify CrosspostdropdownItem, keep replies from being crossposted
* Moved query for missing item fields when crossposting to use-crossposter hook
* Remove unneeded param in CrosspostDropdownItem, lint
* Small fixes post rebase
* Remove unused import
* fix nostr-tools version, fix package-lock.json
* Update components/item-info.js
Co-authored-by: ekzyis <ek@stacker.news>
* Remove unused param, determine poll item type from pollCost field, add mutiny strfry relay to defaults
* Update toaster implementations, use no-cache for item query, restructure crosspostItem to use await with try catch
* crosspost info modal that lives under adv-post-form now has dynamic crossposting info
* Move determineItemType into handleEventCreation, mover item/event handing outside of do ... while loop
* Lint
* Reconcile skip method with onCancel function in toaster
* Handle failedRelays being undefined
* determine item type from router.query.type if available otherwise use item fields
* Initiliaze failerRelays as undefined but handle error explicitly
* Lint
* Fix crosspost default value for link, poll, bounty forms
---------
Co-authored-by: ekzyis <27162016+ekzyis@users.noreply.github.com>
Co-authored-by: ekzyis <ek@stacker.news>
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2024-02-22 01:18:36 +00:00
< li > crosspost your items to nostr < / l i >
2023-10-04 18:47:09 +00:00
< li > requires NIP - 07 extension for signing < / l i >
< li > we use your NIP - 05 relays if set < / l i >
Nostr crossposting all item types (#779)
* crosspost-item
* crosspost old items, update with nEventId
* Updating noteId encoding, cleaning up a little
* Fixing item-info condition, cleaning up
* Linting
* Add createdAt variable back
* Change instances of eventId to noteId
* Adding upsertNoteId mutation
* Cleaning up updateItem, using toasts to communivate success/failure in crosspost-item
* Linting
* Move crosspost to share button, make sure only OP can crosspost
* Lint
* Simplify conditions
* user might have no nostr extension installed
Co-authored-by: ekzyis <27162016+ekzyis@users.noreply.github.com>
* change upsertNoteId to updateNoteID for resolver and mutations, change isOp to mine, remove unused noteId params
* Basic setup for crossposting poll / link items
* post rebase fixes and Bounty and job crossposts
* Job crossposting working
* adding back accidentally removed import
* Lint / rebase
* Outsource as much crossposting logic from discussion-form into use-crossposter as possible
* Fix incorrect property for user relays, fix itemId param in updateNoteId
* Fix toast messages / error cases in use-crossposter
* Update item forms to for updated use-crossposter hook
* CrosspostDropdownItem in share updated to accomodate use-crossposter update
* Encode paramaterized replacable event id's in naddress format with nostr-tools, bounty to follw nip-99 spec
* Increase timeout on relay connection / cleaning up
* No longer crossposting job
* Add blastr, fix crosspost button in item-info for polls/discussions, finish removing job crosspostr code
* Fix toaster error, create reusable crossposterror function to surface toaster
* Cleaning up / comments / linting
* Update copy
* Simplify CrosspostdropdownItem, keep replies from being crossposted
* Moved query for missing item fields when crossposting to use-crossposter hook
* Remove unneeded param in CrosspostDropdownItem, lint
* Small fixes post rebase
* Remove unused import
* fix nostr-tools version, fix package-lock.json
* Update components/item-info.js
Co-authored-by: ekzyis <ek@stacker.news>
* Remove unused param, determine poll item type from pollCost field, add mutiny strfry relay to defaults
* Update toaster implementations, use no-cache for item query, restructure crosspostItem to use await with try catch
* crosspost info modal that lives under adv-post-form now has dynamic crossposting info
* Move determineItemType into handleEventCreation, mover item/event handing outside of do ... while loop
* Lint
* Reconcile skip method with onCancel function in toaster
* Handle failedRelays being undefined
* determine item type from router.query.type if available otherwise use item fields
* Initiliaze failerRelays as undefined but handle error explicitly
* Lint
* Fix crosspost default value for link, poll, bounty forms
---------
Co-authored-by: ekzyis <27162016+ekzyis@users.noreply.github.com>
Co-authored-by: ekzyis <ek@stacker.news>
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2024-02-22 01:18:36 +00:00
< li > we use these relays by default : < / l i >
2023-10-04 18:47:09 +00:00
< ul >
{ DEFAULT _CROSSPOSTING _RELAYS . map ( ( relay , i ) => (
< li key = { i } > { relay } < / l i >
) ) }
< / u l >
< / u l >
< / I n f o >
< / d i v >
}
name = 'nostrCrossposting'
/ >
< Input
label = { < > pubkey < small className = 'text-muted ms-2' > optional < /small></ > }
name = 'nostrPubkey'
clear
hint = { < small className = 'text-muted' > used for NIP - 05 < / s m a l l > }
/ >
< VariableInput
label = { < > relays < small className = 'text-muted ms-2' > optional < /small></ > }
name = 'nostrRelays'
clear
min = { 0 }
max = { NOSTR _MAX _RELAY _NUM }
hint = { < small className = 'text-muted' > used for NIP - 05 and crossposting < / s m a l l > }
2023-01-07 00:53:09 +00:00
/ >
2022-06-02 22:55:23 +00:00
< div className = 'd-flex' >
2023-07-24 18:35:05 +00:00
< SubmitButton variant = 'info' className = 'ms-auto mt-1 px-4' > save < / S u b m i t B u t t o n >
2022-06-02 22:55:23 +00:00
< / d i v >
< / F o r m >
2023-08-13 19:12:18 +00:00
< div className = 'text-start w-100' >
2022-06-02 22:55:23 +00:00
< div className = 'form-label' > saturday newsletter < / d i v >
< Button href = 'https://mail.stacker.news/subscription/form' target = '_blank' > ( re ) subscribe < / B u t t o n >
2024-03-14 20:32:34 +00:00
{ settings ? . authMethods && < AuthMethods methods = { settings . authMethods } apiKeyEnabled = { settings . apiKeyEnabled } / > }
2022-06-02 22:55:23 +00:00
< / d i v >
< / d i v >
2023-07-23 15:08:43 +00:00
< / C e n t e r L a y o u t >
2023-07-21 22:33:11 +00:00
)
}
2023-11-09 17:50:43 +00:00
const DropBolt11sCheckbox = ( { ssrData , ... props } ) => {
const showModal = useShowModal ( )
const { data } = useQuery ( gql ` { numBolt11s } ` )
const { numBolt11s } = data || ssrData
return (
< Checkbox
onClick = { e => {
if ( e . target . checked ) {
showModal ( onClose => {
return (
< >
< p className = 'fw-bolder' > { numBolt11s } withdrawal invoices will be deleted with this setting . < / p >
< p className = 'fw-bolder' > You sure ? This is a gone forever kind of delete . < / p >
< div className = 'd-flex justify-content-end' >
< Button
variant = 'danger' onClick = { async ( ) => {
await onClose ( )
} }
> I am sure
< / B u t t o n >
< / d i v >
< / >
)
} )
}
} }
{ ... props }
/ >
)
}
2023-07-23 15:08:43 +00:00
function QRLinkButton ( { provider , unlink , status } ) {
const showModal = useShowModal ( )
const text = status ? 'Unlink' : 'Link'
const onClick = status
? unlink
: ( ) => showModal ( onClose =>
< div className = 'd-flex flex-column align-items-center' >
2023-08-17 17:36:23 +00:00
< LightningAuth / >
2023-07-23 15:08:43 +00:00
< / d i v > )
return (
< LoginButton
key = { provider }
className = 'd-block mt-2' type = { provider } text = { text } onClick = { onClick }
/ >
)
}
2023-08-08 00:50:01 +00:00
function NostrLinkButton ( { unlink , status } ) {
const showModal = useShowModal ( )
const text = status ? 'Unlink' : 'Link'
const onClick = status
? unlink
: ( ) => showModal ( onClose =>
< div className = 'd-flex flex-column align-items-center' >
< NostrAuth text = 'Unlink' / >
< / d i v > )
return (
< LoginButton
className = 'd-block mt-2' type = 'nostr' text = { text } onClick = { onClick }
/ >
)
}
2023-07-23 15:08:43 +00:00
function UnlinkObstacle ( { onClose , type , unlinkAuth } ) {
2023-07-23 14:16:12 +00:00
const router = useRouter ( )
2023-08-25 23:21:51 +00:00
const toaster = useToast ( )
2023-07-23 15:08:43 +00:00
return (
< div >
You are removing your last auth method . It is recommended you link another auth method before removing
your last auth method . If you ' d like to proceed anyway , type the following below
2023-07-24 18:35:05 +00:00
< div className = 'text-danger fw-bold my-2' >
2023-07-23 15:08:43 +00:00
If I logout , even accidentally , I will never be able to access my account again
< / d i v >
< Form
className = 'mt-3'
initial = { {
warning : ''
} }
schema = { lastAuthRemovalSchema }
onSubmit = { async ( ) => {
2023-08-25 23:21:51 +00:00
try {
await unlinkAuth ( { variables : { authType : type } } )
router . push ( '/settings' )
onClose ( )
toaster . success ( 'unlinked auth method' )
} catch ( err ) {
console . error ( err )
toaster . danger ( 'failed to unlink auth method' )
}
2023-07-23 15:08:43 +00:00
} }
>
< Input
name = 'warning'
required
/ >
2023-07-24 18:35:05 +00:00
< SubmitButton className = 'd-flex ms-auto' variant = 'danger' > do it < / S u b m i t B u t t o n >
2023-07-23 15:08:43 +00:00
< / F o r m >
< / d i v >
)
}
2024-03-14 20:32:34 +00:00
function AuthMethods ( { methods , apiKeyEnabled } ) {
2023-07-23 15:08:43 +00:00
const showModal = useShowModal ( )
2023-07-30 20:39:18 +00:00
const router = useRouter ( )
2023-08-25 23:21:51 +00:00
const toaster = useToast ( )
2023-07-30 20:39:18 +00:00
const [ err , setErr ] = useState ( authErrorMessage ( router . query . error ) )
2022-06-02 22:55:23 +00:00
const [ unlinkAuth ] = useMutation (
gql `
mutation unlinkAuth ( $authType : String ! ) {
unlinkAuth ( authType : $authType ) {
lightning
email
twitter
github
2023-08-08 00:50:01 +00:00
nostr
2022-06-02 22:55:23 +00:00
}
} ` , {
update ( cache , { data : { unlinkAuth } } ) {
cache . modify ( {
id : 'ROOT_QUERY' ,
fields : {
settings ( existing ) {
2023-11-12 17:59:18 +00:00
return {
... existing ,
privates : {
... existing . privates ,
authMethods : { ... unlinkAuth }
}
}
2022-06-02 22:55:23 +00:00
}
}
} )
}
}
)
2023-07-30 19:38:50 +00:00
// sort to prevent hydration mismatch
2024-03-14 20:32:34 +00:00
const providers = Object . keys ( methods ) . filter ( k => k !== '__typename' && k !== 'apiKey' ) . sort ( )
2023-01-18 18:49:20 +00:00
2022-06-02 22:55:23 +00:00
const unlink = async type => {
// if there's only one auth method left
2023-01-18 18:49:20 +00:00
const links = providers . reduce ( ( t , p ) => t + ( methods [ p ] ? 1 : 0 ) , 0 )
2022-06-02 22:55:23 +00:00
if ( links === 1 ) {
2023-07-29 19:38:20 +00:00
showModal ( onClose => ( < UnlinkObstacle onClose = { onClose } type = { type } unlinkAuth = { unlinkAuth } / > ) )
2022-06-02 22:55:23 +00:00
} else {
2023-08-25 23:21:51 +00:00
try {
await unlinkAuth ( { variables : { authType : type } } )
toaster . success ( 'unlinked auth method' )
} catch ( err ) {
console . error ( err )
toaster . danger ( 'failed to unlink auth method' )
}
2022-06-02 22:55:23 +00:00
}
}
return (
< >
< div className = 'form-label mt-3' > auth methods < / d i v >
2023-07-31 13:39:10 +00:00
{ err && (
< Alert
variant = 'danger' onClose = { ( ) => {
const { pathname , query : { error , nodata , ... rest } } = router
router . replace ( {
pathname ,
query : { nodata , ... rest }
} , { pathname , query : { ... rest } } , { shallow : true } )
setErr ( undefined )
} } dismissible
> { err }
< / A l e r t >
) }
2023-07-30 20:39:18 +00:00
2023-07-23 15:08:43 +00:00
{ providers ? . map ( provider => {
if ( provider === 'email' ) {
return methods . email
? (
< div key = { provider } className = 'mt-2 d-flex align-items-center' >
< Input
name = 'email'
placeholder = { methods . email }
groupClassName = 'mb-0'
readOnly
noForm
2023-07-23 14:16:12 +00:00
/ >
2023-07-23 15:08:43 +00:00
< Button
2023-07-24 18:35:05 +00:00
className = 'ms-2' variant = 'secondary' onClick = {
2023-07-23 14:16:12 +00:00
async ( ) => {
2023-07-23 15:08:43 +00:00
await unlink ( 'email' )
2023-07-23 14:16:12 +00:00
}
}
2023-07-23 15:08:43 +00:00
> Unlink Email
< / B u t t o n >
< / d i v >
)
: < div key = { provider } className = 'mt-2' > < EmailLinkForm / > < / d i v >
2023-08-17 17:36:23 +00:00
} else if ( provider === 'lightning' ) {
2023-07-23 15:08:43 +00:00
return (
< QRLinkButton
key = { provider } provider = { provider }
status = { methods [ provider ] } unlink = { async ( ) => await unlink ( provider ) }
/ >
)
2023-08-08 00:50:01 +00:00
} else if ( provider === 'nostr' ) {
return < NostrLinkButton key = 'nostr' status = { methods [ provider ] } unlink = { async ( ) => await unlink ( provider ) } / >
2023-07-23 15:08:43 +00:00
} else {
return (
< LoginButton
className = 'mt-2 d-block'
key = { provider }
type = { provider . toLowerCase ( ) }
onClick = { async ( ) => {
if ( methods [ provider ] ) {
await unlink ( provider )
} else {
signIn ( provider )
}
} }
text = { methods [ provider ] ? 'Unlink' : 'Link' }
/ >
)
2022-06-02 22:55:23 +00:00
}
2023-01-18 18:49:20 +00:00
} ) }
2024-03-14 20:32:34 +00:00
< ApiKey apiKey = { methods . apiKey } enabled = { apiKeyEnabled } / >
2022-06-02 22:55:23 +00:00
< / >
)
}
export function EmailLinkForm ( { callbackUrl } ) {
const [ linkUnverifiedEmail ] = useMutation (
gql `
mutation linkUnverifiedEmail ( $email : String ! ) {
linkUnverifiedEmail ( email : $email )
} `
)
return (
< Form
initial = { {
email : ''
} }
2023-02-08 19:38:04 +00:00
schema = { emailSchema }
2022-06-02 22:55:23 +00:00
onSubmit = { async ( { email } ) => {
// add email to user's account
// then call signIn
const { data } = await linkUnverifiedEmail ( { variables : { email } } )
if ( data . linkUnverifiedEmail ) {
signIn ( 'email' , { email , callbackUrl } )
}
} }
>
< div className = 'd-flex align-items-center' >
2021-10-30 16:20:11 +00:00
< Input
2022-06-02 22:55:23 +00:00
name = 'email'
placeholder = 'email@example.com'
2021-10-30 16:20:11 +00:00
required
2022-04-21 22:50:02 +00:00
groupClassName = 'mb-0'
/ >
2023-07-24 18:35:05 +00:00
< SubmitButton className = 'ms-2' variant = 'secondary' > Link Email < / S u b m i t B u t t o n >
2022-06-02 22:55:23 +00:00
< / d i v >
< / F o r m >
2021-10-30 16:20:11 +00:00
)
}
2024-03-14 20:32:34 +00:00
2024-03-26 18:27:14 +00:00
function ApiKey ( { enabled , apiKey } ) {
2024-03-26 18:26:09 +00:00
const showModal = useShowModal ( )
2024-03-14 20:32:34 +00:00
const me = useMe ( )
const [ generateApiKey ] = useMutation (
gql `
mutation generateApiKey ( $id : ID ! ) {
generateApiKey ( id : $id )
} ` ,
{
update ( cache , { data : { generateApiKey } } ) {
cache . modify ( {
id : 'ROOT_QUERY' ,
fields : {
settings ( existing ) {
return {
... existing ,
privates : {
... existing . privates ,
apiKey : generateApiKey ,
2024-03-26 18:26:09 +00:00
authMethods : { ... existing . privates . authMethods , apiKey : true }
2024-03-14 20:32:34 +00:00
}
}
}
}
} )
}
}
)
2024-03-26 18:26:09 +00:00
const toaster = useToast ( )
2024-03-14 20:32:34 +00:00
const subject = '[API Key Request] <your title here>'
const body =
encodeURI ( ` **[API Key Request]**
Hi , I would like to use API keys with the [ Stacker News GraphQL API ] ( / a p i / g r a p h q l ) f o r t h e f o l l o w i n g r e a s o n s :
...
I expect to call the following GraphQL queries or mutations :
... ( you can leave empty if unknown )
I estimate that I will call the GraphQL API this many times ( rough estimate is fine ) :
... ( you can leave empty if unknown )
` )
const metaLink = encodeURI ( ` /~meta/post?type=discussion&title= ${ subject } &text= ${ body } ` )
const mailto = ` mailto:hello@stacker.news?subject= ${ subject } &body= ${ body } `
// link to DM with k00b on Telegram
const telegramLink = 'https://t.me/k00bideh'
// link to DM with ek on SimpleX
const simplexLink = 'https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE%3D%40smp10.simplex.im%2FxNnPk9DkTbQJ6NckWom9mi5vheo_VPLm%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAnFUiU0M8jS1JY34LxUoPr7mdJlFZwf3pFkjRrhprdQs%253D%26srv%3Drb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion'
2024-03-26 21:07:55 +00:00
const disabled = ! enabled || apiKey
2024-03-14 20:32:34 +00:00
return (
< >
< div className = 'form-label mt-3' > api key < / d i v >
< div className = 'mt-2 d-flex align-items-center' >
< OverlayTrigger
placement = 'bottom'
2024-03-26 21:07:55 +00:00
overlay = { disabled ? < Tooltip > { apiKey ? 'you can have only one API key at a time' : 'request access to API keys in ~meta' } < /Tooltip> : <></ > }
2024-03-14 20:32:34 +00:00
trigger = { [ 'hover' , 'focus' ] }
>
< div >
2024-03-26 18:26:09 +00:00
< Button
disabled = { disabled }
variant = 'secondary'
onClick = { async ( ) => {
try {
const { data } = await generateApiKey ( { variables : { id : me . id } } )
const { generateApiKey : apiKey } = data
showModal ( ( ) => < ApiKeyModal apiKey = { apiKey } / > , { keepOpen : true } )
} catch ( err ) {
console . error ( err )
toaster . danger ( 'error generating api key' )
}
} }
> Generate API key
< / B u t t o n >
2024-03-14 20:32:34 +00:00
< / d i v >
< / O v e r l a y T r i g g e r >
2024-03-26 21:13:17 +00:00
{ apiKey &&
< DeleteIcon
style = { { cursor : 'pointer' } } className = 'fill-danger mx-1' width = { 24 } height = { 24 }
onClick = { async ( ) => {
showModal ( ( onClose ) => < ApiKeyDeleteObstacle onClose = { onClose } / > )
} }
/ > }
2024-03-14 20:32:34 +00:00
< Info >
< ul className = 'fw-bold' >
< li > use API keys with our < Link target = '_blank' href = '/api/graphql' > GraphQL API < / L i n k > f o r a u t h e n t i c a t i o n < / l i >
< li > you need to add the API key to the < span className = 'text-monospace' > X - API - Key < / s p a n > h e a d e r o f y o u r r e q u e s t s < / l i >
< li > you can currently only generate API keys if we enabled it for your account < / l i >
< li >
you can { ' ' }
< Link target = '_blank' href = { metaLink } rel = 'noreferrer' > create a post in ~ meta < / L i n k > t o r e q u e s t a c c e s s
or reach out to us via
< ul >
< li > < Link target = '_blank' href = { mailto } rel = 'noreferrer' > email < / L i n k > < / l i >
< li > < Link target = '_blank' href = { telegramLink } rel = 'noreferrer' > Telegram < / L i n k > < / l i >
< li > < Link target = '_blank' href = { simplexLink } rel = 'noreferrer' > SimpleX < / L i n k > < / l i >
< / u l >
< / l i >
< li > please include following information in your request :
< ul >
< li > your nym on SN < / l i >
< li > what you want to achieve with authenticated API access < / l i >
< li > which GraphQL queries or mutations you expect to call < / l i >
< li > your ( rough ) estimate how often you will call the GraphQL API < / l i >
< / u l >
< / l i >
< / u l >
< / I n f o >
< / d i v >
< / >
)
}
2024-03-25 00:46:12 +00:00
2024-03-26 18:26:09 +00:00
function ApiKeyModal ( { apiKey } ) {
return (
< >
< p className = 'fw-bold' >
Make sure to copy your API key now . < br / >
This is the only time we will show it to you .
< / p >
< CopyInput readOnly noForm placeholder = { apiKey } hint = { < > use the < span className = 'text-monospace' > X - API - Key < /span> header to include this key in your requests</ > } / >
< / >
)
}
2024-03-26 21:07:55 +00:00
function ApiKeyDeleteObstacle ( { onClose } ) {
const me = useMe ( )
const [ deleteApiKey ] = useMutation (
gql `
mutation deleteApiKey ( $id : ID ! ) {
deleteApiKey ( id : $id ) {
id
}
} ` ,
{
update ( cache , { data : { deleteApiKey } } ) {
cache . modify ( {
id : 'ROOT_QUERY' ,
fields : {
settings ( existing ) {
return {
... existing ,
privates : {
... existing . privates ,
authMethods : { ... existing . privates . authMethods , apiKey : false }
}
}
}
}
} )
}
}
)
const toaster = useToast ( )
return (
< div className = 'text-center' >
< p className = 'fw-bold' >
Do you really want to delete your API key ?
< / p >
< div className = 'd-flex flex-row justify-content-end' >
< Button
variant = 'danger' onClick = { async ( ) => {
try {
await deleteApiKey ( { variables : { id : me . id } } )
onClose ( )
} catch ( err ) {
console . error ( err )
toaster . danger ( 'error deleting api key' )
}
} }
> do it
< / B u t t o n >
< / d i v >
< / d i v >
)
}
2024-03-25 08:53:02 +00:00
const ZapUndosField = ( ) => {
2024-03-25 00:46:12 +00:00
const [ checkboxField ] = useField ( { name : 'zapUndosEnabled' } )
return (
< div className = 'd-flex flex-row align-items-center' >
< Input
name = 'zapUndos'
disabled = { ! checkboxField . value }
label = {
< Checkbox
name = 'zapUndosEnabled'
groupClassName = 'mb-0'
label = {
< div className = 'd-flex align-items-center' >
zap undos
< Info >
< ul className = 'fw-bold' >
< li > An undo button is shown after every zap that exceeds or is equal to the threshold < / l i >
< li > The button is shown for 5 seconds < / l i >
< li > The button is only shown for zaps from the custodial wallet < / l i >
< li > Use a budget or manual approval with attached wallets < / l i >
< / u l >
< / I n f o >
< / d i v >
}
/ >
}
append = { < InputGroup . Text className = 'text-monospace' > sats < / I n p u t G r o u p . T e x t > }
hint = { < small className = 'text-muted' > threshold at which undo button is shown < / s m a l l > }
/ >
< / d i v >
)
}