2024-06-03 17:41:15 -05:00
|
|
|
import { getGetServerSideProps } from '@/api/ssrApollo'
|
|
|
|
import { Form, ClientInput, ClientCheckbox, PasswordInput } from '@/components/form'
|
|
|
|
import { CenterLayout } from '@/components/layout'
|
|
|
|
import { WalletSecurityBanner } from '@/components/banners'
|
2024-06-20 16:48:46 +02:00
|
|
|
import { WalletLogs } from '@/components/wallet-logger'
|
2024-06-03 17:41:15 -05:00
|
|
|
import { useToast } from '@/components/toast'
|
|
|
|
import { useRouter } from 'next/router'
|
2024-07-16 06:09:27 +02:00
|
|
|
import { useWallet, Status } from 'wallets'
|
2024-06-24 12:53:59 +02:00
|
|
|
import Info from '@/components/info'
|
|
|
|
import Text from '@/components/text'
|
2024-06-26 14:31:35 +02:00
|
|
|
import { AutowithdrawSettings } from '@/components/autowithdraw-shared'
|
2024-07-02 05:38:41 +02:00
|
|
|
import dynamic from 'next/dynamic'
|
2024-07-17 01:21:30 +02:00
|
|
|
import { array, object, string } from 'yup'
|
2024-07-17 00:04:35 +02:00
|
|
|
import { autowithdrawSchemaMembers } from '@/lib/validate'
|
|
|
|
import { useMe } from '@/components/me'
|
|
|
|
import { TOR_REGEXP } from '@/lib/url'
|
2024-07-02 05:38:41 +02:00
|
|
|
|
|
|
|
const WalletButtonBar = dynamic(() => import('@/components/wallet-buttonbar.js'), { ssr: false })
|
2024-06-03 17:41:15 -05:00
|
|
|
|
|
|
|
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
|
|
|
|
|
|
|
export default function WalletSettings () {
|
|
|
|
const toaster = useToast()
|
|
|
|
const router = useRouter()
|
|
|
|
const { wallet: name } = router.query
|
|
|
|
const wallet = useWallet(name)
|
2024-07-17 00:04:35 +02:00
|
|
|
const me = useMe()
|
2024-06-03 17:41:15 -05:00
|
|
|
|
|
|
|
const initial = wallet.fields.reduce((acc, field) => {
|
2024-07-05 21:00:37 +02:00
|
|
|
// We still need to run over all wallet fields via reduce
|
2024-07-05 19:17:28 +02:00
|
|
|
// even though we use wallet.config as the initial value
|
2024-07-05 21:00:37 +02:00
|
|
|
// since wallet.config is empty when wallet is not configured.
|
|
|
|
// Also, wallet.config includes general fields like
|
|
|
|
// 'enabled' and 'priority' which are not defined in wallet.fields.
|
2024-06-03 17:41:15 -05:00
|
|
|
return {
|
|
|
|
...acc,
|
|
|
|
[field.name]: wallet.config?.[field.name] || ''
|
|
|
|
}
|
2024-07-05 19:17:28 +02:00
|
|
|
}, wallet.config)
|
2024-06-03 17:41:15 -05:00
|
|
|
|
2024-07-17 00:04:35 +02:00
|
|
|
const schema = generateSchema(wallet, { me })
|
|
|
|
|
2024-06-03 17:41:15 -05:00
|
|
|
return (
|
|
|
|
<CenterLayout>
|
|
|
|
<h2 className='pb-2'>{wallet.card.title}</h2>
|
2024-07-15 16:23:24 +02:00
|
|
|
<h6 className='text-muted text-center pb-3'><Text>{wallet.card.subtitle}</Text></h6>
|
2024-07-16 22:08:41 +02:00
|
|
|
{!wallet.walletType && <WalletSecurityBanner />}
|
2024-06-03 17:41:15 -05:00
|
|
|
<Form
|
|
|
|
initial={initial}
|
2024-07-17 00:04:35 +02:00
|
|
|
schema={schema}
|
2024-07-05 21:00:37 +02:00
|
|
|
onSubmit={async (values) => {
|
2024-06-03 17:41:15 -05:00
|
|
|
try {
|
2024-06-20 20:16:47 +02:00
|
|
|
const newConfig = !wallet.isConfigured
|
2024-07-04 21:34:36 +02:00
|
|
|
|
|
|
|
// enable wallet if wallet was just configured
|
|
|
|
if (newConfig) {
|
2024-07-05 21:00:37 +02:00
|
|
|
values.enabled = true
|
2024-07-04 21:34:36 +02:00
|
|
|
}
|
|
|
|
|
2024-06-21 22:32:06 +02:00
|
|
|
await wallet.save(values)
|
2024-07-04 21:34:36 +02:00
|
|
|
|
2024-07-05 21:00:37 +02:00
|
|
|
if (values.enabled) wallet.enable()
|
2024-06-20 19:56:37 +02:00
|
|
|
else wallet.disable()
|
2024-07-04 21:34:36 +02:00
|
|
|
|
2024-06-03 17:41:15 -05:00
|
|
|
toaster.success('saved settings')
|
|
|
|
router.push('/settings/wallets')
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
2024-06-20 19:56:37 +02:00
|
|
|
const message = 'failed to attach: ' + err.message || err.toString?.()
|
|
|
|
toaster.danger(message)
|
2024-06-03 17:41:15 -05:00
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<WalletFields wallet={wallet} />
|
2024-07-16 22:08:41 +02:00
|
|
|
{wallet.walletType
|
2024-07-04 21:38:54 +02:00
|
|
|
? <AutowithdrawSettings wallet={wallet} />
|
2024-06-26 14:31:35 +02:00
|
|
|
: (
|
|
|
|
<ClientCheckbox
|
2024-07-04 21:38:54 +02:00
|
|
|
disabled={!wallet.isConfigured}
|
2024-06-26 14:31:35 +02:00
|
|
|
initialValue={wallet.status === Status.Enabled}
|
|
|
|
label='enabled'
|
|
|
|
name='enabled'
|
|
|
|
/>
|
|
|
|
)}
|
2024-06-03 17:41:15 -05:00
|
|
|
<WalletButtonBar
|
|
|
|
wallet={wallet} onDelete={async () => {
|
|
|
|
try {
|
2024-07-05 21:00:37 +02:00
|
|
|
await wallet.delete()
|
2024-06-03 17:41:15 -05:00
|
|
|
toaster.success('saved settings')
|
|
|
|
router.push('/settings/wallets')
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
2024-06-20 19:56:37 +02:00
|
|
|
const message = 'failed to detach: ' + err.message || err.toString?.()
|
|
|
|
toaster.danger(message)
|
2024-06-03 17:41:15 -05:00
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</Form>
|
|
|
|
<div className='mt-3 w-100'>
|
|
|
|
<WalletLogs wallet={wallet} embedded />
|
|
|
|
</div>
|
|
|
|
</CenterLayout>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
function WalletFields ({ wallet: { config, fields } }) {
|
2024-07-07 10:32:01 +02:00
|
|
|
return fields.map(({ name, label, type, help, optional, ...props }, i) => {
|
2024-06-26 14:01:54 +02:00
|
|
|
const rawProps = {
|
|
|
|
...props,
|
|
|
|
name,
|
2024-06-03 17:41:15 -05:00
|
|
|
initialValue: config?.[name],
|
2024-06-24 12:53:59 +02:00
|
|
|
label: (
|
|
|
|
<div className='d-flex align-items-center'>
|
|
|
|
{label}
|
2024-06-26 14:31:35 +02:00
|
|
|
{/* help can be a string or object to customize the label */}
|
2024-06-24 12:53:59 +02:00
|
|
|
{help && (
|
2024-06-26 14:31:35 +02:00
|
|
|
<Info label={help.label || 'help'}>
|
|
|
|
<Text>{help.text || help}</Text>
|
2024-06-24 12:53:59 +02:00
|
|
|
</Info>
|
|
|
|
)}
|
2024-06-26 14:31:35 +02:00
|
|
|
{optional && (
|
|
|
|
<small className='text-muted ms-2'>
|
2024-07-15 16:23:24 +02:00
|
|
|
{typeof optional === 'boolean' ? 'optional' : <Text>{optional}</Text>}
|
2024-06-26 14:31:35 +02:00
|
|
|
</small>
|
|
|
|
)}
|
2024-06-24 12:53:59 +02:00
|
|
|
</div>
|
|
|
|
),
|
|
|
|
required: !optional,
|
2024-06-26 14:01:54 +02:00
|
|
|
autoFocus: i === 0
|
2024-06-03 17:41:15 -05:00
|
|
|
}
|
|
|
|
if (type === 'text') {
|
2024-06-26 14:01:54 +02:00
|
|
|
return <ClientInput key={i} {...rawProps} />
|
2024-06-03 17:41:15 -05:00
|
|
|
}
|
|
|
|
if (type === 'password') {
|
2024-06-26 14:01:54 +02:00
|
|
|
return <PasswordInput key={i} {...rawProps} newPass />
|
2024-06-03 17:41:15 -05:00
|
|
|
}
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
}
|
2024-07-17 00:04:35 +02:00
|
|
|
|
|
|
|
function generateSchema (wallet, { me }) {
|
|
|
|
if (wallet.schema) return wallet.schema
|
|
|
|
|
|
|
|
const fieldValidator = (field) => {
|
|
|
|
if (!field.validate) {
|
|
|
|
// default validation
|
|
|
|
let validator = string()
|
|
|
|
if (!field.optional) validator = validator.required('required')
|
|
|
|
return validator
|
|
|
|
}
|
|
|
|
|
2024-07-17 00:36:25 +02:00
|
|
|
if (field.validate.schema) {
|
|
|
|
// complex validation
|
|
|
|
return field.validate.schema
|
|
|
|
}
|
|
|
|
|
2024-07-17 01:21:30 +02:00
|
|
|
const { type: validationType, words, min, max } = field.validate
|
2024-07-17 00:04:35 +02:00
|
|
|
|
|
|
|
let validator
|
|
|
|
|
|
|
|
const stringTypes = ['url', 'string']
|
|
|
|
|
|
|
|
if (stringTypes.includes(validationType)) {
|
|
|
|
validator = string()
|
|
|
|
|
|
|
|
if (field.validate.length) {
|
|
|
|
validator = validator.length(field.validate.length)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (validationType === 'url') {
|
|
|
|
validator = process.env.NODE_ENV === 'development'
|
|
|
|
? validator
|
|
|
|
.or([string().matches(/^(http:\/\/)?localhost:\d+$/), string().url()], 'invalid url')
|
|
|
|
: validator
|
|
|
|
.url()
|
|
|
|
.test(async (url, context) => {
|
2024-07-17 01:03:45 +02:00
|
|
|
if (field.validate.torAllowed && TOR_REGEXP.test(url)) {
|
2024-07-17 00:04:35 +02:00
|
|
|
// allow HTTP and HTTPS over Tor
|
|
|
|
if (!/^https?:\/\//.test(url)) {
|
|
|
|
return context.createError({ message: 'http or https required' })
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
// force HTTPS over clearnet
|
|
|
|
await string().https().validate(url)
|
|
|
|
} catch (err) {
|
|
|
|
return context.createError({ message: err.message })
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-07-17 01:21:30 +02:00
|
|
|
if (words) {
|
|
|
|
validator = array()
|
|
|
|
.transform(function (value, originalValue) {
|
|
|
|
if (this.isType(value) && value !== null) {
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
return originalValue ? originalValue.trim().split(/[\s]+/) : []
|
|
|
|
})
|
|
|
|
.test(async (values, context) => {
|
|
|
|
for (const v of values) {
|
|
|
|
try {
|
|
|
|
await string().oneOf(words).validate(v)
|
|
|
|
} catch {
|
|
|
|
return context.createError({ message: `'${v}' is not a valid ${field.label} word` })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if (min !== undefined) validator = validator.min(min)
|
|
|
|
if (max !== undefined) validator = validator.max(max)
|
|
|
|
|
2024-07-17 00:04:35 +02:00
|
|
|
if (!field.optional) validator = validator.required('required')
|
|
|
|
|
|
|
|
return validator
|
|
|
|
}
|
|
|
|
|
|
|
|
return object(
|
|
|
|
wallet.fields.reduce((acc, field) => {
|
|
|
|
return {
|
|
|
|
...acc,
|
|
|
|
[field.name]: fieldValidator(field)
|
|
|
|
}
|
|
|
|
}, wallet.walletType ? autowithdrawSchemaMembers({ me }) : {})
|
|
|
|
)
|
|
|
|
}
|