feat: use native feature instead of react

This commit is contained in:
reya 2024-06-21 10:24:09 +07:00
parent 59eaaec903
commit 1283432632
25 changed files with 280 additions and 439 deletions

View File

@ -15,10 +15,6 @@
"@lume/utils": "workspace:^", "@lume/utils": "workspace:^",
"@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@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-popover": "^1.0.7",
"@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-switch": "^1.0.3",
@ -37,12 +33,10 @@
"react-currency-input-field": "^3.8.0", "react-currency-input-field": "^3.8.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.52.0", "react-hook-form": "^7.52.0",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^14.1.2", "react-i18next": "^14.1.2",
"react-string-replace": "^1.1.1", "react-string-replace": "^1.1.1",
"slate": "^0.103.0", "slate": "^0.103.0",
"slate-react": "^0.105.0", "slate-react": "^0.105.0",
"sonner": "^1.5.0",
"use-debounce": "^10.0.1", "use-debounce": "^10.0.1",
"virtua": "^0.31.0" "virtua": "^0.31.0"
}, },

View File

@ -1,13 +1,13 @@
import { NostrQuery } from "@lume/system"; import { NostrQuery } from "@lume/system";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import { message } from "@tauri-apps/plugin-dialog";
import { import {
type Dispatch, type Dispatch,
type ReactNode, type ReactNode,
type SetStateAction, type SetStateAction,
useState, useState,
} from "react"; } from "react";
import { toast } from "sonner";
export function AvatarUploader({ export function AvatarUploader({
setPicture, setPicture,
@ -27,7 +27,7 @@ export function AvatarUploader({
setPicture(image); setPicture(image);
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
toast.error(String(e)); await message(String(e), { title: "Lume", kind: "error" });
} }
}; };

View File

@ -4,8 +4,8 @@ import { Spinner } from "@lume/ui";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext } from "@tanstack/react-router";
import { Menu, MenuItem } from "@tauri-apps/api/menu"; import { Menu, MenuItem } from "@tauri-apps/api/menu";
import { message } from "@tauri-apps/plugin-dialog";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { toast } from "sonner";
import { useNoteContext } from "../provider"; import { useNoteContext } from "../provider";
export function NoteRepost({ large = false }: { large?: boolean }) { export function NoteRepost({ large = false }: { large?: boolean }) {
@ -27,12 +27,12 @@ export function NoteRepost({ large = false }: { large?: boolean }) {
// update state // update state
setLoading(false); setLoading(false);
setIsRepost(true); setIsRepost(true);
// notify
toast.success("You've reposted this post successfully");
} catch { } catch {
setLoading(false); setLoading(false);
toast.error("Repost failed, try again later"); await message("Repost failed, try again later", {
title: "Lume",
kind: "info",
});
} }
}; };

View File

@ -87,8 +87,7 @@ export function NoteContent({
)); ));
return richContent; return richContent;
} catch (e) { } catch {
console.log("[parser]: ", e);
return event.content; return event.content;
} }
}, [event.content]); }, [event.content]);

View File

@ -1,62 +1,62 @@
import { LumeWindow } from "@lume/system";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import * as HoverCard from "@radix-ui/react-hover-card"; import { Menu, MenuItem } from "@tauri-apps/api/menu";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { useCallback } from "react";
import { User } from "../user"; import { User } from "../user";
import { useNoteContext } from "./provider"; import { useNoteContext } from "./provider";
import { LumeWindow } from "@lume/system";
export function NoteUser({ className }: { className?: string }) { export function NoteUser({ className }: { className?: string }) {
const event = useNoteContext(); const event = useNoteContext();
const showContextMenu = useCallback(async (e: React.MouseEvent) => {
e.preventDefault();
const menuItems = await Promise.all([
MenuItem.new({
text: "View Profile",
action: () => LumeWindow.openProfile(event.pubkey),
}),
MenuItem.new({
text: "Copy Public Key",
action: async () => {
const pubkey = await event.pubkeyAsBech32();
await writeText(pubkey);
},
}),
]);
const menu = await Menu.new({
items: menuItems,
});
await menu.popup().catch((e) => console.error(e));
}, []);
return ( return (
<User.Provider pubkey={event.pubkey}> <User.Provider pubkey={event.pubkey}>
<HoverCard.Root> <User.Root className={cn("flex items-start justify-between", className)}>
<User.Root <div className="flex w-full gap-2">
className={cn("flex items-start justify-between", className)} <button
> type="button"
<div className="flex w-full gap-2"> onClick={(e) => showContextMenu(e)}
<HoverCard.Trigger className="shrink-0"> className="shrink-0"
<User.Avatar className="object-cover rounded-full size-8 outline outline-1 -outline-offset-1 outline-black/15" />
</HoverCard.Trigger>
<div className="flex items-center w-full gap-3">
<div className="flex items-center gap-1">
<User.Name className="font-semibold text-neutral-950 dark:text-neutral-50" />
<User.NIP05 />
</div>
<div className="text-neutral-600 dark:text-neutral-400">·</div>
<User.Time
time={event.created_at}
className="text-neutral-600 dark:text-neutral-400"
/>
</div>
</div>
</User.Root>
<HoverCard.Portal>
<HoverCard.Content
className="w-[300px] rounded-xl bg-black p-3 data-[side=bottom]:animate-slideUpAndFade data-[state=open]:transition-all dark:bg-white dark:shadow-none"
sideOffset={5}
side="right"
> >
<div className="flex flex-col gap-2"> <User.Avatar className="object-cover rounded-full size-8 outline outline-1 -outline-offset-1 outline-black/15" />
<User.Avatar className="object-cover rounded-lg size-11" /> </button>
<div className="flex flex-col gap-2"> <div className="flex items-center w-full gap-3">
<div className="inline-flex items-center gap-1"> <div className="flex items-center gap-1">
<User.Name className="font-semibold leading-tight text-white dark:text-neutral-900" /> <User.Name className="font-semibold text-neutral-950 dark:text-neutral-50" />
<User.NIP05 /> <User.NIP05 />
</div>
<User.About className="text-sm text-white line-clamp-3 dark:text-neutral-900" />
<button
type="button"
onClick={() => LumeWindow.openProfile(event.pubkey)}
className="inline-flex items-center justify-center w-full mt-2 text-sm font-medium bg-white rounded-lg h-9 hover:bg-neutral-200 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-neutral-200"
>
View profile
</button>
</div>
</div> </div>
<HoverCard.Arrow className="fill-black dark:fill-white" /> <div className="text-neutral-600 dark:text-neutral-400">·</div>
</HoverCard.Content> <User.Time
</HoverCard.Portal> time={event.created_at}
</HoverCard.Root> className="text-neutral-600 dark:text-neutral-400"
/>
</div>
</div>
</User.Root>
</User.Provider> </User.Provider>
); );
} }

View File

@ -1,18 +1,14 @@
import { User } from "@/components/user"; import { User } from "@/components/user";
import { import { ComposeFilledIcon, HorizontalDotsIcon, PlusIcon } from "@lume/icons";
ComposeFilledIcon,
HorizontalDotsIcon,
PlusIcon,
SearchIcon,
} from "@lume/icons";
import { LumeWindow, NostrAccount } from "@lume/system"; import { LumeWindow, NostrAccount } from "@lume/system";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import * as Popover from "@radix-ui/react-popover"; import * as Popover from "@radix-ui/react-popover";
import { Outlet, createFileRoute } from "@tanstack/react-router"; import { Outlet, createFileRoute } from "@tanstack/react-router";
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import { Menu, MenuItem } from "@tauri-apps/api/menu";
import { getCurrent } from "@tauri-apps/api/window"; import { getCurrent } from "@tauri-apps/api/window";
import { useEffect, useMemo, useState } from "react"; import { message } from "@tauri-apps/plugin-dialog";
import { toast } from "sonner"; import { useCallback, useEffect, useMemo, useState } from "react";
export const Route = createFileRoute("/$account")({ export const Route = createFileRoute("/$account")({
beforeLoad: async () => { beforeLoad: async () => {
@ -63,12 +59,12 @@ function Screen() {
} }
function Accounts() { function Accounts() {
const navigate = Route.useNavigate();
const { accounts } = Route.useRouteContext(); const { accounts } = Route.useRouteContext();
const { account } = Route.useParams(); const { account } = Route.useParams();
const [windowWidth, setWindowWidth] = useState<number>(null); const [windowWidth, setWindowWidth] = useState<number>(null);
const navigate = Route.useNavigate();
const sortedList = useMemo(() => { const sortedList = useMemo(() => {
const list = accounts; const list = accounts;
@ -82,9 +78,33 @@ function Accounts() {
return list; return list;
}, [accounts]); }, [accounts]);
const changeAccount = async (npub: string) => { const showContextMenu = useCallback(
async (e: React.MouseEvent, npub: string) => {
e.preventDefault();
const menuItems = await Promise.all([
MenuItem.new({
text: "View Profile",
action: () => LumeWindow.openProfile(npub),
}),
MenuItem.new({
text: "Open Settings",
action: () => LumeWindow.openSettings(),
}),
]);
const menu = await Menu.new({
items: menuItems,
});
await menu.popup().catch((e) => console.error(e));
},
[],
);
const changeAccount = async (e: React.MouseEvent, npub: string) => {
if (npub === account) { if (npub === account) {
return await LumeWindow.openProfile(account); return showContextMenu(e, npub);
} }
// Change current account and update signer // Change current account and update signer
@ -102,7 +122,7 @@ function Accounts() {
replace: true, replace: true,
}); });
} else { } else {
toast.warning("Something wrong."); await message("Something wrong.", { title: "Accounts", kind: "error" });
} }
}; };
@ -135,7 +155,11 @@ function Accounts() {
{sortedList {sortedList
.slice(0, windowWidth > 500 ? account.length : 2) .slice(0, windowWidth > 500 ? account.length : 2)
.map((user) => ( .map((user) => (
<button key={user} type="button" onClick={() => changeAccount(user)}> <button
key={user}
type="button"
onClick={(e) => changeAccount(e, user)}
>
<User.Provider pubkey={user}> <User.Provider pubkey={user}>
<User.Root <User.Root
className={cn( className={cn(
@ -161,12 +185,12 @@ function Accounts() {
<HorizontalDotsIcon className="size-5" /> <HorizontalDotsIcon className="size-5" />
</Popover.Trigger> </Popover.Trigger>
<Popover.Portal> <Popover.Portal>
<Popover.Content className="flex h-11 select-none items-center justify-center rounded-md bg-neutral-950 p-1 text-sm text-neutral-50 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-50 dark:text-neutral-950"> <Popover.Content className="flex h-11 select-none items-center justify-center rounded-md bg-black/20 p-1 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
{sortedList.slice(2).map((user) => ( {sortedList.slice(2).map((user) => (
<button <button
key={user} key={user}
type="button" type="button"
onClick={() => changeAccount(user)} onClick={(e) => changeAccount(e, user)}
className="inline-flex items-center justify-center rounded-md size-9 hover:bg-white/10" className="inline-flex items-center justify-center rounded-md size-9 hover:bg-white/10"
> >
<User.Provider pubkey={user}> <User.Provider pubkey={user}>

View File

@ -1,10 +1,8 @@
import { CancelCircleIcon, CheckCircleIcon, InfoCircleIcon } from "@lume/icons";
import type { Settings } from "@lume/system"; import type { Settings } from "@lume/system";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import type { QueryClient } from "@tanstack/react-query"; import type { QueryClient } from "@tanstack/react-query";
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"; import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
import type { Platform } from "@tauri-apps/plugin-os"; import type { Platform } from "@tauri-apps/plugin-os";
import { Toaster } from "sonner";
interface RouterContext { interface RouterContext {
// System // System
@ -19,21 +17,7 @@ interface RouterContext {
} }
export const Route = createRootRouteWithContext<RouterContext>()({ export const Route = createRootRouteWithContext<RouterContext>()({
component: () => ( component: () => <Outlet />,
<>
<Toaster
position="bottom-right"
icons={{
success: <CheckCircleIcon className="size-5" />,
info: <InfoCircleIcon className="size-5" />,
error: <CancelCircleIcon className="size-5" />,
}}
closeButton
theme="system"
/>
<Outlet />
</>
),
pendingComponent: Pending, pendingComponent: Pending,
wrapInSuspense: true, wrapInSuspense: true,
}); });

View File

@ -5,9 +5,9 @@ import * as Checkbox from "@radix-ui/react-checkbox";
import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { writeText } from "@tauri-apps/plugin-clipboard-manager"; import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner";
export const Route = createFileRoute("/auth/$account/backup")({ export const Route = createFileRoute("/auth/$account/backup")({
component: Screen, component: Screen,
@ -29,7 +29,10 @@ function Screen() {
try { try {
if (key) { if (key) {
if (!confirm.c1 || !confirm.c2 || !confirm.c3) { if (!confirm.c1 || !confirm.c2 || !confirm.c3) {
return toast.warning("You need to confirm before continue"); return await message("You need to confirm before continue", {
title: "Backup",
kind: "info",
});
} }
navigate({ to: "/", replace: true }); navigate({ to: "/", replace: true });
@ -48,7 +51,10 @@ function Screen() {
}); });
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
toast.error(String(e)); await message(String(e), {
title: "Backup",
kind: "error",
});
} }
}; };
@ -57,7 +63,10 @@ function Screen() {
await writeText(key); await writeText(key);
setCopied(true); setCopied(true);
} catch (e) { } catch (e) {
toast.error(e); await message(String(e), {
title: "Backup",
kind: "error",
});
} }
}; };

View File

@ -4,10 +4,10 @@ import { NostrAccount } from "@lume/system";
import type { Metadata } from "@lume/types"; import type { Metadata } from "@lume/types";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner";
export const Route = createFileRoute("/auth/create-profile")({ export const Route = createFileRoute("/auth/create-profile")({
component: Screen, component: Screen,
@ -53,7 +53,7 @@ function Screen() {
} }
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
toast.error(String(e)); await message(String(e), { title: "Create Profile", kind: "error" });
} }
}; };

View File

@ -1,8 +1,8 @@
import { NostrAccount } from "@lume/system"; import { NostrAccount } from "@lume/system";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import { createLazyFileRoute } from "@tanstack/react-router"; import { createLazyFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner";
export const Route = createLazyFileRoute("/auth/import")({ export const Route = createLazyFileRoute("/auth/import")({
component: Screen, component: Screen,
@ -16,10 +16,12 @@ function Screen() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const submit = async () => { const submit = async () => {
if (!key.startsWith("nsec1")) if (!key.startsWith("nsec1")) {
return toast.warning( return await message(
"You need to enter a valid private key starts with nsec or ncryptsec", "You need to enter a valid private key starts with nsec or ncryptsec",
{ title: "Import Key", kind: "info" },
); );
}
try { try {
setLoading(true); setLoading(true);
@ -31,7 +33,7 @@ function Screen() {
} }
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
toast.error(e); await message(String(e), { title: "Import Key", kind: "error" });
} }
}; };

View File

@ -1,8 +1,8 @@
import { NostrAccount } from "@lume/system"; import { NostrAccount } from "@lume/system";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import { createLazyFileRoute } from "@tanstack/react-router"; import { createLazyFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner";
export const Route = createLazyFileRoute("/auth/remote")({ export const Route = createLazyFileRoute("/auth/remote")({
component: Screen, component: Screen,
@ -15,10 +15,12 @@ function Screen() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const submit = async () => { const submit = async () => {
if (!uri.startsWith("bunker://")) if (!uri.startsWith("bunker://")) {
return toast.warning( return await message(
"You need to enter a valid Connect URI starts with bunker://", "You need to enter a valid Connect URI starts with bunker://",
{ title: "Nostr Connect", kind: "info" },
); );
}
try { try {
setLoading(true); setLoading(true);
@ -30,7 +32,7 @@ function Screen() {
} }
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
toast.error(e); await message(String(e), { title: "Nostr Connect", kind: "error" });
} }
}; };

View File

@ -3,9 +3,9 @@ import { NostrQuery } from "@lume/system";
import type { Relay } from "@lume/types"; import type { Relay } from "@lume/types";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner";
export const Route = createFileRoute("/bootstrap-relays")({ export const Route = createFileRoute("/bootstrap-relays")({
loader: async () => { loader: async () => {
@ -32,7 +32,7 @@ function Screen() {
setRelays((prev) => [...prev, relay]); setRelays((prev) => [...prev, relay]);
reset(); reset();
} catch (e) { } catch (e) {
toast.error(String(e)); await message(String(e), { title: "Bootstrap Relays", kind: "error" });
} }
}; };
@ -41,8 +41,7 @@ function Screen() {
setIsLoading(true); setIsLoading(true);
await NostrQuery.saveBootstrapRelays(relays); await NostrQuery.saveBootstrapRelays(relays);
} catch (e) { } catch (e) {
setIsLoading(false); await message(String(e), { title: "Bootstrap Relays", kind: "error" });
toast.error(String(e));
} }
}; };

View File

@ -1,11 +1,11 @@
import { User } from "@/components/user";
import { CancelIcon, PlusIcon } from "@lume/icons"; import { CancelIcon, PlusIcon } from "@lume/icons";
import { NostrAccount, NostrQuery } from "@lume/system";
import type { ColumnRouteSearch } from "@lume/types"; import type { ColumnRouteSearch } from "@lume/types";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import { User } from "@/components/user";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner";
import { NostrAccount, NostrQuery } from "@lume/system";
export const Route = createFileRoute("/create-group")({ export const Route = createFileRoute("/create-group")({
validateSearch: (search: Record<string, string>): ColumnRouteSearch => { validateSearch: (search: Record<string, string>): ColumnRouteSearch => {
@ -65,25 +65,25 @@ function Screen() {
} }
} catch (e) { } catch (e) {
setIsLoading(false); setIsLoading(false);
toast.error(e); await message(String(e), { title: "Create Group", kind: "error" });
} }
}; };
return ( return (
<div className="w-full h-full flex flex-col items-center justify-center gap-4"> <div className="flex flex-col items-center justify-center w-full h-full gap-4">
<div className="text-center flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center text-center">
<h1 className="text-2xl font-serif font-medium"> <h1 className="font-serif text-2xl font-medium">
Focus feeds for people you like Focus feeds for people you like
</h1> </h1>
<p className="leading-tight text-neutral-700 dark:text-neutral-300"> <p className="leading-tight text-neutral-700 dark:text-neutral-300">
Add some people for custom feeds. Add some people for custom feeds.
</p> </p>
</div> </div>
<div className="w-4/5 max-w-full flex flex-col gap-3"> <div className="flex flex-col w-4/5 max-w-full gap-3">
<div className="w-full h-9 shrink-0 flex items-center bg-black/5 dark:bg-white/5 rounded-lg"> <div className="flex items-center w-full rounded-lg h-9 shrink-0 bg-black/5 dark:bg-white/5">
<label <label
htmlFor="name" htmlFor="name"
className="w-16 border-r border-black/10 dark:border-white/10 shrink-0 text-center text-sm font-semibold" className="w-16 text-sm font-semibold text-center border-r border-black/10 dark:border-white/10 shrink-0"
> >
Name Name
</label> </label>
@ -92,10 +92,10 @@ function Screen() {
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
placeholder="Enter a name for this group" placeholder="Enter a name for this group"
className="h-full bg-transparent border-none text-sm px-3 placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400" className="h-full px-3 text-sm bg-transparent border-none placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400"
/> />
</div> </div>
<div className="w-full flex flex-col items-center gap-3"> <div className="flex flex-col items-center w-full gap-3">
<div className="overflow-y-auto scrollbar-none p-2 w-full h-[450px] flex flex-col gap-3 bg-black/5 dark:bg-white/5 backdrop-blur-lg rounded-xl"> <div className="overflow-y-auto scrollbar-none p-2 w-full h-[450px] flex flex-col gap-3 bg-black/5 dark:bg-white/5 backdrop-blur-lg rounded-xl">
<div className="flex gap-2"> <div className="flex gap-2">
<input <input
@ -103,12 +103,12 @@ function Screen() {
value={npub} value={npub}
onChange={(e) => setNpub(e.target.value)} onChange={(e) => setNpub(e.target.value)}
placeholder="npub1..." placeholder="npub1..."
className="h-9 w-full rounded-lg bg-black/10 dark:bg-white/10 border-none text-sm px-3 placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400" className="w-full px-3 text-sm border-none rounded-lg h-9 bg-black/10 dark:bg-white/10 placeholder:text-neutral-600 focus:border-neutral-500 focus:ring-0 dark:placeholder:text-neutral-400"
/> />
<button <button
type="button" type="button"
onClick={() => addUser()} onClick={() => addUser()}
className="inline-flex size-9 rounded-lg items-center justify-center bg-black/20 dark:bg-white/20 shrink-0 text-white hover:bg-blue-500" className="inline-flex items-center justify-center text-white rounded-lg size-9 bg-black/20 dark:bg-white/20 shrink-0 hover:bg-blue-500"
> >
<PlusIcon className="size-6" /> <PlusIcon className="size-6" />
</button> </button>
@ -122,11 +122,11 @@ function Screen() {
key={item} key={item}
type="button" type="button"
onClick={() => toggleUser(item)} onClick={() => toggleUser(item)}
className="inline-flex items-center justify-between px-3 py-2 rounded-lg bg-white dark:bg-black/20 backdrop-blur-lg shadow-primary dark:ring-1 ring-neutral-800/50" className="inline-flex items-center justify-between px-3 py-2 bg-white rounded-lg dark:bg-black/20 backdrop-blur-lg shadow-primary dark:ring-1 ring-neutral-800/50"
> >
<User.Provider pubkey={item}> <User.Provider pubkey={item}>
<User.Root className="flex items-center gap-2.5"> <User.Root className="flex items-center gap-2.5">
<User.Avatar className="size-8 rounded-full object-cover" /> <User.Avatar className="object-cover rounded-full size-8" />
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<User.Name className="text-sm font-medium" /> <User.Name className="text-sm font-medium" />
</div> </div>
@ -138,7 +138,7 @@ function Screen() {
</button> </button>
)) ))
) : ( ) : (
<div className="bg-black/5 dark:bg-white/5 text-sm flex items-center justify-center h-14 rounded-lg"> <div className="flex items-center justify-center text-sm rounded-lg bg-black/5 dark:bg-white/5 h-14">
Empty. Empty.
</div> </div>
)} )}
@ -153,11 +153,11 @@ function Screen() {
key={item} key={item}
type="button" type="button"
onClick={() => toggleUser(item)} onClick={() => toggleUser(item)}
className="inline-flex items-center justify-between px-3 py-2 rounded-lg bg-white dark:bg-black/20 backdrop-blur-lg shadow-primary dark:ring-1 ring-neutral-800/50" className="inline-flex items-center justify-between px-3 py-2 bg-white rounded-lg dark:bg-black/20 backdrop-blur-lg shadow-primary dark:ring-1 ring-neutral-800/50"
> >
<User.Provider pubkey={item}> <User.Provider pubkey={item}>
<User.Root className="flex items-center gap-2.5"> <User.Root className="flex items-center gap-2.5">
<User.Avatar className="size-8 rounded-full object-cover" /> <User.Avatar className="object-cover rounded-full size-8" />
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<User.Name className="text-sm font-medium" /> <User.Name className="text-sm font-medium" />
</div> </div>
@ -166,7 +166,7 @@ function Screen() {
</button> </button>
)) ))
) : ( ) : (
<div className="bg-black/5 dark:bg-white/5 text-sm flex items-center justify-center h-14 rounded-lg"> <div className="flex items-center justify-center text-sm rounded-lg bg-black/5 dark:bg-white/5 h-14">
<p> <p>
Find more user at{" "} Find more user at{" "}
<a <a
@ -187,7 +187,7 @@ function Screen() {
type="button" type="button"
onClick={() => submit()} onClick={() => submit()}
disabled={isLoading || users.length < 1} disabled={isLoading || users.length < 1}
className="inline-flex items-center justify-center w-36 rounded-full h-9 bg-blue-500 text-white text-sm font-medium hover:bg-blue-600 disabled:opacity-50" className="inline-flex items-center justify-center text-sm font-medium text-white bg-blue-500 rounded-full w-36 h-9 hover:bg-blue-600 disabled:opacity-50"
> >
{isLoading ? <Spinner /> : "Confirm"} {isLoading ? <Spinner /> : "Confirm"}
</button> </button>

View File

@ -2,8 +2,8 @@ import { NostrAccount } from "@lume/system";
import type { ColumnRouteSearch } from "@lume/types"; import type { ColumnRouteSearch } from "@lume/types";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner";
export const Route = createFileRoute("/create-newsfeed/f2f")({ export const Route = createFileRoute("/create-newsfeed/f2f")({
validateSearch: (search: Record<string, string>): ColumnRouteSearch => { validateSearch: (search: Record<string, string>): ColumnRouteSearch => {
@ -24,8 +24,12 @@ function Screen() {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const submit = async () => { const submit = async () => {
if (!npub.startsWith("npub1")) if (!npub.startsWith("npub1")) {
return toast.warning("You must enter a valid npub."); return await message("You must enter a valid npub.", {
title: "Create Newsfeed",
kind: "info",
});
}
try { try {
setIsLoading(true); setIsLoading(true);
@ -37,13 +41,16 @@ function Screen() {
} }
} catch (e) { } catch (e) {
setIsLoading(false); setIsLoading(false);
toast.error(String(e)); await message(String(e), {
title: "Create Newsfeed",
kind: "error",
});
} }
}; };
return ( return (
<div className="overflow-y-auto scrollbar-none p-2 shrink-0 h-[450px] bg-white dark:bg-white/20 backdrop-blur-lg rounded-xl shadow-primary dark:ring-1 ring-neutral-800/50"> <div className="overflow-y-auto scrollbar-none p-2 shrink-0 h-[450px] bg-white dark:bg-white/20 backdrop-blur-lg rounded-xl shadow-primary dark:ring-1 ring-neutral-800/50">
<div className="h-full flex flex-col justify-between"> <div className="flex flex-col justify-between h-full">
<div className="flex-1 flex flex-col gap-1.5 justify-center px-5"> <div className="flex-1 flex flex-col gap-1.5 justify-center px-5">
<p className="font-semibold text-neutral-500"> <p className="font-semibold text-neutral-500">
You already have a friend on Nostr? You already have a friend on Nostr?
@ -60,7 +67,7 @@ function Screen() {
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<label htmlFor="npub" className="font-medium text-sm"> <label htmlFor="npub" className="text-sm font-medium">
NPUB NPUB
</label> </label>
<input <input
@ -69,13 +76,13 @@ function Screen() {
value={npub} value={npub}
onChange={(e) => setNpub(e.target.value)} onChange={(e) => setNpub(e.target.value)}
spellCheck={false} spellCheck={false}
className="h-11 rounded-lg bg-transparent border border-neutral-200 dark:border-neutral-800 px-3 placeholder:text-neutral-600 focus:border-blue-500 focus:ring-0 dark:placeholder:text-neutral-400" className="px-3 bg-transparent border rounded-lg h-11 border-neutral-200 dark:border-neutral-800 placeholder:text-neutral-600 focus:border-blue-500 focus:ring-0 dark:placeholder:text-neutral-400"
/> />
</div> </div>
<button <button
type="button" type="button"
onClick={() => submit()} onClick={() => submit()}
className="inline-flex items-center justify-center w-full rounded-lg h-9 bg-blue-500 text-white text-sm font-medium hover:bg-blue-600" className="inline-flex items-center justify-center w-full text-sm font-medium text-white bg-blue-500 rounded-lg h-9 hover:bg-blue-600"
> >
{isLoading ? <Spinner /> : "Confirm"} {isLoading ? <Spinner /> : "Confirm"}
</button> </button>

View File

@ -1,11 +1,11 @@
import { createFileRoute } from "@tanstack/react-router";
import { Suspense, useState } from "react";
import { Await, defer } from "@tanstack/react-router";
import { User } from "@/components/user"; import { User } from "@/components/user";
import { Spinner } from "@lume/ui";
import { toast } from "sonner";
import type { ColumnRouteSearch } from "@lume/types";
import { NostrAccount } from "@lume/system"; import { NostrAccount } from "@lume/system";
import type { ColumnRouteSearch } from "@lume/types";
import { Spinner } from "@lume/ui";
import { createFileRoute } from "@tanstack/react-router";
import { Await, defer } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { Suspense, useState } from "react";
export const Route = createFileRoute("/create-newsfeed/users")({ export const Route = createFileRoute("/create-newsfeed/users")({
validateSearch: (search: Record<string, string>): ColumnRouteSearch => { validateSearch: (search: Record<string, string>): ColumnRouteSearch => {
@ -59,16 +59,19 @@ function Screen() {
} }
} catch (e) { } catch (e) {
setIsLoading(false); setIsLoading(false);
toast.error(String(e)); await message(String(e), {
title: "Create Group",
kind: "error",
});
} }
}; };
return ( return (
<div className="w-full flex flex-col items-center gap-3"> <div className="flex flex-col items-center w-full gap-3">
<div className="overflow-y-auto scrollbar-none p-2 w-full h-[450px] bg-black/5 dark:bg-white/5 backdrop-blur-lg rounded-xl"> <div className="overflow-y-auto scrollbar-none p-2 w-full h-[450px] bg-black/5 dark:bg-white/5 backdrop-blur-lg rounded-xl">
<Suspense <Suspense
fallback={ fallback={
<div className="flex h-20 w-full flex-col items-center justify-center gap-1"> <div className="flex flex-col items-center justify-center w-full h-20 gap-1">
<button <button
type="button" type="button"
className="inline-flex items-center gap-2 text-sm font-medium" className="inline-flex items-center gap-2 text-sm font-medium"
@ -85,27 +88,27 @@ function Screen() {
users.profiles.map((item: { pubkey: string }) => ( users.profiles.map((item: { pubkey: string }) => (
<div <div
key={item.pubkey} key={item.pubkey}
className="h-max w-full overflow-hidden mb-2 p-2 bg-white dark:bg-black/20 backdrop-blur-lg rounded-lg shadow-primary dark:ring-1 ring-neutral-800/50" className="w-full p-2 mb-2 overflow-hidden bg-white rounded-lg h-max dark:bg-black/20 backdrop-blur-lg shadow-primary dark:ring-1 ring-neutral-800/50"
> >
<User.Provider pubkey={item.pubkey}> <User.Provider pubkey={item.pubkey}>
<User.Root> <User.Root>
<div className="flex h-full w-full flex-col gap-2"> <div className="flex flex-col w-full h-full gap-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<User.Avatar className="size-7 shrink-0 rounded-full object-cover" /> <User.Avatar className="object-cover rounded-full size-7 shrink-0" />
<User.Name className="text-sm leadning-tight max-w-[15rem] truncate font-semibold" /> <User.Name className="text-sm leadning-tight max-w-[15rem] truncate font-semibold" />
</div> </div>
<button <button
type="button" type="button"
onClick={() => toggleFollow(item.pubkey)} onClick={() => toggleFollow(item.pubkey)}
className="inline-flex h-7 w-20 items-center justify-center rounded-lg bg-black/10 text-sm font-medium hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20" className="inline-flex items-center justify-center w-20 text-sm font-medium rounded-lg h-7 bg-black/10 hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20"
> >
{follows.includes(item.pubkey) {follows.includes(item.pubkey)
? "Unfollow" ? "Unfollow"
: "Follow"} : "Follow"}
</button> </button>
</div> </div>
<User.About className="line-clamp-3 max-w-none select-text text-neutral-800 dark:text-neutral-400" /> <User.About className="select-text line-clamp-3 max-w-none text-neutral-800 dark:text-neutral-400" />
</div> </div>
</User.Root> </User.Root>
</User.Provider> </User.Provider>
@ -119,7 +122,7 @@ function Screen() {
type="button" type="button"
onClick={() => submit()} onClick={() => submit()}
disabled={isLoading || follows.length < 1} disabled={isLoading || follows.length < 1}
className="inline-flex items-center justify-center w-36 rounded-full h-9 bg-blue-500 text-white text-sm font-medium hover:bg-blue-600 disabled:opacity-50" className="inline-flex items-center justify-center text-sm font-medium text-white bg-blue-500 rounded-full w-36 h-9 hover:bg-blue-600 disabled:opacity-50"
> >
{isLoading ? <Spinner /> : "Confirm"} {isLoading ? <Spinner /> : "Confirm"}
</button> </button>

View File

@ -4,8 +4,8 @@ import type { ColumnRouteSearch } from "@lume/types";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import { TOPICS } from "@lume/utils"; import { TOPICS } from "@lume/utils";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner";
type Topic = { type Topic = {
title: string; title: string;
@ -53,7 +53,10 @@ function Screen() {
} }
} catch (e) { } catch (e) {
setIsLoading(false); setIsLoading(false);
toast.error(String(e)); await message(String(e), {
title: "Create Topic",
kind: "error",
});
} }
}; };

View File

@ -4,9 +4,9 @@ import { Spinner } from "@lume/ui";
import { insertImage, isImagePath } from "@lume/utils"; import { insertImage, isImagePath } from "@lume/utils";
import type { UnlistenFn } from "@tauri-apps/api/event"; import type { UnlistenFn } from "@tauri-apps/api/event";
import { getCurrent } from "@tauri-apps/api/window"; import { getCurrent } from "@tauri-apps/api/window";
import { message } from "@tauri-apps/plugin-dialog";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useSlateStatic } from "slate-react"; import { useSlateStatic } from "slate-react";
import { toast } from "sonner";
export function MediaButton() { export function MediaButton() {
const editor = useSlateStatic(); const editor = useSlateStatic();
@ -24,7 +24,7 @@ export function MediaButton() {
setLoading(false); setLoading(false);
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
toast.error(`Upload failed, error: ${e}`); await message(String(e), { title: "Upload", kind: "error" });
} }
}; };

View File

@ -1,12 +1,12 @@
import { PlusIcon, RelayIcon } from "@lume/icons";
import { Spinner } from "@lume/ui";
import { User } from "@/components/user"; import { User } from "@/components/user";
import { PlusIcon, RelayIcon } from "@lume/icons";
import { NostrAccount } from "@lume/system";
import { Spinner } from "@lume/ui";
import { checkForAppUpdates, displayNpub } from "@lume/utils"; import { checkForAppUpdates, displayNpub } from "@lume/utils";
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import { createFileRoute, redirect } from "@tanstack/react-router"; import { createFileRoute, redirect } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner";
import { NostrAccount } from "@lume/system";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
beforeLoad: async () => { beforeLoad: async () => {
@ -51,7 +51,10 @@ function Screen() {
} }
} catch (e) { } catch (e) {
setLoading({ npub: "", status: false }); setLoading({ npub: "", status: false });
toast.error(String(e)); await message(String(e), {
title: "Account",
kind: "error",
});
} }
}; };

View File

@ -1,13 +1,13 @@
import { SearchIcon } from "@lume/icons";
import { type NostrEvent, Kind } from "@lume/types";
import { Spinner } from "@lume/ui";
import { Note } from "@/components/note"; import { Note } from "@/components/note";
import { User } from "@/components/user"; import { User } from "@/components/user";
import { createFileRoute } from "@tanstack/react-router"; import { SearchIcon } from "@lume/icons";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { useDebounce } from "use-debounce";
import { LumeEvent, LumeWindow } from "@lume/system"; import { LumeEvent, LumeWindow } from "@lume/system";
import { Kind, type NostrEvent } from "@lume/types";
import { Spinner } from "@lume/ui";
import { createFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useEffect, useState } from "react";
import { useDebounce } from "use-debounce";
export const Route = createFileRoute("/search")({ export const Route = createFileRoute("/search")({
component: Screen, component: Screen,
@ -34,7 +34,10 @@ function Screen() {
setEvents(sorted); setEvents(sorted);
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
toast.error(String(e)); await message(String(e), {
title: "Search",
kind: "error",
});
} }
}; };

View File

@ -1,11 +1,11 @@
import { User } from "@/components/user"; import { User } from "@/components/user";
import { NostrAccount } from "@lume/system"; import { NostrAccount } from "@lume/system";
import { displayNpub, displayNsec } from "@lume/utils"; import { displayNpub } from "@lume/utils";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { writeText } from "@tauri-apps/plugin-clipboard-manager"; import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner";
interface Account { interface Account {
npub: string; npub: string;
@ -43,7 +43,7 @@ function Account({ account }: { account: string }) {
await writeText(data); await writeText(data);
setCopied(true); setCopied(true);
} catch (e) { } catch (e) {
toast.error(e); await message(String(e), { title: "Backup", kind: "error" });
} }
}; };

View File

@ -1,9 +1,9 @@
import { CancelIcon, PlusIcon } from "@lume/icons"; import { CancelIcon, PlusIcon } from "@lume/icons";
import { NostrQuery } from "@lume/system"; import { NostrQuery } from "@lume/system";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner";
export const Route = createFileRoute("/settings/relay")({ export const Route = createFileRoute("/settings/relay")({
loader: async () => { loader: async () => {
@ -33,7 +33,7 @@ function Screen() {
} }
} catch (e) { } catch (e) {
setIsLoading(false); setIsLoading(false);
toast.error(String(e)); await message(String(e), { title: "Relay", kind: "error" });
} }
}; };
@ -42,22 +42,22 @@ function Screen() {
}, [relayList]); }, [relayList]);
return ( return (
<div className="mx-auto w-full max-w-xl"> <div className="w-full max-w-xl mx-auto">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h2 className="font-semibold text-sm text-neutral-700 dark:text-neutral-300"> <h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
Connected Relays Connected Relays
</h2> </h2>
<div className="flex flex-col divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl px-3"> <div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
{relays.map((relay) => ( {relays.map((relay) => (
<div <div
key={relay} key={relay}
className="flex justify-between items-center h-11" className="flex items-center justify-between h-11"
> >
<div className="inline-flex items-center gap-2 text-sm font-medium"> <div className="inline-flex items-center gap-2 text-sm font-medium">
<span className="relative flex size-2"> <span className="relative flex size-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-teal-400 opacity-75"></span> <span className="absolute inline-flex w-full h-full bg-teal-400 rounded-full opacity-75 animate-ping" />
<span className="relative inline-flex rounded-full size-2 bg-teal-500"></span> <span className="relative inline-flex bg-teal-500 rounded-full size-2" />
</span> </span>
{relay} {relay}
</div> </div>
@ -65,7 +65,7 @@ function Screen() {
<button <button
type="button" type="button"
onClick={() => NostrQuery.removeRelay(relay)} onClick={() => NostrQuery.removeRelay(relay)}
className="inline-flex items-center justify-center size-7 rounded-md hover:bg-black/10 dark:hover:bg-white/10" className="inline-flex items-center justify-center rounded-md size-7 hover:bg-black/10 dark:hover:bg-white/10"
> >
<CancelIcon className="size-4" /> <CancelIcon className="size-4" />
</button> </button>
@ -75,7 +75,7 @@ function Screen() {
<div className="flex items-center h-14"> <div className="flex items-center h-14">
<form <form
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
className="w-full flex items-center gap-2 mb-0" className="flex items-center w-full gap-2 mb-0"
> >
<input <input
{...register("url", { {...register("url", {
@ -85,12 +85,12 @@ function Screen() {
name="url" name="url"
placeholder="wss://..." placeholder="wss://..."
spellCheck={false} spellCheck={false}
className="h-9 flex-1 rounded-lg border-neutral-300 bg-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring-0 dark:border-neutral-700 dark:placeholder:text-neutral-400" className="flex-1 px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring-0 dark:border-neutral-700 dark:placeholder:text-neutral-400"
/> />
<button <button
type="submit" type="submit"
disabled={isLoading} disabled={isLoading}
className="shrink-0 inline-flex h-9 w-16 px-2 items-center justify-center rounded-lg bg-black/20 dark:bg-white/20 font-medium text-sm text-white hover:bg-blue-500 disabled:opacity-50" className="inline-flex items-center justify-center w-16 px-2 text-sm font-medium text-white rounded-lg shrink-0 h-9 bg-black/20 dark:bg-white/20 hover:bg-blue-500 disabled:opacity-50"
> >
<PlusIcon className="size-7" /> <PlusIcon className="size-7" />
</button> </button>
@ -99,21 +99,21 @@ function Screen() {
</div> </div>
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h2 className="font-semibold text-sm text-neutral-700 dark:text-neutral-300"> <h2 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300">
User Relays (NIP-65) User Relays (NIP-65)
</h2> </h2>
<div className="flex flex-col py-2 bg-black/5 dark:bg-white/5 rounded-xl px-3"> <div className="flex flex-col px-3 py-2 bg-black/5 dark:bg-white/5 rounded-xl">
<p className="text-sm text-yellow-500"> <p className="text-sm text-yellow-500">
Lume will automatically connect to the user's relay list, but the Lume will automatically connect to the user's relay list, but the
manager function (like adding, removing, changing relay purpose) manager function (like adding, removing, changing relay purpose)
is not yet available. is not yet available.
</p> </p>
</div> </div>
<div className="flex flex-col divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl px-3"> <div className="flex flex-col px-3 divide-y divide-black/10 dark:divide-white/10 bg-black/5 dark:bg-white/5 rounded-xl">
{relayList.read?.map((relay) => ( {relayList.read?.map((relay) => (
<div <div
key={relay} key={relay}
className="flex justify-between items-center h-11" className="flex items-center justify-between h-11"
> >
<div className="text-sm font-medium">{relay}</div> <div className="text-sm font-medium">{relay}</div>
<div className="text-xs font-semibold">READ</div> <div className="text-xs font-semibold">READ</div>
@ -122,7 +122,7 @@ function Screen() {
{relayList.write?.map((relay) => ( {relayList.write?.map((relay) => (
<div <div
key={relay} key={relay}
className="flex justify-between items-center h-11" className="flex items-center justify-between h-11"
> >
<div className="text-sm font-medium">{relay}</div> <div className="text-sm font-medium">{relay}</div>
<div className="text-xs font-semibold">WRITE</div> <div className="text-xs font-semibold">WRITE</div>
@ -131,7 +131,7 @@ function Screen() {
{relayList.both?.map((relay) => ( {relayList.both?.map((relay) => (
<div <div
key={relay} key={relay}
className="flex justify-between items-center h-11" className="flex items-center justify-between h-11"
> >
<div className="text-sm font-medium">{relay}</div> <div className="text-sm font-medium">{relay}</div>
<div className="text-xs font-semibold">READ + WRITE</div> <div className="text-xs font-semibold">READ + WRITE</div>

View File

@ -5,9 +5,9 @@ import type { Metadata } from "@lume/types";
import { Spinner } from "@lume/ui"; import { Spinner } from "@lume/ui";
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner";
export const Route = createFileRoute("/settings/user")({ export const Route = createFileRoute("/settings/user")({
beforeLoad: async () => { beforeLoad: async () => {
@ -34,31 +34,31 @@ function Screen() {
setLoading(false); setLoading(false);
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
toast.error(String(e)); await message(String(e), { title: "Profile", kind: "error" });
} }
}; };
return ( return (
<div className="flex w-full h-full"> <div className="flex w-full h-full">
<div className="flex-1 h-full flex items-center flex-col justify-center gap-3"> <div className="flex flex-col items-center justify-center flex-1 h-full gap-3">
<div className="relative size-24 rounded-full bg-gradient-to-tr from-orange-100 via-red-50 to-blue-200"> <div className="relative rounded-full size-24 bg-gradient-to-tr from-orange-100 via-red-50 to-blue-200">
{profile.picture ? ( {profile.picture ? (
<img <img
src={picture || profile.picture} src={picture || profile.picture}
alt="avatar" alt="avatar"
loading="lazy" loading="lazy"
decoding="async" decoding="async"
className="absolute inset-0 z-10 h-full w-full rounded-full object-cover" className="absolute inset-0 z-10 object-cover w-full h-full rounded-full"
/> />
) : null} ) : null}
<AvatarUploader <AvatarUploader
setPicture={setPicture} setPicture={setPicture}
className="absolute inset-0 z-20 flex h-full w-full items-center justify-center rounded-full bg-black/10 text-white hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20" className="absolute inset-0 z-20 flex items-center justify-center w-full h-full text-white rounded-full bg-black/10 hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20"
> >
<PlusIcon className="size-8" /> <PlusIcon className="size-8" />
</AvatarUploader> </AvatarUploader>
</div> </div>
<div className="text-center flex flex-col items-center"> <div className="flex flex-col items-center text-center">
<div className="text-lg font-semibold">{profile.display_name}</div> <div className="text-lg font-semibold">{profile.display_name}</div>
<div className="text-neutral-800 dark:text-neutral-200"> <div className="text-neutral-800 dark:text-neutral-200">
{profile.nip05} {profile.nip05}
@ -66,7 +66,7 @@ function Screen() {
<div className="mt-4"> <div className="mt-4">
<Link <Link
to="/settings/backup" to="/settings/backup"
className="px-5 h-9 border border-blue-300 text-sm font-medium hover:bg-blue-200 dark:bg-blue-900 dark:hover:bg-blue-800 rounded-full bg-blue-100 text-blue-500 inline-flex items-center justify-center" className="inline-flex items-center justify-center px-5 text-sm font-medium text-blue-500 bg-blue-100 border border-blue-300 rounded-full h-9 hover:bg-blue-200 dark:bg-blue-900 dark:hover:bg-blue-800"
> >
Backup Account Backup Account
</Link> </Link>
@ -78,7 +78,7 @@ function Screen() {
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-3 mb-0" className="flex flex-col gap-3 mb-0"
> >
<div className="flex w-full flex-col gap-1"> <div className="flex flex-col w-full gap-1">
<label <label
htmlFor="display_name" htmlFor="display_name"
className="text-sm font-medium text-neutral-700 dark:text-neutral-300" className="text-sm font-medium text-neutral-700 dark:text-neutral-300"
@ -89,10 +89,10 @@ function Screen() {
name="display_name" name="display_name"
{...register("display_name")} {...register("display_name")}
spellCheck={false} spellCheck={false}
className="h-9 w-full rounded-lg border-neutral-300 bg-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" className="w-full px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/> />
</div> </div>
<div className="flex w-full flex-col gap-1"> <div className="flex flex-col w-full gap-1">
<label <label
htmlFor="name" htmlFor="name"
className="text-sm font-medium text-neutral-700 dark:text-neutral-300" className="text-sm font-medium text-neutral-700 dark:text-neutral-300"
@ -103,10 +103,10 @@ function Screen() {
name="name" name="name"
{...register("name")} {...register("name")}
spellCheck={false} spellCheck={false}
className="h-9 w-full rounded-lg border-neutral-300 bg-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" className="w-full px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/> />
</div> </div>
<div className="flex w-full flex-col gap-1"> <div className="flex flex-col w-full gap-1">
<label <label
htmlFor="website" htmlFor="website"
className="text-sm font-medium text-neutral-700 dark:text-neutral-300" className="text-sm font-medium text-neutral-700 dark:text-neutral-300"
@ -118,10 +118,10 @@ function Screen() {
type="url" type="url"
{...register("website")} {...register("website")}
spellCheck={false} spellCheck={false}
className="h-9 w-full rounded-lg border-neutral-300 bg-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" className="w-full px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/> />
</div> </div>
<div className="flex w-full flex-col gap-1"> <div className="flex flex-col w-full gap-1">
<label <label
htmlFor="banner" htmlFor="banner"
className="text-sm font-medium text-neutral-700 dark:text-neutral-300" className="text-sm font-medium text-neutral-700 dark:text-neutral-300"
@ -133,10 +133,10 @@ function Screen() {
type="url" type="url"
{...register("banner")} {...register("banner")}
spellCheck={false} spellCheck={false}
className="h-9 w-full rounded-lg border-neutral-300 bg-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" className="w-full px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/> />
</div> </div>
<div className="flex w-full flex-col gap-1"> <div className="flex flex-col w-full gap-1">
<label <label
htmlFor="nip05" htmlFor="nip05"
className="text-sm font-medium text-neutral-700 dark:text-neutral-300" className="text-sm font-medium text-neutral-700 dark:text-neutral-300"
@ -148,10 +148,10 @@ function Screen() {
type="email" type="email"
{...register("nip05")} {...register("nip05")}
spellCheck={false} spellCheck={false}
className="h-9 w-full rounded-lg border-neutral-300 bg-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" className="w-full px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/> />
</div> </div>
<div className="flex w-full flex-col gap-1"> <div className="flex flex-col w-full gap-1">
<label <label
htmlFor="lnaddress" htmlFor="lnaddress"
className="text-sm font-medium text-neutral-700 dark:text-neutral-300" className="text-sm font-medium text-neutral-700 dark:text-neutral-300"
@ -162,13 +162,13 @@ function Screen() {
name="lnaddress" name="lnaddress"
type="email" type="email"
{...register("lud16")} {...register("lud16")}
className="h-9 w-full rounded-lg border-neutral-300 bg-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" className="w-full px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/> />
</div> </div>
<div className="flex items-center justify-end"> <div className="flex items-center justify-end">
<button <button
type="submit" type="submit"
className="inline-flex h-9 w-32 px-2 items-center justify-center rounded-lg bg-blue-500 font-medium text-sm text-white hover:bg-blue-600 disabled:opacity-50" className="inline-flex items-center justify-center w-32 px-2 text-sm font-medium text-white bg-blue-500 rounded-lg h-9 hover:bg-blue-600 disabled:opacity-50"
> >
{loading ? <Spinner className="size-4" /> : "Update Profile"} {loading ? <Spinner className="size-4" /> : "Update Profile"}
</button> </button>

View File

@ -1,7 +1,7 @@
import { createLazyFileRoute } from "@tanstack/react-router"; import { createLazyFileRoute } from "@tanstack/react-router";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner";
export const Route = createLazyFileRoute("/settings/zap")({ export const Route = createLazyFileRoute("/settings/zap")({
component: Screen, component: Screen,
@ -9,7 +9,7 @@ export const Route = createLazyFileRoute("/settings/zap")({
function Screen() { function Screen() {
return ( return (
<div className="mx-auto w-full max-w-xl"> <div className="w-full max-w-xl mx-auto">
<div className="flex flex-col gap-3 divide-y divide-neutral-300 dark:divide-neutral-700"> <div className="flex flex-col gap-3 divide-y divide-neutral-300 dark:divide-neutral-700">
<div className="flex flex-col gap-6 py-3"> <div className="flex flex-col gap-6 py-3">
<Connection /> <Connection />
@ -27,17 +27,17 @@ function Connection() {
try { try {
await invoke("set_nwc", { uri }); await invoke("set_nwc", { uri });
} catch (e) { } catch (e) {
toast.error(String(e)); await message(String(e), { title: "Zap", kind: "info" });
} }
}; };
return ( return (
<div className="flex items-start gap-6"> <div className="flex items-start gap-6">
<div className="w-36 shrink-0 text-end font-medium text-sm"> <div className="text-sm font-medium w-36 shrink-0 text-end">
Connection Connection
</div> </div>
<div className="flex-1"> <div className="flex-1">
<div className="flex w-full flex-col gap-1"> <div className="flex flex-col w-full gap-1">
<label <label
htmlFor="nwc" htmlFor="nwc"
className="text-sm font-medium text-neutral-700 dark:text-neutral-300" className="text-sm font-medium text-neutral-700 dark:text-neutral-300"
@ -51,12 +51,12 @@ function Connection() {
value={uri} value={uri}
onChange={(e) => setUri(e.target.value)} onChange={(e) => setUri(e.target.value)}
placeholder="nostrconnect://" placeholder="nostrconnect://"
className="h-9 w-full rounded-lg border-neutral-300 bg-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" className="w-full px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/> />
<button <button
type="button" type="button"
onClick={() => connect()} onClick={() => connect()}
className="inline-flex h-9 w-24 items-center justify-center rounded-lg bg-neutral-200 text-sm font-medium hover:bg-neutral-300 dark:bg-neutral-900 dark:hover:bg-neutral-700" className="inline-flex items-center justify-center w-24 text-sm font-medium rounded-lg h-9 bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-900 dark:hover:bg-neutral-700"
> >
Connect Connect
</button> </button>
@ -70,11 +70,11 @@ function Connection() {
function DefaultAmount() { function DefaultAmount() {
return ( return (
<div className="flex items-start gap-6"> <div className="flex items-start gap-6">
<div className="w-36 shrink-0 text-end font-medium text-sm"> <div className="text-sm font-medium w-36 shrink-0 text-end">
Default amount Default amount
</div> </div>
<div className="flex-1"> <div className="flex-1">
<div className="flex w-full flex-col gap-1"> <div className="flex flex-col w-full gap-1">
<label <label
htmlFor="amount" htmlFor="amount"
className="text-sm font-medium text-neutral-700 dark:text-neutral-300" className="text-sm font-medium text-neutral-700 dark:text-neutral-300"
@ -86,11 +86,11 @@ function DefaultAmount() {
name="amount" name="amount"
type="number" type="number"
value={21} value={21}
className="h-9 w-full rounded-lg border-neutral-300 bg-transparent px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" className="w-full px-3 bg-transparent rounded-lg h-9 border-neutral-300 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:border-neutral-700 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
/> />
<button <button
type="button" type="button"
className="inline-flex h-9 w-24 items-center justify-center rounded-lg bg-neutral-200 text-sm font-medium hover:bg-neutral-300 dark:bg-neutral-900 dark:hover:bg-neutral-700" className="inline-flex items-center justify-center w-24 text-sm font-medium rounded-lg h-9 bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-900 dark:hover:bg-neutral-700"
> >
Update Update
</button> </button>

View File

@ -1,13 +1,13 @@
import { Balance } from "@/components/balance"; import { Balance } from "@/components/balance";
import { Box, Container } from "@lume/ui";
import { User } from "@/components/user"; import { User } from "@/components/user";
import { LumeEvent } from "@lume/system";
import { Box, Container } from "@lume/ui";
import { createLazyFileRoute } from "@tanstack/react-router"; import { createLazyFileRoute } from "@tanstack/react-router";
import { getCurrent } from "@tauri-apps/api/webviewWindow"; import { getCurrent } from "@tauri-apps/api/webviewWindow";
import { message } from "@tauri-apps/plugin-dialog";
import { useState } from "react"; import { useState } from "react";
import CurrencyInput from "react-currency-input-field"; import CurrencyInput from "react-currency-input-field";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { LumeEvent } from "@lume/system";
const DEFAULT_VALUES = [69, 100, 200, 500]; const DEFAULT_VALUES = [69, 100, 200, 500];
@ -22,7 +22,7 @@ function Screen() {
const { pubkey, account } = Route.useSearch(); const { pubkey, account } = Route.useSearch();
const [amount, setAmount] = useState(21); const [amount, setAmount] = useState(21);
const [message, setMessage] = useState(""); const [content, setContent] = useState("");
const [isCompleted, setIsCompleted] = useState(false); const [isCompleted, setIsCompleted] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -31,7 +31,7 @@ function Screen() {
// start loading // start loading
setIsLoading(true); setIsLoading(true);
const val = await LumeEvent.zap(id, amount, message); const val = await LumeEvent.zap(id, amount, content);
if (val) { if (val) {
setIsCompleted(true); setIsCompleted(true);
@ -41,7 +41,10 @@ function Screen() {
} }
} catch (e) { } catch (e) {
setIsLoading(false); setIsLoading(false);
toast.error(e); await message(String(e), {
title: "Zap",
kind: "error",
});
} }
}; };
@ -49,19 +52,19 @@ function Screen() {
<Container> <Container>
<Balance account={account} /> <Balance account={account} />
<Box className="flex flex-col gap-3"> <Box className="flex flex-col gap-3">
<div className="flex h-full flex-col justify-between py-5"> <div className="flex flex-col justify-between h-full py-5">
<div className="flex h-11 shrink-0 items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2 h-11 shrink-0">
{t("note.zap.modalTitle")}{" "} {t("note.zap.modalTitle")}{" "}
<User.Provider pubkey={pubkey}> <User.Provider pubkey={pubkey}>
<User.Root className="inline-flex items-center gap-2 rounded-full bg-neutral-100 p-1 dark:bg-neutral-900"> <User.Root className="inline-flex items-center gap-2 p-1 rounded-full bg-neutral-100 dark:bg-neutral-900">
<User.Avatar className="size-6 rounded-full" /> <User.Avatar className="rounded-full size-6" />
<User.Name className="pr-2 text-sm font-medium" /> <User.Name className="pr-2 text-sm font-medium" />
</User.Root> </User.Root>
</User.Provider> </User.Provider>
</div> </div>
<div className="flex flex-1 flex-col justify-between px-5"> <div className="flex flex-col justify-between flex-1 px-5">
<div className="relative flex flex-1 flex-col pb-8"> <div className="relative flex flex-col flex-1 pb-8">
<div className="inline-flex h-full flex-1 items-center justify-center gap-1"> <div className="inline-flex items-center justify-center flex-1 h-full gap-1">
<CurrencyInput <CurrencyInput
placeholder="0" placeholder="0"
defaultValue={21} defaultValue={21}
@ -71,9 +74,9 @@ function Screen() {
max={10000} // 1M sats max={10000} // 1M sats
maxLength={10000} // 1M sats maxLength={10000} // 1M sats
onValueChange={(value) => setAmount(Number(value))} onValueChange={(value) => setAmount(Number(value))}
className="w-full flex-1 border-none bg-transparent text-right text-4xl font-semibold placeholder:text-neutral-600 focus:outline-none focus:ring-0 dark:text-neutral-400" className="flex-1 w-full text-4xl font-semibold text-right bg-transparent border-none placeholder:text-neutral-600 focus:outline-none focus:ring-0 dark:text-neutral-400"
/> />
<span className="w-full flex-1 text-left text-4xl font-semibold text-neutral-500 dark:text-neutral-400"> <span className="flex-1 w-full text-4xl font-semibold text-left text-neutral-500 dark:text-neutral-400">
sats sats
</span> </span>
</div> </div>
@ -90,11 +93,11 @@ function Screen() {
))} ))}
</div> </div>
</div> </div>
<div className="flex w-full flex-col gap-2"> <div className="flex flex-col w-full gap-2">
<input <input
name="message" name="message"
value={message} value={content}
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setContent(e.target.value)}
spellCheck={false} spellCheck={false}
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"

194
pnpm-lock.yaml generated
View File

@ -72,18 +72,6 @@ importers:
'@radix-ui/react-checkbox': '@radix-ui/react-checkbox':
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) version: 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-collapsible':
specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-dialog':
specifier: ^1.0.5
version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-dropdown-menu':
specifier: ^2.0.6
version: 2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-hover-card':
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-popover': '@radix-ui/react-popover':
specifier: ^1.0.7 specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
@ -138,9 +126,6 @@ importers:
react-hook-form: react-hook-form:
specifier: ^7.52.0 specifier: ^7.52.0
version: 7.52.0(react@18.3.1) version: 7.52.0(react@18.3.1)
react-hotkeys-hook:
specifier: ^4.5.0
version: 4.5.0(react-dom@18.3.1)(react@18.3.1)
react-i18next: react-i18next:
specifier: ^14.1.2 specifier: ^14.1.2
version: 14.1.2(i18next@23.11.5)(react-dom@18.3.1)(react@18.3.1) version: 14.1.2(i18next@23.11.5)(react-dom@18.3.1)(react@18.3.1)
@ -153,9 +138,6 @@ importers:
slate-react: slate-react:
specifier: ^0.105.0 specifier: ^0.105.0
version: 0.105.0(react-dom@18.3.1)(react@18.3.1)(slate@0.103.0) version: 0.105.0(react-dom@18.3.1)(react@18.3.1)(slate@0.103.0)
sonner:
specifier: ^1.5.0
version: 1.5.0(react-dom@18.3.1)(react@18.3.1)
use-debounce: use-debounce:
specifier: ^10.0.1 specifier: ^10.0.1
version: 10.0.1(react@18.3.1) version: 10.0.1(react@18.3.1)
@ -1588,34 +1570,6 @@ packages:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
dev: false dev: false
/@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==}
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.24.7
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-id': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/@radix-ui/react-collection@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1): /@radix-ui/react-collection@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies: peerDependencies:
@ -1694,40 +1648,6 @@ packages:
react: 18.3.1 react: 18.3.1
dev: false dev: false
/@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
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.24.7
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-id': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
aria-hidden: 1.2.4
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-remove-scroll: 2.5.5(@types/react@18.3.3)(react@18.3.1)
dev: false
/@radix-ui/react-direction@1.0.1(@types/react@18.3.3)(react@18.3.1): /@radix-ui/react-direction@1.0.1(@types/react@18.3.3)(react@18.3.1):
resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
peerDependencies: peerDependencies:
@ -1780,33 +1700,6 @@ packages:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
dev: false dev: false
/@radix-ui/react-dropdown-menu@2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==}
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.24.7
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-id': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-menu': 2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.3)(react@18.3.1): /@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.3)(react@18.3.1):
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
peerDependencies: peerDependencies:
@ -1844,35 +1737,6 @@ packages:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
dev: false dev: false
/@radix-ui/react-hover-card@1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A==}
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.24.7
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-popper': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/@radix-ui/react-id@1.0.1(@types/react@18.3.3)(react@18.3.1): /@radix-ui/react-id@1.0.1(@types/react@18.3.3)(react@18.3.1):
resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
peerDependencies: peerDependencies:
@ -1888,44 +1752,6 @@ packages:
react: 18.3.1 react: 18.3.1
dev: false dev: false
/@radix-ui/react-menu@2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==}
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.24.7
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-direction': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-id': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-popper': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
aria-hidden: 1.2.4
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-remove-scroll: 2.5.5(@types/react@18.3.3)(react@18.3.1)
dev: false
/@radix-ui/react-popover@1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1): /@radix-ui/react-popover@1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==} resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==}
peerDependencies: peerDependencies:
@ -5222,16 +5048,6 @@ packages:
react: 18.3.1 react: 18.3.1
dev: false dev: false
/react-hotkeys-hook@4.5.0(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==}
peerDependencies:
react: '>=16.8.1'
react-dom: '>=16.8.1'
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1)(react@18.3.1): /react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==} resolution: {integrity: sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==}
peerDependencies: peerDependencies:
@ -5653,16 +5469,6 @@ packages:
tiny-warning: 1.0.3 tiny-warning: 1.0.3
dev: false dev: false
/sonner@1.5.0(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==}
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/source-map-js@1.2.0: /source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}