mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-22 08:44:26 +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 { MutinyBalance } from "@mutinywallet/node-manager";
|
||||||
import { createResource, Show, Suspense } from "solid-js";
|
import { createResource, Show, Suspense } from "solid-js";
|
||||||
|
|
||||||
import { useNodeManager } from "~/state/nodeManagerState";
|
|
||||||
import { ButtonLink } from "./Button";
|
import { ButtonLink } from "./Button";
|
||||||
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
|
||||||
function prettyPrintAmount(n?: number | bigint): string {
|
function prettyPrintAmount(n?: number | bigint): string {
|
||||||
if (!n || n.valueOf() === 0) {
|
if (!n || n.valueOf() === 0) {
|
||||||
@@ -17,16 +17,20 @@ function prettyPrintBalance(b: MutinyBalance): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function BalanceBox() {
|
export default function BalanceBox() {
|
||||||
const { nodeManager } = useNodeManager();
|
const [state, _] = useMegaStore();
|
||||||
|
|
||||||
const fetchBalance = async () => {
|
const fetchBalance = async () => {
|
||||||
|
if (state.node_manager) {
|
||||||
console.log("Refetching balance");
|
console.log("Refetching balance");
|
||||||
await nodeManager()?.sync();
|
await state.node_manager.sync();
|
||||||
const balance = await nodeManager()?.get_balance();
|
const balance = await state.node_manager.get_balance();
|
||||||
return balance
|
return balance
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const [balance, { refetch: refetchBalance }] = createResource(nodeManager, fetchBalance);
|
const [balance, { refetch: refetchBalance }] = createResource(fetchBalance);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Presence>
|
<Presence>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
} from "solid-start";
|
} from "solid-start";
|
||||||
import "./root.css";
|
import "./root.css";
|
||||||
import { NodeManagerProvider } from "./state/nodeManagerState";
|
import { Provider as MegaStoreProvider } from "./state/megaStore";
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
return (
|
return (
|
||||||
@@ -35,11 +35,11 @@ export default function Root() {
|
|||||||
<Body>
|
<Body>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<NodeManagerProvider>
|
<MegaStoreProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
<FileRoutes />
|
<FileRoutes />
|
||||||
</Routes>
|
</Routes>
|
||||||
</NodeManagerProvider>
|
</MegaStoreProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<Scripts />
|
<Scripts />
|
||||||
|
|||||||
@@ -3,20 +3,24 @@ import { QRCodeSVG } from "solid-qr-code";
|
|||||||
import { Button } from "~/components/Button";
|
import { Button } from "~/components/Button";
|
||||||
import NavBar from "~/components/NavBar";
|
import NavBar from "~/components/NavBar";
|
||||||
import SafeArea from "~/components/SafeArea";
|
import SafeArea from "~/components/SafeArea";
|
||||||
import { useNodeManager } from "~/state/nodeManagerState";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
import { useCopy } from "~/utils/useCopy";
|
import { useCopy } from "~/utils/useCopy";
|
||||||
|
|
||||||
export default function Receive() {
|
export default function Receive() {
|
||||||
const { nodeManager } = useNodeManager();
|
const [state, _] = useMegaStore()
|
||||||
|
|
||||||
// TODO: would be nice if this was just newest unused address
|
// TODO: would be nice if this was just newest unused address
|
||||||
const getNewAddress = async () => {
|
const getNewAddress = async () => {
|
||||||
|
if (state.node_manager) {
|
||||||
console.log("Getting new address");
|
console.log("Getting new address");
|
||||||
const address = await nodeManager()?.get_new_address();
|
const address = await state.node_manager?.get_new_address();
|
||||||
return 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 });
|
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
|
||||||
|
|
||||||
@@ -27,7 +31,7 @@ export default function Receive() {
|
|||||||
copied();
|
copied();
|
||||||
|
|
||||||
}
|
}
|
||||||
let shareData: ShareData = {
|
const shareData: ShareData = {
|
||||||
title: "Mutiny Wallet",
|
title: "Mutiny Wallet",
|
||||||
text: address(),
|
text: address(),
|
||||||
}
|
}
|
||||||
@@ -41,10 +45,11 @@ export default function Receive() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<main class='flex flex-col gap-4 py-8 px-4 items-center'>
|
<main class='flex flex-col py-8 px-4 items-center'>
|
||||||
|
<div class="max-w-[400px] flex flex-col gap-4">
|
||||||
<Show when={address()}>
|
<Show when={address()}>
|
||||||
<div class="w-full bg-white rounded-xl">
|
<div class="w-full bg-white rounded-xl">
|
||||||
<QRCodeSVG value={address() ?? ""} class="w-full h-full p-8 max-h-[640px]" />
|
<QRCodeSVG value={address() ?? ""} class="w-full h-full p-8 max-h-[400px]" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 w-full">
|
<div class="flex gap-2 w-full">
|
||||||
<Button onClick={() => copy(address() ?? "")}>{copied() ? "Copied" : "Copy"}</Button>
|
<Button onClick={() => copy(address() ?? "")}>{copied() ? "Copied" : "Copy"}</Button>
|
||||||
@@ -58,6 +63,7 @@ export default function Receive() {
|
|||||||
<Button onClick={refetchAddress}>Get new address</Button>
|
<Button onClick={refetchAddress}>Get new address</Button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<NavBar activeTab="none" />
|
<NavBar activeTab="none" />
|
||||||
</SafeArea >
|
</SafeArea >
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
|
import { useNavigate } from "solid-start";
|
||||||
import { Button } from "~/components/Button";
|
import { Button } from "~/components/Button";
|
||||||
import NavBar from "~/components/NavBar";
|
import NavBar from "~/components/NavBar";
|
||||||
import SafeArea from "~/components/SafeArea";
|
import SafeArea from "~/components/SafeArea";
|
||||||
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [_, actions] = useMegaStore();
|
||||||
|
|
||||||
function clearWaitlistId() {
|
function clearWaitlistId() {
|
||||||
localStorage.removeItem('waitlist_id');
|
actions.setWaitlistId('');
|
||||||
window.location.reload();
|
navigate("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTestWaitlistId() {
|
function setTestWaitlistId() {
|
||||||
localStorage.setItem('waitlist_id', 'npub17zcnktw7svnechf5g666t33d7slw36sz8el3ep4c7kmyfwjhxn9qjvavs6');
|
actions.setWaitlistId('npub17zcnktw7svnechf5g666t33d7slw36sz8el3ep4c7kmyfwjhxn9qjvavs6');
|
||||||
// reload the window
|
navigate("/")
|
||||||
window.location.reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetNode() {
|
function resetNode() {
|
||||||
@@ -23,8 +27,7 @@ export default function Settings() {
|
|||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// reload the window
|
navigate("/")
|
||||||
window.location.reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,45 +1,27 @@
|
|||||||
|
|
||||||
import App from "~/components/App";
|
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 { WaitlistAlreadyIn } from "~/components/waitlist/WaitlistAlreadyIn";
|
||||||
import WaitlistForm from "~/components/waitlist/WaitlistForm";
|
import WaitlistForm from "~/components/waitlist/WaitlistForm";
|
||||||
import ReloadPrompt from "~/components/Reload";
|
import ReloadPrompt from "~/components/Reload";
|
||||||
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
// On load, check if the user is already on the waitlist
|
const [state, _] = useMegaStore();
|
||||||
const [waitlistId] = createWaitListSignal();
|
|
||||||
const [waitlistData] = createResource(waitlistId, fetchData);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ReloadPrompt />
|
<ReloadPrompt />
|
||||||
|
|
||||||
<Switch fallback={<>Loading...</>} >
|
<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 />
|
<App />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={waitlistData() && waitlistData().date}>
|
<Match when={state.user_status === "waitlisted"}>
|
||||||
<WaitlistAlreadyIn />
|
<WaitlistAlreadyIn />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={!waitlistData.loading && !waitlistData()}>
|
<Match when={state.user_status === "new_here"}>
|
||||||
<WaitlistForm />
|
<WaitlistForm />
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</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