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</h2>
|
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-25 08:59:27 +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</InputGroup.Text>}
|
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</small>}
|
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</div>}
|
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 10x multiple of your default zap</li>
|
|
|
|
<li>e.g. if your zap default is 10 sats
|
|
|
|
<ul>
|
|
|
|
<li>1st click: 10 sats total zapped</li>
|
|
|
|
<li>2nd click: 100 sats total zapped</li>
|
|
|
|
<li>3rd click: 1000 sats total zapped</li>
|
|
|
|
<li>4th click: 10000 sats total zapped</li>
|
|
|
|
<li>and so on ...</li>
|
|
|
|
</ul>
|
|
|
|
</li>
|
|
|
|
<li>You can still custom zap via long press
|
|
|
|
<ul>
|
|
|
|
<li>the next bolt click rounds up to the next greatest 10x multiple of your default</li>
|
|
|
|
</ul>
|
|
|
|
</li>
|
2022-12-09 19:25:38 +00:00
|
|
|
</ul>
|
2024-02-22 00:48:42 +00:00
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
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
|
|
|
/>
|
|
|
|
</div>
|
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</InputGroup.Text>}
|
|
|
|
/>
|
2023-06-12 20:37:12 +00:00
|
|
|
<div className='form-label'>notify me when ...</div>
|
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</div>
|
|
|
|
<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.</li>
|
|
|
|
<li>It makes your invoice descriptions blank.</li>
|
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</li>
|
2022-08-30 21:50:47 +00:00
|
|
|
</ul>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</Info>
|
2022-09-21 19:57:36 +00:00
|
|
|
</div>
|
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</li>
|
|
|
|
<li>applies retroactively, cannot be reversed</li>
|
|
|
|
<li>withdrawal invoices are kept at least {INVOICE_RETENTION_DAYS} days for security and debugging purposes</li>
|
|
|
|
<li>autodeletions are run a daily basis at night</li>
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
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</li>
|
|
|
|
<li>uncheck this to display your github on your profile</li>
|
|
|
|
{me.optional.githubId === null &&
|
|
|
|
<div className='my-2'>
|
|
|
|
<li><i>You don't seem to have a linked github account</i></li>
|
|
|
|
<ul><li>If this is wrong, try unlinking/relinking</li></ul>
|
|
|
|
</div>}
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
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</li>
|
|
|
|
<li>Uncheck this to display your npub on your profile</li>
|
|
|
|
{me.optional.nostrAuthPubkey === null &&
|
|
|
|
<div className='my-2'>
|
|
|
|
<li>You don't seem to have a linked nostr account</li>
|
|
|
|
<ul><li>If this is wrong, try unlinking/relinking</li></ul>
|
|
|
|
</div>}
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
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</li>
|
|
|
|
<li>Uncheck this to display your twitter on your profile</li>
|
|
|
|
{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</li></ul>
|
|
|
|
</div>}
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
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</li>
|
|
|
|
<li>this prevents IP address leaks to arbitrary sites</li>
|
|
|
|
<li>if we fail to load an image, the raw link will be shown</li>
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
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</li>
|
|
|
|
<li>this information is used to fix bugs</li>
|
2023-09-18 23:00:16 +00:00
|
|
|
<li>this information includes:
|
|
|
|
<ul><li>timestamps</li></ul>
|
|
|
|
<ul><li>a randomly generated fancy name</li></ul>
|
|
|
|
<ul><li>your user agent</li></ul>
|
|
|
|
<ul><li>your operating system</li></ul>
|
|
|
|
</li>
|
|
|
|
<li>this information can not be traced back to you without your fancy name</li>
|
|
|
|
<li>fancy names are generated in your browser</li>
|
|
|
|
</ul>
|
|
|
|
<div className='text-muted fst-italic'>your fancy name: {logger.name}</div>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
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</div>
|
|
|
|
<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</li>
|
|
|
|
<li>don't down rank flagged content</li>
|
2022-09-21 19:57:36 +00:00
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
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</li>
|
2023-07-09 17:37:12 +00:00
|
|
|
<li>help onboard new stackers to SN and Lightning</li>
|
2022-09-27 21:19:15 +00:00
|
|
|
<li>you might be subject to more spam</li>
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
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</li>
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
name='nsfwMode'
|
2022-09-21 19:57:36 +00:00
|
|
|
/>
|
2023-10-04 18:47:09 +00:00
|
|
|
<h4>nostr</h4>
|
|
|
|
<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</li>
|
2023-10-04 18:47:09 +00:00
|
|
|
<li>requires NIP-07 extension for signing</li>
|
|
|
|
<li>we use your NIP-05 relays if set</li>
|
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:</li>
|
2023-10-04 18:47:09 +00:00
|
|
|
<ul>
|
|
|
|
{DEFAULT_CROSSPOSTING_RELAYS.map((relay, i) => (
|
|
|
|
<li key={i}>{relay}</li>
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
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</small>}
|
|
|
|
/>
|
|
|
|
<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</small>}
|
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</SubmitButton>
|
2022-06-02 22:55:23 +00:00
|
|
|
</div>
|
|
|
|
</Form>
|
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</div>
|
|
|
|
<Button href='https://mail.stacker.news/subscription/form' target='_blank'>(re)subscribe</Button>
|
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
|
|
|
</div>
|
|
|
|
</div>
|
2023-07-23 15:08:43 +00:00
|
|
|
</CenterLayout>
|
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
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
{...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
|
|
|
</div>)
|
|
|
|
|
|
|
|
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' />
|
|
|
|
</div>)
|
|
|
|
|
|
|
|
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
|
|
|
|
</div>
|
|
|
|
<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</SubmitButton>
|
2023-07-23 15:08:43 +00:00
|
|
|
</Form>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
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</div>
|
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}
|
|
|
|
</Alert>
|
|
|
|
)}
|
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
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
: <div key={provider} className='mt-2'><EmailLinkForm /></div>
|
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</SubmitButton>
|
2022-06-02 22:55:23 +00:00
|
|
|
</div>
|
|
|
|
</Form>
|
2021-10-30 16:20:11 +00:00
|
|
|
)
|
|
|
|
}
|
2024-03-14 20:32:34 +00:00
|
|
|
|
|
|
|
export function ApiKey ({ enabled, apiKey }) {
|
|
|
|
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,
|
|
|
|
authMethods: { ...existing.privates.authMethods, apiKey: generateApiKey }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
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: null }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
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](/api/graphql) for the following reasons:
|
|
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
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'
|
|
|
|
|
|
|
|
const disabled = !enabled
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div className='form-label mt-3'>api key</div>
|
|
|
|
<div className='mt-2 d-flex align-items-center'>
|
|
|
|
{apiKey &&
|
|
|
|
<>
|
|
|
|
<CopyInput
|
|
|
|
groupClassName='mb-0'
|
|
|
|
readOnly
|
|
|
|
noForm
|
|
|
|
placeholder={apiKey}
|
|
|
|
/>
|
|
|
|
</>}
|
|
|
|
<OverlayTrigger
|
|
|
|
placement='bottom'
|
|
|
|
overlay={disabled ? <Tooltip>request access to API keys in ~meta</Tooltip> : <></>}
|
|
|
|
trigger={['hover', 'focus']}
|
|
|
|
>
|
|
|
|
<div>
|
|
|
|
{apiKey
|
|
|
|
? <DeleteIcon
|
|
|
|
style={{ cursor: 'pointer' }} className='fill-grey mx-1' width={24} height={24}
|
|
|
|
onClick={async () => {
|
|
|
|
await deleteApiKey({ variables: { id: me.id } })
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
: (
|
|
|
|
<Button
|
|
|
|
disabled={disabled} className={apiKey ? 'ms-2' : ''} variant='secondary' onClick={async () => {
|
|
|
|
await generateApiKey({ variables: { id: me.id } })
|
|
|
|
}}
|
|
|
|
>Generate API key
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</OverlayTrigger>
|
|
|
|
<Info>
|
|
|
|
<ul className='fw-bold'>
|
|
|
|
<li>use API keys with our <Link target='_blank' href='/api/graphql'>GraphQL API</Link> for authentication</li>
|
|
|
|
<li>you need to add the API key to the <span className='text-monospace'>X-API-Key</span> header of your requests</li>
|
|
|
|
<li>you can currently only generate API keys if we enabled it for your account</li>
|
|
|
|
<li>
|
|
|
|
you can{' '}
|
|
|
|
<Link target='_blank' href={metaLink} rel='noreferrer'>create a post in ~meta</Link> to request access
|
|
|
|
or reach out to us via
|
|
|
|
<ul>
|
|
|
|
<li><Link target='_blank' href={mailto} rel='noreferrer'>email</Link></li>
|
|
|
|
<li><Link target='_blank' href={telegramLink} rel='noreferrer'>Telegram</Link></li>
|
|
|
|
<li><Link target='_blank' href={simplexLink} rel='noreferrer'>SimpleX</Link></li>
|
|
|
|
</ul>
|
|
|
|
</li>
|
|
|
|
<li>please include following information in your request:
|
|
|
|
<ul>
|
|
|
|
<li>your nym on SN</li>
|
|
|
|
<li>what you want to achieve with authenticated API access</li>
|
|
|
|
<li>which GraphQL queries or mutations you expect to call</li>
|
|
|
|
<li>your (rough) estimate how often you will call the GraphQL API</li>
|
|
|
|
</ul>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
2024-03-25 00:46:12 +00:00
|
|
|
|
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</li>
|
|
|
|
<li>The button is shown for 5 seconds</li>
|
|
|
|
<li>The button is only shown for zaps from the custodial wallet</li>
|
|
|
|
<li>Use a budget or manual approval with attached wallets</li>
|
|
|
|
</ul>
|
|
|
|
</Info>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
|
|
|
hint={<small className='text-muted'>threshold at which undo button is shown</small>}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|