ekzyis ae73b0c19f
Support receiving via LNbits (#1278)
* Support receiving with LNbits

* Remove hardcoded LNbits url on server

* Fix saveConfig ignoring save errors

* saveConfig was meant to only ignore validation errors, not save errors
* on server save errors, we redirected as if save was successful
* this is now fixed with a promise chain
* logging payments vs receivals was also moved to correct place

* Fix enabled falsely disabled on SSR

If a wallet was configured for payments but not for receivals and you refreshed the configuration form, enabled was disabled even though payments were enabled.

This was the case since we don't know during SSR if it's enabled since this information is stored on the client.

* Fix missing 'receivals disabled' log message

* Move 'wallet detached for payments' log message

* Fix stale walletId during detach

If page was reloaded, walletId in clearConfig was stale since callback dependency was missing.

* Add missing callback dependencies for saveConfig

* Verify that invoiceKey != adminKey

* Verify LNbits keys are hex-encoded

* Fix local config polluted with server data

* Fix creation of duplicate wallets

* Remove unused dependency

* Fix missing error message in logs

* Fix setPriority

* Rename: localConfig -> clientConfig

* Add description to LNbits autowithdrawals

* Rename: receivals -> receives

* Use try/catch instead of promise chain in saveConfig

* add connect label to lnbits for no url found for lnbits

* Fix adminKey not saved

* Remove hardcoded LNbits url on server again

* Add LNbits ATTACH.md

* Delete old docs to attach LNbits with polar

* Add missing callback dependencies

* Set editable: false

* Only set readOnly if field is configured

---------

Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2024-08-12 17:23:39 -05:00

142 lines
4.8 KiB
JavaScript

import { getGetServerSideProps } from '@/api/ssrApollo'
import { Form, ClientInput, ClientCheckbox, PasswordInput, CheckboxGroup } from '@/components/form'
import { CenterLayout } from '@/components/layout'
import { WalletSecurityBanner } from '@/components/banners'
import { WalletLogs } from '@/components/wallet-logger'
import { useToast } from '@/components/toast'
import { useRouter } from 'next/router'
import { useWallet, Status } from 'wallets'
import Info from '@/components/info'
import Text from '@/components/text'
import { AutowithdrawSettings } from '@/components/autowithdraw-shared'
import dynamic from 'next/dynamic'
const WalletButtonBar = dynamic(() => import('@/components/wallet-buttonbar.js'), { ssr: false })
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)
const initial = wallet.fields.reduce((acc, field) => {
// We still need to run over all wallet fields via reduce
// even though we use wallet.config as the initial value
// 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.
return {
...acc,
[field.name]: wallet.config?.[field.name] || ''
}
}, wallet.config)
// check if wallet uses the form-level validation built into Formik or a Yup schema
const validateProps = typeof wallet.fieldValidation === 'function'
? { validate: wallet.fieldValidation }
: { schema: wallet.fieldValidation }
return (
<CenterLayout>
<h2 className='pb-2'>{wallet.card.title}</h2>
<h6 className='text-muted text-center pb-3'><Text>{wallet.card.subtitle}</Text></h6>
{!wallet.walletType && wallet.hasConfig > 0 && <WalletSecurityBanner />}
<Form
initial={initial}
{...validateProps}
onSubmit={async ({ amount, ...values }) => {
try {
const newConfig = !wallet.isConfigured
// enable wallet if wallet was just configured
if (newConfig) {
values.enabled = true
}
await wallet.save(values)
toaster.success('saved settings')
router.push('/settings/wallets')
} catch (err) {
console.error(err)
toaster.danger(err.message || err.toString?.())
}
}}
>
<WalletFields wallet={wallet} />
{wallet.walletType
? <AutowithdrawSettings wallet={wallet} />
: (
<CheckboxGroup name='enabled'>
<ClientCheckbox
disabled={!wallet.isConfigured}
initialValue={wallet.status === Status.Enabled}
label='enabled'
name='enabled'
groupClassName='mb-0'
/>
</CheckboxGroup>
)}
<WalletButtonBar
wallet={wallet} onDelete={async () => {
try {
await wallet.delete()
toaster.success('saved settings')
router.push('/settings/wallets')
} catch (err) {
console.error(err)
const message = 'failed to detach: ' + err.message || err.toString?.()
toaster.danger(message)
}
}}
/>
</Form>
<div className='mt-3 w-100'>
<WalletLogs wallet={wallet} embedded />
</div>
</CenterLayout>
)
}
function WalletFields ({ wallet: { config, fields, isConfigured } }) {
return fields
.map(({ name, label, type, help, optional, editable, clientOnly, serverOnly, ...props }, i) => {
const rawProps = {
...props,
name,
initialValue: config?.[name],
readOnly: isConfigured && editable === false && !!config?.[name],
groupClassName: props.hidden ? 'd-none' : undefined,
label: label
? (
<div className='d-flex align-items-center'>
{label}
{/* help can be a string or object to customize the label */}
{help && (
<Info label={help.label || 'help'}>
<Text>{help.text || help}</Text>
</Info>
)}
{optional && (
<small className='text-muted ms-2'>
{typeof optional === 'boolean' ? 'optional' : <Text>{optional}</Text>}
</small>
)}
</div>
)
: undefined,
required: !optional,
autoFocus: i === 0
}
if (type === 'text') {
return <ClientInput key={i} {...rawProps} />
}
if (type === 'password') {
return <PasswordInput key={i} {...rawProps} newPass />
}
return null
})
}