Skip to content

Commit

Permalink
feat: basic onboarding flow
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Jan 24, 2024
1 parent 7cf88fd commit e6f7f9e
Show file tree
Hide file tree
Showing 17 changed files with 241 additions and 1,897 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"editor.defaultFormatter": "golang.go"
},
"editor.formatOnSave": true,
"go.buildTags": "http,wails"
"go.buildTags": "http,wails",
"typescript.preferences.importModuleSpecifier": "non-relative"
}
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"react-hot-toast": "^2.4.1",
"react-qr-code": "^2.0.12",
"react-router-dom": "^6.21.0",
"swr": "^2.2.4"
"swr": "^2.2.4",
"zustand": "^4.5.0"
},
"devDependencies": {
"@tailwindcss/aspect-ratio": "^0.4.2",
Expand Down
10 changes: 6 additions & 4 deletions frontend/platform_specific/http/src/utils/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ export const request = async <T>(
const fetchResponse = await fetch(...args);

let body: T | undefined;
try {
body = await fetchResponse.json();
} catch (error) {
console.error(error);
if (fetchResponse.status !== 204) {
try {
body = await fetchResponse.json();
} catch (error) {
console.error(error);
}
}

if (!fetchResponse.ok) {
Expand Down
55 changes: 29 additions & 26 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,43 @@ import ShowApp from "src/screens/apps/ShowApp";
import NewApp from "src/screens/apps/NewApp";
import AppCreated from "src/screens/apps/AppCreated";
import NotFound from "src/screens/NotFound";
import { useInfo } from "./hooks/useInfo";
import Loading from "./components/Loading";
import { Setup } from "./screens/Setup";
import Start from "./screens/Start";
import { SetupNode } from "src/screens/setup/SetupNode";
import { Welcome } from "src/screens/Welcome";
import { SetupPassword } from "src/screens/setup/SetupPassword";
import Start from "src/screens/Start";
import { AppsRedirect } from "src/components/redirects/AppsRedirect";
import { StartRedirect } from "src/components/redirects/StartRedirect";
import { HomeRedirect } from "src/components/redirects/HomeRedirect";

function App() {
const { data: info } = useInfo();

if (!info) {
return <Loading />;
}

let home;
if (info?.setupCompleted && info.running) {
home = "/apps";
} else if (info.setupCompleted && !info.running) {
home = "/start";
} else {
home = "/setup";
}

return (
<div className="bg:white min-h-full dark:bg-black">
<Toaster />
<HashRouter>
<Routes>
<Route path="/" element={<Navbar />}>
<Route index element={<Navigate to={home} replace />} />
<Route path="start" element={<Start />} />
<Route path="setup" element={<Setup />} />
<Route path="apps" element={<AppsList />} />
<Route path="apps/:pubkey" element={<ShowApp />} />
<Route path="apps/new" element={<NewApp />} />
<Route path="apps/created" element={<AppCreated />} />
<Route path="" element={<HomeRedirect />} />
<Route
path="start"
element={
<StartRedirect>
<Start />
</StartRedirect>
}
></Route>
<Route path="welcome" element={<Welcome />}></Route>
<Route path="setup">
<Route path="" element={<Navigate to="password" />} />
<Route path="password" element={<SetupPassword />} />
<Route path="node" element={<SetupNode />} />
</Route>
<Route path="apps" element={<AppsRedirect />}>
<Route index path="" element={<AppsList />} />
<Route path=":pubkey" element={<ShowApp />} />
<Route path="new" element={<NewApp />} />
<Route path="created" element={<AppCreated />} />
<Route path="*" element={<NotFound />} />
</Route>
<Route path="about" element={<About />} />
</Route>
<Route path="/*" element={<NotFound />} />
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/components/redirects/AppsRedirect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useInfo } from "src/hooks/useInfo";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import React from "react";
import Loading from "src/components/Loading";

export function AppsRedirect() {
const { data: info } = useInfo();
const location = useLocation();
const navigate = useNavigate();

// TODO: re-add login redirect: https://github.com/getAlby/nostr-wallet-connect/commit/59b041886098dda4ff38191e3dd704ec36360673
React.useEffect(() => {
if (!info || info.running) {
return;
}
navigate("/");
}, [info, location, navigate]);

if (!info) {
return <Loading />;
}

return <Outlet />;
}
29 changes: 29 additions & 0 deletions frontend/src/components/redirects/HomeRedirect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useInfo } from "src/hooks/useInfo";
import { useLocation, useNavigate } from "react-router-dom";
import React from "react";
import Loading from "src/components/Loading";

export function HomeRedirect() {
const { data: info } = useInfo();
const location = useLocation();
const navigate = useNavigate();

React.useEffect(() => {
if (!info) {
return;
}
let to: string | undefined;
if (info.setupCompleted && info.running) {
to = "/apps";
} else if (info.setupCompleted && !info.running) {
to = "/start";
} else {
to = "/welcome";
}
navigate(to);
}, [info, location, navigate]);

if (!info) {
return <Loading />;
}
}
24 changes: 24 additions & 0 deletions frontend/src/components/redirects/StartRedirect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useInfo } from "src/hooks/useInfo";
import { useLocation, useNavigate } from "react-router-dom";
import React from "react";
import Loading from "src/components/Loading";

export function StartRedirect({ children }: React.PropsWithChildren) {
const { data: info } = useInfo();
const location = useLocation();
const navigate = useNavigate();

// TODO: re-add login redirect: https://github.com/getAlby/nostr-wallet-connect/commit/59b041886098dda4ff38191e3dd704ec36360673
React.useEffect(() => {
if (!info || (info.setupCompleted && !info.running)) {
return;
}
navigate("/");
}, [info, location, navigate]);

if (!info) {
return <Loading />;
}

return children;
}
6 changes: 3 additions & 3 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import "./fonts.css";
import App from "src/App.tsx";
import "src/index.css";
import "src/fonts.css";

ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/screens/Start.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import { useCSRF } from "src/hooks/useCSRF";
import { request } from "src/utils/request";
import ConnectButton from "src/components/ConnectButton";
Expand Down Expand Up @@ -40,7 +40,11 @@ export default function Start() {

return (
<>
<form onSubmit={onSubmit}>
<h1 className="text-lg">Start NWC</h1>
<p className="text-lg mb-10">
To start, please enter your unlock password
</p>
<form onSubmit={onSubmit} className="mb-10">
<>
<label
htmlFor="greenlight-invite-code"
Expand All @@ -58,6 +62,10 @@ export default function Start() {
<ConnectButton isConnecting={loading} />
</>
</form>

<Link to="/setup" className=" text-red-500">
Forgot password?
</Link>
</>
);
}
17 changes: 17 additions & 0 deletions frontend/src/screens/Welcome.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Link } from "react-router-dom";

export function Welcome() {
return (
<div className="flex flex-col justify-center items-center gap-10">
<p>Welcome to NWC!</p>
<p>NWC is ... and allows you to ...</p>
<Link to="/about" className="text-purple-500">
Learn more
</Link>

<Link to="/setup" className="text-green-500">
Get Started
</Link>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { useNavigate } from "react-router-dom";
import ConnectButton from "src/components/ConnectButton";
import { useCSRF } from "src/hooks/useCSRF";
import { useInfo } from "src/hooks/useInfo";
import useSetupStore from "src/state/SetupStore";
import { BackendType } from "src/types";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request"; // build the project for this to appear

export function Setup() {
export function SetupNode() {
const [backendType, setBackendType] = React.useState<BackendType>("BREEZ");
// TODO: proper onboarding
const [unlockPassword, setUnlockPassword] = React.useState("");
const { unlockPassword } = useSetupStore();
const [isConnecting, setConnecting] = React.useState(false);
const navigate = useNavigate();

Expand Down Expand Up @@ -56,21 +56,7 @@ export function Setup() {
<p className="mb-4">
Enter your node connection credentials to connect to your wallet.
</p>
{/* TODO: proper onboarding */}
<label
htmlFor="unlock-password"
className="block font-medium text-gray-900 dark:text-white"
>
NWC unlock password
</label>
<input
name="unlock-password"
onChange={(e) => setUnlockPassword(e.target.value)}
value={unlockPassword}
type="password"
id="unlock-password"
className="mb-4 dark:bg-surface-00dp block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:ring-2 focus:ring-purple-700 dark:border-gray-700 dark:text-white dark:placeholder-gray-400 dark:ring-offset-gray-800 dark:focus:ring-purple-600"
/>

<label
htmlFor="backend-type"
className="block font-medium text-gray-900 dark:text-white"
Expand Down
69 changes: 69 additions & 0 deletions frontend/src/screens/setup/SetupPassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { useInfo } from "src/hooks/useInfo";
import useSetupStore from "src/state/SetupStore";

export function SetupPassword() {
const store = useSetupStore();
const [confirmPassword, setConfirmPassword] = React.useState("");
const navigate = useNavigate();
const { data: info } = useInfo();

function onSubmit(e: React.FormEvent) {
e.preventDefault();
if (store.unlockPassword !== confirmPassword) {
alert("Password doesn't match!");
return;
}
navigate("/setup/node");
}

return (
<>
{info?.setupCompleted && (
<p className="mb-4 text-red-500">
Your node is already setup! only continue if you actually want to
change your connection settings.
</p>
)}
<form onSubmit={onSubmit}>
<div>
<label
htmlFor="unlock-password"
className="block font-medium text-gray-900 dark:text-white"
>
Choose an unlock password
</label>
<p className="text-sm">
This will encrypt your node information for next time you start NWC.
You'll also be asked to confirm your password when you setup a new
app connection.
</p>
<input
name="unlock-password"
onChange={(e) => store.setUnlockPassword(e.target.value)}
value={store.unlockPassword}
type="password"
id="unlock-password"
className="mb-4 dark:bg-surface-00dp block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:ring-2 focus:ring-purple-700 dark:border-gray-700 dark:text-white dark:placeholder-gray-400 dark:ring-offset-gray-800 dark:focus:ring-purple-600"
/>
<label
htmlFor="confirm-password"
className="block font-medium text-gray-900 dark:text-white"
>
Confirm password
</label>
<input
name="confirm-password"
onChange={(e) => setConfirmPassword(e.target.value)}
value={confirmPassword}
type="password"
id="confirm-password"
className="mb-4 dark:bg-surface-00dp block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:ring-2 focus:ring-purple-700 dark:border-gray-700 dark:text-white dark:placeholder-gray-400 dark:ring-offset-gray-800 dark:focus:ring-purple-600"
/>
</div>
<button>Submit</button>
</form>
</>
);
}
13 changes: 13 additions & 0 deletions frontend/src/state/SetupStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { create } from "zustand";

interface SetupStore {
readonly unlockPassword: string;
setUnlockPassword(unlockPassword: string): void;
}

const useSetupStore = create<SetupStore>((set) => ({
unlockPassword: "",
setUnlockPassword: (unlockPassword) => set({ unlockPassword }),
}));

export default useSetupStore;
2 changes: 1 addition & 1 deletion frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type BudgetRenewalType = "daily" | "weekly" | "monthly" | "yearly" | "";

export type IconMap = {
[key in RequestMethodType]: (
props: React.SVGAttributes<SVGElement>,
props: React.SVGAttributes<SVGElement>
) => JSX.Element;
};

Expand Down
9 changes: 8 additions & 1 deletion frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1709,7 +1709,7 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"

use-sync-external-store@^1.2.0:
use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
Expand Down Expand Up @@ -1756,3 +1756,10 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==

zustand@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.0.tgz#141354af56f91de378aa6c4b930032ab338f3ef0"
integrity sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==
dependencies:
use-sync-external-store "1.2.0"
Loading

0 comments on commit e6f7f9e

Please sign in to comment.