Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

feat: onboarding #36

Merged
merged 26 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4a539f1
feat: add mnemonic screen and other improvements
im-adithya Feb 12, 2024
b67a6ca
chore: navigate away from unlock page if node is not started
rolznz Feb 14, 2024
d99a975
chore: minor copy changes
rolznz Feb 14, 2024
cf62444
chore: refactoring
im-adithya Feb 14, 2024
0a9a73d
fix: navbar highlight
rolznz Feb 14, 2024
87be59c
chore: only show already setup alert on first setup page, improve copy
rolznz Feb 14, 2024
3ba295b
fix: support pre-configured node flow
rolznz Feb 14, 2024
c754cff
fix: redirect from welcome screen if node is already setup
rolznz Feb 14, 2024
fa01684
fix: only show setup link if nwc is not running
rolznz Feb 14, 2024
dffb98c
chore: cleanup
im-adithya Feb 14, 2024
9e74363
feat: add finish screen for setup
im-adithya Feb 14, 2024
f47ce8e
Merge pull request #37 from getAlby/fix/preconfigured-node
im-adithya Feb 14, 2024
f5d1a9e
chore: remove unused import
im-adithya Feb 14, 2024
89d8899
chore: styling issues and popicons
im-adithya Feb 14, 2024
64d70f0
chore: finish screen feedback
rolznz Feb 14, 2024
c01da73
chore: replace new-connection image with new logo
im-adithya Feb 14, 2024
6764a46
chore: address review feedback
rolznz Feb 14, 2024
2b420b5
chore: fix mnemonic inputs
im-adithya Feb 14, 2024
04e0739
chore: change mnemonic button text to finish
im-adithya Feb 14, 2024
2f1d514
chore: use input component
im-adithya Feb 14, 2024
552407a
chore: remove bitcoin design icons
im-adithya Feb 14, 2024
3763b32
Merge remote-tracking branch 'origin/task-onboarding-2' into chore/fi…
rolznz Feb 14, 2024
1900cb3
fix: handle connection failure and restore node info
rolznz Feb 14, 2024
27a1d78
Merge pull request #39 from getAlby/chore/finish-screen-feedback
rolznz Feb 14, 2024
1e60d96
Merge pull request #38 from getAlby/task-finish-screen
rolznz Feb 14, 2024
1c4d167
fix: revert invite check in breez.go
rolznz Feb 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,32 +86,28 @@ _If you get a blank screen the first load, close the window and start the app ag

Breez SDK requires gcc to build the Breez bindings. Run `choco install mingw` and copy the breez SDK bindings file into the root of this directory (from your go installation directory) as per the [Breez SDK instructions](https://github.com/breez/breez-sdk-go?tab=readme-ov-file#windows). ALSO copy the bindings file into the output directory alongside the .exe in order to run it.

## Configuration parameters
## Optional configuration parameters

- `NOSTR_PRIVKEY`: the private key of this service. Should be a securely randomly generated 32 byte hex string.
- `CLIENT_NOSTR_PUBKEY`: if set, this service will only listen to events authored by this public key. You can set this to your own nostr public key.
- `RELAY`: default: "wss://relay.getalby.com/v1"
- `COOKIE_SECRET`: a randomly generated secret string. (only needed in http mode)
- `DATABASE_URI`: a sqlite filename. Default: .data/nwc.db
- `PORT`: the port on which the app should listen on (default: 8080)
- `LN_BACKEND_TYPE`: LND or BREEZ
- `WORK_DIR`: directory to store NWC data files. Default: .data
-

### LND Backend parameters

_For cert and macaroon, either hex or file options can be used._
Currently only LND can be configured via env. Other node types must be configured via the UI.

_To configure via env, the following parameters must be provided:_

- `LN_BACKEND_TYPE`: LND
- `LND_ADDRESS`: the LND gRPC address, eg. `localhost:10009` (used with the LND backend)
- `LND_CERT_FILE`: the location where LND's `tls.cert` file can be found (used with the LND backend)
- `LND_MACAROON_FILE`: the location where LND's `admin.macaroon` file can be found (used with the LND backend)

### BREEZ Backend parameters

- `BREEZ_MNEMONIC`: your bip39 mnemonic key phrase e.g. "define limit soccer guilt trim mechanic beyond outside best give south shine"
- `BREEZ_API_KEY`: contact breez for more info
- `GREENLIGHT_INVITE_CODE`: contact blockstream for more info

## Application deeplink options

### `/apps/new` deeplink options
Expand Down
10 changes: 6 additions & 4 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,11 @@ func (api *API) ListApps() ([]models.App, error) {

func (api *API) GetInfo() *models.InfoResponse {
info := models.InfoResponse{}
backend, _ := api.svc.cfg.Get("LNBackendType", "")
info.SetupCompleted = backend != ""
backendType, _ := api.svc.cfg.Get("LNBackendType", "")
unlockPasswordCheck, _ := api.svc.cfg.Get("UnlockPasswordCheck", "")
info.SetupCompleted = unlockPasswordCheck != ""
info.Running = api.svc.lnClient != nil
info.BackendType = backendType
return &info
}

Expand All @@ -231,8 +233,8 @@ func (api *API) Setup(setupRequest *models.SetupRequest) error {
if setupRequest.BreezAPIKey != "" {
api.svc.cfg.SetUpdate("BreezAPIKey", setupRequest.BreezAPIKey, setupRequest.UnlockPassword)
}
if setupRequest.BreezMnemonic != "" {
api.svc.cfg.SetUpdate("BreezMnemonic", setupRequest.BreezMnemonic, setupRequest.UnlockPassword)
if setupRequest.Mnemonic != "" {
api.svc.cfg.SetUpdate("Mnemonic", setupRequest.Mnemonic, setupRequest.UnlockPassword)
}
if setupRequest.GreenlightInviteCode != "" {
api.svc.cfg.SetUpdate("GreenlightInviteCode", setupRequest.GreenlightInviteCode, setupRequest.UnlockPassword)
Expand Down
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Alby - Nostr Wallet Connect</title>
<link rel="icon" href="/nwc-logo.svg" />
<link rel="icon" href="/nwc-brandmark.svg" />
</head>
<body>
<div id="root"></div>
Expand Down
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"preview": "vite preview"
},
"dependencies": {
"@popicons/react": "^0.0.9",
"@scure/bip39": "^1.2.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
Expand Down
1 change: 0 additions & 1 deletion frontend/public/nwc-logo.svg

This file was deleted.

6 changes: 6 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import NewApp from "src/screens/apps/NewApp";
import AppCreated from "src/screens/apps/AppCreated";
import NotFound from "src/screens/NotFound";
import { SetupNode } from "src/screens/setup/SetupNode";
import { SetupWallet } from "src/screens/setup/SetupWallet";
import { Welcome } from "src/screens/Welcome";
import { SetupPassword } from "src/screens/setup/SetupPassword";
import Start from "src/screens/Start";
Expand All @@ -19,6 +20,8 @@ import { StartRedirect } from "src/components/redirects/StartRedirect";
import { HomeRedirect } from "src/components/redirects/HomeRedirect";
import Unlock from "src/screens/Unlock";
import { SetupRedirect } from "src/components/redirects/SetupRedirect";
import { SetupMnemonic } from "src/screens/setup/SetupMnemonic";
import { SetupFinish } from "src/screens/setup/SetupFinish";

function App() {
return (
Expand All @@ -41,6 +44,9 @@ function App() {
<Route path="" element={<Navigate to="password" replace />} />
<Route path="password" element={<SetupPassword />} />
<Route path="node" element={<SetupNode />} />
<Route path="wallet" element={<SetupWallet />} />
<Route path="mnemonic" element={<SetupMnemonic />} />
<Route path="finish" element={<SetupFinish />} />
</Route>
<Route path="apps" element={<AppsRedirect />}>
<Route index path="" element={<AppsList />} />
Expand Down
Binary file modified frontend/src/assets/images/new-connection.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions frontend/src/components/Alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { classNames } from "src/utils/classes";

type Props = {
type: "warn" | "info";
children: React.ReactNode;
};

export default function Alert({ type, children }: Props) {
return (
<div
className={classNames(
"rounded-md p-3",
type == "warn" &&
"text-orange-700 bg-orange-50 dark:text-orange-200 dark:bg-orange-900",
type == "info" &&
"text-blue-700 bg-blue-50 dark:text-blue-300 dark:bg-blue-900"
)}
>
{children}
</div>
);
}
10 changes: 7 additions & 3 deletions frontend/src/components/ConnectButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@ type ConnectButtonProps = {
isConnecting: boolean;
loadingText?: string;
submitText?: string;
disabled?: boolean;
};

export default function ConnectButton({
isConnecting,
loadingText,
submitText,
disabled,
}: ConnectButtonProps) {
return (
<button
type="submit"
className={`mt-4 inline-flex w-full gap-2 ${
isConnecting ? "bg-gray-300 dark:bg-gray-700" : "bg-purple-700"
} cursor-pointer items-center justify-center rounded-md px-5 py-3 font-medium text-white shadow transition duration-150 hover:bg-purple-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 dark:text-neutral-200`}
disabled={isConnecting}
isConnecting || disabled
? "bg-gray-300 dark:bg-gray-700 pointer-events-none"
: "bg-purple-700 hover:bg-purple-900"
} cursor-pointer items-center justify-center rounded-md px-5 py-3 font-medium text-white shadow transition duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 dark:text-neutral-200`}
disabled={isConnecting || disabled}
>
{isConnecting ? (
<>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import nwcComboMark from "src/assets/images/nwc-combomark.svg";
export default function Container({ children }: React.PropsWithChildren) {
return (
<div className="flex flex-col items-center w-full">
<div className="w-96 flex flex-col items-center">
<div className="max-w-md flex flex-col items-center">
<div className="mb-14">
<img alt="NWC Logo" className="h-12 inline" src={nwcComboMark} />
</div>
Expand Down
106 changes: 106 additions & 0 deletions frontend/src/components/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { useRef } from "react";

import { classNames } from "src/utils/classes";

type Props = {
suffix?: string;
endAdornment?: React.ReactNode;
block?: boolean;
};

export default function Input({
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
name,
id,
placeholder,
type = "text",
required = false,
pattern,
title,
onChange,
onFocus,
onBlur,
value,
autoFocus = false,
autoComplete = "off",
disabled,
min,
max,
suffix,
endAdornment,
block = true,
className,
...otherProps
}: React.InputHTMLAttributes<HTMLInputElement> & Props) {
const inputEl = useRef<HTMLInputElement>(null);
const outerStyles =
"rounded-md border border-gray-300 dark:border-neutral-800 transition duration-300 flex-1";

const inputNode = (
<input
{...otherProps}
ref={inputEl}
type={type}
name={name}
id={id}
className={classNames(
"placeholder-gray-500 dark:placeholder-neutral-600",
block && "block w-full",
!suffix && !endAdornment
? `${outerStyles} focus:ring-primary focus:border-primary focus:ring-1`
: "pr-0 border-0 focus:ring-0",
disabled
? "bg-gray-50 dark:bg-surface-01dp text-gray-500 dark:text-neutral-500"
: "bg-white dark:bg-black dark:text-white",
!!className && className
)}
placeholder={placeholder}
required={required}
pattern={pattern}
title={title}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
value={value}
autoFocus={autoFocus}
autoComplete={autoComplete}
disabled={disabled}
min={min}
max={max}
/>
);

if (!suffix && !endAdornment) return inputNode;

return (
<div
className={classNames(
"flex items-stretch overflow-hidden",
!disabled &&
"focus-within:ring-primary focus-within:border-primary focus-within:dark:border-primary focus-within:ring-1",
outerStyles
)}
>
{inputNode}
{suffix && (
<span
className="flex items-center px-3 font-medium bg-white dark:bg-surface-00dp dark:text-white"
onClick={() => {
inputEl.current?.focus();
}}
>
{suffix}
</span>
)}
{endAdornment && (
<span
className={classNames(
"flex items-center bg-white dark:bg-black dark:text-neutral-400",
!!disabled && "bg-gray-50 dark:bg-surface-01dp"
)}
>
{endAdornment}
</span>
)}
</div>
);
}
94 changes: 94 additions & 0 deletions frontend/src/components/MnemonicInputs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { wordlist } from "@scure/bip39/wordlists/english";
import { useState } from "react";
import PasswordViewAdornment from "src/components/PasswordAdornment";
import Input from "src/components/Input";

type MnemonicInputsProps = {
mnemonic?: string;
setMnemonic?(mnemonic: string): void;
readOnly?: boolean;
};

export default function MnemonicInputs({
mnemonic,
setMnemonic,
readOnly,
children,
}: React.PropsWithChildren<MnemonicInputsProps>) {
const [revealedIndex, setRevealedIndex] = useState<number | undefined>(
undefined
);

const words = mnemonic?.split(" ") || [];
while (words.length < 12) {
words.push("");
}

while (words.length > 12) {
words.pop();
}

return (
<div className="border border-gray-200 dark:border-neutral-700 bg-white dark:bg-surface-02dp rounded-lg p-6 flex flex-col gap-8 items-center justify-center w-full mx-auto">
<h3 className="text-lg font-semibold dark:text-white">
Recovery phrase to your wallet
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-x-8 gap-y-5 justify-center">
{words.map((word, i) => {
const isRevealed = revealedIndex === i;
const inputId = `mnemonic-word-${i}`;
return (
<div key={i} className="flex justify-center items-center gap-2">
<span className="text-gray-600 dark:text-neutral-400 text-right">
{i + 1}.
</span>
<div className="relative">
<Input
id={inputId}
autoFocus={!readOnly && i === 0}
onFocus={() => setRevealedIndex(i)}
onBlur={() => setRevealedIndex(undefined)}
readOnly={readOnly}
block={false}
className="w-32 text-center"
list={readOnly ? undefined : "wordlist"}
value={isRevealed ? word : word.length ? "•••••" : ""}
onChange={(e) => {
if (revealedIndex !== i) {
return;
}
words[i] = e.target.value;
setMnemonic?.(
words
.map((word) => word.trim())
.join(" ")
.trim()
);
}}
endAdornment={
<PasswordViewAdornment
isRevealed={isRevealed}
onChange={(passwordView) => {
if (passwordView) {
document.getElementById(inputId)?.focus();
}
}}
/>
}
/>
</div>
</div>
);
})}
</div>
{!readOnly && (
<datalist id="wordlist">
{wordlist.map((word) => (
<option key={word} value={word} />
))}
</datalist>
)}
{children}
</div>
);
}
Loading
Loading