* 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>
137 lines
4.8 KiB
JavaScript
137 lines
4.8 KiB
JavaScript
import { isTemplate, isWallet, protocolClientSchema, protocolFields, protocolFormId, walletLud16Domain } from '@/wallets/lib/util'
|
|
import { createContext, useContext, useEffect, useMemo, useCallback, useState } from 'react'
|
|
import { useWalletProtocolUpsert } from '@/wallets/client/hooks'
|
|
import { MultiStepForm, useFormState, useStep } from '@/components/multi-step-form'
|
|
|
|
export const Step = {
|
|
SEND: 'send',
|
|
RECEIVE: 'receive',
|
|
SETTINGS: 'settings'
|
|
}
|
|
|
|
const WalletMultiStepFormContext = createContext()
|
|
|
|
export function WalletMultiStepFormContextProvider ({ wallet, initial, steps, children }) {
|
|
// save selected protocol, but useProtocol will always return the first protocol if no protocol is selected
|
|
const [protocol, setProtocol] = useState(null)
|
|
const value = useMemo(() => ({ wallet, protocol, setProtocol }), [wallet, protocol, setProtocol])
|
|
return (
|
|
<WalletMultiStepFormContext.Provider value={value}>
|
|
<MultiStepForm initial={initial} steps={steps}>
|
|
{children}
|
|
</MultiStepForm>
|
|
</WalletMultiStepFormContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useWallet () {
|
|
const { wallet } = useContext(WalletMultiStepFormContext)
|
|
return wallet
|
|
}
|
|
|
|
export function useWalletProtocols () {
|
|
const step = useStep()
|
|
const wallet = useWallet()
|
|
|
|
const protocolFilter = useCallback(p => step === Step.SEND ? p.send : !p.send, [step])
|
|
|
|
return useMemo(() => {
|
|
// all protocols are templates if wallet is a template
|
|
if (isTemplate(wallet)) {
|
|
return wallet.protocols.filter(protocolFilter)
|
|
}
|
|
// return template for every protocol that isn't configured
|
|
const configured = wallet.protocols.filter(protocolFilter)
|
|
const templates = wallet.template.protocols.filter(protocolFilter)
|
|
return templates.map(p => configured.find(c => c.name === p.name) ?? p)
|
|
}, [wallet, protocolFilter])
|
|
}
|
|
|
|
export function useProtocol () {
|
|
const { protocol, setProtocol } = useContext(WalletMultiStepFormContext)
|
|
const protocols = useWalletProtocols()
|
|
|
|
useEffect(() => {
|
|
// when we move between send and receive, we need to make sure that we've selected a protocol
|
|
// that actually exists, so if the protocol is not found, we set it to the first protocol
|
|
if (!protocol || !protocols.find(p => p.id === protocol.id)) {
|
|
setProtocol(protocols[0])
|
|
}
|
|
}, [protocol, protocols, setProtocol])
|
|
|
|
// make sure we always have a protocol, even on first render before useEffect runs
|
|
return useMemo(() => [protocol ?? protocols[0], setProtocol], [protocol, protocols, setProtocol])
|
|
}
|
|
|
|
function useProtocolFormState (protocol) {
|
|
const formId = protocolFormId(protocol)
|
|
const [formState, setFormState] = useFormState(formId)
|
|
const setProtocolFormState = useCallback(
|
|
({ enabled, ...config }) => {
|
|
setFormState({ ...protocol, enabled, config })
|
|
},
|
|
[setFormState, protocol])
|
|
return useMemo(() => [formState, setProtocolFormState], [formState, setProtocolFormState])
|
|
}
|
|
|
|
export function useProtocolForm (protocol) {
|
|
const [formState, setFormState] = useProtocolFormState(protocol)
|
|
const [complementaryFormState] = useProtocolFormState({ name: protocol.name, send: !protocol.send })
|
|
const wallet = useWallet()
|
|
const lud16Domain = walletLud16Domain(wallet.name)
|
|
const fields = protocolFields(protocol)
|
|
const initial = fields.reduce((acc, field) => {
|
|
// we only fallback to the existing protocol config because formState was not initialized yet on first render
|
|
// after init, we use formState as the source of truth everywhere
|
|
let value = formState?.config?.[field.name] ?? protocol.config?.[field.name]
|
|
|
|
if (!value && field.share) {
|
|
value = complementaryFormState?.config?.[field.name]
|
|
}
|
|
|
|
if (field.name === 'address' && lud16Domain && value) {
|
|
value = value.split('@')[0]
|
|
}
|
|
|
|
return {
|
|
...acc,
|
|
[field.name]: value || ''
|
|
}
|
|
}, { enabled: formState?.enabled ?? protocol.enabled })
|
|
|
|
let schema = protocolClientSchema(protocol)
|
|
if (lud16Domain) {
|
|
schema = schema.transform(({ address, ...rest }) => {
|
|
return {
|
|
address: address ? `${address}@${lud16Domain}` : '',
|
|
...rest
|
|
}
|
|
})
|
|
}
|
|
|
|
return useMemo(() => [{ fields, initial, schema }, setFormState], [fields, initial, schema, setFormState])
|
|
}
|
|
|
|
export function useSaveWallet () {
|
|
const wallet = useWallet()
|
|
const [formState] = useFormState()
|
|
const upsert = useWalletProtocolUpsert()
|
|
|
|
const save = useCallback(async () => {
|
|
let walletId = isWallet(wallet) ? wallet.id : undefined
|
|
for (const protocol of Object.values(formState)) {
|
|
const { id } = await upsert(
|
|
{
|
|
...wallet,
|
|
id: walletId,
|
|
__typename: walletId ? 'Wallet' : 'WalletTemplate'
|
|
},
|
|
protocol, { ...protocol.config, enabled: protocol.enabled }
|
|
)
|
|
walletId ??= id
|
|
}
|
|
}, [wallet, formState, upsert])
|
|
|
|
return save
|
|
}
|