diff --git a/package.json b/package.json index d91a0e5..40e559a 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "dependencies": { "@kobalte/core": "^0.8.2", "@kobalte/tailwindcss": "^0.5.0", + "@modular-forms/solid": "^0.12.0", "@motionone/solid": "^10.16.0", "@mutinywallet/mutiny-wasm": "^0.2.5", "@nostr-dev-kit/ndk": "^0.0.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 412f984..c8b220b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,9 @@ dependencies: '@kobalte/tailwindcss': specifier: ^0.5.0 version: 0.5.0(tailwindcss@3.3.1) + '@modular-forms/solid': + specifier: ^0.12.0 + version: 0.12.0(solid-js@1.7.3) '@motionone/solid': specifier: ^10.16.0 version: 10.16.0(solid-js@1.7.3) @@ -1561,6 +1564,14 @@ packages: solid-js: 1.7.3 dev: false + /@modular-forms/solid@0.12.0(solid-js@1.7.3): + resolution: {integrity: sha512-LJF+jvuJ0oG2vyxFu7MQ2bfIEaBeWFqtw6nc/PifDlufWyW/5mnhUyMxtlyTdAJAAc7A7NX02XvPA+yDCpcTVw==} + peerDependencies: + solid-js: ^1.3.1 + dependencies: + solid-js: 1.7.3 + dev: false + /@motionone/animation@10.15.1: resolution: {integrity: sha512-mZcJxLjHor+bhcPuIFErMDNyrdb2vJur8lSfMCsuCB4UyV8ILZLvK+t+pg56erv8ud9xQGK/1OGPt10agPrCyQ==} dependencies: diff --git a/src/assets/check-spinner.gif b/src/assets/check-spinner.gif new file mode 100644 index 0000000..97e3f3a Binary files /dev/null and b/src/assets/check-spinner.gif differ diff --git a/src/components/BalanceBox.tsx b/src/components/BalanceBox.tsx index 6bf5b33..c1190eb 100644 --- a/src/components/BalanceBox.tsx +++ b/src/components/BalanceBox.tsx @@ -49,10 +49,7 @@ export default function BalanceBox() { -
- Send - Receive -
+ }> }>
@@ -72,6 +69,10 @@ export default function BalanceBox() { +
+ Send + Receive +
) } diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx index e206deb..4ec624a 100644 --- a/src/components/Dialog.tsx +++ b/src/components/Dialog.tsx @@ -6,6 +6,7 @@ const OVERLAY = "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm" const DIALOG_POSITIONER = "fixed inset-0 z-50 flex items-center justify-center" const DIALOG_CONTENT = "w-[80vw] max-w-[400px] p-4 bg-gray/50 backdrop-blur-md shadow-xl rounded-xl border border-white/10" +// TODO: implement this like toast so it's just one global confirm and I can call it with `confirm({ title: "Are you sure?", description: "This will delete your node" })` export const ConfirmDialog: ParentComponent<{ isOpen: boolean; loading: boolean; onCancel: () => void, onConfirm: () => void }> = (props) => { return ( diff --git a/src/components/SettingsStringsEditor.tsx b/src/components/SettingsStringsEditor.tsx new file mode 100644 index 0000000..11c19b4 --- /dev/null +++ b/src/components/SettingsStringsEditor.tsx @@ -0,0 +1,97 @@ +import { createForm, url } from '@modular-forms/solid'; +import { TextField } from '~/components/layout/TextField'; +import { NodeManagerSettingStrings, getExistingSettings } from '~/logic/nodeManagerSetup'; +import { Button } from '~/components/layout'; +import { createSignal } from 'solid-js'; +import { deleteDb } from '~/routes/Settings'; +import { showToast } from './Toaster'; +import eify from '~/utils/eify'; +import { ConfirmDialog } from "~/components/Dialog"; +import { useMegaStore } from '~/state/megaStore'; + +export function SettingsStringsEditor() { + const existingSettings = getExistingSettings(); + const [settingsForm, { Form, Field, FieldArray }] = createForm({ initialValues: existingSettings }); + const [confirmOpen, setConfirmOpen] = createSignal(false); + + const [settingsTemp, setSettingsTemp] = createSignal(); + + const [state, actions] = useMegaStore(); + + async function handleSubmit(values: NodeManagerSettingStrings) { + try { + const existing = getExistingSettings(); + const newSettings = { ...existing, ...values } + if (existing.network !== values.network) { + // If the network changes we need to confirm the wipe + // Save the settings so we can get them later + setSettingsTemp(newSettings); + setConfirmOpen(true); + } else { + await actions.setupNodeManager(newSettings); + window.location.reload(); + } + } catch (e) { + console.error(e) + showToast(eify(e)) + } + console.log(values) + } + + async function confirmStateReset() { + try { + deleteDb("gossip") + localStorage.clear(); + showToast({ title: "Deleted", description: `Deleted all data` }) + const loadedValues = settingsTemp(); + + await actions.setupNodeManager(loadedValues); + window.location.reload(); + } catch (e) { + console.error(e) + showToast(eify(e)) + } + + setConfirmOpen(false); + } + + return
+ setConfirmOpen(false)} /> + + {(field, props) => ( + // TODO: make a cool select component +
+ + +
+ )} +
+ + {(field, props) => ( + + )} + + + {(field, props) => ( + + )} + + + {(field, props) => ( + + )} + + + {(field, props) => ( + + )} + + + + +} \ No newline at end of file diff --git a/src/components/layout/TextField.tsx b/src/components/layout/TextField.tsx new file mode 100644 index 0000000..51ac702 --- /dev/null +++ b/src/components/layout/TextField.tsx @@ -0,0 +1,49 @@ +import { TextField as KTextField } from '@kobalte/core'; +import { type JSX, Show, splitProps } from 'solid-js'; + +type TextFieldProps = { + name: string; + type?: 'text' | 'email' | 'tel' | 'password' | 'url' | 'date'; + label?: string; + placeholder?: string; + value: string | undefined; + error: string; + required?: boolean; + multiline?: boolean; + ref: (element: HTMLInputElement | HTMLTextAreaElement) => void; + onInput: JSX.EventHandler; + onChange: JSX.EventHandler; + onBlur: JSX.EventHandler; +}; + +export function TextField(props: TextFieldProps) { + const [fieldProps] = splitProps(props, [ + 'placeholder', + 'ref', + 'onInput', + 'onChange', + 'onBlur', + ]); + return ( + + + + {props.label} + + + } + > + + + {props.error} + + ); +} \ No newline at end of file diff --git a/src/logic/nodeManagerSetup.ts b/src/logic/nodeManagerSetup.ts index db1d9d9..607d637 100644 --- a/src/logic/nodeManagerSetup.ts +++ b/src/logic/nodeManagerSetup.ts @@ -1,8 +1,13 @@ import init, { NodeManager } from '@mutinywallet/mutiny-wasm'; +// export type NodeManagerSettingStrings = { +// network?: string, proxy?: string, esplora?: string, rgs?: string, lsp?: string, +// } + +type Network = "mainnet" | "testnet" | "regtest" | "signet"; export type NodeManagerSettingStrings = { - network?: string, proxy?: string, esplora?: string, rgs?: string, lsp?: string, + network?: Network, proxy?: string, esplora?: string, rgs?: string, lsp?: string, } export function getExistingSettings(): NodeManagerSettingStrings { @@ -64,12 +69,12 @@ export async function checkForWasm() { } } -export async function setupNodeManager(): Promise { +export async function setupNodeManager(settings?: NodeManagerSettingStrings): Promise { const _ = await init(); console.time("Setup"); console.log("Starting setup...") - const { network, proxy, esplora, rgs, lsp } = await setAndGetMutinySettings() + const { network, proxy, esplora, rgs, lsp } = await setAndGetMutinySettings(settings) console.log("Initializing Node Manager") console.log("Using network", network); console.log("Using proxy", proxy); diff --git a/src/routes/Receive.tsx b/src/routes/Receive.tsx index ee7c9bc..17ceb67 100644 --- a/src/routes/Receive.tsx +++ b/src/routes/Receive.tsx @@ -9,11 +9,10 @@ import { useMegaStore } from "~/state/megaStore"; import { satsToUsd } from "~/utils/conversions"; import { objectToSearchParams } from "~/utils/objectToSearchParams"; import { useCopy } from "~/utils/useCopy"; -import { JsonModal } from '~/components/JsonModal'; import mempoolTxUrl from "~/utils/mempoolTxUrl"; import { ReceiveSuccessModal } from "~/components/ReceiveSuccessModal"; -import party from '~/assets/party.gif'; +import party from '~/assets/check-spinner.gif'; import { Amount } from "~/components/Amount"; type OnChainTx = { diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx index 38d53f5..7e106c1 100644 --- a/src/routes/Settings.tsx +++ b/src/routes/Settings.tsx @@ -1,46 +1,64 @@ +import { createSignal } from "solid-js"; import { useNavigate } from "solid-start"; +import { ConfirmDialog } from "~/components/Dialog"; import KitchenSink from "~/components/KitchenSink"; import { Button, Card, DefaultMain, Hr, LargeHeader, SafeArea } from "~/components/layout"; import NavBar from "~/components/NavBar"; +import { SettingsStringsEditor } from "~/components/SettingsStringsEditor"; +import { showToast } from "~/components/Toaster"; import { useMegaStore } from "~/state/megaStore"; +export function deleteDb(name: string) { + const req = indexedDB.deleteDatabase(name); + req.onsuccess = function () { + console.log("Deleted database successfully"); + showToast({ title: "Deleted", description: `Deleted "${name}" database successfully` }) + }; + req.onerror = function () { + console.error("Couldn't delete database"); + showToast(new Error("Couldn't delete database")) + }; + req.onblocked = function () { + console.error("Couldn't delete database due to the operation being blocked"); + showToast(new Error("Couldn't delete database due to the operation being blocked")) + }; +} + export default function Settings() { const navigate = useNavigate(); const [_, actions] = useMegaStore(); - function clearWaitlistId() { - actions.setWaitlistId(''); - navigate("/") + async function resetNode() { + setConfirmLoading(true); + deleteDb("gossip") + localStorage.clear(); + showToast({ title: "Deleted", description: `Deleted all data` }) + setConfirmOpen(false); + setConfirmLoading(false); + setTimeout(() => { + window.location.reload(); + }, 1000); } - function setTestWaitlistId() { - actions.setWaitlistId('npub17zcnktw7svnechf5g666t33d7slw36sz8el3ep4c7kmyfwjhxn9qjvavs6'); - navigate("/") + async function confirmReset() { + setConfirmOpen(true); } - function resetNode() { - Object.keys(localStorage).forEach(function (key) { - if (key.startsWith('waitlist_id')) { - // Don't do anything because it's annoying to set my waitlist_id every time - } else { - localStorage.removeItem(key); - } - }); - navigate("/") - } + const [confirmOpen, setConfirmOpen] = createSignal(false); + const [confirmLoading, setConfirmLoading] = createSignal(false); return ( Settings + - - - + {/* */} + {/* */} + + setConfirmOpen(false)} /> -
-
diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index d0d60e8..20dd68e 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -2,7 +2,7 @@ import { ParentComponent, createContext, createEffect, onCleanup, onMount, useContext } from "solid-js"; import { createStore } from "solid-js/store"; -import { setupNodeManager } from "~/logic/nodeManagerSetup"; +import { NodeManagerSettingStrings, setupNodeManager } from "~/logic/nodeManagerSetup"; import { MutinyBalance, NodeManager } from "@mutinywallet/mutiny-wasm"; const MegaStoreContext = createContext(); @@ -19,7 +19,7 @@ export type MegaStore = [{ price: number }, { fetchUserStatus(): Promise; - setupNodeManager(): Promise; + setupNodeManager(settings?: NodeManagerSettingStrings): Promise; setWaitlistId(waitlist_id: string): void; sync(): Promise; }]; @@ -52,9 +52,9 @@ export const Provider: ParentComponent = (props) => { return "new_here" } }, - async setupNodeManager(): Promise { + async setupNodeManager(settings?: NodeManagerSettingStrings): Promise { try { - const nodeManager = await setupNodeManager() + const nodeManager = await setupNodeManager(settings) setState({ node_manager: nodeManager }) } catch (e) { console.error(e)