import { createForm, custom, getValue, required, setValue, submit } from "@modular-forms/solid"; import { Motion } from "@motionone/solid"; import { NDKUser, NDKUserProfile } from "@nostr-dev-kit/ndk"; import { nip19 } from "nostr-tools"; import { For, Show, Suspense, createMemo, createResource, createSignal } from "solid-js"; import { useParams, useSearchParams } from "solid-start"; import { Header, VStack } from "~/components"; import { AutoFaq } from "~/components/AutoZapFaq"; import { CheckOut } from "~/components/CheckOut"; import { CreateAutozapPage } from "~/components/CreateButton"; import { NWA } from "~/components/Auth"; import { LoadingSpinner } from "~/components/LoadingSpinner"; const PRIMAL_API = "https://primal-cache.mutinywallet.com/api"; const API_URL = import.meta.env.VITE_ZAPPLE_API_URL; const ZAPPLE_PAY_NPUB = "npub1wxl6njlcgygduct7jkgzrvyvd9fylj4pqvll6p32h59wyetm5fxqjchcan"; export function getHexpubFromNpub(npub?: string) { if (!npub) return; // If it doesn't start with npub let's just assume it's a hexpub if (!npub.startsWith("npub")) return npub; const user = new NDKUser({ npub }); return user.hexpubkey; } export function getNpubFromHexpub(hexpub?: string) { if (!hexpub) return; const user = new NDKUser({ hexpubkey: hexpub }); return user.npub; } async function fetchUser(npub: string) { try { const hexpubkey = getHexpubFromNpub(npub); const restPayload = JSON.stringify([ "user_profile", { pubkey: hexpubkey } ]); const response = await fetch(PRIMAL_API, { method: "POST", headers: { "Content-Type": "application/json" }, body: restPayload }); if (!response.ok) { throw new Error(`Failed to load profile`); } // should be an array of kind 0 and kind 10000105 const data = await response.json(); // get the profile out of the array const profile = data.find( (profile: any) => profile.kind === 0 && profile.pubkey === hexpubkey ); // return profile const parsedProfile = JSON.parse(profile.content); return parsedProfile; } catch (e) { console.error(e); } } type AutoZapFormType = { include_sender_npub: boolean; from_npub?: string; manual_nwc: boolean; amount_sats: string; nwc: string; interval: "day" | "week" | "month"; }; type ZapplePayPayload = { npub: string; to_npub: string; amount_sats: number; time_period: "day" | "week" | "month"; auth_id?: string; nwc?: string; }; function AutoZapForm(props: { npub: string; userProfile?: NDKUserProfile; callbackNwc?: string; }) { const [error, setError] = createSignal(undefined); const [saved, setSaved] = createSignal(false); const [nwaAuthId, setNwaAuthId] = createSignal( undefined ); const [searchParams] = useSearchParams(); function restoreStateFromLocalStorage() { if (typeof window === "undefined") return undefined; const formState = localStorage.getItem(`autozap:${props.npub}`); // Clear localstorage localStorage.removeItem(`autozap:${props.npub}`); const fromnpub = localStorage.getItem("fromnpub"); if (formState) { const parsedState = JSON.parse(formState); if (fromnpub) { parsedState.from_npub = fromnpub; } return parsedState; } } const cachedFormState = restoreStateFromLocalStorage(); const [autoForm, { Form, Field }] = createForm({ validate: (values) => { const errors: Record = {}; if (values.manual_nwc && !values.nwc) { errors.nwc = "Please enter a NWC string"; } return errors; }, initialValues: { include_sender_npub: cachedFormState?.include_sender_npub || false, from_npub: cachedFormState?.from_npub || undefined, amount_sats: cachedFormState?.amount_sats || undefined, manual_nwc: false, nwc: "", interval: cachedFormState?.interval || undefined } }); const handleSubmit = async (f: AutoZapFormType) => { const { from_npub, amount_sats, nwc, interval } = f; const to_npub_hex = nip19.decode(props.npub).data; // If there's no from npub set, just use zapple pay's npub const from_npub_hex = nip19.decode(from_npub || ZAPPLE_PAY_NPUB).data; // Save from_npub to localstorage if (from_npub) { localStorage.setItem("fromnpub", from_npub); } const payload: ZapplePayPayload = { npub: from_npub_hex.toString(), to_npub: to_npub_hex.toString(), amount_sats: Number(amount_sats), time_period: interval }; try { if (nwaAuthId()) { payload.auth_id = nwaAuthId(); console.log("using auth id", nwaAuthId()); } else if (nwc) { payload.nwc = nwc; console.log("using nwc", nwc); } else { throw new Error("You must connect a wallet"); } const res = await fetch(`https://${API_URL}/create-subscription`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); if (res.ok) { setSaved(true); } else { // if there's response text print that error const text = await res.text(); throw new Error(text); } } catch (e) { console.error(e); setError(e as Error); } }; // regex for just numbers const regex = /^[0-9]+$/; const nwaParams = createMemo(() => { const amount = getValue(autoForm, "amount_sats"); if (!amount) return undefined; const to_npub_hex = nip19.decode(props.npub).data; const time_period = getValue(autoForm, "interval"); if (!time_period) return undefined; return { identity: to_npub_hex.toString(), amount, time_period }; }); function handleConnectedChange(authId?: string) { console.log("connected change", authId); setNwaAuthId(authId); if (authId) { submit(autoForm); } } return ( <>
{ return regex.test(value!); }, "Please enter a number") ]} > {(field, props) => (
How much?
{field.error && (
{field.error}
)}
)}
{(field, props) => (
How often?
)}
{(field, props) => (
)}
{ let decoded = nip19.decode(value!); return !!decoded.data; }, "Please enter a valid npub") ]} > {(field, props) => (
Okay, what's your npub?
{field.error && (
{field.error}
)}
)}
Connect your wallet
} > {(field, props) => ( )} {(field, props) => ( <>