mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-23 17:14:23 +01:00
remove waitlist
This commit is contained in:
committed by
Tony Giorgio
parent
9a5b2d3bcd
commit
20ac1da50c
@@ -47,7 +47,6 @@
|
|||||||
"class-variance-authority": "^0.4.0",
|
"class-variance-authority": "^0.4.0",
|
||||||
"i18next": "^22.5.1",
|
"i18next": "^22.5.1",
|
||||||
"i18next-browser-languagedetector": "^7.1.0",
|
"i18next-browser-languagedetector": "^7.1.0",
|
||||||
"nostr-tools": "^1.11.1",
|
|
||||||
"qr-scanner": "^1.4.2",
|
"qr-scanner": "^1.4.2",
|
||||||
"solid-js": "^1.7.7",
|
"solid-js": "^1.7.7",
|
||||||
"solid-qr-code": "^0.0.8",
|
"solid-qr-code": "^0.0.8",
|
||||||
|
|||||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@@ -37,9 +37,6 @@ dependencies:
|
|||||||
i18next-browser-languagedetector:
|
i18next-browser-languagedetector:
|
||||||
specifier: ^7.1.0
|
specifier: ^7.1.0
|
||||||
version: 7.1.0
|
version: 7.1.0
|
||||||
nostr-tools:
|
|
||||||
specifier: ^1.11.1
|
|
||||||
version: 1.12.1
|
|
||||||
qr-scanner:
|
qr-scanner:
|
||||||
specifier: ^1.4.2
|
specifier: ^1.4.2
|
||||||
version: 1.4.2
|
version: 1.4.2
|
||||||
@@ -1880,16 +1877,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==}
|
resolution: {integrity: sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
/@noble/curves@1.0.0:
|
|
||||||
resolution: {integrity: sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==}
|
|
||||||
dependencies:
|
|
||||||
'@noble/hashes': 1.3.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@noble/hashes@1.3.0:
|
|
||||||
resolution: {integrity: sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@nodelib/fs.scandir@2.1.5:
|
/@nodelib/fs.scandir@2.1.5:
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -2050,25 +2037,6 @@ packages:
|
|||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
rollup: 3.26.2
|
rollup: 3.26.2
|
||||||
|
|
||||||
/@scure/base@1.1.1:
|
|
||||||
resolution: {integrity: sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@scure/bip32@1.3.0:
|
|
||||||
resolution: {integrity: sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==}
|
|
||||||
dependencies:
|
|
||||||
'@noble/curves': 1.0.0
|
|
||||||
'@noble/hashes': 1.3.0
|
|
||||||
'@scure/base': 1.1.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@scure/bip39@1.2.0:
|
|
||||||
resolution: {integrity: sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==}
|
|
||||||
dependencies:
|
|
||||||
'@noble/hashes': 1.3.0
|
|
||||||
'@scure/base': 1.1.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@sideway/address@4.1.4:
|
/@sideway/address@4.1.4:
|
||||||
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
|
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4459,16 +4427,6 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/nostr-tools@1.12.1:
|
|
||||||
resolution: {integrity: sha512-ZeoV7g3jBUAlb4mKa3C+6hrc84htPkbebMShfGNgV4vAiz18e/sQukUBFL6vb/+sxZy+dBQFkRwsJIaVFs8Gfw==}
|
|
||||||
dependencies:
|
|
||||||
'@noble/curves': 1.0.0
|
|
||||||
'@noble/hashes': 1.3.0
|
|
||||||
'@scure/base': 1.1.1
|
|
||||||
'@scure/bip32': 1.3.0
|
|
||||||
'@scure/bip39': 1.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/npm-run-path@4.0.1:
|
/npm-run-path@4.0.1:
|
||||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|||||||
@@ -8,14 +8,12 @@ export function LoadingBar(props: { value: number; max: number }) {
|
|||||||
case 0:
|
case 0:
|
||||||
return "Just getting started";
|
return "Just getting started";
|
||||||
case 1:
|
case 1:
|
||||||
return "Checking user status";
|
|
||||||
case 2:
|
|
||||||
return "Double checking something";
|
return "Double checking something";
|
||||||
case 3:
|
case 2:
|
||||||
return "Downloading";
|
return "Downloading";
|
||||||
case 4:
|
case 3:
|
||||||
return "Setup";
|
return "Setup";
|
||||||
case 5:
|
case 4:
|
||||||
return "Done";
|
return "Done";
|
||||||
default:
|
default:
|
||||||
return "Just getting started";
|
return "Just getting started";
|
||||||
@@ -44,16 +42,14 @@ export function LoadingIndicator() {
|
|||||||
switch (state.load_stage) {
|
switch (state.load_stage) {
|
||||||
case "fresh":
|
case "fresh":
|
||||||
return 0;
|
return 0;
|
||||||
case "checking_user":
|
|
||||||
return 1;
|
|
||||||
case "checking_double_init":
|
case "checking_double_init":
|
||||||
return 2;
|
return 1;
|
||||||
case "downloading":
|
case "downloading":
|
||||||
return 3;
|
return 2;
|
||||||
case "setup":
|
case "setup":
|
||||||
return 4;
|
return 3;
|
||||||
case "done":
|
case "done":
|
||||||
return 5;
|
return 4;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -61,7 +57,7 @@ export function LoadingIndicator() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={state.load_stage !== "done"}>
|
<Show when={state.load_stage !== "done"}>
|
||||||
<LoadingBar value={loadStageValue()} max={5} />
|
<LoadingBar value={loadStageValue()} max={4} />
|
||||||
</Show>
|
</Show>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { Component, For, createEffect, createSignal } from "solid-js";
|
|
||||||
|
|
||||||
import { nip19 } from "nostr-tools";
|
|
||||||
import { Linkify } from "~/components/layout";
|
|
||||||
|
|
||||||
type NostrEvent = {
|
|
||||||
content: string;
|
|
||||||
created_at: number;
|
|
||||||
id?: string;
|
|
||||||
tags: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Note: Component<{ e: NostrEvent }> = (props) => {
|
|
||||||
const linkRoot = "https://snort.social/e/";
|
|
||||||
|
|
||||||
const [noteId, setNoteId] = createSignal("");
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (props.e.id) {
|
|
||||||
setNoteId(nip19.noteEncode(props.e.id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="flex gap-4 border-b border-faint-white py-6 items-start w-full">
|
|
||||||
<img
|
|
||||||
class="bg-black rounded-xl flex-0"
|
|
||||||
src="../180.png"
|
|
||||||
width={45}
|
|
||||||
height={45}
|
|
||||||
/>
|
|
||||||
<div class="flex flex-col gap-2 flex-1">
|
|
||||||
<p class="break-words">
|
|
||||||
{/* {props.e.content} */}
|
|
||||||
<Linkify initialText={props.e.content} />
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
class="no-underline hover:underline hover:decoration-light-text"
|
|
||||||
href={`${linkRoot}${noteId()}`}
|
|
||||||
>
|
|
||||||
<small class="text-light-text">
|
|
||||||
{new Date(props.e.created_at * 1000).toLocaleString()}
|
|
||||||
</small>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function filterReplies(event: NostrEvent) {
|
|
||||||
// If there's a "p" tag or an "e" tag we want to return false, otherwise true
|
|
||||||
for (const tag of event.tags) {
|
|
||||||
if (tag[0] === "p" || tag[0] === "e") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Notes: Component<{ notes: NostrEvent[] }> = (props) => {
|
|
||||||
return (
|
|
||||||
<ul class="flex flex-col">
|
|
||||||
<For
|
|
||||||
each={props.notes
|
|
||||||
.filter(filterReplies)
|
|
||||||
.sort((a, b) => b.created_at - a.created_at)}
|
|
||||||
>
|
|
||||||
{(item) => (
|
|
||||||
<li class="w-full">
|
|
||||||
<Note e={item as NostrEvent} />
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Notes;
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import { createResource, Show } from "solid-js";
|
|
||||||
|
|
||||||
const relayUrls = [
|
|
||||||
"wss://nostr.zebedee.cloud",
|
|
||||||
"wss://relay.snort.social",
|
|
||||||
"wss://nos.lol",
|
|
||||||
"wss://nostr.fmt.wiz.biz",
|
|
||||||
"wss://relay.damus.io",
|
|
||||||
"wss://eden.nostr.land"
|
|
||||||
];
|
|
||||||
|
|
||||||
import { SimplePool } from "nostr-tools";
|
|
||||||
import { LoadingSpinner } from "~/components/layout";
|
|
||||||
import Notes from "~/components/waitlist/Notes";
|
|
||||||
import logo from "~/assets/icons/mutiny-logo.svg";
|
|
||||||
|
|
||||||
const pool = new SimplePool();
|
|
||||||
|
|
||||||
const postsFetcher = async () => {
|
|
||||||
const filter = {
|
|
||||||
authors: [
|
|
||||||
"df173277182f3155d37b330211ba1de4a81500c02d195e964f91be774ec96708"
|
|
||||||
],
|
|
||||||
since: 0,
|
|
||||||
kinds: [1]
|
|
||||||
};
|
|
||||||
|
|
||||||
const events = await pool.list(relayUrls, [filter]);
|
|
||||||
|
|
||||||
return events;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function WaitlistAlreadyIn() {
|
|
||||||
const [posts] = createResource("", postsFetcher);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main class="flex flex-col gap-4 sm:gap-4 py-8 px-4 max-w-xl mx-auto items-start drop-shadow-blue-glow">
|
|
||||||
<a href="https://mutinywallet.com">
|
|
||||||
<img src={logo} class="h-10" alt="logo" />
|
|
||||||
</a>
|
|
||||||
<h1 class="text-4xl font-bold">You're on a list!</h1>
|
|
||||||
<h2 class="text-xl pr-4">
|
|
||||||
We'll message you when Mutiny Wallet is ready.
|
|
||||||
</h2>
|
|
||||||
<div class="px-4 sm:px-8 py-8 rounded-xl bg-half-black w-full">
|
|
||||||
<h2 class="text-sm font-semibold uppercase">Recent Updates</h2>
|
|
||||||
<Show
|
|
||||||
when={!posts.loading}
|
|
||||||
fallback={
|
|
||||||
<div class="h-[10rem]">
|
|
||||||
<LoadingSpinner big wide />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Notes notes={(posts() && posts()) || []} />
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
import { Match, Switch, createSignal } from "solid-js";
|
|
||||||
import { Button } from "~/components/layout";
|
|
||||||
import { StyledRadioGroup } from "../layout/Radio";
|
|
||||||
import { TextField } from "../layout/TextField";
|
|
||||||
import {
|
|
||||||
SubmitHandler,
|
|
||||||
createForm,
|
|
||||||
email,
|
|
||||||
getValue,
|
|
||||||
required,
|
|
||||||
setValue
|
|
||||||
} from "@modular-forms/solid";
|
|
||||||
import { showToast } from "../Toaster";
|
|
||||||
import eify from "~/utils/eify";
|
|
||||||
import logo from "~/assets/icons/mutiny-logo.svg";
|
|
||||||
|
|
||||||
const WAITLIST_ENDPOINT =
|
|
||||||
"https://waitlist.mutiny-waitlist.workers.dev/waitlist";
|
|
||||||
|
|
||||||
const COMMUNICATION_METHODS = [
|
|
||||||
{ value: "nostr", label: "Nostr", caption: "Your freshest npub" },
|
|
||||||
{ value: "email", label: "Email", caption: "Burners welcome" }
|
|
||||||
];
|
|
||||||
|
|
||||||
type WaitlistForm = {
|
|
||||||
user_type: "nostr" | "email";
|
|
||||||
id: string;
|
|
||||||
comment?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValues: WaitlistForm = { user_type: "nostr", id: "", comment: "" };
|
|
||||||
|
|
||||||
export default function WaitlistForm() {
|
|
||||||
const [waitlistForm, { Form, Field }] = createForm<WaitlistForm>({
|
|
||||||
initialValues
|
|
||||||
});
|
|
||||||
|
|
||||||
const [loading, setLoading] = createSignal(false);
|
|
||||||
|
|
||||||
const newHandleSubmit: SubmitHandler<WaitlistForm> = async (
|
|
||||||
f: WaitlistForm
|
|
||||||
) => {
|
|
||||||
console.log(f);
|
|
||||||
|
|
||||||
// TODO: not sure why waitlistForm.submitting doesn't work for me
|
|
||||||
// https://modularforms.dev/solid/guides/handle-submission
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const res = await fetch(WAITLIST_ENDPOINT, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify(f)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error("nope");
|
|
||||||
} else {
|
|
||||||
// On success set the id in local storage and reload the page
|
|
||||||
localStorage.setItem("waitlist_id", f.id);
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (f.user_type === "nostr") {
|
|
||||||
const error = new Error(
|
|
||||||
"Something went wrong. Are you sure that's a valid npub?"
|
|
||||||
);
|
|
||||||
showToast(eify(error));
|
|
||||||
} else {
|
|
||||||
const error = new Error("Something went wrong. Not sure what.");
|
|
||||||
showToast(eify(error));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main class="flex flex-col gap-8 py-8 px-4 max-w-xl mx-auto">
|
|
||||||
<a href="https://mutinywallet.com">
|
|
||||||
<img src={logo} class="h-10" alt="logo" />
|
|
||||||
</a>
|
|
||||||
<h1 class="text-4xl font-bold">Join Waitlist</h1>
|
|
||||||
<h2 class="text-xl">
|
|
||||||
Sign up for our waitlist and we'll send a message when Mutiny
|
|
||||||
Wallet is ready for you.
|
|
||||||
</h2>
|
|
||||||
<Form onSubmit={newHandleSubmit} class="flex flex-col gap-8">
|
|
||||||
<Field name="user_type">
|
|
||||||
{(field, _props) => (
|
|
||||||
// TODO: there's probably a "real" way to do this with modular-forms
|
|
||||||
<StyledRadioGroup
|
|
||||||
value={field.value || "nostr"}
|
|
||||||
onValueChange={(newValue) =>
|
|
||||||
setValue(
|
|
||||||
waitlistForm,
|
|
||||||
"user_type",
|
|
||||||
newValue as "nostr" | "email"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
choices={COMMUNICATION_METHODS}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Switch>
|
|
||||||
<Match
|
|
||||||
when={
|
|
||||||
getValue(waitlistForm, "user_type", {
|
|
||||||
shouldActive: false
|
|
||||||
}) === "nostr"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Field
|
|
||||||
name="id"
|
|
||||||
validate={[
|
|
||||||
required("We need some way to contact you")
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{(field, props) => (
|
|
||||||
<TextField
|
|
||||||
{...props}
|
|
||||||
value={field.value}
|
|
||||||
error={field.error}
|
|
||||||
label="Nostr npub or NIP-05"
|
|
||||||
placeholder="npub..."
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</Match>
|
|
||||||
<Match
|
|
||||||
when={
|
|
||||||
getValue(waitlistForm, "user_type", {
|
|
||||||
shouldActive: false
|
|
||||||
}) === "email"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Field
|
|
||||||
name="id"
|
|
||||||
validate={[
|
|
||||||
required("We need some way to contact you"),
|
|
||||||
email(
|
|
||||||
"That doesn't look like an email address to me"
|
|
||||||
)
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{(field, props) => (
|
|
||||||
<TextField
|
|
||||||
{...props}
|
|
||||||
value={field.value}
|
|
||||||
error={field.error}
|
|
||||||
type="email"
|
|
||||||
label="Email"
|
|
||||||
placeholder="email@nokycemail.com"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
<Field name="comment">
|
|
||||||
{(field, props) => (
|
|
||||||
<TextField
|
|
||||||
multiline
|
|
||||||
{...props}
|
|
||||||
value={field.value}
|
|
||||||
error={field.error}
|
|
||||||
label="Comments"
|
|
||||||
placeholder="I want a lightning wallet that does..."
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Button
|
|
||||||
loading={loading()}
|
|
||||||
disabled={
|
|
||||||
loading() ||
|
|
||||||
!waitlistForm.dirty ||
|
|
||||||
waitlistForm.submitting ||
|
|
||||||
waitlistForm.invalid
|
|
||||||
}
|
|
||||||
class="self-start"
|
|
||||||
intent="red"
|
|
||||||
type="submit"
|
|
||||||
layout="pad"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -32,14 +32,14 @@ export default function Root() {
|
|||||||
<Meta name="theme-color" content="rgb(23,23,23)" />
|
<Meta name="theme-color" content="rgb(23,23,23)" />
|
||||||
<Meta
|
<Meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Lightning wallet for the web"
|
content="Mutiny is a self-custodial lightning wallet that runs in the browser."
|
||||||
/>
|
/>
|
||||||
<Link rel="icon" href="/favicon.ico" />
|
<Link rel="icon" href="/favicon.ico" />
|
||||||
<Meta name="twitter:card" content="summary_large_image" />
|
<Meta name="twitter:card" content="summary_large_image" />
|
||||||
<Meta name="twitter:title" content="Mutiny Wallet" />
|
<Meta name="twitter:title" content="Mutiny Wallet" />
|
||||||
<Meta
|
<Meta
|
||||||
name="twitter:description"
|
name="twitter:description"
|
||||||
content="Sign up for our waitlist and we'll send a message when Mutiny Wallet is ready for you."
|
content="Mutiny is a self-custodial lightning wallet that runs in the browser."
|
||||||
/>
|
/>
|
||||||
<Meta
|
<Meta
|
||||||
name="twitter:site"
|
name="twitter:site"
|
||||||
@@ -53,7 +53,7 @@ export default function Root() {
|
|||||||
<Meta property="og:title" content="Mutiny Wallet" />
|
<Meta property="og:title" content="Mutiny Wallet" />
|
||||||
<Meta
|
<Meta
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="Sign up for our waitlist and we'll send a message when Mutiny Wallet is ready for you."
|
content="Mutiny is a self-custodial lightning wallet that runs in the browser."
|
||||||
/>
|
/>
|
||||||
<Meta
|
<Meta
|
||||||
property="og:url"
|
property="og:url"
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import App from "~/components/App";
|
import App from "~/components/App";
|
||||||
import { Switch, Match } from "solid-js";
|
import { Switch, Match } from "solid-js";
|
||||||
import { WaitlistAlreadyIn } from "~/components/waitlist/WaitlistAlreadyIn";
|
|
||||||
import WaitlistForm from "~/components/waitlist/WaitlistForm";
|
|
||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
import { FullscreenLoader } from "~/components/layout";
|
import { FullscreenLoader } from "~/components/layout";
|
||||||
import SetupErrorDisplay from "~/components/SetupErrorDisplay";
|
import SetupErrorDisplay from "~/components/SetupErrorDisplay";
|
||||||
@@ -14,15 +12,9 @@ export default function Home() {
|
|||||||
<Match when={state.setup_error}>
|
<Match when={state.setup_error}>
|
||||||
<SetupErrorDisplay initialError={state.setup_error!} />
|
<SetupErrorDisplay initialError={state.setup_error!} />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={state.user_status === "approved"}>
|
<Match when={true}>
|
||||||
<App />
|
<App />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={state.user_status === "waitlisted"}>
|
|
||||||
<WaitlistAlreadyIn />
|
|
||||||
</Match>
|
|
||||||
<Match when={state.user_status === "new_here"}>
|
|
||||||
<WaitlistForm />
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,8 @@ import { ParsedParams } from "~/logic/waila";
|
|||||||
|
|
||||||
const MegaStoreContext = createContext<MegaStore>();
|
const MegaStoreContext = createContext<MegaStore>();
|
||||||
|
|
||||||
type UserStatus = undefined | "new_here" | "waitlisted" | "approved";
|
|
||||||
|
|
||||||
export type LoadStage =
|
export type LoadStage =
|
||||||
| "fresh"
|
| "fresh"
|
||||||
| "checking_user"
|
|
||||||
| "checking_double_init"
|
| "checking_double_init"
|
||||||
| "downloading"
|
| "downloading"
|
||||||
| "setup"
|
| "setup"
|
||||||
@@ -37,11 +34,8 @@ export type LoadStage =
|
|||||||
|
|
||||||
export type MegaStore = [
|
export type MegaStore = [
|
||||||
{
|
{
|
||||||
already_approved?: boolean;
|
|
||||||
waitlist_id?: string;
|
|
||||||
mutiny_wallet?: MutinyWallet;
|
mutiny_wallet?: MutinyWallet;
|
||||||
deleting: boolean;
|
deleting: boolean;
|
||||||
user_status: UserStatus;
|
|
||||||
scan_result?: ParsedParams;
|
scan_result?: ParsedParams;
|
||||||
balance?: MutinyBalance;
|
balance?: MutinyBalance;
|
||||||
is_syncing?: boolean;
|
is_syncing?: boolean;
|
||||||
@@ -59,13 +53,11 @@ export type MegaStore = [
|
|||||||
load_stage: LoadStage;
|
load_stage: LoadStage;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fetchUserStatus(): Promise<UserStatus>;
|
|
||||||
setupMutinyWallet(
|
setupMutinyWallet(
|
||||||
settings?: MutinyWalletSettingStrings,
|
settings?: MutinyWalletSettingStrings,
|
||||||
password?: string
|
password?: string
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
deleteMutinyWallet(): Promise<void>;
|
deleteMutinyWallet(): Promise<void>;
|
||||||
setWaitlistId(waitlist_id: string): void;
|
|
||||||
setScanResult(scan_result: ParsedParams | undefined): void;
|
setScanResult(scan_result: ParsedParams | undefined): void;
|
||||||
sync(): Promise<void>;
|
sync(): Promise<void>;
|
||||||
dismissRestorePrompt(): void;
|
dismissRestorePrompt(): void;
|
||||||
@@ -78,13 +70,8 @@ export type MegaStore = [
|
|||||||
|
|
||||||
export const Provider: ParentComponent = (props) => {
|
export const Provider: ParentComponent = (props) => {
|
||||||
const [state, setState] = createStore({
|
const [state, setState] = createStore({
|
||||||
already_approved:
|
|
||||||
import.meta.env.VITE_SELFHOSTED === "true" ||
|
|
||||||
localStorage.getItem("already_approved") === "true",
|
|
||||||
waitlist_id: localStorage.getItem("waitlist_id"),
|
|
||||||
mutiny_wallet: undefined as MutinyWallet | undefined,
|
mutiny_wallet: undefined as MutinyWallet | undefined,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
user_status: undefined as UserStatus,
|
|
||||||
scan_result: undefined as ParsedParams | undefined,
|
scan_result: undefined as ParsedParams | undefined,
|
||||||
price: 0,
|
price: 0,
|
||||||
has_backed_up: localStorage.getItem("has_backed_up") === "true",
|
has_backed_up: localStorage.getItem("has_backed_up") === "true",
|
||||||
@@ -112,47 +99,6 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
async fetchUserStatus(): Promise<UserStatus> {
|
|
||||||
if (state.already_approved) {
|
|
||||||
console.log("welcome back!");
|
|
||||||
return "approved";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Using a PWA
|
|
||||||
if (state.is_pwa) {
|
|
||||||
localStorage.setItem("already_approved", "true");
|
|
||||||
return "approved";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Got an invite link
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const invite = urlParams.get("invite");
|
|
||||||
if (invite === "true") {
|
|
||||||
localStorage.setItem("already_approved", "true");
|
|
||||||
return "approved";
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// Remember them so we don't have to check every time
|
|
||||||
localStorage.setItem("already_approved", "true");
|
|
||||||
return "approved";
|
|
||||||
} else {
|
|
||||||
return "waitlisted";
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return "new_here";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async checkForSubscription(justPaid?: boolean): Promise<void> {
|
async checkForSubscription(justPaid?: boolean): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const timestamp = await state.mutiny_wallet?.check_subscribed();
|
const timestamp = await state.mutiny_wallet?.check_subscribed();
|
||||||
@@ -261,9 +207,6 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setWaitlistId(waitlist_id: string) {
|
|
||||||
setState({ waitlist_id });
|
|
||||||
},
|
|
||||||
async sync(): Promise<void> {
|
async sync(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (state.mutiny_wallet && !state.is_syncing) {
|
if (state.mutiny_wallet && !state.is_syncing) {
|
||||||
@@ -327,58 +270,40 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
|
|
||||||
// Fetch status from remote on load
|
// Fetch status from remote on load
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
setState({ load_stage: "checking_user" });
|
function handleExisting() {
|
||||||
// eslint-disable-next-line
|
if (state.existing_tab_detected) {
|
||||||
actions.fetchUserStatus().then((status) => {
|
setState({
|
||||||
setState({ user_status: status });
|
setup_error: new Error(
|
||||||
|
"Existing tab detected, aborting setup"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("running setup node manager...");
|
||||||
|
|
||||||
function handleExisting() {
|
actions
|
||||||
if (state.existing_tab_detected) {
|
.setupMutinyWallet()
|
||||||
setState({
|
.then(() => console.log("node manager setup done"));
|
||||||
setup_error: new Error(
|
|
||||||
"Existing tab detected, aborting setup"
|
|
||||||
)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log("running setup node manager...");
|
|
||||||
|
|
||||||
actions
|
// Setup an event listener to stop the mutiny wallet when the page unloads
|
||||||
.setupMutinyWallet()
|
window.onunload = async (_e) => {
|
||||||
.then(() => console.log("node manager setup done"));
|
console.log("stopping mutiny_wallet");
|
||||||
|
await state.mutiny_wallet?.stop();
|
||||||
// Setup an event listener to stop the mutiny wallet when the page unloads
|
console.log("mutiny_wallet stopped");
|
||||||
window.onunload = async (_e) => {
|
sessionStorage.removeItem("MUTINY_WALLET_INITIALIZED");
|
||||||
console.log("stopping mutiny_wallet");
|
};
|
||||||
await state.mutiny_wallet?.stop();
|
|
||||||
console.log("mutiny_wallet stopped");
|
|
||||||
sessionStorage.removeItem("MUTINY_WALLET_INITIALIZED");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleGoodBrowser() {
|
function handleGoodBrowser() {
|
||||||
console.log("checking if any other tabs are open");
|
console.log("checking if any other tabs are open");
|
||||||
// 500ms should hopefully be enough time for any tabs to reply
|
// 500ms should hopefully be enough time for any tabs to reply
|
||||||
timeout(500).then(handleExisting);
|
timeout(500).then(handleExisting);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only load node manager when status is approved
|
if (!state.mutiny_wallet && !state.deleting) {
|
||||||
if (
|
console.log("checking for browser compatibility...");
|
||||||
state.user_status === "approved" &&
|
actions.checkBrowserCompat().then(handleGoodBrowser);
|
||||||
!state.mutiny_wallet &&
|
}
|
||||||
!state.deleting
|
|
||||||
) {
|
|
||||||
console.log("checking for browser compatibility...");
|
|
||||||
actions.checkBrowserCompat().then(handleGoodBrowser);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Be reactive to changes in waitlist_id
|
|
||||||
createEffect(() => {
|
|
||||||
state.waitlist_id
|
|
||||||
? localStorage.setItem("waitlist_id", state.waitlist_id)
|
|
||||||
: localStorage.removeItem("waitlist_id");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user