Repository: joinmarket-webui/joinmarket-webui
Pull request #296 (2022-05-26 15:38:01)

ui: Settings screen according to figma
Adapt `Settings` screen according to Figma.

Differences:
- "Log out" is in section "Wallet" instead of "Settings"

Question:
- Should the "Create new wallet" button be removed? Personally, leaning towards "Yes". (related https://github.com/joinmarket-webui/joinmarket-webui/issues/228)

## 📸 Before
<img src="https://user-images.githubusercontent.com/3358649/170521543-7c7e6278-29d0-4ccb-9013-8756af58749e.png" width=400 />

## 📸 After
<img src="https://user-images.githubusercontent.com/3358649/170521710-54d5b07c-3aa9-4ad7-8ed9-3205dac656aa.png" width=400 />

editwentyone: the Logout is for the whole app, not on wallet level. it should go over Wallet category please :) 
theborakompanioni: > maybe even "Log out from Jam"

Hmm.. I am not sure I know what this means.

Currently "logging out" is just forgetting the session key. What should happen, when someone "logs out"?

editwentyone: its should bring you to "login page" https://www.figma.com/file/kfejZJFlwBywvLEnPEmJo1/?node-id=3994%3A73345

but I think we do not have that yet 
dnlggr: > the Logout is for the whole app, not on wallet level.

I think these are equivalent things at this time (as @theborakompanioni mentioned) right?
editwentyone: I think that "log out" from Wallet, because its now underneath is misleading. 

logout should be only used when the user logged in somewhere, if that's not the case, we need to rethink what's actually happening.

I would move logout back up again and hide it as long as we don't have login implemented. why would a user logout aka "lock" the wallet and not just close the window? 
theborakompanioni: > I would move logout back up again and hide it as long as we don't have login implemented. 

Removed it for now.

> why would a user logout aka "lock" the wallet and not just close the window? 

You are right, the (now removed) "Log out" action, by forgetting the auth token, would have been the same as closing the window.
However, I can relate to some _uneasiness_ when closing a tab with an "unlocked" wallet. A dedicated button would "feel" a little better, albeit there is no difference in what actually happens.

> what happens when we just **close** the window?

The auth token is removed (as `sessionStorage` cleared when the page session ends).

> 
> ~
> 
> so we have:
> 
>     * **create wallet**: creating new wallets, not possible while another wallet is still used and unlocked
> 
>     * **unlock wallet**: wallet is _inactive_, **needs password** to be active
> 
>     * **open wallet**: entering, using, wallet is _active_
> 
>     * **lock wallet**: deactivate wallet, wallet is now _inactive_
> 
>     * **Logout** wallet (misunderstanding): wallet stays active in the background, **needs password** to be opened/ entered again

Maybe something like "Leave wallet", "Exit wallet" or "Close wallet" (although the term "Closing" might be confused by "Locking")?

In any case, I have just removed it for now and will support anything that you think makes most sense from an UI/UX perspective.
Do you think these changes can be merged?

<img src="https://user-images.githubusercontent.com/3358649/172131107-f966c04d-6820-44a3-a431-93580c54f46c.png" width=400 />



Patch:
diff --git a/public/sprite.svg b/public/sprite.svg
index e685395..2970663 100644
--- a/public/sprite.svg
+++ b/public/sprite.svg
@@ -129,4 +129,7 @@
      d="M 12,0 A 12,12 0 0 0 0,12 12,12 0 0 0 12,24 12,12 0 0 0 24,12 12,12 0 0 0 12,0 Z m 3.13805,6.87 c 0.834,0 1.58395,0.34795 2.11195,0.91195 0.66,-0.132 1.27798,-0.37195 1.83598,-0.70195 -0.216,0.678 -0.67196,1.24195 -1.27196,1.60195 0.588,-0.072 1.14596,-0.22797 1.66196,-0.45597 -0.39,0.582 -0.88198,1.092 -1.44598,1.5 0.006,0.12 0.006,0.24607 0.006,0.37207 0,3.822 -2.90996,8.23195 -8.23196,8.23195 -1.632,0 -3.15607,-0.48005 -4.42207,-1.30805 0.228,0.03 0.456,0.0421 0.69,0.0421 1.356,0 2.60403,-0.46197 3.59403,-1.23597 -1.266,-0.024 -2.334,-0.858 -2.7,-2.01 0.18,0.036 0.35997,0.0539 0.54597,0.0539 0.264,0 0.52207,-0.0359 0.76207,-0.10195 -1.326,-0.27 -2.32207,-1.43405 -2.32207,-2.83805 v -0.036 c 0.39,0.216 0.83405,0.348 1.30805,0.36 -0.78,-0.516 -1.29,-1.40398 -1.29,-2.40598 0,-0.528 0.144,-1.02595 0.39,-1.45195 1.428,1.752 3.55802,2.9039 5.96402,3.0239 -0.054,-0.21 -0.078,-0.432 -0.078,-0.66 0,-1.596 1.29607,-2.89195 2.89207,-2.89195 z"
      style="stroke-width:0.06" />
     </symbol>
+    <symbol id="plus" fill="none" stroke="currentColor"  viewBox="0 0 24 24">
+        <path stroke-linecap="round" d="M12 3.5v17M20.5 12h-17"></path>
+    </symbol>
 </svg>
diff --git a/src/components/CreateWallet.jsx b/src/components/CreateWallet.jsx
index 8564d6c..31ad823 100644
--- a/src/components/CreateWallet.jsx
+++ b/src/components/CreateWallet.jsx
@@ -392,7 +392,7 @@ export default function CreateWallet({ startWallet }) {
           <Trans i18nKey="create_wallet.alert_other_wallet_unlocked">
             Currently <strong>{{ walletName: walletDisplayName(serviceInfo?.walletName) }}</strong> is active. You need
             to lock it first.
-            <Link to={routes.home} className="alert-link">
+            <Link to={routes.walletList} className="alert-link">
               Go back
             </Link>
             .
diff --git a/src/components/CurrentWalletMagic.jsx b/src/components/CurrentWalletMagic.jsx
index 26c6956..81a28ba 100644
--- a/src/components/CurrentWalletMagic.jsx
+++ b/src/components/CurrentWalletMagic.jsx
@@ -191,14 +191,6 @@ export default function CurrentWalletMagic() {
         <rb.Row>
           <hr className="my-4" />
         </rb.Row>
-        <rb.Row>
-          <ExtendedLink disabled={isLoading} to={routes.home} className="btn btn-outline-dark">
-            <div className="d-flex justify-content-center align-items-center">
-              <Sprite symbol="wallet" width="24" height="24" />
-              <div className="ps-1">{t('current_wallet.button_switch_wallet')}</div>
-            </div>
-          </ExtendedLink>
-        </rb.Row>
       </>
     </div>
   )
diff --git a/src/components/Earn.jsx b/src/components/Earn.jsx
index 3897f96..31701b1 100644
--- a/src/components/Earn.jsx
+++ b/src/components/Earn.jsx
@@ -8,7 +8,7 @@ import { useCurrentWallet } from '../context/WalletContext'
 import { useServiceInfo, useReloadServiceInfo } from '../context/ServiceInfoContext'
 import Sprite from './Sprite'
 import PageTitle from './PageTitle'
-import ToggleSwitch from './ToggleSwitch'
+import SegmentedTabs from './SegmentedTabs'
 import { EarnReportOverlay } from './EarnReport'
 import * as Api from '../libs/JmWalletApi'
 import styles from './Earn.module.css'
@@ -84,6 +84,8 @@ export default function Earn() {
   const currentWallet = useCurrentWallet()
   const serviceInfo = useServiceInfo()
   const reloadServiceInfo = useReloadServiceInfo()
+
+  const [isAdvancedView, setIsAdvancedView] = useState(settings.useAdvancedWalletMode)
   const [alert, setAlert] = useState(null)
   const [serviceInfoAlert, setServiceInfoAlert] = useState(null)
   const [isLoading, setIsLoading] = useState(true)
@@ -110,7 +112,7 @@ export default function Earn() {
       Api.postMakerStart({ walletName, token }, data)
         .then((res) => (res.ok ? true : Api.Helper.throwError(res)))
         // show the loader a little longer to avoid flickering
-        .then((_) => new Promise((r) => setTimeout(r, 200)))
+        .then((result) => new Promise((r) => setTimeout(() => r(result), 200)))
         .catch((e) => {
           setIsWaitingMakerStart(false)
           throw e
@@ -129,7 +131,7 @@ export default function Earn() {
     // Wait for the websocket or session response!
     return Api.getMakerStop({ walletName, token })
       .then((res) => (res.ok ? true : Api.Helper.throwError(res)))
-      .then((_) => new Promise((r) => setTimeout(r, MAKER_STOP_RESPONSE_DELAY_MS)))
+      .then((result) => new Promise((r) => setTimeout(() => r(result), MAKER_STOP_RESPONSE_DELAY_MS)))
       .catch((e) => {
         setIsWaitingMakerStop(false)
         throw e
@@ -193,7 +195,7 @@ export default function Earn() {
     }
 
     if (isRelOffer) {
-      if (values.feeRel < feeRelMin || values.feeRel > feeRelMax) {
+      if (typeof values.feeRel !== 'number' || values.feeRel < feeRelMin || values.feeRel > feeRelMax) {
         errors.feeRel = t('earn.feedback_invalid_rel_fee', {
           feeRelPercentageMin: `${factorToPercentage(feeRelMin)}%`,
           feeRelPercentageMax: `${factorToPercentage(feeRelMax)}%`,
@@ -202,12 +204,12 @@ export default function Earn() {
     }
 
     if (isAbsOffer) {
-      if (values.feeAbs < 0) {
+      if (typeof values.feeAbs !== 'number' || values.feeAbs < 0) {
         errors.feeAbs = t('earn.feedback_invalid_abs_fee')
       }
     }
 
-    if (values.minsize < 0) {
+    if (typeof values.minsize !== 'number' || values.minsize < 0) {
       errors.minsize = t('earn.feedback_invalid_min_amount')
     }
 
@@ -243,11 +245,11 @@ export default function Earn() {
   }
 
   return (
-    <div className="earn">
+    <div className={styles['earn']}>
+      <PageTitle title={t('earn.title')} subtitle={t('earn.subtitle')} />
+
       <rb.Row>
         <rb.Col>
-          <PageTitle title={t('earn.title')} subtitle={t('earn.subtitle')} />
-
           <rb.Fade in={serviceInfo?.coinjoinInProgress} mountOnEnter={true} unmountOnExit={true}>
             <rb.Alert variant="info" className="mb-4">
               {t('earn.alert_coinjoin_in_progress')}
@@ -268,121 +270,164 @@ export default function Earn() {
               {({ handleSubmit, setFieldValue, handleChange, handleBlur, values, touched, errors, isSubmitting }) => (
                 <rb.Form onSubmit={handleSubmit} noValidate>
                   {!serviceInfo?.makerRunning && !isWaitingMakerStart && !isWaitingMakerStop && (
-                    <>
-                      {settings.useAdvancedWalletMode && (
-                        <rb.Form.Group className="mb-3" controlId="offertype">
-                          <ToggleSwitch
-                            label={t('earn.toggle_rel_offer')}
-                            initialValue={isRelativeOffer(values.offertype)}
-                            onToggle={(isToggled) =>
-                              setFieldValue('offertype', isToggled ? OFFERTYPE_REL : OFFERTYPE_ABS, true)
-                            }
-                            disabled={isLoading || isSubmitting}
-                          />
-                        </rb.Form.Group>
-                      )}
-                      {values.offertype === OFFERTYPE_REL ? (
-                        <rb.Form.Group className="mb-3" controlId="feeRel">
-                          <rb.Form.Label className="mb-0">
-                            {t('earn.label_rel_fee', {
-                              fee: values.feeRel !== '' ? `(${factorToPercentage(values.feeRel)}%)` : '',
-                            })}
-                          </rb.Form.Label>
-                          <div className="mb-2">
-                            <rb.Form.Text className="text-secondary">{t('earn.description_rel_fee')}</rb.Form.Text>
-                          </div>
-                          {isLoading ? (
-                            <rb.Placeholder as="div" animation="wave">
-                              <rb.Placeholder xs={12} className={styles['input-loader']} />
-                            </rb.Placeholder>
-                          ) : (
-                            <rb.Form.Control
-                              className="slashed-zeroes"
-                              type="number"
-                              name="feeRel"
-                              disabled={isSubmitting}
-                              onChange={(e) => {
-                                const value = e.target.value || ''
-                                setFieldValue('feeRel', value && percentageToFactor(e.target.value), true)
+                    <div className={styles['settings-container']}>
+                      <rb.Button
+                        variant={`${settings.theme}`}
+                        className={`${styles['settings-btn']} d-flex align-items-center`}
+                        onClick={() => setIsAdvancedView((current) => !current)}
+                      >
+                        {t('earn.button_settings')}
+                        <Sprite
+                          symbol={`caret-${isAdvancedView ? 'up' : 'down'}`}
+                          className="ms-1"
+                          width="20"
+                          height="20"
+                        />
+                      </rb.Button>
+                      {isAdvancedView && (
+                        <div className="my-4">
+                          <rb.Form.Group className="mb-4 d-flex justify-content-center" controlId="offertype">
+                            <SegmentedTabs
+                              name="offertype"
+                              tabs={[
+                                {
+                                  label: t('earn.radio_abs_offer_label'),
+                                  value: OFFERTYPE_ABS,
+                                },
+                                {
+                                  label: t('earn.radio_rel_offer_label'),
+                                  value: OFFERTYPE_REL,
+                                },
+                              ]}
+                              onChange={(tab, checked) => {
+                                checked && setFieldValue('offertype', tab.value, true)
                               }}
-                              onBlur={handleBlur}
-                              value={factorToPercentage(values.feeRel)}
-                              isValid={touched.feeRel && !errors.feeRel}
-                              isInvalid={touched.feeRel && errors.feeRel}
-                              min={0}
-                              step={feeRelPercentageStep}
-                            />
-                          )}
-                          <rb.Form.Control.Feedback type="invalid">{errors.feeRel}</rb.Form.Control.Feedback>
-                        </rb.Form.Group>
-                      ) : (
-                        <rb.Form.Group className="mb-3" controlId="feeAbs">
-                          <rb.Form.Label className="mb-0">
-                            {t('earn.label_abs_fee', {
-                              fee:
-                                '(' +
-                                (values.feeAbs === '' ? 0 : values.feeAbs) +
-                                ' ' +
-                                (values.feeAbs === 1 ? 'sat' : 'sats') +
-                                ')',
-                            })}
-                          </rb.Form.Label>
-                          <div className="mb-2">
-                            <rb.Form.Text className="text-secondary">{t('earn.description_abs_fee')}</rb.Form.Text>
-                          </div>
-                          {isLoading ? (
-                            <rb.Placeholder as="div" animation="wave">
-                              <rb.Placeholder xs={12} className={styles['input-loader']} />
-                            </rb.Placeholder>
-                          ) : (
-                            <rb.Form.Control
-                              className="slashed-zeroes"
-                              type="number"
-                              name="feeAbs"
-                              value={values.feeAbs}
-                              disabled={isSubmitting}
-                              onChange={handleChange}
-                              onBlur={handleBlur}
-                              isValid={touched.feeAbs && !errors.feeAbs}
-                              isInvalid={touched.feeAbs && errors.feeAbs}
-                              min={0}
-                              step={1}
+                              initialValue={values.offertype}
+                              disabled={isLoading || isSubmitting}
                             />
-                          )}
-                          <rb.Form.Control.Feedback type="invalid">{errors.feeAbs}</rb.Form.Control.Feedback>
-                        </rb.Form.Group>
-                      )}
-                      {settings.useAdvancedWalletMode && (
-                        <rb.Form.Group className="mb-3" controlId="minsize">
-                          <rb.Form.Label>{t('earn.label_min_amount')}</rb.Form.Label>
-                          {isLoading ? (
-                            <rb.Placeholder as="div" animation="wave">
-                              <rb.Placeholder xs={12} className={styles['input-loader']} />
-                            </rb.Placeholder>
+                          </rb.Form.Group>
+                          {values.offertype === OFFERTYPE_REL ? (
+                            <rb.Form.Group className="mb-3" controlId="feeRel">
+                              <rb.Form.Label className="mb-0">
+                                {t('earn.label_rel_fee', {
+                                  fee:
+                                    typeof values.feeRel === 'number' ? `(${factorToPercentage(values.feeRel)}%)` : '',
+                                })}
+                              </rb.Form.Label>
+                              <div className="mb-2">
+                                <rb.Form.Text className="text-secondary">{t('earn.description_rel_fee')}</rb.Form.Text>
+                              </div>
+                              {isLoading ? (
+                                <rb.Placeholder as="div" animation="wave">
+                                  <rb.Placeholder xs={12} className={styles['input-loader']} />
+                                </rb.Placeholder>
+                              ) : (
+                                <rb.InputGroup>
+                                  <rb.InputGroup.Text id="feeRel-addon1" className={styles['input-group-text']}>
+                                    %
+                                  </rb.InputGroup.Text>
+                                  <rb.Form.Control
+                                    aria-label={t('earn.label_rel_fee', { fee: '' })}
+                                    className="slashed-zeroes"
+                                    type="number"
+                                    name="feeRel"
+                                    disabled={isSubmitting}
+                                    onChange={(e) => {
+                                      const value = e.target.value || ''
+                                      setFieldValue('feeRel', value !== '' ? percentageToFactor(value) : '', true)
+                                    }}
+                                    onBlur={handleBlur}
+                                    value={typeof values.feeRel === 'number' ? factorToPercentage(values.feeRel) : ''}
+                                    isValid={touched.feeRel && !errors.feeRel}
+                                    isInvalid={touched.feeRel && errors.feeRel}
+                                    min={0}
+                                    step={feeRelPercentageStep}
+                                  />
+                                </rb.InputGroup>
+                              )}
+                              <rb.Form.Control.Feedback type="invalid">{errors.feeRel}</rb.Form.Control.Feedback>
+                            </rb.Form.Group>
                           ) : (
-                            <rb.Form.Control
-                              className="slashed-zeroes"
-                              type="number"
-                              name="minsize"
-                              value={values.minsize}
-                              disabled={isSubmitting}
-                              onChange={handleChange}
-                              onBlur={handleBlur}
-                              isValid={touched.minsize && !errors.minsize}
-                              isInvalid={touched.minsize && errors.minsize}
-                              min={0}
-                              step={1000}
-                            />
+                            <rb.Form.Group className="mb-3" controlId="feeAbs">
+                              <rb.Form.Label className="mb-0">
+                                {t('earn.label_abs_fee', {
+                                  fee:
+                                    typeof values.feeAbs === 'number'
+                                      ? `(${values.feeAbs} ${values.feeAbs === 1 ? 'sat' : 'sats'})`
+                                      : '',
+                                })}
+                              </rb.Form.Label>
+                              <div className="mb-2">
+                                <rb.Form.Text className="text-secondary">{t('earn.description_abs_fee')}</rb.Form.Text>
+                              </div>
+                              {isLoading ? (
+                                <rb.Placeholder as="div" animation="wave">
+                                  <rb.Placeholder xs={12} className={styles['input-loader']} />
+                                </rb.Placeholder>
+                              ) : (
+                                <rb.InputGroup>
+                                  <rb.InputGroup.Text id="feeAbs-addon1" className={styles['input-group-text']}>
+                                    <Sprite symbol="sats" width="24" height="24" />
+                                  </rb.InputGroup.Text>
+                                  <rb.Form.Control
+                                    aria-label={t('earn.label_abs_fee', { fee: '' })}
+                                    className="slashed-zeroes"
+                                    type="number"
+                                    name="feeAbs"
+                                    value={values.feeAbs}
+                                    disabled={isSubmitting}
+                                    onChange={handleChange}
+                                    onBlur={handleBlur}
+                                    isValid={touched.feeAbs && !errors.feeAbs}
+                                    isInvalid={touched.feeAbs && errors.feeAbs}
+                                    min={0}
+                                    step={1}
+                                  />
+                                </rb.InputGroup>
+                              )}
+                              <rb.Form.Control.Feedback type="invalid">{errors.feeAbs}</rb.Form.Control.Feedback>
+                            </rb.Form.Group>
                           )}
-                          <rb.Form.Control.Feedback type="invalid">{errors.minsize}</rb.Form.Control.Feedback>
-                        </rb.Form.Group>
+
+                          <rb.Form.Group controlId="minsize">
+                            <rb.Form.Label>{t('earn.label_min_amount')}</rb.Form.Label>
+                            {isLoading ? (
+                              <rb.Placeholder as="div" animation="wave">
+                                <rb.Placeholder xs={12} className={styles['input-loader']} />
+                              </rb.Placeholder>
+                            ) : (
+                              <rb.InputGroup>
+                                <rb.InputGroup.Text id="minsize-addon1" className={styles['input-group-text']}>
+                                  <Sprite symbol="sats" width="24" height="24" />
+                                </rb.InputGroup.Text>
+                                <rb.Form.Control
+                                  className="slashed-zeroes"
+                                  type="number"
+                                  name="minsize"
+                                  value={values.minsize}
+                                  disabled={isSubmitting}
+                                  onChange={handleChange}
+                                  onBlur={handleBlur}
+                                  isValid={touched.minsize && !errors.minsize}
+                                  isInvalid={touched.minsize && errors.minsize}
+                                  min={0}
+                                  step={1000}
+                                />
+                              </rb.InputGroup>
+                            )}
+                            <rb.Form.Control.Feedback type="invalid">{errors.minsize}</rb.Form.Control.Feedback>
+                          </rb.Form.Group>
+                        </div>
                       )}
-                    </>
+
+                      <hr />
+                    </div>
                   )}
 
                   <rb.Button
                     variant="dark"
                     type="submit"
+                    className="mt-2"
                     disabled={isLoading || isSubmitting || isWaitingMakerStart || isWaitingMakerStop}
                   >
                     <div className="d-flex justify-content-center align-items-center">
@@ -413,24 +458,20 @@ export default function Earn() {
         </rb.Col>
       </rb.Row>
 
-      {settings.useAdvancedWalletMode && (
-        <>
-          <rb.Row className="mt-5 mb-3">
-            <rb.Col className="d-flex justify-content-center">
-              <rb.Button
-                variant="outline-dark"
-                className="border-0 mb-2 d-inline-flex align-items-center"
-                onClick={() => setIsShowReport(true)}
-              >
-                <Sprite symbol="show" width="24" height="24" className="me-2" />
-                {t('earn.button_show_report')}
-              </rb.Button>
-            </rb.Col>
-          </rb.Row>
-
+      <rb.Row className="mt-5 mb-3">
+        <rb.Col className="d-flex justify-content-center">
           <EarnReportOverlay show={isShowReport} onHide={() => setIsShowReport(false)} />
-        </>
-      )}
+
+          <rb.Button
+            variant="outline-dark"
+            className="border-0 mb-2 d-inline-flex align-items-center"
+            onClick={() => setIsShowReport(true)}
+          >
+            <Sprite symbol="show" width="24" height="24" className="me-2" />
+            {t('earn.button_show_report')}
+          </rb.Button>
+        </rb.Col>
+      </rb.Row>
     </div>
   )
 }
diff --git a/src/components/Earn.module.css b/src/components/Earn.module.css
index f11c86f..4f0ec45 100644
--- a/src/components/Earn.module.css
+++ b/src/components/Earn.module.css
@@ -1,4 +1,42 @@
-.input-loader {
+.earn .input-loader {
   height: 3.5rem;
   border-radius: 0.25rem;
 }
+
+.earn form input:not([type='checkbox']) {
+  height: 3.5rem;
+}
+
+.earn form button {
+  height: 3rem;
+  width: 100%;
+}
+
+.settings-container {
+  margin-top: 1.5rem;
+}
+
+.settings-container button.settings-btn {
+  background-color: transparent !important;
+  border: none;
+  padding-left: 0;
+  font-weight: 500;
+}
+
+.settings-container .input-group-text {
+  width: 5ch;
+  display: inline-flex;
+  justify-content: center;
+  align-items: center;
+  font-size: 1.2rem;
+}
+
+.earn hr {
+  background-color: rgba(222, 222, 222, 1);
+  opacity: 100%;
+}
+
+:root[data-theme='dark'] .earn hr {
+  background-color: var(--bs-gray-800);
+  opacity: 100%;
+}
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx
index b834a92..39a9058 100644
--- a/src/components/Navbar.jsx
+++ b/src/components/Navbar.jsx
@@ -121,7 +121,7 @@ const TrailingNav = ({ coinjoinInProgess, onClick }) => {
       </rb.Nav.Item>
       <rb.Nav.Item className="d-flex align-items-stretch">
         <NavLink
-          to={routes.home}
+          to={routes.walletList}
           onClick={onClick}
           className={({ isActive }) =>
             'nav-link d-flex align-items-center justify-content-center px-0' + (isActive ? ' active' : '')
diff --git a/src/components/Receive.jsx b/src/components/Receive.jsx
index 3223bb6..122d235 100644
--- a/src/components/Receive.jsx
+++ b/src/components/Receive.jsx
@@ -70,7 +70,7 @@ export default function Receive() {
   }
 
   return (
-    <div className="receive">
+    <div className={`${styles.receive}`}>
       <PageTitle title={t('receive.title')} subtitle={t('receive.subtitle')} />
       {alert && <rb.Alert variant={alert.variant}>{alert.message}</rb.Alert>}
       <div className="qr-container">
@@ -101,53 +101,55 @@ export default function Receive() {
           </rb.Card.Body>
         </rb.Card>
       </div>
-      <div className={styles['settings-container']}>
-        <rb.Button
-          variant={`${settings.theme}`}
-          className="ps-0 border-0 d-flex align-items-center"
-          onClick={() => setShowSettings(!showSettings)}
-        >
-          {t('receive.button_settings')}
-          <Sprite symbol={`caret-${showSettings ? 'up' : 'down'}`} className="ms-1" width="20" height="20" />
-        </rb.Button>
-      </div>
       <rb.Form onSubmit={onSubmit} validated={validated} noValidate>
-        {showSettings && (
-          <>
-            {settings.useAdvancedWalletMode && (
-              <rb.Form.Group className="mt-4" controlId="account">
-                <rb.Form.Label>{t('receive.label_choose_account')}</rb.Form.Label>
-                <rb.Form.Select
-                  defaultValue={account}
-                  onChange={(e) => setAccount(parseInt(e.target.value, 10))}
-                  required
-                  disabled={isLoading || accounts.length === 0}
-                >
-                  {accounts.map((val) => (
-                    <option key={val} value={val}>
-                      {t('receive.account_selector_option_dev_mode', { number: val })}
-                    </option>
-                  ))}
-                </rb.Form.Select>
+        <div className={styles['settings-container']}>
+          <rb.Button
+            variant={`${settings.theme}`}
+            className={`${styles['settings-btn']} d-flex align-items-center`}
+            onClick={() => setShowSettings(!showSettings)}
+          >
+            {t('receive.button_settings')}
+            <Sprite symbol={`caret-${showSettings ? 'up' : 'down'}`} className="ms-1" width="20" height="20" />
+          </rb.Button>
+          {showSettings && (
+            <div className="my-4">
+              {settings.useAdvancedWalletMode && (
+                <rb.Form.Group className="mb-4" controlId="account">
+                  <rb.Form.Label>{t('receive.label_choose_account')}</rb.Form.Label>
+                  <rb.Form.Select
+                    defaultValue={account}
+                    onChange={(e) => setAccount(parseInt(e.target.value, 10))}
+                    required
+                    disabled={isLoading || accounts.length === 0}
+                  >
+                    {accounts.map((val) => (
+                      <option key={val} value={val}>
+                        {t('receive.account_selector_option_dev_mode', { number: val })}
+                      </option>
+                    ))}
+                  </rb.Form.Select>
+                </rb.Form.Group>
+              )}
+              <rb.Form.Group controlId="amountSats">
+                <rb.Form.Label>{t('receive.label_amount')}</rb.Form.Label>
+                <rb.Form.Control
+                  className="slashed-zeroes"
+                  name="amount"
+                  type="number"
+                  placeholder="0"
+                  value={amount}
+                  min={0}
+                  onChange={(e) => setAmount(e.target.value)}
+                  disabled={isLoading}
+                />
+                <rb.Form.Control.Feedback type="invalid">
+                  {t('receive.feedback_invalid_amount')}
+                </rb.Form.Control.Feedback>
               </rb.Form.Group>
-            )}
-            <rb.Form.Group className="my-4" controlId="amountSats">
-              <rb.Form.Label>{t('receive.label_amount')}</rb.Form.Label>
-              <rb.Form.Control
-                className="slashed-zeroes"
-                name="amount"
-                type="number"
-                placeholder="0"
-                value={amount}
-                min={0}
-                onChange={(e) => setAmount(e.target.value)}
-                disabled={isLoading}
-              />
-              <rb.Form.Control.Feedback type="invalid">{t('receive.feedback_invalid_amount')}</rb.Form.Control.Feedback>
-            </rb.Form.Group>
-          </>
-        )}
-        <hr />
+            </div>
+          )}
+          <hr />
+        </div>
         <rb.Button variant="outline-dark" type="submit" disabled={isLoading} className="mt-2" style={{ width: '100%' }}>
           {isLoading ? (
             <div className="d-flex justify-content-center align-items-center">
diff --git a/src/components/Receive.module.css b/src/components/Receive.module.css
index 8492515..0dc2ff7 100644
--- a/src/components/Receive.module.css
+++ b/src/components/Receive.module.css
@@ -1,7 +1,39 @@
+.receive hr {
+  background-color: rgba(222, 222, 222, 1);
+  opacity: 100%;
+}
+
+:root[data-theme='dark'] .receive hr {
+  background-color: var(--bs-gray-800);
+  opacity: 100%;
+}
+
+.receive button {
+  padding: 0.25rem 1rem;
+  font-weight: 500;
+  border-color: rgba(222, 222, 222, 1);
+}
+
 .settings-container {
   margin-top: 1.5rem;
 }
 
+.settings-container button.settings-btn {
+  background-color: transparent !important;
+  border: none;
+  padding-left: 0;
+}
+
+.receive form input {
+  height: 3.5rem;
+  width: 100%;
+}
+
+.receive select {
+  height: 3.5rem;
+  width: 100%;
+}
+
 .receive-placeholder-container {
   height: 1.5rem;
   text-align: center;
diff --git a/src/components/SegmentedTabs.module.css b/src/components/SegmentedTabs.module.css
new file mode 100644
index 0000000..55a7522
--- /dev/null
+++ b/src/components/SegmentedTabs.module.css
@@ -0,0 +1,56 @@
+.segmented-tabs {
+  background-color: var(--bs-gray-300);
+  border-radius: 0.25rem;
+  padding: 0.25rem;
+  width: 100%;
+}
+
+:root[data-theme='dark'] .segmented-tabs {
+  color: var(--bs-gray-100);
+  background-color: var(--bs-gray-800);
+}
+
+.segmented-tab {
+  flex: 1 1 0;
+}
+
+.segmented-tab input[type='radio'] {
+  display: none;
+}
+
+.segmented-tab label {
+  background-color: white;
+  padding: 0.25rem 1rem;
+  border-radius: 0.1rem;
+  font-weight: 500;
+  width: 100%;
+  text-align: center;
+}
+
+.segmented-tab input[type='radio']:disabled ~ label {
+  background-color: transparent;
+  color: var(--bs-gray-600);
+  opacity: 0.5;
+}
+
+:root[data-theme='dark'] .segmented-tab input[type='radio']:disabled ~ label {
+  color: var(--bs-gray-600);
+  opacity: 0.5;
+}
+
+.segmented-tab input[type='radio']:not(:checked):not(:disabled) ~ label {
+  background-color: transparent;
+}
+
+.segmented-tab input[type='radio']:not(:disabled) ~ label {
+  cursor: pointer;
+}
+
+.segmented-tab input[type='radio']:checked:not(:disabled) ~ label {
+  background-color: var(--bs-gray-100);
+  box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.1);
+}
+
+:root[data-theme='dark'] .segmented-tab input[type='radio']:checked:not(:disabled) ~ label {
+  background-color: var(--bs-gray-600);
+}
diff --git a/src/components/SegmentedTabs.tsx b/src/components/SegmentedTabs.tsx
new file mode 100644
index 0000000..3d4c75c
--- /dev/null
+++ b/src/components/SegmentedTabs.tsx
@@ -0,0 +1,68 @@
+import React from 'react'
+import * as rb from 'react-bootstrap'
+import styles from './SegmentedTabs.module.css'
+
+interface SegmentedTab {
+  label: string
+  value: string
+  disabled?: boolean
+}
+
+function SegmentedTabFormCheck({ id, name, value, label, disabled, defaultChecked, onChange }: rb.FormCheckProps) {
+  const _onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    e.stopPropagation()
+    onChange && onChange(e)
+  }
+
+  return (
+    <>
+      <rb.Form.Check
+        bsPrefix={styles['segmented-tab']}
+        type="radio"
+        id={id}
+        value={value}
+        name={name}
+        label={label}
+        disabled={disabled}
+        defaultChecked={defaultChecked}
+        onChange={_onChange}
+      />
+    </>
+  )
+}
+
+interface SegmentedTabsProps {
+  name: string
+  tabs: SegmentedTab[]
+  onChange: (tab: SegmentedTab, checked: boolean) => void
+  initialValue?: string
+  disabled?: boolean
+}
+
+export default function SegmentedTabs({ name, tabs, onChange, initialValue, disabled = false }: SegmentedTabsProps) {
+  const _onChange = (e: React.ChangeEvent<HTMLInputElement>, tab: SegmentedTab) => {
+    e.stopPropagation()
+    onChange(tab, e.currentTarget.checked)
+  }
+
+  return (
+    <div className={styles['segmented-tabs']}>
+      <div className="d-flex gap-1">
+        {tabs.map((tab, index) => {
+          return (
+            <SegmentedTabFormCheck
+              key={index}
+              id={`${name}-${index}`}
+              name={name}
+              label={tab.label}
+              disabled={disabled || tab.disabled}
+              inline={true}
+              defaultChecked={initialValue === tab.value}
+              onChange={(e) => _onChange(e, tab)}
+            />
+          )
+        })}
+      </div>
+    </div>
+  )
+}
diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx
index e5277c5..3ffa166 100644
--- a/src/components/Settings.jsx
+++ b/src/components/Settings.jsx
@@ -1,4 +1,5 @@
 import React, { useEffect, useState } from 'react'
+import { Link } from 'react-router-dom'
 import * as rb from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
 import Sprite from './Sprite'
@@ -9,7 +10,9 @@ import { useSettings, useSettingsDispatch } from '../context/SettingsContext'
 import { useCurrentWallet } from '../context/WalletContext'
 import { SATS, BTC, walletDisplayName } from '../utils'
 import * as Api from '../libs/JmWalletApi'
+import { routes } from '../constants/routes'
 import languages from '../i18n/languages'
+import styles from './Settings.module.css'
 
 function SeedModal({ show = false, onHide }) {
   const { t } = useTranslation()
@@ -96,59 +99,49 @@ export default function Settings() {
   const isLightTheme = settings.theme === window.JM.THEMES[0]
 
   return (
-    <div>
+    <div className={styles.settings}>
       <PageTitle title={t('settings.title')} />
       <div className="d-flex flex-column gap-4">
-        <div style={{ marginLeft: '-.75rem' }} className="d-flex flex-column gap-2">
+        <div className={styles['settings-group-container']}>
           <rb.Button
             variant="outline-dark"
-            className="border-0 d-inline-flex align-items-center"
+            className={styles['settings-btn']}
             onClick={() => settingsDispatch({ showBalance: !settings.showBalance })}
           >
-            <Sprite symbol={settings.showBalance ? 'hide' : 'show'} width="24" height="24" className="me-2" />
+            <Sprite symbol={settings.showBalance ? 'hide' : 'show'} width="24" height="24" />
             {settings.showBalance ? t('settings.hide_balance') : t('settings.show_balance')}
           </rb.Button>
 
           <rb.Button
             variant="outline-dark"
-            className="border-0 d-inline-flex align-items-center"
+            className={styles['settings-btn']}
             onClick={() => settingsDispatch({ unit: isSats ? BTC : SATS })}
           >
-            <Sprite symbol={isSats ? BTC : SATS} width="24" height="24" className="me-2" />
+            <Sprite symbol={isSats ? BTC : SATS} width="24" height="24" />
             {isSats ? t('settings.use_btc') : t('settings.use_sats')}
           </rb.Button>
 
           <rb.Button
             variant="outline-dark"
-            className="border-0 d-inline-flex align-items-center"
+            className={styles['settings-btn']}
             onClick={(e) => setTheme(isLightTheme ? window.JM.THEMES[1] : window.JM.THEMES[0])}
           >
-            <Sprite
-              symbol={isLightTheme ? window.JM.THEMES[0] : window.JM.THEMES[1]}
-              width="24"
-              height="24"
-              className="me-2"
-            />
+            <Sprite symbol={isLightTheme ? window.JM.THEMES[0] : window.JM.THEMES[1]} width="24" height="24" />
             {isLightTheme ? t('settings.use_dark_theme') : t('settings.use_light_theme')}
           </rb.Button>
 
           <rb.Button
             variant="outline-dark"
-            className="border-0 d-inline-flex align-items-center"
+            className={styles['settings-btn']}
             onClick={(e) => settingsDispatch({ useAdvancedWalletMode: !settings.useAdvancedWalletMode })}
           >
-            <Sprite
-              symbol={settings.useAdvancedWalletMode ? 'wand' : 'console'}
-              width="24"
-              height="24"
-              className="me-2"
-            />
+            <Sprite symbol={settings.useAdvancedWalletMode ? 'wand' : 'console'} width="24" height="24" />
             {settings.useAdvancedWalletMode ? t('settings.use_normal_mode') : t('settings.use_dev_mode')}
           </rb.Button>
 
           <rb.Dropdown>
-            <rb.Dropdown.Toggle variant="outline-dark" className="border-0 d-inline-flex align-items-center">
-              <Sprite symbol="globe" width="24" height="24" className="me-2" />
+            <rb.Dropdown.Toggle variant="outline-dark" className={styles['settings-btn']}>
+              <Sprite symbol="globe" width="24" height="24" />
               {languages.find((lng) => lng.key === (i18n.resolvedLanguage || i18n.language))?.description ||
                 languages[0].description}
             </rb.Dropdown.Toggle>
@@ -163,25 +156,31 @@ export default function Settings() {
               })}
               <rb.Dropdown.Item
                 href="https://github.com/joinmarket-webui/joinmarket-webui/tree/master/src/i18n/README.md"
+                target="_blank"
                 rel="noopener noreferrer"
               >
                 {t('settings.text_help_translate')}
               </rb.Dropdown.Item>
             </rb.Dropdown.Menu>
           </rb.Dropdown>
+        </div>
 
-          <rb.Button
-            variant="outline-dark"
-            className="border-0 mb-2 d-inline-flex align-items-center"
-            onClick={(e) => setShowingSeed(true)}
-          >
-            <Sprite symbol="mnemonic" width="24" height="24" className="me-2" />
+        <h2 className="mb-0">{t('settings.section_title_wallet')}</h2>
+        <div className={styles['settings-group-container']}>
+          <rb.Button variant="outline-dark" className={styles['settings-btn']} onClick={(e) => setShowingSeed(true)}>
+            <Sprite symbol="mnemonic" width="24" height="24" />
             {showingSeed ? t('settings.hide_seed') : t('settings.show_seed')}
           </rb.Button>
           {showingSeed && <SeedModal show={showingSeed} onHide={() => setShowingSeed(false)} />}
+
+          <Link to={routes.walletList} className={`btn btn-outline-dark ${styles['settings-btn']}`}>
+            <Sprite symbol="wallet" width="24" height="24" />
+            {t('settings.button_switch_wallet')}
+          </Link>
         </div>
 
-        <div className="d-flex flex-column gap-3">
+        <h2 className="mb-0">{t('settings.section_title_community')}</h2>
+        <div className={styles['settings-links']}>
           <a
             href="https://github.com/joinmarket-webui/joinmarket-webui"
             target="_blank"
diff --git a/src/components/Settings.module.css b/src/components/Settings.module.css
new file mode 100644
index 0000000..da1d349
--- /dev/null
+++ b/src/components/Settings.module.css
@@ -0,0 +1,29 @@
+.settings .settings-btn {
+  width: 100%;
+  display: inline-flex;
+  align-items: center;
+  gap: 0.5rem;
+
+  border: none;
+  text-align: var(--bs-body-text-align);
+}
+
+.settings .settings-btn > svg:first-child {
+  min-width: 24px;
+  min-height: 24px;
+}
+
+.settings-group-container {
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+  margin-bottom: 0.25rem;
+}
+
+.settings-links {
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+  margin-bottom: 0.25rem;
+  padding: 0.375rem 0.75rem;
+}
diff --git a/src/components/ToggleSwitch.jsx b/src/components/ToggleSwitch.tsx
similarity index 71%
rename from src/components/ToggleSwitch.jsx
rename to src/components/ToggleSwitch.tsx
index 1244c7b..78a862a 100644
--- a/src/components/ToggleSwitch.jsx
+++ b/src/components/ToggleSwitch.tsx
@@ -1,15 +1,24 @@
 import React from 'react'
 import styles from './ToggleSwitch.module.css'
 
+interface ToggleSwitchProps {
+  label: string
+  subtitle?: string
+  onToggle: (isToggled: boolean) => void
+  initialValue?: boolean
+  disabled?: boolean
+}
+
 export default function ToggleSwitch({
   label,
   subtitle = undefined,
   onToggle,
   initialValue = false,
   disabled = false,
-}) {
-  const onClick = (e) => {
-    onToggle(e.target.checked)
+}: ToggleSwitchProps) {
+  const onClick = (e: React.MouseEvent<HTMLInputElement>) => {
+    e.stopPropagation()
+    onToggle(e.currentTarget.checked)
   }
 
   return (
diff --git a/src/components/Wallets.jsx b/src/components/Wallets.jsx
index 37bae88..0a3b524 100644
--- a/src/components/Wallets.jsx
+++ b/src/components/Wallets.jsx
@@ -1,6 +1,7 @@
 import React, { useEffect, useState } from 'react'
 import { Link } from 'react-router-dom'
 import * as rb from 'react-bootstrap'
+import Sprite from './Sprite'
 import Alert from './Alert'
 import Wallet from './Wallet'
 import PageTitle from './PageTitle'
@@ -115,8 +116,12 @@ export default function Wallets({ startWallet, stopWallet }) {
         <Link
           to={routes.createWallet}
           className={`btn mt-4 ${walletList?.length === 0 ? 'btn-lg btn-dark' : 'btn-outline-dark'}`}
+          data-testid="new-wallet-btn"
         >
-          {t('wallets.button_new_wallet')}
+          <div className="d-flex justify-content-center align-items-center">
+            <Sprite symbol="plus" width="24" height="24" className="me-2" />
+            <span>{t('wallets.button_new_wallet')}</span>
+          </div>
         </Link>
       </div>
     </div>
diff --git a/src/constants/routes.ts b/src/constants/routes.ts
index af27705..73b034f 100644
--- a/src/constants/routes.ts
+++ b/src/constants/routes.ts
@@ -1,5 +1,6 @@
 export const routes = {
   home: '/',
+  walletList: '/',
   jam: '/jam',
   send: '/send',
   receive: '/receive',
diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json
index 61747f4..6f3ebc1 100644
--- a/src/i18n/locales/en/translation.json
+++ b/src/i18n/locales/en/translation.json
@@ -109,7 +109,6 @@
     "text_loading": "Loading",
     "button_deposit": "Receive",
     "button_withdraw": "Send",
-    "button_switch_wallet": "Switch wallet",
     "error_loading_failed": "Loading wallet failed."
   },
   "current_wallet_advanced": {
@@ -132,6 +131,8 @@
   },
   "settings": {
     "title": "Settings",
+    "section_title_wallet": "Wallet",
+    "section_title_community": "Community",
     "show_balance": "Show Balance",
     "hide_balance": "Hide Balance",
     "use_sats": "Display amounts in sats",
@@ -144,6 +145,7 @@
     "show_seed": "Show seed phrase",
     "hide_seed": "Hide seed phrase",
     "reveal_seed": "Reveal seed phrase",
+    "button_switch_wallet": "Switch wallet",
     "error_loading_seed_failed": "Could not retrieve seed phrase.",
     "seed_modal_info_text": "Please write down your seed phrase and password! Without this information you will not be able to access and recover your wallet!",
     "github": "GitHub",
@@ -215,7 +217,9 @@
     "alert_stopping": "Service is stopping.",
     "alert_running": "Earning sats...",
     "alert_coinjoin_in_progress": "A collaborative transaction is currently in progress.",
-    "toggle_rel_offer": "Relative offer",
+    "button_settings": "Advanced",
+    "radio_abs_offer_label": "Absolute offer",
+    "radio_rel_offer_label": "Relative offer",
     "label_rel_fee": "Relative fee {{ fee }}",
     "description_rel_fee": "The fee to charge others, as a percentage of the transaction amount.",
     "feedback_invalid_rel_fee": "Please provide a relative fee between {{feeRelPercentageMin}} and {{feeRelPercentageMax}}.",
diff --git a/src/i18n/locales/fr/translation.json b/src/i18n/locales/fr/translation.json
index a770910..1b012da 100644
--- a/src/i18n/locales/fr/translation.json
+++ b/src/i18n/locales/fr/translation.json
@@ -111,7 +111,6 @@
     "text_loading": "Chargement",
     "button_deposit": "Déposer",
     "button_withdraw": "Retirer",
-    "button_switch_wallet": "Changer de porte-monnaie",
     "error_loading_failed": "Le chargement du portefeuille a échoué."
   },
   "current_wallet_advanced": {
@@ -146,6 +145,7 @@
     "show_seed": "Afficher la phrase graine",
     "hide_seed": "Cacher la phrase graine",
     "reveal_seed": "Révéler la phrase graine",
+    "button_switch_wallet": "Changer de porte-monnaie",
     "error_loading_seed_failed": "Impossible de récupérer la phrase graine.",
     "seed_modal_info_text": "Veuillez noter votre phrase graine et votre mot de passe ! Sans ces informations, vous ne serez pas en mesure d'accéder à votre portefeuille !"
   },
diff --git a/src/index.css b/src/index.css
index d062422..94aeba1 100644
--- a/src/index.css
+++ b/src/index.css
@@ -377,52 +377,6 @@ footer .cheatsheet-link.nav-link {
   text-shadow: 0 0 15px var(--bs-gray-400);
 }
 
-/* Send, Receive, Earn Styles */
-
-.earn form input:not([type='checkbox']) {
-  height: 3.5rem;
-  width: 100%;
-}
-
-.earn form button {
-  height: 3rem;
-  width: 100%;
-}
-
-.receive form input {
-  height: 3.5rem;
-  width: 100%;
-}
-
-.receive select {
-  height: 3.5rem;
-  width: 100%;
-}
-
-.receive .btn.btn-outline-dark {
-  padding: 0.25rem 1rem;
-  font-weight: 500;
-  border-color: rgba(222, 222, 222, 1);
-}
-
-.receive .btn.btn-light {
-  background-color: transparent;
-}
-
-.receive .btn.btn-dark {
-  background-color: transparent !important;
-}
-
-.receive hr {
-  background-color: rgba(222, 222, 222, 1);
-  opacity: 100%;
-}
-
-:root[data-theme='dark'] hr {
-  background-color: var(--bs-gray-800);
-  opacity: 100%;
-}
-
 /* Boostrap overrides */
 
 h2 {


Test patch:
diff --git a/docker/regtest/readme.md b/docker/regtest/readme.md
index 820767a..f17be80 100644
--- a/docker/regtest/readme.md
+++ b/docker/regtest/readme.md
@@ -12,12 +12,15 @@ npm run regtest:rebuild
 # start the regtest environment
 npm run regtest:up
 
-# fund wallets and start maker
-./docker/regtest/init-setup.sh
+# fund wallets and start maker in secondary container
+npm run regtest:init
 
 # mine blocks in regtest periodically
 npm run regtest:mine
 
+# start jam in development mode
+npm start
+
 [...]
 
 # stop the regtest environment
@@ -29,12 +32,15 @@ npm run regtest:clear
 
 ## Commands
 
-### Run
+### Start
 
 Start the regtest environment with:
 
 ```sh
 npm run regtest:up
+
+# (optional) fund wallets and start maker in secondary container
+npm run regtest:init
 ```
 
 Once the regtest environment is up and running you can start Jam with:
@@ -55,6 +61,14 @@ If you want to start from scratch (removing all volumes):
 npm run regtest:clear
 ```
 
+### Mine
+Mine regtest blocks in a fixed interval (current default is every 10 seconds).
+This is useful for features that await confirmations or need incoming blocks regularly.
+e.g. This is necessary for scheduled transactions to execute successfully.
+```sh
+npm run regtest:mine
+```
+
 ## Images
 
 The [Docker setup](dockerfile-deps/joinmarket/latest/Dockerfile) is an adaption of [joinmarket-webui-standalone](https://github.com/joinmarket-webui/joinmarket-webui-docker/tree/master/standalone) with as little adaptations as possible.
@@ -95,18 +109,12 @@ docker exec -t jm_regtest_joinmarket git log --oneline -1
 
 Some helper scripts are included to make recurring tasks and interaction with the containers easier.
 
-### `npm run regtest:mine`
-
-Mine regtest blocks in a fixed interval (current default is every 10 seconds).
-This is useful for features that await confirmations or need incoming blocks regularly.
-e.g. This is necessary for scheduled transactions to execute successfully.
-
-
 ### `init-setup.sh`
 
 This script helps in providing both JoinMarket containers a wallet with spendable coins and starting the Maker Service in the secondary container.
 Its main goal is to make CoinJoin transactions possible in the regtest environment.
 It should be run immediately after the Docker setup is successfully started so you can start developing right away.
+A wallet named `funded.jmdat` with password `test` will be created if it does not exist.
 
 ```sh
 # fund wallets and start maker service in secondary container
@@ -214,5 +222,3 @@ joinmarket_1  | 2009-01-03 00:02:44,907 INFO success: ob-watcher entered RUNNING
 
 - [JoinMarket Server (GitHub)](https://github.com/JoinMarket-Org/joinmarket-clientserver)
 - [JoinMarket Server Testing Docs (GitHub)](https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/docs/TESTING.md)
-- [BTCPay Server JoinMarket Docker Setup (GitHub)](https://github.com/btcpayserver/dockerfile-deps/tree/master/JoinMarket)
-- [BTCPay Server JoinMarket Image (DockerHub)](https://hub.docker.com/r/btcpayserver/joinmarket)
diff --git a/src/components/Settings.test.jsx b/src/components/Settings.test.jsx
index 65a1455..9602b41 100644
--- a/src/components/Settings.test.jsx
+++ b/src/components/Settings.test.jsx
@@ -12,14 +12,20 @@ describe('<Settings />', () => {
   it('should render settings without errors', () => {
     act(setup)
 
-    expect(screen.getByText('settings.title')).toBeInTheDocument()
-    expect(screen.queryByText(/settings.(show|hide)_balance/)).toBeInTheDocument()
-    expect(screen.queryByText(/settings.(show|hide)_seed/)).toBeInTheDocument()
-    expect(screen.queryByText(/settings.use_(sats|bitcoin)/)).toBeInTheDocument()
-    expect(screen.queryByText(/settings.use_(dark|light)_theme/)).toBeInTheDocument()
-    expect(screen.queryByText(/settings.use_(normal|dev)_mode/)).toBeInTheDocument()
-    expect(screen.queryByText(/English/)).toBeInTheDocument()
-    expect(screen.queryByText(/settings.github/)).toBeInTheDocument()
-    expect(screen.queryByText(/settings.telegram/)).toBeInTheDocument()
+    expect(screen.getByText('settings.title')).toBeVisible()
+    expect(screen.queryByText(/settings.(show|hide)_balance/)).toBeVisible()
+    expect(screen.queryByText(/settings.use_(sats|bitcoin)/)).toBeVisible()
+    expect(screen.queryByText(/settings.use_(dark|light)_theme/)).toBeVisible()
+    expect(screen.queryByText(/settings.use_(normal|dev)_mode/)).toBeVisible()
+    expect(screen.queryByText(/English/)).toBeVisible()
+
+    expect(screen.getByText('settings.section_title_wallet')).toBeVisible()
+    expect(screen.queryByText(/settings.(show|hide)_seed/)).toBeVisible()
+    expect(screen.queryByText(/settings.button_switch_wallet/)).toBeVisible()
+
+    expect(screen.getByText('settings.section_title_community')).toBeVisible()
+    expect(screen.queryByText(/settings.github/)).toBeVisible()
+    expect(screen.queryByText(/settings.telegram/)).toBeVisible()
+    expect(screen.queryByText(/settings.jm_twitter/)).toBeVisible()
   })
 })
diff --git a/src/components/Wallets.test.jsx b/src/components/Wallets.test.jsx
index 8a97279..3648f98 100644
--- a/src/components/Wallets.test.jsx
+++ b/src/components/Wallets.test.jsx
@@ -73,7 +73,7 @@ describe('<Wallets />', () => {
 
     expect(screen.getByText('wallets.text_loading')).toBeInTheDocument()
 
-    const callToActionButtonBefore = screen.getByText('wallets.button_new_wallet')
+    const callToActionButtonBefore = screen.getByTestId('new-wallet-btn')
     expect(callToActionButtonBefore.classList.contains('btn')).toBe(true)
     expect(callToActionButtonBefore.classList.contains('btn-lg')).toBe(false)
 
@@ -81,7 +81,7 @@ describe('<Wallets />', () => {
 
     expect(screen.getByText('wallets.subtitle_no_wallets')).toBeInTheDocument()
 
-    const callToActionButtonAfter = screen.getByText('wallets.button_new_wallet')
+    const callToActionButtonAfter = screen.getByTestId('new-wallet-btn')
     expect(callToActionButtonAfter.classList.contains('btn-lg')).toBe(true)
   })
 
@@ -111,7 +111,7 @@ describe('<Wallets />', () => {
     expect(screen.getByText('wallet0')).toBeInTheDocument()
     expect(screen.getByText('wallet1')).toBeInTheDocument()
 
-    const callToActionButton = screen.getByText('wallets.button_new_wallet')
+    const callToActionButton = screen.getByTestId('new-wallet-btn')
     expect(callToActionButton.classList.contains('btn')).toBe(true)
     expect(callToActionButton.classList.contains('btn-lg')).toBe(false)
   })