Repository: joinmarket-webui/joinmarket-webui
Pull request #211 (2022-04-05 16:36:25)

feat: add cheatsheet
This PR adds a cheatsheet component.

It is currently only shown in `devMode` for now, as some items on the list (e.g. Fidelity Bonds), are not yet supported.

Differences to the Figma design:
- Adds a close button to the overlay - this is needed on small mobile devices (Should it be hidden on bigger screens?)
- Footer Link Icon does not match (currently using `File` as placeholder – found no real alternative on https://bitcoinicons.com/ yet)

## 📸
<img src="https://user-images.githubusercontent.com/3358649/162630840-4177792e-a89e-4f3f-80c7-8584689c64a4.png" width="400"><img src="https://user-images.githubusercontent.com/3358649/162630834-3cd40559-f48b-43b0-9189-38c01e6452df.png" width="400">

theborakompanioni: Footer `Nav.Item`'s still need some styling on mobile.
editwentyone: Figma: added a close button and changed icon to the normal bitcoin file icon. valid point for mobile use.
 
would love to see the cheatsheet jump up after a delay when the user uses JAM for the first time, directly after the creation of the first wallet (see rapid prototype for a better understanding). 

also its better to hide "cheatsheet" in the footer until the user created the first wallet (if easy and possible). just to not confuse too much at the beginning. 
theborakompanioni: Updated screenshots.
theborakompanioni: > would love to see the cheatsheet jump up once fully automatically after a delay when the user uses JAM for the first time, [...]

Done. Will be shown once after a wallet has been unlocked, just like the onboarding screen.

> 
> the underlined words are links, the first 3 links in the text are linking to openoms sources and the other links are linking to parts inside the jam app. i.e.: (1) **Fund** your wallet first. (click it inside the prototype)

:+1:

> also its better to hide "cheatsheet" in the footer until the user created the first wallet (if easy and possible). just to not confuse too much at the beginning.

It will be displayed only if a wallet is unlocked - not on the login screen (links would not work).
> 
> I gave point 4 fidelity bond another color to show its inactive for now, but never the less, there are some informations that enlightens the user. maybe its a good idea to also communicate the upcoming feature FB

:+1: Done. Opacity decreased for item 4.
dergigi: Nice! That looks great already!

Some word choices could be improved, or at least we should explain the concepts somewhere. Here is what came to mind:

* Why is "adding noise" good?
* What is "sweep"?
* "very private" - how about "more private"?
* Not sure if we should focus on yield that heavily, as others mentioned in the past the amounts that you will earn are probably rather low. Maybe do some expectation management?

Feel free to ignore the above, we can improve all this at a later point as well. It's a good first version! 👏 
theborakompanioni: > Nice! That looks great already!

All honor to @editwentyone.

> Some word choices could be improved, or at least we should explain the concepts somewhere. 

I am always grateful for concrete wording suggestions, because there are so many people who have the necessary talent for this - but I am afraid I am not one of them.

> Here is what came to mind:
> 
>     * Why is "adding noise" good?

Changed from "add more noise" to "increase privacy of all". "noise" is avoided entirely. It is not used anywhere and would - like you said - need an explanation. Acceptable approach?

>     * What is "sweep"?

Since we use the term "sweep" on the Send screen also, do you think it can be explained there and is this the appropriate context? Refrained from changing anything in the cheatsheet component regarding this for now.

>     * "very private" - how about "more private"?

:+1: "more private" it is.

>     * Not sure if we should focus on yield that heavily, as others mentioned in the past the amounts that you will earn are probably rather low. Maybe do some expectation management?

Yep, that is true. Maybe also better explained on the "Earn" screen, when the context is appropriate (?).
> 
> Feel free to ignore the above, we can improve all this at a later point as well. It's a good first version! clap

One question before it is merged - should item 3 and 4 be flipped?
dergigi: > Should item 3 and 4 be flipped?

I'd say yes.

Another thing that touches on something that we discussed often—something that isn't fully resolved yet—is the mentioning of "levels." 

If we manage to implement and ship #179 with the next release, manual consolidation (and thus anything that mentions "levels" at all) might fall to the wayside for most users, since this would be automated with scheduled transactions.

I'll propose some wording changes in the code directly.
dergigi: I just noticed that there are two sources of truth for the text due to the translation engine, so I will just type it out here before I propose code changes that might be misplaced:

---

**The Cheatsheet**

Follow the steps below to increase your financial privacy. It is advisable to switch from [earning as a maker](#) to [sending as a taker](#) back and forth. [Learn more.](#)

**1) Fund your wallet.**
Deposit some funds yourself or receive it from others.

**2) Send a collaborative transaction to yourself.**
Collaborative transactions increase everyone's privacy.

**3) Optional: Lock funds in a fidelity bond.**
A fidelity bond increases your chances of selling liquidity substantially.

**4) Provide liquidity and earn yield.**
Offer your sats to the marketplace. No trust or custody required—you are always in full control of your funds.

**5) Make use of scheduled transactions**
Automatically plan and execute a series of collaborative transactions. You can also automatically sweep your funds to cold storage, or use them to open a lightning channel, for example.

**6) Go to step one and repeat.**
Still confused? Dig into the documentation.

---

This assumes that we will ship #179 soon, which I think we will. I'm an optimist! 😁 

Let me know what you think.
theborakompanioni: > I just noticed that there are two sources of truth for the text due to the translation engine, so I will just type it out here before I propose code changes that might be misplaced.

Ah, yes, totally forgot that fact when applying the suggestions before.

> This assumes that we will ship #179 soon, which I think we will. I'm an optimist! grin

Yes. Worst case is, we have to apply css class `upcoming-feature` to that item. Not an issue :+1:

> Let me know what you think.

I'd merge it! Ping @editwentyone for a competent design perspective.

![image](https://user-images.githubusercontent.com/3358649/162977405-ba4196c8-9ffd-40b5-8c04-0aff52731546.png)
![image](https://user-images.githubusercontent.com/3358649/162977445-cb487886-cdc2-4955-84fb-7d9d1224df7e.png)

editwentyone: hey :) because on mobile its a lot of text, I would suggest to fix the Numbers at the top of the cell, I also fixed it in Figma. 

also I would suggest to link only the important words like Fund, Send, Earn, it reads faster 
theborakompanioni: > hey :) because on mobile its a lot of text, I would suggest to fix the Numbers at the top of the cell, I also fixed it in Figma.

:+1: Done.

> also I would suggest to link only the important words like Fund, Send, Earn, it reads faster

:+1: Done.
I changed to wording slightly in order for links come first (at least in English).
- "Provide liquidity and earn yield." => "Earn yield by providing liquidity."
- "Make use of scheduled transactions." => "Use scheduled transactions."

<img src="https://user-images.githubusercontent.com/3358649/163137898-0230f638-ef3c-41b8-9593-010752df1d6d.png" width=350 />
<img src="https://user-images.githubusercontent.com/3358649/163137930-53b2a205-e921-4d65-910f-b68c11a11a4d.png" width=400 />


Patch:
diff --git a/public/sprite.svg b/public/sprite.svg
index 11e74fc..6d06baf 100644
--- a/public/sprite.svg
+++ b/public/sprite.svg
@@ -23,6 +23,7 @@
 <symbol id="caret-down" viewBox="0 0 24 24" fill="currentColor"><path fill-rule="evenodd" d="M4.47 9.4a.75.75 0 011.06 0l6.364 6.364a.25.25 0 00.354 0L18.612 9.4a.75.75 0 011.06 1.06l-6.364 6.364a1.75 1.75 0 01-2.475 0L4.47 10.46a.75.75 0 010-1.06z" clip-rule="evenodd"></path></symbol>
 <symbol id="mnemonic" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path fill-rule="evenodd" d="M5 6.5A1.5 1.5 0 016.5 5h11A1.5 1.5 0 0119 6.5v11a1.5 1.5 0 01-1.5 1.5h-11A1.5 1.5 0 015 17.5v-11zm1.5 0H9V8H6.5V6.5zM9 9.667H6.5v1.5H9v-1.5zm-2.5 3.166H9v1.5H6.5v-1.5zM9 16H6.5v1.5H9V16zm1.75-9.5h2.5V8h-2.5V6.5zm2.5 3.167h-2.5v1.5h2.5v-1.5zm-2.5 3.166h2.5v1.5h-2.5v-1.5zM13.25 16h-2.5v1.5h2.5V16zM15 6.5h2.5V8H15V6.5zm2.5 3.167H15v1.5h2.5v-1.5zM15 12.833h2.5v1.5H15v-1.5zM17.5 16H15v1.5h2.5V16z" clip-rule="evenodd"></path></symbol>
 <symbol id="globe" viewBox="0 0 24 24" fill="currentColor"><path fill-rule="evenodd" d="M10.51 6.617c-.518 1.341-.855 3.245-.855 5.385 0 2.14.337 4.044.856 5.385.26.673.552 1.166.838 1.478.285.312.506.385.651.385.145 0 .366-.073.652-.385.285-.312.577-.805.837-1.478.52-1.341.856-3.245.856-5.385 0-2.14-.337-4.044-.856-5.385-.26-.673-.552-1.166-.838-1.478-.285-.312-.506-.385-.651-.385-.145 0-.366.073-.651.385-.286.312-.578.805-.838 1.478zm-.268-2.49c.455-.499 1.048-.873 1.758-.873s1.303.374 1.758.872c.455.497.83 1.175 1.13 1.95.601 1.553.957 3.649.957 5.926s-.356 4.373-.957 5.926c-.3.774-.675 1.453-1.13 1.95-.455.497-1.048.872-1.758.872s-1.303-.375-1.758-.872c-.455-.497-.83-1.175-1.13-1.95-.601-1.553-.957-3.649-.957-5.926s.356-4.373.957-5.926c.3-.775.675-1.453 1.13-1.95z" clip-rule="evenodd"></path><path fill-rule="evenodd" d="M12 4.746a7.25 7.25 0 100 14.5 7.25 7.25 0 000-14.5zm-8.75 7.25a8.75 8.75 0 1117.5 0 8.75 8.75 0 01-17.5 0z" clip-rule="evenodd"></path><path fill-rule="evenodd" d="M3.25 11.99a.75.75 0 01.75-.75l16 .012a.75.75 0 110 1.5l-16-.011a.75.75 0 01-.75-.75z" clip-rule="evenodd"></path></symbol>
+<symbol id="file" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path d="M6.5 6A1.5 1.5 0 018 4.5h5.5l4 4V18a1.5 1.5 0 01-1.5 1.5H8A1.5 1.5 0 016.5 18V6z"></path><path stroke-linejoin="round" d="M13 4.5V9h4.5"></path></symbol>
 <symbol id="sweep" viewBox="0 0 24 24" fill="none"><path d="M5.5 19.5L18.5 19.5L18.5 12.5C18.5 11.9696 18.2893 11.4609 17.9142 11.0858C17.5391 10.7107 17.0304 10.5 16.5 10.5L13.5 10.5L13.5 7C13.5 6.60217 13.342 6.22064 13.0607 5.93934C12.7794 5.65804 12.3978 5.5 12 5.5C11.6022 5.5 11.2206 5.65804 10.9393 5.93934C10.658 6.22064 10.5 6.60217 10.5 7L10.5 10.5L7.5 10.5C6.96957 10.5 6.46086 10.7107 6.08579 11.0858C5.71071 11.4609 5.5 11.9696 5.5 12.5L5.5 19.5Z" stroke="currentColor"/><path d="M16 19L16 18M18 13.5L6 13.5L18 13.5ZM8 19L8 17L8 19ZM10.5 19L10.5 15.5L10.5 19Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></symbol>
 <symbol id="node" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path stroke-linecap="round" d="M13.5 7l3.5 3.5M7 13.5l3.5 3.5M10.5 7L7 10.5M17 13.5L13.5 17"></path><circle cx="12" cy="5.5" r="2"></circle><circle cx="12" cy="18.5" r="2"></circle><circle cx="5.5" cy="12" r="2"></circle><circle cx="18.5" cy="12" r="2"></circle><circle cx="12" cy="5.5" r=".5"></circle><circle cx="12" cy="18.5" r=".5"></circle><circle cx="5.5" cy="12" r=".5"></circle><circle cx="18.5" cy="12" r=".5"></circle></symbol>
 <symbol id="receive" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M5 15.747V18a1 1 0 001 1h12a1 1 0 001-1v-2.253M12.202 13.5V5M15.546 11.15l-3.344 3.266-3.344-3.266"></path></symbol>
diff --git a/src/components/App.jsx b/src/components/App.jsx
index cae8b34..a71d3f8 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -12,27 +12,31 @@ import CurrentWalletAdvanced from './CurrentWalletAdvanced'
 import Settings from './Settings'
 import Navbar from './Navbar'
 import Layout from './Layout'
-import { useSettings } from '../context/SettingsContext'
+import Sprite from './Sprite'
+import { useSettings, useSettingsDispatch } from '../context/SettingsContext'
 import { useWebsocketState } from '../context/WebsocketContext'
 import { useCurrentWallet, useSetCurrentWallet } from '../context/WalletContext'
 import { useSessionConnectionError } from '../context/ServiceInfoContext'
 import { setSession, clearSession } from '../session'
 import Onboarding from './Onboarding'
-import Sprite from './Sprite'
+import Cheatsheet from './Cheatsheet'
 import { routes } from '../constants/routes'
+import { isFeatureEnabled } from '../constants/featureFlags'
 
 export default function App() {
   const { t } = useTranslation()
+  const settings = useSettings()
+  const settingsDispatch = useSettingsDispatch()
+  const websocketState = useWebsocketState()
   const currentWallet = useCurrentWallet()
   const setCurrentWallet = useSetCurrentWallet()
   const sessionConnectionError = useSessionConnectionError()
 
   const [websocketConnected, setWebsocketConnected] = useState()
   const [showAlphaWarning, setShowAlphaWarning] = useState(false)
-  const settings = useSettings()
-  const websocketState = useWebsocketState()
+  const [showCheatsheet, setShowCheatsheet] = useState(false)
 
-  const devMode = process.env.NODE_ENV === 'development'
+  const cheatsheetEnabled = currentWallet && isFeatureEnabled('cheatsheet')
 
   const startWallet = useCallback(
     (name, token) => {
@@ -52,6 +56,19 @@ export default function App() {
     setWebsocketConnected(websocketState === WebSocket.OPEN)
   }, [websocketState])
 
+  useEffect(() => {
+    let timer
+    // show the cheatsheet once after the first wallet has been created
+    if (cheatsheetEnabled && settings.showCheatsheet) {
+      timer = setTimeout(() => {
+        setShowCheatsheet(true)
+        settingsDispatch({ showCheatsheet: false })
+      }, 1_000)
+    }
+
+    return () => clearTimeout(timer)
+  }, [cheatsheetEnabled, settings, settingsDispatch])
+
   if (settings.showOnboarding === true) {
     return (
       <rb.Container className="onboarding my-5">
@@ -95,7 +112,7 @@ export default function App() {
            * that it stays visible in case the backend becomes unavailable.
            */}
           <Route element={<Layout />}>
-            <Route path={routes.createWallet} element={<CreateWallet startWallet={startWallet} devMode={devMode} />} />
+            <Route path={routes.createWallet} element={<CreateWallet startWallet={startWallet} />} />
           </Route>
           {/**
            * This section defines all routes that are displayed only if the backend is reachable.
@@ -128,6 +145,7 @@ export default function App() {
           )}
         </Routes>
       </rb.Container>
+
       <rb.Nav as="footer" className="border-top py-2">
         <rb.Container fluid="xl" className="d-flex flex-column flex-md-row justify-content-center py-2 px-4">
           <div className="d-flex flex-1 order-2 order-md-0 flex-column justify-content-center align-items-center align-items-md-start">
@@ -144,47 +162,66 @@ export default function App() {
               </Trans>
             </div>
           </div>
-          <div className="d-flex order-1 flex-1 flex-grow-0 justify-content-center align-items-center px-4">
-            <rb.Nav.Item>
-              <a
-                href="https://github.com/joinmarket-webui/joinmarket-webui/wiki"
-                target="_blank"
-                rel="noopener noreferrer"
-                className="nav-link text-secondary px-2"
-              >
-                {t('footer.docs')}
-              </a>
-            </rb.Nav.Item>
-            <rb.Nav.Item>
-              <a
-                href="https://github.com/joinmarket-webui/joinmarket-webui#-features"
-                target="_blank"
-                rel="noopener noreferrer"
-                className="nav-link text-secondary px-2"
-              >
-                {t('footer.features')}
-              </a>
-            </rb.Nav.Item>
-            <rb.Nav.Item>
-              <a
-                href="https://github.com/joinmarket-webui/joinmarket-webui"
-                target="_blank"
-                rel="noopener noreferrer"
-                className="nav-link text-secondary px-2"
-              >
-                {t('footer.github')}
-              </a>
-            </rb.Nav.Item>
-            <rb.Nav.Item>
-              <a
-                href="https://twitter.com/joinmarket"
-                target="_blank"
-                rel="noopener noreferrer"
-                className="nav-link text-secondary px-2"
-              >
-                {t('footer.twitter')}
-              </a>
-            </rb.Nav.Item>
+          <div className="d-flex order-1 flex-1 flex-grow-0 flex-column flex-sm-row justify-content-center align-items-center pt-2 pt-sm-0 px-4">
+            {cheatsheetEnabled && (
+              <div className="order-1 order-sm-0">
+                <Cheatsheet show={showCheatsheet} onHide={() => setShowCheatsheet(false)} />
+                <rb.Nav.Item>
+                  <rb.Button
+                    variant="link"
+                    className="cheatsheet-link nav-link text-start border-0 px-2"
+                    onClick={() => setShowCheatsheet(true)}
+                  >
+                    <div className="d-flex justify-content-center align-items-center">
+                      <Sprite symbol="file" width="24" height="24" />
+                      <div className="ps-0">{t('footer.cheatsheet')}</div>
+                    </div>
+                  </rb.Button>
+                </rb.Nav.Item>
+              </div>
+            )}
+            <div className="d-flex flex-row">
+              <rb.Nav.Item>
+                <a
+                  href="https://github.com/joinmarket-webui/joinmarket-webui/wiki"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  className="nav-link text-secondary px-2"
+                >
+                  {t('footer.docs')}
+                </a>
+              </rb.Nav.Item>
+              <rb.Nav.Item>
+                <a
+                  href="https://github.com/joinmarket-webui/joinmarket-webui#-features"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  className="nav-link text-secondary px-2"
+                >
+                  {t('footer.features')}
+                </a>
+              </rb.Nav.Item>
+              <rb.Nav.Item>
+                <a
+                  href="https://github.com/joinmarket-webui/joinmarket-webui"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  className="nav-link text-secondary px-2"
+                >
+                  {t('footer.github')}
+                </a>
+              </rb.Nav.Item>
+              <rb.Nav.Item>
+                <a
+                  href="https://twitter.com/joinmarket"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  className="nav-link text-secondary px-2"
+                >
+                  {t('footer.twitter')}
+                </a>
+              </rb.Nav.Item>
+            </div>
           </div>
           <div className="d-flex order-0 order-md-2 flex-1 justify-content-center justify-content-md-end align-items-center">
             <span
diff --git a/src/components/Cheatsheet.tsx b/src/components/Cheatsheet.tsx
new file mode 100644
index 0000000..2168d5a
--- /dev/null
+++ b/src/components/Cheatsheet.tsx
@@ -0,0 +1,135 @@
+import React, { PropsWithChildren } from 'react'
+import * as rb from 'react-bootstrap'
+import { Link } from 'react-router-dom'
+import { Trans, useTranslation } from 'react-i18next'
+import { routes } from '../constants/routes'
+
+interface CheatsheetProps {
+  show: boolean
+  onHide: () => void
+}
+
+type NumberedProps = {
+  number: number
+  className?: string
+}
+
+function Numbered({ number }: { number: number }) {
+  return <div className="numbered">{number}</div>
+}
+
+function ListItem({ number, children, ...props }: PropsWithChildren<NumberedProps>) {
+  return (
+    <rb.Stack className={`cheatsheet-list-item ${props.className || ''}`} direction="horizontal" gap={3}>
+      <Numbered number={number} />
+      <rb.Stack gap={0}>{children}</rb.Stack>
+    </rb.Stack>
+  )
+}
+
+export default function Cheatsheet({ show = false, onHide }: CheatsheetProps) {
+  const { t } = useTranslation()
+
+  return (
+    <rb.Offcanvas className="cheatsheet" show={show} onHide={onHide} placement="bottom">
+      <rb.Offcanvas.Header closeButton>
+        <rb.Stack>
+          <rb.Offcanvas.Title>{t('cheatsheet.title')}</rb.Offcanvas.Title>
+          <div className="small text-secondary">
+            <Trans i18nKey="cheatsheet.description">
+              Follow the steps below to increase your financial privacy. It is advisable to switch from{' '}
+              <a
+                href="https://github.com/openoms/bitcoin-tutorials/blob/master/joinmarket/joinmarket_private_flow.md#the-maker-role"
+                target="_blank"
+                rel="noopener noreferrer"
+              >
+                earning as a maker
+              </a>{' '}
+              to{' '}
+              <a
+                href="https://github.com/openoms/bitcoin-tutorials/blob/master/joinmarket/joinmarket_private_flow.md#the-taker-role"
+                target="_blank"
+                rel="noopener noreferrer"
+              >
+                sending as a taker
+              </a>{' '}
+              back and forth.{' '}
+              <a
+                href="https://github.com/openoms/bitcoin-tutorials/blob/master/joinmarket/joinmarket_private_flow.md#a-private-flow-through-joinmarket"
+                target="_blank"
+                rel="noopener noreferrer"
+              >
+                Learn more.
+              </a>
+            </Trans>
+          </div>
+        </rb.Stack>
+      </rb.Offcanvas.Header>
+      <rb.Offcanvas.Body>
+        <rb.Stack className="mb-4" gap={4}>
+          <ListItem number={1}>
+            <h6>
+              <Trans i18nKey="cheatsheet.item_1.title">
+                <Link to={routes.receive}>Fund</Link> your wallet.
+              </Trans>
+            </h6>
+            <div className="small text-secondary">{t('cheatsheet.item_1.description')}</div>
+          </ListItem>
+          <ListItem number={2}>
+            <h6>
+              <Trans i18nKey="cheatsheet.item_2.title">
+                <Link to={routes.send}>Send</Link> a collaborative transaction to yourself.
+              </Trans>
+            </h6>
+            <div className="small text-secondary">{t('cheatsheet.item_2.description')}</div>
+          </ListItem>
+          <ListItem number={3} className="upcoming-feature">
+            <h6>
+              <Trans i18nKey="cheatsheet.item_3.title">
+                Optional: <Link to={routes.earn}>Lock</Link> funds in a fidelity bond.
+              </Trans>
+            </h6>
+            <div className="small text-secondary">
+              {t('cheatsheet.item_3.description')}
+              <br />
+              {/* the following phrase is intentionally not translated because it will be removed soon */}
+              <strong>Feature not implemented yet. Coming soon!</strong>
+            </div>
+          </ListItem>
+          <ListItem number={4}>
+            <h6>
+              <Trans i18nKey="cheatsheet.item_4.title">
+                <Link to={routes.earn}>Earn</Link> yield by providing liquidity.
+              </Trans>
+            </h6>
+            <div className="small text-secondary">{t('cheatsheet.item_4.description')}</div>
+          </ListItem>
+          <ListItem number={5}>
+            <h6>
+              <Trans i18nKey="cheatsheet.item_5.title">
+                <Link to={routes.send}>Schedule</Link> transactions.
+              </Trans>
+            </h6>
+            <div className="small text-secondary">{t('cheatsheet.item_5.description')}</div>
+          </ListItem>
+          <ListItem number={6}>
+            <h6>{t('cheatsheet.item_6.title')}</h6>
+            <div className="small text-secondary">
+              <Trans i18nKey="cheatsheet.item_6.description">
+                Still confused?{' '}
+                <a
+                  href="https://github.com/openoms/bitcoin-tutorials/blob/master/joinmarket/joinmarket_private_flow.md#a-private-flow-through-joinmarket"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                >
+                  Dig into the documentation
+                </a>
+                .
+              </Trans>
+            </div>
+          </ListItem>
+        </rb.Stack>
+      </rb.Offcanvas.Body>
+    </rb.Offcanvas>
+  )
+}
diff --git a/src/components/CreateWallet.jsx b/src/components/CreateWallet.jsx
index d10219c..1df612c 100644
--- a/src/components/CreateWallet.jsx
+++ b/src/components/CreateWallet.jsx
@@ -11,6 +11,7 @@ import { useServiceInfo } from '../context/ServiceInfoContext'
 import * as Api from '../libs/JmWalletApi'
 import './CreateWallet.css'
 import { routes } from '../constants/routes'
+import { isFeatureEnabled } from '../constants/featureFlags'
 
 const PreventLeavingPageByMistake = () => {
   // prompt users before refreshing or closing the page when this component is present.
@@ -173,13 +174,13 @@ const SeedWordInput = ({ number, targetWord, isValid, setIsValid }) => {
   )
 }
 
-const BackupConfirmation = ({ createdWallet, walletConfirmed, parentStepSetter, devMode }) => {
+const BackupConfirmation = ({ createdWallet, walletConfirmed, parentStepSetter }) => {
   const seedphrase = createdWallet.seedphrase.split(' ')
 
   const { t } = useTranslation()
   const [seedBackup, setSeedBackup] = useState(false)
   const [seedWordConfirmations, setSeedWordConfirmations] = useState(new Array(seedphrase.length).fill(false))
-  const [showSkipButton] = useState(devMode)
+  const [showSkipButton] = useState(isFeatureEnabled('skipWalletBackupConfirmation'))
 
   useEffect(() => {
     setSeedBackup(seedWordConfirmations.every((wordConfirmed) => wordConfirmed))
@@ -250,7 +251,7 @@ const BackupConfirmation = ({ createdWallet, walletConfirmed, parentStepSetter,
   )
 }
 
-const WalletCreationConfirmation = ({ createdWallet, walletConfirmed, devMode }) => {
+const WalletCreationConfirmation = ({ createdWallet, walletConfirmed }) => {
   const { t } = useTranslation()
   const [userConfirmed, setUserConfirmed] = useState(false)
   const [revealSensitiveInfo, setRevealSensitiveInfo] = useState(false)
@@ -306,14 +307,13 @@ const WalletCreationConfirmation = ({ createdWallet, walletConfirmed, devMode })
           parentStepSetter={childStepSetter}
           createdWallet={createdWallet}
           walletConfirmed={walletConfirmed}
-          devMode={devMode}
         />
       )}
     </>
   )
 }
 
-export default function CreateWallet({ startWallet, devMode = false }) {
+export default function CreateWallet({ startWallet }) {
   const { t } = useTranslation()
   const serviceInfo = useServiceInfo()
   const navigate = useNavigate()
@@ -362,9 +362,7 @@ export default function CreateWallet({ startWallet, devMode = false }) {
       )}
       {alert && <rb.Alert variant={alert.variant}>{alert.message}</rb.Alert>}
       {canCreate && <WalletCreationForm createWallet={createWallet} />}
-      {isCreated && (
-        <WalletCreationConfirmation createdWallet={createdWallet} walletConfirmed={walletConfirmed} devMode={devMode} />
-      )}
+      {isCreated && <WalletCreationConfirmation createdWallet={createdWallet} walletConfirmed={walletConfirmed} />}
       {!canCreate && !isCreated && (
         <rb.Alert variant="warning">
           <Trans i18nKey="create_wallet.alert_other_wallet_unlocked">
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx
index 571cf4b..22927cb 100644
--- a/src/components/Navbar.jsx
+++ b/src/components/Navbar.jsx
@@ -166,7 +166,7 @@ export default function Navbar() {
                     <span>{t('navbar.menu')}</span>
                   </rb.Navbar.Toggle>
                 </div>
-                <rb.Navbar.Offcanvas className={`navbar-${settings.theme}`} placement="end">
+                <rb.Navbar.Offcanvas className={`navbar-offcanvas navbar-${settings.theme}`} placement="end">
                   <rb.Offcanvas.Header>
                     <rb.Offcanvas.Title>{t('navbar.title')}</rb.Offcanvas.Title>
                   </rb.Offcanvas.Header>
@@ -212,7 +212,7 @@ export default function Navbar() {
                     <span>{t('navbar.menu_mobile')}</span>
                   </rb.Navbar.Toggle>
                 </div>
-                <rb.Navbar.Offcanvas className={`navbar-${settings.theme}`} placement="end">
+                <rb.Navbar.Offcanvas className={`navbar-offcanvas navbar-${settings.theme}`} placement="end">
                   <rb.Offcanvas.Header>
                     <rb.Offcanvas.Title>{t('navbar.title')}</rb.Offcanvas.Title>
                   </rb.Offcanvas.Header>
diff --git a/src/constants/featureFlags.ts b/src/constants/featureFlags.ts
new file mode 100644
index 0000000..ab33e32
--- /dev/null
+++ b/src/constants/featureFlags.ts
@@ -0,0 +1,22 @@
+interface FeatureFlags {
+  skipWalletBackupConfirmation: boolean
+  cheatsheet: boolean
+}
+
+const devMode = process.env.NODE_ENV === 'development'
+
+const featureFlags: FeatureFlags = {
+  skipWalletBackupConfirmation: devMode,
+  cheatsheet: devMode,
+}
+
+type FeatureFlag = keyof FeatureFlags
+
+export const isFeatureEnabled = (name: FeatureFlag): boolean => {
+  return featureFlags[name] || false
+}
+
+// only to be used in tests
+export const __testSetFeatureEnabled = (name: FeatureFlag, enabled: boolean): boolean => {
+  return (featureFlags[name] = enabled)
+}
diff --git a/src/context/SettingsContext.jsx b/src/context/SettingsContext.jsx
index 57dcbcb..63f5150 100644
--- a/src/context/SettingsContext.jsx
+++ b/src/context/SettingsContext.jsx
@@ -7,6 +7,7 @@ const initialSettings = {
   showBalance: false,
   unit: BTC,
   showOnboarding: true,
+  showCheatsheet: true,
   useAdvancedWalletMode: false,
 }
 
diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json
index 29db351..1a72534 100644
--- a/src/i18n/locales/en/translation.json
+++ b/src/i18n/locales/en/translation.json
@@ -18,6 +18,7 @@
     "warning_alert_title": "Warning",
     "warning_alert_text": "While JoinMarket is tried and tested, Jam is not. It is in an alpha stage, so use with caution.",
     "warning_alert_button_ok": "Fine with me.",
+    "cheatsheet": "Cheatsheet",
     "docs": "Docs",
     "features": "Features",
     "github": "GitHub",
@@ -226,5 +227,33 @@
     "text_stopping": "Stopping",
     "button_show_report": "Show report",
     "button_hide_report": "Hide report"
+  },
+  "cheatsheet": {
+    "title": "The Cheatsheet",
+    "description": "Follow the steps below to increase your financial privacy. It is advisable to switch from <2>earning as a maker</2> to <6>sending as a taker</6> back and forth. <10>Learn more.</10>",
+    "item_1": {
+      "title": "<0>Fund</0> your wallet.",
+      "description": "Deposit some funds yourself or receive it from others."
+    },
+    "item_2": {
+      "title": "<0>Send</0> a collaborative transaction to yourself.",
+      "description": "Collaborative transactions increase everyone's privacy."
+    },
+    "item_3": {
+      "title": "Optional: <1>Lock</1> funds in a fidelity bond.",
+      "description": "A fidelity bond increases your chances of selling liquidity substantially."
+    },
+    "item_4": {
+      "title": "<0>Earn</0> yield by providing liquidity.",
+      "description": "Offer your sats to the marketplace. No trust or custody required—you are always in full control of your funds."
+    },
+    "item_5": {
+      "title": "<0>Schedule</0> transactions.",
+      "description": "Automatically plan and execute a series of collaborative transactions. You can also automatically sweep your funds to cold storage, or use them to open a lightning channel, for example."
+    },
+    "item_6": {
+      "title": "Go to step one and repeat.",
+      "description": "Still confused? Dig into the <2>documentation</2>."
+    }
   }
 }
diff --git a/src/index.css b/src/index.css
index 46bf07f..5d18057 100644
--- a/src/index.css
+++ b/src/index.css
@@ -207,7 +207,7 @@ body,
 }
 
 #mainNav,
-.offcanvas-header {
+.navbar-offcanvas.offcanvas-header {
   height: 76px;
 }
 
@@ -319,8 +319,8 @@ main {
     border-bottom: 2px solid black;
   }
 
-  .offcanvas,
-  .offcanvas-backdrop {
+  .navbar-offcanvas.offcanvas,
+  .navbar-offcanvas.offcanvas-backdrop {
     display: none;
   }
 }
@@ -357,6 +357,59 @@ main {
   }
 }
 
+/* Cheatsheet Styles */
+.cheatsheet {
+  height: auto;
+  /* page height - navbar height - some spacing*/
+  max-height: calc(100vh - 76px - 1rem);
+  border-top-left-radius: 1rem;
+  border-top-right-radius: 1rem;
+  margin: 0px auto;
+  width: 528px;
+}
+
+.cheatsheet .offcanvas-header .btn-close {
+  margin-bottom: auto;
+}
+
+.cheatsheet .cheatsheet-list-item {
+  align-items: start;
+}
+
+.cheatsheet .cheatsheet-list-item.upcoming-feature {
+  opacity: 0.25;
+}
+
+.cheatsheet .cheatsheet-list-item h6 {
+  margin-bottom: 0.1rem;
+}
+.cheatsheet a {
+  color: inherit;
+}
+
+.cheatsheet .numbered {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  min-width: 2rem;
+  height: 2rem;
+  border-radius: 50%;
+  background-color: rgb(0, 0, 0);
+  color: white;
+}
+
+:root[data-theme='dark'] .cheatsheet .numbered {
+  color: rgb(0, 0, 0);
+  background-color: white;
+}
+
+.cheatsheet-link.nav-link {
+  color: var(--bs-gray-900);
+}
+
+:root[data-theme='dark'] .cheatsheet-link.nav-link {
+  color: var(--bs-gray-400);
+}
 /* Blurred Text Styles */
 
 .blurred-text {
@@ -439,7 +492,8 @@ h2 {
   color: var(--bs-white);
 }
 
-.modal-header .btn-close {
+.modal-header .btn-close,
+:root[data-theme='dark'] .offcanvas-header .btn-close {
   background: transparent
     url('data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27 fill=%27%23fff%27%3e%3cpath d=%27M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z%27/%3e%3c/svg%3e')
     center/1em auto no-repeat;


Test patch:
diff --git a/src/components/CreateWallet.test.jsx b/src/components/CreateWallet.test.jsx
index fdc5358..38842b3 100644
--- a/src/components/CreateWallet.test.jsx
+++ b/src/components/CreateWallet.test.jsx
@@ -2,6 +2,7 @@ import React from 'react'
 import user from '@testing-library/user-event'
 import { render, screen, waitFor, waitForElementToBeRemoved } from '../testUtils'
 import { act } from 'react-dom/test-utils'
+import { __testSetFeatureEnabled } from '../constants/featureFlags'
 
 import * as apiMock from '../libs/JmWalletApi'
 
@@ -21,8 +22,7 @@ describe('<CreateWallet />', () => {
 
   const setup = (props) => {
     const startWallet = props?.startWallet || NOOP
-    const devMode = props?.devMode || false
-    render(<CreateWallet startWallet={startWallet} devMode={devMode} />)
+    render(<CreateWallet startWallet={startWallet} />)
   }
 
   beforeEach(() => {
@@ -114,7 +114,7 @@ describe('<CreateWallet />', () => {
     expect(screen.queryByText('create_wallet.button_create')).not.toBeInTheDocument()
   })
 
-  it('should verify that "skip" button is NOT visible when not in development mode', async () => {
+  it('should verify that "skip" button is NOT visible by default (feature is disabled)', async () => {
     apiMock.postWalletCreate.mockResolvedValueOnce({
       ok: true,
       json: () =>
@@ -125,7 +125,7 @@ describe('<CreateWallet />', () => {
         }),
     })
 
-    act(() => setup({ devMode: false }))
+    act(setup)
 
     act(() => {
       user.type(screen.getByPlaceholderText('create_wallet.placeholder_wallet_name'), testWalletName)
@@ -154,7 +154,9 @@ describe('<CreateWallet />', () => {
     expect(screen.getByText('create_wallet.confirmation_button_fund_wallet')).toBeDisabled()
   })
 
-  it('should verify that "skip" button IS visible in development mode', async () => {
+  it('should verify that "skip" button IS visible when feature is enabled', async () => {
+    __testSetFeatureEnabled('skipWalletBackupConfirmation', true)
+
     apiMock.postWalletCreate.mockResolvedValueOnce({
       ok: true,
       json: () =>
@@ -165,7 +167,7 @@ describe('<CreateWallet />', () => {
         }),
     })
 
-    act(() => setup({ devMode: true }))
+    act(setup)
 
     act(() => {
       user.type(screen.getByPlaceholderText('create_wallet.placeholder_wallet_name'), testWalletName)