* Wallet flow * Prepopulate fields of complementary protocol * Remove TODO about one mutation for save We need to save protocols in separate mutations so we can use the wallet id returned by the first protocol save for the following protocol saves and save them all to the same wallet. * Fix badges not updated on wallet delete * Fix useProtocol call * Fix lightning address save via prompt * Don't pass share as attribute to DOM * Fix useCallback dependency * Progress numbers as SVGs * Fix progress line margins * Remove unused saveWallet arguments * Update cache with settings response * Fix line does not connect with number 1 * Don't reuse page nav arrows in form nav * Fix missing SVG hover style * Fix missing space in wallet save log message * Reuse CSS from nav.module.css * align buttons and their icons/text * center form progress line * increase top padding of form on smaller screens * provide margin above button bar on settings form --------- Co-authored-by: k00b <k00b@stacker.news>
232 lines
7.4 KiB
JavaScript
232 lines
7.4 KiB
JavaScript
import { useCallback, useMemo } from 'react'
|
|
import { InputGroup, Nav } from 'react-bootstrap'
|
|
import classNames from 'classnames'
|
|
import styles from '@/styles/wallet.module.css'
|
|
import navStyles from '@/styles/nav.module.css'
|
|
import { Checkbox, Form, Input, PasswordInput, SubmitButton } from '@/components/form'
|
|
import CancelButton from '@/components/cancel-button'
|
|
import Text from '@/components/text'
|
|
import Info from '@/components/info'
|
|
import { useFormState, useMaxSteps, useNext, useStepIndex } from '@/components/multi-step-form'
|
|
import { isTemplate, isWallet, protocolDisplayName, protocolFormId, protocolLogName, walletLud16Domain } from '@/wallets/lib/util'
|
|
import { WalletLayout, WalletLayoutHeader, WalletLayoutImageOrName, WalletLogs } from '@/wallets/client/components'
|
|
import { TemplateLogsProvider, useTestSendPayment, useWalletLogger, useTestCreateInvoice, useWalletSupport } from '@/wallets/client/hooks'
|
|
import ArrowRight from '@/svgs/arrow-right-s-fill.svg'
|
|
|
|
import { WalletMultiStepFormContextProvider, Step, useWallet, useWalletProtocols, useProtocol, useProtocolForm } from './hooks'
|
|
import { Settings } from './settings'
|
|
import { BackButton, SkipButton } from './button'
|
|
|
|
export function WalletMultiStepForm ({ wallet }) {
|
|
const initial = useMemo(() => wallet.protocols
|
|
.filter(p => !isTemplate(p))
|
|
.reduce((acc, p) => {
|
|
const formId = protocolFormId(p)
|
|
return {
|
|
...acc,
|
|
[formId]: p
|
|
}
|
|
}, {}), [wallet])
|
|
|
|
const support = useWalletSupport(wallet)
|
|
const steps = useMemo(() =>
|
|
[
|
|
support.send && Step.SEND,
|
|
support.receive && Step.RECEIVE,
|
|
Step.SETTINGS
|
|
].filter(Boolean),
|
|
[support])
|
|
|
|
return (
|
|
<WalletLayout>
|
|
<div className={styles.form}>
|
|
<WalletLayoutHeader>
|
|
<WalletLayoutImageOrName name={wallet.name} maxHeight='80px' />
|
|
</WalletLayoutHeader>
|
|
<WalletMultiStepFormContextProvider wallet={wallet} initial={initial} steps={steps}>
|
|
{steps.map(step => {
|
|
// WalletForm is aware of the current step via hooks
|
|
// and can thus render a different form for send vs. receive
|
|
if (step === Step.SEND) return <WalletForm key={step} />
|
|
if (step === Step.RECEIVE) return <WalletForm key={step} />
|
|
return <Settings key={step} />
|
|
})}
|
|
</WalletMultiStepFormContextProvider>
|
|
</div>
|
|
</WalletLayout>
|
|
)
|
|
}
|
|
|
|
function WalletForm () {
|
|
return (
|
|
<TemplateLogsProvider>
|
|
<WalletProtocolSelector />
|
|
<WalletProtocolForm />
|
|
</TemplateLogsProvider>
|
|
)
|
|
}
|
|
|
|
function WalletProtocolSelector () {
|
|
const protocols = useWalletProtocols()
|
|
const [protocol, selectProtocol] = useProtocol()
|
|
|
|
return (
|
|
<Nav className={classNames(navStyles.nav, 'mt-0')} activeKey={protocol?.name}>
|
|
{
|
|
protocols.map(p => {
|
|
return (
|
|
<Nav.Item key={p.id} onClick={() => selectProtocol(p)}>
|
|
<Nav.Link eventKey={p.name}>
|
|
{protocolDisplayName(p)}
|
|
</Nav.Link>
|
|
</Nav.Item>
|
|
)
|
|
})
|
|
}
|
|
</Nav>
|
|
)
|
|
}
|
|
|
|
function WalletProtocolForm () {
|
|
const wallet = useWallet()
|
|
const [protocol] = useProtocol()
|
|
const next = useNext()
|
|
const testSendPayment = useTestSendPayment(protocol)
|
|
const testCreateInvoice = useTestCreateInvoice(protocol)
|
|
const logger = useWalletLogger(protocol)
|
|
const [{ fields, initial, schema }, setFormState] = useProtocolForm(protocol)
|
|
|
|
// create a copy of values to avoid mutating the original
|
|
const onSubmit = useCallback(async ({ ...values }) => {
|
|
const lud16Domain = walletLud16Domain(wallet.name)
|
|
if (values.address && lud16Domain) {
|
|
values.address = `${values.address}@${lud16Domain}`
|
|
}
|
|
|
|
const name = protocolLogName(protocol)
|
|
|
|
if (isTemplate(protocol)) {
|
|
values.enabled = true
|
|
}
|
|
|
|
if (values.enabled) {
|
|
try {
|
|
if (protocol.send) {
|
|
logger.info(`testing ${name} send ...`)
|
|
const additionalValues = await testSendPayment(values)
|
|
values = { ...values, ...additionalValues }
|
|
logger.ok(`${name} send ok`)
|
|
} else {
|
|
logger.info(`testing ${name} receive ...`)
|
|
await testCreateInvoice(values)
|
|
logger.ok(`${name} receive ok`)
|
|
}
|
|
} catch (err) {
|
|
logger.error(err.message)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
setFormState(values)
|
|
next()
|
|
}, [protocol, wallet, setFormState, testSendPayment, logger, next])
|
|
|
|
return (
|
|
<>
|
|
<Form
|
|
key={`form-${protocol.id}`}
|
|
enableReinitialize
|
|
initial={initial}
|
|
schema={schema}
|
|
onSubmit={onSubmit}
|
|
>
|
|
{fields.map(field => <WalletProtocolFormField key={field.name} {...field} />)}
|
|
{!isTemplate(protocol) && <Checkbox name='enabled' label='enabled' />}
|
|
<WalletProtocolFormNavigator />
|
|
</Form>
|
|
<WalletLogs className='mt-3' protocol={protocol} key={`logs-${protocol.id}`} />
|
|
</>
|
|
)
|
|
}
|
|
|
|
function WalletProtocolFormNavigator () {
|
|
const wallet = useWallet()
|
|
const stepIndex = useStepIndex()
|
|
const maxSteps = useMaxSteps()
|
|
const [formState] = useFormState()
|
|
|
|
// was something already configured or was something configured just now?
|
|
const configExists = (isWallet(wallet) && wallet.protocols.length > 0) || Object.keys(formState).length > 0
|
|
|
|
// don't allow going to settings as last step with nothing configured
|
|
const hideSkip = stepIndex === maxSteps - 2 && !configExists
|
|
|
|
return (
|
|
<div className='d-flex justify-content-end align-items-center'>
|
|
{stepIndex === 0 ? <CancelButton>cancel</CancelButton> : <BackButton />}
|
|
{!hideSkip ? <SkipButton /> : <div className='ms-auto' />}
|
|
<SubmitButton variant='primary' className='ps-3 pe-2 d-flex align-items-center'>
|
|
next
|
|
<ArrowRight width={24} height={24} />
|
|
</SubmitButton>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function WalletProtocolFormField ({ type, ...props }) {
|
|
const wallet = useWallet()
|
|
const [protocol] = useProtocol()
|
|
|
|
function transform ({ validate, encrypt, editable, help, share, ...props }) {
|
|
const [upperHint, bottomHint] = Array.isArray(props.hint) ? props.hint : [null, props.hint]
|
|
|
|
const parseHelpText = text => Array.isArray(text) ? text.join('\n\n') : text
|
|
const _help = help
|
|
? (
|
|
typeof help === 'string'
|
|
? { label: null, text: help }
|
|
: (
|
|
Array.isArray(help)
|
|
? { label: null, text: parseHelpText(help) }
|
|
: { label: help.label, text: parseHelpText(help.text) }
|
|
)
|
|
)
|
|
: null
|
|
|
|
const readOnly = !!protocol.config?.[props.name] && editable === false
|
|
|
|
const label = (
|
|
<div className='d-flex align-items-center'>
|
|
{props.label}
|
|
{_help && (
|
|
<Info label={_help.label}>
|
|
<Text>{_help.text}</Text>
|
|
</Info>
|
|
)}
|
|
<small className='text-muted ms-2'>
|
|
{upperHint
|
|
? <Text>{upperHint}</Text>
|
|
: (!props.required ? 'optional' : null)}
|
|
</small>
|
|
</div>
|
|
)
|
|
|
|
return { ...props, hint: bottomHint, label, readOnly }
|
|
}
|
|
|
|
switch (type) {
|
|
case 'text': {
|
|
let append
|
|
const lud16Domain = walletLud16Domain(wallet.name)
|
|
if (props.name === 'address' && lud16Domain) {
|
|
append = <InputGroup.Text className='text-monospace'>@{lud16Domain}</InputGroup.Text>
|
|
}
|
|
return <Input {...transform(props)} append={append} />
|
|
}
|
|
case 'password':
|
|
return <PasswordInput {...transform(props)} />
|
|
default:
|
|
return null
|
|
}
|
|
}
|