feat: update create account screen

This commit is contained in:
reya 2024-01-07 07:42:08 +07:00
parent 8e8e6fe244
commit 70707f69c8
23 changed files with 765 additions and 416 deletions

View File

@ -28,9 +28,10 @@
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"@tauri-apps/api": "2.0.0-alpha.13",
"@tauri-apps/plugin-autostart": "2.0.0-alpha.5",
"@tauri-apps/plugin-clipboard-manager": "2.0.0-alpha.5",
@ -45,7 +46,7 @@
"@tauri-apps/plugin-updater": "2.0.0-alpha.5",
"@tauri-apps/plugin-upload": "2.0.0-alpha.5",
"@vidstack/react": "^1.9.8",
"framer-motion": "^10.17.0",
"framer-motion": "^10.17.6",
"minidenticons": "^4.2.0",
"nanoid": "^5.0.4",
"nostr-fetch": "^0.14.1",
@ -71,7 +72,7 @@
"autoprefixer": "^10.4.16",
"cross-env": "^7.0.3",
"encoding": "^0.1.13",
"postcss": "^8.4.32",
"postcss": "^8.4.33",
"tailwind-merge": "^1.14.0",
"tailwindcss": "^3.4.0",
"typescript": "^5.3.3",

View File

@ -1,7 +1,7 @@
import { useArk, useStorage } from "@lume/ark";
import { LoaderIcon } from "@lume/icons";
import { AppLayout, AuthLayout, HomeLayout, SettingsLayout } from "@lume/ui";
import { NDKKind } from "@nostr-dev-kit/ndk";
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
import { fetch } from "@tauri-apps/plugin-http";
import {
RouterProvider,
@ -179,6 +179,25 @@ export default function Router() {
},
{
path: "create",
loader: async () => {
const trusted: NDKEvent[] = [];
const services = await ark.ndk.fetchEvents({
kinds: [NDKKind.AppHandler],
"#k": ["24133"],
});
for (const service of services) {
const nip05 = JSON.parse(service.content).nip05;
const validate = await ark.validateNIP05({
pubkey: service.pubkey,
nip05,
});
if (validate) trusted.push(service);
}
return trusted;
},
async lazy() {
const { CreateAccountScreen } = await import(
"./routes/auth/create"
@ -186,6 +205,15 @@ export default function Router() {
return { Component: CreateAccountScreen };
},
},
{
path: "create-profile",
async lazy() {
const { CreateProfileScreen } = await import(
"./routes/auth/create-profile"
);
return { Component: CreateProfileScreen };
},
},
{
path: "import",
async lazy() {

View File

@ -0,0 +1,5 @@
export function CreateProfileScreen() {
return (
<div className="flex items-center justify-center w-full h-full">WIP</div>
);
}

View File

@ -1,28 +1,40 @@
import { useArk, useStorage } from "@lume/ark";
import { ArrowLeftIcon, InfoIcon, LoaderIcon } from "@lume/icons";
import { User } from "@lume/ui";
import { NDKKind, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { downloadDir } from "@tauri-apps/api/path";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs";
import { motion } from "framer-motion";
import { minidenticon } from "minidenticons";
import { generatePrivateKey, getPublicKey, nip19 } from "nostr-tools";
import { useStorage } from "@lume/ark";
import { CheckIcon, ChevronDownIcon, LoaderIcon } from "@lume/icons";
import NDK, {
NDKEvent,
NDKNip46Signer,
NDKPrivateKeySigner,
} from "@nostr-dev-kit/ndk";
import * as Select from "@radix-ui/react-select";
import { Window } from "@tauri-apps/api/window";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { useLoaderData, useNavigate } from "react-router-dom";
import { toast } from "sonner";
import { AvatarUploader } from "./components/avatarUploader";
const Item = ({ event }: { event: NDKEvent }) => {
const domain = JSON.parse(event.content).nip05.replace("_@", "");
return (
<Select.Item
value={event.id}
className="relative flex items-center pr-10 leading-none rounded-md select-none text-neutral-100 rounded-mg h-9 pl-7"
>
<Select.ItemText>@{domain}</Select.ItemText>
<Select.ItemIndicator className="absolute left-0 inline-flex items-center justify-center transform h-7">
<CheckIcon className="size-4" />
</Select.ItemIndicator>
</Select.Item>
);
};
export function CreateAccountScreen() {
const [picture, setPicture] = useState("");
const [downloaded, setDownloaded] = useState(false);
const [loading, setLoading] = useState(false);
const [keys, setKeys] = useState<null | {
npub: string;
nsec: string;
}>(null);
const storage = useStorage();
const services = useLoaderData() as NDKEvent[];
const navigate = useNavigate();
const [serviceId, setServiceId] = useState(services[0].id);
const [loading, setIsLoading] = useState(false);
const {
register,
@ -30,282 +42,177 @@ export function CreateAccountScreen() {
formState: { isDirty, isValid },
} = useForm();
const ark = useArk();
const storage = useStorage();
const navigate = useNavigate();
const svgURI = `data:image/svg+xml;utf8,${encodeURIComponent(
minidenticon("lume new account", 90, 50),
)}`;
const onSubmit = async (data: { name: string; about: string }) => {
try {
setLoading(true);
const profile = {
...data,
name: data.name,
display_name: data.name,
bio: data.about,
picture: picture,
avatar: picture,
};
const userPrivkey = generatePrivateKey();
const userPubkey = getPublicKey(userPrivkey);
const userNpub = nip19.npubEncode(userPubkey);
const userNsec = nip19.nsecEncode(userPrivkey);
const signer = new NDKPrivateKeySigner(userPrivkey);
ark.updateNostrSigner({ signer });
const publish = await ark.createEvent({
content: JSON.stringify(profile),
kind: NDKKind.Metadata,
tags: [],
});
if (publish) {
await storage.createAccount({
id: userNpub,
pubkey: userPubkey,
privkey: userPrivkey,
});
setKeys({ npub: userNpub, nsec: userNsec });
setLoading(false);
} else {
toast.error("Cannot publish user profile, please try again later.");
setLoading(false);
}
} catch (e) {
return toast.error(e);
}
const getDomainName = (id: string) => {
const event = services.find((ev) => ev.id === id);
return JSON.parse(event.content).nip05.replace("_@", "") as string;
};
const copyNsec = async () => {
await writeText(keys.nsec);
};
const onSubmit = async (data: { username: string; email: string }) => {
setIsLoading(true);
const download = async () => {
try {
const downloadPath = await downloadDir();
const fileName = `nostr_keys_${new Date().toISOString()}.txt`;
const filePath = await save({
defaultPath: `${downloadPath}/${fileName}`,
const domain = getDomainName(serviceId);
const service = services.find((ev) => ev.id === serviceId);
const localSigner = NDKPrivateKeySigner.generate();
const localUser = await localSigner.user();
const bunker = new NDK({
explicitRelayUrls: [
"wss://relay.nsecbunker.com/",
"wss://nostr.vulpem.com/",
],
});
await bunker.connect(2000);
const remoteSigner = new NDKNip46Signer(
bunker,
service.pubkey,
localSigner,
);
remoteSigner.addListener("authUrl", (authUrl: string) => {
const authWindow = new Window("auth", {
url: authUrl,
title: domain,
titleBarStyle: "overlay",
width: 415,
height: 600,
center: true
});
authWindow.listen(
"tauri://close-requested",
async () => await authWindow.close(),
);
});
if (filePath) {
await writeTextFile(
filePath,
`Nostr account, generated by Lume (lume.nu)\nPublic key: ${keys.npub}\nPrivate key: ${keys.nsec}`,
);
const account = await remoteSigner.createAccount(
data.username,
domain,
data.email,
);
setDownloaded(true);
} // else { user cancel action }
} catch (e) {
return toast.error(e);
if (!account) {
setIsLoading(false);
return toast.error("Failed to create new account, try again later");
}
await storage.createAccount({
id: localUser.npub,
pubkey: localUser.pubkey,
privkey: localSigner.privateKey,
});
setIsLoading(false);
return navigate("/auth/create-profile");
};
return (
<div className="relative flex h-full w-full items-center justify-center">
<div className="absolute left-[8px] top-2">
{!keys ? (
<button
type="button"
onClick={() => navigate(-1)}
className="group inline-flex items-center gap-2 text-sm font-medium"
>
<div className="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-neutral-200 text-neutral-800 group-hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-200 dark:group-hover:bg-neutral-700">
<ArrowLeftIcon className="h-4 w-4" />
</div>
Back
</button>
) : null}
</div>
<div className="mx-auto flex w-full max-w-md flex-col gap-10">
<h1 className="text-center text-2xl font-semibold">
Let&apos;s set up your account.
</h1>
<div className="relative flex items-center justify-center w-full h-full">
<div className="flex flex-col w-full max-w-md gap-8 mx-auto">
<div className="flex flex-col gap-3">
{!keys ? (
<div className="rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950">
<form
onSubmit={handleSubmit(onSubmit)}
className="mb-0 flex flex-col"
>
<h1 className="text-2xl font-semibold text-center">
Let's get you set up on Nostr.
</h1>
<p className="text-lg font-medium leading-snug text-neutral-600 dark:text-neutral-500">
With an account on Nostr, you'll be able to use with any client that
you want.
</p>
</div>
<div className="flex flex-col gap-8">
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-3 mb-0"
>
<div className="flex flex-col gap-6 p-5 bg-neutral-950 rounded-2xl">
<div className="flex flex-col gap-2">
<label
htmlFor="username"
className="text-sm font-semibold uppercase text-neutral-600"
>
Username *
</label>
<div className="flex items-center justify-between w-full gap-2 bg-neutral-900 rounded-xl">
<input
type={"text"}
{...register("username", {
required: true,
minLength: 1,
})}
spellCheck={false}
placeholder="satoshi"
className="flex-1 min-w-0 text-xl bg-transparent border-transparent outline-none focus:outline-none focus:ring-0 focus:border-none h-14 ring-0 placeholder:text-neutral-600"
/>
<Select.Root value={serviceId} onValueChange={setServiceId}>
<Select.Trigger className="inline-flex items-center justify-end gap-2 pr-3 text-xl font-semibold text-blue-500 w-max shrink-0">
<Select.Value>@{getDomainName(serviceId)}</Select.Value>
<Select.Icon>
<ChevronDownIcon className="size-5" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="border rounded-lg bg-neutral-900 border-neutral-800">
<Select.Viewport className="p-3">
<Select.Group>
<Select.Label className="mb-2 px-7 text-neutral-600">
Public services
</Select.Label>
{services.map((service) => (
<Item key={service.id} event={service} />
))}
</Select.Group>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
</div>
</div>
<div className="flex flex-col gap-2">
<label
htmlFor="email"
className="text-sm font-semibold uppercase text-neutral-600"
>
Backup Email (Optional)
</label>
<input
type={"hidden"}
{...register("picture")}
value={picture}
type={"email"}
{...register("email", { required: false })}
spellCheck={false}
autoCapitalize="none"
autoCorrect="none"
className="px-3 text-xl border-transparent rounded-xl h-14 bg-neutral-900 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-800"
/>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<span className="font-semibold">Avatar</span>
<div className="flex h-36 w-full flex-col items-center justify-center gap-3 rounded-lg bg-neutral-100 dark:bg-neutral-900">
{picture.length > 0 ? (
<img
src={picture}
alt="user's avatar"
className="h-14 w-14 rounded-xl object-cover"
/>
) : (
<img
src={svgURI}
alt="user's avatar"
className="h-14 w-14 rounded-xl bg-black dark:bg-white"
/>
)}
<AvatarUploader setPicture={setPicture} />
</div>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="name" className="font-semibold">
Name *
</label>
<input
type={"text"}
{...register("name", {
required: true,
minLength: 1,
})}
spellCheck={false}
className="h-11 rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="about" className="font-semibold">
Bio
</label>
<textarea
{...register("about")}
spellCheck={false}
className="relative h-24 w-full resize-none rounded-lg border-transparent bg-neutral-100 px-3 py-2 !outline-none placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/>
</div>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2 rounded-lg bg-blue-100 p-3 text-sm text-blue-800 dark:bg-blue-900 dark:text-blue-200">
<InfoIcon className="h-8 w-8" />
<p>
There are many more settings you can configure from the
&quot;Settings&quot; screen. Be sure to visit it later.
</p>
</div>
<button
type="submit"
disabled={!isDirty || !isValid}
className="inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50"
>
{loading ? (
<LoaderIcon className="h-4 w-4 animate-spin" />
) : (
"Create and Continue"
)}
</button>
</div>
</div>
</form>
</div>
</div>
) : (
<>
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{
opacity: 1,
y: 0,
}}
className="rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950"
>
<User pubkey={keys.npub} variant="simple" />
</motion.div>
<motion.div
initial={{ opacity: 0, y: 80 }}
animate={{
opacity: 1,
y: 0,
}}
className="rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950"
>
<div className="flex flex-col gap-1.5">
<h5 className="font-semibold">Backup account</h5>
<div>
<p className="mb-2 select-text text-sm text-neutral-800 dark:text-neutral-200">
Your private key is your password. If you lose this key,
you will lose access to your account! Copy it and keep it
in a safe place.{" "}
<span className="text-red-500">
There is no way to reset your private key.
</span>
</p>
<p className="select-text text-sm text-neutral-800 dark:text-neutral-200">
Public key is used for sharing with other people so that
they can find you using the public key.
</p>
</div>
<div className="mt-3 flex flex-col gap-3">
<div className="flex flex-col gap-1">
<label htmlFor="nsec" className="text-sm font-semibold">
Private key
</label>
<div className="relative w-full">
<input
readOnly
value={`${keys.nsec.substring(
0,
10,
)}**************************`}
className="h-11 w-full rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/>
<div className="absolute right-0 top-0 inline-flex h-11 items-center justify-center px-2">
<button
type="button"
onClick={copyNsec}
className="rounded-md bg-neutral-200 px-2 py-1 text-sm font-medium hover:bg-neutral-400 dark:bg-neutral-700 dark:hover:bg-neutral-600"
>
Copy
</button>
</div>
</div>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="nsec" className="text-sm font-semibold">
Public key
</label>
<input
readOnly
value={keys.npub}
className="h-11 w-full rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/>
</div>
</div>
{!downloaded ? (
<button
type="button"
onClick={() => download()}
className="mt-1 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600"
>
Download account keys
</button>
) : null}
</div>
</motion.div>
</>
)}
{downloaded ? (
<motion.button
initial={{ opacity: 0, y: 50 }}
animate={{
opacity: 1,
y: 0,
}}
className="inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600"
type="button"
onClick={() => navigate("/auth/onboarding")}
<button
type="submit"
className="inline-flex items-center justify-center w-full h-12 font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600"
>
Finish
</motion.button>
) : null}
{loading ? (
<LoaderIcon className="size-5 animate-spin" />
) : (
"Create Account"
)}
</button>
</form>
<div className="flex flex-col gap-3">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-neutral-900" />
</div>
<div className="relative flex justify-center">
<span className="px-2 font-medium bg-black text-neutral-600">
Or
</span>
</div>
</div>
<button
type="submit"
className="inline-flex items-center justify-center w-full h-12 font-medium text-neutral-50 rounded-xl bg-neutral-950 hover:bg-neutral-900"
>
Generate nostr keys
</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,313 @@
import { useArk, useStorage } from "@lume/ark";
import { ArrowLeftIcon, InfoIcon, LoaderIcon } from "@lume/icons";
import { User } from "@lume/ui";
import { NDKKind, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { downloadDir } from "@tauri-apps/api/path";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs";
import { motion } from "framer-motion";
import { minidenticon } from "minidenticons";
import { generatePrivateKey, getPublicKey, nip19 } from "nostr-tools";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { toast } from "sonner";
import { AvatarUploader } from "./components/avatarUploader";
export function CreateAccountScreen() {
const [picture, setPicture] = useState("");
const [downloaded, setDownloaded] = useState(false);
const [loading, setLoading] = useState(false);
const [keys, setKeys] = useState<null | {
npub: string;
nsec: string;
}>(null);
const {
register,
handleSubmit,
formState: { isDirty, isValid },
} = useForm();
const ark = useArk();
const storage = useStorage();
const navigate = useNavigate();
const svgURI = `data:image/svg+xml;utf8,${encodeURIComponent(
minidenticon("lume new account", 90, 50),
)}`;
const onSubmit = async (data: { name: string; about: string }) => {
try {
setLoading(true);
const profile = {
...data,
name: data.name,
display_name: data.name,
bio: data.about,
picture: picture,
avatar: picture,
};
const userPrivkey = generatePrivateKey();
const userPubkey = getPublicKey(userPrivkey);
const userNpub = nip19.npubEncode(userPubkey);
const userNsec = nip19.nsecEncode(userPrivkey);
const signer = new NDKPrivateKeySigner(userPrivkey);
ark.updateNostrSigner({ signer });
const publish = await ark.createEvent({
content: JSON.stringify(profile),
kind: NDKKind.Metadata,
tags: [],
});
if (publish) {
await storage.createAccount({
id: userNpub,
pubkey: userPubkey,
privkey: userPrivkey,
});
setKeys({ npub: userNpub, nsec: userNsec });
setLoading(false);
} else {
toast.error("Cannot publish user profile, please try again later.");
setLoading(false);
}
} catch (e) {
return toast.error(e);
}
};
const copyNsec = async () => {
await writeText(keys.nsec);
};
const download = async () => {
try {
const downloadPath = await downloadDir();
const fileName = `nostr_keys_${new Date().toISOString()}.txt`;
const filePath = await save({
defaultPath: `${downloadPath}/${fileName}`,
});
if (filePath) {
await writeTextFile(
filePath,
`Nostr account, generated by Lume (lume.nu)\nPublic key: ${keys.npub}\nPrivate key: ${keys.nsec}`,
);
setDownloaded(true);
} // else { user cancel action }
} catch (e) {
return toast.error(e);
}
};
return (
<div className="relative flex h-full w-full items-center justify-center">
<div className="absolute left-[8px] top-2">
{!keys ? (
<button
type="button"
onClick={() => navigate(-1)}
className="group inline-flex items-center gap-2 text-sm font-medium"
>
<div className="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-neutral-200 text-neutral-800 group-hover:bg-neutral-300 dark:bg-neutral-800 dark:text-neutral-200 dark:group-hover:bg-neutral-700">
<ArrowLeftIcon className="h-4 w-4" />
</div>
Back
</button>
) : null}
</div>
<div className="mx-auto flex w-full max-w-md flex-col gap-10">
<h1 className="text-center text-2xl font-semibold">
Let&apos;s set up your account.
</h1>
<div className="flex flex-col gap-3">
{!keys ? (
<div className="rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950">
<form
onSubmit={handleSubmit(onSubmit)}
className="mb-0 flex flex-col"
>
<input
type={"hidden"}
{...register("picture")}
value={picture}
/>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<span className="font-semibold">Avatar</span>
<div className="flex h-36 w-full flex-col items-center justify-center gap-3 rounded-lg bg-neutral-100 dark:bg-neutral-900">
{picture.length > 0 ? (
<img
src={picture}
alt="user's avatar"
className="h-14 w-14 rounded-xl object-cover"
/>
) : (
<img
src={svgURI}
alt="user's avatar"
className="h-14 w-14 rounded-xl bg-black dark:bg-white"
/>
)}
<AvatarUploader setPicture={setPicture} />
</div>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="name" className="font-semibold">
Name *
</label>
<input
type={"text"}
{...register("name", {
required: true,
minLength: 1,
})}
spellCheck={false}
className="h-11 rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="about" className="font-semibold">
Bio
</label>
<textarea
{...register("about")}
spellCheck={false}
className="relative h-24 w-full resize-none rounded-lg border-transparent bg-neutral-100 px-3 py-2 !outline-none placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/>
</div>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2 rounded-lg bg-blue-100 p-3 text-sm text-blue-800 dark:bg-blue-900 dark:text-blue-200">
<InfoIcon className="h-8 w-8" />
<p>
There are many more settings you can configure from the
&quot;Settings&quot; screen. Be sure to visit it later.
</p>
</div>
<button
type="submit"
disabled={!isDirty || !isValid}
className="inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50"
>
{loading ? (
<LoaderIcon className="h-4 w-4 animate-spin" />
) : (
"Create and Continue"
)}
</button>
</div>
</div>
</form>
</div>
) : (
<>
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{
opacity: 1,
y: 0,
}}
className="rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950"
>
<User pubkey={keys.npub} variant="simple" />
</motion.div>
<motion.div
initial={{ opacity: 0, y: 80 }}
animate={{
opacity: 1,
y: 0,
}}
className="rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950"
>
<div className="flex flex-col gap-1.5">
<h5 className="font-semibold">Backup account</h5>
<div>
<p className="mb-2 select-text text-sm text-neutral-800 dark:text-neutral-200">
Your private key is your password. If you lose this key,
you will lose access to your account! Copy it and keep it
in a safe place.{" "}
<span className="text-red-500">
There is no way to reset your private key.
</span>
</p>
<p className="select-text text-sm text-neutral-800 dark:text-neutral-200">
Public key is used for sharing with other people so that
they can find you using the public key.
</p>
</div>
<div className="mt-3 flex flex-col gap-3">
<div className="flex flex-col gap-1">
<label htmlFor="nsec" className="text-sm font-semibold">
Private key
</label>
<div className="relative w-full">
<input
readOnly
value={`${keys.nsec.substring(
0,
10,
)}**************************`}
className="h-11 w-full rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/>
<div className="absolute right-0 top-0 inline-flex h-11 items-center justify-center px-2">
<button
type="button"
onClick={copyNsec}
className="rounded-md bg-neutral-200 px-2 py-1 text-sm font-medium hover:bg-neutral-400 dark:bg-neutral-700 dark:hover:bg-neutral-600"
>
Copy
</button>
</div>
</div>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="nsec" className="text-sm font-semibold">
Public key
</label>
<input
readOnly
value={keys.npub}
className="h-11 w-full rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-900 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/>
</div>
</div>
{!downloaded ? (
<button
type="button"
onClick={() => download()}
className="mt-1 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600"
>
Download account keys
</button>
) : null}
</div>
</motion.div>
</>
)}
{downloaded ? (
<motion.button
initial={{ opacity: 0, y: 50 }}
animate={{
opacity: 1,
y: 0,
}}
className="inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600"
type="button"
onClick={() => navigate("/auth/onboarding")}
>
Finish
</motion.button>
) : null}
</div>
</div>
</div>
);
}

View File

@ -10,7 +10,7 @@
"devDependencies": {
"@biomejs/biome": "1.4.1",
"@tauri-apps/cli": "2.0.0-alpha.20",
"turbo": "^1.11.2"
"turbo": "^1.11.3"
},
"packageManager": "pnpm@8.9.0",
"engines": {

View File

@ -18,7 +18,7 @@
"@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"@tauri-apps/api": "2.0.0-alpha.13",
"@tauri-apps/plugin-clipboard-manager": "2.0.0-alpha.5",
"@tauri-apps/plugin-dialog": "2.0.0-alpha.5",

View File

@ -9,6 +9,7 @@ import NDK, {
NDKRelayAuthPolicies,
} from "@nostr-dev-kit/ndk";
import { ndkAdapter } from "@nostr-fetch/adapter-ndk";
import { fetch } from "@tauri-apps/plugin-http";
import { platform } from "@tauri-apps/plugin-os";
import { relaunch } from "@tauri-apps/plugin-process";
import Database from "@tauri-apps/plugin-sql";
@ -141,6 +142,9 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
// clientNip89: '',
});
// use tauri fetch
ndk.httpFetch = fetch;
// add signer
const signer = await initNostrSigner({
storage,

View File

@ -102,3 +102,4 @@ export * from "./src/help";
export * from "./src/plusSquare";
export * from "./src/column";
export * from "./src/addMedia";
export * from "./src/check";

View File

@ -0,0 +1,24 @@
import { SVGProps } from "react";
export function CheckIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5 12.713l5.017 5.012.4-.701a28.598 28.598 0 018.7-9.42L20 7"
/>
</svg>
);
}

View File

@ -1,24 +1,24 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export function ChevronDownIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M8 10L12 14L16 10"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 10.14a20.36 20.36 0 003.702 3.893c.175.141.42.141.596 0A20.361 20.361 0 0016 10.14"
/>
</svg>
);
}

View File

@ -9,7 +9,7 @@
"@lume/ui": "workspace:^",
"@lume/utils": "workspace:^",
"@nostr-dev-kit/ndk": "^2.3.2",
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"react": "^18.2.0",
"react-router-dom": "^6.21.1",
"sonner": "^1.3.1",

View File

@ -9,7 +9,7 @@
"@lume/ui": "workspace:^",
"@lume/utils": "workspace:^",
"@nostr-dev-kit/ndk": "^2.3.2",
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"react": "^18.2.0",
"react-router-dom": "^6.21.1",
"sonner": "^1.3.1",

View File

@ -9,7 +9,7 @@
"@lume/ui": "workspace:^",
"@lume/utils": "workspace:^",
"@nostr-dev-kit/ndk": "^2.3.2",
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"react": "^18.2.0",
"react-router-dom": "^6.21.1",
"sonner": "^1.3.1",

View File

@ -9,7 +9,7 @@
"@lume/ui": "workspace:^",
"@lume/utils": "workspace:^",
"@nostr-dev-kit/ndk": "^2.3.2",
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"react": "^18.2.0",
"react-router-dom": "^6.21.1",
"sonner": "^1.3.1",

View File

@ -9,7 +9,7 @@
"@lume/ui": "workspace:^",
"@lume/utils": "workspace:^",
"@nostr-dev-kit/ndk": "^2.3.2",
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"react": "^18.2.0",
"react-router-dom": "^6.21.1",
"sonner": "^1.3.1",

View File

@ -9,7 +9,7 @@
"@lume/ui": "workspace:^",
"@lume/utils": "workspace:^",
"@nostr-dev-kit/ndk": "^2.3.2",
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"react": "^18.2.0",
"react-router-dom": "^6.21.1",
"sonner": "^1.3.1",

View File

@ -9,7 +9,7 @@
"@lume/ui": "workspace:^",
"@lume/utils": "workspace:^",
"@nostr-dev-kit/ndk": "^2.3.2",
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"react": "^18.2.0",
"react-router-dom": "^6.21.1",
"sonner": "^1.3.1",

View File

@ -12,7 +12,7 @@
"@tauri-apps/plugin-os": "2.0.0-alpha.6",
"@tauri-apps/plugin-shell": "2.0.0-alpha.5",
"@tauri-apps/plugin-sql": "2.0.0-alpha.5",
"nostr-tools": "1.17",
"nostr-tools": "~1.17.0",
"react": "^18.2.0"
},
"devDependencies": {

View File

@ -14,17 +14,17 @@
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"@tauri-apps/api": "2.0.0-alpha.13",
"@tauri-apps/plugin-http": "2.0.0-alpha.6",
"@tauri-apps/plugin-os": "2.0.0-alpha.6",
"framer-motion": "^10.17.0",
"framer-motion": "^10.17.6",
"jotai": "^2.6.1",
"minidenticons": "^4.2.0",
"nostr-tools": "~1.17.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.4.1",
"react-hotkeys-hook": "^4.4.3",
"react-router-dom": "^6.21.1",
"slate": "^0.101.5",
"slate-react": "^0.101.5",

View File

@ -1,18 +1,34 @@
import { SettingsIcon } from "@lume/icons";
import { ArrowLeftIcon, SettingsIcon } from "@lume/icons";
import { type Platform } from "@tauri-apps/plugin-os";
import { Outlet } from "react-router-dom";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import { WindowTitleBar } from "../titlebar";
export function AuthLayout({ platform }: { platform: Platform }) {
const location = useLocation();
const navigate = useNavigate();
const canGoBack = location.key === "default";
return (
<div className="flex flex-col w-screen h-screen bg-black">
<div className="flex flex-col w-screen h-screen bg-black text-neutral-50">
{platform !== "macos" ? (
<WindowTitleBar platform={platform} />
) : (
<div data-tauri-drag-region className="h-9 shrink-0" />
)}
<div className="relative w-full h-full">
<div className="absolute top-0 right-9">
<div className="absolute top-0 z-10 flex items-center justify-between w-full px-9">
{canGoBack ? (
<button
type="button"
onClick={() => navigate(-1)}
className="inline-flex items-center justify-center rounded-lg size-10 group"
>
<ArrowLeftIcon className="size-6 text-neutral-700 group-hover:text-neutral-500" />
</button>
) : (
<div />
)}
<div className="inline-flex items-center justify-center rounded-lg size-10 bg-neutral-950 group hover:bg-neutral-900">
<SettingsIcon className="size-6 text-neutral-700 group-hover:text-neutral-500" />
</div>

View File

@ -8,7 +8,7 @@
"access": "public"
},
"dependencies": {
"@tanstack/react-query": "^5.17.0",
"@tanstack/react-query": "^5.17.1",
"@tauri-apps/api": "2.0.0-alpha.13",
"@tauri-apps/plugin-notification": "2.0.0-alpha.5",
"clsx": "^2.1.0",

View File

@ -15,8 +15,8 @@ importers:
specifier: 2.0.0-alpha.20
version: 2.0.0-alpha.20
turbo:
specifier: ^1.11.2
version: 1.11.2
specifier: ^1.11.3
version: 1.11.3
apps/desktop:
dependencies:
@ -83,6 +83,9 @@ importers:
'@radix-ui/react-popover':
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-select':
specifier: ^2.0.0
version: 2.0.0(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-switch':
specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
@ -90,8 +93,8 @@ importers:
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
'@tauri-apps/api':
specifier: 2.0.0-alpha.13
version: 2.0.0-alpha.13
@ -135,8 +138,8 @@ importers:
specifier: ^1.9.8
version: 1.9.8(@types/react@18.2.46)(react@18.2.0)
framer-motion:
specifier: ^10.17.0
version: 10.17.0(react-dom@18.2.0)(react@18.2.0)
specifier: ^10.17.6
version: 10.17.6(react-dom@18.2.0)(react@18.2.0)
minidenticons:
specifier: ^4.2.0
version: 4.2.0
@ -200,7 +203,7 @@ importers:
version: 3.5.0(vite@5.0.10)
autoprefixer:
specifier: ^10.4.16
version: 10.4.16(postcss@8.4.32)
version: 10.4.16(postcss@8.4.33)
cross-env:
specifier: ^7.0.3
version: 7.0.3
@ -208,8 +211,8 @@ importers:
specifier: ^0.1.13
version: 0.1.13
postcss:
specifier: ^8.4.32
version: 8.4.32
specifier: ^8.4.33
version: 8.4.33
tailwind-merge:
specifier: ^1.14.0
version: 1.14.0
@ -271,8 +274,8 @@ importers:
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
'@tauri-apps/api':
specifier: 2.0.0-alpha.13
version: 2.0.0-alpha.13
@ -417,8 +420,8 @@ importers:
specifier: ^2.3.2
version: 2.3.2(typescript@5.3.3)
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
react:
specifier: ^18.2.0
version: 18.2.0
@ -469,8 +472,8 @@ importers:
specifier: ^2.3.2
version: 2.3.2(typescript@5.3.3)
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
react:
specifier: ^18.2.0
version: 18.2.0
@ -521,8 +524,8 @@ importers:
specifier: ^2.3.2
version: 2.3.2(typescript@5.3.3)
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
react:
specifier: ^18.2.0
version: 18.2.0
@ -573,8 +576,8 @@ importers:
specifier: ^2.3.2
version: 2.3.2(typescript@5.3.3)
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
react:
specifier: ^18.2.0
version: 18.2.0
@ -625,8 +628,8 @@ importers:
specifier: ^2.3.2
version: 2.3.2(typescript@5.3.3)
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
react:
specifier: ^18.2.0
version: 18.2.0
@ -677,8 +680,8 @@ importers:
specifier: ^2.3.2
version: 2.3.2(typescript@5.3.3)
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
react:
specifier: ^18.2.0
version: 18.2.0
@ -729,8 +732,8 @@ importers:
specifier: ^2.3.2
version: 2.3.2(typescript@5.3.3)
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
react:
specifier: ^18.2.0
version: 18.2.0
@ -809,7 +812,7 @@ importers:
specifier: 2.0.0-alpha.5
version: 2.0.0-alpha.5
nostr-tools:
specifier: '1.17'
specifier: ~1.17.0
version: 1.17.0(typescript@5.3.3)
react:
specifier: ^18.2.0
@ -892,8 +895,8 @@ importers:
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
'@tauri-apps/api':
specifier: 2.0.0-alpha.13
version: 2.0.0-alpha.13
@ -904,8 +907,8 @@ importers:
specifier: 2.0.0-alpha.6
version: 2.0.0-alpha.6
framer-motion:
specifier: ^10.17.0
version: 10.17.0(react-dom@18.2.0)(react@18.2.0)
specifier: ^10.17.6
version: 10.17.6(react-dom@18.2.0)(react@18.2.0)
jotai:
specifier: ^2.6.1
version: 2.6.1(@types/react@18.2.46)(react@18.2.0)
@ -922,8 +925,8 @@ importers:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
react-hotkeys-hook:
specifier: ^4.4.1
version: 4.4.1(react-dom@18.2.0)(react@18.2.0)
specifier: ^4.4.3
version: 4.4.3(react-dom@18.2.0)(react@18.2.0)
react-router-dom:
specifier: ^6.21.1
version: 6.21.1(react-dom@18.2.0)(react@18.2.0)
@ -962,8 +965,8 @@ importers:
packages/utils:
dependencies:
'@tanstack/react-query':
specifier: ^5.17.0
version: 5.17.0(react@18.2.0)
specifier: ^5.17.1
version: 5.17.1(react@18.2.0)
'@tauri-apps/api':
specifier: 2.0.0-alpha.13
version: 2.0.0-alpha.13
@ -1541,6 +1544,12 @@ packages:
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
dev: false
/@radix-ui/number@1.0.1:
resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==}
dependencies:
'@babel/runtime': 7.23.7
dev: false
/@radix-ui/primitive@1.0.1:
resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
dependencies:
@ -2104,6 +2113,47 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-select@2.0.0(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.7
'@radix-ui/number': 1.0.1
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.46)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.46)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.46)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.46)(react@18.2.0)
'@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.46)(react@18.2.0)
'@radix-ui/react-popper': 1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.46)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.46)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.46)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.46)(react@18.2.0)
'@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.46)(react@18.2.0)
'@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.46
'@types/react-dom': 18.2.18
aria-hidden: 1.2.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.5.5(@types/react@18.2.46)(react@18.2.0)
dev: false
/@radix-ui/react-slot@1.0.2(@types/react@18.2.46)(react@18.2.0):
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
peerDependencies:
@ -2463,8 +2513,8 @@ packages:
/@scure/bip39@1.2.1:
resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==}
dependencies:
'@noble/hashes': 1.3.3
'@scure/base': 1.1.5
'@noble/hashes': 1.3.1
'@scure/base': 1.1.1
dev: false
/@swc/core-darwin-arm64@1.3.102:
@ -2611,16 +2661,16 @@ packages:
tailwindcss: 3.4.0
dev: true
/@tanstack/query-core@5.17.0:
resolution: {integrity: sha512-LoBaPtbMY26kRS+ohII4thTsWkJJsXKGitOLikTo2aqPA4yy7cfFJITs8DRnuERT7tLF5xfG9Lnm33Vp/38Vmw==}
/@tanstack/query-core@5.17.1:
resolution: {integrity: sha512-kUXozQmU7NBtzX5dM6qfFNZN+YK/9Ct37hnG/ogdgI4mExIx7VH/qRepsPhKfNrJz2w81/JykmM3Uug6sVpUSw==}
dev: false
/@tanstack/react-query@5.17.0(react@18.2.0):
resolution: {integrity: sha512-iNSn6ZA7mHUjrT0a271eKoa1oR1HznlrGbb475awft1kuP3zrhyUCrI8tlGowOr7zRoAxJholjwxO+gfz1IObw==}
/@tanstack/react-query@5.17.1(react@18.2.0):
resolution: {integrity: sha512-4JYgX0kU+pvwVQi5eRiHGvBK7WnahEl6lmaxd32ZVSKmByAxLgaewoxBR03cdDNse8lUD2zGOe0sx3M/EGRlmA==}
peerDependencies:
react: ^18.0.0
dependencies:
'@tanstack/query-core': 5.17.0
'@tanstack/query-core': 5.17.1
react: 18.2.0
dev: false
@ -3104,7 +3154,7 @@ packages:
retry: 0.12.0
dev: true
/autoprefixer@10.4.16(postcss@8.4.32):
/autoprefixer@10.4.16(postcss@8.4.33):
resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
@ -3112,11 +3162,11 @@ packages:
postcss: ^8.1.0
dependencies:
browserslist: 4.22.2
caniuse-lite: 1.0.30001572
caniuse-lite: 1.0.30001574
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.0.0
postcss: 8.4.32
postcss: 8.4.33
postcss-value-parser: 4.2.0
dev: true
@ -3195,8 +3245,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001572
electron-to-chromium: 1.4.617
caniuse-lite: 1.0.30001574
electron-to-chromium: 1.4.622
node-releases: 2.0.14
update-browserslist-db: 1.0.13(browserslist@4.22.2)
dev: true
@ -3235,8 +3285,8 @@ packages:
engines: {node: '>= 6'}
dev: true
/caniuse-lite@1.0.30001572:
resolution: {integrity: sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw==}
/caniuse-lite@1.0.30001574:
resolution: {integrity: sha512-BtYEK4r/iHt/txm81KBudCUcTy7t+s9emrIaHqjYurQ10x71zJ5VQ9x1dYPcz/b+pKSp4y/v1xSI67A+LzpNyg==}
dev: true
/case-anything@2.1.13:
@ -3542,8 +3592,8 @@ packages:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: true
/electron-to-chromium@1.4.617:
resolution: {integrity: sha512-sYNE3QxcDS4ANW1k4S/wWYMXjCVcFSOX3Bg8jpuMFaXt/x8JCmp0R1Xe1ZXDX4WXnSRBf+GJ/3eGWicUuQq5cg==}
/electron-to-chromium@1.4.622:
resolution: {integrity: sha512-GZ47DEy0Gm2Z8RVG092CkFvX7SdotG57c4YZOe8W8qD4rOmk3plgeNmiLVRHP/Liqj1wRiY3uUUod9vb9hnxZA==}
dev: true
/emoji-regex@8.0.0:
@ -3804,7 +3854,7 @@ packages:
engines: {node: ^12.20 || >= 14.13}
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.2.1
web-streams-polyfill: 3.3.2
dev: false
/fill-range@7.0.1:
@ -3887,8 +3937,8 @@ packages:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
dev: true
/framer-motion@10.17.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-92brXaYasyEwaPV7tnHnc6MKxdN84CxWE1aZ80q/mlS+wQo0rxp/pmjGt5hdAEK5RCKJsWToI+MyIcGoA91Msg==}
/framer-motion@10.17.6(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-WPPm0vLGTbhLOsD7v1fEv3yjX1RrmzsVI3CZ6dpBJvVb7wKMA6mpZsQzTYiSUDz/YIlvTUHHY0Jum7iEHnLHDA==}
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
@ -4834,29 +4884,29 @@ packages:
engines: {node: '>= 6'}
dev: true
/postcss-import@15.1.0(postcss@8.4.32):
/postcss-import@15.1.0(postcss@8.4.33):
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
engines: {node: '>=14.0.0'}
peerDependencies:
postcss: ^8.0.0
dependencies:
postcss: 8.4.32
postcss: 8.4.33
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.8
dev: true
/postcss-js@4.0.1(postcss@8.4.32):
/postcss-js@4.0.1(postcss@8.4.33):
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
engines: {node: ^12 || ^14 || >= 16}
peerDependencies:
postcss: ^8.4.21
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.32
postcss: 8.4.33
dev: true
/postcss-load-config@4.0.2(postcss@8.4.32):
/postcss-load-config@4.0.2(postcss@8.4.33):
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
engines: {node: '>= 14'}
peerDependencies:
@ -4869,17 +4919,17 @@ packages:
optional: true
dependencies:
lilconfig: 3.0.0
postcss: 8.4.32
postcss: 8.4.33
yaml: 2.3.4
dev: true
/postcss-nested@6.0.1(postcss@8.4.32):
/postcss-nested@6.0.1(postcss@8.4.33):
resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
dependencies:
postcss: 8.4.32
postcss: 8.4.33
postcss-selector-parser: 6.0.15
dev: true
@ -4903,8 +4953,8 @@ packages:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
dev: true
/postcss@8.4.32:
resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==}
/postcss@8.4.33:
resolution: {integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.7
@ -5148,8 +5198,8 @@ packages:
react: 18.2.0
dev: false
/react-hotkeys-hook@4.4.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-sClBMBioFEgFGYLTWWRKvhxcCx1DRznd+wkFHwQZspnRBkHTgruKIHptlK/U/2DPX8BhHoRGzpMVWUXMmdZlmw==}
/react-hotkeys-hook@4.4.3(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-G6psp7OUm9xxY4G2vL48tBwWUVJLvD/PeInaPdPvqRJ8GoXBu6Djqr6WIw5gu1M0SbR1epNUlvpccxu2ZzmtFQ==}
peerDependencies:
react: '>=16.8.1'
react-dom: '>=16.8.1'
@ -5732,11 +5782,11 @@ packages:
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.0.0
postcss: 8.4.32
postcss-import: 15.1.0(postcss@8.4.32)
postcss-js: 4.0.1(postcss@8.4.32)
postcss-load-config: 4.0.2(postcss@8.4.32)
postcss-nested: 6.0.1(postcss@8.4.32)
postcss: 8.4.33
postcss-import: 15.1.0(postcss@8.4.33)
postcss-js: 4.0.1(postcss@8.4.33)
postcss-load-config: 4.0.2(postcss@8.4.33)
postcss-nested: 6.0.1(postcss@8.4.33)
postcss-selector-parser: 6.0.15
resolve: 1.22.8
sucrase: 3.35.0
@ -5833,64 +5883,64 @@ packages:
resolution: {integrity: sha512-h9wayHHFI5+yqt8iau0vqH96cTNhezhZ/Fk/hrIdpfkiMu3lg9nzyvMfs5bIdX51IVzZO6DudLqhkL/rVXpT6g==}
dev: false
/turbo-darwin-64@1.11.2:
resolution: {integrity: sha512-toFmRG/adriZY3hOps7nYCfqHAS+Ci6xqgX3fbo82kkLpC6OBzcXnleSwuPqjHVAaRNhVoB83L5njcE9Qwi2og==}
/turbo-darwin-64@1.11.3:
resolution: {integrity: sha512-IsOOg2bVbIt3o/X8Ew9fbQp5t1hTHN3fGNQYrPQwMR2W1kIAC6RfbVD4A9OeibPGyEPUpwOH79hZ9ydFH5kifw==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/turbo-darwin-arm64@1.11.2:
resolution: {integrity: sha512-FCsEDZ8BUSFYEOSC3rrARQrj7x2VOrmVcfrMUIhexTxproRh4QyMxLfr6LALk4ymx6jbDCxWa6Szal8ckldFbA==}
/turbo-darwin-arm64@1.11.3:
resolution: {integrity: sha512-FsJL7k0SaPbJzI/KCnrf/fi3PgCDCjTliMc/kEFkuWVA6Httc3Q4lxyLIIinz69q6JTx8wzh6yznUMzJRI3+dg==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/turbo-linux-64@1.11.2:
resolution: {integrity: sha512-Vzda/o/QyEske5CxLf0wcu7UUS+7zB90GgHZV4tyN+WZtoouTvbwuvZ3V6b5Wgd3OJ/JwWR0CXDK7Sf4VEMr7A==}
/turbo-linux-64@1.11.3:
resolution: {integrity: sha512-SvW7pvTVRGsqtSkII5w+wriZXvxqkluw5FO/MNAdFw0qmoov+PZ237+37/NgArqE3zVn1GX9P6nUx9VO+xcQAg==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/turbo-linux-arm64@1.11.2:
resolution: {integrity: sha512-bRLwovQRz0yxDZrM4tQEAYV0fBHEaTzUF0JZ8RG1UmZt/CqtpnUrJpYb1VK8hj1z46z9YehARpYCwQ2K0qU4yw==}
/turbo-linux-arm64@1.11.3:
resolution: {integrity: sha512-YhUfBi1deB3m+3M55X458J6B7RsIS7UtM3P1z13cUIhF+pOt65BgnaSnkHLwETidmhRh8Dl3GelaQGrB3RdCDw==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/turbo-windows-64@1.11.2:
resolution: {integrity: sha512-LgTWqkHAKgyVuLYcEPxZVGPInTjjeCnN5KQMdJ4uQZ+xMDROvMFS2rM93iQl4ieDJgidwHCxxCxaU9u8c3d/Kg==}
/turbo-windows-64@1.11.3:
resolution: {integrity: sha512-s+vEnuM2TiZuAUUUpmBHDr6vnNbJgj+5JYfnYmVklYs16kXh+EppafYQOAkcRIMAh7GjV3pLq5/uGqc7seZeHA==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/turbo-windows-arm64@1.11.2:
resolution: {integrity: sha512-829aVBU7IX0c/B4G7g1VI8KniAGutHhIupkYMgF6xPkYVev2G3MYe6DMS/vsLt9GGM9ulDtdWxWrH5P2ngK8IQ==}
/turbo-windows-arm64@1.11.3:
resolution: {integrity: sha512-ZR5z5Zpc7cASwfdRAV5yNScCZBsgGSbcwiA/u3farCacbPiXsfoWUkz28iyrx21/TRW0bi6dbsB2v17swa8bjw==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/turbo@1.11.2:
resolution: {integrity: sha512-jPC7LVQJzebs5gWf8FmEvsvXGNyKbN+O9qpvv98xpNaM59aS0/Irhd0H0KbcqnXfsz7ETlzOC3R+xFWthC4Z8A==}
/turbo@1.11.3:
resolution: {integrity: sha512-RCJOUFcFMQNIGKSjC9YmA5yVP1qtDiBA0Lv9VIgrXraI5Da1liVvl3VJPsoDNIR9eFMyA/aagx1iyj6UWem5hA==}
hasBin: true
optionalDependencies:
turbo-darwin-64: 1.11.2
turbo-darwin-arm64: 1.11.2
turbo-linux-64: 1.11.2
turbo-linux-arm64: 1.11.2
turbo-windows-64: 1.11.2
turbo-windows-arm64: 1.11.2
turbo-darwin-64: 1.11.3
turbo-darwin-arm64: 1.11.3
turbo-linux-64: 1.11.3
turbo-linux-arm64: 1.11.3
turbo-windows-64: 1.11.3
turbo-windows-arm64: 1.11.3
dev: true
/type-fest@2.19.0:
@ -6196,7 +6246,7 @@ packages:
dependencies:
'@types/node': 20.10.6
esbuild: 0.19.11
postcss: 8.4.32
postcss: 8.4.33
rollup: 4.9.2
optionalDependencies:
fsevents: 2.3.3
@ -6206,8 +6256,8 @@ packages:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
dev: false
/web-streams-polyfill@3.2.1:
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
/web-streams-polyfill@3.3.2:
resolution: {integrity: sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==}
engines: {node: '>= 8'}
dev: false