only load nodemanager if user is approved

This commit is contained in:
Paul Miller
2023-04-08 12:11:44 -05:00
parent 6ca4dee75d
commit 29530e008c
7 changed files with 162 additions and 95 deletions

View File

@@ -2,8 +2,8 @@ import { Motion, Presence } from "@motionone/solid";
import { MutinyBalance } from "@mutinywallet/node-manager";
import { createResource, Show, Suspense } from "solid-js";
import { useNodeManager } from "~/state/nodeManagerState";
import { ButtonLink } from "./Button";
import { useMegaStore } from "~/state/megaStore";
function prettyPrintAmount(n?: number | bigint): string {
if (!n || n.valueOf() === 0) {
@@ -17,16 +17,20 @@ function prettyPrintBalance(b: MutinyBalance): string {
}
export default function BalanceBox() {
const { nodeManager } = useNodeManager();
const [state, _] = useMegaStore();
const fetchBalance = async () => {
console.log("Refetching balance");
await nodeManager()?.sync();
const balance = await nodeManager()?.get_balance();
return balance
if (state.node_manager) {
console.log("Refetching balance");
await state.node_manager.sync();
const balance = await state.node_manager.get_balance();
return balance
} else {
return undefined
}
};
const [balance, { refetch: refetchBalance }] = createResource(nodeManager, fetchBalance);
const [balance, { refetch: refetchBalance }] = createResource(fetchBalance);
return (
<Presence>

View File

@@ -13,7 +13,7 @@ import {
Title,
} from "solid-start";
import "./root.css";
import { NodeManagerProvider } from "./state/nodeManagerState";
import { Provider as MegaStoreProvider } from "./state/megaStore";
export default function Root() {
return (
@@ -35,11 +35,11 @@ export default function Root() {
<Body>
<Suspense>
<ErrorBoundary>
<NodeManagerProvider>
<MegaStoreProvider>
<Routes>
<FileRoutes />
</Routes>
</NodeManagerProvider>
</MegaStoreProvider>
</ErrorBoundary>
</Suspense>
<Scripts />

View File

@@ -3,20 +3,24 @@ import { QRCodeSVG } from "solid-qr-code";
import { Button } from "~/components/Button";
import NavBar from "~/components/NavBar";
import SafeArea from "~/components/SafeArea";
import { useNodeManager } from "~/state/nodeManagerState";
import { useMegaStore } from "~/state/megaStore";
import { useCopy } from "~/utils/useCopy";
export default function Receive() {
const { nodeManager } = useNodeManager();
const [state, _] = useMegaStore()
// TODO: would be nice if this was just newest unused address
const getNewAddress = async () => {
console.log("Getting new address");
const address = await nodeManager()?.get_new_address();
return address
if (state.node_manager) {
console.log("Getting new address");
const address = await state.node_manager?.get_new_address();
return address
} else {
return undefined
}
};
const [address, { refetch: refetchAddress }] = createResource(nodeManager, getNewAddress);
const [address, { refetch: refetchAddress }] = createResource(getNewAddress);
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
@@ -27,7 +31,7 @@ export default function Receive() {
copied();
}
let shareData: ShareData = {
const shareData: ShareData = {
title: "Mutiny Wallet",
text: address(),
}
@@ -41,23 +45,25 @@ export default function Receive() {
return (
<SafeArea>
<main class='flex flex-col gap-4 py-8 px-4 items-center'>
<Show when={address()}>
<div class="w-full bg-white rounded-xl">
<QRCodeSVG value={address() ?? ""} class="w-full h-full p-8 max-h-[640px]" />
</div>
<div class="flex gap-2 w-full">
<Button onClick={() => copy(address() ?? "")}>{copied() ? "Copied" : "Copy"}</Button>
<Button onClick={share}>Share</Button>
</div>
<div class="rounded-xl p-4 flex flex-col gap-2 bg-[rgba(0,0,0,0.5)]">
<header class='text-sm font-semibold uppercase'>
Address / Invoice
</header>
<code class="break-all">{address()}</code>
<Button onClick={refetchAddress}>Get new address</Button>
</div>
</Show>
<main class='flex flex-col py-8 px-4 items-center'>
<div class="max-w-[400px] flex flex-col gap-4">
<Show when={address()}>
<div class="w-full bg-white rounded-xl">
<QRCodeSVG value={address() ?? ""} class="w-full h-full p-8 max-h-[400px]" />
</div>
<div class="flex gap-2 w-full">
<Button onClick={() => copy(address() ?? "")}>{copied() ? "Copied" : "Copy"}</Button>
<Button onClick={share}>Share</Button>
</div>
<div class="rounded-xl p-4 flex flex-col gap-2 bg-[rgba(0,0,0,0.5)]">
<header class='text-sm font-semibold uppercase'>
Address / Invoice
</header>
<code class="break-all">{address()}</code>
<Button onClick={refetchAddress}>Get new address</Button>
</div>
</Show>
</div>
</main>
<NavBar activeTab="none" />
</SafeArea >

View File

@@ -1,18 +1,22 @@
import { useNavigate } from "solid-start";
import { Button } from "~/components/Button";
import NavBar from "~/components/NavBar";
import SafeArea from "~/components/SafeArea";
import { useMegaStore } from "~/state/megaStore";
export default function Settings() {
const navigate = useNavigate();
const [_, actions] = useMegaStore();
function clearWaitlistId() {
localStorage.removeItem('waitlist_id');
window.location.reload();
actions.setWaitlistId('');
navigate("/")
}
function setTestWaitlistId() {
localStorage.setItem('waitlist_id', 'npub17zcnktw7svnechf5g666t33d7slw36sz8el3ep4c7kmyfwjhxn9qjvavs6');
// reload the window
window.location.reload();
actions.setWaitlistId('npub17zcnktw7svnechf5g666t33d7slw36sz8el3ep4c7kmyfwjhxn9qjvavs6');
navigate("/")
}
function resetNode() {
@@ -23,8 +27,7 @@ export default function Settings() {
localStorage.removeItem(key);
}
});
// reload the window
window.location.reload();
navigate("/")
}
return (

View File

@@ -1,45 +1,27 @@
import App from "~/components/App";
import { Accessor, createEffect, createResource, Setter, createSignal, Switch, Match } from "solid-js";
import { Switch, Match } from "solid-js";
import { WaitlistAlreadyIn } from "~/components/waitlist/WaitlistAlreadyIn";
import WaitlistForm from "~/components/waitlist/WaitlistForm";
import ReloadPrompt from "~/components/Reload";
function createWaitListSignal(): [Accessor<string>, Setter<string>] {
const [state, setState] = createSignal("");
const originalState = localStorage.getItem("waitlist_id")
if (originalState) {
setState(localStorage.getItem("waitlist_id") || "");
}
createEffect(() => localStorage.setItem("waitlist_id", state()));
return [state, setState];
}
async function fetchData(source: string) {
if (source) {
const data = await fetch(`https://waitlist.mutiny-waitlist.workers.dev/waitlist/${source}`);
return data.json();
} else {
return null
}
}
import { useMegaStore } from "~/state/megaStore";
export default function Home() {
// On load, check if the user is already on the waitlist
const [waitlistId] = createWaitListSignal();
const [waitlistData] = createResource(waitlistId, fetchData);
const [state, _] = useMegaStore();
return (
<>
<ReloadPrompt />
<Switch fallback={<>Loading...</>} >
<Match when={waitlistData() && waitlistData().approval_date}>
{/* TODO: might need this state.node_manager guard on all wallet routes */}
<Match when={state.user_status === "approved" && state.node_manager}>
<App />
</Match>
<Match when={waitlistData() && waitlistData().date}>
<Match when={state.user_status === "waitlisted"}>
<WaitlistAlreadyIn />
</Match>
<Match when={!waitlistData.loading && !waitlistData()}>
<Match when={state.user_status === "new_here"}>
<WaitlistForm />
</Match>
</Switch>

100
src/state/megaStore.tsx Normal file
View File

@@ -0,0 +1,100 @@
// Inspired by https://github.com/solidjs/solid-realworld/blob/main/src/store/index.js
import { ParentComponent, createContext, createEffect, useContext } from "solid-js";
import { createStore } from "solid-js/store";
import { setupNodeManager } from "~/logic/nodeManagerSetup";
import { NodeManager } from "@mutinywallet/node-manager";
const MegaStoreContext = createContext<MegaStore>();
type UserStatus = undefined | "new_here" | "waitlisted" | "approved" | "paid"
export type MegaStore = [{
waitlist_id: string | null;
node_manager: NodeManager | undefined;
user_status: UserStatus;
}, {
status(): Promise<UserStatus>;
setupNodeManager(): Promise<void>;
setWaitlistId(waitlist_id: string): void;
}];
export const Provider: ParentComponent = (props) => {
const [state, setState] = createStore({
waitlist_id: localStorage.getItem("waitlist_id"),
node_manager: undefined as NodeManager | undefined,
user_status: undefined as UserStatus,
});
const actions = {
async status(): Promise<UserStatus> {
if (!state.waitlist_id) {
return "new_here"
}
try {
const res = await fetch(`https://waitlist.mutiny-waitlist.workers.dev/waitlist/${state.waitlist_id}`)
const data = await res.json();
if (data.approval_date) {
return "approved"
} else {
return "waitlisted"
}
// TODO: handle paid status
} catch (e) {
return "new_here"
}
},
async setupNodeManager(): Promise<void> {
try {
const nodeManager = await setupNodeManager()
setState({ node_manager: nodeManager })
} catch (e) {
console.error(e)
}
},
setWaitlistId(waitlist_id: string) {
setState({ waitlist_id })
}
};
// Fetch status from remote on load
createEffect(async () => {
const status = await actions.status()
setState({ user_status: status })
})
// Only node manager when status is approved
createEffect(async () => {
if (state.user_status === "approved" && !state.node_manager) {
console.log("running setup node manager...")
await actions.setupNodeManager()
}
})
// Be reactive to changes in waitlist_id
createEffect(() => {
state.waitlist_id ? localStorage.setItem("waitlist_id", state.waitlist_id) : localStorage.removeItem("waitlist_id");
});
const store = [state, actions] as MegaStore;
return (
<MegaStoreContext.Provider value={store}>
{props.children}
</MegaStoreContext.Provider>
)
}
export function useMegaStore() {
// This is a trick to narrow the typescript types: https://docs.solidjs.com/references/api-reference/component-apis/createContext
const context = useContext(MegaStoreContext);
if (!context) {
throw new Error("useMegaStore: cannot find a MegaStoreContext")
}
return context;
}

View File

@@ -1,28 +0,0 @@
import { NodeManager } from "@mutinywallet/node-manager";
import { createContext, JSX, useContext, createResource, Resource } from "solid-js";
import { setupNodeManager } from "~/logic/nodeManagerSetup";
const NodeManagerContext = createContext<{ nodeManager: Resource<NodeManager> }>();
export function NodeManagerProvider(props: { children: JSX.Element }) {
const [nodeManager] = createResource(setupNodeManager);
const value = {
nodeManager,
};
return (
<NodeManagerContext.Provider value={value}>
{props.children}
</NodeManagerContext.Provider>
)
}
export function useNodeManager() {
// This is a trick to narrow the typescript types: https://docs.solidjs.com/references/api-reference/component-apis/createContext
const context = useContext(NodeManagerContext);
if (!context) {
throw new Error("useNodeManager: cannot find a NodeManagerContext")
}
return context;
}