feat: improve
This commit is contained in:
@@ -18,11 +18,10 @@
|
||||
"@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.22.2",
|
||||
"@tanstack/react-router": "^1.16.6",
|
||||
"@tanstack/react-query": "^5.24.1",
|
||||
"@tanstack/react-router": "^1.17.4",
|
||||
"framer-motion": "^11.0.6",
|
||||
"get-urls": "^12.1.0",
|
||||
"jotai": "^2.6.5",
|
||||
"media-chrome": "^2.2.5",
|
||||
"minidenticons": "^4.2.0",
|
||||
"nanoid": "^5.0.6",
|
||||
@@ -34,11 +33,11 @@
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-i18next": "^14.0.5",
|
||||
"react-router-dom": "^6.22.1",
|
||||
"react-router-dom": "^6.22.2",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"slate": "^0.101.5",
|
||||
"slate-react": "^0.101.6",
|
||||
"sonner": "^1.4.1",
|
||||
"sonner": "^1.4.2",
|
||||
"string-strip-html": "^13.4.6",
|
||||
"uqr": "^0.1.2",
|
||||
"use-debounce": "^10.0.0",
|
||||
@@ -48,7 +47,7 @@
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.58",
|
||||
"@types/react": "^18.2.60",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { useArk, useProfile } from "@lume/ark";
|
||||
import { SettingsIcon, UserIcon } from "@lume/icons";
|
||||
import { cn, useNetworkStatus } from "@lume/utils";
|
||||
import * as Avatar from "@radix-ui/react-avatar";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import { minidenticon } from "minidenticons";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LogoutDialog } from "./logoutDialog";
|
||||
|
||||
export function ActiveAccount() {
|
||||
const ark = useArk();
|
||||
const isOnline = useNetworkStatus();
|
||||
const svgURI = useMemo(
|
||||
() =>
|
||||
`data:image/svg+xml;utf8,${encodeURIComponent(
|
||||
minidenticon(ark.account.npub, 90, 50),
|
||||
)}`,
|
||||
[],
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { profile } = useProfile(ark.account.npub);
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<Avatar.Root
|
||||
className={cn(
|
||||
"rounded-full ring-1 ring-offset-2 ring-offset-neutral-200 dark:ring-offset-neutral-950",
|
||||
isOnline ? "ring-teal-500" : "ring-red-500",
|
||||
)}
|
||||
>
|
||||
<Avatar.Image
|
||||
src={profile?.picture}
|
||||
alt={ark.account.npub}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
className="aspect-square h-auto w-7 rounded-full object-cover"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={150}>
|
||||
<img
|
||||
src={svgURI}
|
||||
alt={ark.account.npub}
|
||||
className="aspect-square h-auto w-7 rounded-full bg-black dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
side="left"
|
||||
sideOffset={10}
|
||||
className="relative top-2 flex w-[200px] flex-col overflow-hidden rounded-2xl bg-white/50 p-2 ring-1 ring-black/10 backdrop-blur-2xl focus:outline-none dark:bg-black/50 dark:ring-white/10"
|
||||
>
|
||||
<DropdownMenu.Item asChild>
|
||||
<a
|
||||
href="/settings/profile"
|
||||
className="inline-flex h-9 items-center gap-3 rounded-lg px-3 text-sm font-medium text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<UserIcon className="size-4" />
|
||||
{t("user.editProfile")}
|
||||
</a>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
<a
|
||||
href="/settings/"
|
||||
className="inline-flex h-9 items-center gap-3 rounded-lg px-3 text-sm font-medium text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<SettingsIcon className="size-4" />
|
||||
{t("user.settings")}
|
||||
</a>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="my-1 h-px bg-black/10 dark:bg-white/10" />
|
||||
<LogoutDialog />
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { useArk } from "@lume/ark";
|
||||
import { LogoutIcon } from "@lume/icons";
|
||||
import * as AlertDialog from "@radix-ui/react-alert-dialog";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export function LogoutDialog() {
|
||||
const ark = useArk();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
// clear cache
|
||||
queryClient.clear();
|
||||
ark.account = null;
|
||||
|
||||
// redirect to welcome screen
|
||||
navigate({ to: "/auth/" });
|
||||
} catch (e) {
|
||||
toast.error(String(e));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AlertDialog.Root>
|
||||
<AlertDialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 items-center gap-3 rounded-lg px-3 text-sm font-medium text-black/70 hover:bg-black/10 hover:text-black focus:outline-none dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<LogoutIcon className="size-4" />
|
||||
{t("user.logout")}
|
||||
</button>
|
||||
</AlertDialog.Trigger>
|
||||
<AlertDialog.Portal>
|
||||
<AlertDialog.Overlay className="fixed inset-0 z-50 bg-black/20 backdrop-blur-sm dark:bg-black/20" />
|
||||
<AlertDialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<div className="relative h-min w-full max-w-md rounded-xl bg-neutral-100 dark:bg-neutral-900">
|
||||
<div className="flex flex-col gap-1 border-b border-white/5 px-5 py-4">
|
||||
<AlertDialog.Title className="text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{t("user.logoutConfirmTitle")}
|
||||
</AlertDialog.Title>
|
||||
<AlertDialog.Description className="text-sm leading-tight text-neutral-600 dark:text-neutral-400">
|
||||
{t("user.logoutConfirmSubtitle")}
|
||||
</AlertDialog.Description>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 px-5 py-3">
|
||||
<AlertDialog.Cancel asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 items-center justify-center rounded-lg px-4 text-sm font-medium text-neutral-900 outline-none hover:bg-neutral-200 dark:text-neutral-100 dark:hover:bg-neutral-800"
|
||||
>
|
||||
{t("global.cancel")}
|
||||
</button>
|
||||
</AlertDialog.Cancel>
|
||||
<AlertDialog.Action asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => logout()}
|
||||
className="inline-flex h-9 items-center justify-center rounded-lg bg-red-500 px-4 text-sm font-medium text-white outline-none hover:bg-red-600"
|
||||
>
|
||||
{t("user.logout")}
|
||||
</button>
|
||||
</AlertDialog.Action>
|
||||
</div>
|
||||
</div>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Portal>
|
||||
</AlertDialog.Root>
|
||||
);
|
||||
}
|
||||
@@ -9,9 +9,14 @@ export function Box({
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("flex h-full min-h-0 w-full", className)}>
|
||||
<div className="flex h-full min-h-0 w-full">
|
||||
<div className="h-full w-full flex-1 px-2 pb-2">
|
||||
<div className="h-full w-full overflow-hidden overflow-y-auto rounded-xl bg-white px-3 pt-3 shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5">
|
||||
<div
|
||||
className={cn(
|
||||
"h-full w-full overflow-hidden overflow-y-auto rounded-xl bg-white shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,28 +3,32 @@ import { cn } from "@lume/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function EmptyFeed({
|
||||
text,
|
||||
subtext,
|
||||
className,
|
||||
}: { text?: string; subtext?: string; className?: string }) {
|
||||
const { t } = useTranslation();
|
||||
text,
|
||||
subtext,
|
||||
className,
|
||||
}: {
|
||||
text?: string;
|
||||
subtext?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"w-full py-5 flex items-center justify-center flex-col gap-2 rounded-xl bg-neutral-50 dark:bg-neutral-950",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<InfoIcon className="size-8 text-blue-500" />
|
||||
<div className="text-center">
|
||||
<p className="font-semibold text-lg">
|
||||
{text ? text : t("global.emptyFeedTitle")}
|
||||
</p>
|
||||
<p className="leading-tight text-sm">
|
||||
{subtext ? subtext : t("global.emptyFeedSubtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full flex-col items-center justify-center gap-2 rounded-xl bg-neutral-50 py-3 dark:bg-neutral-950",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<InfoIcon className="size-8 text-blue-500" />
|
||||
<div className="text-center">
|
||||
<p className="text-lg font-semibold">
|
||||
{text ? text : t("global.emptyFeedTitle")}
|
||||
</p>
|
||||
<p className="text-sm leading-tight">
|
||||
{subtext ? subtext : t("global.emptyFeedSubtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReplyIcon, ShareIcon } from "@lume/icons";
|
||||
import { LinkIcon, ReplyIcon } from "@lume/icons";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNoteContext } from "../provider";
|
||||
@@ -41,8 +41,8 @@ export function NoteReply() {
|
||||
onClick={() => ark.open_thread(event.id)}
|
||||
className="inline-flex h-9 items-center gap-2 rounded-lg px-3 text-sm font-medium text-white hover:bg-neutral-900 focus:outline-none dark:text-black dark:hover:bg-neutral-100"
|
||||
>
|
||||
<ShareIcon className="size-4" />
|
||||
{t("note.buttons.view")}
|
||||
<LinkIcon className="size-4" />
|
||||
{t("note.buttons.open")}
|
||||
</button>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
|
||||
@@ -105,7 +105,7 @@ export function MentionNote({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="my-1 flex w-full cursor-default flex-col rounded-2xl border border-black/10 px-3 pt-1 dark:border-white/10">
|
||||
<div className="my-1 flex w-full cursor-default flex-col rounded-xl border border-black/10 px-3 pt-1 dark:border-white/10">
|
||||
<User.Provider pubkey={data.pubkey}>
|
||||
<User.Root className="flex h-10 items-center gap-2">
|
||||
<User.Avatar className="size-6 shrink-0 rounded-full object-cover" />
|
||||
|
||||
@@ -35,7 +35,10 @@ export function ImagePreview({ url }: { url: string }) {
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
|
||||
<div onClick={open} className="group relative my-1 rounded-2xl">
|
||||
<div
|
||||
onClick={open}
|
||||
className="group relative my-1 overflow-hidden rounded-xl"
|
||||
>
|
||||
<img
|
||||
src={url}
|
||||
alt={url}
|
||||
@@ -43,7 +46,7 @@ export function ImagePreview({ url }: { url: string }) {
|
||||
decoding="async"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
onError={fallback}
|
||||
className="h-auto w-full rounded-2xl object-cover"
|
||||
className="h-auto w-full object-cover"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user