wip: new design
This commit is contained in:
@@ -26,7 +26,6 @@
|
||||
"react": "^18.2.0",
|
||||
"react-currency-input-field": "^3.6.14",
|
||||
"react-i18next": "^14.0.2",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"sonner": "^1.4.0",
|
||||
"string-strip-html": "^13.4.6",
|
||||
|
||||
@@ -19,6 +19,15 @@ export class Ark {
|
||||
}
|
||||
}
|
||||
|
||||
public async create_keys() {
|
||||
try {
|
||||
const cmd: Keys = await invoke("create_keys");
|
||||
return cmd;
|
||||
} catch (e) {
|
||||
console.error(String(e));
|
||||
}
|
||||
}
|
||||
|
||||
public async save_account(keys: Keys) {
|
||||
try {
|
||||
const cmd: boolean = await invoke("save_key", { nsec: keys.nsec });
|
||||
|
||||
@@ -1,88 +1,87 @@
|
||||
import { useOpenGraph } from "@lume/utils";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
function isImage(url: string) {
|
||||
return /^https?:\/\/.+\.(jpg|jpeg|png|webp|avif)$/.test(url);
|
||||
return /^https?:\/\/.+\.(jpg|jpeg|png|webp|avif)$/.test(url);
|
||||
}
|
||||
|
||||
export function LinkPreview({ url }: { url: string }) {
|
||||
const domain = new URL(url);
|
||||
const { isLoading, isError, data } = useOpenGraph(url);
|
||||
const domain = new URL(url);
|
||||
const { isLoading, isError, data } = useOpenGraph(url);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex flex-col w-full mt-1 mb-2.5 rounded-xl overflow-hidden bg-neutral-100 dark:bg-neutral-900 border border-black/5 dark:border-white/5">
|
||||
<div className="w-full h-48 shrink-0 animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<div className="w-2/3 h-3 rounded animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="w-3/4 h-3 rounded animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<span className="mt-2.5 text-sm leading-none text-neutral-600 dark:text-neutral-400">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="mb-2.5 mt-1 flex w-full flex-col overflow-hidden rounded-xl border border-black/5 bg-neutral-100 dark:border-white/5 dark:bg-neutral-900">
|
||||
<div className="h-48 w-full shrink-0 animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<div className="h-3 w-2/3 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="h-3 w-3/4 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<span className="mt-2.5 text-sm leading-none text-neutral-600 dark:text-neutral-400">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data.title && !data.image && !data.description) {
|
||||
return (
|
||||
<Link
|
||||
to={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
{url}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
if (!data.title && !data.image && !data.description) {
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
{url}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Link
|
||||
to={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
{url}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
if (isError) {
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
{url}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex flex-col w-full mt-1 mb-2.5 overflow-hidden rounded-xl bg-neutral-100 dark:bg-neutral-900 border border-black/5 dark:border-white/5"
|
||||
>
|
||||
{isImage(data.image) ? (
|
||||
<img
|
||||
src={data.image}
|
||||
alt={url}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="object-cover w-full h-48 shrink-0 bg-white rounded-t-lg"
|
||||
/>
|
||||
) : null}
|
||||
<div className="flex flex-col items-start p-3">
|
||||
<div className="flex flex-col items-start text-left">
|
||||
{data.title ? (
|
||||
<div className="text-base font-semibold break-p text-neutral-900 dark:text-neutral-100">
|
||||
{data.title}
|
||||
</div>
|
||||
) : null}
|
||||
{data.description ? (
|
||||
<div className="mb-2 text-balance text-sm break-p line-clamp-3 text-neutral-700 dark:text-neutral-400">
|
||||
{data.description}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="text-sm break-all text-blue-500 font-semibold">
|
||||
{domain.hostname}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mb-2.5 mt-1 flex w-full flex-col overflow-hidden rounded-xl border border-black/5 bg-neutral-100 dark:border-white/5 dark:bg-neutral-900"
|
||||
>
|
||||
{isImage(data.image) ? (
|
||||
<img
|
||||
src={data.image}
|
||||
alt={url}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-48 w-full shrink-0 rounded-t-lg bg-white object-cover"
|
||||
/>
|
||||
) : null}
|
||||
<div className="flex flex-col items-start p-3">
|
||||
<div className="flex flex-col items-start text-left">
|
||||
{data.title ? (
|
||||
<div className="break-p text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{data.title}
|
||||
</div>
|
||||
) : null}
|
||||
{data.description ? (
|
||||
<div className="break-p mb-2 line-clamp-3 text-balance text-sm text-neutral-700 dark:text-neutral-400">
|
||||
{data.description}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="break-all text-sm font-semibold text-blue-500">
|
||||
{domain.hostname}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,14 +7,19 @@ export function UserCover({ className }: { className?: string }) {
|
||||
if (!user) {
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse bg-gray-3 dark:bg-gray-7", className)}
|
||||
className={cn(
|
||||
"animate-pulse bg-neutral-300 dark:bg-neutral-700",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (user && !user.profile.banner) {
|
||||
return (
|
||||
<div className={cn("bg-gradient-to-b from-sky-4 to-blue-2", className)} />
|
||||
<div
|
||||
className={cn("bg-gradient-to-b from-blue-400 to-teal-200", className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export function UserNip05({ className }: { className?: string }) {
|
||||
: user?.profile.nip05}
|
||||
</p>
|
||||
{!isLoading && verified ? (
|
||||
<VerifiedIcon className="size-4 text-green-10" />
|
||||
<VerifiedIcon className="text-teal-500 size-4" />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ export * from "./src/lume";
|
||||
export * from "./src/media";
|
||||
export * from "./src/mute";
|
||||
export * from "./src/space";
|
||||
export * from "./src/spaceFilled";
|
||||
export * from "./src/navArrowDown";
|
||||
export * from "./src/plus";
|
||||
export * from "./src/plusCircle";
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
export function HomeIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M12 17v-4M7.606 5.65l-2.6 2.456c-.74.698-1.11 1.047-1.374 1.46a4 4 0 00-.513 1.191C3 11.233 3 11.742 3 12.76V14.6c0 2.24 0 3.36.436 4.216a4 4 0 001.748 1.748C6.04 21 7.16 21 9.4 21h5.2c2.24 0 3.36 0 4.216-.436a4 4 0 001.748-1.748C21 17.96 21 16.84 21 14.6v-1.841c0-1.017 0-1.526-.119-2.002a4 4 0 00-.513-1.19c-.265-.414-.634-.763-1.374-1.461l-2.6-2.456c-1.546-1.46-2.32-2.19-3.201-2.466a4 4 0 00-2.386 0c-.882.275-1.655 1.006-3.201 2.466z" />
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M3 12.759c0-1.017 0-1.526.119-2.002a4 4 0 01.513-1.19c.265-.414.634-.763 1.374-1.461l2.6-2.456c1.546-1.46 2.32-2.19 3.201-2.466a4 4 0 012.386 0c.882.275 1.655 1.006 3.201 2.466l2.6 2.456c.74.698 1.11 1.047 1.374 1.46a4 4 0 01.513 1.191c.119.476.119.985.119 2.002V14.6c0 2.24 0 3.36-.436 4.216a4 4 0 01-1.748 1.748C17.96 21 16.84 21 14.6 21H9.4c-2.24 0-3.36 0-4.216-.436a4 4 0 01-1.748-1.748C3 17.96 3 16.84 3 14.6v-1.841z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
export function HomeFilledIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M10.51 2.23a5 5 0 012.98 0c.61.19 1.136.525 1.681.963.528.425 1.132.996 1.88 1.702l2.716 2.565c.657.62 1.111 1.049 1.443 1.567.293.458.51.96.642 1.488.148.598.148 1.222.148 2.125v2.003c0 1.084 0 1.958-.058 2.666-.06.729-.185 1.369-.487 1.96a5 5 0 01-2.185 2.186c-.592.302-1.232.428-1.961.487C16.6 22 15.727 22 14.643 22H9.357c-1.084 0-1.958 0-2.666-.058-.728-.06-1.369-.185-1.96-.487a5 5 0 01-2.186-2.185c-.302-.592-.428-1.232-.487-1.961C2 16.6 2 15.727 2 14.643V12.64c0-.903 0-1.527.148-2.125a5 5 0 01.642-1.488c.332-.518.786-.947 1.443-1.567l2.716-2.565c.748-.706 1.352-1.277 1.88-1.702.545-.438 1.071-.773 1.68-.964zM13 13a1 1 0 10-2 0v4a1 1 0 102 0v-4z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M13.49 2.23a5 5 0 00-2.98 0c-.61.19-1.136.525-1.68.963-.529.425-1.133.996-1.88 1.702L4.232 7.46c-.657.62-1.111 1.049-1.443 1.567a5 5 0 00-.642 1.488C2 11.113 2 11.737 2 12.64v2.003c0 1.084 0 1.958.058 2.666.06.729.185 1.369.487 1.96a5 5 0 002.185 2.186c.592.302 1.233.428 1.961.487C7.4 22 8.273 22 9.357 22h5.286c1.084 0 1.958 0 2.666-.058.729-.06 1.369-.185 1.961-.487a5 5 0 002.185-2.185c.302-.592.428-1.232.487-1.961C22 16.6 22 15.727 22 14.643V12.64c0-.903 0-1.527-.148-2.125a5.002 5.002 0 00-.642-1.488c-.332-.518-.786-.947-1.443-1.567l-2.716-2.565c-.748-.706-1.352-1.277-1.88-1.702-.545-.438-1.071-.773-1.68-.964z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { SVGProps } from 'react';
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export function SpaceIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
export function SpaceIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -11,11 +13,12 @@ export function SpaceIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElemen
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M14.522 2.261a4 4 0 00-5.044 0l-5 4.062A4 4 0 003 9.428V17a4 4 0 004 4h10a4 4 0 004-4V9.428a4 4 0 00-1.478-3.105l-5-4.062zM8 15a1 1 0 100 2h8a1 1 0 100-2H8z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M8 7v10m4-10v4m4-4v7m-5 7h2c2.8 0 4.2 0 5.27-.545a5 5 0 002.185-2.185C21 17.2 21 15.8 21 13v-2c0-2.8 0-4.2-.545-5.27a5 5 0 00-2.185-2.185C17.2 3 15.8 3 13 3h-2c-2.8 0-4.2 0-5.27.545A5 5 0 003.545 5.73C3 6.8 3 8.2 3 11v2c0 2.8 0 4.2.545 5.27a5 5 0 002.185 2.185C6.8 21 8.2 21 11 21z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
21
packages/icons/src/spaceFilled.tsx
Normal file
21
packages/icons/src/spaceFilled.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export function SpaceFilledIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.956 2h2.088c1.363 0 2.447 0 3.321.071.896.074 1.66.227 2.359.583a6 6 0 012.622 2.622c.356.7.51 1.463.583 2.359.071.874.071 1.958.071 3.321v2.088c0 1.363 0 2.447-.071 3.321-.074.896-.227 1.66-.583 2.359a6 6 0 01-2.622 2.622c-.7.356-1.463.51-2.359.583-.874.071-1.958.071-3.321.071h-2.088c-1.363 0-2.447 0-3.321-.071-.896-.074-1.66-.227-2.359-.583a6 6 0 01-2.622-2.622c-.356-.7-.51-1.463-.583-2.359C2 15.491 2 14.407 2 13.044v-2.088c0-1.363 0-2.447.071-3.321.074-.896.227-1.66.583-2.359a6 6 0 012.622-2.622c.7-.356 1.463-.51 2.359-.583C8.509 2 9.593 2 10.956 2zM9 7a1 1 0 00-2 0v10a1 1 0 102 0V7zm3-1a1 1 0 00-1 1v4a1 1 0 102 0V7a1 1 0 00-1-1zm5 1a1 1 0 10-2 0v7a1 1 0 102 0V7z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -8,10 +8,8 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
},
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
},
|
||||
|
||||
@@ -9,10 +9,8 @@
|
||||
"@lume/storage": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
},
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
},
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
},
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
},
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
},
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
},
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
},
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.4.0",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"sonner": "^1.4.0",
|
||||
"virtua": "^0.23.3"
|
||||
},
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
"@lume/ui": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@tanstack/react-query": "^5.18.1",
|
||||
"react": "^18.2.0",
|
||||
"react-router-dom": "^6.22.0"
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tailwindcss": "workspace:^",
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
".": "./tailwind.config.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@evilmartians/harmony": "^1.2.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"tailwind-scrollbar": "^3.0.5",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tailwindcss-radix-colors": "^1.2.0"
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
import harmonyPalette from "@evilmartians/harmony/tailwind";
|
||||
|
||||
const config = {
|
||||
theme: {
|
||||
colors: harmonyPalette,
|
||||
extend: {
|
||||
keyframes: {
|
||||
slideDownAndFade: {
|
||||
@@ -45,7 +48,6 @@ const config = {
|
||||
plugins: [
|
||||
require("@tailwindcss/forms"),
|
||||
require("@tailwindcss/typography"),
|
||||
require("tailwindcss-radix-colors"),
|
||||
require("tailwind-scrollbar")({ nocompatible: true }),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@lume/ark": "workspace:^",
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/storage": "workspace:^",
|
||||
@@ -22,7 +21,6 @@
|
||||
"framer-motion": "^11.0.3",
|
||||
"jotai": "^2.6.4",
|
||||
"minidenticons": "^4.2.0",
|
||||
"nostr-tools": "~1.17.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.50.0",
|
||||
@@ -41,7 +39,6 @@
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
||||
@@ -6,80 +6,80 @@ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import { minidenticon } from "minidenticons";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Logout } from "./logout";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
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 ark = useArk();
|
||||
const isOnline = useNetworkStatus();
|
||||
const svgURI = useMemo(
|
||||
() =>
|
||||
`data:image/svg+xml;utf8,${encodeURIComponent(
|
||||
minidenticon(ark.account.npub, 90, 50),
|
||||
)}`,
|
||||
[],
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { user } = useProfile(ark.account.npub);
|
||||
const { t } = useTranslation();
|
||||
const { user } = useProfile(ark.account.npub);
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div className="relative">
|
||||
<Avatar.Root>
|
||||
<Avatar.Image
|
||||
src={user?.picture}
|
||||
alt={ark.account.npub}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
className="object-cover w-full h-auto aspect-square rounded-xl"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={150}>
|
||||
<img
|
||||
src={svgURI}
|
||||
alt={ark.account.npub}
|
||||
className="w-full h-auto bg-black aspect-square rounded-xl dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<span
|
||||
className={cn(
|
||||
"absolute bottom-0 right-0 block h-2 w-2 rounded-full ring-2 ring-gray-1 dark:ring-graydark-1",
|
||||
isOnline ? "bg-green-9" : "bg-red-9",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
side="right"
|
||||
sideOffset={5}
|
||||
className="relative top-5 flex w-[200px] p-2 flex-col overflow-hidden rounded-2xl bg-white/50 dark:bg-black/50 ring-1 ring-black/10 dark:ring-white/10 backdrop-blur-2xl focus:outline-none"
|
||||
>
|
||||
<DropdownMenu.Item asChild>
|
||||
<Link
|
||||
to="/settings/profile"
|
||||
className="inline-flex items-center gap-3 px-3 text-sm font-medium rounded-lg h-9 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")}
|
||||
</Link>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
<Link
|
||||
to="/settings/"
|
||||
className="inline-flex items-center gap-3 px-3 text-sm font-medium rounded-lg h-9 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")}
|
||||
</Link>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="h-px my-1 bg-black/10 dark:bg-white/10" />
|
||||
<Logout />
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div className="relative">
|
||||
<Avatar.Root>
|
||||
<Avatar.Image
|
||||
src={user?.picture}
|
||||
alt={ark.account.npub}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
className="aspect-square h-auto w-full rounded-xl object-cover"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={150}>
|
||||
<img
|
||||
src={svgURI}
|
||||
alt={ark.account.npub}
|
||||
className="aspect-square h-auto w-full rounded-xl bg-black dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<span
|
||||
className={cn(
|
||||
"absolute bottom-0 right-0 block size-3 rounded-full ring-2 ring-neutral-200 dark:ring-neutral-800",
|
||||
isOnline ? "bg-teal-500" : "bg-red-500",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
side="right"
|
||||
sideOffset={5}
|
||||
className="relative top-5 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>
|
||||
<Link
|
||||
to="/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")}
|
||||
</Link>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
<Link
|
||||
to="/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")}
|
||||
</Link>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="my-1 h-px bg-black/10 dark:bg-white/10" />
|
||||
<Logout />
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,83 +1,76 @@
|
||||
import { useArk } from "@lume/ark";
|
||||
import { LogoutIcon } from "@lume/icons";
|
||||
import { useStorage } from "@lume/storage";
|
||||
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 { useNavigate } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export function Logout() {
|
||||
const ark = useArk();
|
||||
const storage = useStorage();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const ark = useArk();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
// logout
|
||||
await storage.logout();
|
||||
const logout = async () => {
|
||||
try {
|
||||
// clear cache
|
||||
queryClient.clear();
|
||||
ark.account = null;
|
||||
|
||||
// clear cache
|
||||
queryClient.clear();
|
||||
ark.account = null;
|
||||
ark.ndk.signer = null;
|
||||
ark.ndk.activeUser = null;
|
||||
// redirect to welcome screen
|
||||
navigate({ to: "/auth/" });
|
||||
} catch (e) {
|
||||
toast.error(String(e));
|
||||
}
|
||||
};
|
||||
|
||||
// redirect to welcome screen
|
||||
navigate("/auth/");
|
||||
} catch (e) {
|
||||
toast.error(String(e));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AlertDialog.Root>
|
||||
<AlertDialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-3 px-3 text-sm font-medium rounded-lg h-9 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>
|
||||
);
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
ArrowUpSquareIcon,
|
||||
BellFilledIcon,
|
||||
BellIcon,
|
||||
HomeFilledIcon,
|
||||
HomeIcon,
|
||||
PlusIcon,
|
||||
SearchFilledIcon,
|
||||
SearchIcon,
|
||||
SettingsFilledIcon,
|
||||
SettingsIcon,
|
||||
ArrowUpSquareIcon,
|
||||
BellFilledIcon,
|
||||
BellIcon,
|
||||
HomeFilledIcon,
|
||||
HomeIcon,
|
||||
PlusIcon,
|
||||
SearchFilledIcon,
|
||||
SearchIcon,
|
||||
SettingsFilledIcon,
|
||||
SettingsIcon,
|
||||
} from "@lume/icons";
|
||||
import { cn, editorAtom, searchAtom } from "@lume/utils";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
@@ -22,160 +22,160 @@ import { ActiveAccount } from "./account/active";
|
||||
import { UnreadActivity } from "./unread";
|
||||
|
||||
export function Navigation() {
|
||||
const [isEditorOpen, setIsEditorOpen] = useAtom(editorAtom);
|
||||
const [search, setSearch] = useAtom(searchAtom);
|
||||
const [update, setUpdate] = useState<Update>(null);
|
||||
const [isEditorOpen, setIsEditorOpen] = useAtom(editorAtom);
|
||||
const [search, setSearch] = useAtom(searchAtom);
|
||||
const [update, setUpdate] = useState<Update>(null);
|
||||
|
||||
// shortcut for editor
|
||||
useHotkeys("meta+n", () => setIsEditorOpen((state) => !state), []);
|
||||
// shortcut for editor
|
||||
useHotkeys("meta+n", () => setIsEditorOpen((state) => !state), []);
|
||||
|
||||
const installNewUpdate = async () => {
|
||||
if (!update) return;
|
||||
const installNewUpdate = async () => {
|
||||
if (!update) return;
|
||||
|
||||
const yes = await confirm(update.body, {
|
||||
title: `v${update.version} is available`,
|
||||
type: "info",
|
||||
});
|
||||
const yes = await confirm(update.body, {
|
||||
title: `v${update.version} is available`,
|
||||
type: "info",
|
||||
});
|
||||
|
||||
if (yes) {
|
||||
await update.downloadAndInstall();
|
||||
await relaunch();
|
||||
}
|
||||
};
|
||||
if (yes) {
|
||||
await update.downloadAndInstall();
|
||||
await relaunch();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function checkNewUpdate() {
|
||||
const newVersion = await check();
|
||||
setUpdate(newVersion);
|
||||
}
|
||||
checkNewUpdate();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
async function checkNewUpdate() {
|
||||
const newVersion = await check();
|
||||
setUpdate(newVersion);
|
||||
}
|
||||
checkNewUpdate();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex flex-col justify-between w-20 h-full px-4 py-3 shrink-0"
|
||||
>
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="flex flex-col gap-3">
|
||||
<ActiveAccount />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsEditorOpen((state) => !state)}
|
||||
className={cn(
|
||||
"flex items-center justify-center h-auto w-full aspect-square rounded-xl text-gray-normal",
|
||||
isEditorOpen
|
||||
? "bg-blue-solid text-white"
|
||||
: "bg-gray-4 hover:bg-blue-solid dark:bg-graydark-4",
|
||||
)}
|
||||
>
|
||||
<PlusIcon className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="w-2/3 h-px mx-auto my-5 bg-black/10 dark:bg-white/10" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Link
|
||||
to="/app/space"
|
||||
className="inline-flex flex-col items-center justify-center"
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl",
|
||||
isActive
|
||||
? "bg-black/10 text-black dark:bg-white/10 dark:text-white"
|
||||
: "text-black/50 dark:text-neutral-400",
|
||||
)}
|
||||
>
|
||||
{isActive ? (
|
||||
<HomeFilledIcon className="size-6" />
|
||||
) : (
|
||||
<HomeIcon className="size-6" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/app/activity"
|
||||
className="inline-flex flex-col items-center justify-center"
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"relative inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl",
|
||||
isActive
|
||||
? "bg-black/10 text-black dark:bg-white/10 dark:text-white"
|
||||
: "text-black/50 dark:text-neutral-400",
|
||||
)}
|
||||
>
|
||||
{isActive ? (
|
||||
<BellFilledIcon className="size-6" />
|
||||
) : (
|
||||
<BellIcon className="size-6" />
|
||||
)}
|
||||
<UnreadActivity />
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{update ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={installNewUpdate}
|
||||
className="relative inline-flex flex-col items-center justify-center"
|
||||
>
|
||||
<span className="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-teal-500/60 ring-teal-500/80 text-teal-50 dark:bg-teal-500/10 dark:text-teal-400 ring-1 ring-inset dark:ring-teal-500/20">
|
||||
Update
|
||||
</span>
|
||||
<div className="inline-flex items-center justify-center w-full h-auto aspect-square rounded-xl text-black/50 dark:text-neutral-400">
|
||||
<ArrowUpSquareIcon className="size-6" />
|
||||
</div>
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSearch((open) => !open)}
|
||||
className="inline-flex flex-col items-center justify-center"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl",
|
||||
search
|
||||
? "bg-black/10 text-black dark:bg-white/10 dark:text-white"
|
||||
: "text-black/50 dark:text-neutral-400",
|
||||
)}
|
||||
>
|
||||
{search ? (
|
||||
<SearchFilledIcon className="size-6" />
|
||||
) : (
|
||||
<SearchIcon className="size-6" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
<Link
|
||||
to="/settings"
|
||||
className="inline-flex flex-col items-center justify-center"
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl",
|
||||
isActive
|
||||
? "bg-black/10 text-black dark:bg-white/10 dark:text-white"
|
||||
: "text-black/50 dark:text-neutral-400",
|
||||
)}
|
||||
>
|
||||
{isActive ? (
|
||||
<SettingsFilledIcon className="size-6" />
|
||||
) : (
|
||||
<SettingsIcon className="size-6" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex h-full w-20 shrink-0 flex-col justify-between px-4 py-3"
|
||||
>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="flex flex-col gap-3">
|
||||
<ActiveAccount />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsEditorOpen((state) => !state)}
|
||||
className={cn(
|
||||
"inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl",
|
||||
isEditorOpen
|
||||
? "bg-blue-500 text-white"
|
||||
: "bg-neutral-300 hover:bg-neutral-400 dark:bg-neutral-700 hover:dark:bg-neutral-600",
|
||||
)}
|
||||
>
|
||||
<PlusIcon className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="mx-auto my-5 h-px w-2/3 bg-black/10 dark:bg-white/10" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Link
|
||||
to="/app/space"
|
||||
className="inline-flex flex-col items-center justify-center"
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl",
|
||||
isActive
|
||||
? "bg-neutral-300 hover:bg-neutral-400 dark:bg-neutral-700 hover:dark:bg-neutral-600"
|
||||
: "text-neutral-600 dark:text-neutral-400",
|
||||
)}
|
||||
>
|
||||
{isActive ? (
|
||||
<HomeFilledIcon className="size-6" />
|
||||
) : (
|
||||
<HomeIcon className="size-6" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
to="/app/activity"
|
||||
className="inline-flex flex-col items-center justify-center"
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"relative inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl",
|
||||
isActive
|
||||
? "bg-neutral-300 hover:bg-neutral-400 dark:bg-neutral-700 hover:dark:bg-neutral-600"
|
||||
: "text-neutral-600 dark:text-neutral-400",
|
||||
)}
|
||||
>
|
||||
{isActive ? (
|
||||
<BellFilledIcon className="size-6" />
|
||||
) : (
|
||||
<BellIcon className="size-6" />
|
||||
)}
|
||||
<UnreadActivity />
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{update ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={installNewUpdate}
|
||||
className="relative inline-flex flex-col items-center justify-center"
|
||||
>
|
||||
<span className="inline-flex items-center rounded-full bg-teal-500/60 px-2 py-1 text-xs font-semibold text-teal-50 ring-1 ring-inset ring-teal-500/80 dark:bg-teal-500/10 dark:text-teal-400 dark:ring-teal-500/20">
|
||||
Update
|
||||
</span>
|
||||
<div className="inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl text-black/50 dark:text-neutral-400">
|
||||
<ArrowUpSquareIcon className="size-6" />
|
||||
</div>
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSearch((open) => !open)}
|
||||
className="inline-flex flex-col items-center justify-center"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl",
|
||||
search
|
||||
? "bg-neutral-300 hover:bg-neutral-400 dark:bg-neutral-700 hover:dark:bg-neutral-600"
|
||||
: "text-neutral-600 dark:text-neutral-400",
|
||||
)}
|
||||
>
|
||||
{search ? (
|
||||
<SearchFilledIcon className="size-6" />
|
||||
) : (
|
||||
<SearchIcon className="size-6" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
<Link
|
||||
to="/settings"
|
||||
className="inline-flex flex-col items-center justify-center"
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex aspect-square h-auto w-full items-center justify-center rounded-xl",
|
||||
isActive
|
||||
? "bg-neutral-300 hover:bg-neutral-400 dark:bg-neutral-700 hover:dark:bg-neutral-600"
|
||||
: "text-neutral-600 dark:text-neutral-400",
|
||||
)}
|
||||
>
|
||||
{isActive ? (
|
||||
<SettingsFilledIcon className="size-6" />
|
||||
) : (
|
||||
<SettingsIcon className="size-6" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,10 +53,10 @@ export const User = memo(function User({
|
||||
<Avatar.Image
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-6 w-6 rounded-md bg-black dark:bg-white"
|
||||
className="w-6 h-6 bg-black rounded-md dark:bg-white"
|
||||
/>
|
||||
</Avatar.Root>
|
||||
<div className="flex flex-1 items-baseline gap-2">
|
||||
<div className="flex items-baseline flex-1 gap-2">
|
||||
<h5 className="max-w-[10rem] truncate font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{fallbackName}
|
||||
</h5>
|
||||
@@ -70,24 +70,24 @@ export const User = memo(function User({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-6 items-center gap-2">
|
||||
<div className="flex items-center h-6 gap-2">
|
||||
<Avatar.Root className="shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-6 w-6 rounded-md"
|
||||
className="w-6 h-6 rounded-md"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-6 w-6 rounded-md bg-black dark:bg-white"
|
||||
className="w-6 h-6 bg-black rounded-md dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div className="flex flex-1 items-baseline gap-2">
|
||||
<div className="flex items-baseline flex-1 gap-2">
|
||||
<h5 className="max-w-[10rem] truncate font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{user?.name ||
|
||||
user?.display_name ||
|
||||
@@ -107,11 +107,11 @@ export const User = memo(function User({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar.Root className="h-8 w-8 shrink-0">
|
||||
<Avatar.Root className="w-8 h-8 shrink-0">
|
||||
<Avatar.Image
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-8 w-8 rounded-md bg-black dark:bg-white"
|
||||
className="w-8 h-8 bg-black rounded-md dark:bg-white"
|
||||
/>
|
||||
</Avatar.Root>
|
||||
<h5 className="max-w-[10rem] truncate font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
@@ -123,19 +123,19 @@ export const User = memo(function User({
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar.Root className="h-8 w-8 shrink-0">
|
||||
<Avatar.Root className="w-8 h-8 shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
className="h-8 w-8 rounded-md"
|
||||
className="w-8 h-8 rounded-md"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-8 w-8 rounded-md bg-black dark:bg-white"
|
||||
className="w-8 h-8 bg-black rounded-md dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
@@ -156,11 +156,11 @@ export const User = memo(function User({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar.Root className="h-8 w-8 shrink-0">
|
||||
<Avatar.Root className="w-8 h-8 shrink-0">
|
||||
<Avatar.Image
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-8 w-8 rounded-md bg-black dark:bg-white"
|
||||
className="w-8 h-8 bg-black rounded-md dark:bg-white"
|
||||
/>
|
||||
</Avatar.Root>
|
||||
<h5 className="max-w-[10rem] truncate font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
@@ -172,27 +172,24 @@ export const User = memo(function User({
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar.Root className="h-8 w-8 shrink-0">
|
||||
<Avatar.Root className="w-8 h-8 shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
className="h-8 w-8 rounded-md"
|
||||
className="w-8 h-8 rounded-md"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-8 w-8 rounded-md bg-black dark:bg-white"
|
||||
className="w-8 h-8 bg-black rounded-md dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<h5 className="max-w-[10rem] truncate font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{user?.name ||
|
||||
user?.display_name ||
|
||||
user?.displayName ||
|
||||
fallbackName}
|
||||
{user?.name || user?.display_name || fallbackName}
|
||||
</h5>
|
||||
</div>
|
||||
);
|
||||
@@ -202,10 +199,10 @@ export const User = memo(function User({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="h-14 w-14 shrink-0 animate-pulse rounded-lg bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="rounded-lg h-14 w-14 shrink-0 animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="h-3.5 w-36 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="h-4 w-24 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="w-24 h-4 rounded animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -213,37 +210,37 @@ export const User = memo(function User({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="h-20 bg-gray-200 dark:bg-gray-800 rounded-t-lg">
|
||||
<div className="h-20 bg-gray-200 rounded-t-lg dark:bg-gray-800">
|
||||
{user?.banner ? (
|
||||
<img
|
||||
src={user.banner}
|
||||
alt="banner"
|
||||
className="w-full h-full object-cover"
|
||||
className="object-cover w-full h-full"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col gap-2.5 px-3 -mt-6">
|
||||
<Avatar.Root className="shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
src={user?.picture}
|
||||
alt={pubkey}
|
||||
decoding="async"
|
||||
className="size-11 rounded-lg object-cover"
|
||||
className="object-cover rounded-lg size-11"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="size-11 rounded-lg bg-black dark:bg-white"
|
||||
className="bg-black rounded-lg size-11 dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div className="flex flex-col items-start text-start">
|
||||
<p className="max-w-[15rem] truncate text-lg font-semibold leadning-tight">
|
||||
{user?.name || user?.display_name || user?.displayName}
|
||||
{user?.name || user?.display_name}
|
||||
</p>
|
||||
<p className="break-p text-neutral-700 dark:text-neutral-600 max-w-none select-text whitespace-pre-line">
|
||||
{user?.about || user?.bio || "No bio"}
|
||||
<p className="whitespace-pre-line select-text break-p text-neutral-700 dark:text-neutral-600 max-w-none">
|
||||
{user?.about || "No bio"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -255,10 +252,10 @@ export const User = memo(function User({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="h-10 w-10 shrink-0 animate-pulse rounded-lg bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="flex w-full flex-col items-start gap-1">
|
||||
<div className="h-4 w-36 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="h-4 w-24 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="w-10 h-10 rounded-lg shrink-0 animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="flex flex-col items-start w-full gap-1">
|
||||
<div className="h-4 rounded w-36 animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="w-24 h-4 rounded animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -266,23 +263,23 @@ export const User = memo(function User({
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Avatar.Root className="h-10 w-10 shrink-0">
|
||||
<Avatar.Root className="w-10 h-10 shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-10 w-10 rounded-lg object-cover"
|
||||
className="object-cover w-10 h-10 rounded-lg"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 rounded-lg bg-black dark:bg-white"
|
||||
className="w-10 h-10 bg-black rounded-lg dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<div className="flex flex-col items-start w-full">
|
||||
<h3 className="max-w-[15rem] truncate text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{user?.name || user?.display_name || user?.displayName}
|
||||
</h3>
|
||||
@@ -297,7 +294,7 @@ export const User = memo(function User({
|
||||
if (variant === "avatar") {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="h-12 w-12 animate-pulse rounded-lg bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="w-12 h-12 rounded-lg animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -308,13 +305,13 @@ export const User = memo(function User({
|
||||
alt={pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-12 w-12 rounded-lg"
|
||||
className="w-12 h-12 rounded-lg"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-12 w-12 rounded-lg bg-black dark:bg-white"
|
||||
className="w-12 h-12 bg-black rounded-lg dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
@@ -324,24 +321,24 @@ export const User = memo(function User({
|
||||
if (variant === "miniavatar") {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="h-10 w-10 shrink-0 animate-pulse rounded-lg bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="w-10 h-10 rounded-lg shrink-0 animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Avatar.Root className="h-10 w-10 shrink-0">
|
||||
<Avatar.Root className="w-10 h-10 shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-10 w-10 rounded-lg"
|
||||
className="w-10 h-10 rounded-lg"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 rounded-lg bg-black dark:bg-white"
|
||||
className="w-10 h-10 bg-black rounded-lg dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
@@ -352,11 +349,11 @@ export const User = memo(function User({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<Avatar.Root className="h-10 w-10 shrink-0">
|
||||
<Avatar.Root className="w-10 h-10 shrink-0">
|
||||
<Avatar.Image
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 rounded-lg bg-black object-cover dark:bg-white"
|
||||
className="object-cover w-10 h-10 bg-black rounded-lg dark:bg-white"
|
||||
/>
|
||||
</Avatar.Root>
|
||||
<div className="absolute left-2 top-2 inline-flex items-center gap-1.5 font-semibold leading-tight">
|
||||
@@ -371,19 +368,19 @@ export const User = memo(function User({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Avatar.Root className="h-10 w-10 shrink-0">
|
||||
<Avatar.Root className="w-10 h-10 shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-10 w-10 rounded-lg object-cover"
|
||||
className="object-cover w-10 h-10 rounded-lg"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 rounded-lg bg-black dark:bg-white"
|
||||
className="w-10 h-10 bg-black rounded-lg dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
@@ -405,7 +402,7 @@ export const User = memo(function User({
|
||||
if (variant === "stacked") {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="inline-block h-8 w-8 animate-pulse rounded-full bg-neutral-300 ring-1 ring-neutral-200 dark:bg-neutral-700 dark:ring-neutral-800" />
|
||||
<div className="inline-block w-8 h-8 rounded-full animate-pulse bg-neutral-300 ring-1 ring-neutral-200 dark:bg-neutral-700 dark:ring-neutral-800" />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -416,13 +413,13 @@ export const User = memo(function User({
|
||||
alt={pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="inline-block h-8 w-8 rounded-full ring-1 ring-neutral-200 dark:ring-neutral-800"
|
||||
className="inline-block w-8 h-8 rounded-full ring-1 ring-neutral-200 dark:ring-neutral-800"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="inline-block h-8 w-8 rounded-full bg-black ring-1 ring-neutral-200 dark:bg-white dark:ring-neutral-800"
|
||||
className="inline-block w-8 h-8 bg-black rounded-full ring-1 ring-neutral-200 dark:bg-white dark:ring-neutral-800"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
@@ -432,7 +429,7 @@ export const User = memo(function User({
|
||||
if (variant === "ministacked") {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="inline-block h-6 w-6 animate-pulse rounded-full bg-neutral-300 ring-1 ring-white dark:ring-black" />
|
||||
<div className="inline-block w-6 h-6 rounded-full animate-pulse bg-neutral-300 ring-1 ring-white dark:ring-black" />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -443,13 +440,13 @@ export const User = memo(function User({
|
||||
alt={pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="inline-block h-6 w-6 rounded-full ring-1 ring-white dark:ring-black"
|
||||
className="inline-block w-6 h-6 rounded-full ring-1 ring-white dark:ring-black"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="inline-block h-6 w-6 rounded-full bg-black ring-1 ring-white dark:bg-white dark:ring-black"
|
||||
className="inline-block w-6 h-6 bg-black rounded-full ring-1 ring-white dark:bg-white dark:ring-black"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
@@ -460,12 +457,12 @@ export const User = memo(function User({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
<div className="inline-flex h-10 w-10 items-center justify-center">
|
||||
<RepostIcon className="h-5 w-5 text-blue-500" />
|
||||
<div className="inline-flex items-center justify-center w-10 h-10">
|
||||
<RepostIcon className="w-5 h-5 text-blue-500" />
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<div className="h-6 w-6 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="h-4 w-24 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="w-6 h-6 rounded animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="w-24 h-4 rounded animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -473,8 +470,8 @@ export const User = memo(function User({
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 px-3">
|
||||
<div className="inline-flex w-10 items-center justify-center">
|
||||
<RepostIcon className="h-5 w-5 text-blue-500" />
|
||||
<div className="inline-flex items-center justify-center w-10">
|
||||
<RepostIcon className="w-5 h-5 text-blue-500" />
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<Avatar.Root className="shrink-0">
|
||||
@@ -483,13 +480,13 @@ export const User = memo(function User({
|
||||
alt={pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-6 w-6 rounded object-cover"
|
||||
className="object-cover w-6 h-6 rounded"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-6 w-6 rounded bg-black dark:bg-white"
|
||||
className="w-6 h-6 bg-black rounded dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
@@ -510,35 +507,35 @@ export const User = memo(function User({
|
||||
if (variant === "thread") {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex h-16 items-center gap-3 px-3">
|
||||
<div className="h-10 w-10 shrink-0 animate-pulse rounded-lg bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="flex flex-1 flex-col gap-1">
|
||||
<div className="h-4 w-36 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="h-3 w-24 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="flex items-center h-16 gap-3 px-3">
|
||||
<div className="w-10 h-10 rounded-lg shrink-0 animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="flex flex-col flex-1 gap-1">
|
||||
<div className="h-4 rounded w-36 animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
<div className="w-24 h-3 rounded animate-pulse bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-16 items-center gap-3 px-3">
|
||||
<Avatar.Root className="h-10 w-10 shrink-0">
|
||||
<div className="flex items-center h-16 gap-3 px-3">
|
||||
<Avatar.Root className="w-10 h-10 shrink-0">
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-10 w-10 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
|
||||
className="object-cover w-10 h-10 rounded-lg ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 rounded-lg bg-black ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50"
|
||||
className="w-10 h-10 bg-black rounded-lg ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="flex flex-col flex-1">
|
||||
<h5 className="max-w-[15rem] truncate font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{user?.name || user?.display_name || user?.displayName || "Anon"}
|
||||
</h5>
|
||||
@@ -559,10 +556,10 @@ export const User = memo(function User({
|
||||
<Avatar.Image
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-9 w-9 rounded-lg bg-black ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50"
|
||||
className="bg-black rounded-lg h-9 w-9 ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50"
|
||||
/>
|
||||
</Avatar.Root>
|
||||
<div className="h-6 flex-1">
|
||||
<div className="flex-1 h-6">
|
||||
<div className="max-w-[15rem] truncate font-semibold text-neutral-950 dark:text-neutral-50">
|
||||
{fallbackName}
|
||||
</div>
|
||||
@@ -579,24 +576,24 @@ export const User = memo(function User({
|
||||
alt={pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="h-9 w-9 rounded-lg bg-white object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
|
||||
className="object-cover bg-white rounded-lg h-9 w-9 ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={fallbackAvatar}
|
||||
alt={pubkey}
|
||||
className="h-9 w-9 rounded-lg bg-black ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50"
|
||||
className="bg-black rounded-lg h-9 w-9 ring-1 ring-neutral-200/50 dark:bg-white dark:ring-neutral-800/50"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div className="flex h-6 flex-1 items-start gap-2">
|
||||
<div className="flex items-start flex-1 h-6 gap-2">
|
||||
<div className="max-w-[15rem] truncate font-semibold text-neutral-950 dark:text-neutral-50">
|
||||
{user?.name ||
|
||||
user?.display_name ||
|
||||
user?.displayName ||
|
||||
fallbackName}
|
||||
</div>
|
||||
<div className="ml-auto inline-flex items-center gap-3">
|
||||
<div className="inline-flex items-center gap-3 ml-auto">
|
||||
<div className="text-neutral-500 dark:text-neutral-400">
|
||||
{createdAt}
|
||||
</div>
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
"clsx": "^2.1.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"jotai": "^2.6.4",
|
||||
"nostr-tools": "1.17.0",
|
||||
"react": "^18.2.0",
|
||||
"tailwind-merge": "^2.2.1"
|
||||
"nostr-tools": "^2.1.7",
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lume/tsconfig": "workspace:^",
|
||||
"@lume/types": "workspace:^",
|
||||
"@types/react": "^18.2.52",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user