replace eslint/prettier with rome

This commit is contained in:
Ren Amamiya
2023-05-14 17:05:53 +07:00
parent 48d690d33a
commit 409a625dcc
154 changed files with 7639 additions and 8525 deletions

View File

@@ -1,18 +1,21 @@
import { Image } from '@shared/image';
import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from '@stores/constants';
import { DEFAULT_AVATAR } from "@stores/constants";
export default function ActiveAccount({ user }: { user: any }) {
const userData = JSON.parse(user.metadata);
const userData = JSON.parse(user.metadata);
return (
<button className="relative h-10 w-10 overflow-hidden rounded-lg">
<Image
src={userData.picture || DEFAULT_AVATAR}
alt="user's avatar"
loading="auto"
className="h-10 w-10 object-cover"
/>
</button>
);
return (
<button
type="button"
className="relative h-10 w-10 overflow-hidden rounded-lg"
>
<Image
src={userData.picture || DEFAULT_AVATAR}
alt="user's avatar"
loading="auto"
className="h-10 w-10 object-cover"
/>
</button>
);
}

View File

@@ -1,17 +1,17 @@
import { Image } from '@shared/image';
import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from '@stores/constants';
import { DEFAULT_AVATAR } from "@stores/constants";
export default function InactiveAccount({ user }: { user: any }) {
const userData = JSON.parse(user.metadata);
const userData = JSON.parse(user.metadata);
return (
<div className="relative h-10 w-10 shrink rounded-lg">
<Image
src={userData.picture || DEFAULT_AVATAR}
alt="user's avatar"
className="h-10 w-10 rounded-lg object-cover"
/>
</div>
);
return (
<div className="relative h-10 w-10 shrink rounded-lg">
<Image
src={userData.picture || DEFAULT_AVATAR}
alt="user's avatar"
className="h-10 w-10 rounded-lg object-cover"
/>
</div>
);
}

View File

@@ -1,24 +1,27 @@
import { usePageContext } from '@utils/hooks/usePageContext';
import { usePageContext } from "@utils/hooks/usePageContext";
import { twMerge } from 'tailwind-merge';
import { twMerge } from "tailwind-merge";
export default function ActiveLink({
href,
className,
activeClassName,
children,
href,
className,
activeClassName,
children,
}: {
href: string;
className: string;
activeClassName: string;
children: React.ReactNode;
href: string;
className: string;
activeClassName: string;
children: React.ReactNode;
}) {
const pageContext = usePageContext();
const pathName = pageContext.urlPathname;
const pageContext = usePageContext();
const pathName = pageContext.urlPathname;
return (
<a href={href} className={twMerge(className, href === pathName ? activeClassName : '')}>
{children}
</a>
);
return (
<a
href={href}
className={twMerge(className, href === pathName ? activeClassName : "")}
>
{children}
</a>
);
}

View File

@@ -1,55 +1,75 @@
import ArrowLeftIcon from '@icons/arrowLeft';
import ArrowRightIcon from '@icons/arrowRight';
import RefreshIcon from '@icons/refresh';
import ArrowLeftIcon from "@icons/arrowLeft";
import ArrowRightIcon from "@icons/arrowRight";
import RefreshIcon from "@icons/refresh";
export default function AppHeader() {
const goBack = () => {
window.history.back();
};
const goBack = () => {
window.history.back();
};
const goForward = () => {
window.history.forward();
};
const goForward = () => {
window.history.forward();
};
const reload = () => {
window.location.reload();
};
const reload = () => {
window.location.reload();
};
return (
<div data-tauri-drag-region className="flex h-full w-full flex-1 items-center px-2">
<div data-tauri-drag-region className="flex w-full items-center justify-center gap-2">
<div className="flex h-full items-center gap-2">
<button
onClick={() => goBack()}
className="group inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<ArrowLeftIcon width={14} height={14} className="text-zinc-500 group-hover:text-zinc-300" />
</button>
<button
onClick={() => goForward()}
className="group inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<ArrowRightIcon width={14} height={14} className="text-zinc-500 group-hover:text-zinc-300" />
</button>
</div>
<div>
<input
autoCapitalize="none"
autoCorrect="off"
autoFocus={false}
placeholder="Search..."
className="h-6 w-[453px] rounded border border-zinc-800 bg-zinc-900 px-2.5 text-center text-[11px] text-sm leading-5 text-zinc-500 placeholder:leading-5 placeholder:text-zinc-600 focus:outline-none"
/>
</div>
<div className="flex h-full items-center gap-2">
<button
onClick={() => reload()}
className="group inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<RefreshIcon width={14} height={14} className="text-zinc-500 group-hover:text-zinc-300" />
</button>
</div>
</div>
</div>
);
return (
<div
data-tauri-drag-region
className="flex h-full w-full flex-1 items-center px-2"
>
<div
data-tauri-drag-region
className="flex w-full items-center justify-center gap-2"
>
<div className="flex h-full items-center gap-2">
<button
type="button"
onClick={() => goBack()}
className="group inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<ArrowLeftIcon
width={14}
height={14}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
<button
type="button"
onClick={() => goForward()}
className="group inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<ArrowRightIcon
width={14}
height={14}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
</div>
<div>
<input
autoCapitalize="none"
autoCorrect="off"
placeholder="Search..."
className="h-6 w-[453px] rounded border border-zinc-800 bg-zinc-900 px-2.5 text-center text-[11px] text-sm leading-5 text-zinc-500 placeholder:leading-5 placeholder:text-zinc-600 focus:outline-none"
/>
</div>
<div className="flex h-full items-center gap-2">
<button
type="button"
onClick={() => reload()}
className="group inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<RefreshIcon
width={14}
height={14}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
</div>
</div>
</div>
);
}

View File

@@ -1,75 +1,86 @@
import { createBlobFromFile } from '@utils/createBlobFromFile';
import { createBlobFromFile } from "@utils/createBlobFromFile";
import { open } from '@tauri-apps/api/dialog';
import { Body, fetch } from '@tauri-apps/api/http';
import { useState } from 'react';
import { open } from "@tauri-apps/api/dialog";
import { Body, fetch } from "@tauri-apps/api/http";
import { useState } from "react";
export function AvatarUploader({ valueState }: { valueState: 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 webpImage = 'https://void.cat/d/' + res.data.file.id + '.webp';
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 webpImage = `https://void.cat/d/${res.data.file.id}.webp`;
valueState(webpImage);
setLoading(false);
}
};
valueState(webpImage);
setLoading(false);
}
};
return (
<button
onClick={() => openFileDialog()}
type="button"
className="inline-flex h-6 items-center justify-center rounded bg-zinc-900 px-3 text-xs font-medium text-zinc-200 ring-1 ring-zinc-800 hover:bg-zinc-700"
>
{loading ? (
<svg
className="h-4 w-4 animate-spin text-black dark:text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<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"
></path>
</svg>
) : (
<span className="leading-none">Upload</span>
)}
</button>
);
return (
<button
onClick={() => openFileDialog()}
type="button"
className="inline-flex h-6 items-center justify-center rounded bg-zinc-900 px-3 text-xs font-medium text-zinc-200 ring-1 ring-zinc-800 hover:bg-zinc-700"
>
{loading ? (
<svg
className="h-4 w-4 animate-spin text-black dark:text-white"
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>
) : (
<span className="leading-none">Upload</span>
)}
</button>
);
}

View File

@@ -1,124 +1,134 @@
import PlusCircleIcon from '@shared/icons/plusCircle';
import PlusCircleIcon from "@shared/icons/plusCircle";
import { createBlobFromFile } from '@utils/createBlobFromFile';
import { createBlobFromFile } from "@utils/createBlobFromFile";
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 { 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";
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"
autoFocus={false}
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-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<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"
></path>
</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-white"
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>
);
}

View File

@@ -1,92 +1,105 @@
import { Post } from '@shared/composer/types/post';
import { User } from '@shared/composer/user';
import { Post } from "@shared/composer/types/post";
import { User } from "@shared/composer/user";
import CancelIcon from '@icons/cancel';
import ChevronDownIcon from '@icons/chevronDown';
import ChevronRightIcon from '@icons/chevronRight';
import ComposeIcon from '@icons/compose';
import CancelIcon from "@icons/cancel";
import ChevronDownIcon from "@icons/chevronDown";
import ChevronRightIcon from "@icons/chevronRight";
import ComposeIcon from "@icons/compose";
import { composerAtom } from '@stores/composer';
import { composerAtom } from "@stores/composer";
import { useActiveAccount } from '@utils/hooks/useActiveAccount';
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
import { Dialog, Transition } from '@headlessui/react';
import { useAtom } from 'jotai';
import { Fragment, useState } from 'react';
import { Dialog, Transition } from "@headlessui/react";
import { useAtom } from "jotai";
import { Fragment, useState } from "react";
export function ComposerModal() {
const [isOpen, setIsOpen] = useState(false);
const [composer] = useAtom(composerAtom);
const [isOpen, setIsOpen] = useState(false);
const [composer] = useAtom(composerAtom);
const { account, isLoading, isError } = useActiveAccount();
const { account, isLoading, isError } = useActiveAccount();
const closeModal = () => {
setIsOpen(false);
};
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
const openModal = () => {
setIsOpen(true);
};
return (
<>
<button
type="button"
autoFocus={false}
onClick={() => openModal()}
className="inline-flex h-7 w-max items-center justify-center gap-1 rounded-md bg-fuchsia-500 px-2.5 text-xs font-medium text-zinc-200 shadow-button hover:bg-fuchsia-600 focus:outline-none"
>
<ComposeIcon width={14} height={14} />
Compose
</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 h-min w-full max-w-xl rounded-lg border border-zinc-800 bg-zinc-900">
<div className="flex items-center justify-between px-4 py-4">
<div className="flex items-center gap-2">
<div>{!isLoading && !isError && account && <User data={account} />}</div>
<span>
<ChevronRightIcon width={14} height={14} className="text-zinc-500" />
</span>
<div className="inline-flex h-6 w-max items-center justify-center gap-0.5 rounded bg-zinc-800 pl-3 pr-1.5 text-xs font-medium text-zinc-400 shadow-mini-button">
New Post
<ChevronDownIcon width={14} height={14} />
</div>
</div>
<div
onClick={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>
{composer.type === 'post' && account && <Post pubkey={account.pubkey} privkey={account.privkey} />}
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
return (
<>
<button
type="button"
onClick={() => openModal()}
className="inline-flex h-7 w-max items-center justify-center gap-1 rounded-md bg-fuchsia-500 px-2.5 text-xs font-medium text-zinc-200 shadow-button hover:bg-fuchsia-600 focus:outline-none"
>
<ComposeIcon width={14} height={14} />
Compose
</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 h-min w-full max-w-xl rounded-lg border border-zinc-800 bg-zinc-900">
<div className="flex items-center justify-between px-4 py-4">
<div className="flex items-center gap-2">
<div>
{!isLoading && !isError && account && (
<User data={account} />
)}
</div>
<span>
<ChevronRightIcon
width={14}
height={14}
className="text-zinc-500"
/>
</span>
<div className="inline-flex h-6 w-max items-center justify-center gap-0.5 rounded bg-zinc-800 pl-3 pr-1.5 text-xs font-medium text-zinc-400 shadow-mini-button">
New Post
<ChevronDownIcon width={14} height={14} />
</div>
</div>
<div
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>
{composer.type === "post" && account && (
<Post pubkey={account.pubkey} privkey={account.privkey} />
)}
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
}

View File

@@ -1,124 +1,141 @@
import { ImageUploader } from '@shared/composer/imageUploader';
import TrashIcon from '@shared/icons/trash';
import { RelayContext } from '@shared/relayProvider';
import { ImageUploader } from "@shared/composer/imageUploader";
import TrashIcon from "@shared/icons/trash";
import { RelayContext } from "@shared/relayProvider";
import { WRITEONLY_RELAYS } from '@stores/constants';
import { WRITEONLY_RELAYS } from "@stores/constants";
import { dateToUnix } from '@utils/date';
import { dateToUnix } from "@utils/date";
import { getEventHash, signEvent } from 'nostr-tools';
import { useCallback, useContext, 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 { getEventHash, signEvent } from "nostr-tools";
import { useCallback, useContext, useMemo, useState } from "react";
import { Node, Transforms, createEditor } from "slate";
import { withHistory } from "slate-history";
import {
Editable,
ReactEditor,
Slate,
useSlateStatic,
withReact,
} from "slate-react";
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: any; children: any; element: any }) => {
const editor: any = useSlateStatic();
const path = ReactEditor.findPath(editor, element);
const ImagePreview = ({
attributes,
children,
element,
}: { attributes: any; children: any; element: any }) => {
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 src={element.url} className="m-0 h-auto w-full rounded-md" />
<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-xs 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 w-full rounded-md"
/>
<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-xs font-medium text-zinc-400 shadow-mini-button hover:bg-zinc-700"
>
<TrashIcon width={14} height={14} className="text-zinc-100" />
</button>
</div>
</figure>
);
};
export function Post({ pubkey, privkey }: { pubkey: string; privkey: string }) {
const pool: any = useContext(RelayContext);
const pool: any = useContext(RelayContext);
const editor = useMemo(() => withReact(withImages(withHistory(createEditor()))), []);
const [content, setContent] = useState<Node[]>([
{
children: [
{
text: '',
},
],
},
]);
const editor = useMemo(
() => withReact(withImages(withHistory(createEditor()))),
[],
);
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 submit = () => {
// serialize content
const serializedContent = serialize(content);
console.log(serializedContent);
const submit = () => {
// serialize content
const serializedContent = serialize(content);
console.log(serializedContent);
const event: any = {
content: serializedContent,
created_at: dateToUnix(),
kind: 1,
pubkey: pubkey,
tags: [],
};
event.id = getEventHash(event);
event.sig = signEvent(event, privkey);
const event: any = {
content: serializedContent,
created_at: dateToUnix(),
kind: 1,
pubkey: pubkey,
tags: [],
};
event.id = getEventHash(event);
event.sig = signEvent(event, privkey);
// publish note
pool.publish(event, WRITEONLY_RELAYS);
};
// publish note
pool.publish(event, WRITEONLY_RELAYS);
};
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} />;
}
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>
<div className="prose prose-zinc relative h-max w-full max-w-none select-text break-words pb-3 dark:prose-invert prose-p:mb-0.5 prose-p:mt-0 prose-p:text-[15px] prose-p:leading-tight prose-a:text-[15px] prose-a:font-normal prose-a:leading-tight prose-a:text-fuchsia-500 prose-a:no-underline hover:prose-a:text-fuchsia-600 hover:prose-a:underline prose-ol:mb-1 prose-ul:mb-1 prose-li:text-[15px] prose-li:leading-tight">
<Editable
autoFocus
placeholder="What's on your mind?"
spellCheck="false"
className="!min-h-[86px]"
renderElement={renderElement}
/>
</div>
</div>
<div className="flex items-center justify-between">
<ImageUploader />
<button
type="button"
autoFocus={false}
onClick={submit}
className="inline-flex h-7 w-max items-center justify-center gap-1 rounded-md bg-fuchsia-500 px-3.5 text-xs font-medium text-zinc-200 shadow-button hover:bg-fuchsia-600"
>
Post
</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="prose prose-zinc relative h-max w-full max-w-none select-text break-words pb-3 dark:prose-invert prose-p:mb-0.5 prose-p:mt-0 prose-p:text-[15px] prose-p:leading-tight prose-a:text-[15px] prose-a:font-normal prose-a:leading-tight prose-a:text-fuchsia-500 prose-a:no-underline hover:prose-a:text-fuchsia-600 hover:prose-a:underline prose-ol:mb-1 prose-ul:mb-1 prose-li:text-[15px] prose-li:leading-tight">
<Editable
autoFocus
placeholder="What's on your mind?"
spellCheck="false"
className="!min-h-[86px]"
renderElement={renderElement}
/>
</div>
</div>
<div className="flex items-center justify-between">
<ImageUploader />
<button
type="button"
onClick={submit}
className="inline-flex h-7 w-max items-center justify-center gap-1 rounded-md bg-fuchsia-500 px-3.5 text-xs font-medium text-zinc-200 shadow-button hover:bg-fuchsia-600"
>
Post
</button>
</div>
</div>
</Slate>
);
}

View File

@@ -1,25 +1,27 @@
import { Image } from '@shared/image';
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, IMGPROXY_URL } from '@stores/constants';
import { DEFAULT_AVATAR, IMGPROXY_URL } from "@stores/constants";
export function User({ data }: { data: any }) {
const metadata = JSON.parse(data.metadata);
const metadata = JSON.parse(data.metadata);
return (
<div className="flex items-center gap-2">
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
<Image
src={`${IMGPROXY_URL}/rs:fit:100:100/plain/${metadata?.picture ? metadata.picture : DEFAULT_AVATAR}`}
alt={data.pubkey}
className="h-8 w-8 object-cover"
loading="auto"
/>
</div>
<h5 className="text-sm font-semibold leading-none text-zinc-100">
{metadata?.display_name || metadata?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700"></div>
)}
</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={`${IMGPROXY_URL}/rs:fit:100:100/plain/${
metadata?.picture ? metadata.picture : DEFAULT_AVATAR
}`}
alt={data.pubkey}
className="h-8 w-8 object-cover"
loading="auto"
/>
</div>
<h5 className="text-sm font-semibold leading-none text-zinc-100">
{metadata?.display_name || metadata?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
</h5>
</div>
);
}

View File

@@ -1,117 +1,130 @@
import { RelayContext } from '@shared/relayProvider';
import { RelayContext } from "@shared/relayProvider";
import HeartBeatIcon from '@icons/heartbeat';
import HeartBeatIcon from "@icons/heartbeat";
import { READONLY_RELAYS } from '@stores/constants';
import { hasNewerNoteAtom } from '@stores/note';
import { READONLY_RELAYS } from "@stores/constants";
import { hasNewerNoteAtom } from "@stores/note";
import { dateToUnix } from '@utils/date';
import { useActiveAccount } from '@utils/hooks/useActiveAccount';
import { createChat, createNote, updateAccount } from '@utils/storage';
import { getParentID, nip02ToArray } from '@utils/transform';
import { dateToUnix } from "@utils/date";
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
import { createChat, createNote, updateAccount } from "@utils/storage";
import { getParentID, nip02ToArray } from "@utils/transform";
import { useSetAtom } from 'jotai';
import { useContext, useRef } from 'react';
import useSWRSubscription from 'swr/subscription';
import { useSetAtom } from "jotai";
import { useContext, useRef } from "react";
import useSWRSubscription from "swr/subscription";
export default function EventCollector() {
const pool: any = useContext(RelayContext);
const pool: any = useContext(RelayContext);
const setHasNewerNote = useSetAtom(hasNewerNoteAtom);
const now = useRef(new Date());
const setHasNewerNote = useSetAtom(hasNewerNoteAtom);
const now = useRef(new Date());
const { account, isLoading, isError } = useActiveAccount();
const { account, isLoading, isError } = useActiveAccount();
useSWRSubscription(!isLoading && !isError && account ? ['eventCollector', account] : null, ([, key], {}) => {
const follows = JSON.parse(key.follows);
const followsAsArray = nip02ToArray(follows);
const unsubscribe = pool.subscribe(
[
{
kinds: [1, 6],
authors: followsAsArray,
since: dateToUnix(now.current),
},
{
kinds: [0, 3],
authors: [key.pubkey],
},
{
kinds: [4],
'#p': [key.pubkey],
since: dateToUnix(now.current),
},
{
kinds: [30023],
since: dateToUnix(now.current),
},
],
READONLY_RELAYS,
(event: any) => {
switch (event.kind) {
// metadata
case 0:
updateAccount('metadata', event.content, event.pubkey);
break;
// short text note
case 1:
const parentID = getParentID(event.tags, event.id);
createNote(
event.id,
account.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
parentID
);
// notify user reload to get newer note
setHasNewerNote(true);
break;
// contacts
case 3:
// update account's folllows with NIP-02 tag list
updateAccount('follows', event.tags, event.pubkey);
break;
// chat
case 4:
if (event.pubkey !== key.pubkey) {
createChat(key.id, event.pubkey, event.created_at);
}
break;
// repost
case 6:
createNote(
event.id,
key.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
event.id
);
break;
// long post
case 30023:
// insert event to local database
createNote(event.id, account.id, event.pubkey, event.kind, event.tags, event.content, event.created_at, '');
break;
default:
break;
}
}
);
useSWRSubscription(
!isLoading && !isError && account ? ["eventCollector", account] : null,
([, key]) => {
const follows = JSON.parse(key.follows);
const followsAsArray = nip02ToArray(follows);
const unsubscribe = pool.subscribe(
[
{
kinds: [1, 6],
authors: followsAsArray,
since: dateToUnix(now.current),
},
{
kinds: [0, 3],
authors: [key.pubkey],
},
{
kinds: [4],
"#p": [key.pubkey],
since: dateToUnix(now.current),
},
{
kinds: [30023],
since: dateToUnix(now.current),
},
],
READONLY_RELAYS,
(event: any) => {
switch (event.kind) {
// metadata
case 0:
updateAccount("metadata", event.content, event.pubkey);
break;
// short text note
case 1: {
const parentID = getParentID(event.tags, event.id);
createNote(
event.id,
account.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
parentID,
);
// notify user reload to get newer note
setHasNewerNote(true);
break;
}
// contacts
case 3:
// update account's folllows with NIP-02 tag list
updateAccount("follows", event.tags, event.pubkey);
break;
// chat
case 4:
if (event.pubkey !== key.pubkey) {
createChat(key.id, event.pubkey, event.created_at);
}
break;
// repost
case 6:
createNote(
event.id,
key.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
event.id,
);
break;
// long post
case 30023:
// insert event to local database
createNote(
event.id,
account.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
"",
);
break;
default:
break;
}
},
);
return () => {
unsubscribe();
};
});
return () => {
unsubscribe();
};
},
);
return (
<div className="inline-flex h-6 w-6 items-center justify-center rounded text-zinc-400 hover:bg-zinc-900 hover:text-zinc-200">
<HeartBeatIcon width={14} height={14} />
</div>
);
return (
<div className="inline-flex h-6 w-6 items-center justify-center rounded text-zinc-400 hover:bg-zinc-900 hover:text-zinc-200">
<HeartBeatIcon width={14} height={14} />
</div>
);
}

View File

@@ -1,98 +1,110 @@
import PlusIcon from '@icons/plus';
import PlusIcon from "@icons/plus";
import { channelContentAtom } from '@stores/channel';
import { chatContentAtom } from '@stores/chat';
import { noteContentAtom } from '@stores/note';
import { channelContentAtom } from "@stores/channel";
import { chatContentAtom } from "@stores/chat";
import { noteContentAtom } from "@stores/note";
import { createBlobFromFile } from '@utils/createBlobFromFile';
import { createBlobFromFile } from "@utils/createBlobFromFile";
import { open } from '@tauri-apps/api/dialog';
import { Body, fetch } from '@tauri-apps/api/http';
import { useSetAtom } from 'jotai';
import { useState } from 'react';
import { open } from "@tauri-apps/api/dialog";
import { Body, fetch } from "@tauri-apps/api/http";
import { useSetAtom } from "jotai";
import { useState } from "react";
export function ImagePicker({ type }: { type: string }) {
let atom;
let atom;
switch (type) {
case 'note':
atom = noteContentAtom;
break;
case 'chat':
atom = chatContentAtom;
break;
case 'channel':
atom = channelContentAtom;
break;
default:
throw new Error('Invalid type');
}
switch (type) {
case "note":
atom = noteContentAtom;
break;
case "chat":
atom = chatContentAtom;
break;
case "channel":
atom = channelContentAtom;
break;
default:
throw new Error("Invalid type");
}
const [loading, setLoading] = useState(false);
const setValue = useSetAtom(atom);
const [loading, setLoading] = useState(false);
const setValue = useSetAtom(atom);
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 webpImage = 'https://void.cat/d/' + res.data.file.id + '.webp';
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 webpImage = `https://void.cat/d/${res.data.file.id}.webp`;
setValue((content: string) => content + ' ' + webpImage);
setLoading(false);
}
};
setValue((content: string) => `${content} ${webpImage}`);
setLoading(false);
}
};
return (
<button
onClick={() => openFileDialog()}
className="inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-zinc-700"
>
{loading ? (
<svg
className="h-4 w-4 animate-spin text-black dark:text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<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"
></path>
</svg>
) : (
<PlusIcon width={16} height={16} className="text-zinc-400" />
)}
</button>
);
return (
<button
type="button"
onClick={() => openFileDialog()}
className="inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-zinc-700"
>
{loading ? (
<svg
className="h-4 w-4 animate-spin text-black dark:text-white"
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>
) : (
<PlusIcon width={16} height={16} className="text-zinc-400" />
)}
</button>
);
}

View File

@@ -1,15 +1,22 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,15 +1,22 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,13 +1,22 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,14 +1,21 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,14 +1,23 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default function CheckCircleIcon(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>
);
export default function CheckCircleIcon(
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>
);
}

View File

@@ -1,15 +1,24 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default function ChevronDownIcon(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>
);
export default function ChevronDownIcon(
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>
);
}

View File

@@ -1,15 +1,24 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default function ChevronRightIcon(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>
);
export default function ChevronRightIcon(
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>
);
}

View File

@@ -1,12 +1,21 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,15 +1,24 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,15 +1,24 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,12 +1,21 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,12 +1,21 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,15 +1,24 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,12 +1,21 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,14 +1,21 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,10 +1,14 @@
export default 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>
);
}

View File

@@ -1,18 +1,27 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,15 +1,24 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default function MyspaceIcon(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.75 7.75H15.25M8.75 11.75H12.25M4.25 3.25V20.75H19.75V3.25H4.25Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export default function MyspaceIcon(
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.75 7.75H15.25M8.75 11.75H12.25M4.25 3.25V20.75H19.75V3.25H4.25Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,12 +1,20 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default function NavArrowDownIcon(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.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>
);
export default function NavArrowDownIcon(
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>
);
}

View File

@@ -1,15 +1,22 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,15 +1,24 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,14 +1,21 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,15 +1,22 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,14 +1,23 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default function ReplyMessageIcon(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>
);
export default function ReplyMessageIcon(
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>
);
}

View File

@@ -1,15 +1,22 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,12 +1,21 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,12 +1,21 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,15 +1,24 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default 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 default 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>
);
}

View File

@@ -1,14 +1,21 @@
import { SVGProps } from 'react';
import { SVGProps } from "react";
export default function ZapIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M20.25 8.75H13.25V1.75L3.75 15.0473H10.75V22.25L20.25 8.75Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinejoin="round"
/>
</svg>
);
export default function ZapIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M20.25 8.75H13.25V1.75L3.75 15.0473H10.75V22.25L20.25 8.75Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,11 +1,18 @@
import { DEFAULT_AVATAR } from '@stores/constants';
import { DEFAULT_AVATAR } from "@stores/constants";
export function Image(props) {
const addImageFallback = (event) => {
event.currentTarget.src = DEFAULT_AVATAR;
};
const addImageFallback = (event) => {
event.currentTarget.src = DEFAULT_AVATAR;
};
return (
<img {...props} loading="lazy" decoding="async" onError={addImageFallback} style={{ contentVisibility: 'auto' }} />
);
return (
<img
{...props}
loading="lazy"
decoding="async"
onError={addImageFallback}
alt="lume default img"
style={{ contentVisibility: "auto" }}
/>
);
}

View File

@@ -1,65 +1,79 @@
import ActiveAccount from '@shared/accounts/active';
import InactiveAccount from '@shared/accounts/inactive';
import ActiveAccount from "@shared/accounts/active";
import InactiveAccount from "@shared/accounts/inactive";
import BellIcon from '@icons/bell';
import PlusIcon from '@icons/plus';
import BellIcon from "@icons/bell";
import PlusIcon from "@icons/plus";
import { APP_VERSION } from '@stores/constants';
import { APP_VERSION } from "@stores/constants";
import { getAccounts, getActiveAccount } from '@utils/storage';
import { getAccounts, getActiveAccount } from "@utils/storage";
import useSWR from 'swr';
import useSWR from "swr";
const allFetcher = () => getAccounts();
const fetcher = () => getActiveAccount();
export default function MultiAccounts() {
const { data: accounts }: any = useSWR('allAccounts', allFetcher);
const { data: activeAccount }: any = useSWR('activeAccount', fetcher);
const { data: accounts }: any = useSWR("allAccounts", allFetcher);
const { data: activeAccount }: any = useSWR("activeAccount", fetcher);
return (
<div className="flex h-full flex-col items-center justify-between pb-4 pt-3">
<div className="flex flex-col items-center">
<div className="flex flex-col gap-2">
<>
{!activeAccount ? (
<div className="group relative flex h-10 w-10 shrink animate-pulse items-center justify-center rounded-lg bg-zinc-900"></div>
) : (
<ActiveAccount user={activeAccount} />
)}
</>
<div>
<button
type="button"
className="group relative flex h-10 w-10 shrink items-center justify-center rounded-lg bg-zinc-900 hover:bg-zinc-800"
>
<BellIcon width={16} height={16} className="text-zinc-400 group-hover:text-zinc-200" />
</button>
</div>
</div>
<div className="my-2 h-px w-2/3 bg-zinc-800"></div>
<div className="flex flex-col gap-3">
<>
{!accounts ? (
<div className="group relative flex h-10 w-10 shrink animate-pulse items-center justify-center rounded-lg bg-zinc-900"></div>
) : (
accounts.map((account: { is_active: number; pubkey: string }) => (
<InactiveAccount key={account.pubkey} user={account} />
))
)}
</>
<button
type="button"
className="group relative flex h-10 w-10 shrink items-center justify-center rounded-lg border-2 border-dashed border-transparent hover:border-zinc-600"
>
<PlusIcon width={16} height={16} className="text-zinc-400 group-hover:text-zinc-200" />
</button>
</div>
</div>
<div className="flex flex-col gap-0.5 text-center">
<span className="text-sm font-black uppercase leading-tight text-zinc-600">Lume</span>
<span className="text-xs font-medium text-zinc-700">v{APP_VERSION}</span>
</div>
</div>
);
return (
<div className="flex h-full flex-col items-center justify-between pb-4 pt-3">
<div className="flex flex-col items-center">
<div className="flex flex-col gap-2">
<>
{!activeAccount ? (
<div className="group relative flex h-10 w-10 shrink animate-pulse items-center justify-center rounded-lg bg-zinc-900" />
) : (
<ActiveAccount user={activeAccount} />
)}
</>
<div>
<button
type="button"
className="group relative flex h-10 w-10 shrink items-center justify-center rounded-lg bg-zinc-900 hover:bg-zinc-800"
>
<BellIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-zinc-200"
/>
</button>
</div>
</div>
<div className="my-2 h-px w-2/3 bg-zinc-800" />
<div className="flex flex-col gap-3">
<>
{!accounts ? (
<div className="group relative flex h-10 w-10 shrink animate-pulse items-center justify-center rounded-lg bg-zinc-900" />
) : (
accounts.map(
(account: { is_active: number; pubkey: string }) => (
<InactiveAccount key={account.pubkey} user={account} />
),
)
)}
</>
<button
type="button"
className="group relative flex h-10 w-10 shrink items-center justify-center rounded-lg border-2 border-dashed border-transparent hover:border-zinc-600"
>
<PlusIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-zinc-200"
/>
</button>
</div>
</div>
<div className="flex flex-col gap-0.5 text-center">
<span className="text-sm font-black uppercase leading-tight text-zinc-600">
Lume
</span>
<span className="text-xs font-medium text-zinc-700">
v{APP_VERSION}
</span>
</div>
</div>
);
}

View File

@@ -1,102 +1,116 @@
import ChannelsList from '@app/channel/components/list';
import ChatsList from '@app/chat/components/list';
import ChannelsList from "@app/channel/components/list";
import ChatsList from "@app/chat/components/list";
import ActiveLink from '@shared/activeLink';
import { ComposerModal } from '@shared/composer/modal';
import EventCollector from '@shared/eventCollector';
import ActiveLink from "@shared/activeLink";
import { ComposerModal } from "@shared/composer/modal";
import EventCollector from "@shared/eventCollector";
import MyspaceIcon from '@icons/myspace';
import NavArrowDownIcon from '@icons/navArrowDown';
import ThreadsIcon from '@icons/threads';
import WorldIcon from '@icons/world';
import MyspaceIcon from "@icons/myspace";
import NavArrowDownIcon from "@icons/navArrowDown";
import ThreadsIcon from "@icons/threads";
import WorldIcon from "@icons/world";
import { Disclosure } from '@headlessui/react';
import { Disclosure } from "@headlessui/react";
export default function Navigation() {
return (
<div className="relative flex h-full flex-col gap-3 pt-1.5">
<div className="flex h-11 items-center justify-between px-3.5">
<ComposerModal />
<EventCollector />
</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 text-zinc-400">
<ActiveLink
href="/app/today"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] font-semibold hover:text-zinc-200"
activeClassName="bg-zinc-900/50 hover:bg-zinc-900"
>
<span className="inline-flex h-5 w-5 items-center justify-center rounded bg-zinc-900">
<WorldIcon width={12} height={12} className="text-zinc-200" />
</span>
<span>Today</span>
</ActiveLink>
<ActiveLink
href="/app/threads"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] font-semibold hover:text-zinc-200"
activeClassName=""
>
<span className="inline-flex h-5 w-5 items-center justify-center rounded bg-zinc-900">
<ThreadsIcon width={12} height={12} className="text-zinc-200" />
</span>
<span>Threads</span>
</ActiveLink>
<ActiveLink
href="/app/space"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] font-semibold hover:text-zinc-200"
activeClassName=""
>
<span className="inline-flex h-5 w-5 items-center justify-center rounded bg-zinc-900">
<MyspaceIcon width={12} height={12} className="text-zinc-200" />
</span>
<span>Space</span>
</ActiveLink>
</div>
</div>
{/* Channels */}
<Disclosure defaultOpen={true}>
{({ open }) => (
<div className="flex flex-col gap-0.5 px-1.5">
<Disclosure.Button className="flex items-center gap-1 px-2.5">
<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">Channels</h3>
</Disclosure.Button>
<Disclosure.Panel>
<ChannelsList />
</Disclosure.Panel>
</div>
)}
</Disclosure>
{/* Chats */}
<Disclosure defaultOpen={true}>
{({ open }) => (
<div className="flex flex-col gap-0.5 px-1.5">
<Disclosure.Button className="flex items-center gap-1 px-2.5">
<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>
);
return (
<div className="relative flex h-full flex-col gap-3 pt-1.5">
<div className="flex h-11 items-center justify-between px-3.5">
<ComposerModal />
<EventCollector />
</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 text-zinc-400">
<ActiveLink
href="/app/today"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] font-semibold hover:text-zinc-200"
activeClassName="bg-zinc-900/50 hover:bg-zinc-900"
>
<span className="inline-flex h-5 w-5 items-center justify-center rounded bg-zinc-900">
<WorldIcon width={12} height={12} className="text-zinc-200" />
</span>
<span>Today</span>
</ActiveLink>
<ActiveLink
href="/app/threads"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] font-semibold hover:text-zinc-200"
activeClassName=""
>
<span className="inline-flex h-5 w-5 items-center justify-center rounded bg-zinc-900">
<ThreadsIcon width={12} height={12} className="text-zinc-200" />
</span>
<span>Threads</span>
</ActiveLink>
<ActiveLink
href="/app/space"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] font-semibold hover:text-zinc-200"
activeClassName=""
>
<span className="inline-flex h-5 w-5 items-center justify-center rounded bg-zinc-900">
<MyspaceIcon width={12} height={12} className="text-zinc-200" />
</span>
<span>Space</span>
</ActiveLink>
</div>
</div>
{/* Channels */}
<Disclosure defaultOpen={true}>
{({ open }) => (
<div className="flex flex-col gap-0.5 px-1.5">
<Disclosure.Button className="flex items-center gap-1 px-2.5">
<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">
Channels
</h3>
</Disclosure.Button>
<Disclosure.Panel>
<ChannelsList />
</Disclosure.Panel>
</div>
)}
</Disclosure>
{/* Chats */}
<Disclosure defaultOpen={true}>
{({ open }) => (
<div className="flex flex-col gap-0.5 px-1.5">
<Disclosure.Button className="flex items-center gap-1 px-2.5">
<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>
);
}

View File

@@ -1,21 +1,25 @@
import { useNetworkStatus } from '@utils/hooks/useNetworkStatus';
import { useNetworkStatus } from "@utils/hooks/useNetworkStatus";
export function NetworkStatusIndicator() {
const isOnline = useNetworkStatus();
const isOnline = useNetworkStatus();
return (
<div className="inline-flex items-center gap-1 rounded-md px-1.5 py-1 hover:bg-zinc-900">
<div className="relative flex h-1.5 w-1.5">
<span
className={`absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 ${
isOnline ? 'bg-green-400' : 'bg-red-400'
}`}
></span>
<span
className={`relative inline-flex h-1.5 w-1.5 rounded-full ${isOnline ? 'bg-green-400' : 'bg-amber-400'}`}
></span>
</div>
<p className="text-xs font-medium text-zinc-500">{isOnline ? 'Online' : 'Offline'}</p>
</div>
);
return (
<div className="inline-flex items-center gap-1 rounded-md px-1.5 py-1 hover:bg-zinc-900">
<div className="relative flex h-1.5 w-1.5">
<span
className={`absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 ${
isOnline ? "bg-green-400" : "bg-red-400"
}`}
/>
<span
className={`relative inline-flex h-1.5 w-1.5 rounded-full ${
isOnline ? "bg-green-400" : "bg-amber-400"
}`}
/>
</div>
<p className="text-xs font-medium text-zinc-500">
{isOnline ? "Online" : "Offline"}
</p>
</div>
);
}

View File

@@ -1,24 +1,26 @@
import { RelayContext } from '@shared/relaysProvider';
import { UserFollow } from '@shared/user/follow';
import { RelayContext } from "@shared/relaysProvider";
import { UserFollow } from "@shared/user/follow";
import { READONLY_RELAYS } from '@stores/constants';
import { READONLY_RELAYS } from "@stores/constants";
import destr from 'destr';
import { Author } from 'nostr-relaypool';
import { useContext, useEffect, useState } from 'react';
import destr from "destr";
import { Author } from "nostr-relaypool";
import { useContext, useEffect, useState } from "react";
export default function ProfileFollowers({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const [followers, setFollowers] = useState(null);
const pool: any = useContext(RelayContext);
const [followers, setFollowers] = useState(null);
useEffect(() => {
const user = new Author(pool, READONLY_RELAYS, id);
user.followers((res) => setFollowers(destr(res.tags)), 0, 100);
}, [id, pool]);
useEffect(() => {
const user = new Author(pool, READONLY_RELAYS, id);
user.followers((res) => setFollowers(destr(res.tags)), 0, 100);
}, [id, pool]);
return (
<div className="flex flex-col gap-3 px-3 py-5">
{followers && followers.map((follower) => <UserFollow key={follower[1]} pubkey={follower[1]} />)}
</div>
);
return (
<div className="flex flex-col gap-3 px-3 py-5">
{followers?.map((follower) => (
<UserFollow key={follower[1]} pubkey={follower[1]} />
))}
</div>
);
}

View File

@@ -1,23 +1,25 @@
import { RelayContext } from '@shared/relaysProvider';
import { UserFollow } from '@shared/user/follow';
import { RelayContext } from "@shared/relaysProvider";
import { UserFollow } from "@shared/user/follow";
import { READONLY_RELAYS } from '@stores/constants';
import { READONLY_RELAYS } from "@stores/constants";
import { Author } from 'nostr-relaypool';
import { useContext, useEffect, useState } from 'react';
import { Author } from "nostr-relaypool";
import { useContext, useEffect, useState } from "react";
export default function ProfileFollows({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const [follows, setFollows] = useState(null);
const pool: any = useContext(RelayContext);
const [follows, setFollows] = useState(null);
useEffect(() => {
const user = new Author(pool, READONLY_RELAYS, id);
user.follows((res) => setFollows(res), 0);
}, [id, pool]);
useEffect(() => {
const user = new Author(pool, READONLY_RELAYS, id);
user.follows((res) => setFollows(res), 0);
}, [id, pool]);
return (
<div className="flex flex-col gap-3 px-3 py-5">
{follows && follows.map((follow) => <UserFollow key={follow.pubkey} pubkey={follow.pubkey} />)}
</div>
);
return (
<div className="flex flex-col gap-3 px-3 py-5">
{follows?.map((follow) => (
<UserFollow key={follow.pubkey} pubkey={follow.pubkey} />
))}
</div>
);
}

View File

@@ -1,48 +1,61 @@
import { Image } from '@shared/image';
import { RelayContext } from '@shared/relayProvider';
import { Image } from "@shared/image";
import { RelayContext } from "@shared/relayProvider";
import { DEFAULT_AVATAR, READONLY_RELAYS } from '@stores/constants';
import { DEFAULT_AVATAR, READONLY_RELAYS } from "@stores/constants";
import { shortenKey } from '@utils/shortenKey';
import { shortenKey } from "@utils/shortenKey";
import destr from 'destr';
import { Author } from 'nostr-relaypool';
import { useContext, useEffect, useState } from 'react';
import destr from "destr";
import { Author } from "nostr-relaypool";
import { useContext, useEffect, useState } from "react";
const DEFAULT_BANNER = 'https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg';
const DEFAULT_BANNER =
"https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg";
export default function ProfileMetadata({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const [profile, setProfile] = useState(null);
const pool: any = useContext(RelayContext);
const [profile, setProfile] = useState(null);
useEffect(() => {
const user = new Author(pool, READONLY_RELAYS, id);
user.metaData((res) => setProfile(destr(res.content)), 0);
}, [id, pool]);
useEffect(() => {
const user = new Author(pool, READONLY_RELAYS, id);
user.metaData((res) => setProfile(destr(res.content)), 0);
}, [id, pool]);
return (
<>
<div className="relative">
<div className="relative h-56 w-full rounded-t-lg bg-zinc-800">
<Image src={profile?.banner || DEFAULT_BANNER} alt="user's banner" className="h-58 w-full object-cover" />
</div>
<div className="relative -top-8 z-10 px-4">
<div className="relative h-16 w-16 rounded-lg bg-zinc-900 ring-2 ring-zinc-900">
<Image src={profile?.picture || DEFAULT_AVATAR} alt={id} className="h-16 w-16 rounded-lg object-cover" />
</div>
</div>
</div>
<div className="-mt-4 mb-8 px-4">
<div>
<div className="mb-3 flex flex-col">
<h3 className="text-lg font-semibold leading-tight text-zinc-100">
{profile?.display_name || profile?.name}
</h3>
<span className="text-sm leading-tight text-zinc-500">{profile?.username || (id && shortenKey(id))}</span>
</div>
<div className="prose-sm prose-zinc leading-tight dark:prose-invert">{profile?.about}</div>
</div>
</div>
</>
);
return (
<>
<div className="relative">
<div className="relative h-56 w-full rounded-t-lg bg-zinc-800">
<Image
src={profile?.banner || DEFAULT_BANNER}
alt="user's banner"
className="h-58 w-full object-cover"
/>
</div>
<div className="relative -top-8 z-10 px-4">
<div className="relative h-16 w-16 rounded-lg bg-zinc-900 ring-2 ring-zinc-900">
<Image
src={profile?.picture || DEFAULT_AVATAR}
alt={id}
className="h-16 w-16 rounded-lg object-cover"
/>
</div>
</div>
</div>
<div className="-mt-4 mb-8 px-4">
<div>
<div className="mb-3 flex flex-col">
<h3 className="text-lg font-semibold leading-tight text-zinc-100">
{profile?.display_name || profile?.name}
</h3>
<span className="text-sm leading-tight text-zinc-500">
{profile?.username || (id && shortenKey(id))}
</span>
</div>
<div className="prose-sm prose-zinc leading-tight dark:prose-invert">
{profile?.about}
</div>
</div>
</div>
</>
);
}

View File

@@ -1,27 +1,27 @@
import { NoteBase } from '@shared/note/base';
import { RelayContext } from '@shared/relaysProvider';
import { NoteBase } from "@shared/note/base";
import { RelayContext } from "@shared/relaysProvider";
import { READONLY_RELAYS } from '@stores/constants';
import { READONLY_RELAYS } from "@stores/constants";
import { Author } from 'nostr-relaypool';
import { useContext, useEffect, useState } from 'react';
import { Author } from "nostr-relaypool";
import { useContext, useEffect, useState } from "react";
export default function ProfileNotes({ id }: { id: string }) {
const pool: any = useContext(RelayContext);
const [data, setData] = useState([]);
const pool: any = useContext(RelayContext);
const [data, setData] = useState([]);
useEffect(() => {
const user = new Author(pool, READONLY_RELAYS, id);
user.text((res) => setData((data) => [...data, res]), 100, 0);
}, [id, pool]);
useEffect(() => {
const user = new Author(pool, READONLY_RELAYS, id);
user.text((res) => setData((data) => [...data, res]), 100, 0);
}, [id, pool]);
return (
<div className="flex flex-col">
{data.map((item) => (
<div key={item.id}>
<NoteBase event={item} />
</div>
))}
</div>
);
return (
<div className="flex flex-col">
{data.map((item) => (
<div key={item.id}>
<NoteBase event={item} />
</div>
))}
</div>
);
}

View File

@@ -1,17 +1,17 @@
import { FULL_RELAYS } from '@stores/constants';
import { FULL_RELAYS } from "@stores/constants";
import { RelayPool } from 'nostr-relaypool';
import { createContext } from 'react';
import { RelayPool } from "nostr-relaypool";
import { createContext } from "react";
export const RelayContext = createContext({});
const pool = new RelayPool(FULL_RELAYS, {
useEventCache: false,
subscriptionCache: true,
logErrorsAndNotices: false,
logSubscriptions: false,
useEventCache: false,
subscriptionCache: true,
logErrorsAndNotices: false,
logSubscriptions: false,
});
export function RelayProvider({ children }: { children: React.ReactNode }) {
return <RelayContext.Provider value={pool}>{children}</RelayContext.Provider>;
return <RelayContext.Provider value={pool}>{children}</RelayContext.Provider>;
}

View File

@@ -1,44 +1,58 @@
import { autoUpdate, offset, shift, useFloating, useFocus, useHover, useInteractions } from '@floating-ui/react';
import { useState } from 'react';
import {
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);
export function Tooltip({
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 px-4 py-2 text-xs 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 bg-zinc-800 px-4 py-2 text-xs font-medium leading-none text-zinc-100"
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
}}
{...getFloatingProps()}
>
{message}
</div>
)}
</>
);
}