diff --git a/components/multi-step-form.js b/components/multi-step-form.js index 797b917f33..e7e1152725 100644 --- a/components/multi-step-form.js +++ b/components/multi-step-form.js @@ -203,6 +203,17 @@ export function useStep () { return steps[stepIndex] } +export function useIsFirstStep () { + const stepIndex = useStepIndex() + return stepIndex === 0 +} + +export function useIsLastStep () { + const maxSteps = useMaxSteps() + const stepIndex = useStepIndex() + return stepIndex === maxSteps - 1 +} + export function useNext () { const { next } = useContext(MultiStepFormContext) return next diff --git a/pages/wallets/index.js b/pages/wallets/index.js index 57c9098273..0f8cc466ff 100644 --- a/pages/wallets/index.js +++ b/pages/wallets/index.js @@ -115,6 +115,8 @@ export default function Wallet () { use real bitcoin
wallet logs + + settings {showPassphrase && ( <> diff --git a/pages/wallets/settings.js b/pages/wallets/settings.js new file mode 100644 index 0000000000..94fd6150d3 --- /dev/null +++ b/pages/wallets/settings.js @@ -0,0 +1,213 @@ +import { useCallback } from 'react' +import InputGroup from 'react-bootstrap/InputGroup' +import { useField } from 'formik' +import { useRouter } from 'next/router' +import { useMutation, useQuery } from '@apollo/client' +import { Checkbox, Form, Input, SubmitButton } from '@/components/form' +import Info from '@/components/info' +import { useToast } from '@/components/toast' +import AccordianItem from '@/components/accordian-item' +import { isNumber } from '@/lib/format' +import { walletSettingsSchema } from '@/lib/validate' +import { SET_WALLET_SETTINGS, WALLET_SETTINGS } from '@/wallets/client/fragments' +import { WalletLayout, WalletLayoutHeader } from '@/wallets/client/components' + +export default function WalletSettings () { + const { data } = useQuery(WALLET_SETTINGS) + const [setSettings] = useMutation(SET_WALLET_SETTINGS) + const toaster = useToast() + const router = useRouter() + + const onSubmit = useCallback(async (settings) => { + try { + await setSettings({ + variables: { settings }, + update: (cache, { data }) => { + cache.writeQuery({ + query: WALLET_SETTINGS, + data: { + walletSettings: { + __typename: 'WalletSettings', + ...data?.setWalletSettings + } + } + }) + } + }) + router.push('/wallets') + } catch (err) { + console.error(err) + toaster.danger('failed to save wallet') + } + }, [setSettings, toaster, router]) + + const initial = { + receiveCreditsBelowSats: data?.walletSettings?.receiveCreditsBelowSats ?? 10, + sendCreditsBelowSats: data?.walletSettings?.sendCreditsBelowSats ?? 10, + autoWithdrawThreshold: data?.walletSettings?.autoWithdrawThreshold ?? 10000, + autoWithdrawMaxFeePercent: data?.walletSettings?.autoWithdrawMaxFeePercent ?? 1, + autoWithdrawMaxFeeTotal: data?.walletSettings?.autoWithdrawMaxFeeTotal ?? 1, + proxyReceive: data?.walletSettings?.proxyReceive ?? true + } + + return ( + +
+
+ wallet settings +
+ +
+ save +
+ +
+ ) +} + +function Settings () { + return ( + <> + + + + + + } + /> + + ) +} + +function AutowithdrawSettings () { + const [{ value: threshold }] = useField('autoWithdrawThreshold') + const sendThreshold = Math.max(Math.floor(threshold / 10), 1) + + return ( + <> + sats} + required + type='number' + min={0} + groupClassName='mb-2' + /> + + max fee rate + +
    +
  • configure fee budget for autowithdrawals
  • +
  • if max fee total is higher for a withdrawal, we will use it instead to find a route
  • +
  • higher fee settings increase the likelihood of successful withdrawals
  • +
+
+
+ } + name='autoWithdrawMaxFeePercent' + append={%} + required + type='number' + min={0} + /> + + max fee total + + + + + } + name='autoWithdrawMaxFeeTotal' + append={sats} + required + type='number' + min={0} + /> + + ) +} + +function LightningAddressSettings () { + return ( + <> + enhance privacy of my lightning address + + + + + } + name='proxyReceive' + groupClassName='mb-3' + /> + + ) +} + +function CowboyCreditsSettings () { + return ( + <> + + receive credits for zaps below + + + + + } + name='receiveCreditsBelowSats' + required + append={sats} + type='number' + min={0} + /> + + send credits for zaps below + + + + + } + name='sendCreditsBelowSats' + required + append={sats} + type='number' + min={0} + /> + + ) +} diff --git a/wallets/client/components/form/hooks.js b/wallets/client/components/form/hooks.js index 579ed176e8..c9a01fb17f 100644 --- a/wallets/client/components/form/hooks.js +++ b/wallets/client/components/form/hooks.js @@ -1,5 +1,6 @@ import { isTemplate, isWallet, protocolClientSchema, protocolFields, protocolFormId, walletLud16Domain } from '@/wallets/lib/util' import { createContext, useContext, useEffect, useMemo, useCallback, useState } from 'react' +import { useRouter } from 'next/router' import { useWalletProtocolUpsert } from '@/wallets/client/hooks' import { MultiStepForm, useFormState, useStep } from '@/components/multi-step-form' import { parseNwcUrl } from '@/wallets/lib/validate' @@ -133,6 +134,7 @@ export function useSaveWallet () { const wallet = useWallet() const [formState] = useFormState() const upsert = useWalletProtocolUpsert() + const router = useRouter() const save = useCallback(async () => { let walletId = isWallet(wallet) ? wallet.id : undefined @@ -147,7 +149,8 @@ export function useSaveWallet () { ) walletId ??= id } - }, [wallet, formState, upsert]) + router.push('/wallets') + }, [wallet, formState, upsert, router]) return save } diff --git a/wallets/client/components/form/index.js b/wallets/client/components/form/index.js index 55dc93e974..4aaf243a8b 100644 --- a/wallets/client/components/form/index.js +++ b/wallets/client/components/form/index.js @@ -7,15 +7,14 @@ import { Checkbox, Form, Input, PasswordInput, SubmitButton } from '@/components 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 { useIsFirstStep, useIsLastStep, useNext } from '@/components/multi-step-form' +import { isTemplate, protocolDisplayName, protocolFormId, protocolLogName, walletLud16Domain } from '@/wallets/lib/util' import { WalletGuide, 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 { useFormikContext } from 'formik' -import { WalletMultiStepFormContextProvider, Step, useWallet, useWalletProtocols, useProtocol, useProtocolForm } from './hooks' -import { Settings } from './settings' +import { WalletMultiStepFormContextProvider, Step, useWallet, useWalletProtocols, useProtocol, useProtocolForm, useSaveWallet } from './hooks' import { BackButton, SkipButton } from './button' export function WalletMultiStepForm ({ wallet }) { @@ -33,8 +32,7 @@ export function WalletMultiStepForm ({ wallet }) { const steps = useMemo(() => [ support.send && Step.SEND, - support.receive && Step.RECEIVE, - Step.SETTINGS + support.receive && Step.RECEIVE ].filter(Boolean), [support]) @@ -51,7 +49,7 @@ export function WalletMultiStepForm ({ wallet }) { // and can thus render a different form for send vs. receive if (step === Step.SEND) return if (step === Step.RECEIVE) return - return + return null })} @@ -92,12 +90,21 @@ function WalletProtocolSelector () { function WalletProtocolForm () { const wallet = useWallet() const [protocol] = useProtocol() - const next = useNext() + + // on the last step, we save the wallet, otherwise we just go to the next step + const isLastStep = useIsLastStep() + const formNext = useNext() + const formSave = useSaveWallet() + const testSendPayment = useTestSendPayment(protocol) const testCreateInvoice = useTestCreateInvoice(protocol) const logger = useWalletLogger(protocol) const [{ fields, initial, schema }, setFormState] = useProtocolForm(protocol) + const next = useCallback(() => { + isLastStep ? formSave() : formNext() + }, [isLastStep, formSave, formNext]) + // create a copy of values to avoid mutating the original const onSubmit = useCallback(async ({ ...values }) => { const lud16Domain = walletLud16Domain(wallet.name) @@ -152,25 +159,31 @@ function WalletProtocolForm () { } 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 + // show 'cancel' in the first step + const showCancel = useIsFirstStep() + // show 'save' instead of 'next' in the last step + const isLastStep = useIsLastStep() + // show 'skip' if there's a next step + const showSkip = !isLastStep return (
- {stepIndex === 0 ? cancel : } - {!hideSkip ? :
} - - next - - + {showCancel ? cancel : } + {showSkip ? :
} + { + isLastStep + ? ( + + save + + ) + : ( + + next + + + ) + }
) } diff --git a/wallets/client/components/form/settings.js b/wallets/client/components/form/settings.js index 65ac898099..dc94f96383 100644 --- a/wallets/client/components/form/settings.js +++ b/wallets/client/components/form/settings.js @@ -45,7 +45,6 @@ export function Settings () { }) } }) - router.push('/wallets') } catch (err) { console.error(err) toaster.danger('failed to save wallet')