mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-19 07:14:22 +01:00
only load nodemanager if user is approved
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 >
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
100
src/state/megaStore.tsx
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user