spruce up waitlist form

This commit is contained in:
Paul Miller
2023-04-21 11:08:02 -05:00
parent e3d75fe5c9
commit 37cac92a16
4 changed files with 85 additions and 87 deletions

View File

@@ -9,16 +9,16 @@ export function StyledRadioGroup(props: { value: string, choices: Choices, onVal
<RadioGroup.Root value={props.value} onValueChange={(e) => props.onValueChange(e)} class="grid w-full gap-4 grid-cols-2">
<For each={props.choices}>
{choice =>
<RadioGroup.Item value={choice.value} class="ui-checked:bg-white bg-white/10 rounded outline outline-black/50 ui-checked:outline-m-blue ui-checked:outline-2">
<RadioGroup.Item value={choice.value} class="ui-checked:bg-neutral-950 bg-white/10 rounded outline outline-black/50 ui-checked:outline-m-blue ui-checked:outline-2">
<div class="py-3 px-4">
<RadioGroup.ItemInput />
<RadioGroup.ItemControl >
<RadioGroup.ItemIndicator />
</RadioGroup.ItemControl>
<RadioGroup.ItemLabel class="ui-checked:text-m-blue text-neutral-400">
<RadioGroup.ItemLabel class="ui-checked:text-white text-neutral-400">
<div class="block">
<div class="text-lg font-semibold">{choice.label}</div>
<div class="text-lg font-light">{choice.caption}</div>
<div class="text-sm font-light">{choice.caption}</div>
</div>
</RadioGroup.ItemLabel>
</div>

View File

@@ -39,9 +39,9 @@ export function TextField(props: TextFieldProps) {
</Show>
<Show
when={props.multiline}
fallback={<KTextField.Input {...fieldProps} type={props.type} class="w-full p-2 rounded-lg bg-white/10" />}
fallback={<KTextField.Input {...fieldProps} type={props.type} class="w-full p-2 rounded-lg bg-white/10 placeholder-neutral-400" />}
>
<KTextField.TextArea {...fieldProps} autoResize class="w-full p-2 rounded-lg bg-white/10" />
<KTextField.TextArea {...fieldProps} autoResize class="w-full p-2 rounded-lg bg-white/10 placeholder-neutral-400" />
</Show>
<KTextField.ErrorMessage>{props.error}</KTextField.ErrorMessage>
</KTextField.Root>

View File

@@ -12,6 +12,7 @@ const relayUrls = [
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()
@@ -34,14 +35,17 @@ export function WaitlistAlreadyIn() {
const [posts] = createResource("", postsFetcher);
return (
<main class='flex flex-col gap-2 sm:gap-4 py-8 px-4 max-w-xl mx-auto items-center drop-shadow-blue-glow'>
<main class='flex flex-col gap-2 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">
We'll message you when Mutiny Wallet is ready.
</h2>
<div class="px-4 sm:px-8 py-8 rounded-xl bg-half-black">
<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 /></div>}>
<Show when={!posts.loading} fallback={<div class="h-[10rem]"><LoadingSpinner big /></div>}>
<Notes notes={posts() && posts() || []} />
</Show>
</div>

View File

@@ -1,116 +1,110 @@
import { createSignal } from "solid-js";
import { Button, LoadingSpinner } from "~/components/layout";
const INPUT = "w-full mb-4 p-2 rounded-lg text-black"
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 [nostr, setNostr] = createSignal(true);
const [error, setError] = createSignal<string | undefined>(undefined);
const [waitlistForm, { Form, Field }] = createForm<WaitlistForm>({ initialValues });
const [loading, setLoading] = createSignal(false);
// Form submission function that takes the form data and sends it to the backend
const handleSubmit = async (e: Event) => {
e.preventDefault();
setError(undefined);
setLoading(true);
const form = e.currentTarget;
const data = new FormData(form as HTMLFormElement);
const value = Object.fromEntries(data.entries());
console.log(value);
let payload: null | { user_type: string, id: string, comment: string } = null;
if (nostr()) {
payload = {
user_type: "nostr",
id: value.pubkey as string,
comment: value.comments as string
}
} else {
payload = {
user_type: "email",
id: value.email as string,
comment: value.comments as string
}
}
console.log(payload);
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 {
if (!payload || !payload.id) {
throw new Error("nope");
}
const res = await fetch(WAITLIST_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
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', payload.id);
localStorage.setItem('waitlist_id', f.id);
window.location.reload();
}
} catch (e) {
if (nostr()) {
setError("Something went wrong. Are you sure that's a valid npub?");
if (f.user_type === "nostr") {
const error = new Error("Something went wrong. Are you sure that's a valid npub?")
showToast(eify(error))
} else {
setError("Something went wrong. Are you sure that's a valid email?");
const error = new Error("Something went wrong. Not sure what.")
showToast(eify(error))
}
setTimeout(() => setLoading(false), 1000);
return
} finally {
setLoading(false)
}
}
return (
<main class='flex flex-col gap-4 py-8 px-4 max-w-xl mx-auto drop-shadow-blue-glow'>
<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>
{/* HTML form with three inputs: nostr pubkey (text), email (text), and a textarea for comments */}
<h2 class="text-xl">
Sign up for our waitlist and we'll send a message when Mutiny Wallet is ready for you.
</h2>
<div class="p-8 rounded-xl bg-half-black">
<div class="flex gap-4 mb-6">
<Button intent={nostr() ? "active" : "inactive"} onClick={() => setNostr(true)}>Nostr</Button>
<Button intent={nostr() ? "inactive" : "active"} onClick={() => setNostr(false)}> Email</Button>
</div>
{error() &&
<div class="mb-6">
<p class="text-m-red">Error: {error()}</p>
</div>
}
<form class="flex flex-col items-start gap-2" onSubmit={handleSubmit}>
{nostr() &&
<>
<label class="font-semibold" for="pubkey">Nostr npub or NIP-05</label>
<input class={INPUT} type="text" id="pubkey" name="pubkey" placeholder="npub..." />
</>
}
{
!nostr() &&
<>
<label class="font-semibold" for="email">Email</label>
<input class={INPUT} type="text" id="email" name="email" placeholder="email@mutinywallet.com" />
</>
}
<label class="font-semibold" for="comments">Comments</label>
<textarea class={INPUT} id="comments" name="comments" rows={4} placeholder="I want a lightning wallet that does..." />
{loading() &&
<LoadingSpinner />
}
{!loading() &&
<Button intent="red" layout="pad" >Submit</Button>
}
</form>
</div>
<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} 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>
)
}