rome -> eslint + prettier
This commit is contained in:
@@ -1,105 +1,105 @@
|
||||
import { createChat, getLastLogin } from "@libs/storage";
|
||||
import { Image } from "@shared/image";
|
||||
import { NetworkStatusIndicator } from "@shared/networkStatusIndicator";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { sendNativeNotification } from "@utils/notification";
|
||||
import { produce } from "immer";
|
||||
import { useContext, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { produce } from 'immer';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { createChat, getLastLogin } from '@libs/storage';
|
||||
|
||||
import { Image } from '@shared/image';
|
||||
import { NetworkStatusIndicator } from '@shared/networkStatusIndicator';
|
||||
import { RelayContext } from '@shared/relayProvider';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { sendNativeNotification } from '@utils/notification';
|
||||
|
||||
const lastLogin = await getLastLogin();
|
||||
|
||||
export function ActiveAccount({ data }: { data: any }) {
|
||||
const ndk = useContext(RelayContext);
|
||||
const queryClient = useQueryClient();
|
||||
const ndk = useContext(RelayContext);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { status, user } = useProfile(data.pubkey);
|
||||
const { status, user } = useProfile(data.pubkey);
|
||||
|
||||
const chat = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createChat(
|
||||
data.id,
|
||||
data.receiver_pubkey,
|
||||
data.sender_pubkey,
|
||||
data.content,
|
||||
data.tags,
|
||||
data.created_at,
|
||||
);
|
||||
},
|
||||
onSuccess: (data: any) => {
|
||||
const prev = queryClient.getQueryData(["chats"]);
|
||||
const next = produce(prev, (draft: any) => {
|
||||
const target = draft.findIndex(
|
||||
(m: { sender_pubkey: string }) => m.sender_pubkey === data,
|
||||
);
|
||||
if (target !== -1) {
|
||||
draft[target]["new_messages"] =
|
||||
draft[target]["new_messages"] + 1 || 1;
|
||||
} else {
|
||||
draft.push({ sender_pubkey: data, new_messages: 1 });
|
||||
}
|
||||
});
|
||||
queryClient.setQueryData(["chats"], next);
|
||||
},
|
||||
});
|
||||
const chat = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createChat(
|
||||
data.id,
|
||||
data.receiver_pubkey,
|
||||
data.sender_pubkey,
|
||||
data.content,
|
||||
data.tags,
|
||||
data.created_at
|
||||
);
|
||||
},
|
||||
onSuccess: (data: any) => {
|
||||
const prev = queryClient.getQueryData(['chats']);
|
||||
const next = produce(prev, (draft: any) => {
|
||||
const target = draft.findIndex(
|
||||
(m: { sender_pubkey: string }) => m.sender_pubkey === data
|
||||
);
|
||||
if (target !== -1) {
|
||||
draft[target]['new_messages'] = draft[target]['new_messages'] + 1 || 1;
|
||||
} else {
|
||||
draft.push({ sender_pubkey: data, new_messages: 1 });
|
||||
}
|
||||
});
|
||||
queryClient.setQueryData(['chats'], next);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const since = lastLogin > 0 ? lastLogin : Math.floor(Date.now() / 1000);
|
||||
const sub = ndk.subscribe(
|
||||
{
|
||||
kinds: [4],
|
||||
"#p": [data.pubkey],
|
||||
since: since,
|
||||
},
|
||||
{
|
||||
closeOnEose: false,
|
||||
},
|
||||
);
|
||||
useEffect(() => {
|
||||
const since = lastLogin > 0 ? lastLogin : Math.floor(Date.now() / 1000);
|
||||
const sub = ndk.subscribe(
|
||||
{
|
||||
kinds: [4],
|
||||
'#p': [data.pubkey],
|
||||
since: since,
|
||||
},
|
||||
{
|
||||
closeOnEose: false,
|
||||
}
|
||||
);
|
||||
|
||||
sub.addListener("event", (event) => {
|
||||
switch (event.kind) {
|
||||
case 4:
|
||||
// update state
|
||||
chat.mutate({
|
||||
id: event.id,
|
||||
receiver_pubkey: data.pubkey,
|
||||
sender_pubkey: event.pubkey,
|
||||
content: event.content,
|
||||
tags: event.tags,
|
||||
created_at: event.created_at,
|
||||
});
|
||||
// send native notifiation
|
||||
sendNativeNotification("You've received new message");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
sub.addListener('event', (event) => {
|
||||
switch (event.kind) {
|
||||
case 4:
|
||||
// update state
|
||||
chat.mutate({
|
||||
id: event.id,
|
||||
receiver_pubkey: data.pubkey,
|
||||
sender_pubkey: event.pubkey,
|
||||
content: event.content,
|
||||
tags: event.tags,
|
||||
created_at: event.created_at,
|
||||
});
|
||||
// send native notifiation
|
||||
sendNativeNotification("You've received new message");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
sub.stop();
|
||||
};
|
||||
}, []);
|
||||
return () => {
|
||||
sub.stop();
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (status === "loading") {
|
||||
return <div className="w-9 h-9 rounded-md bg-zinc-800 animate-pulse" />;
|
||||
}
|
||||
if (status === 'loading') {
|
||||
return <div className="h-9 w-9 animate-pulse rounded-md bg-zinc-800" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/app/user/${data.pubkey}`}
|
||||
className="relative inline-block h-9 w-9"
|
||||
>
|
||||
<Image
|
||||
src={user.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={data.npub}
|
||||
className="h-9 w-9 rounded-md object-cover"
|
||||
/>
|
||||
<NetworkStatusIndicator />
|
||||
</Link>
|
||||
);
|
||||
return (
|
||||
<Link to={`/app/user/${data.pubkey}`} className="relative inline-block h-9 w-9">
|
||||
<Image
|
||||
src={user.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={data.npub}
|
||||
className="h-9 w-9 rounded-md object-cover"
|
||||
/>
|
||||
<NetworkStatusIndicator />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { Image } from "@shared/image";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
|
||||
export function InactiveAccount({ data }: { data: any }) {
|
||||
const { user } = useProfile(data.npub);
|
||||
const { user } = useProfile(data.npub);
|
||||
|
||||
return (
|
||||
<div className="relative h-9 w-9 shrink-0">
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={data.npub}
|
||||
className="h-9 w-9 rounded object-cover"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative h-9 w-9 shrink-0">
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={data.npub}
|
||||
className="h-9 w-9 rounded object-cover"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,48 +1,49 @@
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "@shared/icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from '@shared/icons';
|
||||
|
||||
export function AppHeader({ reverse }: { reverse?: boolean }) {
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
const goBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const goForward = () => {
|
||||
navigate(1);
|
||||
};
|
||||
const goForward = () => {
|
||||
navigate(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={`shrink-0 flex h-11 w-full px-3 border-b border-zinc-900 items-center ${
|
||||
reverse ? "justify-start" : "justify-end"
|
||||
}`}
|
||||
>
|
||||
<div className="flex gap-2.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => goBack()}
|
||||
className="group inline-flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<ArrowLeftIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-500 group-hover:text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => goForward()}
|
||||
className="group inline-flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<ArrowRightIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-500 group-hover:text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={`flex h-11 w-full shrink-0 items-center border-b border-zinc-900 px-3 ${
|
||||
reverse ? 'justify-start' : 'justify-end'
|
||||
}`}
|
||||
>
|
||||
<div className="flex gap-2.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => goBack()}
|
||||
className="group inline-flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<ArrowLeftIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-500 group-hover:text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => goForward()}
|
||||
className="group inline-flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<ArrowRightIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-500 group-hover:text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { Navigation } from "@shared/navigation";
|
||||
import { Outlet, ScrollRestoration } from "react-router-dom";
|
||||
import { Outlet, ScrollRestoration } from 'react-router-dom';
|
||||
|
||||
import { Navigation } from '@shared/navigation';
|
||||
|
||||
export function AppLayout() {
|
||||
return (
|
||||
<div className="flex w-screen h-screen">
|
||||
<div className="relative flex flex-row shrink-0">
|
||||
<Navigation />
|
||||
</div>
|
||||
<div className="w-full h-full">
|
||||
<Outlet />
|
||||
<ScrollRestoration />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex h-screen w-screen">
|
||||
<div className="relative flex shrink-0 flex-row">
|
||||
<Navigation />
|
||||
</div>
|
||||
<div className="h-full w-full">
|
||||
<Outlet />
|
||||
<ScrollRestoration />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,65 +1,66 @@
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "@shared/icons";
|
||||
import { platform } from "@tauri-apps/api/os";
|
||||
import { Outlet, useNavigate } from "react-router-dom";
|
||||
import { platform } from '@tauri-apps/api/os';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from '@shared/icons';
|
||||
|
||||
const platformName = await platform();
|
||||
|
||||
export function AuthLayout() {
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
const goBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const goForward = () => {
|
||||
navigate(1);
|
||||
};
|
||||
const goForward = () => {
|
||||
navigate(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100">
|
||||
<div className="flex h-screen w-full flex-col">
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="relative h-11 shrink-0 border border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
|
||||
>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex h-full w-full flex-1 items-center px-2"
|
||||
>
|
||||
<div
|
||||
className={`flex h-full items-center gap-2 ${
|
||||
platformName === "darwin" ? "pl-[68px]" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => goBack()}
|
||||
className="group inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-zinc-900"
|
||||
>
|
||||
<ArrowLeftIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-500 group-hover:text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => goForward()}
|
||||
className="group inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-zinc-900"
|
||||
>
|
||||
<ArrowRightIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-500 group-hover:text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex min-h-0 w-full flex-1">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100">
|
||||
<div className="flex h-screen w-full flex-col">
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="relative h-11 shrink-0 border border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
|
||||
>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex h-full w-full flex-1 items-center px-2"
|
||||
>
|
||||
<div
|
||||
className={`flex h-full items-center gap-2 ${
|
||||
platformName === 'darwin' ? 'pl-[68px]' : ''
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => goBack()}
|
||||
className="group inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-zinc-900"
|
||||
>
|
||||
<ArrowLeftIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-500 group-hover:text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => goForward()}
|
||||
className="group inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-zinc-900"
|
||||
>
|
||||
<ArrowRightIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-500 group-hover:text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex min-h-0 w-full flex-1">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,69 +1,71 @@
|
||||
import { LoaderIcon, PlusIcon } from "@shared/icons";
|
||||
import { open } from "@tauri-apps/api/dialog";
|
||||
import { Body, fetch } from "@tauri-apps/api/http";
|
||||
import { createBlobFromFile } from "@utils/createBlobFromFile";
|
||||
import { useState } from "react";
|
||||
import { open } from '@tauri-apps/api/dialog';
|
||||
import { Body, fetch } from '@tauri-apps/api/http';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { LoaderIcon, PlusIcon } from '@shared/icons';
|
||||
|
||||
import { createBlobFromFile } from '@utils/createBlobFromFile';
|
||||
|
||||
export function AvatarUploader({ setPicture }: { setPicture: any }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const openFileDialog = async () => {
|
||||
const selected: any = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Image",
|
||||
extensions: ["png", "jpeg", "jpg", "gif"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (Array.isArray(selected)) {
|
||||
// user selected multiple files
|
||||
} else if (selected === null) {
|
||||
// user cancelled the selection
|
||||
} else {
|
||||
setLoading(true);
|
||||
const openFileDialog = async () => {
|
||||
const selected: any = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: 'Image',
|
||||
extensions: ['png', 'jpeg', 'jpg', 'gif'],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (Array.isArray(selected)) {
|
||||
// user selected multiple files
|
||||
} else if (selected === null) {
|
||||
// user cancelled the selection
|
||||
} else {
|
||||
setLoading(true);
|
||||
|
||||
const filename = selected.split("/").pop();
|
||||
const file = await createBlobFromFile(selected);
|
||||
const buf = await file.arrayBuffer();
|
||||
const filename = selected.split('/').pop();
|
||||
const file = await createBlobFromFile(selected);
|
||||
const buf = await file.arrayBuffer();
|
||||
|
||||
const res: { data: { file: { id: string } } } = await fetch(
|
||||
"https://void.cat/upload?cli=false",
|
||||
{
|
||||
method: "POST",
|
||||
timeout: 5,
|
||||
headers: {
|
||||
accept: "*/*",
|
||||
"Content-Type": "application/octet-stream",
|
||||
"V-Filename": filename,
|
||||
"V-Description": "Upload from https://lume.nu",
|
||||
"V-Strip-Metadata": "true",
|
||||
},
|
||||
body: Body.bytes(buf),
|
||||
},
|
||||
);
|
||||
const image = `https://void.cat/d/${res.data.file.id}.jpg`;
|
||||
const res: { data: { file: { id: string } } } = await fetch(
|
||||
'https://void.cat/upload?cli=false',
|
||||
{
|
||||
method: 'POST',
|
||||
timeout: 5,
|
||||
headers: {
|
||||
accept: '*/*',
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'V-Filename': filename,
|
||||
'V-Description': 'Upload from https://lume.nu',
|
||||
'V-Strip-Metadata': 'true',
|
||||
},
|
||||
body: Body.bytes(buf),
|
||||
}
|
||||
);
|
||||
const image = `https://void.cat/d/${res.data.file.id}.jpg`;
|
||||
|
||||
// update parent state
|
||||
setPicture(image);
|
||||
// update parent state
|
||||
setPicture(image);
|
||||
|
||||
// disable loader
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// disable loader
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openFileDialog()}
|
||||
className="w-full h-full inline-flex items-center justify-center bg-zinc-900/40"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-6 w-6 animate-spin text-zinc-100" />
|
||||
) : (
|
||||
<PlusIcon className="h-6 w-6 text-zinc-100" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openFileDialog()}
|
||||
className="inline-flex h-full w-full items-center justify-center bg-zinc-900/40"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-6 w-6 animate-spin text-zinc-100" />
|
||||
) : (
|
||||
<PlusIcon className="h-6 w-6 text-zinc-100" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,69 +1,71 @@
|
||||
import { LoaderIcon, PlusIcon } from "@shared/icons";
|
||||
import { open } from "@tauri-apps/api/dialog";
|
||||
import { Body, fetch } from "@tauri-apps/api/http";
|
||||
import { createBlobFromFile } from "@utils/createBlobFromFile";
|
||||
import { useState } from "react";
|
||||
import { open } from '@tauri-apps/api/dialog';
|
||||
import { Body, fetch } from '@tauri-apps/api/http';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { LoaderIcon, PlusIcon } from '@shared/icons';
|
||||
|
||||
import { createBlobFromFile } from '@utils/createBlobFromFile';
|
||||
|
||||
export function BannerUploader({ setBanner }: { setBanner: any }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const openFileDialog = async () => {
|
||||
const selected: any = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Image",
|
||||
extensions: ["png", "jpeg", "jpg", "gif"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (Array.isArray(selected)) {
|
||||
// user selected multiple files
|
||||
} else if (selected === null) {
|
||||
// user cancelled the selection
|
||||
} else {
|
||||
setLoading(true);
|
||||
const openFileDialog = async () => {
|
||||
const selected: any = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: 'Image',
|
||||
extensions: ['png', 'jpeg', 'jpg', 'gif'],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (Array.isArray(selected)) {
|
||||
// user selected multiple files
|
||||
} else if (selected === null) {
|
||||
// user cancelled the selection
|
||||
} else {
|
||||
setLoading(true);
|
||||
|
||||
const filename = selected.split("/").pop();
|
||||
const file = await createBlobFromFile(selected);
|
||||
const buf = await file.arrayBuffer();
|
||||
const filename = selected.split('/').pop();
|
||||
const file = await createBlobFromFile(selected);
|
||||
const buf = await file.arrayBuffer();
|
||||
|
||||
const res: { data: { file: { id: string } } } = await fetch(
|
||||
"https://void.cat/upload?cli=false",
|
||||
{
|
||||
method: "POST",
|
||||
timeout: 5,
|
||||
headers: {
|
||||
accept: "*/*",
|
||||
"Content-Type": "application/octet-stream",
|
||||
"V-Filename": filename,
|
||||
"V-Description": "Upload from https://lume.nu",
|
||||
"V-Strip-Metadata": "true",
|
||||
},
|
||||
body: Body.bytes(buf),
|
||||
},
|
||||
);
|
||||
const image = `https://void.cat/d/${res.data.file.id}.jpg`;
|
||||
const res: { data: { file: { id: string } } } = await fetch(
|
||||
'https://void.cat/upload?cli=false',
|
||||
{
|
||||
method: 'POST',
|
||||
timeout: 5,
|
||||
headers: {
|
||||
accept: '*/*',
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'V-Filename': filename,
|
||||
'V-Description': 'Upload from https://lume.nu',
|
||||
'V-Strip-Metadata': 'true',
|
||||
},
|
||||
body: Body.bytes(buf),
|
||||
}
|
||||
);
|
||||
const image = `https://void.cat/d/${res.data.file.id}.jpg`;
|
||||
|
||||
// update parent state
|
||||
setBanner(image);
|
||||
// update parent state
|
||||
setBanner(image);
|
||||
|
||||
// disable loader
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// disable loader
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openFileDialog()}
|
||||
className="w-full h-full inline-flex items-center justify-center bg-zinc-900/40"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-8 w-8 animate-spin text-zinc-100" />
|
||||
) : (
|
||||
<PlusIcon className="h-8 w-8 text-zinc-100" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openFileDialog()}
|
||||
className="inline-flex h-full w-full items-center justify-center bg-zinc-900/40"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-8 w-8 animate-spin text-zinc-100" />
|
||||
) : (
|
||||
<PlusIcon className="h-8 w-8 text-zinc-100" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import { ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { ReactNode } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function Button({
|
||||
preset,
|
||||
children,
|
||||
disabled = false,
|
||||
onClick = undefined,
|
||||
preset,
|
||||
children,
|
||||
disabled = false,
|
||||
onClick = undefined,
|
||||
}: {
|
||||
preset: "small" | "publish" | "large";
|
||||
children: ReactNode;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
preset: 'small' | 'publish' | 'large';
|
||||
children: ReactNode;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
}) {
|
||||
let preClass: string;
|
||||
switch (preset) {
|
||||
case "small":
|
||||
preClass =
|
||||
"w-min h-9 px-4 bg-fuchsia-500 rounded-md text-sm font-medium text-zinc-100 hover:bg-fuchsia-600";
|
||||
break;
|
||||
case "publish":
|
||||
preClass =
|
||||
"w-min h-9 px-4 bg-fuchsia-500 rounded-md text-sm font-medium text-zinc-100 hover:bg-fuchsia-600";
|
||||
break;
|
||||
case "large":
|
||||
preClass =
|
||||
"h-11 w-full bg-fuchsia-500 rounded-md font-medium text-zinc-100 hover:bg-fuchsia-600";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
let preClass: string;
|
||||
switch (preset) {
|
||||
case 'small':
|
||||
preClass =
|
||||
'w-min h-9 px-4 bg-fuchsia-500 rounded-md text-sm font-medium text-zinc-100 hover:bg-fuchsia-600';
|
||||
break;
|
||||
case 'publish':
|
||||
preClass =
|
||||
'w-min h-9 px-4 bg-fuchsia-500 rounded-md text-sm font-medium text-zinc-100 hover:bg-fuchsia-600';
|
||||
break;
|
||||
case 'large':
|
||||
preClass =
|
||||
'h-11 w-full bg-fuchsia-500 rounded-md font-medium text-zinc-100 hover:bg-fuchsia-600';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={twMerge(
|
||||
"inline-flex items-center justify-center gap-1 transform active:translate-y-1 disabled:pointer-events-none disabled:opacity-50 focus:outline-none",
|
||||
preClass,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={twMerge(
|
||||
'inline-flex transform items-center justify-center gap-1 focus:outline-none active:translate-y-1 disabled:pointer-events-none disabled:opacity-50',
|
||||
preClass
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,132 +1,134 @@
|
||||
import { PlusCircleIcon } from "@shared/icons";
|
||||
import { open } from "@tauri-apps/api/dialog";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { Body, fetch } from "@tauri-apps/api/http";
|
||||
import { createBlobFromFile } from "@utils/createBlobFromFile";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Transforms } from "slate";
|
||||
import { useSlateStatic } from "slate-react";
|
||||
import { open } from '@tauri-apps/api/dialog';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { Body, fetch } from '@tauri-apps/api/http';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Transforms } from 'slate';
|
||||
import { useSlateStatic } from 'slate-react';
|
||||
|
||||
import { PlusCircleIcon } from '@shared/icons';
|
||||
|
||||
import { createBlobFromFile } from '@utils/createBlobFromFile';
|
||||
|
||||
export function ImageUploader() {
|
||||
const editor = useSlateStatic();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const editor = useSlateStatic();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const insertImage = (editor, url) => {
|
||||
const image = { type: "image", url, children: [{ text: url }] };
|
||||
Transforms.insertNodes(editor, image);
|
||||
};
|
||||
const insertImage = (editor, url) => {
|
||||
const image = { type: 'image', url, children: [{ text: url }] };
|
||||
Transforms.insertNodes(editor, image);
|
||||
};
|
||||
|
||||
const uploadToVoidCat = useCallback(
|
||||
async (filepath) => {
|
||||
const filename = filepath.split("/").pop();
|
||||
const file = await createBlobFromFile(filepath);
|
||||
const buf = await file.arrayBuffer();
|
||||
const uploadToVoidCat = useCallback(
|
||||
async (filepath) => {
|
||||
const filename = filepath.split('/').pop();
|
||||
const file = await createBlobFromFile(filepath);
|
||||
const buf = await file.arrayBuffer();
|
||||
|
||||
try {
|
||||
const res: { data: { file: { id: string } } } = await fetch(
|
||||
"https://void.cat/upload?cli=false",
|
||||
{
|
||||
method: "POST",
|
||||
timeout: 5,
|
||||
headers: {
|
||||
accept: "*/*",
|
||||
"Content-Type": "application/octet-stream",
|
||||
"V-Filename": filename,
|
||||
"V-Description": "Uploaded from https://lume.nu",
|
||||
"V-Strip-Metadata": "true",
|
||||
},
|
||||
body: Body.bytes(buf),
|
||||
},
|
||||
);
|
||||
const image = `https://void.cat/d/${res.data.file.id}.webp`;
|
||||
// update parent state
|
||||
insertImage(editor, image);
|
||||
// reset loading state
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
// reset loading state
|
||||
setLoading(false);
|
||||
// handle error
|
||||
if (error instanceof SyntaxError) {
|
||||
// Unexpected token < in JSON
|
||||
console.log("There was a SyntaxError", error);
|
||||
} else {
|
||||
console.log("There was an error", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
[editor],
|
||||
);
|
||||
try {
|
||||
const res: { data: { file: { id: string } } } = await fetch(
|
||||
'https://void.cat/upload?cli=false',
|
||||
{
|
||||
method: 'POST',
|
||||
timeout: 5,
|
||||
headers: {
|
||||
accept: '*/*',
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'V-Filename': filename,
|
||||
'V-Description': 'Uploaded from https://lume.nu',
|
||||
'V-Strip-Metadata': 'true',
|
||||
},
|
||||
body: Body.bytes(buf),
|
||||
}
|
||||
);
|
||||
const image = `https://void.cat/d/${res.data.file.id}.webp`;
|
||||
// update parent state
|
||||
insertImage(editor, image);
|
||||
// reset loading state
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
// reset loading state
|
||||
setLoading(false);
|
||||
// handle error
|
||||
if (error instanceof SyntaxError) {
|
||||
// Unexpected token < in JSON
|
||||
console.log('There was a SyntaxError', error);
|
||||
} else {
|
||||
console.log('There was an error', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
[editor]
|
||||
);
|
||||
|
||||
const openFileDialog = async () => {
|
||||
const selected: any = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Image",
|
||||
extensions: ["png", "jpeg", "jpg", "gif"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (Array.isArray(selected)) {
|
||||
// user selected multiple files
|
||||
} else if (selected === null) {
|
||||
// user cancelled the selection
|
||||
} else {
|
||||
setLoading(true);
|
||||
// upload file
|
||||
uploadToVoidCat(selected);
|
||||
}
|
||||
};
|
||||
const openFileDialog = async () => {
|
||||
const selected: any = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: 'Image',
|
||||
extensions: ['png', 'jpeg', 'jpg', 'gif'],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (Array.isArray(selected)) {
|
||||
// user selected multiple files
|
||||
} else if (selected === null) {
|
||||
// user cancelled the selection
|
||||
} else {
|
||||
setLoading(true);
|
||||
// upload file
|
||||
uploadToVoidCat(selected);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initFileDrop() {
|
||||
const unlisten = await listen("tauri://file-drop", (event) => {
|
||||
// set loading state
|
||||
setLoading(true);
|
||||
// upload file
|
||||
uploadToVoidCat(event.payload[0]);
|
||||
});
|
||||
useEffect(() => {
|
||||
async function initFileDrop() {
|
||||
const unlisten = await listen('tauri://file-drop', (event) => {
|
||||
// set loading state
|
||||
setLoading(true);
|
||||
// upload file
|
||||
uploadToVoidCat(event.payload[0]);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten();
|
||||
};
|
||||
}
|
||||
return () => {
|
||||
unlisten();
|
||||
};
|
||||
}
|
||||
|
||||
initFileDrop();
|
||||
}, [uploadToVoidCat]);
|
||||
initFileDrop();
|
||||
}, [uploadToVoidCat]);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openFileDialog()}
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded hover:bg-zinc-800"
|
||||
>
|
||||
{loading ? (
|
||||
<svg
|
||||
className="h-4 w-4 animate-spin text-black dark:text-zinc-100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<title id="loading">Loading</title>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<PlusCircleIcon width={20} height={20} className="text-zinc-500" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openFileDialog()}
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded hover:bg-zinc-800"
|
||||
>
|
||||
{loading ? (
|
||||
<svg
|
||||
className="h-4 w-4 animate-spin text-black dark:text-zinc-100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<title id="loading">Loading</title>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<PlusCircleIcon width={20} height={20} className="text-zinc-500" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,96 +1,94 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Button } from "@shared/button";
|
||||
import { Post } from "@shared/composer/types/post";
|
||||
import { User } from "@shared/composer/user";
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { Fragment } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { Post } from '@shared/composer/types/post';
|
||||
import { User } from '@shared/composer/user';
|
||||
import {
|
||||
CancelIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
ComposeIcon,
|
||||
} from "@shared/icons";
|
||||
import { useComposer } from "@stores/composer";
|
||||
import { COMPOSE_SHORTCUT } from "@stores/shortcuts";
|
||||
import { useAccount } from "@utils/hooks/useAccount";
|
||||
import { Fragment } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
CancelIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
ComposeIcon,
|
||||
} from '@shared/icons';
|
||||
|
||||
import { useComposer } from '@stores/composer';
|
||||
import { COMPOSE_SHORTCUT } from '@stores/shortcuts';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
|
||||
export function Composer() {
|
||||
const { account } = useAccount();
|
||||
const { account } = useAccount();
|
||||
|
||||
const [toggle, open] = useComposer((state) => [
|
||||
state.toggleModal,
|
||||
state.open,
|
||||
]);
|
||||
const [toggle, open] = useComposer((state) => [state.toggleModal, state.open]);
|
||||
|
||||
const closeModal = () => {
|
||||
toggle(false);
|
||||
};
|
||||
const closeModal = () => {
|
||||
toggle(false);
|
||||
};
|
||||
|
||||
useHotkeys(COMPOSE_SHORTCUT, () => toggle(true));
|
||||
useHotkeys(COMPOSE_SHORTCUT, () => toggle(true));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => toggle(true)} preset="small">
|
||||
<ComposeIcon width={14} height={14} />
|
||||
Compose
|
||||
</Button>
|
||||
<Transition appear show={open} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative h-min w-full max-w-xl rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="flex items-center justify-between px-4 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div>{account && <User pubkey={account.pubkey} />}</div>
|
||||
<span>
|
||||
<ChevronRightIcon
|
||||
width={14}
|
||||
height={14}
|
||||
className="text-zinc-500"
|
||||
/>
|
||||
</span>
|
||||
<div className="inline-flex h-7 w-max items-center justify-center gap-0.5 rounded bg-zinc-800 pl-3 pr-1.5 text-sm font-medium text-zinc-400">
|
||||
New Post
|
||||
<ChevronDownIcon width={14} height={14} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={closeModal}
|
||||
onKeyDown={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-800"
|
||||
>
|
||||
<CancelIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{account && <Post />}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => toggle(true)} preset="small">
|
||||
<ComposeIcon width={14} height={14} />
|
||||
Compose
|
||||
</Button>
|
||||
<Transition appear show={open} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative h-min w-full max-w-xl rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="flex items-center justify-between px-4 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div>{account && <User pubkey={account.pubkey} />}</div>
|
||||
<span>
|
||||
<ChevronRightIcon
|
||||
width={14}
|
||||
height={14}
|
||||
className="text-zinc-500"
|
||||
/>
|
||||
</span>
|
||||
<div className="inline-flex h-7 w-max items-center justify-center gap-0.5 rounded bg-zinc-800 pl-3 pr-1.5 text-sm font-medium text-zinc-400">
|
||||
New Post
|
||||
<ChevronDownIcon width={14} height={14} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={closeModal}
|
||||
onKeyDown={closeModal}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-800"
|
||||
>
|
||||
<CancelIcon width={16} height={16} className="text-zinc-500" />
|
||||
</div>
|
||||
</div>
|
||||
{account && <Post />}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,179 +1,171 @@
|
||||
import { usePublish } from "@libs/ndk";
|
||||
import { Button } from "@shared/button";
|
||||
import { ImageUploader } from "@shared/composer/imageUploader";
|
||||
import { TrashIcon } from "@shared/icons";
|
||||
import { MentionNote } from "@shared/notes/mentions/note";
|
||||
import { useComposer } from "@stores/composer";
|
||||
import { FULL_RELAYS } from "@stores/constants";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Node, Transforms, createEditor } from "slate";
|
||||
import { withHistory } from "slate-history";
|
||||
import {
|
||||
Editable,
|
||||
ReactEditor,
|
||||
Slate,
|
||||
useSlateStatic,
|
||||
withReact,
|
||||
} from "slate-react";
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Node, Transforms, createEditor } from 'slate';
|
||||
import { withHistory } from 'slate-history';
|
||||
import { Editable, ReactEditor, Slate, useSlateStatic, withReact } from 'slate-react';
|
||||
|
||||
import { usePublish } from '@libs/ndk';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { ImageUploader } from '@shared/composer/imageUploader';
|
||||
import { TrashIcon } from '@shared/icons';
|
||||
import { MentionNote } from '@shared/notes/mentions/note';
|
||||
|
||||
import { useComposer } from '@stores/composer';
|
||||
import { FULL_RELAYS } from '@stores/constants';
|
||||
|
||||
const withImages = (editor) => {
|
||||
const { isVoid } = editor;
|
||||
const { isVoid } = editor;
|
||||
|
||||
editor.isVoid = (element) => {
|
||||
return element.type === "image" ? true : isVoid(element);
|
||||
};
|
||||
editor.isVoid = (element) => {
|
||||
return element.type === 'image' ? true : isVoid(element);
|
||||
};
|
||||
|
||||
return editor;
|
||||
return editor;
|
||||
};
|
||||
|
||||
const ImagePreview = ({
|
||||
attributes,
|
||||
children,
|
||||
element,
|
||||
attributes,
|
||||
children,
|
||||
element,
|
||||
}: {
|
||||
attributes: any;
|
||||
children: any;
|
||||
element: any;
|
||||
attributes: any;
|
||||
children: any;
|
||||
element: any;
|
||||
}) => {
|
||||
const editor: any = useSlateStatic();
|
||||
const path = ReactEditor.findPath(editor, element);
|
||||
const editor: any = useSlateStatic();
|
||||
const path = ReactEditor.findPath(editor, element);
|
||||
|
||||
return (
|
||||
<figure {...attributes} className="m-0 mt-3">
|
||||
{children}
|
||||
<div contentEditable={false} className="relative">
|
||||
<img
|
||||
alt={element.url}
|
||||
src={element.url}
|
||||
className="m-0 h-auto max-h-[300px] w-full rounded-md object-cover"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
||||
className="absolute right-2 top-2 inline-flex h-7 w-7 items-center justify-center gap-0.5 rounded bg-zinc-800 text-base font-medium text-zinc-400 shadow-mini-button hover:bg-zinc-700"
|
||||
>
|
||||
<TrashIcon width={14} height={14} className="text-zinc-100" />
|
||||
</button>
|
||||
</div>
|
||||
</figure>
|
||||
);
|
||||
return (
|
||||
<figure {...attributes} className="m-0 mt-3">
|
||||
{children}
|
||||
<div contentEditable={false} className="relative">
|
||||
<img
|
||||
alt={element.url}
|
||||
src={element.url}
|
||||
className="m-0 h-auto max-h-[300px] w-full rounded-md object-cover"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
||||
className="shadow-mini-button absolute right-2 top-2 inline-flex h-7 w-7 items-center justify-center gap-0.5 rounded bg-zinc-800 text-base font-medium text-zinc-400 hover:bg-zinc-700"
|
||||
>
|
||||
<TrashIcon width={14} height={14} className="text-zinc-100" />
|
||||
</button>
|
||||
</div>
|
||||
</figure>
|
||||
);
|
||||
};
|
||||
|
||||
export function Post() {
|
||||
const publish = usePublish();
|
||||
const editor = useMemo(
|
||||
() => withReact(withImages(withHistory(createEditor()))),
|
||||
[],
|
||||
);
|
||||
const publish = usePublish();
|
||||
const editor = useMemo(() => withReact(withImages(withHistory(createEditor()))), []);
|
||||
|
||||
const [repost, reply, toggle] = useComposer((state) => [
|
||||
state.repost,
|
||||
state.reply,
|
||||
state.toggleModal,
|
||||
]);
|
||||
const [content, setContent] = useState<Node[]>([
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const [repost, reply, toggle] = useComposer((state) => [
|
||||
state.repost,
|
||||
state.reply,
|
||||
state.toggleModal,
|
||||
]);
|
||||
const [content, setContent] = useState<Node[]>([
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const serialize = useCallback((nodes: Node[]) => {
|
||||
return nodes.map((n) => Node.string(n)).join("\n");
|
||||
}, []);
|
||||
const serialize = useCallback((nodes: Node[]) => {
|
||||
return nodes.map((n) => Node.string(n)).join('\n');
|
||||
}, []);
|
||||
|
||||
const getRef = () => {
|
||||
if (repost.id) {
|
||||
return repost.id;
|
||||
} else if (reply.id) {
|
||||
return reply.id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const getRef = () => {
|
||||
if (repost.id) {
|
||||
return repost.id;
|
||||
} else if (reply.id) {
|
||||
return reply.id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const refID = getRef();
|
||||
const refID = getRef();
|
||||
|
||||
const submit = async () => {
|
||||
let tags: string[][] = [];
|
||||
let kind: number;
|
||||
const submit = async () => {
|
||||
let tags: string[][] = [];
|
||||
let kind: number;
|
||||
|
||||
if (repost.id && repost.pubkey) {
|
||||
kind = 6;
|
||||
tags = [
|
||||
["e", repost.id, FULL_RELAYS[0], "root"],
|
||||
["p", repost.pubkey],
|
||||
];
|
||||
} else if (reply.id && reply.pubkey) {
|
||||
kind = 1;
|
||||
if (reply.root && reply.root !== reply.id) {
|
||||
tags = [
|
||||
["e", reply.id, FULL_RELAYS[0], "root"],
|
||||
["e", reply.root, FULL_RELAYS[0], "reply"],
|
||||
["p", reply.pubkey],
|
||||
];
|
||||
} else {
|
||||
tags = [
|
||||
["e", reply.id, FULL_RELAYS[0], "root"],
|
||||
["p", reply.pubkey],
|
||||
];
|
||||
}
|
||||
} else {
|
||||
kind = 1;
|
||||
tags = [];
|
||||
}
|
||||
if (repost.id && repost.pubkey) {
|
||||
kind = 6;
|
||||
tags = [
|
||||
['e', repost.id, FULL_RELAYS[0], 'root'],
|
||||
['p', repost.pubkey],
|
||||
];
|
||||
} else if (reply.id && reply.pubkey) {
|
||||
kind = 1;
|
||||
if (reply.root && reply.root !== reply.id) {
|
||||
tags = [
|
||||
['e', reply.id, FULL_RELAYS[0], 'root'],
|
||||
['e', reply.root, FULL_RELAYS[0], 'reply'],
|
||||
['p', reply.pubkey],
|
||||
];
|
||||
} else {
|
||||
tags = [
|
||||
['e', reply.id, FULL_RELAYS[0], 'root'],
|
||||
['p', reply.pubkey],
|
||||
];
|
||||
}
|
||||
} else {
|
||||
kind = 1;
|
||||
tags = [];
|
||||
}
|
||||
|
||||
// serialize content
|
||||
const serializedContent = serialize(content);
|
||||
// serialize content
|
||||
const serializedContent = serialize(content);
|
||||
|
||||
// publish message
|
||||
await publish({ content: serializedContent, kind, tags });
|
||||
// publish message
|
||||
await publish({ content: serializedContent, kind, tags });
|
||||
|
||||
// close modal
|
||||
toggle(false);
|
||||
};
|
||||
// close modal
|
||||
toggle(false);
|
||||
};
|
||||
|
||||
const renderElement = useCallback((props: any) => {
|
||||
switch (props.element.type) {
|
||||
case "image":
|
||||
if (props.element.url) {
|
||||
return <ImagePreview {...props} />;
|
||||
}
|
||||
default:
|
||||
return <p {...props.attributes}>{props.children}</p>;
|
||||
}
|
||||
}, []);
|
||||
const renderElement = useCallback((props: any) => {
|
||||
switch (props.element.type) {
|
||||
case 'image':
|
||||
if (props.element.url) {
|
||||
return <ImagePreview {...props} />;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return <p {...props.attributes}>{props.children}</p>;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Slate editor={editor} value={content} onChange={setContent}>
|
||||
<div className="flex h-full flex-col px-4 pb-4">
|
||||
<div className="flex h-full w-full gap-2">
|
||||
<div className="flex w-8 shrink-0 items-center justify-center">
|
||||
<div className="h-full w-[2px] bg-zinc-800" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Editable
|
||||
autoFocus
|
||||
placeholder={
|
||||
refID ? "Share your thoughts on it" : "What's on your mind?"
|
||||
}
|
||||
spellCheck="false"
|
||||
className={`${refID ? "!min-h-42" : "!min-h-[86px]"} markdown`}
|
||||
renderElement={renderElement}
|
||||
/>
|
||||
{refID && <MentionNote id={refID} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<ImageUploader />
|
||||
<Button onClick={() => submit()} preset="publish">
|
||||
Publish
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Slate>
|
||||
);
|
||||
return (
|
||||
<Slate editor={editor} value={content} onChange={setContent}>
|
||||
<div className="flex h-full flex-col px-4 pb-4">
|
||||
<div className="flex h-full w-full gap-2">
|
||||
<div className="flex w-8 shrink-0 items-center justify-center">
|
||||
<div className="h-full w-[2px] bg-zinc-800" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Editable
|
||||
placeholder={refID ? 'Share your thoughts on it' : "What's on your mind?"}
|
||||
spellCheck="false"
|
||||
className={`${refID ? '!min-h-42' : '!min-h-[86px]'} markdown`}
|
||||
renderElement={renderElement}
|
||||
/>
|
||||
{refID && <MentionNote id={refID} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<ImageUploader />
|
||||
<Button onClick={() => submit()} preset="publish">
|
||||
Publish
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Slate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import { Image } from "@shared/image";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
|
||||
export function User({ pubkey }: { pubkey: string }) {
|
||||
const { user } = useProfile(pubkey);
|
||||
const { user } = useProfile(pubkey);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-8 w-8 object-cover"
|
||||
/>
|
||||
</div>
|
||||
<h5 className="text-base font-semibold leading-none text-zinc-100">
|
||||
{user?.nip05 || user?.name || (
|
||||
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-8 w-8 object-cover"
|
||||
/>
|
||||
</div>
|
||||
<h5 className="text-base font-semibold leading-none text-zinc-100">
|
||||
{user?.nip05 || user?.name || (
|
||||
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,328 +1,339 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { usePublish } from "@libs/ndk";
|
||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
import { AvatarUploader } from "@shared/avatarUploader";
|
||||
import { BannerUploader } from "@shared/bannerUploader";
|
||||
import {
|
||||
CancelIcon,
|
||||
CheckCircleIcon,
|
||||
LoaderIcon,
|
||||
UnverifiedIcon,
|
||||
} from "@shared/icons";
|
||||
import { Image } from "@shared/image";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { fetch } from "@tauri-apps/api/http";
|
||||
import { useAccount } from "@utils/hooks/useAccount";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { fetch } from '@tauri-apps/api/http';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { usePublish } from '@libs/ndk';
|
||||
|
||||
import { AvatarUploader } from '@shared/avatarUploader';
|
||||
import { BannerUploader } from '@shared/bannerUploader';
|
||||
import { CancelIcon, CheckCircleIcon, LoaderIcon, UnverifiedIcon } from '@shared/icons';
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
|
||||
export function EditProfileModal() {
|
||||
const queryClient = useQueryClient();
|
||||
const publish = usePublish();
|
||||
const queryClient = useQueryClient();
|
||||
const publish = usePublish();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [picture, setPicture] = useState(DEFAULT_AVATAR);
|
||||
const [banner, setBanner] = useState("");
|
||||
const [nip05, setNIP05] = useState({ verified: false, text: "" });
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [picture, setPicture] = useState(DEFAULT_AVATAR);
|
||||
const [banner, setBanner] = useState('');
|
||||
const [nip05, setNIP05] = useState({ verified: false, text: '' });
|
||||
|
||||
const { account } = useAccount();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
setError,
|
||||
formState: { isValid, errors },
|
||||
} = useForm({
|
||||
defaultValues: async () => {
|
||||
const res: any = queryClient.getQueryData(["user", account.pubkey]);
|
||||
if (res.image) {
|
||||
setPicture(res.image);
|
||||
}
|
||||
if (res.banner) {
|
||||
setBanner(res.banner);
|
||||
}
|
||||
if (res.nip05) {
|
||||
setNIP05((prev) => ({ ...prev, text: res.nip05 }));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
});
|
||||
const { account } = useAccount();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
setError,
|
||||
formState: { isValid, errors },
|
||||
} = useForm({
|
||||
defaultValues: async () => {
|
||||
const res: any = queryClient.getQueryData(['user', account.pubkey]);
|
||||
if (res.image) {
|
||||
setPicture(res.image);
|
||||
}
|
||||
if (res.banner) {
|
||||
setBanner(res.banner);
|
||||
}
|
||||
if (res.nip05) {
|
||||
setNIP05((prev) => ({ ...prev, text: res.nip05 }));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const verifyNIP05 = async (data: string) => {
|
||||
if (data) {
|
||||
const url = data.split("@");
|
||||
const username = url[0];
|
||||
const service = url[1];
|
||||
const verifyURL = `https://${service}/.well-known/nostr.json?name=${username}`;
|
||||
const verifyNIP05 = async (data: string) => {
|
||||
if (data) {
|
||||
const url = data.split('@');
|
||||
const username = url[0];
|
||||
const service = url[1];
|
||||
const verifyURL = `https://${service}/.well-known/nostr.json?name=${username}`;
|
||||
|
||||
const res: any = await fetch(verifyURL, {
|
||||
method: "GET",
|
||||
timeout: 30,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
});
|
||||
const res: any = await fetch(verifyURL, {
|
||||
method: 'GET',
|
||||
timeout: 30,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) return false;
|
||||
if (res.data.names[username] === account.pubkey) {
|
||||
setNIP05((prev) => ({ ...prev, verified: true }));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!res.ok) return false;
|
||||
if (res.data.names[username] === account.pubkey) {
|
||||
setNIP05((prev) => ({ ...prev, verified: true }));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
// start loading
|
||||
setLoading(true);
|
||||
const onSubmit = async (data: any) => {
|
||||
// start loading
|
||||
setLoading(true);
|
||||
|
||||
let event: NDKEvent;
|
||||
let event: NDKEvent;
|
||||
|
||||
const content = {
|
||||
...data,
|
||||
username: data.name,
|
||||
display_name: data.name,
|
||||
bio: data.about,
|
||||
image: data.picture,
|
||||
};
|
||||
const content = {
|
||||
...data,
|
||||
username: data.name,
|
||||
display_name: data.name,
|
||||
bio: data.about,
|
||||
image: data.picture,
|
||||
};
|
||||
|
||||
if (data.nip05) {
|
||||
const verify = await verifyNIP05(data.nip05);
|
||||
if (verify) {
|
||||
event = await publish({
|
||||
content: JSON.stringify({ ...content, nip05: data.nip05 }),
|
||||
kind: 0,
|
||||
tags: [],
|
||||
});
|
||||
} else {
|
||||
setNIP05((prev) => ({ ...prev, verified: false }));
|
||||
setError("nip05", {
|
||||
type: "manual",
|
||||
message: "Can't verify your Lume ID / NIP-05, please check again",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
event = await publish({
|
||||
content: JSON.stringify(content),
|
||||
kind: 0,
|
||||
tags: [],
|
||||
});
|
||||
}
|
||||
if (data.nip05) {
|
||||
const verify = await verifyNIP05(data.nip05);
|
||||
if (verify) {
|
||||
event = await publish({
|
||||
content: JSON.stringify({ ...content, nip05: data.nip05 }),
|
||||
kind: 0,
|
||||
tags: [],
|
||||
});
|
||||
} else {
|
||||
setNIP05((prev) => ({ ...prev, verified: false }));
|
||||
setError('nip05', {
|
||||
type: 'manual',
|
||||
message: "Can't verify your Lume ID / NIP-05, please check again",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
event = await publish({
|
||||
content: JSON.stringify(content),
|
||||
kind: 0,
|
||||
tags: [],
|
||||
});
|
||||
}
|
||||
|
||||
if (event.id) {
|
||||
setTimeout(() => {
|
||||
// invalid cache
|
||||
queryClient.invalidateQueries(["user", account.pubkey]);
|
||||
// reset form
|
||||
reset();
|
||||
// reset state
|
||||
setLoading(false);
|
||||
setIsOpen(false);
|
||||
setPicture(DEFAULT_AVATAR);
|
||||
setBanner(null);
|
||||
}, 1200);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
if (event.id) {
|
||||
setTimeout(() => {
|
||||
// invalid cache
|
||||
queryClient.invalidateQueries(['user', account.pubkey]);
|
||||
// reset form
|
||||
reset();
|
||||
// reset state
|
||||
setLoading(false);
|
||||
setIsOpen(false);
|
||||
setPicture(DEFAULT_AVATAR);
|
||||
setBanner(null);
|
||||
}, 1200);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!nip05.verified && /\S+@\S+\.\S+/.test(nip05.text)) {
|
||||
verifyNIP05(nip05.text);
|
||||
}
|
||||
}, [nip05.text]);
|
||||
useEffect(() => {
|
||||
if (!nip05.verified && /\S+@\S+\.\S+/.test(nip05.text)) {
|
||||
verifyNIP05(nip05.text);
|
||||
}
|
||||
}, [nip05.text]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openModal()}
|
||||
className="inline-flex w-36 h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500 text-sm font-medium"
|
||||
>
|
||||
Edit profile
|
||||
</button>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col rounded-lg border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-semibold leading-none text-zinc-100"
|
||||
>
|
||||
Edit profile
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<CancelIcon className="w-5 h-5 text-zinc-300" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col overflow-y-auto">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="mb-0">
|
||||
<input
|
||||
type={"hidden"}
|
||||
{...register("picture")}
|
||||
value={picture}
|
||||
className="relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||
/>
|
||||
<input
|
||||
type={"hidden"}
|
||||
{...register("banner")}
|
||||
value={banner}
|
||||
className="relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||
/>
|
||||
<div className="relative">
|
||||
<div className="relative w-full h-44 bg-zinc-800">
|
||||
<Image
|
||||
src={banner}
|
||||
fallback="https://void.cat/d/QY1myro5tkHVs2nY7dy74b.jpg"
|
||||
alt="user's banner"
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-10 w-full h-full">
|
||||
<BannerUploader setBanner={setBanner} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 mb-5">
|
||||
<div className="z-10 relative h-14 w-14 -mt-7">
|
||||
<Image
|
||||
src={picture}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt="user's avatar"
|
||||
className="h-14 w-14 object-cover ring-2 ring-zinc-900 rounded-lg"
|
||||
/>
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-10 w-full h-full">
|
||||
<AvatarUploader setPicture={setPicture} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 px-4 pb-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type={"text"}
|
||||
{...register("name", {
|
||||
required: true,
|
||||
minLength: 4,
|
||||
})}
|
||||
spellCheck={false}
|
||||
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
|
||||
Lume ID / NIP-05
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
{...register("nip05", {
|
||||
required: true,
|
||||
minLength: 4,
|
||||
})}
|
||||
spellCheck={false}
|
||||
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
|
||||
/>
|
||||
<div className="absolute top-1/2 right-2 transform -translate-y-1/2">
|
||||
{nip05.verified ? (
|
||||
<span className="inline-flex items-center gap-1 rounded h-6 px-2 bg-green-500 text-sm font-medium">
|
||||
<CheckCircleIcon className="w-4 h-4 text-white" />
|
||||
Verified
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 rounded h-6 px-2 bg-red-500 text-sm font-medium">
|
||||
<UnverifiedIcon className="w-4 h-4 text-white" />
|
||||
Unverified
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{errors.nip05 && (
|
||||
<p className="mt-1 text-sm text-red-400">
|
||||
{errors.nip05.message.toString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
|
||||
Bio
|
||||
</label>
|
||||
<textarea
|
||||
{...register("about")}
|
||||
spellCheck={false}
|
||||
className="relative resize-none h-20 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
|
||||
Website
|
||||
</label>
|
||||
<input
|
||||
type={"text"}
|
||||
{...register("website", { required: false })}
|
||||
spellCheck={false}
|
||||
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isValid}
|
||||
className="inline-flex items-center justify-center gap-1 transform active:translate-y-1 disabled:pointer-events-none disabled:opacity-50 focus:outline-none h-11 w-full bg-fuchsia-500 rounded-md font-medium text-zinc-100 hover:bg-fuchsia-600"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
"Update"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openModal()}
|
||||
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-zinc-900 text-sm font-medium hover:bg-fuchsia-500"
|
||||
>
|
||||
Edit profile
|
||||
</button>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col rounded-lg border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-semibold leading-none text-zinc-100"
|
||||
>
|
||||
Edit profile
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<CancelIcon className="h-5 w-5 text-zinc-300" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col overflow-y-auto">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="mb-0">
|
||||
<input
|
||||
type={'hidden'}
|
||||
{...register('picture')}
|
||||
value={picture}
|
||||
className="shadow-input relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||
/>
|
||||
<input
|
||||
type={'hidden'}
|
||||
{...register('banner')}
|
||||
value={banner}
|
||||
className="shadow-input relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
|
||||
/>
|
||||
<div className="relative">
|
||||
<div className="relative h-44 w-full bg-zinc-800">
|
||||
<Image
|
||||
src={banner}
|
||||
fallback="https://void.cat/d/QY1myro5tkHVs2nY7dy74b.jpg"
|
||||
alt="user's banner"
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
<div className="absolute left-1/2 top-1/2 z-10 h-full w-full -translate-x-1/2 -translate-y-1/2 transform">
|
||||
<BannerUploader setBanner={setBanner} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-5 px-4">
|
||||
<div className="relative z-10 -mt-7 h-14 w-14">
|
||||
<Image
|
||||
src={picture}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt="user's avatar"
|
||||
className="h-14 w-14 rounded-lg object-cover ring-2 ring-zinc-900"
|
||||
/>
|
||||
<div className="absolute left-1/2 top-1/2 z-10 h-full w-full -translate-x-1/2 -translate-y-1/2 transform">
|
||||
<AvatarUploader setPicture={setPicture} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 px-4 pb-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type={'text'}
|
||||
{...register('name', {
|
||||
required: true,
|
||||
minLength: 4,
|
||||
})}
|
||||
spellCheck={false}
|
||||
className="relative h-10 w-full rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label
|
||||
htmlFor="nip05"
|
||||
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
|
||||
>
|
||||
Lume ID / NIP-05
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
{...register('nip05', {
|
||||
required: true,
|
||||
minLength: 4,
|
||||
})}
|
||||
spellCheck={false}
|
||||
className="relative h-10 w-full rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform">
|
||||
{nip05.verified ? (
|
||||
<span className="inline-flex h-6 items-center gap-1 rounded bg-green-500 px-2 text-sm font-medium">
|
||||
<CheckCircleIcon className="h-4 w-4 text-white" />
|
||||
Verified
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex h-6 items-center gap-1 rounded bg-red-500 px-2 text-sm font-medium">
|
||||
<UnverifiedIcon className="h-4 w-4 text-white" />
|
||||
Unverified
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{errors.nip05 && (
|
||||
<p className="mt-1 text-sm text-red-400">
|
||||
{errors.nip05.message.toString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label
|
||||
htmlFor="about"
|
||||
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
|
||||
>
|
||||
Bio
|
||||
</label>
|
||||
<textarea
|
||||
{...register('about')}
|
||||
spellCheck={false}
|
||||
className="relative h-20 w-full resize-none rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label
|
||||
htmlFor="website"
|
||||
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
|
||||
>
|
||||
Website
|
||||
</label>
|
||||
<input
|
||||
type={'text'}
|
||||
{...register('website', { required: false })}
|
||||
spellCheck={false}
|
||||
className="relative h-10 w-full rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isValid}
|
||||
className="inline-flex h-11 w-full transform items-center justify-center gap-1 rounded-md bg-fuchsia-500 font-medium text-zinc-100 hover:bg-fuchsia-600 focus:outline-none active:translate-y-1 disabled:pointer-events-none disabled:opacity-50"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
'Update'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ArrowLeftIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M10 18.25L3.75 12M3.75 12L10 5.75M3.75 12H20.25"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function ArrowLeftIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M10 18.25L3.75 12M3.75 12L10 5.75M3.75 12H20.25"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ArrowRightIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M14 5.75L20.25 12M20.25 12L14 18.25M20.25 12H3.75"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function ArrowRightIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M14 5.75L20.25 12M20.25 12L14 18.25M20.25 12H3.75"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ArrowRightCircleIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M7.75 12h8M13 8.75l2.896 2.896a.5.5 0 010 .708L13 15.25M21.25 12a9.25 9.25 0 11-18.5 0 9.25 9.25 0 0118.5 0z"
|
||||
/>
|
||||
</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="1.5"
|
||||
d="M7.75 12h8M13 8.75l2.896 2.896a.5.5 0 010 .708L13 15.25M21.25 12a9.25 9.25 0 11-18.5 0 9.25 9.25 0 0118.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function BellIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M16 18.25C15.3267 20.0159 13.7891 21.25 12 21.25C10.2109 21.25 8.67327 20.0159 8 18.25M20.5 18.25L18.9554 8.67345C18.4048 5.2596 15.458 2.75 12 2.75C8.54203 2.75 5.59523 5.2596 5.04461 8.67345L3.5 18.25H20.5Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function BellIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M16 18.25C15.3267 20.0159 13.7891 21.25 12 21.25C10.2109 21.25 8.67327 20.0159 8 18.25M20.5 18.25L18.9554 8.67345C18.4048 5.2596 15.458 2.75 12 2.75C8.54203 2.75 5.59523 5.2596 5.04461 8.67345L3.5 18.25H20.5Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function CancelIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M4.75 4.75L19.25 19.25M19.25 4.75L4.75 19.25"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function CancelIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M4.75 4.75L19.25 19.25M19.25 4.75L4.75 19.25"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function CheckCircleIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM15.5805 9.97493C15.8428 9.65434 15.7955 9.18183 15.4749 8.91953C15.1543 8.65724 14.6818 8.70449 14.4195 9.02507L10.4443 13.8837L9.03033 12.4697C8.73744 12.1768 8.26256 12.1768 7.96967 12.4697C7.67678 12.7626 7.67678 13.2374 7.96967 13.5303L9.96967 15.5303C10.1195 15.6802 10.3257 15.7596 10.5374 15.7491C10.749 15.7385 10.9463 15.6389 11.0805 15.4749L15.5805 9.97493Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM15.5805 9.97493C15.8428 9.65434 15.7955 9.18183 15.4749 8.91953C15.1543 8.65724 14.6818 8.70449 14.4195 9.02507L10.4443 13.8837L9.03033 12.4697C8.73744 12.1768 8.26256 12.1768 7.96967 12.4697C7.67678 12.7626 7.67678 13.2374 7.96967 13.5303L9.96967 15.5303C10.1195 15.6802 10.3257 15.7596 10.5374 15.7491C10.749 15.7385 10.9463 15.6389 11.0805 15.4749L15.5805 9.97493Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ChevronDownIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M8 10L12 14L16 10"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M8 10L12 14L16 10"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ChevronRightIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M10 16L14 12L10 8"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M10 16L14 12L10 8"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function CommandIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="square"
|
||||
strokeWidth="1.5"
|
||||
d="M9.25 9.25V6.5A2.75 2.75 0 106.5 9.25h2.75zm0 0h5.5m-5.5 0v5.5m5.5-5.5V6.5a2.75 2.75 0 112.75 2.75h-2.75zm0 0v5.5m0 0h-5.5m5.5 0v2.75a2.75 2.75 0 102.75-2.75h-2.75zm-5.5 0v2.75a2.75 2.75 0 11-2.75-2.75h2.75z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function CommandIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="square"
|
||||
strokeWidth="1.5"
|
||||
d="M9.25 9.25V6.5A2.75 2.75 0 106.5 9.25h2.75zm0 0h5.5m-5.5 0v5.5m5.5-5.5V6.5a2.75 2.75 0 112.75 2.75h-2.75zm0 0v5.5m0 0h-5.5m5.5 0v2.75a2.75 2.75 0 102.75-2.75h-2.75zm-5.5 0v2.75a2.75 2.75 0 11-2.75-2.75h2.75z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ComposeIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12.75 8.25L12.2197 7.71967C12.079 7.86032 12 8.05109 12 8.25H12.75ZM12.75 11.25H12C12 11.6642 12.3358 12 12.75 12V11.25ZM15.75 11.25V12C15.9489 12 16.1397 11.921 16.2803 11.7803L15.75 11.25ZM18.75 2.25L19.2803 1.71967C18.9874 1.42678 18.5126 1.42678 18.2197 1.71967L18.75 2.25ZM21.75 5.25L22.2803 5.78033C22.5732 5.48744 22.5732 5.01256 22.2803 4.71967L21.75 5.25ZM20.25 20.25V21C20.6642 21 21 20.6642 21 20.25H20.25ZM3.75 20.25H3C3 20.6642 3.33579 21 3.75 21V20.25ZM3.75 3.75V3C3.33579 3 3 3.33579 3 3.75H3.75ZM11.25 4.5C11.6642 4.5 12 4.16421 12 3.75C12 3.33579 11.6642 3 11.25 3V4.5ZM21 12.75C21 12.3358 20.6642 12 20.25 12C19.8358 12 19.5 12.3358 19.5 12.75H21ZM12 8.25V11.25H13.5V8.25H12ZM12.75 12H15.75V10.5H12.75V12ZM13.2803 8.78033L19.2803 2.78033L18.2197 1.71967L12.2197 7.71967L13.2803 8.78033ZM18.2197 2.78033L21.2197 5.78033L22.2803 4.71967L19.2803 1.71967L18.2197 2.78033ZM21.2197 4.71967L15.2197 10.7197L16.2803 11.7803L22.2803 5.78033L21.2197 4.71967ZM20.25 19.5H3.75V21H20.25V19.5ZM4.5 20.25V3.75H3V20.25H4.5ZM3.75 4.5H11.25V3H3.75V4.5ZM19.5 12.75V20.25H21V12.75H19.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function ComposeIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12.75 8.25L12.2197 7.71967C12.079 7.86032 12 8.05109 12 8.25H12.75ZM12.75 11.25H12C12 11.6642 12.3358 12 12.75 12V11.25ZM15.75 11.25V12C15.9489 12 16.1397 11.921 16.2803 11.7803L15.75 11.25ZM18.75 2.25L19.2803 1.71967C18.9874 1.42678 18.5126 1.42678 18.2197 1.71967L18.75 2.25ZM21.75 5.25L22.2803 5.78033C22.5732 5.48744 22.5732 5.01256 22.2803 4.71967L21.75 5.25ZM20.25 20.25V21C20.6642 21 21 20.6642 21 20.25H20.25ZM3.75 20.25H3C3 20.6642 3.33579 21 3.75 21V20.25ZM3.75 3.75V3C3.33579 3 3 3.33579 3 3.75H3.75ZM11.25 4.5C11.6642 4.5 12 4.16421 12 3.75C12 3.33579 11.6642 3 11.25 3V4.5ZM21 12.75C21 12.3358 20.6642 12 20.25 12C19.8358 12 19.5 12.3358 19.5 12.75H21ZM12 8.25V11.25H13.5V8.25H12ZM12.75 12H15.75V10.5H12.75V12ZM13.2803 8.78033L19.2803 2.78033L18.2197 1.71967L12.2197 7.71967L13.2803 8.78033ZM18.2197 2.78033L21.2197 5.78033L22.2803 4.71967L19.2803 1.71967L18.2197 2.78033ZM21.2197 4.71967L15.2197 10.7197L16.2803 11.7803L22.2803 5.78033L21.2197 4.71967ZM20.25 19.5H3.75V21H20.25V19.5ZM4.5 20.25V3.75H3V20.25H4.5ZM3.75 4.5H11.25V3H3.75V4.5ZM19.5 12.75V20.25H21V12.75H19.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function CopyIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M15.25 15.25V21.25H2.75V8.75H8.75M8.75 15.25H21.25V2.75H8.75V15.25Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function CopyIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M15.25 15.25V21.25H2.75V8.75H8.75M8.75 15.25H21.25V2.75H8.75V15.25Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function EditIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M13.25 6.25L17 2.5L21.5 7L17.75 10.75M13.25 6.25L2.75 16.75V21.25H7.25L17.75 10.75M13.25 6.25L17.75 10.75"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function EditIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M13.25 6.25L17 2.5L21.5 7L17.75 10.75M13.25 6.25L2.75 16.75V21.25H7.25L17.75 10.75M13.25 6.25L17.75 10.75"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,61 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function EmptyIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="120"
|
||||
height="120"
|
||||
fill="none"
|
||||
viewBox="0 0 120 120"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath="url(#clip0_110_63)">
|
||||
<path
|
||||
fill="#27272A"
|
||||
fillRule="evenodd"
|
||||
d="M60 120c33.137 0 60-26.863 60-60S93.137 0 60 0C45.133 0 39.482 17.832 29 26.787 16.119 37.792 0 41.73 0 60c0 33.137 26.863 60 60 60z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<g filter="url(#filter0_f_110_63)">
|
||||
<path
|
||||
fill="#18181B"
|
||||
fillRule="evenodd"
|
||||
d="M64 101c19.33 0 35-13.208 35-29.5S83.33 42 64 42c-8.672 0-11.969 8.767-18.083 13.17C38.403 60.58 29 62.517 29 71.5 29 87.792 44.67 101 64 101z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
fill="#3F3F46"
|
||||
fillRule="evenodd"
|
||||
d="M82.941 59H65.06C59.504 59 55 63.476 55 68.997v4.871c0 5.521 4.504 9.997 10.059 9.997h18.879l5.779 4.685a2.02 2.02 0 002.83-.286c.293-.356.453-.803.453-1.263V68.997C93 63.476 88.496 59 82.941 59z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="#D4D4D8"
|
||||
fillRule="evenodd"
|
||||
d="M41.161 39h32.678C81.659 39 88 45.408 88 53.314v12.864c0 7.905-6.34 14.314-14.161 14.314H41.547l-9.186 7.742a3.244 3.244 0 01-4.603-.422A3.325 3.325 0 0127 85.697V53.314C27 45.408 33.34 39 41.161 39z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_110_63"
|
||||
width="92"
|
||||
height="81"
|
||||
x="18"
|
||||
y="31"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur
|
||||
result="effect1_foregroundBlur_110_63"
|
||||
stdDeviation="5.5"
|
||||
/>
|
||||
</filter>
|
||||
<clipPath id="clip0_110_63">
|
||||
<path fill="#fff" d="M0 0H120V120H0z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
export function EmptyIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="120"
|
||||
height="120"
|
||||
fill="none"
|
||||
viewBox="0 0 120 120"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath="url(#clip0_110_63)">
|
||||
<path
|
||||
fill="#27272A"
|
||||
fillRule="evenodd"
|
||||
d="M60 120c33.137 0 60-26.863 60-60S93.137 0 60 0C45.133 0 39.482 17.832 29 26.787 16.119 37.792 0 41.73 0 60c0 33.137 26.863 60 60 60z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<g filter="url(#filter0_f_110_63)">
|
||||
<path
|
||||
fill="#18181B"
|
||||
fillRule="evenodd"
|
||||
d="M64 101c19.33 0 35-13.208 35-29.5S83.33 42 64 42c-8.672 0-11.969 8.767-18.083 13.17C38.403 60.58 29 62.517 29 71.5 29 87.792 44.67 101 64 101z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
fill="#3F3F46"
|
||||
fillRule="evenodd"
|
||||
d="M82.941 59H65.06C59.504 59 55 63.476 55 68.997v4.871c0 5.521 4.504 9.997 10.059 9.997h18.879l5.779 4.685a2.02 2.02 0 002.83-.286c.293-.356.453-.803.453-1.263V68.997C93 63.476 88.496 59 82.941 59z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="#D4D4D8"
|
||||
fillRule="evenodd"
|
||||
d="M41.161 39h32.678C81.659 39 88 45.408 88 53.314v12.864c0 7.905-6.34 14.314-14.161 14.314H41.547l-9.186 7.742a3.244 3.244 0 01-4.603-.422A3.325 3.325 0 0127 85.697V53.314C27 45.408 33.34 39 41.161 39z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_110_63"
|
||||
width="92"
|
||||
height="81"
|
||||
x="18"
|
||||
y="31"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur result="effect1_foregroundBlur_110_63" stdDeviation="5.5" />
|
||||
</filter>
|
||||
<clipPath id="clip0_110_63">
|
||||
<path fill="#fff" d="M0 0H120V120H0z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function EnterIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3.75 4.75V15H20.25M20.25 15L16.25 11M20.25 15L16.25 19"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function EnterIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3.75 4.75V15H20.25M20.25 15L16.25 11M20.25 15L16.25 19"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function EyeOffIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M9.1654 4.42071C8.76876 4.5401 8.544 4.95841 8.66339 5.35505C8.78277 5.75169 9.20109 5.97645 9.59772 5.85706L9.1654 4.42071ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM19.1413 14.9666C18.8678 15.2776 18.8982 15.7515 19.2092 16.0251C19.5203 16.2986 19.9942 16.2682 20.2677 15.9571L19.1413 14.9666ZM3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L3.28033 2.21967ZM2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM17.4703 17.4703L18.0006 16.9399L17.4703 17.4703ZM20.7197 21.7803C21.0126 22.0732 21.4874 22.0732 21.7803 21.7803C22.0732 21.4874 22.0732 21.0126 21.7803 20.7197L20.7197 21.7803ZM10.2322 10.2322C10.5251 9.93934 10.5251 9.46447 10.2322 9.17157C9.93934 8.87868 9.46447 8.87868 9.17157 9.17157L10.2322 10.2322ZM14.8284 14.8284C15.1213 14.5355 15.1213 14.0607 14.8284 13.7678C14.5355 13.4749 14.0607 13.4749 13.7678 13.7678L14.8284 14.8284ZM9.59772 5.85706C13.745 4.60878 18.4769 6.624 21.329 12.3351L22.671 11.6649C19.5775 5.47055 14.1791 2.91165 9.1654 4.42071L9.59772 5.85706ZM20.2677 15.9571C21.1654 14.9364 21.9755 13.7277 22.671 12.3351L21.329 11.6649C20.6865 12.9515 19.9468 14.0507 19.1413 14.9666L20.2677 15.9571ZM2.21967 3.28033L5.99937 7.06003L7.06003 5.99937L3.28033 2.21967L2.21967 3.28033ZM2.67098 12.335C3.84083 9.99245 5.33197 8.27257 6.95699 7.14609L6.10242 5.91332C4.24158 7.20327 2.5948 9.13019 1.32902 11.6648L2.67098 12.335ZM5.99937 7.06003L16.9399 18.0006L18.0006 16.9399L7.06003 5.99937L5.99937 7.06003ZM16.9399 18.0006L20.7197 21.7803L21.7803 20.7197L18.0006 16.9399L16.9399 18.0006ZM1.32902 12.335C3.20469 16.0909 5.92036 18.5148 8.91701 19.5009C11.922 20.4898 15.1308 20.0045 17.8975 18.0866L17.043 16.8539C14.6436 18.5171 11.9221 18.9107 9.38589 18.0761C6.84135 17.2388 4.40494 15.1369 2.67098 11.6648L1.32902 12.335ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 11.3094 9.779 10.6855 10.2322 10.2322L9.17157 9.17157C8.44854 9.89461 8 10.8956 8 12H9.5ZM13.7678 13.7678C13.3145 14.221 12.6906 14.5 12 14.5V16C13.1044 16 14.1054 15.5515 14.8284 14.8284L13.7678 13.7678Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function EyeOffIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M9.1654 4.42071C8.76876 4.5401 8.544 4.95841 8.66339 5.35505C8.78277 5.75169 9.20109 5.97645 9.59772 5.85706L9.1654 4.42071ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM19.1413 14.9666C18.8678 15.2776 18.8982 15.7515 19.2092 16.0251C19.5203 16.2986 19.9942 16.2682 20.2677 15.9571L19.1413 14.9666ZM3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L3.28033 2.21967ZM2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM17.4703 17.4703L18.0006 16.9399L17.4703 17.4703ZM20.7197 21.7803C21.0126 22.0732 21.4874 22.0732 21.7803 21.7803C22.0732 21.4874 22.0732 21.0126 21.7803 20.7197L20.7197 21.7803ZM10.2322 10.2322C10.5251 9.93934 10.5251 9.46447 10.2322 9.17157C9.93934 8.87868 9.46447 8.87868 9.17157 9.17157L10.2322 10.2322ZM14.8284 14.8284C15.1213 14.5355 15.1213 14.0607 14.8284 13.7678C14.5355 13.4749 14.0607 13.4749 13.7678 13.7678L14.8284 14.8284ZM9.59772 5.85706C13.745 4.60878 18.4769 6.624 21.329 12.3351L22.671 11.6649C19.5775 5.47055 14.1791 2.91165 9.1654 4.42071L9.59772 5.85706ZM20.2677 15.9571C21.1654 14.9364 21.9755 13.7277 22.671 12.3351L21.329 11.6649C20.6865 12.9515 19.9468 14.0507 19.1413 14.9666L20.2677 15.9571ZM2.21967 3.28033L5.99937 7.06003L7.06003 5.99937L3.28033 2.21967L2.21967 3.28033ZM2.67098 12.335C3.84083 9.99245 5.33197 8.27257 6.95699 7.14609L6.10242 5.91332C4.24158 7.20327 2.5948 9.13019 1.32902 11.6648L2.67098 12.335ZM5.99937 7.06003L16.9399 18.0006L18.0006 16.9399L7.06003 5.99937L5.99937 7.06003ZM16.9399 18.0006L20.7197 21.7803L21.7803 20.7197L18.0006 16.9399L16.9399 18.0006ZM1.32902 12.335C3.20469 16.0909 5.92036 18.5148 8.91701 19.5009C11.922 20.4898 15.1308 20.0045 17.8975 18.0866L17.043 16.8539C14.6436 18.5171 11.9221 18.9107 9.38589 18.0761C6.84135 17.2388 4.40494 15.1369 2.67098 11.6648L1.32902 12.335ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 11.3094 9.779 10.6855 10.2322 10.2322L9.17157 9.17157C8.44854 9.89461 8 10.8956 8 12H9.5ZM13.7678 13.7678C13.3145 14.221 12.6906 14.5 12 14.5V16C13.1044 16 14.1054 15.5515 14.8284 14.8284L13.7678 13.7678Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function EyeOnIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM2.67098 12.335C4.9893 7.69273 8.55546 5.49997 12 5.5C15.4445 5.50003 19.0107 7.69284 21.329 12.3351L22.671 11.6649C20.1618 6.64058 16.1417 4.00003 12 4C7.85827 3.99997 3.83815 6.64046 1.32902 11.6648L2.67098 12.335ZM1.32902 12.335C3.83815 17.3593 7.85826 19.9999 12 19.9999C16.1417 20 20.1618 17.3595 22.671 12.3351L21.329 11.6649C19.0107 16.3072 15.4445 18.4999 12 18.4999C8.55547 18.4999 4.9893 16.3071 2.67098 11.6648L1.32902 12.335ZM14.5 12C14.5 13.3807 13.3807 14.5 12 14.5V16C14.2091 16 16 14.2091 16 12H14.5ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 10.6193 10.6193 9.5 12 9.5V8C9.79086 8 8 9.79086 8 12H9.5ZM12 9.5C13.3807 9.5 14.5 10.6193 14.5 12H16C16 9.79086 14.2091 8 12 8V9.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function EyeOnIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM2.67098 12.335C4.9893 7.69273 8.55546 5.49997 12 5.5C15.4445 5.50003 19.0107 7.69284 21.329 12.3351L22.671 11.6649C20.1618 6.64058 16.1417 4.00003 12 4C7.85827 3.99997 3.83815 6.64046 1.32902 11.6648L2.67098 12.335ZM1.32902 12.335C3.83815 17.3593 7.85826 19.9999 12 19.9999C16.1417 20 20.1618 17.3595 22.671 12.3351L21.329 11.6649C19.0107 16.3072 15.4445 18.4999 12 18.4999C8.55547 18.4999 4.9893 16.3071 2.67098 11.6648L1.32902 12.335ZM14.5 12C14.5 13.3807 13.3807 14.5 12 14.5V16C14.2091 16 16 14.2091 16 12H14.5ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 10.6193 10.6193 9.5 12 9.5V8C9.79086 8 8 9.79086 8 12H9.5ZM12 9.5C13.3807 9.5 14.5 10.6193 14.5 12H16C16 9.79086 14.2091 8 12 8V9.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function FeedIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M19.5 8.75V11.5M19.5 11.5V14.25M19.5 11.5H16.75M19.5 11.5H22.25M14.75 6.5C14.75 8.57107 13.0711 10.25 11 10.25C8.92893 10.25 7.25 8.57107 7.25 6.5C7.25 4.42893 8.92893 2.75 11 2.75C13.0711 2.75 14.75 4.42893 14.75 6.5ZM3.5 20.25C3.86894 16.3254 6.8098 13.25 11 13.25C15.1902 13.25 18.1311 16.3254 18.5 20.25H3.5Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function FeedIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M19.5 8.75V11.5M19.5 11.5V14.25M19.5 11.5H16.75M19.5 11.5H22.25M14.75 6.5C14.75 8.57107 13.0711 10.25 11 10.25C8.92893 10.25 7.25 8.57107 7.25 6.5C7.25 4.42893 8.92893 2.75 11 2.75C13.0711 2.75 14.75 4.42893 14.75 6.5ZM3.5 20.25C3.86894 16.3254 6.8098 13.25 11 13.25C15.1902 13.25 18.1311 16.3254 18.5 20.25H3.5Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function FollowIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M11.85 13.251c-3.719.065-6.427 2.567-7.18 5.915-.13.575.338 1.084.927 1.084h6.901m-.647-6.999l.147-.001c.353 0 .696.022 1.03.064m-1.177-.063a7.889 7.889 0 00-1.852.249m3.028-.186c.334.042.658.104.972.186m-.972-.186a7.475 7.475 0 011.972.524m3.25 1.412v3m0 0v3m0-3h-3m3 0h3m-5.5-11.75a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function FollowIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M11.85 13.251c-3.719.065-6.427 2.567-7.18 5.915-.13.575.338 1.084.927 1.084h6.901m-.647-6.999l.147-.001c.353 0 .696.022 1.03.064m-1.177-.063a7.889 7.889 0 00-1.852.249m3.028-.186c.334.042.658.104.972.186m-.972-.186a7.475 7.475 0 011.972.524m3.25 1.412v3m0 0v3m0-3h-3m3 0h3m-5.5-11.75a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function HeartBeatIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M1.75 11.75H6L9 2.75L15 21.25L18 11.75H22.25"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function HeartBeatIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M1.75 11.75H6L9 2.75L15 21.25L18 11.75H22.25"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function HideIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M9.1654 4.42071C8.76876 4.5401 8.544 4.95841 8.66339 5.35505C8.78277 5.75169 9.20109 5.97645 9.59772 5.85706L9.1654 4.42071ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM19.1413 14.9666C18.8678 15.2776 18.8982 15.7515 19.2092 16.0251C19.5203 16.2986 19.9942 16.2682 20.2677 15.9571L19.1413 14.9666ZM3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L3.28033 2.21967ZM2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM17.4703 17.4703L18.0006 16.9399L17.4703 17.4703ZM20.7197 21.7803C21.0126 22.0732 21.4874 22.0732 21.7803 21.7803C22.0732 21.4874 22.0732 21.0126 21.7803 20.7197L20.7197 21.7803ZM10.2322 10.2322C10.5251 9.93934 10.5251 9.46447 10.2322 9.17157C9.93934 8.87868 9.46447 8.87868 9.17157 9.17157L10.2322 10.2322ZM14.8284 14.8284C15.1213 14.5355 15.1213 14.0607 14.8284 13.7678C14.5355 13.4749 14.0607 13.4749 13.7678 13.7678L14.8284 14.8284ZM9.59772 5.85706C13.745 4.60878 18.4769 6.624 21.329 12.3351L22.671 11.6649C19.5775 5.47055 14.1791 2.91165 9.1654 4.42071L9.59772 5.85706ZM20.2677 15.9571C21.1654 14.9364 21.9755 13.7277 22.671 12.3351L21.329 11.6649C20.6865 12.9515 19.9468 14.0507 19.1413 14.9666L20.2677 15.9571ZM2.21967 3.28033L5.99937 7.06003L7.06003 5.99937L3.28033 2.21967L2.21967 3.28033ZM2.67098 12.335C3.84083 9.99245 5.33197 8.27257 6.95699 7.14609L6.10242 5.91332C4.24158 7.20327 2.5948 9.13019 1.32902 11.6648L2.67098 12.335ZM5.99937 7.06003L16.9399 18.0006L18.0006 16.9399L7.06003 5.99937L5.99937 7.06003ZM16.9399 18.0006L20.7197 21.7803L21.7803 20.7197L18.0006 16.9399L16.9399 18.0006ZM1.32902 12.335C3.20469 16.0909 5.92036 18.5148 8.91701 19.5009C11.922 20.4898 15.1308 20.0045 17.8975 18.0866L17.043 16.8539C14.6436 18.5171 11.9221 18.9107 9.38589 18.0761C6.84135 17.2388 4.40494 15.1369 2.67098 11.6648L1.32902 12.335ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 11.3094 9.779 10.6855 10.2322 10.2322L9.17157 9.17157C8.44854 9.89461 8 10.8956 8 12H9.5ZM13.7678 13.7678C13.3145 14.221 12.6906 14.5 12 14.5V16C13.1044 16 14.1054 15.5515 14.8284 14.8284L13.7678 13.7678Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function HideIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M9.1654 4.42071C8.76876 4.5401 8.544 4.95841 8.66339 5.35505C8.78277 5.75169 9.20109 5.97645 9.59772 5.85706L9.1654 4.42071ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM19.1413 14.9666C18.8678 15.2776 18.8982 15.7515 19.2092 16.0251C19.5203 16.2986 19.9942 16.2682 20.2677 15.9571L19.1413 14.9666ZM3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L3.28033 2.21967ZM2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM17.4703 17.4703L18.0006 16.9399L17.4703 17.4703ZM20.7197 21.7803C21.0126 22.0732 21.4874 22.0732 21.7803 21.7803C22.0732 21.4874 22.0732 21.0126 21.7803 20.7197L20.7197 21.7803ZM10.2322 10.2322C10.5251 9.93934 10.5251 9.46447 10.2322 9.17157C9.93934 8.87868 9.46447 8.87868 9.17157 9.17157L10.2322 10.2322ZM14.8284 14.8284C15.1213 14.5355 15.1213 14.0607 14.8284 13.7678C14.5355 13.4749 14.0607 13.4749 13.7678 13.7678L14.8284 14.8284ZM9.59772 5.85706C13.745 4.60878 18.4769 6.624 21.329 12.3351L22.671 11.6649C19.5775 5.47055 14.1791 2.91165 9.1654 4.42071L9.59772 5.85706ZM20.2677 15.9571C21.1654 14.9364 21.9755 13.7277 22.671 12.3351L21.329 11.6649C20.6865 12.9515 19.9468 14.0507 19.1413 14.9666L20.2677 15.9571ZM2.21967 3.28033L5.99937 7.06003L7.06003 5.99937L3.28033 2.21967L2.21967 3.28033ZM2.67098 12.335C3.84083 9.99245 5.33197 8.27257 6.95699 7.14609L6.10242 5.91332C4.24158 7.20327 2.5948 9.13019 1.32902 11.6648L2.67098 12.335ZM5.99937 7.06003L16.9399 18.0006L18.0006 16.9399L7.06003 5.99937L5.99937 7.06003ZM16.9399 18.0006L20.7197 21.7803L21.7803 20.7197L18.0006 16.9399L16.9399 18.0006ZM1.32902 12.335C3.20469 16.0909 5.92036 18.5148 8.91701 19.5009C11.922 20.4898 15.1308 20.0045 17.8975 18.0866L17.043 16.8539C14.6436 18.5171 11.9221 18.9107 9.38589 18.0761C6.84135 17.2388 4.40494 15.1369 2.67098 11.6648L1.32902 12.335ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 11.3094 9.779 10.6855 10.2322 10.2322L9.17157 9.17157C8.44854 9.89461 8 10.8956 8 12H9.5ZM13.7678 13.7678C13.3145 14.221 12.6906 14.5 12 14.5V16C13.1044 16 14.1054 15.5515 14.8284 14.8284L13.7678 13.7678Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ImageIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3.75 3.75V3C3.33579 3 3 3.33579 3 3.75H3.75ZM20.25 3.75H21C21 3.33579 20.6642 3 20.25 3V3.75ZM20.25 20.25V21C20.6642 21 21 20.6642 21 20.25H20.25ZM3.75 20.25H3C3 20.6642 3.33579 21 3.75 21V20.25ZM3.23598 15.4538C2.93435 15.7377 2.91996 16.2124 3.20385 16.514C3.48774 16.8157 3.96239 16.83 4.26402 16.5462L3.23598 15.4538ZM8 12L8.53033 11.4697C8.24369 11.183 7.78117 11.176 7.48598 11.4538L8 12ZM12 16L11.4697 16.5303C11.7626 16.8232 12.2374 16.8232 12.5303 16.5303L12 16ZM14 14L14.5303 13.4697C14.2374 13.1768 13.7626 13.1768 13.4697 13.4697L14 14ZM19.4697 20.5303C19.7626 20.8232 20.2374 20.8232 20.5303 20.5303C20.8232 20.2374 20.8232 19.7626 20.5303 19.4697L19.4697 20.5303ZM3.75 4.5H20.25V3H3.75V4.5ZM19.5 3.75V20.25H21V3.75H19.5ZM20.25 19.5H3.75V21H20.25V19.5ZM4.5 20.25V3.75H3V20.25H4.5ZM4.26402 16.5462L8.51402 12.5462L7.48598 11.4538L3.23598 15.4538L4.26402 16.5462ZM7.46967 12.5303L11.4697 16.5303L12.5303 15.4697L8.53033 11.4697L7.46967 12.5303ZM12.5303 16.5303L14.5303 14.5303L13.4697 13.4697L11.4697 15.4697L12.5303 16.5303ZM13.4697 14.5303L19.4697 20.5303L20.5303 19.4697L14.5303 13.4697L13.4697 14.5303ZM15 9C15 9.41421 14.6642 9.75 14.25 9.75V11.25C15.4926 11.25 16.5 10.2426 16.5 9H15ZM14.25 9.75C13.8358 9.75 13.5 9.41421 13.5 9H12C12 10.2426 13.0074 11.25 14.25 11.25V9.75ZM13.5 9C13.5 8.58579 13.8358 8.25 14.25 8.25V6.75C13.0074 6.75 12 7.75736 12 9H13.5ZM14.25 8.25C14.6642 8.25 15 8.58579 15 9H16.5C16.5 7.75736 15.4926 6.75 14.25 6.75V8.25Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function ImageIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3.75 3.75V3C3.33579 3 3 3.33579 3 3.75H3.75ZM20.25 3.75H21C21 3.33579 20.6642 3 20.25 3V3.75ZM20.25 20.25V21C20.6642 21 21 20.6642 21 20.25H20.25ZM3.75 20.25H3C3 20.6642 3.33579 21 3.75 21V20.25ZM3.23598 15.4538C2.93435 15.7377 2.91996 16.2124 3.20385 16.514C3.48774 16.8157 3.96239 16.83 4.26402 16.5462L3.23598 15.4538ZM8 12L8.53033 11.4697C8.24369 11.183 7.78117 11.176 7.48598 11.4538L8 12ZM12 16L11.4697 16.5303C11.7626 16.8232 12.2374 16.8232 12.5303 16.5303L12 16ZM14 14L14.5303 13.4697C14.2374 13.1768 13.7626 13.1768 13.4697 13.4697L14 14ZM19.4697 20.5303C19.7626 20.8232 20.2374 20.8232 20.5303 20.5303C20.8232 20.2374 20.8232 19.7626 20.5303 19.4697L19.4697 20.5303ZM3.75 4.5H20.25V3H3.75V4.5ZM19.5 3.75V20.25H21V3.75H19.5ZM20.25 19.5H3.75V21H20.25V19.5ZM4.5 20.25V3.75H3V20.25H4.5ZM4.26402 16.5462L8.51402 12.5462L7.48598 11.4538L3.23598 15.4538L4.26402 16.5462ZM7.46967 12.5303L11.4697 16.5303L12.5303 15.4697L8.53033 11.4697L7.46967 12.5303ZM12.5303 16.5303L14.5303 14.5303L13.4697 13.4697L11.4697 15.4697L12.5303 16.5303ZM13.4697 14.5303L19.4697 20.5303L20.5303 19.4697L14.5303 13.4697L13.4697 14.5303ZM15 9C15 9.41421 14.6642 9.75 14.25 9.75V11.25C15.4926 11.25 16.5 10.2426 16.5 9H15ZM14.25 9.75C13.8358 9.75 13.5 9.41421 13.5 9H12C12 10.2426 13.0074 11.25 14.25 11.25V9.75ZM13.5 9C13.5 8.58579 13.8358 8.25 14.25 8.25V6.75C13.0074 6.75 12 7.75736 12 9H13.5ZM14.25 8.25C14.6642 8.25 15 8.58579 15 9H16.5C16.5 7.75736 15.4926 6.75 14.25 6.75V8.25Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
// @index('./*.tsx', f => `export * from '${f.path}'`)
|
||||
export * from "./arrowLeft";
|
||||
export * from "./arrowRight";
|
||||
export * from "./bell";
|
||||
export * from "./cancel";
|
||||
export * from "./checkCircle";
|
||||
export * from "./chevronDown";
|
||||
export * from "./chevronRight";
|
||||
export * from "./compose";
|
||||
export * from "./copy";
|
||||
export * from "./edit";
|
||||
export * from "./enter";
|
||||
export * from "./eyeOff";
|
||||
export * from "./eyeOn";
|
||||
export * from "./feed";
|
||||
export * from "./heartbeat";
|
||||
export * from "./hide";
|
||||
export * from "./image";
|
||||
export * from "./like";
|
||||
export * from "./lume";
|
||||
export * from "./media";
|
||||
export * from "./mute";
|
||||
export * from "./space";
|
||||
export * from "./navArrowDown";
|
||||
export * from "./plus";
|
||||
export * from "./plusCircle";
|
||||
export * from "./refresh";
|
||||
export * from "./reply";
|
||||
export * from "./replyMessage";
|
||||
export * from "./repost";
|
||||
export * from "./threads";
|
||||
export * from "./trash";
|
||||
export * from "./world";
|
||||
export * from "./zap";
|
||||
export * from "./loader";
|
||||
export * from "./trending";
|
||||
export * from "./empty";
|
||||
export * from "./cmd";
|
||||
export * from "./verticalDots";
|
||||
export * from "./signal";
|
||||
export * from "./unverified";
|
||||
export * from "./settings";
|
||||
export * from "./logout";
|
||||
export * from "./follow";
|
||||
export * from "./unfollow";
|
||||
export * from './arrowLeft';
|
||||
export * from './arrowRight';
|
||||
export * from './bell';
|
||||
export * from './cancel';
|
||||
export * from './checkCircle';
|
||||
export * from './chevronDown';
|
||||
export * from './chevronRight';
|
||||
export * from './compose';
|
||||
export * from './copy';
|
||||
export * from './edit';
|
||||
export * from './enter';
|
||||
export * from './eyeOff';
|
||||
export * from './eyeOn';
|
||||
export * from './feed';
|
||||
export * from './heartbeat';
|
||||
export * from './hide';
|
||||
export * from './image';
|
||||
export * from './like';
|
||||
export * from './lume';
|
||||
export * from './media';
|
||||
export * from './mute';
|
||||
export * from './space';
|
||||
export * from './navArrowDown';
|
||||
export * from './plus';
|
||||
export * from './plusCircle';
|
||||
export * from './refresh';
|
||||
export * from './reply';
|
||||
export * from './replyMessage';
|
||||
export * from './repost';
|
||||
export * from './threads';
|
||||
export * from './trash';
|
||||
export * from './world';
|
||||
export * from './zap';
|
||||
export * from './loader';
|
||||
export * from './trending';
|
||||
export * from './empty';
|
||||
export * from './cmd';
|
||||
export * from './verticalDots';
|
||||
export * from './signal';
|
||||
export * from './unverified';
|
||||
export * from './settings';
|
||||
export * from './logout';
|
||||
export * from './follow';
|
||||
export * from './unfollow';
|
||||
// @endindex
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function LikeIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12 5.57193C18.3331 -0.86765 29.1898 11.0916 12 20.75C-5.18982 11.0916 5.66687 -0.867651 12 5.57193Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function LikeIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M12 5.57193C18.3331 -0.86765 29.1898 11.0916 12 20.75C-5.18982 11.0916 5.66687 -0.867651 12 5.57193Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function LoaderIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<title id="loading">Loading</title>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function LoaderIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<title id="loading">Loading</title>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function LogoutIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M20.25 12H9m11.25 0l-4.5 4.5m4.5-4.5l-4.5-4.5m-4.5 12.75h-6.5a1 1 0 01-1-1V4.75a1 1 0 011-1h6.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function LogoutIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M20.25 12H9m11.25 0l-4.5 4.5m4.5-4.5l-4.5-4.5m-4.5 12.75h-6.5a1 1 0 01-1-1V4.75a1 1 0 011-1h6.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
export function LumeIcon({ className }: { className: string }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M7.337 19.099a.32.32 0 0 1-.373.021 20.911 20.911 0 0 0-4.259-2.022c-.17-.063-.191-.297-.031-.383a13.876 13.876 0 0 0 4.886-4.639A13.715 13.715 0 0 0 9.69 5.14c0-.17.149-.309.32-.309h3.981c.17 0 .309.138.32.309.074 2.468.809 4.852 2.129 6.937a13.88 13.88 0 0 0 4.886 4.64c.16.095.139.33-.032.383-1.266.436-2.49.99-3.651 1.66-.203.116-.405.244-.607.361a.32.32 0 0 1-.373-.021 18.293 18.293 0 0 1-4.567-5.331l-.096-.16-.096.16a18.158 18.158 0 0 1-4.567 5.33Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" className={className}>
|
||||
<path
|
||||
d="M7.337 19.099a.32.32 0 0 1-.373.021 20.911 20.911 0 0 0-4.259-2.022c-.17-.063-.191-.297-.031-.383a13.876 13.876 0 0 0 4.886-4.639A13.715 13.715 0 0 0 9.69 5.14c0-.17.149-.309.32-.309h3.981c.17 0 .309.138.32.309.074 2.468.809 4.852 2.129 6.937a13.88 13.88 0 0 0 4.886 4.64c.16.095.139.33-.032.383-1.266.436-2.49.99-3.651 1.66-.203.116-.405.244-.607.361a.32.32 0 0 1-.373-.021 18.293 18.293 0 0 1-4.567-5.331l-.096-.16-.096.16a18.158 18.158 0 0 1-4.567 5.33Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function MediaIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M6.25 7.5C6.94036 7.5 7.5 6.94036 7.5 6.25C7.5 5.55964 6.94036 5 6.25 5C5.55964 5 5 5.55964 5 6.25C5 6.94036 5.55964 7.5 6.25 7.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2.75 2C2.33579 2 2 2.33579 2 2.75V15.25C2 15.6642 2.33579 16 2.75 16H8V21.25C8 21.6642 8.33579 22 8.75 22H21.25C21.6642 22 22 21.6642 22 21.25V8.75C22 8.33579 21.6642 8 21.25 8H16V2.75C16 2.33579 15.6642 2 15.25 2H2.75ZM14.5 8V3.5H3.5V10.764L5.58203 9.37596C5.83396 9.20801 6.16216 9.20801 6.41408 9.37596L8 10.4332V8.75C8 8.33579 8.33579 8 8.75 8H14.5ZM8 12.236L5.99806 10.9014L3.5 12.5668V14.5H8V12.236ZM13 17.5585V12.4415C13 12.2472 13.212 12.1272 13.3786 12.2272L17.6427 14.7856C17.8045 14.8827 17.8045 15.1173 17.6427 15.2144L13.3786 17.7728C13.212 17.8728 13 17.7528 13 17.5585Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function MediaIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M6.25 7.5C6.94036 7.5 7.5 6.94036 7.5 6.25C7.5 5.55964 6.94036 5 6.25 5C5.55964 5 5 5.55964 5 6.25C5 6.94036 5.55964 7.5 6.25 7.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2.75 2C2.33579 2 2 2.33579 2 2.75V15.25C2 15.6642 2.33579 16 2.75 16H8V21.25C8 21.6642 8.33579 22 8.75 22H21.25C21.6642 22 22 21.6642 22 21.25V8.75C22 8.33579 21.6642 8 21.25 8H16V2.75C16 2.33579 15.6642 2 15.25 2H2.75ZM14.5 8V3.5H3.5V10.764L5.58203 9.37596C5.83396 9.20801 6.16216 9.20801 6.41408 9.37596L8 10.4332V8.75C8 8.33579 8.33579 8 8.75 8H14.5ZM8 12.236L5.99806 10.9014L3.5 12.5668V14.5H8V12.236ZM13 17.5585V12.4415C13 12.2472 13.212 12.1272 13.3786 12.2272L17.6427 14.7856C17.8045 14.8827 17.8045 15.1173 17.6427 15.2144L13.3786 17.7728C13.212 17.8728 13 17.7528 13 17.5585Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function MuteIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M17 5.93934V3.75C17 3.47592 16.8505 3.22366 16.6101 3.09208C16.3696 2.9605 16.0766 2.97055 15.8457 3.1183L9.78055 7H5.75C5.33579 7 5 7.33579 5 7.75V16.25C5 16.6642 5.33579 17 5.75 17H5.93934L3.21967 19.7197C2.92678 20.0126 2.92678 20.4874 3.21967 20.7803C3.51256 21.0732 3.98744 21.0732 4.28033 20.7803L20.7803 4.28033C21.0732 3.98744 21.0732 3.51256 20.7803 3.21967C20.4874 2.92678 20.0126 2.92678 19.7197 3.21967L17 5.93934ZM7.43934 15.5H6.5V8.5H10C10.1433 8.5 10.2836 8.45895 10.4043 8.3817L15.5 5.12045V7.43934L7.43934 15.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M15.5 18.8796L11.1102 16.0701L10.0243 17.156L15.8457 20.8817C16.0766 21.0294 16.3696 21.0395 16.6101 20.9079C16.8505 20.7763 17 20.5241 17 20.25V10.1803L15.5 11.6803V18.8796Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function MuteIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M17 5.93934V3.75C17 3.47592 16.8505 3.22366 16.6101 3.09208C16.3696 2.9605 16.0766 2.97055 15.8457 3.1183L9.78055 7H5.75C5.33579 7 5 7.33579 5 7.75V16.25C5 16.6642 5.33579 17 5.75 17H5.93934L3.21967 19.7197C2.92678 20.0126 2.92678 20.4874 3.21967 20.7803C3.51256 21.0732 3.98744 21.0732 4.28033 20.7803L20.7803 4.28033C21.0732 3.98744 21.0732 3.51256 20.7803 3.21967C20.4874 2.92678 20.0126 2.92678 19.7197 3.21967L17 5.93934ZM7.43934 15.5H6.5V8.5H10C10.1433 8.5 10.2836 8.45895 10.4043 8.3817L15.5 5.12045V7.43934L7.43934 15.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M15.5 18.8796L11.1102 16.0701L10.0243 17.156L15.8457 20.8817C16.0766 21.0294 16.3696 21.0395 16.6101 20.9079C16.8505 20.7763 17 20.5241 17 20.25V10.1803L15.5 11.6803V18.8796Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function NavArrowDownIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<title id="navArrowDown">Nav Arrow Down</title>
|
||||
<path
|
||||
d="M4.29233 7.97419C3.37989 6.14866 4.70668 4 6.74799 4H17.2519C19.2932 4 20.62 6.14866 19.7076 7.97419L14.4556 18.4819C13.4439 20.5061 10.556 20.506 9.54431 18.4819L4.29233 7.97419Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<title id="navArrowDown">Nav Arrow Down</title>
|
||||
<path
|
||||
d="M4.29233 7.97419C3.37989 6.14866 4.70668 4 6.74799 4H17.2519C19.2932 4 20.62 6.14866 19.7076 7.97419L14.4556 18.4819C13.4439 20.5061 10.556 20.506 9.54431 18.4819L4.29233 7.97419Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function PlusIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12 3.75V12M12 12V20.25M12 12H3.75M12 12H20.25"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function PlusIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M12 3.75V12M12 12V20.25M12 12H3.75M12 12H20.25"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function PlusCircleIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M16.2426 12.0005H7.75736M12 16.2431V7.75781M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function PlusCircleIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M16.2426 12.0005H7.75736M12 16.2431V7.75781M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function RefreshIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M11.979 4.5C7.83687 4.5 4.479 7.85786 4.479 12C4.479 16.1421 7.83687 19.5 11.979 19.5C15.2434 19.5 18.0225 17.4141 19.0524 14.5001C19.1905 14.1095 19.619 13.9048 20.0095 14.0429C20.4 14.1809 20.6047 14.6094 20.4667 14.9999C19.2315 18.4945 15.8988 21 11.979 21C7.00844 21 2.979 16.9706 2.979 12C2.979 7.02944 7.00844 3 11.979 3C13.709 3 15.1419 3.42256 16.4191 4.20651C17.1663 4.6651 17.8487 5.24046 18.5 5.90708V4C18.5 3.58579 18.8358 3.25 19.25 3.25C19.6642 3.25 20 3.58579 20 4V8C20 8.41421 19.6642 8.75 19.25 8.75H15.25C14.8358 8.75 14.5 8.41421 14.5 8C14.5 7.58579 14.8358 7.25 15.25 7.25H17.7068C17.0285 6.51595 16.3546 5.92693 15.6345 5.4849C14.6015 4.85088 13.4417 4.5 11.979 4.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function RefreshIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M11.979 4.5C7.83687 4.5 4.479 7.85786 4.479 12C4.479 16.1421 7.83687 19.5 11.979 19.5C15.2434 19.5 18.0225 17.4141 19.0524 14.5001C19.1905 14.1095 19.619 13.9048 20.0095 14.0429C20.4 14.1809 20.6047 14.6094 20.4667 14.9999C19.2315 18.4945 15.8988 21 11.979 21C7.00844 21 2.979 16.9706 2.979 12C2.979 7.02944 7.00844 3 11.979 3C13.709 3 15.1419 3.42256 16.4191 4.20651C17.1663 4.6651 17.8487 5.24046 18.5 5.90708V4C18.5 3.58579 18.8358 3.25 19.25 3.25C19.6642 3.25 20 3.58579 20 4V8C20 8.41421 19.6642 8.75 19.25 8.75H15.25C14.8358 8.75 14.5 8.41421 14.5 8C14.5 7.58579 14.8358 7.25 15.25 7.25H17.7068C17.0285 6.51595 16.3546 5.92693 15.6345 5.4849C14.6015 4.85088 13.4417 4.5 11.979 4.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ReplyIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12 21.25C17.1086 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.1086 2.75 12 2.75C6.89137 2.75 2.75 6.89137 2.75 12C2.75 13.529 3.12096 14.9713 3.77778 16.2418L2.75 21.25L7.88889 20.2885C9.12732 20.9039 10.5232 21.25 12 21.25Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function ReplyIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M12 21.25C17.1086 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.1086 2.75 12 2.75C6.89137 2.75 2.75 6.89137 2.75 12C2.75 13.529 3.12096 14.9713 3.77778 16.2418L2.75 21.25L7.88889 20.2885C9.12732 20.9039 10.5232 21.25 12 21.25Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ReplyMessageIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M1.75 12L11.25 3.75V8.5C19.75 8.5 22 11.75 22 20.25C20.5 17.25 19.75 15.5 11.25 15.5V20.25L1.75 12Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M1.75 12L11.25 3.75V8.5C19.75 8.5 22 11.75 22 20.25C20.5 17.25 19.75 15.5 11.25 15.5V20.25L1.75 12Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function RepostIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M17.25 21.25L20.25 18.25L17.25 15.25M6.75 2.75L3.75 5.75L6.75 8.75M5.25 5.75H20.25V10.75M3.75 13.75V18.25H18.75"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function RepostIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M17.25 21.25L20.25 18.25L17.25 15.25M6.75 2.75L3.75 5.75L6.75 8.75M5.25 5.75H20.25V10.75M3.75 13.75V18.25H18.75"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function SettingsIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M8.552 5.37l-1.793-.414a1 1 0 00-.932.267l-.604.604a1 1 0 00-.267.932l.414 1.793a1 1 0 01-.42 1.056l-1.755 1.17a1 1 0 00-.445.832v.78a1 1 0 00.445.832l1.755 1.17a1 1 0 01.42 1.056l-.414 1.793a1 1 0 00.267.932l.604.604a1 1 0 00.932.267l1.793-.414a1 1 0 011.056.42l1.17 1.755a1 1 0 00.832.445h.78a1 1 0 00.832-.445l1.17-1.755a1 1 0 011.056-.42l1.793.414a1 1 0 00.932-.267l.604-.604a1 1 0 00.267-.932l-.414-1.793a1 1 0 01.42-1.056l1.755-1.17a1 1 0 00.445-.832v-.78a1 1 0 00-.445-.832l-1.755-1.17a1 1 0 01-.42-1.056l.414-1.793a1 1 0 00-.267-.932l-.604-.604a1 1 0 00-.932-.267l-1.793.414a1 1 0 01-1.056-.42l-1.17-1.755a1 1 0 00-.832-.445h-.78a1 1 0 00-.832.445L9.608 4.95a1 1 0 01-1.056.42z"
|
||||
/>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M14.75 12a2.75 2.75 0 11-5.5 0 2.75 2.75 0 015.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function SettingsIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M8.552 5.37l-1.793-.414a1 1 0 00-.932.267l-.604.604a1 1 0 00-.267.932l.414 1.793a1 1 0 01-.42 1.056l-1.755 1.17a1 1 0 00-.445.832v.78a1 1 0 00.445.832l1.755 1.17a1 1 0 01.42 1.056l-.414 1.793a1 1 0 00.267.932l.604.604a1 1 0 00.932.267l1.793-.414a1 1 0 011.056.42l1.17 1.755a1 1 0 00.832.445h.78a1 1 0 00.832-.445l1.17-1.755a1 1 0 011.056-.42l1.793.414a1 1 0 00.932-.267l.604-.604a1 1 0 00.267-.932l-.414-1.793a1 1 0 01.42-1.056l1.755-1.17a1 1 0 00.445-.832v-.78a1 1 0 00-.445-.832l-1.755-1.17a1 1 0 01-.42-1.056l.414-1.793a1 1 0 00-.267-.932l-.604-.604a1 1 0 00-.932-.267l-1.793.414a1 1 0 01-1.056-.42l-1.17-1.755a1 1 0 00-.832-.445h-.78a1 1 0 00-.832.445L9.608 4.95a1 1 0 01-1.056.42z"
|
||||
/>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M14.75 12a2.75 2.75 0 11-5.5 0 2.75 2.75 0 015.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function SignalIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M5.46 5.46A9.221 9.221 0 002.75 12a9.221 9.221 0 002.71 6.541M8.365 8.367a5.123 5.123 0 00-1.505 3.634c0 1.419.575 2.704 1.505 3.633m7.268 0a5.122 5.122 0 001.505-3.633c0-1.42-.575-2.704-1.505-3.634m2.907 10.174a9.22 9.22 0 002.709-6.54 9.22 9.22 0 00-2.71-6.541M12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function SignalIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M5.46 5.46A9.221 9.221 0 002.75 12a9.221 9.221 0 002.71 6.541M8.365 8.367a5.123 5.123 0 00-1.505 3.634c0 1.419.575 2.704 1.505 3.633m7.268 0a5.122 5.122 0 001.505-3.633c0-1.42-.575-2.704-1.505-3.634m2.907 10.174a9.22 9.22 0 002.709-6.54 9.22 9.22 0 00-2.71-6.541M12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function SpaceIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M2.79055 12.1075C1.4706 12.8201 0.744814 13.464 0.86677 13.9192C1.16059 15.0158 6.26396 14.6011 12.2655 12.993C18.2669 11.3849 22.8939 9.19232 22.6001 8.09576C22.4781 7.64061 21.5276 7.44582 20.0282 7.48865M2.79055 12.1075C2.80163 12.8595 2.90491 13.6226 3.10854 14.3825C4.43075 19.3171 9.48321 22.2508 14.3935 20.935C19.3038 19.6193 22.2126 14.5525 20.8904 9.61792C20.6867 8.85797 20.3946 8.14547 20.0282 7.48865M2.79055 12.1075C2.72972 7.9763 5.45127 4.1785 9.60537 3.06541C13.7595 1.95233 18.0153 3.88054 20.0282 7.48865"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function SpaceIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M2.79055 12.1075C1.4706 12.8201 0.744814 13.464 0.86677 13.9192C1.16059 15.0158 6.26396 14.6011 12.2655 12.993C18.2669 11.3849 22.8939 9.19232 22.6001 8.09576C22.4781 7.64061 21.5276 7.44582 20.0282 7.48865M2.79055 12.1075C2.80163 12.8595 2.90491 13.6226 3.10854 14.3825C4.43075 19.3171 9.48321 22.2508 14.3935 20.935C19.3038 19.6193 22.2126 14.5525 20.8904 9.61792C20.6867 8.85797 20.3946 8.14547 20.0282 7.48865M2.79055 12.1075C2.72972 7.9763 5.45127 4.1785 9.60537 3.06541C13.7595 1.95233 18.0153 3.88054 20.0282 7.48865"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ThreadIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M2.75 12h18.5M2.75 5.75h18.5m-18.5 12.5h8.75"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function ThreadIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M2.75 12h18.5M2.75 5.75h18.5m-18.5 12.5h8.75"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ThreadsIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M2.75 3.75V3C2.33579 3 2 3.33579 2 3.75H2.75ZM16.25 3.75H17C17 3.33579 16.6642 3 16.25 3V3.75ZM21.25 12H22C22 11.5858 21.6642 11.25 21.25 11.25V12ZM6.75 15C6.33579 15 6 15.3358 6 15.75C6 16.1642 6.33579 16.5 6.75 16.5V15ZM6.75 7.75V7C6.33579 7 6 7.33579 6 7.75H6.75ZM12.25 7.75H13C13 7.33579 12.6642 7 12.25 7V7.75ZM12.25 12.25V13C12.6642 13 13 12.6642 13 12.25H12.25ZM6.75 12.25H6C6 12.6642 6.33579 13 6.75 13V12.25ZM2.75 4.5H16.25V3H2.75V4.5ZM22 17.75V12H20.5V17.75H22ZM15.5 3.75V12H17V3.75H15.5ZM15.5 12V17.75H17V12H15.5ZM21.25 11.25H16.25V12.75H21.25V11.25ZM2 3.75V17.75H3.5V3.75H2ZM5.25 21H18.5V19.5H5.25V21ZM2 17.75C2 19.5449 3.45507 21 5.25 21V19.5C4.2835 19.5 3.5 18.7165 3.5 17.75H2ZM18.75 21C20.5449 21 22 19.5449 22 17.75H20.5C20.5 18.7165 19.7165 19.5 18.75 19.5V21ZM18.75 19.5C17.7835 19.5 17 18.7165 17 17.75H15.5C15.5 19.5449 16.9551 21 18.75 21V19.5ZM6.75 16.5H12.25V15H6.75V16.5ZM6.75 8.5H12.25V7H6.75V8.5ZM11.5 7.75V12.25H13V7.75H11.5ZM12.25 11.5H6.75V13H12.25V11.5ZM7.5 12.25V7.75H6V12.25H7.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function ThreadsIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M2.75 3.75V3C2.33579 3 2 3.33579 2 3.75H2.75ZM16.25 3.75H17C17 3.33579 16.6642 3 16.25 3V3.75ZM21.25 12H22C22 11.5858 21.6642 11.25 21.25 11.25V12ZM6.75 15C6.33579 15 6 15.3358 6 15.75C6 16.1642 6.33579 16.5 6.75 16.5V15ZM6.75 7.75V7C6.33579 7 6 7.33579 6 7.75H6.75ZM12.25 7.75H13C13 7.33579 12.6642 7 12.25 7V7.75ZM12.25 12.25V13C12.6642 13 13 12.6642 13 12.25H12.25ZM6.75 12.25H6C6 12.6642 6.33579 13 6.75 13V12.25ZM2.75 4.5H16.25V3H2.75V4.5ZM22 17.75V12H20.5V17.75H22ZM15.5 3.75V12H17V3.75H15.5ZM15.5 12V17.75H17V12H15.5ZM21.25 11.25H16.25V12.75H21.25V11.25ZM2 3.75V17.75H3.5V3.75H2ZM5.25 21H18.5V19.5H5.25V21ZM2 17.75C2 19.5449 3.45507 21 5.25 21V19.5C4.2835 19.5 3.5 18.7165 3.5 17.75H2ZM18.75 21C20.5449 21 22 19.5449 22 17.75H20.5C20.5 18.7165 19.7165 19.5 18.75 19.5V21ZM18.75 19.5C17.7835 19.5 17 18.7165 17 17.75H15.5C15.5 19.5449 16.9551 21 18.75 21V19.5ZM6.75 16.5H12.25V15H6.75V16.5ZM6.75 8.5H12.25V7H6.75V8.5ZM11.5 7.75V12.25H13V7.75H11.5ZM12.25 11.5H6.75V13H12.25V11.5ZM7.5 12.25V7.75H6V12.25H7.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function TrashIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M5.75 21.25L5.00156 21.2983C5.02702 21.6929 5.35453 22 5.75 22V21.25ZM18.25 21.25V22C18.6455 22 18.973 21.6929 18.9984 21.2983L18.25 21.25ZM2.75 5C2.33579 5 2 5.33579 2 5.75C2 6.16421 2.33579 6.5 2.75 6.5V5ZM21.25 6.5C21.6642 6.5 22 6.16421 22 5.75C22 5.33579 21.6642 5 21.25 5V6.5ZM10.5 10.75C10.5 10.3358 10.1642 10 9.75 10C9.33579 10 9 10.3358 9 10.75H10.5ZM9 16.25C9 16.6642 9.33579 17 9.75 17C10.1642 17 10.5 16.6642 10.5 16.25H9ZM15 10.75C15 10.3358 14.6642 10 14.25 10C13.8358 10 13.5 10.3358 13.5 10.75H15ZM13.5 16.25C13.5 16.6642 13.8358 17 14.25 17C14.6642 17 15 16.6642 15 16.25H13.5ZM15.1477 5.93694C15.2509 6.33808 15.6598 6.57957 16.0609 6.47633C16.4621 6.37308 16.7036 5.9642 16.6003 5.56306L15.1477 5.93694ZM4.00156 5.79829L5.00156 21.2983L6.49844 21.2017L5.49844 5.70171L4.00156 5.79829ZM5.75 22H18.25V20.5H5.75V22ZM18.9984 21.2983L19.9984 5.79829L18.5016 5.70171L17.5016 21.2017L18.9984 21.2983ZM19.25 5H4.75V6.5H19.25V5ZM2.75 6.5H4.75V5H2.75V6.5ZM19.25 6.5H21.25V5H19.25V6.5ZM9 10.75V16.25H10.5V10.75H9ZM13.5 10.75V16.25H15V10.75H13.5ZM12 3.5C13.5134 3.5 14.7868 4.53504 15.1477 5.93694L16.6003 5.56306C16.0731 3.51451 14.2144 2 12 2V3.5ZM8.85237 5.93694C9.21319 4.53504 10.4867 3.5 12 3.5V2C9.78568 2 7.92697 3.51451 7.39971 5.56306L8.85237 5.93694Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function TrashIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M5.75 21.25L5.00156 21.2983C5.02702 21.6929 5.35453 22 5.75 22V21.25ZM18.25 21.25V22C18.6455 22 18.973 21.6929 18.9984 21.2983L18.25 21.25ZM2.75 5C2.33579 5 2 5.33579 2 5.75C2 6.16421 2.33579 6.5 2.75 6.5V5ZM21.25 6.5C21.6642 6.5 22 6.16421 22 5.75C22 5.33579 21.6642 5 21.25 5V6.5ZM10.5 10.75C10.5 10.3358 10.1642 10 9.75 10C9.33579 10 9 10.3358 9 10.75H10.5ZM9 16.25C9 16.6642 9.33579 17 9.75 17C10.1642 17 10.5 16.6642 10.5 16.25H9ZM15 10.75C15 10.3358 14.6642 10 14.25 10C13.8358 10 13.5 10.3358 13.5 10.75H15ZM13.5 16.25C13.5 16.6642 13.8358 17 14.25 17C14.6642 17 15 16.6642 15 16.25H13.5ZM15.1477 5.93694C15.2509 6.33808 15.6598 6.57957 16.0609 6.47633C16.4621 6.37308 16.7036 5.9642 16.6003 5.56306L15.1477 5.93694ZM4.00156 5.79829L5.00156 21.2983L6.49844 21.2017L5.49844 5.70171L4.00156 5.79829ZM5.75 22H18.25V20.5H5.75V22ZM18.9984 21.2983L19.9984 5.79829L18.5016 5.70171L17.5016 21.2017L18.9984 21.2983ZM19.25 5H4.75V6.5H19.25V5ZM2.75 6.5H4.75V5H2.75V6.5ZM19.25 6.5H21.25V5H19.25V6.5ZM9 10.75V16.25H10.5V10.75H9ZM13.5 10.75V16.25H15V10.75H13.5ZM12 3.5C13.5134 3.5 14.7868 4.53504 15.1477 5.93694L16.6003 5.56306C16.0731 3.51451 14.2144 2 12 2V3.5ZM8.85237 5.93694C9.21319 4.53504 10.4867 3.5 12 3.5V2C9.78568 2 7.92697 3.51451 7.39971 5.56306L8.85237 5.93694Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function TrendingIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M15.75 6.75h5.5v5.5m-.514-4.975L13 15l-4-4-6.25 6.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function TrendingIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M15.75 6.75h5.5v5.5m-.514-4.975L13 15l-4-4-6.25 6.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function UnfollowIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M17.748 11.25h4.5m-7.5-4.75a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM3.67 19.166C4.432 15.773 7.203 13.25 11 13.25c3.795 0 6.566 2.524 7.328 5.916.13.575-.338 1.084-.927 1.084H4.597c-.59 0-1.056-.51-.927-1.084z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function UnfollowIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M17.748 11.25h4.5m-7.5-4.75a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM3.67 19.166C4.432 15.773 7.203 13.25 11 13.25c3.795 0 6.566 2.524 7.328 5.916.13.575-.338 1.084-.927 1.084H4.597c-.59 0-1.056-.51-.927-1.084z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function UnverifiedIcon(
|
||||
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"
|
||||
fillRule="evenodd"
|
||||
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12zm7.53-3.53a.75.75 0 00-1.06 1.06L10.94 12l-2.47 2.47a.75.75 0 101.06 1.06L12 13.06l2.47 2.47a.75.75 0 101.06-1.06L13.06 12l2.47-2.47a.75.75 0 00-1.06-1.06L12 10.94 9.53 8.47z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function UnverifiedIcon(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"
|
||||
fillRule="evenodd"
|
||||
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12zm7.53-3.53a.75.75 0 00-1.06 1.06L10.94 12l-2.47 2.47a.75.75 0 101.06 1.06L12 13.06l2.47 2.47a.75.75 0 101.06-1.06L13.06 12l2.47-2.47a.75.75 0 00-1.06-1.06L12 10.94 9.53 8.47z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function VerticalDotsIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
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="M12 4.75a1 1 0 100-2 1 1 0 000 2zM12 13a1 1 0 100-2 1 1 0 000 2zM12 21.25a1 1 0 100-2 1 1 0 000 2z"
|
||||
/>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M12 4.75a1 1 0 100-2 1 1 0 000 2zM12 13a1 1 0 100-2 1 1 0 000 2zM12 21.25a1 1 0 100-2 1 1 0 000 2z"
|
||||
/>
|
||||
</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="M12 4.75a1 1 0 100-2 1 1 0 000 2zM12 13a1 1 0 100-2 1 1 0 000 2zM12 21.25a1 1 0 100-2 1 1 0 000 2z"
|
||||
/>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M12 4.75a1 1 0 100-2 1 1 0 000 2zM12 13a1 1 0 100-2 1 1 0 000 2zM12 21.25a1 1 0 100-2 1 1 0 000 2z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function WorldIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M19.7783 4.22184L4.22197 19.7782M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12ZM18.5163 18.516C17.3167 19.7156 13.427 17.7707 9.82826 14.172C6.22955 10.5733 4.28467 6.68352 5.48424 5.48395C6.68381 4.28438 10.5736 6.22927 14.1723 9.82798C17.771 13.4267 19.7159 17.3165 18.5163 18.516Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function WorldIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M19.7783 4.22184L4.22197 19.7782M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12ZM18.5163 18.516C17.3167 19.7156 13.427 17.7707 9.82826 14.172C6.22955 10.5733 4.28467 6.68352 5.48424 5.48395C6.68381 4.28438 10.5736 6.22927 14.1723 9.82798C17.771 13.4267 19.7159 17.3165 18.5163 18.516Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SVGProps } from "react";
|
||||
import { SVGProps } from 'react';
|
||||
|
||||
export function ZapIcon(
|
||||
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3.75 12.75L8.75 2.75H18L15.25 8.25H21.25L6.75 21.25L8.89706 12.75H3.75Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export function ZapIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3.75 12.75L8.75 2.75H18L15.25 8.25H21.25L6.75 21.25L8.89706 12.75H3.75Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { ImgHTMLAttributes } from "react";
|
||||
import { ImgHTMLAttributes } from 'react';
|
||||
|
||||
interface Props extends ImgHTMLAttributes<any> {
|
||||
fallback: string;
|
||||
fallback: string;
|
||||
}
|
||||
|
||||
export function Image({ src, fallback, ...props }: Props) {
|
||||
return (
|
||||
<img
|
||||
{...props}
|
||||
src={src || fallback}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null;
|
||||
currentTarget.src = fallback;
|
||||
}}
|
||||
decoding="async"
|
||||
alt="lume default img"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<img
|
||||
{...props}
|
||||
src={src || fallback}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null;
|
||||
currentTarget.src = fallback;
|
||||
}}
|
||||
decoding="async"
|
||||
alt="lume default img"
|
||||
style={{ contentVisibility: 'auto' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,123 +1,119 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { removeAll } from "@libs/storage";
|
||||
import { CancelIcon, LogoutIcon } from "@shared/icons";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { relaunch } from "@tauri-apps/api/process";
|
||||
import { Fragment, useState } from "react";
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { relaunch } from '@tauri-apps/api/process';
|
||||
import { Fragment, useState } from 'react';
|
||||
|
||||
import { removeAll } from '@libs/storage';
|
||||
|
||||
import { CancelIcon, LogoutIcon } from '@shared/icons';
|
||||
|
||||
export function Logout() {
|
||||
const queryClient = useQueryClient();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
// reset database
|
||||
await removeAll();
|
||||
// reset react query
|
||||
queryClient.clear();
|
||||
// navigate
|
||||
await relaunch();
|
||||
};
|
||||
const logout = async () => {
|
||||
// reset database
|
||||
await removeAll();
|
||||
// reset react query
|
||||
queryClient.clear();
|
||||
// navigate
|
||||
await relaunch();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openModal()}
|
||||
aria-label="Logout"
|
||||
className="inline-flex items-center justify-center w-9 h-9 rounded-md border-t bg-zinc-800 border-zinc-700/50 transform active:translate-y-1"
|
||||
>
|
||||
<LogoutIcon className="w-4 h-4 text-zinc-400" />
|
||||
</button>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-semibold leading-none text-zinc-100"
|
||||
>
|
||||
Are you sure!
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<CancelIcon
|
||||
width={20}
|
||||
height={20}
|
||||
className="text-zinc-300"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<Dialog.Description className="text-sm leading-tight text-zinc-400">
|
||||
<p className="mb-2">
|
||||
When logout, all local data will be wiped, and restart
|
||||
app then you need to start onboarding process again when
|
||||
you log in.
|
||||
</p>
|
||||
<p>
|
||||
In the next version, Lume will support multi account,
|
||||
then you can switch between all account s instead of
|
||||
logout
|
||||
</p>
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col items-end justify-center overflow-y-auto px-5 py-2.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-9 items-center justify-center rounded-md px-3 text-sm font-medium text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => logout()}
|
||||
className="inline-flex h-9 items-center justify-center rounded-md bg-red-500 px-3 text-sm font-medium text-zinc-100 hover:bg-red-600"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openModal()}
|
||||
aria-label="Logout"
|
||||
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md border-t border-zinc-700/50 bg-zinc-800 active:translate-y-1"
|
||||
>
|
||||
<LogoutIcon className="h-4 w-4 text-zinc-400" />
|
||||
</button>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-semibold leading-none text-zinc-100"
|
||||
>
|
||||
Are you sure!
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<CancelIcon width={20} height={20} className="text-zinc-300" />
|
||||
</button>
|
||||
</div>
|
||||
<Dialog.Description className="text-sm leading-tight text-zinc-400">
|
||||
<p className="mb-2">
|
||||
When logout, all local data will be wiped, and restart app then
|
||||
you need to start onboarding process again when you log in.
|
||||
</p>
|
||||
<p>
|
||||
In the next version, Lume will support multi account, then you can
|
||||
switch between all account s instead of logout
|
||||
</p>
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col items-end justify-center overflow-y-auto px-5 py-2.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-9 items-center justify-center rounded-md px-3 text-sm font-medium text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => logout()}
|
||||
className="inline-flex h-9 items-center justify-center rounded-md bg-red-500 px-3 text-sm font-medium text-zinc-100 hover:bg-red-600"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
import { ActiveAccount } from "@shared/accounts/active";
|
||||
import { SettingsIcon } from "@shared/icons";
|
||||
import { Logout } from "@shared/logout";
|
||||
import { NotificationModal } from "@shared/notification/modal";
|
||||
import { useAccount } from "@utils/hooks/useAccount";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { ActiveAccount } from '@shared/accounts/active';
|
||||
import { SettingsIcon } from '@shared/icons';
|
||||
import { Logout } from '@shared/logout';
|
||||
import { NotificationModal } from '@shared/notification/modal';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
|
||||
export function LumeBar() {
|
||||
const { status, account } = useAccount();
|
||||
const { status, account } = useAccount();
|
||||
|
||||
return (
|
||||
<div className="rounded-xl p-2 border-t border-zinc-800/50 bg-zinc-900/80 backdrop-blur-md">
|
||||
<div className="flex items-center justify-between">
|
||||
{status === "loading" ? (
|
||||
<>
|
||||
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
|
||||
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ActiveAccount data={account} />
|
||||
<NotificationModal pubkey={account.pubkey} />
|
||||
</>
|
||||
)}
|
||||
<Link
|
||||
to="/settings/general"
|
||||
className="inline-flex items-center justify-center w-9 h-9 rounded-md border-t bg-zinc-800 border-zinc-700/50 transform active:translate-y-1"
|
||||
>
|
||||
<SettingsIcon className="w-4 h-4 text-zinc-400" />
|
||||
</Link>
|
||||
<Logout />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900/80 p-2 backdrop-blur-md">
|
||||
<div className="flex items-center justify-between">
|
||||
{status === 'loading' ? (
|
||||
<>
|
||||
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
|
||||
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ActiveAccount data={account} />
|
||||
<NotificationModal pubkey={account.pubkey} />
|
||||
</>
|
||||
)}
|
||||
<Link
|
||||
to="/settings/general"
|
||||
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md border-t border-zinc-700/50 bg-zinc-800 active:translate-y-1"
|
||||
>
|
||||
<SettingsIcon className="h-4 w-4 text-zinc-400" />
|
||||
</Link>
|
||||
<Logout />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,90 +1,92 @@
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { LoaderIcon, MediaIcon } from "@shared/icons";
|
||||
import { open } from "@tauri-apps/api/dialog";
|
||||
import { Body, fetch } from "@tauri-apps/api/http";
|
||||
import { createBlobFromFile } from "@utils/createBlobFromFile";
|
||||
import { useState } from "react";
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
import { open } from '@tauri-apps/api/dialog';
|
||||
import { Body, fetch } from '@tauri-apps/api/http';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { LoaderIcon, MediaIcon } from '@shared/icons';
|
||||
|
||||
import { createBlobFromFile } from '@utils/createBlobFromFile';
|
||||
|
||||
export function MediaUploader({ setState }: { setState: any }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const openFileDialog = async () => {
|
||||
const selected: any = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Image & Video",
|
||||
extensions: ["png", "jpeg", "jpg", "gif", "mp4", "mov"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (Array.isArray(selected)) {
|
||||
// user selected multiple files
|
||||
} else if (selected === null) {
|
||||
// user cancelled the selection
|
||||
} else {
|
||||
// start loading
|
||||
setLoading(true);
|
||||
const openFileDialog = async () => {
|
||||
const selected: any = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: 'Image & Video',
|
||||
extensions: ['png', 'jpeg', 'jpg', 'gif', 'mp4', 'mov'],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (Array.isArray(selected)) {
|
||||
// user selected multiple files
|
||||
} else if (selected === null) {
|
||||
// user cancelled the selection
|
||||
} else {
|
||||
// start loading
|
||||
setLoading(true);
|
||||
|
||||
const filename = selected.split("/").pop();
|
||||
const file = await createBlobFromFile(selected);
|
||||
const buf = await file.arrayBuffer();
|
||||
const filename = selected.split('/').pop();
|
||||
const file = await createBlobFromFile(selected);
|
||||
const buf = await file.arrayBuffer();
|
||||
|
||||
const res: { data: { file: { id: string } } } = await fetch(
|
||||
"https://void.cat/upload?cli=false",
|
||||
{
|
||||
method: "POST",
|
||||
timeout: 5,
|
||||
headers: {
|
||||
accept: "*/*",
|
||||
"Content-Type": "application/octet-stream",
|
||||
"V-Filename": filename,
|
||||
"V-Description": "Upload from https://lume.nu",
|
||||
"V-Strip-Metadata": "true",
|
||||
},
|
||||
body: Body.bytes(buf),
|
||||
},
|
||||
);
|
||||
const res: { data: { file: { id: string } } } = await fetch(
|
||||
'https://void.cat/upload?cli=false',
|
||||
{
|
||||
method: 'POST',
|
||||
timeout: 5,
|
||||
headers: {
|
||||
accept: '*/*',
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'V-Filename': filename,
|
||||
'V-Description': 'Upload from https://lume.nu',
|
||||
'V-Strip-Metadata': 'true',
|
||||
},
|
||||
body: Body.bytes(buf),
|
||||
}
|
||||
);
|
||||
|
||||
const image = `https://void.cat/d/${res.data.file.id}.webp`;
|
||||
const image = `https://void.cat/d/${res.data.file.id}.webp`;
|
||||
|
||||
// update state
|
||||
setState((prev: string) => `${prev}\n${image}`);
|
||||
// stop loading
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// update state
|
||||
setState((prev: string) => `${prev}\n${image}`);
|
||||
// stop loading
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openFileDialog()}
|
||||
className="group inline-flex h-6 w-6 items-center justify-center rounded bg-zinc-700 hover:bg-zinc-600"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
<MediaIcon
|
||||
width={14}
|
||||
height={14}
|
||||
className="text-zinc-400 group-hover:text-zinc-200"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
|
||||
sideOffset={5}
|
||||
>
|
||||
Upload media
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
);
|
||||
return (
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openFileDialog()}
|
||||
className="group inline-flex h-6 w-6 items-center justify-center rounded bg-zinc-700 hover:bg-zinc-600"
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
|
||||
) : (
|
||||
<MediaIcon
|
||||
width={14}
|
||||
height={14}
|
||||
className="text-zinc-400 group-hover:text-zinc-200"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg 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"
|
||||
sideOffset={5}
|
||||
>
|
||||
Upload media
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,65 +1,63 @@
|
||||
import { ChatsList } from "@app/chat/components/list";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
import { AppHeader } from "@shared/appHeader";
|
||||
import { Composer } from "@shared/composer/modal";
|
||||
import { NavArrowDownIcon, SpaceIcon, TrendingIcon } from "@shared/icons";
|
||||
import { LumeBar } from "@shared/lumeBar";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { ChatsList } from '@app/chat/components/list';
|
||||
|
||||
import { AppHeader } from '@shared/appHeader';
|
||||
import { Composer } from '@shared/composer/modal';
|
||||
import { NavArrowDownIcon, SpaceIcon, TrendingIcon } from '@shared/icons';
|
||||
import { LumeBar } from '@shared/lumeBar';
|
||||
|
||||
export function Navigation() {
|
||||
return (
|
||||
<div className="relative flex w-[232px] flex-col gap-3 border-r border-zinc-900">
|
||||
<AppHeader />
|
||||
<div className="pb-20 flex flex-col gap-5 overflow-y-auto scrollbar-hide">
|
||||
<div className="inlin-lflex h-8 px-3.5">
|
||||
<Composer />
|
||||
</div>
|
||||
{/* Newsfeed */}
|
||||
<div className="flex flex-col gap-0.5 px-1.5">
|
||||
<div className="px-2.5">
|
||||
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
|
||||
Feeds
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<NavLink
|
||||
to="/app/space"
|
||||
preventScrollReset={true}
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
|
||||
isActive ? "bg-zinc-900/50" : "",
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
|
||||
<SpaceIcon width={12} height={12} className="text-zinc-100" />
|
||||
</span>
|
||||
<span className="font-medium">Spaces</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/app/trending"
|
||||
preventScrollReset={true}
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
|
||||
isActive ? "bg-zinc-900/50" : "",
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
|
||||
<TrendingIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="text-zinc-100"
|
||||
/>
|
||||
</span>
|
||||
<span className="font-medium">Trending</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
{/* Channels
|
||||
return (
|
||||
<div className="relative flex w-[232px] flex-col gap-3 border-r border-zinc-900">
|
||||
<AppHeader />
|
||||
<div className="scrollbar-hide flex flex-col gap-5 overflow-y-auto pb-20">
|
||||
<div className="inlin-lflex h-8 px-3.5">
|
||||
<Composer />
|
||||
</div>
|
||||
{/* Newsfeed */}
|
||||
<div className="flex flex-col gap-0.5 px-1.5">
|
||||
<div className="px-2.5">
|
||||
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
|
||||
Feeds
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<NavLink
|
||||
to="/app/space"
|
||||
preventScrollReset={true}
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
'flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200',
|
||||
isActive ? 'bg-zinc-900/50' : ''
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
|
||||
<SpaceIcon width={12} height={12} className="text-zinc-100" />
|
||||
</span>
|
||||
<span className="font-medium">Spaces</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/app/trending"
|
||||
preventScrollReset={true}
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
'flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200',
|
||||
isActive ? 'bg-zinc-900/50' : ''
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
|
||||
<TrendingIcon width={12} height={12} className="text-zinc-100" />
|
||||
</span>
|
||||
<span className="font-medium">Trending</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
{/* Channels
|
||||
<Disclosure defaultOpen={true}>
|
||||
{({ open }) => (
|
||||
<div className="flex flex-col gap-0.5 px-1.5">
|
||||
@@ -86,36 +84,32 @@ export function Navigation() {
|
||||
)}
|
||||
</Disclosure>
|
||||
*/}
|
||||
{/* Chats */}
|
||||
<Disclosure defaultOpen={true}>
|
||||
{({ open }) => (
|
||||
<div className="flex flex-col gap-0.5 px-1.5 pb-6">
|
||||
<Disclosure.Button className="flex items-center gap-1 px-3">
|
||||
<div
|
||||
className={`inline-flex h-5 w-5 transform items-center justify-center transition-transform duration-150 ease-in-out ${
|
||||
open ? "" : "rotate-180"
|
||||
}`}
|
||||
>
|
||||
<NavArrowDownIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="text-zinc-700"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
|
||||
Chats
|
||||
</h3>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<ChatsList />
|
||||
</Disclosure.Panel>
|
||||
</div>
|
||||
)}
|
||||
</Disclosure>
|
||||
</div>
|
||||
<div className="absolute bottom-3 left-0 px-10 w-full">
|
||||
<LumeBar />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
{/* Chats */}
|
||||
<Disclosure defaultOpen={true}>
|
||||
{({ open }) => (
|
||||
<div className="flex flex-col gap-0.5 px-1.5 pb-6">
|
||||
<Disclosure.Button className="flex items-center gap-1 px-3">
|
||||
<div
|
||||
className={`inline-flex h-5 w-5 transform items-center justify-center transition-transform duration-150 ease-in-out ${
|
||||
open ? '' : 'rotate-180'
|
||||
}`}
|
||||
>
|
||||
<NavArrowDownIcon width={12} height={12} className="text-zinc-700" />
|
||||
</div>
|
||||
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
|
||||
Chats
|
||||
</h3>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<ChatsList />
|
||||
</Disclosure.Panel>
|
||||
</div>
|
||||
)}
|
||||
</Disclosure>
|
||||
</div>
|
||||
<div className="absolute bottom-3 left-0 w-full px-10">
|
||||
<LumeBar />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useNetworkStatus } from "@utils/hooks/useNetworkStatus";
|
||||
import { useNetworkStatus } from '@utils/hooks/useNetworkStatus';
|
||||
|
||||
export function NetworkStatusIndicator() {
|
||||
const isOnline = useNetworkStatus();
|
||||
const isOnline = useNetworkStatus();
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`absolute right-0 top-0 block h-2 w-2 -translate-y-1/2 translate-x-1/2 transform rounded-full ${
|
||||
isOnline ? "bg-green-400" : "bg-red-400"
|
||||
} ring-2 ring-zinc-900`}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<span
|
||||
className={`absolute right-0 top-0 block h-2 w-2 -translate-y-1/2 translate-x-1/2 transform rounded-full ${
|
||||
isOnline ? 'bg-green-400' : 'bg-red-400'
|
||||
} ring-2 ring-zinc-900`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
import { MentionNote } from "@shared/notes/mentions/note";
|
||||
import { ImagePreview } from "@shared/notes/preview/image";
|
||||
import { LinkPreview } from "@shared/notes/preview/link";
|
||||
import { VideoPreview } from "@shared/notes/preview/video";
|
||||
import { ReactNode } from "react";
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { MentionNote } from '@shared/notes/mentions/note';
|
||||
import { ImagePreview } from '@shared/notes/preview/image';
|
||||
import { LinkPreview } from '@shared/notes/preview/link';
|
||||
import { VideoPreview } from '@shared/notes/preview/video';
|
||||
|
||||
export function Kind1({
|
||||
content,
|
||||
truncate = false,
|
||||
content,
|
||||
truncate = false,
|
||||
}: {
|
||||
content: {
|
||||
original: string;
|
||||
parsed: ReactNode[];
|
||||
notes: string[];
|
||||
images: string[];
|
||||
videos: string[];
|
||||
links: string[];
|
||||
};
|
||||
truncate?: boolean;
|
||||
content: {
|
||||
original: string;
|
||||
parsed: ReactNode[];
|
||||
notes: string[];
|
||||
images: string[];
|
||||
videos: string[];
|
||||
links: string[];
|
||||
};
|
||||
truncate?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`select-text whitespace-pre-line break-words text-base text-zinc-100 ${
|
||||
truncate ? "line-clamp-3" : ""
|
||||
}`}
|
||||
>
|
||||
{content.parsed}
|
||||
</div>
|
||||
{content.images.length > 0 && (
|
||||
<ImagePreview urls={content.images} truncate={truncate} />
|
||||
)}
|
||||
{content.videos.length > 0 && <VideoPreview urls={content.videos} />}
|
||||
{content.links.length > 0 && <LinkPreview urls={content.links} />}
|
||||
{content.notes.length > 0 &&
|
||||
content.notes.map((note: string) => (
|
||||
<MentionNote key={note} id={note} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`select-text whitespace-pre-line break-words text-base text-zinc-100 ${
|
||||
truncate ? 'line-clamp-3' : ''
|
||||
}`}
|
||||
>
|
||||
{content.parsed}
|
||||
</div>
|
||||
{content.images.length > 0 && (
|
||||
<ImagePreview urls={content.images} truncate={truncate} />
|
||||
)}
|
||||
{content.videos.length > 0 && <VideoPreview urls={content.videos} />}
|
||||
{content.links.length > 0 && <LinkPreview urls={content.links} />}
|
||||
{content.notes.length > 0 &&
|
||||
content.notes.map((note: string) => <MentionNote key={note} id={note} />)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { NDKTag } from "@nostr-dev-kit/ndk";
|
||||
import { Image } from "@shared/image";
|
||||
import { NDKTag } from '@nostr-dev-kit/ndk';
|
||||
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
function isImage(url: string) {
|
||||
return /\.(jpg|jpeg|gif|png|webp|avif)$/.test(url);
|
||||
return /\.(jpg|jpeg|gif|png|webp|avif)$/.test(url);
|
||||
}
|
||||
|
||||
export function Kind1063({ metadata }: { metadata: NDKTag[] }) {
|
||||
const url = metadata[0][1];
|
||||
const url = metadata[0][1];
|
||||
|
||||
return (
|
||||
<div className="mt-3">
|
||||
{isImage(url) && (
|
||||
<Image
|
||||
src={url}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt="image"
|
||||
className="h-auto w-full rounded-lg object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mt-3">
|
||||
{isImage(url) && (
|
||||
<Image
|
||||
src={url}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt="image"
|
||||
className="h-auto w-full rounded-lg object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,70 +1,73 @@
|
||||
import { createBlock } from "@libs/storage";
|
||||
import { Kind1 } from "@shared/notes/contents/kind1";
|
||||
import { Kind1063 } from "@shared/notes/contents/kind1063";
|
||||
import { NoteSkeleton } from "@shared/notes/skeleton";
|
||||
import { User } from "@shared/user";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useEvent } from "@utils/hooks/useEvent";
|
||||
import { memo } from "react";
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { createBlock } from '@libs/storage';
|
||||
|
||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
|
||||
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||
const queryClient = useQueryClient();
|
||||
const { status, data } = useEvent(id);
|
||||
const queryClient = useQueryClient();
|
||||
const { status, data } = useEvent(id);
|
||||
|
||||
const block = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createBlock(data.kind, data.title, data.content);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["blocks"] });
|
||||
},
|
||||
});
|
||||
const block = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createBlock(data.kind, data.title, data.content);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['blocks'] });
|
||||
},
|
||||
});
|
||||
|
||||
const openThread = (event: any, thread: string) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
block.mutate({ kind: 2, title: "Thread", content: thread });
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
const openThread = (event: any, thread: string) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
block.mutate({ kind: 2, title: 'Thread', content: thread });
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => openThread(e, id)}
|
||||
onKeyDown={(e) => openThread(e, id)}
|
||||
className="mt-3 rounded-lg bg-zinc-800/50 border-t border-zinc-700/50 px-3 py-3"
|
||||
>
|
||||
{status === "loading" ? (
|
||||
<NoteSkeleton />
|
||||
) : status === "success" ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} size="small" />
|
||||
<div className="mt-2">
|
||||
{data.kind === 1 && (
|
||||
<Kind1 content={data.content} truncate={true} />
|
||||
)}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
|
||||
<span className="text-zinc-500 text-sm font-medium leading-none">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-fuchsia-500 text-sm leading-none">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => openThread(e, id)}
|
||||
onKeyDown={(e) => openThread(e, id)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="mt-3 rounded-lg border-t border-zinc-700/50 bg-zinc-800/50 px-3 py-3"
|
||||
>
|
||||
{status === 'loading' ? (
|
||||
<NoteSkeleton />
|
||||
) : status === 'success' ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} size="small" />
|
||||
<div className="mt-2">
|
||||
{data.kind === 1 && <Kind1 content={data.content} truncate={true} />}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-sm leading-none text-fuchsia-500">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { shortenKey } from "@utils/shortenKey";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { shortenKey } from '@utils/shortenKey';
|
||||
|
||||
export function MentionUser({ pubkey }: { pubkey: string }) {
|
||||
const { user } = useProfile(pubkey);
|
||||
const { user } = useProfile(pubkey);
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/app/user/${pubkey}`}
|
||||
className="text-fuchsia-500 hover:text-fuchsia-600 no-underline font-normal"
|
||||
>
|
||||
@{user?.name || user?.displayName || shortenKey(pubkey)}
|
||||
</Link>
|
||||
);
|
||||
return (
|
||||
<Link
|
||||
to={`/app/user/${pubkey}`}
|
||||
className="font-normal text-fuchsia-500 no-underline hover:text-fuchsia-600"
|
||||
>
|
||||
@{user?.name || user?.displayName || shortenKey(pubkey)}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,171 +1,171 @@
|
||||
import { createBlock, createReplyNote } from "@libs/storage";
|
||||
import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { LoaderIcon, ReplyIcon, RepostIcon, ZapIcon } from "@shared/icons";
|
||||
import { ThreadIcon } from "@shared/icons/thread";
|
||||
import { NoteReply } from "@shared/notes/metadata/reply";
|
||||
import { NoteRepost } from "@shared/notes/metadata/repost";
|
||||
import { NoteZap } from "@shared/notes/metadata/zap";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { decode } from "light-bolt11-decoder";
|
||||
import { useContext } from "react";
|
||||
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { decode } from 'light-bolt11-decoder';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { createBlock, createReplyNote } from '@libs/storage';
|
||||
|
||||
import { LoaderIcon, ReplyIcon, RepostIcon, ZapIcon } from '@shared/icons';
|
||||
import { ThreadIcon } from '@shared/icons/thread';
|
||||
import { NoteReply } from '@shared/notes/metadata/reply';
|
||||
import { NoteRepost } from '@shared/notes/metadata/repost';
|
||||
import { NoteZap } from '@shared/notes/metadata/zap';
|
||||
import { RelayContext } from '@shared/relayProvider';
|
||||
|
||||
export function NoteMetadata({
|
||||
id,
|
||||
rootID,
|
||||
eventPubkey,
|
||||
id,
|
||||
rootID,
|
||||
eventPubkey,
|
||||
}: {
|
||||
id: string;
|
||||
rootID?: string;
|
||||
eventPubkey: string;
|
||||
id: string;
|
||||
rootID?: string;
|
||||
eventPubkey: string;
|
||||
}) {
|
||||
const ndk = useContext(RelayContext);
|
||||
const queryClient = useQueryClient();
|
||||
const ndk = useContext(RelayContext);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { status, data } = useQuery(["note-metadata", id], async () => {
|
||||
let replies = 0;
|
||||
let reposts = 0;
|
||||
let zap = 0;
|
||||
const { status, data } = useQuery(['note-metadata', id], async () => {
|
||||
let replies = 0;
|
||||
let reposts = 0;
|
||||
let zap = 0;
|
||||
|
||||
const filter: NDKFilter = {
|
||||
"#e": [id],
|
||||
kinds: [1, 6, 9735],
|
||||
};
|
||||
const filter: NDKFilter = {
|
||||
'#e': [id],
|
||||
kinds: [1, 6, 9735],
|
||||
};
|
||||
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
events.forEach((event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
replies += 1;
|
||||
createReplyNote(
|
||||
id,
|
||||
event.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
);
|
||||
break;
|
||||
case 6:
|
||||
reposts += 1;
|
||||
break;
|
||||
case 9735: {
|
||||
const bolt11 = event.tags.find((tag) => tag[0] === "bolt11")[1];
|
||||
if (bolt11) {
|
||||
const decoded = decode(bolt11);
|
||||
const amount = decoded.sections.find(
|
||||
(item) => item.name === "amount",
|
||||
);
|
||||
const sats = amount.value / 1000;
|
||||
zap += sats;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
events.forEach((event: NDKEvent) => {
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
replies += 1;
|
||||
createReplyNote(
|
||||
id,
|
||||
event.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at
|
||||
);
|
||||
break;
|
||||
case 6:
|
||||
reposts += 1;
|
||||
break;
|
||||
case 9735: {
|
||||
const bolt11 = event.tags.find((tag) => tag[0] === 'bolt11')[1];
|
||||
if (bolt11) {
|
||||
const decoded = decode(bolt11);
|
||||
const amount = decoded.sections.find((item) => item.name === 'amount');
|
||||
const sats = amount.value / 1000;
|
||||
zap += sats;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return { replies, reposts, zap };
|
||||
});
|
||||
return { replies, reposts, zap };
|
||||
});
|
||||
|
||||
const block = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createBlock(data.kind, data.title, data.content);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["blocks"] });
|
||||
},
|
||||
});
|
||||
const block = useMutation({
|
||||
mutationFn: (data: any) => {
|
||||
return createBlock(data.kind, data.title, data.content);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['blocks'] });
|
||||
},
|
||||
});
|
||||
|
||||
const openThread = (thread: string) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
block.mutate({ kind: 2, title: "Thread", content: thread });
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
const openThread = (thread: string) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection.toString().length === 0) {
|
||||
block.mutate({ kind: 2, title: 'Thread', content: thread });
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
if (status === "loading") {
|
||||
return (
|
||||
<div className="inline-flex items-center w-full h-12 mt-2">
|
||||
<div className="w-20 group inline-flex items-center gap-1.5">
|
||||
<ReplyIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-20 group inline-flex items-center gap-1.5">
|
||||
<RepostIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-20 group inline-flex items-center gap-1.5">
|
||||
<ZapIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="mt-2 inline-flex h-12 w-full items-center">
|
||||
<div className="group inline-flex w-20 items-center gap-1.5">
|
||||
<ReplyIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="group inline-flex w-20 items-center gap-1.5">
|
||||
<RepostIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="group inline-flex w-20 items-center gap-1.5">
|
||||
<ZapIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-400 group-hover:text-green-400"
|
||||
/>
|
||||
<LoaderIcon
|
||||
width={12}
|
||||
height={12}
|
||||
className="animate-spin text-black dark:text-zinc-100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip.Provider>
|
||||
<div className="inline-flex items-center justify-between w-full h-12 mt-2">
|
||||
<div className="inline-flex justify-between items-center">
|
||||
<NoteReply
|
||||
id={id}
|
||||
rootID={rootID}
|
||||
pubkey={eventPubkey}
|
||||
replies={data.replies}
|
||||
/>
|
||||
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
|
||||
<NoteZap zaps={data.zap} />
|
||||
</div>
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openThread(id)}
|
||||
className="w-6 h-6 inline-flex items-center justify-center rounded border-t border-zinc-700/50 bg-zinc-800 hover:bg-zinc-700"
|
||||
>
|
||||
<ThreadIcon className="w-4 h-4 text-zinc-400" />
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
|
||||
sideOffset={5}
|
||||
>
|
||||
Open thread
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</Tooltip.Provider>
|
||||
);
|
||||
return (
|
||||
<Tooltip.Provider>
|
||||
<div className="mt-2 inline-flex h-12 w-full items-center justify-between">
|
||||
<div className="inline-flex items-center justify-between">
|
||||
<NoteReply
|
||||
id={id}
|
||||
rootID={rootID}
|
||||
pubkey={eventPubkey}
|
||||
replies={data.replies}
|
||||
/>
|
||||
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
|
||||
<NoteZap zaps={data.zap} />
|
||||
</div>
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openThread(id)}
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-700/50 bg-zinc-800 hover:bg-zinc-700"
|
||||
>
|
||||
<ThreadIcon className="h-4 w-4 text-zinc-400" />
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg 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"
|
||||
sideOffset={5}
|
||||
>
|
||||
Open thread
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</Tooltip.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,49 @@
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { ReplyIcon } from "@shared/icons";
|
||||
import { useComposer } from "@stores/composer";
|
||||
import { compactNumber } from "@utils/number";
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
|
||||
import { ReplyIcon } from '@shared/icons';
|
||||
|
||||
import { useComposer } from '@stores/composer';
|
||||
|
||||
import { compactNumber } from '@utils/number';
|
||||
|
||||
export function NoteReply({
|
||||
id,
|
||||
rootID,
|
||||
pubkey,
|
||||
replies,
|
||||
}: { id: string; rootID?: string; pubkey: string; replies: number }) {
|
||||
const setReply = useComposer((state) => state.setReply);
|
||||
id,
|
||||
rootID,
|
||||
pubkey,
|
||||
replies,
|
||||
}: {
|
||||
id: string;
|
||||
rootID?: string;
|
||||
pubkey: string;
|
||||
replies: number;
|
||||
}) {
|
||||
const setReply = useComposer((state) => state.setReply);
|
||||
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setReply(id, rootID, pubkey)}
|
||||
className="group w-20 h-6 group inline-flex items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex items-center justify-center w-6 h-6 rounded group-hover:bg-zinc-800">
|
||||
<ReplyIcon className="w-4 h-4 text-zinc-400 group-hover:text-green-500" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(replies)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
|
||||
sideOffset={5}
|
||||
>
|
||||
Quick reply
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setReply(id, rootID, pubkey)}
|
||||
className="group group inline-flex h-6 w-20 items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
|
||||
<ReplyIcon className="h-4 w-4 text-zinc-400 group-hover:text-green-500" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(replies)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg 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"
|
||||
sideOffset={5}
|
||||
>
|
||||
Quick reply
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,40 +1,47 @@
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { RepostIcon } from "@shared/icons";
|
||||
import { useComposer } from "@stores/composer";
|
||||
import { compactNumber } from "@utils/number";
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
|
||||
import { RepostIcon } from '@shared/icons';
|
||||
|
||||
import { useComposer } from '@stores/composer';
|
||||
|
||||
import { compactNumber } from '@utils/number';
|
||||
|
||||
export function NoteRepost({
|
||||
id,
|
||||
pubkey,
|
||||
reposts,
|
||||
}: { id: string; pubkey: string; reposts: number }) {
|
||||
const setRepost = useComposer((state) => state.setRepost);
|
||||
id,
|
||||
pubkey,
|
||||
reposts,
|
||||
}: {
|
||||
id: string;
|
||||
pubkey: string;
|
||||
reposts: number;
|
||||
}) {
|
||||
const setRepost = useComposer((state) => state.setRepost);
|
||||
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRepost(id, pubkey)}
|
||||
className="group w-20 h-6 group inline-flex items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex items-center justify-center w-6 h-6 rounded group-hover:bg-zinc-800">
|
||||
<RepostIcon className="w-4 h-4 text-zinc-400 group-hover:text-blue-400" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(reposts)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
|
||||
sideOffset={5}
|
||||
>
|
||||
Repost
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRepost(id, pubkey)}
|
||||
className="group group inline-flex h-6 w-20 items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
|
||||
<RepostIcon className="h-4 w-4 text-zinc-400 group-hover:text-blue-400" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(reposts)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg 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"
|
||||
sideOffset={5}
|
||||
>
|
||||
Repost
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { ZapIcon } from "@shared/icons";
|
||||
import { compactNumber } from "@utils/number";
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
|
||||
import { ZapIcon } from '@shared/icons';
|
||||
|
||||
import { compactNumber } from '@utils/number';
|
||||
|
||||
export function NoteZap({ zaps }: { zaps: number }) {
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
className="group w-20 h-6 group inline-flex items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex items-center justify-center w-6 h-6 rounded group-hover:bg-zinc-800">
|
||||
<ZapIcon className="w-4 h-4 text-zinc-400 group-hover:text-orange-400" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(zaps)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
|
||||
sideOffset={5}
|
||||
>
|
||||
Coming Soon
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
return (
|
||||
<Tooltip.Root delayDuration={150}>
|
||||
<button
|
||||
type="button"
|
||||
className="group group inline-flex h-6 w-20 items-center gap-1.5"
|
||||
>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
|
||||
<ZapIcon className="h-4 w-4 text-zinc-400 group-hover:text-orange-400" />
|
||||
</span>
|
||||
</Tooltip.Trigger>
|
||||
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
|
||||
{compactNumber.format(zaps)}
|
||||
</span>
|
||||
</button>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content
|
||||
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg 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"
|
||||
sideOffset={5}
|
||||
>
|
||||
Coming Soon
|
||||
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,89 +1,86 @@
|
||||
import { Kind1 } from "@shared/notes/contents/kind1";
|
||||
import { Kind1063 } from "@shared/notes/contents/kind1063";
|
||||
import { NoteMetadata } from "@shared/notes/metadata";
|
||||
import { NoteParent } from "@shared/notes/parent";
|
||||
import { Repost } from "@shared/notes/repost";
|
||||
import { User } from "@shared/user";
|
||||
import { parser } from "@utils/parser";
|
||||
import { LumeEvent } from "@utils/types";
|
||||
import { useMemo } from "react";
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
||||
import { NoteMetadata } from '@shared/notes/metadata';
|
||||
import { NoteParent } from '@shared/notes/parent';
|
||||
import { Repost } from '@shared/notes/repost';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { parser } from '@utils/parser';
|
||||
import { LumeEvent } from '@utils/types';
|
||||
|
||||
interface Note {
|
||||
event: LumeEvent;
|
||||
block?: number;
|
||||
event: LumeEvent;
|
||||
}
|
||||
|
||||
export function Note({ event, block }: Note) {
|
||||
const isRepost = event.kind === 6;
|
||||
export function Note({ event }: Note) {
|
||||
const isRepost = event.kind === 6;
|
||||
|
||||
const renderParent = useMemo(() => {
|
||||
if (!isRepost && event.parent_id && event.parent_id !== event.event_id) {
|
||||
return <NoteParent id={event.parent_id} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [event.parent_id]);
|
||||
const renderParent = useMemo(() => {
|
||||
if (!isRepost && event.parent_id && event.parent_id !== event.event_id) {
|
||||
return <NoteParent id={event.parent_id} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [event.parent_id]);
|
||||
|
||||
const renderRepost = useMemo(() => {
|
||||
if (isRepost) {
|
||||
return <Repost event={event} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [event.kind]);
|
||||
const renderRepost = useMemo(() => {
|
||||
if (isRepost) {
|
||||
return <Repost event={event} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [event.kind]);
|
||||
|
||||
const renderContent = useMemo(() => {
|
||||
switch (event.kind) {
|
||||
case 1: {
|
||||
const content = parser(event);
|
||||
return <Kind1 content={content} />;
|
||||
}
|
||||
case 6:
|
||||
return null;
|
||||
case 1063:
|
||||
return <Kind1063 metadata={event.tags} />;
|
||||
default:
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
|
||||
<span className="text-zinc-500 text-sm font-medium leading-none">
|
||||
Kind: {event.kind}
|
||||
</span>
|
||||
<p className="text-fuchsia-500 text-sm leading-none">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{event.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}, [event.kind]);
|
||||
const renderContent = useMemo(() => {
|
||||
switch (event.kind) {
|
||||
case 1: {
|
||||
const content = parser(event);
|
||||
return <Kind1 content={content} />;
|
||||
}
|
||||
case 6:
|
||||
return null;
|
||||
case 1063:
|
||||
return <Kind1063 metadata={event.tags} />;
|
||||
default:
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
||||
Kind: {event.kind}
|
||||
</span>
|
||||
<p className="text-sm leading-none text-fuchsia-500">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{event.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}, [event.kind]);
|
||||
|
||||
return (
|
||||
<div className="h-min w-full px-3 py-1.5">
|
||||
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||
{renderParent}
|
||||
<div className="flex flex-col">
|
||||
<User
|
||||
pubkey={event.pubkey}
|
||||
time={event.created_at}
|
||||
repost={isRepost}
|
||||
/>
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{renderContent}
|
||||
{!isRepost && (
|
||||
<NoteMetadata
|
||||
id={event.event_id}
|
||||
rootID={event.parent_id}
|
||||
eventPubkey={event.pubkey}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{renderRepost}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="h-min w-full px-3 py-1.5">
|
||||
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
|
||||
{renderParent}
|
||||
<div className="flex flex-col">
|
||||
<User pubkey={event.pubkey} time={event.created_at} repost={isRepost} />
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{renderContent}
|
||||
{!isRepost && (
|
||||
<NoteMetadata
|
||||
id={event.event_id}
|
||||
rootID={event.parent_id}
|
||||
eventPubkey={event.pubkey}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{renderRepost}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,48 +1,46 @@
|
||||
import { Kind1 } from "@shared/notes/contents/kind1";
|
||||
import { Kind1063 } from "@shared/notes/contents/kind1063";
|
||||
import { NoteMetadata } from "@shared/notes/metadata";
|
||||
import { NoteSkeleton } from "@shared/notes/skeleton";
|
||||
import { User } from "@shared/user";
|
||||
import { useEvent } from "@utils/hooks/useEvent";
|
||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
||||
import { NoteMetadata } from '@shared/notes/metadata';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
|
||||
export function NoteParent({ id }: { id: string }) {
|
||||
const { status, data } = useEvent(id);
|
||||
const { status, data } = useEvent(id);
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col pb-6">
|
||||
<div className="absolute left-[18px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||
{status === "loading" ? (
|
||||
<NoteSkeleton />
|
||||
) : status === "success" ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
|
||||
<span className="text-zinc-500 text-sm font-medium leading-none">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-fuchsia-500 text-sm leading-none">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content || data.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<NoteMetadata
|
||||
id={data.event_id || data.id}
|
||||
eventPubkey={data.pubkey}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative flex flex-col pb-6">
|
||||
<div className="absolute left-[18px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||
{status === 'loading' ? (
|
||||
<NoteSkeleton />
|
||||
) : status === 'success' ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-sm leading-none text-fuchsia-500">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content || data.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<NoteMetadata id={data.event_id || data.id} eventPubkey={data.pubkey} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import { Image } from "@shared/image";
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
export function ImagePreview({
|
||||
urls,
|
||||
truncate,
|
||||
}: { urls: string[]; truncate?: boolean }) {
|
||||
return (
|
||||
<div className="mt-3 max-w-[420px] overflow-hidden">
|
||||
<div className="flex flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<div key={url} className="min-w-0 grow-0 shrink-0 basis-full">
|
||||
<Image
|
||||
src={url}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt="image"
|
||||
className={`${
|
||||
truncate ? "h-auto max-h-[300px]" : "h-auto"
|
||||
} w-full border border-zinc-800/50 rounded-lg object-cover`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export function ImagePreview({ urls, truncate }: { urls: string[]; truncate?: boolean }) {
|
||||
return (
|
||||
<div className="mt-3 max-w-[420px] overflow-hidden">
|
||||
<div className="flex flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<div key={url} className="min-w-0 shrink-0 grow-0 basis-full">
|
||||
<Image
|
||||
src={url}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt="image"
|
||||
className={`${
|
||||
truncate ? 'h-auto max-h-[300px]' : 'h-auto'
|
||||
} w-full rounded-lg border border-zinc-800/50 object-cover`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,64 +1,62 @@
|
||||
import { Image } from "@shared/image";
|
||||
import { useOpenGraph } from "@utils/hooks/useOpenGraph";
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { useOpenGraph } from '@utils/hooks/useOpenGraph';
|
||||
|
||||
export function LinkPreview({ urls }: { urls: string[] }) {
|
||||
const domain = new URL(urls[0]);
|
||||
const { status, data, error } = useOpenGraph(urls[0]);
|
||||
const domain = new URL(urls[0]);
|
||||
const { status, data, error } = useOpenGraph(urls[0]);
|
||||
|
||||
return (
|
||||
<div className="mt-3 max-w-[420px] overflow-hidden rounded-lg bg-zinc-800">
|
||||
{status === "loading" ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full h-44 bg-zinc-700 animate-pulse" />
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<div className="w-2/3 h-3 rounded bg-zinc-700 animate-pulse" />
|
||||
<div className="w-3/4 h-3 rounded bg-zinc-700 animate-pulse" />
|
||||
<span className="mt-2.5 leading-none text-sm text-zinc-500">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<a
|
||||
className="flex flex-col rounded-lg border border-zinc-800/50"
|
||||
href={urls[0]}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{error ? (
|
||||
<div className="px-3 py-3">
|
||||
<p className="text-sm text-zinc-400 break-all line-clamp-3">
|
||||
Can't fetch open graph, click to open webpage
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Image
|
||||
src={
|
||||
data.images?.[0] ||
|
||||
"https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt={urls[0]}
|
||||
className="w-full h-44 object-cover rounded-t-lg"
|
||||
/>
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<h5 className="leading-none font-medium text-zinc-200 line-clamp-1">
|
||||
{data.title}
|
||||
</h5>
|
||||
{data.description && (
|
||||
<p className="text-sm text-zinc-400 break-all line-clamp-3">
|
||||
{data.description}
|
||||
</p>
|
||||
)}
|
||||
<span className="mt-2.5 leading-none text-sm text-zinc-500">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mt-3 max-w-[420px] overflow-hidden rounded-lg bg-zinc-800">
|
||||
{status === 'loading' ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="h-44 w-full animate-pulse bg-zinc-700" />
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<div className="h-3 w-2/3 animate-pulse rounded bg-zinc-700" />
|
||||
<div className="h-3 w-3/4 animate-pulse rounded bg-zinc-700" />
|
||||
<span className="mt-2.5 text-sm leading-none text-zinc-500">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<a
|
||||
className="flex flex-col rounded-lg border border-zinc-800/50"
|
||||
href={urls[0]}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{error ? (
|
||||
<div className="px-3 py-3">
|
||||
<p className="line-clamp-3 break-all text-sm text-zinc-400">
|
||||
Can't fetch open graph, click to open webpage
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Image
|
||||
src={data.images?.[0] || 'https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW'}
|
||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
||||
alt={urls[0]}
|
||||
className="h-44 w-full rounded-t-lg object-cover"
|
||||
/>
|
||||
<div className="flex flex-col gap-2 px-3 py-3">
|
||||
<h5 className="line-clamp-1 font-medium leading-none text-zinc-200">
|
||||
{data.title}
|
||||
</h5>
|
||||
{data.description && (
|
||||
<p className="line-clamp-3 break-all text-sm text-zinc-400">
|
||||
{data.description}
|
||||
</p>
|
||||
)}
|
||||
<span className="mt-2.5 text-sm leading-none text-zinc-500">
|
||||
{domain.hostname}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import ReactPlayer from "react-player/es6";
|
||||
import ReactPlayer from 'react-player/es6';
|
||||
|
||||
export function VideoPreview({ urls }: { urls: string[] }) {
|
||||
return (
|
||||
<div className="relative mt-3 max-w-[420px] flex w-full flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<ReactPlayer
|
||||
key={url}
|
||||
url={url}
|
||||
width="100%"
|
||||
height="auto"
|
||||
className="!h-auto object-fill rounded-lg overflow-hidden"
|
||||
controls={true}
|
||||
pip={true}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative mt-3 flex w-full max-w-[420px] flex-col gap-2">
|
||||
{urls.map((url) => (
|
||||
<ReactPlayer
|
||||
key={url}
|
||||
url={url}
|
||||
width="100%"
|
||||
height="auto"
|
||||
className="!h-auto overflow-hidden rounded-lg object-fill"
|
||||
controls={true}
|
||||
pip={true}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,77 +1,82 @@
|
||||
import { usePublish } from "@libs/ndk";
|
||||
import { Button } from "@shared/button";
|
||||
import { Image } from "@shared/image";
|
||||
import { DEFAULT_AVATAR, FULL_RELAYS } from "@stores/constants";
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { shortenKey } from "@utils/shortenKey";
|
||||
import { useState } from "react";
|
||||
import { useState } from 'react';
|
||||
|
||||
import { usePublish } from '@libs/ndk';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { DEFAULT_AVATAR, FULL_RELAYS } from '@stores/constants';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { shortenKey } from '@utils/shortenKey';
|
||||
|
||||
export function NoteReplyForm({
|
||||
rootID,
|
||||
userPubkey,
|
||||
}: { rootID: string; userPubkey: string }) {
|
||||
const publish = usePublish();
|
||||
const { status, user } = useProfile(userPubkey);
|
||||
const [value, setValue] = useState("");
|
||||
rootID,
|
||||
userPubkey,
|
||||
}: {
|
||||
rootID: string;
|
||||
userPubkey: string;
|
||||
}) {
|
||||
const publish = usePublish();
|
||||
const { status, user } = useProfile(userPubkey);
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
const submit = () => {
|
||||
const tags = [["e", rootID, FULL_RELAYS[0], "root"]];
|
||||
const submit = () => {
|
||||
const tags = [['e', rootID, FULL_RELAYS[0], 'root']];
|
||||
|
||||
// publish event
|
||||
publish({ content: value, kind: 1, tags });
|
||||
// publish event
|
||||
publish({ content: value, kind: 1, tags });
|
||||
|
||||
// reset form
|
||||
setValue("");
|
||||
};
|
||||
// reset form
|
||||
setValue('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="relative w-full flex-1 overflow-hidden">
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Reply to this thread..."
|
||||
className="relative h-20 w-full resize-none rounded-md px-5 py-3 text-base bg-transparent !outline-none placeholder:text-zinc-400 dark:text-zinc-100 dark:placeholder:text-zinc-500"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-t border-zinc-800 w-full py-3 px-5">
|
||||
{status === "loading" ? (
|
||||
<div>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<div className="relative h-9 w-9 shrink-0 rounded">
|
||||
<Image
|
||||
src={user.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={userPubkey}
|
||||
className="h-9 w-9 rounded-md bg-white object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-px leading-none text-sm text-zinc-400">
|
||||
Reply as
|
||||
</p>
|
||||
<p className="leading-none text-sm font-medium text-zinc-100">
|
||||
{user.nip05 || user.name || shortenKey(userPubkey)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => submit()}
|
||||
disabled={value.length === 0 ? true : false}
|
||||
preset="publish"
|
||||
>
|
||||
Reply
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="relative w-full flex-1 overflow-hidden">
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Reply to this thread..."
|
||||
className="relative h-20 w-full resize-none rounded-md bg-transparent px-5 py-3 text-base !outline-none placeholder:text-zinc-400 dark:text-zinc-100 dark:placeholder:text-zinc-500"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full border-t border-zinc-800 px-5 py-3">
|
||||
{status === 'loading' ? (
|
||||
<div>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<div className="relative h-9 w-9 shrink-0 rounded">
|
||||
<Image
|
||||
src={user.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={userPubkey}
|
||||
className="h-9 w-9 rounded-md bg-white object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-px text-sm leading-none text-zinc-400">Reply as</p>
|
||||
<p className="text-sm font-medium leading-none text-zinc-100">
|
||||
{user.nip05 || user.name || shortenKey(userPubkey)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => submit()}
|
||||
disabled={value.length === 0 ? true : false}
|
||||
preset="publish"
|
||||
>
|
||||
Reply
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { Kind1 } from "@shared/notes/contents/kind1";
|
||||
import { NoteMetadata } from "@shared/notes/metadata";
|
||||
import { User } from "@shared/user";
|
||||
import { parser } from "@utils/parser";
|
||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||
import { NoteMetadata } from '@shared/notes/metadata';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { parser } from '@utils/parser';
|
||||
|
||||
export function Reply({ data }: { data: any }) {
|
||||
const content = parser(data);
|
||||
const content = parser(data);
|
||||
|
||||
return (
|
||||
<div className="flex h-min min-h-min w-full select-text flex-col px-3 pt-5 mb-3 rounded-md bg-zinc-900">
|
||||
<div className="flex flex-col">
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-[20px] pl-[50px]">
|
||||
<Kind1 content={content} />
|
||||
<NoteMetadata id={data.event_id} eventPubkey={data.pubkey} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mb-3 flex h-min min-h-min w-full select-text flex-col rounded-md bg-zinc-900 px-3 pt-5">
|
||||
<div className="flex flex-col">
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-[20px] pl-[50px]">
|
||||
<Kind1 content={content} />
|
||||
<NoteMetadata id={data.event_id} eventPubkey={data.pubkey} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
import { getReplies } from "@libs/storage";
|
||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
import { Reply } from "@shared/notes/replies/item";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { getReplies } from '@libs/storage';
|
||||
|
||||
import { Reply } from '@shared/notes/replies/item';
|
||||
|
||||
export function RepliesList({ parent_id }: { parent_id: string }) {
|
||||
const { status, data } = useQuery(["replies", parent_id], async () => {
|
||||
return await getReplies(parent_id);
|
||||
});
|
||||
const { status, data } = useQuery(['replies', parent_id], async () => {
|
||||
return await getReplies(parent_id);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="mb-2">
|
||||
<h5 className="text-lg font-semibold text-zinc-300">Replies</h5>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{status === "loading" ? (
|
||||
<div className="flex gap-2 px-3 py-4">
|
||||
<div className="relative h-9 w-9 shrink animate-pulse rounded-md bg-zinc-800" />
|
||||
<div className="flex w-full flex-1 flex-col justify-center gap-1">
|
||||
<div className="flex items-baseline gap-2 text-base">
|
||||
<div className="h-2.5 w-20 animate-pulse rounded-sm bg-zinc-800" />
|
||||
</div>
|
||||
<div className="h-4 w-44 animate-pulse rounded-sm bg-zinc-800" />
|
||||
</div>
|
||||
</div>
|
||||
) : data.length === 0 ? (
|
||||
<div className="px=3">
|
||||
<div className="w-full flex items-center justify-center rounded-md bg-zinc-900">
|
||||
<div className="py-6 flex flex-col items-center justify-center gap-2">
|
||||
<h3 className="text-3xl">👋</h3>
|
||||
<p className="leading-none text-zinc-400">
|
||||
Share your thought on it...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
data.map((event: NDKEvent) => <Reply key={event.id} data={event} />)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="mb-2">
|
||||
<h5 className="text-lg font-semibold text-zinc-300">Replies</h5>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{status === 'loading' ? (
|
||||
<div className="flex gap-2 px-3 py-4">
|
||||
<div className="relative h-9 w-9 shrink animate-pulse rounded-md bg-zinc-800" />
|
||||
<div className="flex w-full flex-1 flex-col justify-center gap-1">
|
||||
<div className="flex items-baseline gap-2 text-base">
|
||||
<div className="h-2.5 w-20 animate-pulse rounded-sm bg-zinc-800" />
|
||||
</div>
|
||||
<div className="h-4 w-44 animate-pulse rounded-sm bg-zinc-800" />
|
||||
</div>
|
||||
</div>
|
||||
) : data.length === 0 ? (
|
||||
<div className="px=3">
|
||||
<div className="flex w-full items-center justify-center rounded-md bg-zinc-900">
|
||||
<div className="flex flex-col items-center justify-center gap-2 py-6">
|
||||
<h3 className="text-3xl">👋</h3>
|
||||
<p className="leading-none text-zinc-400">Share your thought on it...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
data.map((event: NDKEvent) => <Reply key={event.id} data={event} />)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,51 +1,49 @@
|
||||
import { Kind1 } from "@shared/notes/contents/kind1";
|
||||
import { Kind1063 } from "@shared/notes/contents/kind1063";
|
||||
import { NoteMetadata } from "@shared/notes/metadata";
|
||||
import { NoteSkeleton } from "@shared/notes/skeleton";
|
||||
import { User } from "@shared/user";
|
||||
import { useEvent } from "@utils/hooks/useEvent";
|
||||
import { getRepostID } from "@utils/transform";
|
||||
import { LumeEvent } from "@utils/types";
|
||||
import { Kind1 } from '@shared/notes/contents/kind1';
|
||||
import { Kind1063 } from '@shared/notes/contents/kind1063';
|
||||
import { NoteMetadata } from '@shared/notes/metadata';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
import { getRepostID } from '@utils/transform';
|
||||
import { LumeEvent } from '@utils/types';
|
||||
|
||||
export function Repost({ event }: { event: LumeEvent }) {
|
||||
const repostID = getRepostID(event.tags);
|
||||
const { status, data } = useEvent(repostID);
|
||||
const repostID = getRepostID(event.tags);
|
||||
const { status, data } = useEvent(repostID);
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col mt-12">
|
||||
<div className="absolute left-[18px] -top-10 h-[50px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||
{status === "loading" ? (
|
||||
<NoteSkeleton />
|
||||
) : status === "success" ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
|
||||
<span className="text-zinc-500 text-sm font-medium leading-none">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-fuchsia-500 text-sm leading-none">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content || data.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<NoteMetadata
|
||||
id={data.event_id || data.id}
|
||||
eventPubkey={data.pubkey}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative mt-12 flex flex-col">
|
||||
<div className="absolute -top-10 left-[18px] h-[50px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||
{status === 'loading' ? (
|
||||
<NoteSkeleton />
|
||||
) : status === 'success' ? (
|
||||
<>
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<div className="-mt-6 pl-[49px]">
|
||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||
{data.kind !== 1 && data.kind !== 1063 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
|
||||
<span className="text-sm font-medium leading-none text-zinc-500">
|
||||
Kind: {data.kind}
|
||||
</span>
|
||||
<p className="text-sm leading-none text-fuchsia-500">
|
||||
Lume isn't fully support this kind in newsfeed
|
||||
</p>
|
||||
</div>
|
||||
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
<p>{data.content || data.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<NoteMetadata id={data.event_id || data.id} eventPubkey={data.pubkey} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to fetch event</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
export function NoteSkeleton() {
|
||||
return (
|
||||
<div className="flex h-min flex-col pb-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="h-4 w-20 rounded bg-zinc-700" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mt-5 animate-pulse pl-[49px]">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="h-4 w-full rounded-sm bg-zinc-700" />
|
||||
<div className="h-3 w-2/3 rounded-sm bg-zinc-700" />
|
||||
<div className="h-3 w-1/2 rounded-sm bg-zinc-700" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex h-min flex-col pb-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="h-4 w-20 rounded bg-zinc-700" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mt-5 animate-pulse pl-[49px]">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="h-4 w-full rounded-sm bg-zinc-700" />
|
||||
<div className="h-3 w-2/3 rounded-sm bg-zinc-700" />
|
||||
<div className="h-3 w-1/2 rounded-sm bg-zinc-700" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,165 +1,167 @@
|
||||
import { NotificationUser } from "./user";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
|
||||
import { BellIcon, CancelIcon, LoaderIcon } from "@shared/icons";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { User } from "@shared/user";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { dateToUnix, getHourAgo } from "@utils/date";
|
||||
import { Fragment, useContext, useRef, useState } from "react";
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Fragment, useContext, useRef, useState } from 'react';
|
||||
|
||||
import { BellIcon, CancelIcon, LoaderIcon } from '@shared/icons';
|
||||
import { RelayContext } from '@shared/relayProvider';
|
||||
import { User } from '@shared/user';
|
||||
|
||||
import { dateToUnix, getHourAgo } from '@utils/date';
|
||||
|
||||
import { NotificationUser } from './user';
|
||||
|
||||
export function NotificationModal({ pubkey }: { pubkey: string }) {
|
||||
const ndk = useContext(RelayContext);
|
||||
const now = useRef(new Date());
|
||||
const ndk = useContext(RelayContext);
|
||||
const now = useRef(new Date());
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { status, data } = useQuery(
|
||||
["user-notification", pubkey],
|
||||
async () => {
|
||||
const filter: NDKFilter = {
|
||||
"#p": [pubkey],
|
||||
kinds: [1, 6, 7, 9735],
|
||||
since: dateToUnix(getHourAgo(48, now.current)),
|
||||
};
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
return [...events];
|
||||
},
|
||||
{
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: Infinity,
|
||||
},
|
||||
);
|
||||
const { status, data } = useQuery(
|
||||
['user-notification', pubkey],
|
||||
async () => {
|
||||
const filter: NDKFilter = {
|
||||
'#p': [pubkey],
|
||||
kinds: [1, 6, 7, 9735],
|
||||
since: dateToUnix(getHourAgo(48, now.current)),
|
||||
};
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
return [...events];
|
||||
},
|
||||
{
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: Infinity,
|
||||
}
|
||||
);
|
||||
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
const openModal = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const renderItem = (event: NDKEvent) => {
|
||||
if (event.kind === 1) {
|
||||
return (
|
||||
<div key={event.id} className="flex flex-col px-5 py-2">
|
||||
<User pubkey={event.pubkey} time={event.created_at} isChat={true} />
|
||||
<div className="-mt-[20px] pl-[49px]">
|
||||
<p className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
{event.content}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const renderItem = (event: NDKEvent) => {
|
||||
if (event.kind === 1) {
|
||||
return (
|
||||
<div key={event.id} className="flex flex-col px-5 py-2">
|
||||
<User pubkey={event.pubkey} time={event.created_at} isChat={true} />
|
||||
<div className="-mt-[20px] pl-[49px]">
|
||||
<p className="select-text whitespace-pre-line break-words text-base text-zinc-100">
|
||||
{event.content}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (event.kind === 6) {
|
||||
return (
|
||||
<div key={event.id} className="flex flex-col px-5 py-2">
|
||||
<NotificationUser pubkey={event.pubkey} desc="repost your post" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (event.kind === 6) {
|
||||
return (
|
||||
<div key={event.id} className="flex flex-col px-5 py-2">
|
||||
<NotificationUser pubkey={event.pubkey} desc="repost your post" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (event.kind === 7) {
|
||||
return (
|
||||
<div key={event.id} className="flex flex-col px-5 py-2">
|
||||
<NotificationUser pubkey={event.pubkey} desc="liked your post" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (event.kind === 7) {
|
||||
return (
|
||||
<div key={event.id} className="flex flex-col px-5 py-2">
|
||||
<NotificationUser pubkey={event.pubkey} desc="liked your post" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (event.kind === 9735) {
|
||||
return (
|
||||
<div key={event.id} className="flex flex-col px-5 py-2">
|
||||
<NotificationUser pubkey={event.pubkey} desc="zapped your post" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (event.kind === 9735) {
|
||||
return (
|
||||
<div key={event.id} className="flex flex-col px-5 py-2">
|
||||
<NotificationUser pubkey={event.pubkey} desc="zapped your post" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="flex flex-col px-5 py-2">{event.content}</div>;
|
||||
};
|
||||
return <div className="flex flex-col px-5 py-2">{event.content}</div>;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openModal()}
|
||||
aria-label="Notification"
|
||||
className="inline-flex items-center justify-center w-9 h-9 rounded-md border-t bg-zinc-800 border-zinc-700/50 transform active:translate-y-1"
|
||||
>
|
||||
<BellIcon className="w-4 h-4 text-zinc-400" />
|
||||
</button>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-lg border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-semibold leading-none text-zinc-100"
|
||||
>
|
||||
Notification
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<CancelIcon className="w-5 h-5 text-zinc-300" />
|
||||
</button>
|
||||
</div>
|
||||
<Dialog.Description className="text-sm leading-tight text-zinc-400">
|
||||
All things happen when you rest in 48 hours ago
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[500px] flex flex-col pb-5 overflow-x-hidden overflow-y-auto">
|
||||
{status === "loading" ? (
|
||||
<div className="px-4 py-3 inline-flex items-center justify-center">
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-black dark:text-zinc-100" />
|
||||
</div>
|
||||
) : data.length < 1 ? (
|
||||
<div className="flex flex-col items-center justify-center w-full h-full">
|
||||
<p className="text-4xl mb-1">🎉</p>
|
||||
<p className="text-zinc-500 font-medium">
|
||||
Yo!, you've no new notifications
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
data.map((event) => renderItem(event))
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openModal()}
|
||||
aria-label="Notification"
|
||||
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md border-t border-zinc-700/50 bg-zinc-800 active:translate-y-1"
|
||||
>
|
||||
<BellIcon className="h-4 w-4 text-zinc-400" />
|
||||
</button>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-lg border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-semibold leading-none text-zinc-100"
|
||||
>
|
||||
Notification
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
|
||||
>
|
||||
<CancelIcon className="h-5 w-5 text-zinc-300" />
|
||||
</button>
|
||||
</div>
|
||||
<Dialog.Description className="text-sm leading-tight text-zinc-400">
|
||||
All things happen when you rest in 48 hours ago
|
||||
</Dialog.Description>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-[500px] flex-col overflow-y-auto overflow-x-hidden pb-5">
|
||||
{status === 'loading' ? (
|
||||
<div className="inline-flex items-center justify-center px-4 py-3">
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-black dark:text-zinc-100" />
|
||||
</div>
|
||||
) : data.length < 1 ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||
<p className="mb-1 text-4xl">🎉</p>
|
||||
<p className="font-medium text-zinc-500">
|
||||
Yo!, you've no new notifications
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
data.map((event) => renderItem(event))
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,42 +1,41 @@
|
||||
import { Image } from "@shared/image";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { shortenKey } from "@utils/shortenKey";
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
export function NotificationUser({
|
||||
pubkey,
|
||||
desc,
|
||||
}: { pubkey: string; desc: string }) {
|
||||
const { status, user } = useProfile(pubkey);
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
if (status === "loading") {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-11 w-11 shrink-0 rounded-md bg-zinc-800 animate-pulse" />
|
||||
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start">
|
||||
<span className="w-1/2 h-4 rounded bg-zinc-800 animate-pulse" />
|
||||
<span className="w-1/3 h-3 rounded bg-zinc-800 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { shortenKey } from '@utils/shortenKey';
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<div className="relative h-11 w-11 shrink rounded-md">
|
||||
<Image
|
||||
src={user.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-11 w-11 rounded-md object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 inline-flex items-center justify-start text-start gap-1">
|
||||
<span className="leading-none text-zinc-400">
|
||||
{user.nip05 || user.name || user.displayName || shortenKey(pubkey)}
|
||||
</span>
|
||||
<span className="leading-none text-zinc-400">{desc}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export function NotificationUser({ pubkey, desc }: { pubkey: string; desc: string }) {
|
||||
const { status, user } = useProfile(pubkey);
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-11 w-11 shrink-0 animate-pulse rounded-md bg-zinc-800" />
|
||||
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start">
|
||||
<span className="h-4 w-1/2 animate-pulse rounded bg-zinc-800" />
|
||||
<span className="h-3 w-1/3 animate-pulse rounded bg-zinc-800" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<div className="relative h-11 w-11 shrink rounded-md">
|
||||
<Image
|
||||
src={user.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-11 w-11 rounded-md object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="inline-flex flex-1 items-center justify-start gap-1 text-start">
|
||||
<span className="leading-none text-zinc-400">
|
||||
{user.nip05 || user.name || user.displayName || shortenKey(pubkey)}
|
||||
</span>
|
||||
<span className="leading-none text-zinc-400">{desc}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useAccount } from "@utils/hooks/useAccount";
|
||||
import { ReactNode } from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { ReactNode } from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
|
||||
export function Protected({ children }: { children: ReactNode }) {
|
||||
const { status, account } = useAccount();
|
||||
const { status, account } = useAccount();
|
||||
|
||||
if (status === "success" && !account) {
|
||||
return <Navigate to="/auth/welcome" replace />;
|
||||
}
|
||||
if (status === 'success' && !account) {
|
||||
return <Navigate to="/auth/welcome" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
return children;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { initNDK } from "@libs/ndk";
|
||||
import { getSetting } from "@libs/storage";
|
||||
import NDK from "@nostr-dev-kit/ndk";
|
||||
import { createContext } from "react";
|
||||
import NDK from '@nostr-dev-kit/ndk';
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { initNDK } from '@libs/ndk';
|
||||
import { getSetting } from '@libs/storage';
|
||||
|
||||
export const RelayContext = createContext<NDK>(null);
|
||||
|
||||
const relays = await getSetting("relays");
|
||||
const relays = await getSetting('relays');
|
||||
const relaysArray = JSON.parse(relays);
|
||||
const ndk = await initNDK(relaysArray);
|
||||
|
||||
export function RelayProvider({ children }: { children: React.ReactNode }) {
|
||||
return <RelayContext.Provider value={ndk}>{children}</RelayContext.Provider>;
|
||||
return <RelayContext.Provider value={ndk}>{children}</RelayContext.Provider>;
|
||||
}
|
||||
|
||||
@@ -1,63 +1,64 @@
|
||||
import { AppHeader } from "@shared/appHeader";
|
||||
import { NavLink, Outlet, ScrollRestoration } from "react-router-dom";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { NavLink, Outlet, ScrollRestoration } from 'react-router-dom';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { AppHeader } from '@shared/appHeader';
|
||||
|
||||
export function SettingsLayout() {
|
||||
return (
|
||||
<div className="flex w-screen h-screen">
|
||||
<div className="relative flex flex-row shrink-0">
|
||||
<div className="relative flex w-[232px] flex-col gap-3 border-r border-zinc-900">
|
||||
<AppHeader />
|
||||
<div className="pb-20 flex flex-col gap-5 overflow-y-auto scrollbar-hide">
|
||||
<div className="flex flex-col gap-0.5 px-1.5">
|
||||
<div className="px-2.5">
|
||||
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
|
||||
Settings
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<NavLink
|
||||
to="/settings/general"
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
|
||||
isActive ? "bg-zinc-900/50" : "",
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="font-medium">General</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/settings/shortcuts"
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
|
||||
isActive ? "bg-zinc-900/50" : "",
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="font-medium">Shortcuts</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/settings/account"
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
|
||||
isActive ? "bg-zinc-900/50" : "",
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="font-medium">Account</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-full">
|
||||
<Outlet />
|
||||
<ScrollRestoration />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex h-screen w-screen">
|
||||
<div className="relative flex shrink-0 flex-row">
|
||||
<div className="relative flex w-[232px] flex-col gap-3 border-r border-zinc-900">
|
||||
<AppHeader />
|
||||
<div className="scrollbar-hide flex flex-col gap-5 overflow-y-auto pb-20">
|
||||
<div className="flex flex-col gap-0.5 px-1.5">
|
||||
<div className="px-2.5">
|
||||
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
|
||||
Settings
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<NavLink
|
||||
to="/settings/general"
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
'flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200',
|
||||
isActive ? 'bg-zinc-900/50' : ''
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="font-medium">General</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/settings/shortcuts"
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
'flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200',
|
||||
isActive ? 'bg-zinc-900/50' : ''
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="font-medium">Shortcuts</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/settings/account"
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
'flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200',
|
||||
isActive ? 'bg-zinc-900/50' : ''
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="font-medium">Account</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full w-full">
|
||||
<Outlet />
|
||||
<ScrollRestoration />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
import { CancelIcon } from "@shared/icons";
|
||||
import { CancelIcon } from '@shared/icons';
|
||||
|
||||
export function TitleBar({
|
||||
title,
|
||||
onClick = undefined,
|
||||
}: { title: string; onClick?: () => void }) {
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="group overflow-hidden shrink-0 h-11 w-full flex items-center justify-between px-3 border-b border-zinc-900"
|
||||
>
|
||||
<div className="w-6" />
|
||||
<h3 className="text-sm font-medium text-zinc-200">{title}</h3>
|
||||
{onClick ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="inline-flex h-6 w-6 shrink items-center justify-center rounded hover:bg-zinc-900 transform translate-y-8 group-hover:translate-y-0 transition-transform ease-in-out duration-150"
|
||||
>
|
||||
<CancelIcon width={12} height={12} className="text-zinc-300" />
|
||||
</button>
|
||||
) : (
|
||||
<div className="w-6" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
title,
|
||||
onClick = undefined,
|
||||
}: {
|
||||
title: string;
|
||||
onClick?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="group flex h-11 w-full shrink-0 items-center justify-between overflow-hidden border-b border-zinc-900 px-3"
|
||||
>
|
||||
<div className="w-6" />
|
||||
<h3 className="text-sm font-medium text-zinc-200">{title}</h3>
|
||||
{onClick ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="inline-flex h-6 w-6 shrink translate-y-8 transform items-center justify-center rounded transition-transform duration-150 ease-in-out hover:bg-zinc-900 group-hover:translate-y-0"
|
||||
>
|
||||
<CancelIcon width={12} height={12} className="text-zinc-300" />
|
||||
</button>
|
||||
) : (
|
||||
<div className="w-6" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
import {
|
||||
autoUpdate,
|
||||
offset,
|
||||
shift,
|
||||
useFloating,
|
||||
useFocus,
|
||||
useHover,
|
||||
useInteractions,
|
||||
} from "@floating-ui/react";
|
||||
import { useState } from "react";
|
||||
autoUpdate,
|
||||
offset,
|
||||
shift,
|
||||
useFloating,
|
||||
useFocus,
|
||||
useHover,
|
||||
useInteractions,
|
||||
} from '@floating-ui/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
export function Tooltip({
|
||||
children,
|
||||
message,
|
||||
}: { children: React.ReactNode; message: string }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
children,
|
||||
message,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
message: string;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { x, y, strategy, refs, context } = useFloating({
|
||||
open: isOpen,
|
||||
onOpenChange: setIsOpen,
|
||||
placement: "top",
|
||||
middleware: [offset(8), shift()],
|
||||
whileElementsMounted(...args) {
|
||||
const cleanup = autoUpdate(...args, { animationFrame: true });
|
||||
return cleanup;
|
||||
},
|
||||
});
|
||||
const { x, y, strategy, refs, context } = useFloating({
|
||||
open: isOpen,
|
||||
onOpenChange: setIsOpen,
|
||||
placement: 'top',
|
||||
middleware: [offset(8), shift()],
|
||||
whileElementsMounted(...args) {
|
||||
const cleanup = autoUpdate(...args, { animationFrame: true });
|
||||
return cleanup;
|
||||
},
|
||||
});
|
||||
|
||||
const hover = useHover(context);
|
||||
const focus = useFocus(context);
|
||||
const hover = useHover(context);
|
||||
const focus = useFocus(context);
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([
|
||||
hover,
|
||||
focus,
|
||||
]);
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={refs.setReference} {...getReferenceProps()}>
|
||||
{children}
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
className="w-max select-none rounded-md bg-zinc-800 border border-zinc-700 px-4 py-2 text-sm font-medium leading-none text-zinc-100"
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? 0,
|
||||
left: x ?? 0,
|
||||
}}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div ref={refs.setReference} {...getReferenceProps()}>
|
||||
{children}
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
className="w-max select-none rounded-md border border-zinc-700 bg-zinc-800 px-4 py-2 text-sm font-medium leading-none text-zinc-100"
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? 0,
|
||||
left: x ?? 0,
|
||||
}}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,138 +1,138 @@
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { Image } from "@shared/image";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { formatCreatedAt } from "@utils/createdAt";
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { shortenKey } from "@utils/shortenKey";
|
||||
import { Fragment } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Popover, Transition } from '@headlessui/react';
|
||||
import { Fragment } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { formatCreatedAt } from '@utils/createdAt';
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { shortenKey } from '@utils/shortenKey';
|
||||
|
||||
export function User({
|
||||
pubkey,
|
||||
time,
|
||||
size,
|
||||
repost,
|
||||
isChat = false,
|
||||
pubkey,
|
||||
time,
|
||||
size,
|
||||
repost,
|
||||
isChat = false,
|
||||
}: {
|
||||
pubkey: string;
|
||||
time: number;
|
||||
size?: string;
|
||||
repost?: boolean;
|
||||
isChat?: boolean;
|
||||
pubkey: string;
|
||||
time: number;
|
||||
size?: string;
|
||||
repost?: boolean;
|
||||
isChat?: boolean;
|
||||
}) {
|
||||
const { status, user } = useProfile(pubkey);
|
||||
const createdAt = formatCreatedAt(time, isChat);
|
||||
const { status, user } = useProfile(pubkey);
|
||||
const createdAt = formatCreatedAt(time, isChat);
|
||||
|
||||
const avatarWidth = size === "small" ? "w-6" : "w-11";
|
||||
const avatarHeight = size === "small" ? "h-6" : "h-11";
|
||||
const avatarWidth = size === 'small' ? 'w-6' : 'w-11';
|
||||
const avatarHeight = size === 'small' ? 'h-6' : 'h-11';
|
||||
|
||||
if (status === "loading") {
|
||||
return (
|
||||
<div
|
||||
className={`relative flex gap-3 ${
|
||||
size === "small" ? "items-center" : "items-start"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`${avatarWidth} ${avatarHeight} ${
|
||||
size === "small" ? "rounded" : "rounded-lg"
|
||||
} relative z-10 bg-zinc-800 animate-pulse shrink-0 overflow-hidden`}
|
||||
/>
|
||||
<div className="flex flex-wrap items-baseline gap-1">
|
||||
<div className="w-36 h-3.5 rounded bg-zinc-800 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div
|
||||
className={`relative flex gap-3 ${
|
||||
size === 'small' ? 'items-center' : 'items-start'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`${avatarWidth} ${avatarHeight} ${
|
||||
size === 'small' ? 'rounded' : 'rounded-lg'
|
||||
} relative z-10 shrink-0 animate-pulse overflow-hidden bg-zinc-800`}
|
||||
/>
|
||||
<div className="flex flex-wrap items-baseline gap-1">
|
||||
<div className="h-3.5 w-36 animate-pulse rounded bg-zinc-800" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
className={`relative flex gap-3 ${
|
||||
size === "small" ? "items-center" : "items-start"
|
||||
}`}
|
||||
>
|
||||
<Popover.Button
|
||||
className={`${avatarWidth} ${avatarHeight} relative z-10 bg-zinc-900 shrink-0 overflow-hidden`}
|
||||
>
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className={`${avatarWidth} ${avatarHeight} ${
|
||||
size === "small" ? "rounded" : "rounded-lg"
|
||||
} object-cover`}
|
||||
/>
|
||||
</Popover.Button>
|
||||
<div className="flex flex-wrap items-baseline gap-1">
|
||||
<h5
|
||||
className={`text-zinc-100 font-semibold leading-none truncate ${
|
||||
size === "small" ? "max-w-[8rem]" : "max-w-[12rem]"
|
||||
}`}
|
||||
>
|
||||
{user?.nip05 || user?.name || user?.displayName || shortenKey(pubkey)}
|
||||
</h5>
|
||||
{repost && (
|
||||
<span className="font-semibold leading-none text-fuchsia-500">
|
||||
{" "}
|
||||
reposted
|
||||
</span>
|
||||
)}
|
||||
<span className="leading-none text-zinc-500">·</span>
|
||||
<span className="leading-none text-zinc-500">{createdAt}</span>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<Popover.Panel className="absolute z-50 top-10 mt-3">
|
||||
<div className="w-[250px] overflow-hidden rounded-md border border-zinc-800/50 bg-zinc-900/90 backdrop-blur-lg">
|
||||
<div className="flex gap-2.5 border-b border-zinc-800 px-3 py-3">
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-11 w-11 shrink-0 rounded-lg object-cover"
|
||||
/>
|
||||
<div className="flex-1 flex flex-col gap-2">
|
||||
<div className="inline-flex flex-col gap-1">
|
||||
<h5 className="font-semibold text-sm leading-none">
|
||||
{user?.displayName || user?.name || (
|
||||
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
||||
)}
|
||||
</h5>
|
||||
<span className="max-w-[15rem] text-sm truncate leading-none text-zinc-500">
|
||||
{user?.nip05 || shortenKey(pubkey)}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="line-clamp-3 break-words leading-tight text-zinc-100">
|
||||
{user?.about}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 px-3 py-3">
|
||||
<Link
|
||||
to={`/app/user/${pubkey}`}
|
||||
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-700 hover:bg-fuchsia-500 text-sm font-medium"
|
||||
>
|
||||
View profile
|
||||
</Link>
|
||||
<Link
|
||||
to={`/app/chat/${pubkey}`}
|
||||
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-700 hover:bg-fuchsia-500 text-sm font-medium"
|
||||
>
|
||||
Message
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</Popover>
|
||||
);
|
||||
return (
|
||||
<Popover
|
||||
className={`relative flex gap-3 ${
|
||||
size === 'small' ? 'items-center' : 'items-start'
|
||||
}`}
|
||||
>
|
||||
<Popover.Button
|
||||
className={`${avatarWidth} ${avatarHeight} relative z-10 shrink-0 overflow-hidden bg-zinc-900`}
|
||||
>
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className={`${avatarWidth} ${avatarHeight} ${
|
||||
size === 'small' ? 'rounded' : 'rounded-lg'
|
||||
} object-cover`}
|
||||
/>
|
||||
</Popover.Button>
|
||||
<div className="flex flex-wrap items-baseline gap-1">
|
||||
<h5
|
||||
className={`truncate font-semibold leading-none text-zinc-100 ${
|
||||
size === 'small' ? 'max-w-[8rem]' : 'max-w-[12rem]'
|
||||
}`}
|
||||
>
|
||||
{user?.nip05 || user?.name || user?.displayName || shortenKey(pubkey)}
|
||||
</h5>
|
||||
{repost && (
|
||||
<span className="font-semibold leading-none text-fuchsia-500"> reposted</span>
|
||||
)}
|
||||
<span className="leading-none text-zinc-500">·</span>
|
||||
<span className="leading-none text-zinc-500">{createdAt}</span>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<Popover.Panel className="absolute top-10 z-50 mt-3">
|
||||
<div className="w-[250px] overflow-hidden rounded-md border border-zinc-800/50 bg-zinc-900/90 backdrop-blur-lg">
|
||||
<div className="flex gap-2.5 border-b border-zinc-800 px-3 py-3">
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-11 w-11 shrink-0 rounded-lg object-cover"
|
||||
/>
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<div className="inline-flex flex-col gap-1">
|
||||
<h5 className="text-sm font-semibold leading-none">
|
||||
{user?.displayName || user?.name || (
|
||||
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
||||
)}
|
||||
</h5>
|
||||
<span className="max-w-[15rem] truncate text-sm leading-none text-zinc-500">
|
||||
{user?.nip05 || shortenKey(pubkey)}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="line-clamp-3 break-words leading-tight text-zinc-100">
|
||||
{user?.about}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 px-3 py-3">
|
||||
<Link
|
||||
to={`/app/user/${pubkey}`}
|
||||
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-700 text-sm font-medium hover:bg-fuchsia-500"
|
||||
>
|
||||
View profile
|
||||
</Link>
|
||||
<Link
|
||||
to={`/app/chat/${pubkey}`}
|
||||
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-700 text-sm font-medium hover:bg-fuchsia-500"
|
||||
>
|
||||
Message
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user