rome -> eslint + prettier

This commit is contained in:
Ren Amamiya
2023-07-04 13:24:42 +07:00
parent 744fbd5683
commit a30cf66c2e
187 changed files with 10179 additions and 10066 deletions

View File

@@ -1,105 +1,105 @@
import { createChat, getLastLogin } from "@libs/storage";
import { Image } from "@shared/image";
import { NetworkStatusIndicator } from "@shared/networkStatusIndicator";
import { RelayContext } from "@shared/relayProvider";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useProfile } from "@utils/hooks/useProfile";
import { sendNativeNotification } from "@utils/notification";
import { produce } from "immer";
import { useContext, useEffect } from "react";
import { Link } from "react-router-dom";
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { produce } from 'immer';
import { useContext, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { createChat, getLastLogin } from '@libs/storage';
import { Image } from '@shared/image';
import { NetworkStatusIndicator } from '@shared/networkStatusIndicator';
import { RelayContext } from '@shared/relayProvider';
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfile } from '@utils/hooks/useProfile';
import { sendNativeNotification } from '@utils/notification';
const lastLogin = await getLastLogin();
export function ActiveAccount({ data }: { data: any }) {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const { status, user } = useProfile(data.pubkey);
const { status, user } = useProfile(data.pubkey);
const chat = useMutation({
mutationFn: (data: any) => {
return createChat(
data.id,
data.receiver_pubkey,
data.sender_pubkey,
data.content,
data.tags,
data.created_at,
);
},
onSuccess: (data: any) => {
const prev = queryClient.getQueryData(["chats"]);
const next = produce(prev, (draft: any) => {
const target = draft.findIndex(
(m: { sender_pubkey: string }) => m.sender_pubkey === data,
);
if (target !== -1) {
draft[target]["new_messages"] =
draft[target]["new_messages"] + 1 || 1;
} else {
draft.push({ sender_pubkey: data, new_messages: 1 });
}
});
queryClient.setQueryData(["chats"], next);
},
});
const chat = useMutation({
mutationFn: (data: any) => {
return createChat(
data.id,
data.receiver_pubkey,
data.sender_pubkey,
data.content,
data.tags,
data.created_at
);
},
onSuccess: (data: any) => {
const prev = queryClient.getQueryData(['chats']);
const next = produce(prev, (draft: any) => {
const target = draft.findIndex(
(m: { sender_pubkey: string }) => m.sender_pubkey === data
);
if (target !== -1) {
draft[target]['new_messages'] = draft[target]['new_messages'] + 1 || 1;
} else {
draft.push({ sender_pubkey: data, new_messages: 1 });
}
});
queryClient.setQueryData(['chats'], next);
},
});
useEffect(() => {
const since = lastLogin > 0 ? lastLogin : Math.floor(Date.now() / 1000);
const sub = ndk.subscribe(
{
kinds: [4],
"#p": [data.pubkey],
since: since,
},
{
closeOnEose: false,
},
);
useEffect(() => {
const since = lastLogin > 0 ? lastLogin : Math.floor(Date.now() / 1000);
const sub = ndk.subscribe(
{
kinds: [4],
'#p': [data.pubkey],
since: since,
},
{
closeOnEose: false,
}
);
sub.addListener("event", (event) => {
switch (event.kind) {
case 4:
// update state
chat.mutate({
id: event.id,
receiver_pubkey: data.pubkey,
sender_pubkey: event.pubkey,
content: event.content,
tags: event.tags,
created_at: event.created_at,
});
// send native notifiation
sendNativeNotification("You've received new message");
break;
default:
break;
}
});
sub.addListener('event', (event) => {
switch (event.kind) {
case 4:
// update state
chat.mutate({
id: event.id,
receiver_pubkey: data.pubkey,
sender_pubkey: event.pubkey,
content: event.content,
tags: event.tags,
created_at: event.created_at,
});
// send native notifiation
sendNativeNotification("You've received new message");
break;
default:
break;
}
});
return () => {
sub.stop();
};
}, []);
return () => {
sub.stop();
};
}, []);
if (status === "loading") {
return <div className="w-9 h-9 rounded-md bg-zinc-800 animate-pulse" />;
}
if (status === 'loading') {
return <div className="h-9 w-9 animate-pulse rounded-md bg-zinc-800" />;
}
return (
<Link
to={`/app/user/${data.pubkey}`}
className="relative inline-block h-9 w-9"
>
<Image
src={user.image}
fallback={DEFAULT_AVATAR}
alt={data.npub}
className="h-9 w-9 rounded-md object-cover"
/>
<NetworkStatusIndicator />
</Link>
);
return (
<Link to={`/app/user/${data.pubkey}`} className="relative inline-block h-9 w-9">
<Image
src={user.image}
fallback={DEFAULT_AVATAR}
alt={data.npub}
className="h-9 w-9 rounded-md object-cover"
/>
<NetworkStatusIndicator />
</Link>
);
}

View File

@@ -1,18 +1,20 @@
import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { Image } from '@shared/image';
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfile } from '@utils/hooks/useProfile';
export function InactiveAccount({ data }: { data: any }) {
const { user } = useProfile(data.npub);
const { user } = useProfile(data.npub);
return (
<div className="relative h-9 w-9 shrink-0">
<Image
src={user?.image}
fallback={DEFAULT_AVATAR}
alt={data.npub}
className="h-9 w-9 rounded object-cover"
/>
</div>
);
return (
<div className="relative h-9 w-9 shrink-0">
<Image
src={user?.image}
fallback={DEFAULT_AVATAR}
alt={data.npub}
className="h-9 w-9 rounded object-cover"
/>
</div>
);
}

View File

@@ -1,48 +1,49 @@
import { ArrowLeftIcon, ArrowRightIcon } from "@shared/icons";
import { useNavigate } from "react-router-dom";
import { useNavigate } from 'react-router-dom';
import { ArrowLeftIcon, ArrowRightIcon } from '@shared/icons';
export function AppHeader({ reverse }: { reverse?: boolean }) {
const navigate = useNavigate();
const navigate = useNavigate();
const goBack = () => {
navigate(-1);
};
const goBack = () => {
navigate(-1);
};
const goForward = () => {
navigate(1);
};
const goForward = () => {
navigate(1);
};
return (
<div
data-tauri-drag-region
className={`shrink-0 flex h-11 w-full px-3 border-b border-zinc-900 items-center ${
reverse ? "justify-start" : "justify-end"
}`}
>
<div className="flex gap-2.5">
<button
type="button"
onClick={() => goBack()}
className="group inline-flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-900"
>
<ArrowLeftIcon
width={16}
height={16}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
<button
type="button"
onClick={() => goForward()}
className="group inline-flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-900"
>
<ArrowRightIcon
width={16}
height={16}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
</div>
</div>
);
return (
<div
data-tauri-drag-region
className={`flex h-11 w-full shrink-0 items-center border-b border-zinc-900 px-3 ${
reverse ? 'justify-start' : 'justify-end'
}`}
>
<div className="flex gap-2.5">
<button
type="button"
onClick={() => goBack()}
className="group inline-flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-900"
>
<ArrowLeftIcon
width={16}
height={16}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
<button
type="button"
onClick={() => goForward()}
className="group inline-flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-900"
>
<ArrowRightIcon
width={16}
height={16}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
</div>
</div>
);
}

View File

@@ -1,16 +1,17 @@
import { Navigation } from "@shared/navigation";
import { Outlet, ScrollRestoration } from "react-router-dom";
import { Outlet, ScrollRestoration } from 'react-router-dom';
import { Navigation } from '@shared/navigation';
export function AppLayout() {
return (
<div className="flex w-screen h-screen">
<div className="relative flex flex-row shrink-0">
<Navigation />
</div>
<div className="w-full h-full">
<Outlet />
<ScrollRestoration />
</div>
</div>
);
return (
<div className="flex h-screen w-screen">
<div className="relative flex shrink-0 flex-row">
<Navigation />
</div>
<div className="h-full w-full">
<Outlet />
<ScrollRestoration />
</div>
</div>
);
}

View File

@@ -1,65 +1,66 @@
import { ArrowLeftIcon, ArrowRightIcon } from "@shared/icons";
import { platform } from "@tauri-apps/api/os";
import { Outlet, useNavigate } from "react-router-dom";
import { platform } from '@tauri-apps/api/os';
import { Outlet, useNavigate } from 'react-router-dom';
import { ArrowLeftIcon, ArrowRightIcon } from '@shared/icons';
const platformName = await platform();
export function AuthLayout() {
const navigate = useNavigate();
const navigate = useNavigate();
const goBack = () => {
navigate(-1);
};
const goBack = () => {
navigate(-1);
};
const goForward = () => {
navigate(1);
};
const goForward = () => {
navigate(1);
};
return (
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100">
<div className="flex h-screen w-full flex-col">
<div
data-tauri-drag-region
className="relative h-11 shrink-0 border border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
>
<div
data-tauri-drag-region
className="flex h-full w-full flex-1 items-center px-2"
>
<div
className={`flex h-full items-center gap-2 ${
platformName === "darwin" ? "pl-[68px]" : ""
}`}
>
<button
type="button"
onClick={() => goBack()}
className="group inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-zinc-900"
>
<ArrowLeftIcon
width={16}
height={16}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
<button
type="button"
onClick={() => goForward()}
className="group inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-zinc-900"
>
<ArrowRightIcon
width={16}
height={16}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
</div>
</div>
</div>
<div className="relative flex min-h-0 w-full flex-1">
<Outlet />
</div>
</div>
</div>
);
return (
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100">
<div className="flex h-screen w-full flex-col">
<div
data-tauri-drag-region
className="relative h-11 shrink-0 border border-zinc-100 bg-white dark:border-zinc-900 dark:bg-black"
>
<div
data-tauri-drag-region
className="flex h-full w-full flex-1 items-center px-2"
>
<div
className={`flex h-full items-center gap-2 ${
platformName === 'darwin' ? 'pl-[68px]' : ''
}`}
>
<button
type="button"
onClick={() => goBack()}
className="group inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-zinc-900"
>
<ArrowLeftIcon
width={16}
height={16}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
<button
type="button"
onClick={() => goForward()}
className="group inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-zinc-900"
>
<ArrowRightIcon
width={16}
height={16}
className="text-zinc-500 group-hover:text-zinc-300"
/>
</button>
</div>
</div>
</div>
<div className="relative flex min-h-0 w-full flex-1">
<Outlet />
</div>
</div>
</div>
);
}

View File

@@ -1,69 +1,71 @@
import { LoaderIcon, PlusIcon } from "@shared/icons";
import { open } from "@tauri-apps/api/dialog";
import { Body, fetch } from "@tauri-apps/api/http";
import { createBlobFromFile } from "@utils/createBlobFromFile";
import { useState } from "react";
import { open } from '@tauri-apps/api/dialog';
import { Body, fetch } from '@tauri-apps/api/http';
import { useState } from 'react';
import { LoaderIcon, PlusIcon } from '@shared/icons';
import { createBlobFromFile } from '@utils/createBlobFromFile';
export function AvatarUploader({ setPicture }: { setPicture: any }) {
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(false);
const openFileDialog = async () => {
const selected: any = await open({
multiple: false,
filters: [
{
name: "Image",
extensions: ["png", "jpeg", "jpg", "gif"],
},
],
});
if (Array.isArray(selected)) {
// user selected multiple files
} else if (selected === null) {
// user cancelled the selection
} else {
setLoading(true);
const openFileDialog = async () => {
const selected: any = await open({
multiple: false,
filters: [
{
name: 'Image',
extensions: ['png', 'jpeg', 'jpg', 'gif'],
},
],
});
if (Array.isArray(selected)) {
// user selected multiple files
} else if (selected === null) {
// user cancelled the selection
} else {
setLoading(true);
const filename = selected.split("/").pop();
const file = await createBlobFromFile(selected);
const buf = await file.arrayBuffer();
const filename = selected.split('/').pop();
const file = await createBlobFromFile(selected);
const buf = await file.arrayBuffer();
const res: { data: { file: { id: string } } } = await fetch(
"https://void.cat/upload?cli=false",
{
method: "POST",
timeout: 5,
headers: {
accept: "*/*",
"Content-Type": "application/octet-stream",
"V-Filename": filename,
"V-Description": "Upload from https://lume.nu",
"V-Strip-Metadata": "true",
},
body: Body.bytes(buf),
},
);
const image = `https://void.cat/d/${res.data.file.id}.jpg`;
const res: { data: { file: { id: string } } } = await fetch(
'https://void.cat/upload?cli=false',
{
method: 'POST',
timeout: 5,
headers: {
accept: '*/*',
'Content-Type': 'application/octet-stream',
'V-Filename': filename,
'V-Description': 'Upload from https://lume.nu',
'V-Strip-Metadata': 'true',
},
body: Body.bytes(buf),
}
);
const image = `https://void.cat/d/${res.data.file.id}.jpg`;
// update parent state
setPicture(image);
// update parent state
setPicture(image);
// disable loader
setLoading(false);
}
};
// disable loader
setLoading(false);
}
};
return (
<button
type="button"
onClick={() => openFileDialog()}
className="w-full h-full inline-flex items-center justify-center bg-zinc-900/40"
>
{loading ? (
<LoaderIcon className="h-6 w-6 animate-spin text-zinc-100" />
) : (
<PlusIcon className="h-6 w-6 text-zinc-100" />
)}
</button>
);
return (
<button
type="button"
onClick={() => openFileDialog()}
className="inline-flex h-full w-full items-center justify-center bg-zinc-900/40"
>
{loading ? (
<LoaderIcon className="h-6 w-6 animate-spin text-zinc-100" />
) : (
<PlusIcon className="h-6 w-6 text-zinc-100" />
)}
</button>
);
}

View File

@@ -1,69 +1,71 @@
import { LoaderIcon, PlusIcon } from "@shared/icons";
import { open } from "@tauri-apps/api/dialog";
import { Body, fetch } from "@tauri-apps/api/http";
import { createBlobFromFile } from "@utils/createBlobFromFile";
import { useState } from "react";
import { open } from '@tauri-apps/api/dialog';
import { Body, fetch } from '@tauri-apps/api/http';
import { useState } from 'react';
import { LoaderIcon, PlusIcon } from '@shared/icons';
import { createBlobFromFile } from '@utils/createBlobFromFile';
export function BannerUploader({ setBanner }: { setBanner: any }) {
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(false);
const openFileDialog = async () => {
const selected: any = await open({
multiple: false,
filters: [
{
name: "Image",
extensions: ["png", "jpeg", "jpg", "gif"],
},
],
});
if (Array.isArray(selected)) {
// user selected multiple files
} else if (selected === null) {
// user cancelled the selection
} else {
setLoading(true);
const openFileDialog = async () => {
const selected: any = await open({
multiple: false,
filters: [
{
name: 'Image',
extensions: ['png', 'jpeg', 'jpg', 'gif'],
},
],
});
if (Array.isArray(selected)) {
// user selected multiple files
} else if (selected === null) {
// user cancelled the selection
} else {
setLoading(true);
const filename = selected.split("/").pop();
const file = await createBlobFromFile(selected);
const buf = await file.arrayBuffer();
const filename = selected.split('/').pop();
const file = await createBlobFromFile(selected);
const buf = await file.arrayBuffer();
const res: { data: { file: { id: string } } } = await fetch(
"https://void.cat/upload?cli=false",
{
method: "POST",
timeout: 5,
headers: {
accept: "*/*",
"Content-Type": "application/octet-stream",
"V-Filename": filename,
"V-Description": "Upload from https://lume.nu",
"V-Strip-Metadata": "true",
},
body: Body.bytes(buf),
},
);
const image = `https://void.cat/d/${res.data.file.id}.jpg`;
const res: { data: { file: { id: string } } } = await fetch(
'https://void.cat/upload?cli=false',
{
method: 'POST',
timeout: 5,
headers: {
accept: '*/*',
'Content-Type': 'application/octet-stream',
'V-Filename': filename,
'V-Description': 'Upload from https://lume.nu',
'V-Strip-Metadata': 'true',
},
body: Body.bytes(buf),
}
);
const image = `https://void.cat/d/${res.data.file.id}.jpg`;
// update parent state
setBanner(image);
// update parent state
setBanner(image);
// disable loader
setLoading(false);
}
};
// disable loader
setLoading(false);
}
};
return (
<button
type="button"
onClick={() => openFileDialog()}
className="w-full h-full inline-flex items-center justify-center bg-zinc-900/40"
>
{loading ? (
<LoaderIcon className="h-8 w-8 animate-spin text-zinc-100" />
) : (
<PlusIcon className="h-8 w-8 text-zinc-100" />
)}
</button>
);
return (
<button
type="button"
onClick={() => openFileDialog()}
className="inline-flex h-full w-full items-center justify-center bg-zinc-900/40"
>
{loading ? (
<LoaderIcon className="h-8 w-8 animate-spin text-zinc-100" />
) : (
<PlusIcon className="h-8 w-8 text-zinc-100" />
)}
</button>
);
}

View File

@@ -1,46 +1,46 @@
import { ReactNode } from "react";
import { twMerge } from "tailwind-merge";
import { ReactNode } from 'react';
import { twMerge } from 'tailwind-merge';
export function Button({
preset,
children,
disabled = false,
onClick = undefined,
preset,
children,
disabled = false,
onClick = undefined,
}: {
preset: "small" | "publish" | "large";
children: ReactNode;
disabled?: boolean;
onClick?: () => void;
preset: 'small' | 'publish' | 'large';
children: ReactNode;
disabled?: boolean;
onClick?: () => void;
}) {
let preClass: string;
switch (preset) {
case "small":
preClass =
"w-min h-9 px-4 bg-fuchsia-500 rounded-md text-sm font-medium text-zinc-100 hover:bg-fuchsia-600";
break;
case "publish":
preClass =
"w-min h-9 px-4 bg-fuchsia-500 rounded-md text-sm font-medium text-zinc-100 hover:bg-fuchsia-600";
break;
case "large":
preClass =
"h-11 w-full bg-fuchsia-500 rounded-md font-medium text-zinc-100 hover:bg-fuchsia-600";
break;
default:
break;
}
let preClass: string;
switch (preset) {
case 'small':
preClass =
'w-min h-9 px-4 bg-fuchsia-500 rounded-md text-sm font-medium text-zinc-100 hover:bg-fuchsia-600';
break;
case 'publish':
preClass =
'w-min h-9 px-4 bg-fuchsia-500 rounded-md text-sm font-medium text-zinc-100 hover:bg-fuchsia-600';
break;
case 'large':
preClass =
'h-11 w-full bg-fuchsia-500 rounded-md font-medium text-zinc-100 hover:bg-fuchsia-600';
break;
default:
break;
}
return (
<button
type="button"
onClick={onClick}
disabled={disabled}
className={twMerge(
"inline-flex items-center justify-center gap-1 transform active:translate-y-1 disabled:pointer-events-none disabled:opacity-50 focus:outline-none",
preClass,
)}
>
{children}
</button>
);
return (
<button
type="button"
onClick={onClick}
disabled={disabled}
className={twMerge(
'inline-flex transform items-center justify-center gap-1 focus:outline-none active:translate-y-1 disabled:pointer-events-none disabled:opacity-50',
preClass
)}
>
{children}
</button>
);
}

View File

@@ -1,132 +1,134 @@
import { PlusCircleIcon } from "@shared/icons";
import { open } from "@tauri-apps/api/dialog";
import { listen } from "@tauri-apps/api/event";
import { Body, fetch } from "@tauri-apps/api/http";
import { createBlobFromFile } from "@utils/createBlobFromFile";
import { useCallback, useEffect, useState } from "react";
import { Transforms } from "slate";
import { useSlateStatic } from "slate-react";
import { open } from '@tauri-apps/api/dialog';
import { listen } from '@tauri-apps/api/event';
import { Body, fetch } from '@tauri-apps/api/http';
import { useCallback, useEffect, useState } from 'react';
import { Transforms } from 'slate';
import { useSlateStatic } from 'slate-react';
import { PlusCircleIcon } from '@shared/icons';
import { createBlobFromFile } from '@utils/createBlobFromFile';
export function ImageUploader() {
const editor = useSlateStatic();
const [loading, setLoading] = useState(false);
const editor = useSlateStatic();
const [loading, setLoading] = useState(false);
const insertImage = (editor, url) => {
const image = { type: "image", url, children: [{ text: url }] };
Transforms.insertNodes(editor, image);
};
const insertImage = (editor, url) => {
const image = { type: 'image', url, children: [{ text: url }] };
Transforms.insertNodes(editor, image);
};
const uploadToVoidCat = useCallback(
async (filepath) => {
const filename = filepath.split("/").pop();
const file = await createBlobFromFile(filepath);
const buf = await file.arrayBuffer();
const uploadToVoidCat = useCallback(
async (filepath) => {
const filename = filepath.split('/').pop();
const file = await createBlobFromFile(filepath);
const buf = await file.arrayBuffer();
try {
const res: { data: { file: { id: string } } } = await fetch(
"https://void.cat/upload?cli=false",
{
method: "POST",
timeout: 5,
headers: {
accept: "*/*",
"Content-Type": "application/octet-stream",
"V-Filename": filename,
"V-Description": "Uploaded from https://lume.nu",
"V-Strip-Metadata": "true",
},
body: Body.bytes(buf),
},
);
const image = `https://void.cat/d/${res.data.file.id}.webp`;
// update parent state
insertImage(editor, image);
// reset loading state
setLoading(false);
} catch (error) {
// reset loading state
setLoading(false);
// handle error
if (error instanceof SyntaxError) {
// Unexpected token < in JSON
console.log("There was a SyntaxError", error);
} else {
console.log("There was an error", error);
}
}
},
[editor],
);
try {
const res: { data: { file: { id: string } } } = await fetch(
'https://void.cat/upload?cli=false',
{
method: 'POST',
timeout: 5,
headers: {
accept: '*/*',
'Content-Type': 'application/octet-stream',
'V-Filename': filename,
'V-Description': 'Uploaded from https://lume.nu',
'V-Strip-Metadata': 'true',
},
body: Body.bytes(buf),
}
);
const image = `https://void.cat/d/${res.data.file.id}.webp`;
// update parent state
insertImage(editor, image);
// reset loading state
setLoading(false);
} catch (error) {
// reset loading state
setLoading(false);
// handle error
if (error instanceof SyntaxError) {
// Unexpected token < in JSON
console.log('There was a SyntaxError', error);
} else {
console.log('There was an error', error);
}
}
},
[editor]
);
const openFileDialog = async () => {
const selected: any = await open({
multiple: false,
filters: [
{
name: "Image",
extensions: ["png", "jpeg", "jpg", "gif"],
},
],
});
if (Array.isArray(selected)) {
// user selected multiple files
} else if (selected === null) {
// user cancelled the selection
} else {
setLoading(true);
// upload file
uploadToVoidCat(selected);
}
};
const openFileDialog = async () => {
const selected: any = await open({
multiple: false,
filters: [
{
name: 'Image',
extensions: ['png', 'jpeg', 'jpg', 'gif'],
},
],
});
if (Array.isArray(selected)) {
// user selected multiple files
} else if (selected === null) {
// user cancelled the selection
} else {
setLoading(true);
// upload file
uploadToVoidCat(selected);
}
};
useEffect(() => {
async function initFileDrop() {
const unlisten = await listen("tauri://file-drop", (event) => {
// set loading state
setLoading(true);
// upload file
uploadToVoidCat(event.payload[0]);
});
useEffect(() => {
async function initFileDrop() {
const unlisten = await listen('tauri://file-drop', (event) => {
// set loading state
setLoading(true);
// upload file
uploadToVoidCat(event.payload[0]);
});
return () => {
unlisten();
};
}
return () => {
unlisten();
};
}
initFileDrop();
}, [uploadToVoidCat]);
initFileDrop();
}, [uploadToVoidCat]);
return (
<button
type="button"
onClick={() => openFileDialog()}
className="inline-flex h-8 w-8 items-center justify-center rounded hover:bg-zinc-800"
>
{loading ? (
<svg
className="h-4 w-4 animate-spin text-black dark:text-zinc-100"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<title id="loading">Loading</title>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
) : (
<PlusCircleIcon width={20} height={20} className="text-zinc-500" />
)}
</button>
);
return (
<button
type="button"
onClick={() => openFileDialog()}
className="inline-flex h-8 w-8 items-center justify-center rounded hover:bg-zinc-800"
>
{loading ? (
<svg
className="h-4 w-4 animate-spin text-black dark:text-zinc-100"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<title id="loading">Loading</title>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
) : (
<PlusCircleIcon width={20} height={20} className="text-zinc-500" />
)}
</button>
);
}

View File

@@ -1,96 +1,94 @@
import { Dialog, Transition } from "@headlessui/react";
import { Button } from "@shared/button";
import { Post } from "@shared/composer/types/post";
import { User } from "@shared/composer/user";
import { Dialog, Transition } from '@headlessui/react';
import { Fragment } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { Button } from '@shared/button';
import { Post } from '@shared/composer/types/post';
import { User } from '@shared/composer/user';
import {
CancelIcon,
ChevronDownIcon,
ChevronRightIcon,
ComposeIcon,
} from "@shared/icons";
import { useComposer } from "@stores/composer";
import { COMPOSE_SHORTCUT } from "@stores/shortcuts";
import { useAccount } from "@utils/hooks/useAccount";
import { Fragment } from "react";
import { useHotkeys } from "react-hotkeys-hook";
CancelIcon,
ChevronDownIcon,
ChevronRightIcon,
ComposeIcon,
} from '@shared/icons';
import { useComposer } from '@stores/composer';
import { COMPOSE_SHORTCUT } from '@stores/shortcuts';
import { useAccount } from '@utils/hooks/useAccount';
export function Composer() {
const { account } = useAccount();
const { account } = useAccount();
const [toggle, open] = useComposer((state) => [
state.toggleModal,
state.open,
]);
const [toggle, open] = useComposer((state) => [state.toggleModal, state.open]);
const closeModal = () => {
toggle(false);
};
const closeModal = () => {
toggle(false);
};
useHotkeys(COMPOSE_SHORTCUT, () => toggle(true));
useHotkeys(COMPOSE_SHORTCUT, () => toggle(true));
return (
<>
<Button onClick={() => toggle(true)} preset="small">
<ComposeIcon width={14} height={14} />
Compose
</Button>
<Transition appear show={open} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative h-min w-full max-w-xl rounded-xl border-t border-zinc-800/50 bg-zinc-900">
<div className="flex items-center justify-between px-4 py-4">
<div className="flex items-center gap-2">
<div>{account && <User pubkey={account.pubkey} />}</div>
<span>
<ChevronRightIcon
width={14}
height={14}
className="text-zinc-500"
/>
</span>
<div className="inline-flex h-7 w-max items-center justify-center gap-0.5 rounded bg-zinc-800 pl-3 pr-1.5 text-sm font-medium text-zinc-400">
New Post
<ChevronDownIcon width={14} height={14} />
</div>
</div>
<div
onClick={closeModal}
onKeyDown={closeModal}
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-800"
>
<CancelIcon
width={16}
height={16}
className="text-zinc-500"
/>
</div>
</div>
{account && <Post />}
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
return (
<>
<Button onClick={() => toggle(true)} preset="small">
<ComposeIcon width={14} height={14} />
Compose
</Button>
<Transition appear show={open} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative h-min w-full max-w-xl rounded-xl border-t border-zinc-800/50 bg-zinc-900">
<div className="flex items-center justify-between px-4 py-4">
<div className="flex items-center gap-2">
<div>{account && <User pubkey={account.pubkey} />}</div>
<span>
<ChevronRightIcon
width={14}
height={14}
className="text-zinc-500"
/>
</span>
<div className="inline-flex h-7 w-max items-center justify-center gap-0.5 rounded bg-zinc-800 pl-3 pr-1.5 text-sm font-medium text-zinc-400">
New Post
<ChevronDownIcon width={14} height={14} />
</div>
</div>
<div
onClick={closeModal}
onKeyDown={closeModal}
role="button"
tabIndex={0}
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-800"
>
<CancelIcon width={16} height={16} className="text-zinc-500" />
</div>
</div>
{account && <Post />}
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
}

View File

@@ -1,179 +1,171 @@
import { usePublish } from "@libs/ndk";
import { Button } from "@shared/button";
import { ImageUploader } from "@shared/composer/imageUploader";
import { TrashIcon } from "@shared/icons";
import { MentionNote } from "@shared/notes/mentions/note";
import { useComposer } from "@stores/composer";
import { FULL_RELAYS } from "@stores/constants";
import { useCallback, useMemo, useState } from "react";
import { Node, Transforms, createEditor } from "slate";
import { withHistory } from "slate-history";
import {
Editable,
ReactEditor,
Slate,
useSlateStatic,
withReact,
} from "slate-react";
import { useCallback, useMemo, useState } from 'react';
import { Node, Transforms, createEditor } from 'slate';
import { withHistory } from 'slate-history';
import { Editable, ReactEditor, Slate, useSlateStatic, withReact } from 'slate-react';
import { usePublish } from '@libs/ndk';
import { Button } from '@shared/button';
import { ImageUploader } from '@shared/composer/imageUploader';
import { TrashIcon } from '@shared/icons';
import { MentionNote } from '@shared/notes/mentions/note';
import { useComposer } from '@stores/composer';
import { FULL_RELAYS } from '@stores/constants';
const withImages = (editor) => {
const { isVoid } = editor;
const { isVoid } = editor;
editor.isVoid = (element) => {
return element.type === "image" ? true : isVoid(element);
};
editor.isVoid = (element) => {
return element.type === 'image' ? true : isVoid(element);
};
return editor;
return editor;
};
const ImagePreview = ({
attributes,
children,
element,
attributes,
children,
element,
}: {
attributes: any;
children: any;
element: any;
attributes: any;
children: any;
element: any;
}) => {
const editor: any = useSlateStatic();
const path = ReactEditor.findPath(editor, element);
const editor: any = useSlateStatic();
const path = ReactEditor.findPath(editor, element);
return (
<figure {...attributes} className="m-0 mt-3">
{children}
<div contentEditable={false} className="relative">
<img
alt={element.url}
src={element.url}
className="m-0 h-auto max-h-[300px] w-full rounded-md object-cover"
/>
<button
type="button"
onClick={() => Transforms.removeNodes(editor, { at: path })}
className="absolute right-2 top-2 inline-flex h-7 w-7 items-center justify-center gap-0.5 rounded bg-zinc-800 text-base font-medium text-zinc-400 shadow-mini-button hover:bg-zinc-700"
>
<TrashIcon width={14} height={14} className="text-zinc-100" />
</button>
</div>
</figure>
);
return (
<figure {...attributes} className="m-0 mt-3">
{children}
<div contentEditable={false} className="relative">
<img
alt={element.url}
src={element.url}
className="m-0 h-auto max-h-[300px] w-full rounded-md object-cover"
/>
<button
type="button"
onClick={() => Transforms.removeNodes(editor, { at: path })}
className="shadow-mini-button absolute right-2 top-2 inline-flex h-7 w-7 items-center justify-center gap-0.5 rounded bg-zinc-800 text-base font-medium text-zinc-400 hover:bg-zinc-700"
>
<TrashIcon width={14} height={14} className="text-zinc-100" />
</button>
</div>
</figure>
);
};
export function Post() {
const publish = usePublish();
const editor = useMemo(
() => withReact(withImages(withHistory(createEditor()))),
[],
);
const publish = usePublish();
const editor = useMemo(() => withReact(withImages(withHistory(createEditor()))), []);
const [repost, reply, toggle] = useComposer((state) => [
state.repost,
state.reply,
state.toggleModal,
]);
const [content, setContent] = useState<Node[]>([
{
children: [
{
text: "",
},
],
},
]);
const [repost, reply, toggle] = useComposer((state) => [
state.repost,
state.reply,
state.toggleModal,
]);
const [content, setContent] = useState<Node[]>([
{
children: [
{
text: '',
},
],
},
]);
const serialize = useCallback((nodes: Node[]) => {
return nodes.map((n) => Node.string(n)).join("\n");
}, []);
const serialize = useCallback((nodes: Node[]) => {
return nodes.map((n) => Node.string(n)).join('\n');
}, []);
const getRef = () => {
if (repost.id) {
return repost.id;
} else if (reply.id) {
return reply.id;
} else {
return null;
}
};
const getRef = () => {
if (repost.id) {
return repost.id;
} else if (reply.id) {
return reply.id;
} else {
return null;
}
};
const refID = getRef();
const refID = getRef();
const submit = async () => {
let tags: string[][] = [];
let kind: number;
const submit = async () => {
let tags: string[][] = [];
let kind: number;
if (repost.id && repost.pubkey) {
kind = 6;
tags = [
["e", repost.id, FULL_RELAYS[0], "root"],
["p", repost.pubkey],
];
} else if (reply.id && reply.pubkey) {
kind = 1;
if (reply.root && reply.root !== reply.id) {
tags = [
["e", reply.id, FULL_RELAYS[0], "root"],
["e", reply.root, FULL_RELAYS[0], "reply"],
["p", reply.pubkey],
];
} else {
tags = [
["e", reply.id, FULL_RELAYS[0], "root"],
["p", reply.pubkey],
];
}
} else {
kind = 1;
tags = [];
}
if (repost.id && repost.pubkey) {
kind = 6;
tags = [
['e', repost.id, FULL_RELAYS[0], 'root'],
['p', repost.pubkey],
];
} else if (reply.id && reply.pubkey) {
kind = 1;
if (reply.root && reply.root !== reply.id) {
tags = [
['e', reply.id, FULL_RELAYS[0], 'root'],
['e', reply.root, FULL_RELAYS[0], 'reply'],
['p', reply.pubkey],
];
} else {
tags = [
['e', reply.id, FULL_RELAYS[0], 'root'],
['p', reply.pubkey],
];
}
} else {
kind = 1;
tags = [];
}
// serialize content
const serializedContent = serialize(content);
// serialize content
const serializedContent = serialize(content);
// publish message
await publish({ content: serializedContent, kind, tags });
// publish message
await publish({ content: serializedContent, kind, tags });
// close modal
toggle(false);
};
// close modal
toggle(false);
};
const renderElement = useCallback((props: any) => {
switch (props.element.type) {
case "image":
if (props.element.url) {
return <ImagePreview {...props} />;
}
default:
return <p {...props.attributes}>{props.children}</p>;
}
}, []);
const renderElement = useCallback((props: any) => {
switch (props.element.type) {
case 'image':
if (props.element.url) {
return <ImagePreview {...props} />;
}
break;
default:
return <p {...props.attributes}>{props.children}</p>;
}
}, []);
return (
<Slate editor={editor} value={content} onChange={setContent}>
<div className="flex h-full flex-col px-4 pb-4">
<div className="flex h-full w-full gap-2">
<div className="flex w-8 shrink-0 items-center justify-center">
<div className="h-full w-[2px] bg-zinc-800" />
</div>
<div className="w-full">
<Editable
autoFocus
placeholder={
refID ? "Share your thoughts on it" : "What's on your mind?"
}
spellCheck="false"
className={`${refID ? "!min-h-42" : "!min-h-[86px]"} markdown`}
renderElement={renderElement}
/>
{refID && <MentionNote id={refID} />}
</div>
</div>
<div className="mt-4 flex items-center justify-between">
<ImageUploader />
<Button onClick={() => submit()} preset="publish">
Publish
</Button>
</div>
</div>
</Slate>
);
return (
<Slate editor={editor} value={content} onChange={setContent}>
<div className="flex h-full flex-col px-4 pb-4">
<div className="flex h-full w-full gap-2">
<div className="flex w-8 shrink-0 items-center justify-center">
<div className="h-full w-[2px] bg-zinc-800" />
</div>
<div className="w-full">
<Editable
placeholder={refID ? 'Share your thoughts on it' : "What's on your mind?"}
spellCheck="false"
className={`${refID ? '!min-h-42' : '!min-h-[86px]'} markdown`}
renderElement={renderElement}
/>
{refID && <MentionNote id={refID} />}
</div>
</div>
<div className="mt-4 flex items-center justify-between">
<ImageUploader />
<Button onClick={() => submit()} preset="publish">
Publish
</Button>
</div>
</div>
</Slate>
);
}

View File

@@ -1,25 +1,27 @@
import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { Image } from '@shared/image';
import { DEFAULT_AVATAR } from '@stores/constants';
import { useProfile } from '@utils/hooks/useProfile';
export function User({ pubkey }: { pubkey: string }) {
const { user } = useProfile(pubkey);
const { user } = useProfile(pubkey);
return (
<div className="flex items-center gap-2">
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
<Image
src={user?.image}
fallback={DEFAULT_AVATAR}
alt={pubkey}
className="h-8 w-8 object-cover"
/>
</div>
<h5 className="text-base font-semibold leading-none text-zinc-100">
{user?.nip05 || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
</h5>
</div>
);
return (
<div className="flex items-center gap-2">
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
<Image
src={user?.image}
fallback={DEFAULT_AVATAR}
alt={pubkey}
className="h-8 w-8 object-cover"
/>
</div>
<h5 className="text-base font-semibold leading-none text-zinc-100">
{user?.nip05 || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
</h5>
</div>
);
}

View File

@@ -1,328 +1,339 @@
import { Dialog, Transition } from "@headlessui/react";
import { usePublish } from "@libs/ndk";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { AvatarUploader } from "@shared/avatarUploader";
import { BannerUploader } from "@shared/bannerUploader";
import {
CancelIcon,
CheckCircleIcon,
LoaderIcon,
UnverifiedIcon,
} from "@shared/icons";
import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useQueryClient } from "@tanstack/react-query";
import { fetch } from "@tauri-apps/api/http";
import { useAccount } from "@utils/hooks/useAccount";
import { Fragment, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { Dialog, Transition } from '@headlessui/react';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useQueryClient } from '@tanstack/react-query';
import { fetch } from '@tauri-apps/api/http';
import { Fragment, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { usePublish } from '@libs/ndk';
import { AvatarUploader } from '@shared/avatarUploader';
import { BannerUploader } from '@shared/bannerUploader';
import { CancelIcon, CheckCircleIcon, LoaderIcon, UnverifiedIcon } from '@shared/icons';
import { Image } from '@shared/image';
import { DEFAULT_AVATAR } from '@stores/constants';
import { useAccount } from '@utils/hooks/useAccount';
export function EditProfileModal() {
const queryClient = useQueryClient();
const publish = usePublish();
const queryClient = useQueryClient();
const publish = usePublish();
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [picture, setPicture] = useState(DEFAULT_AVATAR);
const [banner, setBanner] = useState("");
const [nip05, setNIP05] = useState({ verified: false, text: "" });
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [picture, setPicture] = useState(DEFAULT_AVATAR);
const [banner, setBanner] = useState('');
const [nip05, setNIP05] = useState({ verified: false, text: '' });
const { account } = useAccount();
const {
register,
handleSubmit,
reset,
setError,
formState: { isValid, errors },
} = useForm({
defaultValues: async () => {
const res: any = queryClient.getQueryData(["user", account.pubkey]);
if (res.image) {
setPicture(res.image);
}
if (res.banner) {
setBanner(res.banner);
}
if (res.nip05) {
setNIP05((prev) => ({ ...prev, text: res.nip05 }));
}
return res;
},
});
const { account } = useAccount();
const {
register,
handleSubmit,
reset,
setError,
formState: { isValid, errors },
} = useForm({
defaultValues: async () => {
const res: any = queryClient.getQueryData(['user', account.pubkey]);
if (res.image) {
setPicture(res.image);
}
if (res.banner) {
setBanner(res.banner);
}
if (res.nip05) {
setNIP05((prev) => ({ ...prev, text: res.nip05 }));
}
return res;
},
});
const closeModal = () => {
setIsOpen(false);
};
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
const openModal = () => {
setIsOpen(true);
};
const verifyNIP05 = async (data: string) => {
if (data) {
const url = data.split("@");
const username = url[0];
const service = url[1];
const verifyURL = `https://${service}/.well-known/nostr.json?name=${username}`;
const verifyNIP05 = async (data: string) => {
if (data) {
const url = data.split('@');
const username = url[0];
const service = url[1];
const verifyURL = `https://${service}/.well-known/nostr.json?name=${username}`;
const res: any = await fetch(verifyURL, {
method: "GET",
timeout: 30,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
});
const res: any = await fetch(verifyURL, {
method: 'GET',
timeout: 30,
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
});
if (!res.ok) return false;
if (res.data.names[username] === account.pubkey) {
setNIP05((prev) => ({ ...prev, verified: true }));
return true;
} else {
return false;
}
}
};
if (!res.ok) return false;
if (res.data.names[username] === account.pubkey) {
setNIP05((prev) => ({ ...prev, verified: true }));
return true;
} else {
return false;
}
}
};
const onSubmit = async (data: any) => {
// start loading
setLoading(true);
const onSubmit = async (data: any) => {
// start loading
setLoading(true);
let event: NDKEvent;
let event: NDKEvent;
const content = {
...data,
username: data.name,
display_name: data.name,
bio: data.about,
image: data.picture,
};
const content = {
...data,
username: data.name,
display_name: data.name,
bio: data.about,
image: data.picture,
};
if (data.nip05) {
const verify = await verifyNIP05(data.nip05);
if (verify) {
event = await publish({
content: JSON.stringify({ ...content, nip05: data.nip05 }),
kind: 0,
tags: [],
});
} else {
setNIP05((prev) => ({ ...prev, verified: false }));
setError("nip05", {
type: "manual",
message: "Can't verify your Lume ID / NIP-05, please check again",
});
}
} else {
event = await publish({
content: JSON.stringify(content),
kind: 0,
tags: [],
});
}
if (data.nip05) {
const verify = await verifyNIP05(data.nip05);
if (verify) {
event = await publish({
content: JSON.stringify({ ...content, nip05: data.nip05 }),
kind: 0,
tags: [],
});
} else {
setNIP05((prev) => ({ ...prev, verified: false }));
setError('nip05', {
type: 'manual',
message: "Can't verify your Lume ID / NIP-05, please check again",
});
}
} else {
event = await publish({
content: JSON.stringify(content),
kind: 0,
tags: [],
});
}
if (event.id) {
setTimeout(() => {
// invalid cache
queryClient.invalidateQueries(["user", account.pubkey]);
// reset form
reset();
// reset state
setLoading(false);
setIsOpen(false);
setPicture(DEFAULT_AVATAR);
setBanner(null);
}, 1200);
} else {
setLoading(false);
}
};
if (event.id) {
setTimeout(() => {
// invalid cache
queryClient.invalidateQueries(['user', account.pubkey]);
// reset form
reset();
// reset state
setLoading(false);
setIsOpen(false);
setPicture(DEFAULT_AVATAR);
setBanner(null);
}, 1200);
} else {
setLoading(false);
}
};
useEffect(() => {
if (!nip05.verified && /\S+@\S+\.\S+/.test(nip05.text)) {
verifyNIP05(nip05.text);
}
}, [nip05.text]);
useEffect(() => {
if (!nip05.verified && /\S+@\S+\.\S+/.test(nip05.text)) {
verifyNIP05(nip05.text);
}
}, [nip05.text]);
return (
<>
<button
type="button"
onClick={() => openModal()}
className="inline-flex w-36 h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500 text-sm font-medium"
>
Edit profile
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col rounded-lg border-t border-zinc-800/50 bg-zinc-900">
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
<div className="flex items-center justify-between">
<Dialog.Title
as="h3"
className="text-lg font-semibold leading-none text-zinc-100"
>
Edit profile
</Dialog.Title>
<button
type="button"
onClick={closeModal}
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<CancelIcon className="w-5 h-5 text-zinc-300" />
</button>
</div>
</div>
<div className="flex h-full w-full flex-col overflow-y-auto">
<form onSubmit={handleSubmit(onSubmit)} className="mb-0">
<input
type={"hidden"}
{...register("picture")}
value={picture}
className="relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
/>
<input
type={"hidden"}
{...register("banner")}
value={banner}
className="relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
/>
<div className="relative">
<div className="relative w-full h-44 bg-zinc-800">
<Image
src={banner}
fallback="https://void.cat/d/QY1myro5tkHVs2nY7dy74b.jpg"
alt="user's banner"
className="h-full w-full object-cover"
/>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-10 w-full h-full">
<BannerUploader setBanner={setBanner} />
</div>
</div>
<div className="px-4 mb-5">
<div className="z-10 relative h-14 w-14 -mt-7">
<Image
src={picture}
fallback={DEFAULT_AVATAR}
alt="user's avatar"
className="h-14 w-14 object-cover ring-2 ring-zinc-900 rounded-lg"
/>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-10 w-full h-full">
<AvatarUploader setPicture={setPicture} />
</div>
</div>
</div>
</div>
<div className="flex flex-col gap-4 px-4 pb-4">
<div className="flex flex-col gap-1">
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
Name
</label>
<input
type={"text"}
{...register("name", {
required: true,
minLength: 4,
})}
spellCheck={false}
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
Lume ID / NIP-05
</label>
<div className="relative">
<input
{...register("nip05", {
required: true,
minLength: 4,
})}
spellCheck={false}
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
/>
<div className="absolute top-1/2 right-2 transform -translate-y-1/2">
{nip05.verified ? (
<span className="inline-flex items-center gap-1 rounded h-6 px-2 bg-green-500 text-sm font-medium">
<CheckCircleIcon className="w-4 h-4 text-white" />
Verified
</span>
) : (
<span className="inline-flex items-center gap-1 rounded h-6 px-2 bg-red-500 text-sm font-medium">
<UnverifiedIcon className="w-4 h-4 text-white" />
Unverified
</span>
)}
</div>
{errors.nip05 && (
<p className="mt-1 text-sm text-red-400">
{errors.nip05.message.toString()}
</p>
)}
</div>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
Bio
</label>
<textarea
{...register("about")}
spellCheck={false}
className="relative resize-none h-20 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
Website
</label>
<input
type={"text"}
{...register("website", { required: false })}
spellCheck={false}
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
/>
</div>
<div>
<button
type="submit"
disabled={!isValid}
className="inline-flex items-center justify-center gap-1 transform active:translate-y-1 disabled:pointer-events-none disabled:opacity-50 focus:outline-none h-11 w-full bg-fuchsia-500 rounded-md font-medium text-zinc-100 hover:bg-fuchsia-600"
>
{loading ? (
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
) : (
"Update"
)}
</button>
</div>
</div>
</form>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
return (
<>
<button
type="button"
onClick={() => openModal()}
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-zinc-900 text-sm font-medium hover:bg-fuchsia-500"
>
Edit profile
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col rounded-lg border-t border-zinc-800/50 bg-zinc-900">
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
<div className="flex items-center justify-between">
<Dialog.Title
as="h3"
className="text-lg font-semibold leading-none text-zinc-100"
>
Edit profile
</Dialog.Title>
<button
type="button"
onClick={closeModal}
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<CancelIcon className="h-5 w-5 text-zinc-300" />
</button>
</div>
</div>
<div className="flex h-full w-full flex-col overflow-y-auto">
<form onSubmit={handleSubmit(onSubmit)} className="mb-0">
<input
type={'hidden'}
{...register('picture')}
value={picture}
className="shadow-input relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
/>
<input
type={'hidden'}
{...register('banner')}
value={banner}
className="shadow-input relative h-10 w-full rounded-lg border border-black/5 px-3 py-2 shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-100 dark:shadow-black/10 dark:placeholder:text-zinc-500"
/>
<div className="relative">
<div className="relative h-44 w-full bg-zinc-800">
<Image
src={banner}
fallback="https://void.cat/d/QY1myro5tkHVs2nY7dy74b.jpg"
alt="user's banner"
className="h-full w-full object-cover"
/>
<div className="absolute left-1/2 top-1/2 z-10 h-full w-full -translate-x-1/2 -translate-y-1/2 transform">
<BannerUploader setBanner={setBanner} />
</div>
</div>
<div className="mb-5 px-4">
<div className="relative z-10 -mt-7 h-14 w-14">
<Image
src={picture}
fallback={DEFAULT_AVATAR}
alt="user's avatar"
className="h-14 w-14 rounded-lg object-cover ring-2 ring-zinc-900"
/>
<div className="absolute left-1/2 top-1/2 z-10 h-full w-full -translate-x-1/2 -translate-y-1/2 transform">
<AvatarUploader setPicture={setPicture} />
</div>
</div>
</div>
</div>
<div className="flex flex-col gap-4 px-4 pb-4">
<div className="flex flex-col gap-1">
<label
htmlFor="name"
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
>
Name
</label>
<input
type={'text'}
{...register('name', {
required: true,
minLength: 4,
})}
spellCheck={false}
className="relative h-10 w-full rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
/>
</div>
<div className="flex flex-col gap-1">
<label
htmlFor="nip05"
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
>
Lume ID / NIP-05
</label>
<div className="relative">
<input
{...register('nip05', {
required: true,
minLength: 4,
})}
spellCheck={false}
className="relative h-10 w-full rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
/>
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform">
{nip05.verified ? (
<span className="inline-flex h-6 items-center gap-1 rounded bg-green-500 px-2 text-sm font-medium">
<CheckCircleIcon className="h-4 w-4 text-white" />
Verified
</span>
) : (
<span className="inline-flex h-6 items-center gap-1 rounded bg-red-500 px-2 text-sm font-medium">
<UnverifiedIcon className="h-4 w-4 text-white" />
Unverified
</span>
)}
</div>
{errors.nip05 && (
<p className="mt-1 text-sm text-red-400">
{errors.nip05.message.toString()}
</p>
)}
</div>
</div>
<div className="flex flex-col gap-1">
<label
htmlFor="about"
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
>
Bio
</label>
<textarea
{...register('about')}
spellCheck={false}
className="relative h-20 w-full resize-none rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
/>
</div>
<div className="flex flex-col gap-1">
<label
htmlFor="website"
className="text-sm font-semibold uppercase tracking-wider text-zinc-400"
>
Website
</label>
<input
type={'text'}
{...register('website', { required: false })}
spellCheck={false}
className="relative h-10 w-full rounded-lg bg-zinc-800 px-3 py-2 text-zinc-100 !outline-none placeholder:text-zinc-500"
/>
</div>
<div>
<button
type="submit"
disabled={!isValid}
className="inline-flex h-11 w-full transform items-center justify-center gap-1 rounded-md bg-fuchsia-500 font-medium text-zinc-100 hover:bg-fuchsia-600 focus:outline-none active:translate-y-1 disabled:pointer-events-none disabled:opacity-50"
>
{loading ? (
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
) : (
'Update'
)}
</button>
</div>
</div>
</form>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
}

View File

@@ -1,22 +1,15 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ArrowLeftIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M10 18.25L3.75 12M3.75 12L10 5.75M3.75 12H20.25"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function ArrowLeftIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M10 18.25L3.75 12M3.75 12L10 5.75M3.75 12H20.25"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,22 +1,15 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ArrowRightIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M14 5.75L20.25 12M20.25 12L14 18.25M20.25 12H3.75"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function ArrowRightIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M14 5.75L20.25 12M20.25 12L14 18.25M20.25 12H3.75"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,24 +1,24 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ArrowRightCircleIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M7.75 12h8M13 8.75l2.896 2.896a.5.5 0 010 .708L13 15.25M21.25 12a9.25 9.25 0 11-18.5 0 9.25 9.25 0 0118.5 0z"
/>
</svg>
);
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M7.75 12h8M13 8.75l2.896 2.896a.5.5 0 010 .708L13 15.25M21.25 12a9.25 9.25 0 11-18.5 0 9.25 9.25 0 0118.5 0z"
/>
</svg>
);
}

View File

@@ -1,22 +1,20 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function BellIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M16 18.25C15.3267 20.0159 13.7891 21.25 12 21.25C10.2109 21.25 8.67327 20.0159 8 18.25M20.5 18.25L18.9554 8.67345C18.4048 5.2596 15.458 2.75 12 2.75C8.54203 2.75 5.59523 5.2596 5.04461 8.67345L3.5 18.25H20.5Z"
stroke="currentColor"
strokeWidth={1.5}
/>
</svg>
);
export function BellIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M16 18.25C15.3267 20.0159 13.7891 21.25 12 21.25C10.2109 21.25 8.67327 20.0159 8 18.25M20.5 18.25L18.9554 8.67345C18.4048 5.2596 15.458 2.75 12 2.75C8.54203 2.75 5.59523 5.2596 5.04461 8.67345L3.5 18.25H20.5Z"
stroke="currentColor"
strokeWidth={1.5}
/>
</svg>
);
}

View File

@@ -1,21 +1,14 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function CancelIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M4.75 4.75L19.25 19.25M19.25 4.75L4.75 19.25"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
/>
</svg>
);
export function CancelIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M4.75 4.75L19.25 19.25M19.25 4.75L4.75 19.25"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
/>
</svg>
);
}

View File

@@ -1,23 +1,23 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function CheckCircleIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM15.5805 9.97493C15.8428 9.65434 15.7955 9.18183 15.4749 8.91953C15.1543 8.65724 14.6818 8.70449 14.4195 9.02507L10.4443 13.8837L9.03033 12.4697C8.73744 12.1768 8.26256 12.1768 7.96967 12.4697C7.67678 12.7626 7.67678 13.2374 7.96967 13.5303L9.96967 15.5303C10.1195 15.6802 10.3257 15.7596 10.5374 15.7491C10.749 15.7385 10.9463 15.6389 11.0805 15.4749L15.5805 9.97493Z"
fill="currentColor"
/>
</svg>
);
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM15.5805 9.97493C15.8428 9.65434 15.7955 9.18183 15.4749 8.91953C15.1543 8.65724 14.6818 8.70449 14.4195 9.02507L10.4443 13.8837L9.03033 12.4697C8.73744 12.1768 8.26256 12.1768 7.96967 12.4697C7.67678 12.7626 7.67678 13.2374 7.96967 13.5303L9.96967 15.5303C10.1195 15.6802 10.3257 15.7596 10.5374 15.7491C10.749 15.7385 10.9463 15.6389 11.0805 15.4749L15.5805 9.97493Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,24 +1,24 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ChevronDownIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M8 10L12 14L16 10"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M8 10L12 14L16 10"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,24 +1,24 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ChevronRightIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M10 16L14 12L10 8"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M10 16L14 12L10 8"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,23 +1,21 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function CommandIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="square"
strokeWidth="1.5"
d="M9.25 9.25V6.5A2.75 2.75 0 106.5 9.25h2.75zm0 0h5.5m-5.5 0v5.5m5.5-5.5V6.5a2.75 2.75 0 112.75 2.75h-2.75zm0 0v5.5m0 0h-5.5m5.5 0v2.75a2.75 2.75 0 102.75-2.75h-2.75zm-5.5 0v2.75a2.75 2.75 0 11-2.75-2.75h2.75z"
/>
</svg>
);
export function CommandIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="square"
strokeWidth="1.5"
d="M9.25 9.25V6.5A2.75 2.75 0 106.5 9.25h2.75zm0 0h5.5m-5.5 0v5.5m5.5-5.5V6.5a2.75 2.75 0 112.75 2.75h-2.75zm0 0v5.5m0 0h-5.5m5.5 0v2.75a2.75 2.75 0 102.75-2.75h-2.75zm-5.5 0v2.75a2.75 2.75 0 11-2.75-2.75h2.75z"
/>
</svg>
);
}

View File

@@ -1,21 +1,19 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ComposeIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12.75 8.25L12.2197 7.71967C12.079 7.86032 12 8.05109 12 8.25H12.75ZM12.75 11.25H12C12 11.6642 12.3358 12 12.75 12V11.25ZM15.75 11.25V12C15.9489 12 16.1397 11.921 16.2803 11.7803L15.75 11.25ZM18.75 2.25L19.2803 1.71967C18.9874 1.42678 18.5126 1.42678 18.2197 1.71967L18.75 2.25ZM21.75 5.25L22.2803 5.78033C22.5732 5.48744 22.5732 5.01256 22.2803 4.71967L21.75 5.25ZM20.25 20.25V21C20.6642 21 21 20.6642 21 20.25H20.25ZM3.75 20.25H3C3 20.6642 3.33579 21 3.75 21V20.25ZM3.75 3.75V3C3.33579 3 3 3.33579 3 3.75H3.75ZM11.25 4.5C11.6642 4.5 12 4.16421 12 3.75C12 3.33579 11.6642 3 11.25 3V4.5ZM21 12.75C21 12.3358 20.6642 12 20.25 12C19.8358 12 19.5 12.3358 19.5 12.75H21ZM12 8.25V11.25H13.5V8.25H12ZM12.75 12H15.75V10.5H12.75V12ZM13.2803 8.78033L19.2803 2.78033L18.2197 1.71967L12.2197 7.71967L13.2803 8.78033ZM18.2197 2.78033L21.2197 5.78033L22.2803 4.71967L19.2803 1.71967L18.2197 2.78033ZM21.2197 4.71967L15.2197 10.7197L16.2803 11.7803L22.2803 5.78033L21.2197 4.71967ZM20.25 19.5H3.75V21H20.25V19.5ZM4.5 20.25V3.75H3V20.25H4.5ZM3.75 4.5H11.25V3H3.75V4.5ZM19.5 12.75V20.25H21V12.75H19.5Z"
fill="currentColor"
/>
</svg>
);
export function ComposeIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12.75 8.25L12.2197 7.71967C12.079 7.86032 12 8.05109 12 8.25H12.75ZM12.75 11.25H12C12 11.6642 12.3358 12 12.75 12V11.25ZM15.75 11.25V12C15.9489 12 16.1397 11.921 16.2803 11.7803L15.75 11.25ZM18.75 2.25L19.2803 1.71967C18.9874 1.42678 18.5126 1.42678 18.2197 1.71967L18.75 2.25ZM21.75 5.25L22.2803 5.78033C22.5732 5.48744 22.5732 5.01256 22.2803 4.71967L21.75 5.25ZM20.25 20.25V21C20.6642 21 21 20.6642 21 20.25H20.25ZM3.75 20.25H3C3 20.6642 3.33579 21 3.75 21V20.25ZM3.75 3.75V3C3.33579 3 3 3.33579 3 3.75H3.75ZM11.25 4.5C11.6642 4.5 12 4.16421 12 3.75C12 3.33579 11.6642 3 11.25 3V4.5ZM21 12.75C21 12.3358 20.6642 12 20.25 12C19.8358 12 19.5 12.3358 19.5 12.75H21ZM12 8.25V11.25H13.5V8.25H12ZM12.75 12H15.75V10.5H12.75V12ZM13.2803 8.78033L19.2803 2.78033L18.2197 1.71967L12.2197 7.71967L13.2803 8.78033ZM18.2197 2.78033L21.2197 5.78033L22.2803 4.71967L19.2803 1.71967L18.2197 2.78033ZM21.2197 4.71967L15.2197 10.7197L16.2803 11.7803L22.2803 5.78033L21.2197 4.71967ZM20.25 19.5H3.75V21H20.25V19.5ZM4.5 20.25V3.75H3V20.25H4.5ZM3.75 4.5H11.25V3H3.75V4.5ZM19.5 12.75V20.25H21V12.75H19.5Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function CopyIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M15.25 15.25V21.25H2.75V8.75H8.75M8.75 15.25H21.25V2.75H8.75V15.25Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function CopyIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M15.25 15.25V21.25H2.75V8.75H8.75M8.75 15.25H21.25V2.75H8.75V15.25Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function EditIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M13.25 6.25L17 2.5L21.5 7L17.75 10.75M13.25 6.25L2.75 16.75V21.25H7.25L17.75 10.75M13.25 6.25L17.75 10.75"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function EditIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M13.25 6.25L17 2.5L21.5 7L17.75 10.75M13.25 6.25L2.75 16.75V21.25H7.25L17.75 10.75M13.25 6.25L17.75 10.75"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,66 +1,61 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function EmptyIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="120"
height="120"
fill="none"
viewBox="0 0 120 120"
{...props}
>
<g clipPath="url(#clip0_110_63)">
<path
fill="#27272A"
fillRule="evenodd"
d="M60 120c33.137 0 60-26.863 60-60S93.137 0 60 0C45.133 0 39.482 17.832 29 26.787 16.119 37.792 0 41.73 0 60c0 33.137 26.863 60 60 60z"
clipRule="evenodd"
/>
<g filter="url(#filter0_f_110_63)">
<path
fill="#18181B"
fillRule="evenodd"
d="M64 101c19.33 0 35-13.208 35-29.5S83.33 42 64 42c-8.672 0-11.969 8.767-18.083 13.17C38.403 60.58 29 62.517 29 71.5 29 87.792 44.67 101 64 101z"
clipRule="evenodd"
/>
</g>
<path
fill="#3F3F46"
fillRule="evenodd"
d="M82.941 59H65.06C59.504 59 55 63.476 55 68.997v4.871c0 5.521 4.504 9.997 10.059 9.997h18.879l5.779 4.685a2.02 2.02 0 002.83-.286c.293-.356.453-.803.453-1.263V68.997C93 63.476 88.496 59 82.941 59z"
clipRule="evenodd"
/>
<path
fill="#D4D4D8"
fillRule="evenodd"
d="M41.161 39h32.678C81.659 39 88 45.408 88 53.314v12.864c0 7.905-6.34 14.314-14.161 14.314H41.547l-9.186 7.742a3.244 3.244 0 01-4.603-.422A3.325 3.325 0 0127 85.697V53.314C27 45.408 33.34 39 41.161 39z"
clipRule="evenodd"
/>
</g>
<defs>
<filter
id="filter0_f_110_63"
width="92"
height="81"
x="18"
y="31"
colorInterpolationFilters="sRGB"
filterUnits="userSpaceOnUse"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur
result="effect1_foregroundBlur_110_63"
stdDeviation="5.5"
/>
</filter>
<clipPath id="clip0_110_63">
<path fill="#fff" d="M0 0H120V120H0z" />
</clipPath>
</defs>
</svg>
);
export function EmptyIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="120"
height="120"
fill="none"
viewBox="0 0 120 120"
{...props}
>
<g clipPath="url(#clip0_110_63)">
<path
fill="#27272A"
fillRule="evenodd"
d="M60 120c33.137 0 60-26.863 60-60S93.137 0 60 0C45.133 0 39.482 17.832 29 26.787 16.119 37.792 0 41.73 0 60c0 33.137 26.863 60 60 60z"
clipRule="evenodd"
/>
<g filter="url(#filter0_f_110_63)">
<path
fill="#18181B"
fillRule="evenodd"
d="M64 101c19.33 0 35-13.208 35-29.5S83.33 42 64 42c-8.672 0-11.969 8.767-18.083 13.17C38.403 60.58 29 62.517 29 71.5 29 87.792 44.67 101 64 101z"
clipRule="evenodd"
/>
</g>
<path
fill="#3F3F46"
fillRule="evenodd"
d="M82.941 59H65.06C59.504 59 55 63.476 55 68.997v4.871c0 5.521 4.504 9.997 10.059 9.997h18.879l5.779 4.685a2.02 2.02 0 002.83-.286c.293-.356.453-.803.453-1.263V68.997C93 63.476 88.496 59 82.941 59z"
clipRule="evenodd"
/>
<path
fill="#D4D4D8"
fillRule="evenodd"
d="M41.161 39h32.678C81.659 39 88 45.408 88 53.314v12.864c0 7.905-6.34 14.314-14.161 14.314H41.547l-9.186 7.742a3.244 3.244 0 01-4.603-.422A3.325 3.325 0 0127 85.697V53.314C27 45.408 33.34 39 41.161 39z"
clipRule="evenodd"
/>
</g>
<defs>
<filter
id="filter0_f_110_63"
width="92"
height="81"
x="18"
y="31"
colorInterpolationFilters="sRGB"
filterUnits="userSpaceOnUse"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur result="effect1_foregroundBlur_110_63" stdDeviation="5.5" />
</filter>
<clipPath id="clip0_110_63">
<path fill="#fff" d="M0 0H120V120H0z" />
</clipPath>
</defs>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function EnterIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M3.75 4.75V15H20.25M20.25 15L16.25 11M20.25 15L16.25 19"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function EnterIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M3.75 4.75V15H20.25M20.25 15L16.25 11M20.25 15L16.25 19"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,21 +1,19 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function EyeOffIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M9.1654 4.42071C8.76876 4.5401 8.544 4.95841 8.66339 5.35505C8.78277 5.75169 9.20109 5.97645 9.59772 5.85706L9.1654 4.42071ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM19.1413 14.9666C18.8678 15.2776 18.8982 15.7515 19.2092 16.0251C19.5203 16.2986 19.9942 16.2682 20.2677 15.9571L19.1413 14.9666ZM3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L3.28033 2.21967ZM2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM17.4703 17.4703L18.0006 16.9399L17.4703 17.4703ZM20.7197 21.7803C21.0126 22.0732 21.4874 22.0732 21.7803 21.7803C22.0732 21.4874 22.0732 21.0126 21.7803 20.7197L20.7197 21.7803ZM10.2322 10.2322C10.5251 9.93934 10.5251 9.46447 10.2322 9.17157C9.93934 8.87868 9.46447 8.87868 9.17157 9.17157L10.2322 10.2322ZM14.8284 14.8284C15.1213 14.5355 15.1213 14.0607 14.8284 13.7678C14.5355 13.4749 14.0607 13.4749 13.7678 13.7678L14.8284 14.8284ZM9.59772 5.85706C13.745 4.60878 18.4769 6.624 21.329 12.3351L22.671 11.6649C19.5775 5.47055 14.1791 2.91165 9.1654 4.42071L9.59772 5.85706ZM20.2677 15.9571C21.1654 14.9364 21.9755 13.7277 22.671 12.3351L21.329 11.6649C20.6865 12.9515 19.9468 14.0507 19.1413 14.9666L20.2677 15.9571ZM2.21967 3.28033L5.99937 7.06003L7.06003 5.99937L3.28033 2.21967L2.21967 3.28033ZM2.67098 12.335C3.84083 9.99245 5.33197 8.27257 6.95699 7.14609L6.10242 5.91332C4.24158 7.20327 2.5948 9.13019 1.32902 11.6648L2.67098 12.335ZM5.99937 7.06003L16.9399 18.0006L18.0006 16.9399L7.06003 5.99937L5.99937 7.06003ZM16.9399 18.0006L20.7197 21.7803L21.7803 20.7197L18.0006 16.9399L16.9399 18.0006ZM1.32902 12.335C3.20469 16.0909 5.92036 18.5148 8.91701 19.5009C11.922 20.4898 15.1308 20.0045 17.8975 18.0866L17.043 16.8539C14.6436 18.5171 11.9221 18.9107 9.38589 18.0761C6.84135 17.2388 4.40494 15.1369 2.67098 11.6648L1.32902 12.335ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 11.3094 9.779 10.6855 10.2322 10.2322L9.17157 9.17157C8.44854 9.89461 8 10.8956 8 12H9.5ZM13.7678 13.7678C13.3145 14.221 12.6906 14.5 12 14.5V16C13.1044 16 14.1054 15.5515 14.8284 14.8284L13.7678 13.7678Z"
fill="currentColor"
/>
</svg>
);
export function EyeOffIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M9.1654 4.42071C8.76876 4.5401 8.544 4.95841 8.66339 5.35505C8.78277 5.75169 9.20109 5.97645 9.59772 5.85706L9.1654 4.42071ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM19.1413 14.9666C18.8678 15.2776 18.8982 15.7515 19.2092 16.0251C19.5203 16.2986 19.9942 16.2682 20.2677 15.9571L19.1413 14.9666ZM3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L3.28033 2.21967ZM2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM17.4703 17.4703L18.0006 16.9399L17.4703 17.4703ZM20.7197 21.7803C21.0126 22.0732 21.4874 22.0732 21.7803 21.7803C22.0732 21.4874 22.0732 21.0126 21.7803 20.7197L20.7197 21.7803ZM10.2322 10.2322C10.5251 9.93934 10.5251 9.46447 10.2322 9.17157C9.93934 8.87868 9.46447 8.87868 9.17157 9.17157L10.2322 10.2322ZM14.8284 14.8284C15.1213 14.5355 15.1213 14.0607 14.8284 13.7678C14.5355 13.4749 14.0607 13.4749 13.7678 13.7678L14.8284 14.8284ZM9.59772 5.85706C13.745 4.60878 18.4769 6.624 21.329 12.3351L22.671 11.6649C19.5775 5.47055 14.1791 2.91165 9.1654 4.42071L9.59772 5.85706ZM20.2677 15.9571C21.1654 14.9364 21.9755 13.7277 22.671 12.3351L21.329 11.6649C20.6865 12.9515 19.9468 14.0507 19.1413 14.9666L20.2677 15.9571ZM2.21967 3.28033L5.99937 7.06003L7.06003 5.99937L3.28033 2.21967L2.21967 3.28033ZM2.67098 12.335C3.84083 9.99245 5.33197 8.27257 6.95699 7.14609L6.10242 5.91332C4.24158 7.20327 2.5948 9.13019 1.32902 11.6648L2.67098 12.335ZM5.99937 7.06003L16.9399 18.0006L18.0006 16.9399L7.06003 5.99937L5.99937 7.06003ZM16.9399 18.0006L20.7197 21.7803L21.7803 20.7197L18.0006 16.9399L16.9399 18.0006ZM1.32902 12.335C3.20469 16.0909 5.92036 18.5148 8.91701 19.5009C11.922 20.4898 15.1308 20.0045 17.8975 18.0866L17.043 16.8539C14.6436 18.5171 11.9221 18.9107 9.38589 18.0761C6.84135 17.2388 4.40494 15.1369 2.67098 11.6648L1.32902 12.335ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 11.3094 9.779 10.6855 10.2322 10.2322L9.17157 9.17157C8.44854 9.89461 8 10.8956 8 12H9.5ZM13.7678 13.7678C13.3145 14.221 12.6906 14.5 12 14.5V16C13.1044 16 14.1054 15.5515 14.8284 14.8284L13.7678 13.7678Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,21 +1,19 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function EyeOnIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM2.67098 12.335C4.9893 7.69273 8.55546 5.49997 12 5.5C15.4445 5.50003 19.0107 7.69284 21.329 12.3351L22.671 11.6649C20.1618 6.64058 16.1417 4.00003 12 4C7.85827 3.99997 3.83815 6.64046 1.32902 11.6648L2.67098 12.335ZM1.32902 12.335C3.83815 17.3593 7.85826 19.9999 12 19.9999C16.1417 20 20.1618 17.3595 22.671 12.3351L21.329 11.6649C19.0107 16.3072 15.4445 18.4999 12 18.4999C8.55547 18.4999 4.9893 16.3071 2.67098 11.6648L1.32902 12.335ZM14.5 12C14.5 13.3807 13.3807 14.5 12 14.5V16C14.2091 16 16 14.2091 16 12H14.5ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 10.6193 10.6193 9.5 12 9.5V8C9.79086 8 8 9.79086 8 12H9.5ZM12 9.5C13.3807 9.5 14.5 10.6193 14.5 12H16C16 9.79086 14.2091 8 12 8V9.5Z"
fill="currentColor"
/>
</svg>
);
export function EyeOnIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM2.67098 12.335C4.9893 7.69273 8.55546 5.49997 12 5.5C15.4445 5.50003 19.0107 7.69284 21.329 12.3351L22.671 11.6649C20.1618 6.64058 16.1417 4.00003 12 4C7.85827 3.99997 3.83815 6.64046 1.32902 11.6648L2.67098 12.335ZM1.32902 12.335C3.83815 17.3593 7.85826 19.9999 12 19.9999C16.1417 20 20.1618 17.3595 22.671 12.3351L21.329 11.6649C19.0107 16.3072 15.4445 18.4999 12 18.4999C8.55547 18.4999 4.9893 16.3071 2.67098 11.6648L1.32902 12.335ZM14.5 12C14.5 13.3807 13.3807 14.5 12 14.5V16C14.2091 16 16 14.2091 16 12H14.5ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 10.6193 10.6193 9.5 12 9.5V8C9.79086 8 8 9.79086 8 12H9.5ZM12 9.5C13.3807 9.5 14.5 10.6193 14.5 12H16C16 9.79086 14.2091 8 12 8V9.5Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function FeedIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M19.5 8.75V11.5M19.5 11.5V14.25M19.5 11.5H16.75M19.5 11.5H22.25M14.75 6.5C14.75 8.57107 13.0711 10.25 11 10.25C8.92893 10.25 7.25 8.57107 7.25 6.5C7.25 4.42893 8.92893 2.75 11 2.75C13.0711 2.75 14.75 4.42893 14.75 6.5ZM3.5 20.25C3.86894 16.3254 6.8098 13.25 11 13.25C15.1902 13.25 18.1311 16.3254 18.5 20.25H3.5Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function FeedIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M19.5 8.75V11.5M19.5 11.5V14.25M19.5 11.5H16.75M19.5 11.5H22.25M14.75 6.5C14.75 8.57107 13.0711 10.25 11 10.25C8.92893 10.25 7.25 8.57107 7.25 6.5C7.25 4.42893 8.92893 2.75 11 2.75C13.0711 2.75 14.75 4.42893 14.75 6.5ZM3.5 20.25C3.86894 16.3254 6.8098 13.25 11 13.25C15.1902 13.25 18.1311 16.3254 18.5 20.25H3.5Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function FollowIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M11.85 13.251c-3.719.065-6.427 2.567-7.18 5.915-.13.575.338 1.084.927 1.084h6.901m-.647-6.999l.147-.001c.353 0 .696.022 1.03.064m-1.177-.063a7.889 7.889 0 00-1.852.249m3.028-.186c.334.042.658.104.972.186m-.972-.186a7.475 7.475 0 011.972.524m3.25 1.412v3m0 0v3m0-3h-3m3 0h3m-5.5-11.75a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
/>
</svg>
);
export function FollowIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M11.85 13.251c-3.719.065-6.427 2.567-7.18 5.915-.13.575.338 1.084.927 1.084h6.901m-.647-6.999l.147-.001c.353 0 .696.022 1.03.064m-1.177-.063a7.889 7.889 0 00-1.852.249m3.028-.186c.334.042.658.104.972.186m-.972-.186a7.475 7.475 0 011.972.524m3.25 1.412v3m0 0v3m0-3h-3m3 0h3m-5.5-11.75a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function HeartBeatIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M1.75 11.75H6L9 2.75L15 21.25L18 11.75H22.25"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function HeartBeatIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M1.75 11.75H6L9 2.75L15 21.25L18 11.75H22.25"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,21 +1,19 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function HideIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M9.1654 4.42071C8.76876 4.5401 8.544 4.95841 8.66339 5.35505C8.78277 5.75169 9.20109 5.97645 9.59772 5.85706L9.1654 4.42071ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM19.1413 14.9666C18.8678 15.2776 18.8982 15.7515 19.2092 16.0251C19.5203 16.2986 19.9942 16.2682 20.2677 15.9571L19.1413 14.9666ZM3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L3.28033 2.21967ZM2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM17.4703 17.4703L18.0006 16.9399L17.4703 17.4703ZM20.7197 21.7803C21.0126 22.0732 21.4874 22.0732 21.7803 21.7803C22.0732 21.4874 22.0732 21.0126 21.7803 20.7197L20.7197 21.7803ZM10.2322 10.2322C10.5251 9.93934 10.5251 9.46447 10.2322 9.17157C9.93934 8.87868 9.46447 8.87868 9.17157 9.17157L10.2322 10.2322ZM14.8284 14.8284C15.1213 14.5355 15.1213 14.0607 14.8284 13.7678C14.5355 13.4749 14.0607 13.4749 13.7678 13.7678L14.8284 14.8284ZM9.59772 5.85706C13.745 4.60878 18.4769 6.624 21.329 12.3351L22.671 11.6649C19.5775 5.47055 14.1791 2.91165 9.1654 4.42071L9.59772 5.85706ZM20.2677 15.9571C21.1654 14.9364 21.9755 13.7277 22.671 12.3351L21.329 11.6649C20.6865 12.9515 19.9468 14.0507 19.1413 14.9666L20.2677 15.9571ZM2.21967 3.28033L5.99937 7.06003L7.06003 5.99937L3.28033 2.21967L2.21967 3.28033ZM2.67098 12.335C3.84083 9.99245 5.33197 8.27257 6.95699 7.14609L6.10242 5.91332C4.24158 7.20327 2.5948 9.13019 1.32902 11.6648L2.67098 12.335ZM5.99937 7.06003L16.9399 18.0006L18.0006 16.9399L7.06003 5.99937L5.99937 7.06003ZM16.9399 18.0006L20.7197 21.7803L21.7803 20.7197L18.0006 16.9399L16.9399 18.0006ZM1.32902 12.335C3.20469 16.0909 5.92036 18.5148 8.91701 19.5009C11.922 20.4898 15.1308 20.0045 17.8975 18.0866L17.043 16.8539C14.6436 18.5171 11.9221 18.9107 9.38589 18.0761C6.84135 17.2388 4.40494 15.1369 2.67098 11.6648L1.32902 12.335ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 11.3094 9.779 10.6855 10.2322 10.2322L9.17157 9.17157C8.44854 9.89461 8 10.8956 8 12H9.5ZM13.7678 13.7678C13.3145 14.221 12.6906 14.5 12 14.5V16C13.1044 16 14.1054 15.5515 14.8284 14.8284L13.7678 13.7678Z"
fill="currentColor"
/>
</svg>
);
export function HideIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M9.1654 4.42071C8.76876 4.5401 8.544 4.95841 8.66339 5.35505C8.78277 5.75169 9.20109 5.97645 9.59772 5.85706L9.1654 4.42071ZM22 12L22.671 12.3351C22.7763 12.1241 22.7763 11.8759 22.671 11.6649L22 12ZM19.1413 14.9666C18.8678 15.2776 18.8982 15.7515 19.2092 16.0251C19.5203 16.2986 19.9942 16.2682 20.2677 15.9571L19.1413 14.9666ZM3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L3.28033 2.21967ZM2 11.9999L1.32902 11.6648C1.22366 11.8758 1.22366 12.124 1.32902 12.335L2 11.9999ZM17.4703 17.4703L18.0006 16.9399L17.4703 17.4703ZM20.7197 21.7803C21.0126 22.0732 21.4874 22.0732 21.7803 21.7803C22.0732 21.4874 22.0732 21.0126 21.7803 20.7197L20.7197 21.7803ZM10.2322 10.2322C10.5251 9.93934 10.5251 9.46447 10.2322 9.17157C9.93934 8.87868 9.46447 8.87868 9.17157 9.17157L10.2322 10.2322ZM14.8284 14.8284C15.1213 14.5355 15.1213 14.0607 14.8284 13.7678C14.5355 13.4749 14.0607 13.4749 13.7678 13.7678L14.8284 14.8284ZM9.59772 5.85706C13.745 4.60878 18.4769 6.624 21.329 12.3351L22.671 11.6649C19.5775 5.47055 14.1791 2.91165 9.1654 4.42071L9.59772 5.85706ZM20.2677 15.9571C21.1654 14.9364 21.9755 13.7277 22.671 12.3351L21.329 11.6649C20.6865 12.9515 19.9468 14.0507 19.1413 14.9666L20.2677 15.9571ZM2.21967 3.28033L5.99937 7.06003L7.06003 5.99937L3.28033 2.21967L2.21967 3.28033ZM2.67098 12.335C3.84083 9.99245 5.33197 8.27257 6.95699 7.14609L6.10242 5.91332C4.24158 7.20327 2.5948 9.13019 1.32902 11.6648L2.67098 12.335ZM5.99937 7.06003L16.9399 18.0006L18.0006 16.9399L7.06003 5.99937L5.99937 7.06003ZM16.9399 18.0006L20.7197 21.7803L21.7803 20.7197L18.0006 16.9399L16.9399 18.0006ZM1.32902 12.335C3.20469 16.0909 5.92036 18.5148 8.91701 19.5009C11.922 20.4898 15.1308 20.0045 17.8975 18.0866L17.043 16.8539C14.6436 18.5171 11.9221 18.9107 9.38589 18.0761C6.84135 17.2388 4.40494 15.1369 2.67098 11.6648L1.32902 12.335ZM12 14.5C10.6193 14.5 9.5 13.3807 9.5 12H8C8 14.2091 9.79086 16 12 16V14.5ZM9.5 12C9.5 11.3094 9.779 10.6855 10.2322 10.2322L9.17157 9.17157C8.44854 9.89461 8 10.8956 8 12H9.5ZM13.7678 13.7678C13.3145 14.221 12.6906 14.5 12 14.5V16C13.1044 16 14.1054 15.5515 14.8284 14.8284L13.7678 13.7678Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,21 +1,19 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ImageIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M3.75 3.75V3C3.33579 3 3 3.33579 3 3.75H3.75ZM20.25 3.75H21C21 3.33579 20.6642 3 20.25 3V3.75ZM20.25 20.25V21C20.6642 21 21 20.6642 21 20.25H20.25ZM3.75 20.25H3C3 20.6642 3.33579 21 3.75 21V20.25ZM3.23598 15.4538C2.93435 15.7377 2.91996 16.2124 3.20385 16.514C3.48774 16.8157 3.96239 16.83 4.26402 16.5462L3.23598 15.4538ZM8 12L8.53033 11.4697C8.24369 11.183 7.78117 11.176 7.48598 11.4538L8 12ZM12 16L11.4697 16.5303C11.7626 16.8232 12.2374 16.8232 12.5303 16.5303L12 16ZM14 14L14.5303 13.4697C14.2374 13.1768 13.7626 13.1768 13.4697 13.4697L14 14ZM19.4697 20.5303C19.7626 20.8232 20.2374 20.8232 20.5303 20.5303C20.8232 20.2374 20.8232 19.7626 20.5303 19.4697L19.4697 20.5303ZM3.75 4.5H20.25V3H3.75V4.5ZM19.5 3.75V20.25H21V3.75H19.5ZM20.25 19.5H3.75V21H20.25V19.5ZM4.5 20.25V3.75H3V20.25H4.5ZM4.26402 16.5462L8.51402 12.5462L7.48598 11.4538L3.23598 15.4538L4.26402 16.5462ZM7.46967 12.5303L11.4697 16.5303L12.5303 15.4697L8.53033 11.4697L7.46967 12.5303ZM12.5303 16.5303L14.5303 14.5303L13.4697 13.4697L11.4697 15.4697L12.5303 16.5303ZM13.4697 14.5303L19.4697 20.5303L20.5303 19.4697L14.5303 13.4697L13.4697 14.5303ZM15 9C15 9.41421 14.6642 9.75 14.25 9.75V11.25C15.4926 11.25 16.5 10.2426 16.5 9H15ZM14.25 9.75C13.8358 9.75 13.5 9.41421 13.5 9H12C12 10.2426 13.0074 11.25 14.25 11.25V9.75ZM13.5 9C13.5 8.58579 13.8358 8.25 14.25 8.25V6.75C13.0074 6.75 12 7.75736 12 9H13.5ZM14.25 8.25C14.6642 8.25 15 8.58579 15 9H16.5C16.5 7.75736 15.4926 6.75 14.25 6.75V8.25Z"
fill="currentColor"
/>
</svg>
);
export function ImageIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M3.75 3.75V3C3.33579 3 3 3.33579 3 3.75H3.75ZM20.25 3.75H21C21 3.33579 20.6642 3 20.25 3V3.75ZM20.25 20.25V21C20.6642 21 21 20.6642 21 20.25H20.25ZM3.75 20.25H3C3 20.6642 3.33579 21 3.75 21V20.25ZM3.23598 15.4538C2.93435 15.7377 2.91996 16.2124 3.20385 16.514C3.48774 16.8157 3.96239 16.83 4.26402 16.5462L3.23598 15.4538ZM8 12L8.53033 11.4697C8.24369 11.183 7.78117 11.176 7.48598 11.4538L8 12ZM12 16L11.4697 16.5303C11.7626 16.8232 12.2374 16.8232 12.5303 16.5303L12 16ZM14 14L14.5303 13.4697C14.2374 13.1768 13.7626 13.1768 13.4697 13.4697L14 14ZM19.4697 20.5303C19.7626 20.8232 20.2374 20.8232 20.5303 20.5303C20.8232 20.2374 20.8232 19.7626 20.5303 19.4697L19.4697 20.5303ZM3.75 4.5H20.25V3H3.75V4.5ZM19.5 3.75V20.25H21V3.75H19.5ZM20.25 19.5H3.75V21H20.25V19.5ZM4.5 20.25V3.75H3V20.25H4.5ZM4.26402 16.5462L8.51402 12.5462L7.48598 11.4538L3.23598 15.4538L4.26402 16.5462ZM7.46967 12.5303L11.4697 16.5303L12.5303 15.4697L8.53033 11.4697L7.46967 12.5303ZM12.5303 16.5303L14.5303 14.5303L13.4697 13.4697L11.4697 15.4697L12.5303 16.5303ZM13.4697 14.5303L19.4697 20.5303L20.5303 19.4697L14.5303 13.4697L13.4697 14.5303ZM15 9C15 9.41421 14.6642 9.75 14.25 9.75V11.25C15.4926 11.25 16.5 10.2426 16.5 9H15ZM14.25 9.75C13.8358 9.75 13.5 9.41421 13.5 9H12C12 10.2426 13.0074 11.25 14.25 11.25V9.75ZM13.5 9C13.5 8.58579 13.8358 8.25 14.25 8.25V6.75C13.0074 6.75 12 7.75736 12 9H13.5ZM14.25 8.25C14.6642 8.25 15 8.58579 15 9H16.5C16.5 7.75736 15.4926 6.75 14.25 6.75V8.25Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,46 +1,46 @@
// @index('./*.tsx', f => `export * from '${f.path}'`)
export * from "./arrowLeft";
export * from "./arrowRight";
export * from "./bell";
export * from "./cancel";
export * from "./checkCircle";
export * from "./chevronDown";
export * from "./chevronRight";
export * from "./compose";
export * from "./copy";
export * from "./edit";
export * from "./enter";
export * from "./eyeOff";
export * from "./eyeOn";
export * from "./feed";
export * from "./heartbeat";
export * from "./hide";
export * from "./image";
export * from "./like";
export * from "./lume";
export * from "./media";
export * from "./mute";
export * from "./space";
export * from "./navArrowDown";
export * from "./plus";
export * from "./plusCircle";
export * from "./refresh";
export * from "./reply";
export * from "./replyMessage";
export * from "./repost";
export * from "./threads";
export * from "./trash";
export * from "./world";
export * from "./zap";
export * from "./loader";
export * from "./trending";
export * from "./empty";
export * from "./cmd";
export * from "./verticalDots";
export * from "./signal";
export * from "./unverified";
export * from "./settings";
export * from "./logout";
export * from "./follow";
export * from "./unfollow";
export * from './arrowLeft';
export * from './arrowRight';
export * from './bell';
export * from './cancel';
export * from './checkCircle';
export * from './chevronDown';
export * from './chevronRight';
export * from './compose';
export * from './copy';
export * from './edit';
export * from './enter';
export * from './eyeOff';
export * from './eyeOn';
export * from './feed';
export * from './heartbeat';
export * from './hide';
export * from './image';
export * from './like';
export * from './lume';
export * from './media';
export * from './mute';
export * from './space';
export * from './navArrowDown';
export * from './plus';
export * from './plusCircle';
export * from './refresh';
export * from './reply';
export * from './replyMessage';
export * from './repost';
export * from './threads';
export * from './trash';
export * from './world';
export * from './zap';
export * from './loader';
export * from './trending';
export * from './empty';
export * from './cmd';
export * from './verticalDots';
export * from './signal';
export * from './unverified';
export * from './settings';
export * from './logout';
export * from './follow';
export * from './unfollow';
// @endindex

View File

@@ -1,21 +1,14 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function LikeIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12 5.57193C18.3331 -0.86765 29.1898 11.0916 12 20.75C-5.18982 11.0916 5.66687 -0.867651 12 5.57193Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinejoin="round"
/>
</svg>
);
export function LikeIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M12 5.57193C18.3331 -0.86765 29.1898 11.0916 12 20.75C-5.18982 11.0916 5.66687 -0.867651 12 5.57193Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,29 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function LoaderIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<title id="loading">Loading</title>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
);
export function LoaderIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<title id="loading">Loading</title>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function LogoutIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M20.25 12H9m11.25 0l-4.5 4.5m4.5-4.5l-4.5-4.5m-4.5 12.75h-6.5a1 1 0 01-1-1V4.75a1 1 0 011-1h6.5"
/>
</svg>
);
export function LogoutIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M20.25 12H9m11.25 0l-4.5 4.5m4.5-4.5l-4.5-4.5m-4.5 12.75h-6.5a1 1 0 01-1-1V4.75a1 1 0 011-1h6.5"
/>
</svg>
);
}

View File

@@ -1,14 +1,10 @@
export function LumeIcon({ className }: { className: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
className={className}
>
<path
d="M7.337 19.099a.32.32 0 0 1-.373.021 20.911 20.911 0 0 0-4.259-2.022c-.17-.063-.191-.297-.031-.383a13.876 13.876 0 0 0 4.886-4.639A13.715 13.715 0 0 0 9.69 5.14c0-.17.149-.309.32-.309h3.981c.17 0 .309.138.32.309.074 2.468.809 4.852 2.129 6.937a13.88 13.88 0 0 0 4.886 4.64c.16.095.139.33-.032.383-1.266.436-2.49.99-3.651 1.66-.203.116-.405.244-.607.361a.32.32 0 0 1-.373-.021 18.293 18.293 0 0 1-4.567-5.331l-.096-.16-.096.16a18.158 18.158 0 0 1-4.567 5.33Z"
fill="currentColor"
/>
</svg>
);
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" className={className}>
<path
d="M7.337 19.099a.32.32 0 0 1-.373.021 20.911 20.911 0 0 0-4.259-2.022c-.17-.063-.191-.297-.031-.383a13.876 13.876 0 0 0 4.886-4.639A13.715 13.715 0 0 0 9.69 5.14c0-.17.149-.309.32-.309h3.981c.17 0 .309.138.32.309.074 2.468.809 4.852 2.129 6.937a13.88 13.88 0 0 0 4.886 4.64c.16.095.139.33-.032.383-1.266.436-2.49.99-3.651 1.66-.203.116-.405.244-.607.361a.32.32 0 0 1-.373-.021 18.293 18.293 0 0 1-4.567-5.331l-.096-.16-.096.16a18.158 18.158 0 0 1-4.567 5.33Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,27 +1,25 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function MediaIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M6.25 7.5C6.94036 7.5 7.5 6.94036 7.5 6.25C7.5 5.55964 6.94036 5 6.25 5C5.55964 5 5 5.55964 5 6.25C5 6.94036 5.55964 7.5 6.25 7.5Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.75 2C2.33579 2 2 2.33579 2 2.75V15.25C2 15.6642 2.33579 16 2.75 16H8V21.25C8 21.6642 8.33579 22 8.75 22H21.25C21.6642 22 22 21.6642 22 21.25V8.75C22 8.33579 21.6642 8 21.25 8H16V2.75C16 2.33579 15.6642 2 15.25 2H2.75ZM14.5 8V3.5H3.5V10.764L5.58203 9.37596C5.83396 9.20801 6.16216 9.20801 6.41408 9.37596L8 10.4332V8.75C8 8.33579 8.33579 8 8.75 8H14.5ZM8 12.236L5.99806 10.9014L3.5 12.5668V14.5H8V12.236ZM13 17.5585V12.4415C13 12.2472 13.212 12.1272 13.3786 12.2272L17.6427 14.7856C17.8045 14.8827 17.8045 15.1173 17.6427 15.2144L13.3786 17.7728C13.212 17.8728 13 17.7528 13 17.5585Z"
fill="currentColor"
/>
</svg>
);
export function MediaIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M6.25 7.5C6.94036 7.5 7.5 6.94036 7.5 6.25C7.5 5.55964 6.94036 5 6.25 5C5.55964 5 5 5.55964 5 6.25C5 6.94036 5.55964 7.5 6.25 7.5Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.75 2C2.33579 2 2 2.33579 2 2.75V15.25C2 15.6642 2.33579 16 2.75 16H8V21.25C8 21.6642 8.33579 22 8.75 22H21.25C21.6642 22 22 21.6642 22 21.25V8.75C22 8.33579 21.6642 8 21.25 8H16V2.75C16 2.33579 15.6642 2 15.25 2H2.75ZM14.5 8V3.5H3.5V10.764L5.58203 9.37596C5.83396 9.20801 6.16216 9.20801 6.41408 9.37596L8 10.4332V8.75C8 8.33579 8.33579 8 8.75 8H14.5ZM8 12.236L5.99806 10.9014L3.5 12.5668V14.5H8V12.236ZM13 17.5585V12.4415C13 12.2472 13.212 12.1272 13.3786 12.2272L17.6427 14.7856C17.8045 14.8827 17.8045 15.1173 17.6427 15.2144L13.3786 17.7728C13.212 17.8728 13 17.7528 13 17.5585Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,27 +1,25 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function MuteIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17 5.93934V3.75C17 3.47592 16.8505 3.22366 16.6101 3.09208C16.3696 2.9605 16.0766 2.97055 15.8457 3.1183L9.78055 7H5.75C5.33579 7 5 7.33579 5 7.75V16.25C5 16.6642 5.33579 17 5.75 17H5.93934L3.21967 19.7197C2.92678 20.0126 2.92678 20.4874 3.21967 20.7803C3.51256 21.0732 3.98744 21.0732 4.28033 20.7803L20.7803 4.28033C21.0732 3.98744 21.0732 3.51256 20.7803 3.21967C20.4874 2.92678 20.0126 2.92678 19.7197 3.21967L17 5.93934ZM7.43934 15.5H6.5V8.5H10C10.1433 8.5 10.2836 8.45895 10.4043 8.3817L15.5 5.12045V7.43934L7.43934 15.5Z"
fill="currentColor"
/>
<path
d="M15.5 18.8796L11.1102 16.0701L10.0243 17.156L15.8457 20.8817C16.0766 21.0294 16.3696 21.0395 16.6101 20.9079C16.8505 20.7763 17 20.5241 17 20.25V10.1803L15.5 11.6803V18.8796Z"
fill="currentColor"
/>
</svg>
);
export function MuteIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17 5.93934V3.75C17 3.47592 16.8505 3.22366 16.6101 3.09208C16.3696 2.9605 16.0766 2.97055 15.8457 3.1183L9.78055 7H5.75C5.33579 7 5 7.33579 5 7.75V16.25C5 16.6642 5.33579 17 5.75 17H5.93934L3.21967 19.7197C2.92678 20.0126 2.92678 20.4874 3.21967 20.7803C3.51256 21.0732 3.98744 21.0732 4.28033 20.7803L20.7803 4.28033C21.0732 3.98744 21.0732 3.51256 20.7803 3.21967C20.4874 2.92678 20.0126 2.92678 19.7197 3.21967L17 5.93934ZM7.43934 15.5H6.5V8.5H10C10.1433 8.5 10.2836 8.45895 10.4043 8.3817L15.5 5.12045V7.43934L7.43934 15.5Z"
fill="currentColor"
/>
<path
d="M15.5 18.8796L11.1102 16.0701L10.0243 17.156L15.8457 20.8817C16.0766 21.0294 16.3696 21.0395 16.6101 20.9079C16.8505 20.7763 17 20.5241 17 20.25V10.1803L15.5 11.6803V18.8796Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,20 +1,15 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function NavArrowDownIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<title id="navArrowDown">Nav Arrow Down</title>
<path
d="M4.29233 7.97419C3.37989 6.14866 4.70668 4 6.74799 4H17.2519C19.2932 4 20.62 6.14866 19.7076 7.97419L14.4556 18.4819C13.4439 20.5061 10.556 20.506 9.54431 18.4819L4.29233 7.97419Z"
fill="currentColor"
/>
</svg>
);
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<title id="navArrowDown">Nav Arrow Down</title>
<path
d="M4.29233 7.97419C3.37989 6.14866 4.70668 4 6.74799 4H17.2519C19.2932 4 20.62 6.14866 19.7076 7.97419L14.4556 18.4819C13.4439 20.5061 10.556 20.506 9.54431 18.4819L4.29233 7.97419Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,22 +1,15 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function PlusIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12 3.75V12M12 12V20.25M12 12H3.75M12 12H20.25"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function PlusIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M12 3.75V12M12 12V20.25M12 12H3.75M12 12H20.25"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function PlusCircleIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M16.2426 12.0005H7.75736M12 16.2431V7.75781M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function PlusCircleIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M16.2426 12.0005H7.75736M12 16.2431V7.75781M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,21 +1,14 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function RefreshIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.979 4.5C7.83687 4.5 4.479 7.85786 4.479 12C4.479 16.1421 7.83687 19.5 11.979 19.5C15.2434 19.5 18.0225 17.4141 19.0524 14.5001C19.1905 14.1095 19.619 13.9048 20.0095 14.0429C20.4 14.1809 20.6047 14.6094 20.4667 14.9999C19.2315 18.4945 15.8988 21 11.979 21C7.00844 21 2.979 16.9706 2.979 12C2.979 7.02944 7.00844 3 11.979 3C13.709 3 15.1419 3.42256 16.4191 4.20651C17.1663 4.6651 17.8487 5.24046 18.5 5.90708V4C18.5 3.58579 18.8358 3.25 19.25 3.25C19.6642 3.25 20 3.58579 20 4V8C20 8.41421 19.6642 8.75 19.25 8.75H15.25C14.8358 8.75 14.5 8.41421 14.5 8C14.5 7.58579 14.8358 7.25 15.25 7.25H17.7068C17.0285 6.51595 16.3546 5.92693 15.6345 5.4849C14.6015 4.85088 13.4417 4.5 11.979 4.5Z"
fill="currentColor"
/>
</svg>
);
export function RefreshIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.979 4.5C7.83687 4.5 4.479 7.85786 4.479 12C4.479 16.1421 7.83687 19.5 11.979 19.5C15.2434 19.5 18.0225 17.4141 19.0524 14.5001C19.1905 14.1095 19.619 13.9048 20.0095 14.0429C20.4 14.1809 20.6047 14.6094 20.4667 14.9999C19.2315 18.4945 15.8988 21 11.979 21C7.00844 21 2.979 16.9706 2.979 12C2.979 7.02944 7.00844 3 11.979 3C13.709 3 15.1419 3.42256 16.4191 4.20651C17.1663 4.6651 17.8487 5.24046 18.5 5.90708V4C18.5 3.58579 18.8358 3.25 19.25 3.25C19.6642 3.25 20 3.58579 20 4V8C20 8.41421 19.6642 8.75 19.25 8.75H15.25C14.8358 8.75 14.5 8.41421 14.5 8C14.5 7.58579 14.8358 7.25 15.25 7.25H17.7068C17.0285 6.51595 16.3546 5.92693 15.6345 5.4849C14.6015 4.85088 13.4417 4.5 11.979 4.5Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,22 +1,15 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ReplyIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12 21.25C17.1086 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.1086 2.75 12 2.75C6.89137 2.75 2.75 6.89137 2.75 12C2.75 13.529 3.12096 14.9713 3.77778 16.2418L2.75 21.25L7.88889 20.2885C9.12732 20.9039 10.5232 21.25 12 21.25Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function ReplyIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M12 21.25C17.1086 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.1086 2.75 12 2.75C6.89137 2.75 2.75 6.89137 2.75 12C2.75 13.529 3.12096 14.9713 3.77778 16.2418L2.75 21.25L7.88889 20.2885C9.12732 20.9039 10.5232 21.25 12 21.25Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,23 +1,23 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ReplyMessageIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M1.75 12L11.25 3.75V8.5C19.75 8.5 22 11.75 22 20.25C20.5 17.25 19.75 15.5 11.25 15.5V20.25L1.75 12Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinejoin="round"
/>
</svg>
);
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M1.75 12L11.25 3.75V8.5C19.75 8.5 22 11.75 22 20.25C20.5 17.25 19.75 15.5 11.25 15.5V20.25L1.75 12Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,22 +1,15 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function RepostIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M17.25 21.25L20.25 18.25L17.25 15.25M6.75 2.75L3.75 5.75L6.75 8.75M5.25 5.75H20.25V10.75M3.75 13.75V18.25H18.75"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function RepostIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M17.25 21.25L20.25 18.25L17.25 15.25M6.75 2.75L3.75 5.75L6.75 8.75M5.25 5.75H20.25V10.75M3.75 13.75V18.25H18.75"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,29 +1,27 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function SettingsIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinejoin="round"
strokeWidth="1.5"
d="M8.552 5.37l-1.793-.414a1 1 0 00-.932.267l-.604.604a1 1 0 00-.267.932l.414 1.793a1 1 0 01-.42 1.056l-1.755 1.17a1 1 0 00-.445.832v.78a1 1 0 00.445.832l1.755 1.17a1 1 0 01.42 1.056l-.414 1.793a1 1 0 00.267.932l.604.604a1 1 0 00.932.267l1.793-.414a1 1 0 011.056.42l1.17 1.755a1 1 0 00.832.445h.78a1 1 0 00.832-.445l1.17-1.755a1 1 0 011.056-.42l1.793.414a1 1 0 00.932-.267l.604-.604a1 1 0 00.267-.932l-.414-1.793a1 1 0 01.42-1.056l1.755-1.17a1 1 0 00.445-.832v-.78a1 1 0 00-.445-.832l-1.755-1.17a1 1 0 01-.42-1.056l.414-1.793a1 1 0 00-.267-.932l-.604-.604a1 1 0 00-.932-.267l-1.793.414a1 1 0 01-1.056-.42l-1.17-1.755a1 1 0 00-.832-.445h-.78a1 1 0 00-.832.445L9.608 4.95a1 1 0 01-1.056.42z"
/>
<path
stroke="currentColor"
strokeLinejoin="round"
strokeWidth="1.5"
d="M14.75 12a2.75 2.75 0 11-5.5 0 2.75 2.75 0 015.5 0z"
/>
</svg>
);
export function SettingsIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinejoin="round"
strokeWidth="1.5"
d="M8.552 5.37l-1.793-.414a1 1 0 00-.932.267l-.604.604a1 1 0 00-.267.932l.414 1.793a1 1 0 01-.42 1.056l-1.755 1.17a1 1 0 00-.445.832v.78a1 1 0 00.445.832l1.755 1.17a1 1 0 01.42 1.056l-.414 1.793a1 1 0 00.267.932l.604.604a1 1 0 00.932.267l1.793-.414a1 1 0 011.056.42l1.17 1.755a1 1 0 00.832.445h.78a1 1 0 00.832-.445l1.17-1.755a1 1 0 011.056-.42l1.793.414a1 1 0 00.932-.267l.604-.604a1 1 0 00.267-.932l-.414-1.793a1 1 0 01.42-1.056l1.755-1.17a1 1 0 00.445-.832v-.78a1 1 0 00-.445-.832l-1.755-1.17a1 1 0 01-.42-1.056l.414-1.793a1 1 0 00-.267-.932l-.604-.604a1 1 0 00-.932-.267l-1.793.414a1 1 0 01-1.056-.42l-1.17-1.755a1 1 0 00-.832-.445h-.78a1 1 0 00-.832.445L9.608 4.95a1 1 0 01-1.056.42z"
/>
<path
stroke="currentColor"
strokeLinejoin="round"
strokeWidth="1.5"
d="M14.75 12a2.75 2.75 0 11-5.5 0 2.75 2.75 0 015.5 0z"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function SignalIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M5.46 5.46A9.221 9.221 0 002.75 12a9.221 9.221 0 002.71 6.541M8.365 8.367a5.123 5.123 0 00-1.505 3.634c0 1.419.575 2.704 1.505 3.633m7.268 0a5.122 5.122 0 001.505-3.633c0-1.42-.575-2.704-1.505-3.634m2.907 10.174a9.22 9.22 0 002.709-6.54 9.22 9.22 0 00-2.71-6.541M12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z"
/>
</svg>
);
export function SignalIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M5.46 5.46A9.221 9.221 0 002.75 12a9.221 9.221 0 002.71 6.541M8.365 8.367a5.123 5.123 0 00-1.505 3.634c0 1.419.575 2.704 1.505 3.633m7.268 0a5.122 5.122 0 001.505-3.633c0-1.42-.575-2.704-1.505-3.634m2.907 10.174a9.22 9.22 0 002.709-6.54 9.22 9.22 0 00-2.71-6.541M12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z"
/>
</svg>
);
}

View File

@@ -1,22 +1,20 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function SpaceIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M2.79055 12.1075C1.4706 12.8201 0.744814 13.464 0.86677 13.9192C1.16059 15.0158 6.26396 14.6011 12.2655 12.993C18.2669 11.3849 22.8939 9.19232 22.6001 8.09576C22.4781 7.64061 21.5276 7.44582 20.0282 7.48865M2.79055 12.1075C2.80163 12.8595 2.90491 13.6226 3.10854 14.3825C4.43075 19.3171 9.48321 22.2508 14.3935 20.935C19.3038 19.6193 22.2126 14.5525 20.8904 9.61792C20.6867 8.85797 20.3946 8.14547 20.0282 7.48865M2.79055 12.1075C2.72972 7.9763 5.45127 4.1785 9.60537 3.06541C13.7595 1.95233 18.0153 3.88054 20.0282 7.48865"
stroke="currentColor"
strokeWidth={1.5}
/>
</svg>
);
export function SpaceIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M2.79055 12.1075C1.4706 12.8201 0.744814 13.464 0.86677 13.9192C1.16059 15.0158 6.26396 14.6011 12.2655 12.993C18.2669 11.3849 22.8939 9.19232 22.6001 8.09576C22.4781 7.64061 21.5276 7.44582 20.0282 7.48865M2.79055 12.1075C2.80163 12.8595 2.90491 13.6226 3.10854 14.3825C4.43075 19.3171 9.48321 22.2508 14.3935 20.935C19.3038 19.6193 22.2126 14.5525 20.8904 9.61792C20.6867 8.85797 20.3946 8.14547 20.0282 7.48865M2.79055 12.1075C2.72972 7.9763 5.45127 4.1785 9.60537 3.06541C13.7595 1.95233 18.0153 3.88054 20.0282 7.48865"
stroke="currentColor"
strokeWidth={1.5}
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ThreadIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M2.75 12h18.5M2.75 5.75h18.5m-18.5 12.5h8.75"
/>
</svg>
);
export function ThreadIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M2.75 12h18.5M2.75 5.75h18.5m-18.5 12.5h8.75"
/>
</svg>
);
}

View File

@@ -1,21 +1,19 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ThreadsIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M2.75 3.75V3C2.33579 3 2 3.33579 2 3.75H2.75ZM16.25 3.75H17C17 3.33579 16.6642 3 16.25 3V3.75ZM21.25 12H22C22 11.5858 21.6642 11.25 21.25 11.25V12ZM6.75 15C6.33579 15 6 15.3358 6 15.75C6 16.1642 6.33579 16.5 6.75 16.5V15ZM6.75 7.75V7C6.33579 7 6 7.33579 6 7.75H6.75ZM12.25 7.75H13C13 7.33579 12.6642 7 12.25 7V7.75ZM12.25 12.25V13C12.6642 13 13 12.6642 13 12.25H12.25ZM6.75 12.25H6C6 12.6642 6.33579 13 6.75 13V12.25ZM2.75 4.5H16.25V3H2.75V4.5ZM22 17.75V12H20.5V17.75H22ZM15.5 3.75V12H17V3.75H15.5ZM15.5 12V17.75H17V12H15.5ZM21.25 11.25H16.25V12.75H21.25V11.25ZM2 3.75V17.75H3.5V3.75H2ZM5.25 21H18.5V19.5H5.25V21ZM2 17.75C2 19.5449 3.45507 21 5.25 21V19.5C4.2835 19.5 3.5 18.7165 3.5 17.75H2ZM18.75 21C20.5449 21 22 19.5449 22 17.75H20.5C20.5 18.7165 19.7165 19.5 18.75 19.5V21ZM18.75 19.5C17.7835 19.5 17 18.7165 17 17.75H15.5C15.5 19.5449 16.9551 21 18.75 21V19.5ZM6.75 16.5H12.25V15H6.75V16.5ZM6.75 8.5H12.25V7H6.75V8.5ZM11.5 7.75V12.25H13V7.75H11.5ZM12.25 11.5H6.75V13H12.25V11.5ZM7.5 12.25V7.75H6V12.25H7.5Z"
fill="currentColor"
/>
</svg>
);
export function ThreadsIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M2.75 3.75V3C2.33579 3 2 3.33579 2 3.75H2.75ZM16.25 3.75H17C17 3.33579 16.6642 3 16.25 3V3.75ZM21.25 12H22C22 11.5858 21.6642 11.25 21.25 11.25V12ZM6.75 15C6.33579 15 6 15.3358 6 15.75C6 16.1642 6.33579 16.5 6.75 16.5V15ZM6.75 7.75V7C6.33579 7 6 7.33579 6 7.75H6.75ZM12.25 7.75H13C13 7.33579 12.6642 7 12.25 7V7.75ZM12.25 12.25V13C12.6642 13 13 12.6642 13 12.25H12.25ZM6.75 12.25H6C6 12.6642 6.33579 13 6.75 13V12.25ZM2.75 4.5H16.25V3H2.75V4.5ZM22 17.75V12H20.5V17.75H22ZM15.5 3.75V12H17V3.75H15.5ZM15.5 12V17.75H17V12H15.5ZM21.25 11.25H16.25V12.75H21.25V11.25ZM2 3.75V17.75H3.5V3.75H2ZM5.25 21H18.5V19.5H5.25V21ZM2 17.75C2 19.5449 3.45507 21 5.25 21V19.5C4.2835 19.5 3.5 18.7165 3.5 17.75H2ZM18.75 21C20.5449 21 22 19.5449 22 17.75H20.5C20.5 18.7165 19.7165 19.5 18.75 19.5V21ZM18.75 19.5C17.7835 19.5 17 18.7165 17 17.75H15.5C15.5 19.5449 16.9551 21 18.75 21V19.5ZM6.75 16.5H12.25V15H6.75V16.5ZM6.75 8.5H12.25V7H6.75V8.5ZM11.5 7.75V12.25H13V7.75H11.5ZM12.25 11.5H6.75V13H12.25V11.5ZM7.5 12.25V7.75H6V12.25H7.5Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,21 +1,19 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function TrashIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M5.75 21.25L5.00156 21.2983C5.02702 21.6929 5.35453 22 5.75 22V21.25ZM18.25 21.25V22C18.6455 22 18.973 21.6929 18.9984 21.2983L18.25 21.25ZM2.75 5C2.33579 5 2 5.33579 2 5.75C2 6.16421 2.33579 6.5 2.75 6.5V5ZM21.25 6.5C21.6642 6.5 22 6.16421 22 5.75C22 5.33579 21.6642 5 21.25 5V6.5ZM10.5 10.75C10.5 10.3358 10.1642 10 9.75 10C9.33579 10 9 10.3358 9 10.75H10.5ZM9 16.25C9 16.6642 9.33579 17 9.75 17C10.1642 17 10.5 16.6642 10.5 16.25H9ZM15 10.75C15 10.3358 14.6642 10 14.25 10C13.8358 10 13.5 10.3358 13.5 10.75H15ZM13.5 16.25C13.5 16.6642 13.8358 17 14.25 17C14.6642 17 15 16.6642 15 16.25H13.5ZM15.1477 5.93694C15.2509 6.33808 15.6598 6.57957 16.0609 6.47633C16.4621 6.37308 16.7036 5.9642 16.6003 5.56306L15.1477 5.93694ZM4.00156 5.79829L5.00156 21.2983L6.49844 21.2017L5.49844 5.70171L4.00156 5.79829ZM5.75 22H18.25V20.5H5.75V22ZM18.9984 21.2983L19.9984 5.79829L18.5016 5.70171L17.5016 21.2017L18.9984 21.2983ZM19.25 5H4.75V6.5H19.25V5ZM2.75 6.5H4.75V5H2.75V6.5ZM19.25 6.5H21.25V5H19.25V6.5ZM9 10.75V16.25H10.5V10.75H9ZM13.5 10.75V16.25H15V10.75H13.5ZM12 3.5C13.5134 3.5 14.7868 4.53504 15.1477 5.93694L16.6003 5.56306C16.0731 3.51451 14.2144 2 12 2V3.5ZM8.85237 5.93694C9.21319 4.53504 10.4867 3.5 12 3.5V2C9.78568 2 7.92697 3.51451 7.39971 5.56306L8.85237 5.93694Z"
fill="currentColor"
/>
</svg>
);
export function TrashIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M5.75 21.25L5.00156 21.2983C5.02702 21.6929 5.35453 22 5.75 22V21.25ZM18.25 21.25V22C18.6455 22 18.973 21.6929 18.9984 21.2983L18.25 21.25ZM2.75 5C2.33579 5 2 5.33579 2 5.75C2 6.16421 2.33579 6.5 2.75 6.5V5ZM21.25 6.5C21.6642 6.5 22 6.16421 22 5.75C22 5.33579 21.6642 5 21.25 5V6.5ZM10.5 10.75C10.5 10.3358 10.1642 10 9.75 10C9.33579 10 9 10.3358 9 10.75H10.5ZM9 16.25C9 16.6642 9.33579 17 9.75 17C10.1642 17 10.5 16.6642 10.5 16.25H9ZM15 10.75C15 10.3358 14.6642 10 14.25 10C13.8358 10 13.5 10.3358 13.5 10.75H15ZM13.5 16.25C13.5 16.6642 13.8358 17 14.25 17C14.6642 17 15 16.6642 15 16.25H13.5ZM15.1477 5.93694C15.2509 6.33808 15.6598 6.57957 16.0609 6.47633C16.4621 6.37308 16.7036 5.9642 16.6003 5.56306L15.1477 5.93694ZM4.00156 5.79829L5.00156 21.2983L6.49844 21.2017L5.49844 5.70171L4.00156 5.79829ZM5.75 22H18.25V20.5H5.75V22ZM18.9984 21.2983L19.9984 5.79829L18.5016 5.70171L17.5016 21.2017L18.9984 21.2983ZM19.25 5H4.75V6.5H19.25V5ZM2.75 6.5H4.75V5H2.75V6.5ZM19.25 6.5H21.25V5H19.25V6.5ZM9 10.75V16.25H10.5V10.75H9ZM13.5 10.75V16.25H15V10.75H13.5ZM12 3.5C13.5134 3.5 14.7868 4.53504 15.1477 5.93694L16.6003 5.56306C16.0731 3.51451 14.2144 2 12 2V3.5ZM8.85237 5.93694C9.21319 4.53504 10.4867 3.5 12 3.5V2C9.78568 2 7.92697 3.51451 7.39971 5.56306L8.85237 5.93694Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function TrendingIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M15.75 6.75h5.5v5.5m-.514-4.975L13 15l-4-4-6.25 6.25"
/>
</svg>
);
export function TrendingIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M15.75 6.75h5.5v5.5m-.514-4.975L13 15l-4-4-6.25 6.25"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function UnfollowIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M17.748 11.25h4.5m-7.5-4.75a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM3.67 19.166C4.432 15.773 7.203 13.25 11 13.25c3.795 0 6.566 2.524 7.328 5.916.13.575-.338 1.084-.927 1.084H4.597c-.59 0-1.056-.51-.927-1.084z"
/>
</svg>
);
export function UnfollowIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M17.748 11.25h4.5m-7.5-4.75a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM3.67 19.166C4.432 15.773 7.203 13.25 11 13.25c3.795 0 6.566 2.524 7.328 5.916.13.575-.338 1.084-.927 1.084H4.597c-.59 0-1.056-.51-.927-1.084z"
/>
</svg>
);
}

View File

@@ -1,23 +1,21 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function UnverifiedIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
fill="currentColor"
fillRule="evenodd"
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12zm7.53-3.53a.75.75 0 00-1.06 1.06L10.94 12l-2.47 2.47a.75.75 0 101.06 1.06L12 13.06l2.47 2.47a.75.75 0 101.06-1.06L13.06 12l2.47-2.47a.75.75 0 00-1.06-1.06L12 10.94 9.53 8.47z"
clipRule="evenodd"
/>
</svg>
);
export function UnverifiedIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
fill="currentColor"
fillRule="evenodd"
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12zm7.53-3.53a.75.75 0 00-1.06 1.06L10.94 12l-2.47 2.47a.75.75 0 101.06 1.06L12 13.06l2.47 2.47a.75.75 0 101.06-1.06L13.06 12l2.47-2.47a.75.75 0 00-1.06-1.06L12 10.94 9.53 8.47z"
clipRule="evenodd"
/>
</svg>
);
}

View File

@@ -1,28 +1,28 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function VerticalDotsIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
fill="currentColor"
d="M12 4.75a1 1 0 100-2 1 1 0 000 2zM12 13a1 1 0 100-2 1 1 0 000 2zM12 21.25a1 1 0 100-2 1 1 0 000 2z"
/>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M12 4.75a1 1 0 100-2 1 1 0 000 2zM12 13a1 1 0 100-2 1 1 0 000 2zM12 21.25a1 1 0 100-2 1 1 0 000 2z"
/>
</svg>
);
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
fill="currentColor"
d="M12 4.75a1 1 0 100-2 1 1 0 000 2zM12 13a1 1 0 100-2 1 1 0 000 2zM12 21.25a1 1 0 100-2 1 1 0 000 2z"
/>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M12 4.75a1 1 0 100-2 1 1 0 000 2zM12 13a1 1 0 100-2 1 1 0 000 2zM12 21.25a1 1 0 100-2 1 1 0 000 2z"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function WorldIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M19.7783 4.22184L4.22197 19.7782M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12ZM18.5163 18.516C17.3167 19.7156 13.427 17.7707 9.82826 14.172C6.22955 10.5733 4.28467 6.68352 5.48424 5.48395C6.68381 4.28438 10.5736 6.22927 14.1723 9.82798C17.771 13.4267 19.7159 17.3165 18.5163 18.516Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function WorldIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M19.7783 4.22184L4.22197 19.7782M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12ZM18.5163 18.516C17.3167 19.7156 13.427 17.7707 9.82826 14.172C6.22955 10.5733 4.28467 6.68352 5.48424 5.48395C6.68381 4.28438 10.5736 6.22927 14.1723 9.82798C17.771 13.4267 19.7159 17.3165 18.5163 18.516Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
import { SVGProps } from "react";
import { SVGProps } from 'react';
export function ZapIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M3.75 12.75L8.75 2.75H18L15.25 8.25H21.25L6.75 21.25L8.89706 12.75H3.75Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export function ZapIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M3.75 12.75L8.75 2.75H18L15.25 8.25H21.25L6.75 21.25L8.89706 12.75H3.75Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,21 +1,21 @@
import { ImgHTMLAttributes } from "react";
import { ImgHTMLAttributes } from 'react';
interface Props extends ImgHTMLAttributes<any> {
fallback: string;
fallback: string;
}
export function Image({ src, fallback, ...props }: Props) {
return (
<img
{...props}
src={src || fallback}
onError={({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src = fallback;
}}
decoding="async"
alt="lume default img"
style={{ contentVisibility: "auto" }}
/>
);
return (
<img
{...props}
src={src || fallback}
onError={({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src = fallback;
}}
decoding="async"
alt="lume default img"
style={{ contentVisibility: 'auto' }}
/>
);
}

View File

@@ -1,123 +1,119 @@
import { Dialog, Transition } from "@headlessui/react";
import { removeAll } from "@libs/storage";
import { CancelIcon, LogoutIcon } from "@shared/icons";
import { useQueryClient } from "@tanstack/react-query";
import { relaunch } from "@tauri-apps/api/process";
import { Fragment, useState } from "react";
import { Dialog, Transition } from '@headlessui/react';
import { useQueryClient } from '@tanstack/react-query';
import { relaunch } from '@tauri-apps/api/process';
import { Fragment, useState } from 'react';
import { removeAll } from '@libs/storage';
import { CancelIcon, LogoutIcon } from '@shared/icons';
export function Logout() {
const queryClient = useQueryClient();
const [isOpen, setIsOpen] = useState(false);
const queryClient = useQueryClient();
const [isOpen, setIsOpen] = useState(false);
const closeModal = () => {
setIsOpen(false);
};
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
const openModal = () => {
setIsOpen(true);
};
const logout = async () => {
// reset database
await removeAll();
// reset react query
queryClient.clear();
// navigate
await relaunch();
};
const logout = async () => {
// reset database
await removeAll();
// reset react query
queryClient.clear();
// navigate
await relaunch();
};
return (
<>
<button
type="button"
onClick={() => openModal()}
aria-label="Logout"
className="inline-flex items-center justify-center w-9 h-9 rounded-md border-t bg-zinc-800 border-zinc-700/50 transform active:translate-y-1"
>
<LogoutIcon className="w-4 h-4 text-zinc-400" />
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col rounded-xl border-t border-zinc-800/50 bg-zinc-900">
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-6">
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<Dialog.Title
as="h3"
className="text-lg font-semibold leading-none text-zinc-100"
>
Are you sure!
</Dialog.Title>
<button
type="button"
onClick={closeModal}
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<CancelIcon
width={20}
height={20}
className="text-zinc-300"
/>
</button>
</div>
<Dialog.Description className="text-sm leading-tight text-zinc-400">
<p className="mb-2">
When logout, all local data will be wiped, and restart
app then you need to start onboarding process again when
you log in.
</p>
<p>
In the next version, Lume will support multi account,
then you can switch between all account s instead of
logout
</p>
</Dialog.Description>
</div>
</div>
<div className="flex h-full w-full flex-col items-end justify-center overflow-y-auto px-5 py-2.5">
<div className="flex items-center gap-2">
<button
type="button"
onClick={closeModal}
className="inline-flex h-9 items-center justify-center rounded-md px-3 text-sm font-medium text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100"
>
Cancel
</button>
<button
type="button"
onClick={() => logout()}
className="inline-flex h-9 items-center justify-center rounded-md bg-red-500 px-3 text-sm font-medium text-zinc-100 hover:bg-red-600"
>
Confirm
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
return (
<>
<button
type="button"
onClick={() => openModal()}
aria-label="Logout"
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md border-t border-zinc-700/50 bg-zinc-800 active:translate-y-1"
>
<LogoutIcon className="h-4 w-4 text-zinc-400" />
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col rounded-xl border-t border-zinc-800/50 bg-zinc-900">
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-6">
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<Dialog.Title
as="h3"
className="text-lg font-semibold leading-none text-zinc-100"
>
Are you sure!
</Dialog.Title>
<button
type="button"
onClick={closeModal}
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<CancelIcon width={20} height={20} className="text-zinc-300" />
</button>
</div>
<Dialog.Description className="text-sm leading-tight text-zinc-400">
<p className="mb-2">
When logout, all local data will be wiped, and restart app then
you need to start onboarding process again when you log in.
</p>
<p>
In the next version, Lume will support multi account, then you can
switch between all account s instead of logout
</p>
</Dialog.Description>
</div>
</div>
<div className="flex h-full w-full flex-col items-end justify-center overflow-y-auto px-5 py-2.5">
<div className="flex items-center gap-2">
<button
type="button"
onClick={closeModal}
className="inline-flex h-9 items-center justify-center rounded-md px-3 text-sm font-medium text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100"
>
Cancel
</button>
<button
type="button"
onClick={() => logout()}
className="inline-flex h-9 items-center justify-center rounded-md bg-red-500 px-3 text-sm font-medium text-zinc-100 hover:bg-red-600"
>
Confirm
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
}

View File

@@ -1,35 +1,37 @@
import { ActiveAccount } from "@shared/accounts/active";
import { SettingsIcon } from "@shared/icons";
import { Logout } from "@shared/logout";
import { NotificationModal } from "@shared/notification/modal";
import { useAccount } from "@utils/hooks/useAccount";
import { Link } from "react-router-dom";
import { Link } from 'react-router-dom';
import { ActiveAccount } from '@shared/accounts/active';
import { SettingsIcon } from '@shared/icons';
import { Logout } from '@shared/logout';
import { NotificationModal } from '@shared/notification/modal';
import { useAccount } from '@utils/hooks/useAccount';
export function LumeBar() {
const { status, account } = useAccount();
const { status, account } = useAccount();
return (
<div className="rounded-xl p-2 border-t border-zinc-800/50 bg-zinc-900/80 backdrop-blur-md">
<div className="flex items-center justify-between">
{status === "loading" ? (
<>
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
</>
) : (
<>
<ActiveAccount data={account} />
<NotificationModal pubkey={account.pubkey} />
</>
)}
<Link
to="/settings/general"
className="inline-flex items-center justify-center w-9 h-9 rounded-md border-t bg-zinc-800 border-zinc-700/50 transform active:translate-y-1"
>
<SettingsIcon className="w-4 h-4 text-zinc-400" />
</Link>
<Logout />
</div>
</div>
);
return (
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900/80 p-2 backdrop-blur-md">
<div className="flex items-center justify-between">
{status === 'loading' ? (
<>
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
</>
) : (
<>
<ActiveAccount data={account} />
<NotificationModal pubkey={account.pubkey} />
</>
)}
<Link
to="/settings/general"
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md border-t border-zinc-700/50 bg-zinc-800 active:translate-y-1"
>
<SettingsIcon className="h-4 w-4 text-zinc-400" />
</Link>
<Logout />
</div>
</div>
);
}

View File

@@ -1,90 +1,92 @@
import * as Tooltip from "@radix-ui/react-tooltip";
import { LoaderIcon, MediaIcon } from "@shared/icons";
import { open } from "@tauri-apps/api/dialog";
import { Body, fetch } from "@tauri-apps/api/http";
import { createBlobFromFile } from "@utils/createBlobFromFile";
import { useState } from "react";
import * as Tooltip from '@radix-ui/react-tooltip';
import { open } from '@tauri-apps/api/dialog';
import { Body, fetch } from '@tauri-apps/api/http';
import { useState } from 'react';
import { LoaderIcon, MediaIcon } from '@shared/icons';
import { createBlobFromFile } from '@utils/createBlobFromFile';
export function MediaUploader({ setState }: { setState: any }) {
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(false);
const openFileDialog = async () => {
const selected: any = await open({
multiple: false,
filters: [
{
name: "Image & Video",
extensions: ["png", "jpeg", "jpg", "gif", "mp4", "mov"],
},
],
});
if (Array.isArray(selected)) {
// user selected multiple files
} else if (selected === null) {
// user cancelled the selection
} else {
// start loading
setLoading(true);
const openFileDialog = async () => {
const selected: any = await open({
multiple: false,
filters: [
{
name: 'Image & Video',
extensions: ['png', 'jpeg', 'jpg', 'gif', 'mp4', 'mov'],
},
],
});
if (Array.isArray(selected)) {
// user selected multiple files
} else if (selected === null) {
// user cancelled the selection
} else {
// start loading
setLoading(true);
const filename = selected.split("/").pop();
const file = await createBlobFromFile(selected);
const buf = await file.arrayBuffer();
const filename = selected.split('/').pop();
const file = await createBlobFromFile(selected);
const buf = await file.arrayBuffer();
const res: { data: { file: { id: string } } } = await fetch(
"https://void.cat/upload?cli=false",
{
method: "POST",
timeout: 5,
headers: {
accept: "*/*",
"Content-Type": "application/octet-stream",
"V-Filename": filename,
"V-Description": "Upload from https://lume.nu",
"V-Strip-Metadata": "true",
},
body: Body.bytes(buf),
},
);
const res: { data: { file: { id: string } } } = await fetch(
'https://void.cat/upload?cli=false',
{
method: 'POST',
timeout: 5,
headers: {
accept: '*/*',
'Content-Type': 'application/octet-stream',
'V-Filename': filename,
'V-Description': 'Upload from https://lume.nu',
'V-Strip-Metadata': 'true',
},
body: Body.bytes(buf),
}
);
const image = `https://void.cat/d/${res.data.file.id}.webp`;
const image = `https://void.cat/d/${res.data.file.id}.webp`;
// update state
setState((prev: string) => `${prev}\n${image}`);
// stop loading
setLoading(false);
}
};
// update state
setState((prev: string) => `${prev}\n${image}`);
// stop loading
setLoading(false);
}
};
return (
<Tooltip.Provider>
<Tooltip.Root delayDuration={150}>
<Tooltip.Trigger asChild>
<button
type="button"
onClick={() => openFileDialog()}
className="group inline-flex h-6 w-6 items-center justify-center rounded bg-zinc-700 hover:bg-zinc-600"
>
{loading ? (
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
) : (
<MediaIcon
width={14}
height={14}
className="text-zinc-400 group-hover:text-zinc-200"
/>
)}
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
sideOffset={5}
>
Upload media
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
return (
<Tooltip.Provider>
<Tooltip.Root delayDuration={150}>
<Tooltip.Trigger asChild>
<button
type="button"
onClick={() => openFileDialog()}
className="group inline-flex h-6 w-6 items-center justify-center rounded bg-zinc-700 hover:bg-zinc-600"
>
{loading ? (
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
) : (
<MediaIcon
width={14}
height={14}
className="text-zinc-400 group-hover:text-zinc-200"
/>
)}
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
sideOffset={5}
>
Upload media
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
}

View File

@@ -1,65 +1,63 @@
import { ChatsList } from "@app/chat/components/list";
import { Disclosure } from "@headlessui/react";
import { AppHeader } from "@shared/appHeader";
import { Composer } from "@shared/composer/modal";
import { NavArrowDownIcon, SpaceIcon, TrendingIcon } from "@shared/icons";
import { LumeBar } from "@shared/lumeBar";
import { NavLink } from "react-router-dom";
import { twMerge } from "tailwind-merge";
import { Disclosure } from '@headlessui/react';
import { NavLink } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { ChatsList } from '@app/chat/components/list';
import { AppHeader } from '@shared/appHeader';
import { Composer } from '@shared/composer/modal';
import { NavArrowDownIcon, SpaceIcon, TrendingIcon } from '@shared/icons';
import { LumeBar } from '@shared/lumeBar';
export function Navigation() {
return (
<div className="relative flex w-[232px] flex-col gap-3 border-r border-zinc-900">
<AppHeader />
<div className="pb-20 flex flex-col gap-5 overflow-y-auto scrollbar-hide">
<div className="inlin-lflex h-8 px-3.5">
<Composer />
</div>
{/* Newsfeed */}
<div className="flex flex-col gap-0.5 px-1.5">
<div className="px-2.5">
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
Feeds
</h3>
</div>
<div className="flex flex-col">
<NavLink
to="/app/space"
preventScrollReset={true}
className={({ isActive }) =>
twMerge(
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
isActive ? "bg-zinc-900/50" : "",
)
}
>
<span className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
<SpaceIcon width={12} height={12} className="text-zinc-100" />
</span>
<span className="font-medium">Spaces</span>
</NavLink>
<NavLink
to="/app/trending"
preventScrollReset={true}
className={({ isActive }) =>
twMerge(
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
isActive ? "bg-zinc-900/50" : "",
)
}
>
<span className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
<TrendingIcon
width={12}
height={12}
className="text-zinc-100"
/>
</span>
<span className="font-medium">Trending</span>
</NavLink>
</div>
</div>
{/* Channels
return (
<div className="relative flex w-[232px] flex-col gap-3 border-r border-zinc-900">
<AppHeader />
<div className="scrollbar-hide flex flex-col gap-5 overflow-y-auto pb-20">
<div className="inlin-lflex h-8 px-3.5">
<Composer />
</div>
{/* Newsfeed */}
<div className="flex flex-col gap-0.5 px-1.5">
<div className="px-2.5">
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
Feeds
</h3>
</div>
<div className="flex flex-col">
<NavLink
to="/app/space"
preventScrollReset={true}
className={({ isActive }) =>
twMerge(
'flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200',
isActive ? 'bg-zinc-900/50' : ''
)
}
>
<span className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
<SpaceIcon width={12} height={12} className="text-zinc-100" />
</span>
<span className="font-medium">Spaces</span>
</NavLink>
<NavLink
to="/app/trending"
preventScrollReset={true}
className={({ isActive }) =>
twMerge(
'flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200',
isActive ? 'bg-zinc-900/50' : ''
)
}
>
<span className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-800/50 bg-zinc-900">
<TrendingIcon width={12} height={12} className="text-zinc-100" />
</span>
<span className="font-medium">Trending</span>
</NavLink>
</div>
</div>
{/* Channels
<Disclosure defaultOpen={true}>
{({ open }) => (
<div className="flex flex-col gap-0.5 px-1.5">
@@ -86,36 +84,32 @@ export function Navigation() {
)}
</Disclosure>
*/}
{/* Chats */}
<Disclosure defaultOpen={true}>
{({ open }) => (
<div className="flex flex-col gap-0.5 px-1.5 pb-6">
<Disclosure.Button className="flex items-center gap-1 px-3">
<div
className={`inline-flex h-5 w-5 transform items-center justify-center transition-transform duration-150 ease-in-out ${
open ? "" : "rotate-180"
}`}
>
<NavArrowDownIcon
width={12}
height={12}
className="text-zinc-700"
/>
</div>
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
Chats
</h3>
</Disclosure.Button>
<Disclosure.Panel>
<ChatsList />
</Disclosure.Panel>
</div>
)}
</Disclosure>
</div>
<div className="absolute bottom-3 left-0 px-10 w-full">
<LumeBar />
</div>
</div>
);
{/* Chats */}
<Disclosure defaultOpen={true}>
{({ open }) => (
<div className="flex flex-col gap-0.5 px-1.5 pb-6">
<Disclosure.Button className="flex items-center gap-1 px-3">
<div
className={`inline-flex h-5 w-5 transform items-center justify-center transition-transform duration-150 ease-in-out ${
open ? '' : 'rotate-180'
}`}
>
<NavArrowDownIcon width={12} height={12} className="text-zinc-700" />
</div>
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
Chats
</h3>
</Disclosure.Button>
<Disclosure.Panel>
<ChatsList />
</Disclosure.Panel>
</div>
)}
</Disclosure>
</div>
<div className="absolute bottom-3 left-0 w-full px-10">
<LumeBar />
</div>
</div>
);
}

View File

@@ -1,13 +1,13 @@
import { useNetworkStatus } from "@utils/hooks/useNetworkStatus";
import { useNetworkStatus } from '@utils/hooks/useNetworkStatus';
export function NetworkStatusIndicator() {
const isOnline = useNetworkStatus();
const isOnline = useNetworkStatus();
return (
<span
className={`absolute right-0 top-0 block h-2 w-2 -translate-y-1/2 translate-x-1/2 transform rounded-full ${
isOnline ? "bg-green-400" : "bg-red-400"
} ring-2 ring-zinc-900`}
/>
);
return (
<span
className={`absolute right-0 top-0 block h-2 w-2 -translate-y-1/2 translate-x-1/2 transform rounded-full ${
isOnline ? 'bg-green-400' : 'bg-red-400'
} ring-2 ring-zinc-900`}
/>
);
}

View File

@@ -1,41 +1,40 @@
import { MentionNote } from "@shared/notes/mentions/note";
import { ImagePreview } from "@shared/notes/preview/image";
import { LinkPreview } from "@shared/notes/preview/link";
import { VideoPreview } from "@shared/notes/preview/video";
import { ReactNode } from "react";
import { ReactNode } from 'react';
import { MentionNote } from '@shared/notes/mentions/note';
import { ImagePreview } from '@shared/notes/preview/image';
import { LinkPreview } from '@shared/notes/preview/link';
import { VideoPreview } from '@shared/notes/preview/video';
export function Kind1({
content,
truncate = false,
content,
truncate = false,
}: {
content: {
original: string;
parsed: ReactNode[];
notes: string[];
images: string[];
videos: string[];
links: string[];
};
truncate?: boolean;
content: {
original: string;
parsed: ReactNode[];
notes: string[];
images: string[];
videos: string[];
links: string[];
};
truncate?: boolean;
}) {
return (
<>
<div
className={`select-text whitespace-pre-line break-words text-base text-zinc-100 ${
truncate ? "line-clamp-3" : ""
}`}
>
{content.parsed}
</div>
{content.images.length > 0 && (
<ImagePreview urls={content.images} truncate={truncate} />
)}
{content.videos.length > 0 && <VideoPreview urls={content.videos} />}
{content.links.length > 0 && <LinkPreview urls={content.links} />}
{content.notes.length > 0 &&
content.notes.map((note: string) => (
<MentionNote key={note} id={note} />
))}
</>
);
return (
<>
<div
className={`select-text whitespace-pre-line break-words text-base text-zinc-100 ${
truncate ? 'line-clamp-3' : ''
}`}
>
{content.parsed}
</div>
{content.images.length > 0 && (
<ImagePreview urls={content.images} truncate={truncate} />
)}
{content.videos.length > 0 && <VideoPreview urls={content.videos} />}
{content.links.length > 0 && <LinkPreview urls={content.links} />}
{content.notes.length > 0 &&
content.notes.map((note: string) => <MentionNote key={note} id={note} />)}
</>
);
}

View File

@@ -1,23 +1,24 @@
import { NDKTag } from "@nostr-dev-kit/ndk";
import { Image } from "@shared/image";
import { NDKTag } from '@nostr-dev-kit/ndk';
import { Image } from '@shared/image';
function isImage(url: string) {
return /\.(jpg|jpeg|gif|png|webp|avif)$/.test(url);
return /\.(jpg|jpeg|gif|png|webp|avif)$/.test(url);
}
export function Kind1063({ metadata }: { metadata: NDKTag[] }) {
const url = metadata[0][1];
const url = metadata[0][1];
return (
<div className="mt-3">
{isImage(url) && (
<Image
src={url}
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
alt="image"
className="h-auto w-full rounded-lg object-cover"
/>
)}
</div>
);
return (
<div className="mt-3">
{isImage(url) && (
<Image
src={url}
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
alt="image"
className="h-auto w-full rounded-lg object-cover"
/>
)}
</div>
);
}

View File

@@ -1,70 +1,73 @@
import { createBlock } from "@libs/storage";
import { Kind1 } from "@shared/notes/contents/kind1";
import { Kind1063 } from "@shared/notes/contents/kind1063";
import { NoteSkeleton } from "@shared/notes/skeleton";
import { User } from "@shared/user";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useEvent } from "@utils/hooks/useEvent";
import { memo } from "react";
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { memo } from 'react';
import { createBlock } from '@libs/storage';
import { Kind1 } from '@shared/notes/contents/kind1';
import { Kind1063 } from '@shared/notes/contents/kind1063';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { User } from '@shared/user';
import { useEvent } from '@utils/hooks/useEvent';
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
const queryClient = useQueryClient();
const { status, data } = useEvent(id);
const queryClient = useQueryClient();
const { status, data } = useEvent(id);
const block = useMutation({
mutationFn: (data: any) => {
return createBlock(data.kind, data.title, data.content);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["blocks"] });
},
});
const block = useMutation({
mutationFn: (data: any) => {
return createBlock(data.kind, data.title, data.content);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['blocks'] });
},
});
const openThread = (event: any, thread: string) => {
const selection = window.getSelection();
if (selection.toString().length === 0) {
block.mutate({ kind: 2, title: "Thread", content: thread });
} else {
event.stopPropagation();
}
};
const openThread = (event: any, thread: string) => {
const selection = window.getSelection();
if (selection.toString().length === 0) {
block.mutate({ kind: 2, title: 'Thread', content: thread });
} else {
event.stopPropagation();
}
};
return (
<div
onClick={(e) => openThread(e, id)}
onKeyDown={(e) => openThread(e, id)}
className="mt-3 rounded-lg bg-zinc-800/50 border-t border-zinc-700/50 px-3 py-3"
>
{status === "loading" ? (
<NoteSkeleton />
) : status === "success" ? (
<>
<User pubkey={data.pubkey} time={data.created_at} size="small" />
<div className="mt-2">
{data.kind === 1 && (
<Kind1 content={data.content} truncate={true} />
)}
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
{data.kind !== 1 && data.kind !== 1063 && (
<div className="flex flex-col gap-2">
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
<span className="text-zinc-500 text-sm font-medium leading-none">
Kind: {data.kind}
</span>
<p className="text-fuchsia-500 text-sm leading-none">
Lume isn't fully support this kind in newsfeed
</p>
</div>
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{data.content}</p>
</div>
</div>
)}
</div>
</>
) : (
<p>Failed to fetch event</p>
)}
</div>
);
return (
<div
onClick={(e) => openThread(e, id)}
onKeyDown={(e) => openThread(e, id)}
role="button"
tabIndex={0}
className="mt-3 rounded-lg border-t border-zinc-700/50 bg-zinc-800/50 px-3 py-3"
>
{status === 'loading' ? (
<NoteSkeleton />
) : status === 'success' ? (
<>
<User pubkey={data.pubkey} time={data.created_at} size="small" />
<div className="mt-2">
{data.kind === 1 && <Kind1 content={data.content} truncate={true} />}
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
{data.kind !== 1 && data.kind !== 1063 && (
<div className="flex flex-col gap-2">
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
<span className="text-sm font-medium leading-none text-zinc-500">
Kind: {data.kind}
</span>
<p className="text-sm leading-none text-fuchsia-500">
Lume isn&apos;t fully support this kind in newsfeed
</p>
</div>
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{data.content}</p>
</div>
</div>
)}
</div>
</>
) : (
<p>Failed to fetch event</p>
)}
</div>
);
});

View File

@@ -1,16 +1,17 @@
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import { Link } from "react-router-dom";
import { Link } from 'react-router-dom';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
export function MentionUser({ pubkey }: { pubkey: string }) {
const { user } = useProfile(pubkey);
const { user } = useProfile(pubkey);
return (
<Link
to={`/app/user/${pubkey}`}
className="text-fuchsia-500 hover:text-fuchsia-600 no-underline font-normal"
>
@{user?.name || user?.displayName || shortenKey(pubkey)}
</Link>
);
return (
<Link
to={`/app/user/${pubkey}`}
className="font-normal text-fuchsia-500 no-underline hover:text-fuchsia-600"
>
@{user?.name || user?.displayName || shortenKey(pubkey)}
</Link>
);
}

View File

@@ -1,171 +1,171 @@
import { createBlock, createReplyNote } from "@libs/storage";
import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
import * as Tooltip from "@radix-ui/react-tooltip";
import { LoaderIcon, ReplyIcon, RepostIcon, ZapIcon } from "@shared/icons";
import { ThreadIcon } from "@shared/icons/thread";
import { NoteReply } from "@shared/notes/metadata/reply";
import { NoteRepost } from "@shared/notes/metadata/repost";
import { NoteZap } from "@shared/notes/metadata/zap";
import { RelayContext } from "@shared/relayProvider";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { decode } from "light-bolt11-decoder";
import { useContext } from "react";
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
import * as Tooltip from '@radix-ui/react-tooltip';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { decode } from 'light-bolt11-decoder';
import { useContext } from 'react';
import { createBlock, createReplyNote } from '@libs/storage';
import { LoaderIcon, ReplyIcon, RepostIcon, ZapIcon } from '@shared/icons';
import { ThreadIcon } from '@shared/icons/thread';
import { NoteReply } from '@shared/notes/metadata/reply';
import { NoteRepost } from '@shared/notes/metadata/repost';
import { NoteZap } from '@shared/notes/metadata/zap';
import { RelayContext } from '@shared/relayProvider';
export function NoteMetadata({
id,
rootID,
eventPubkey,
id,
rootID,
eventPubkey,
}: {
id: string;
rootID?: string;
eventPubkey: string;
id: string;
rootID?: string;
eventPubkey: string;
}) {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const { status, data } = useQuery(["note-metadata", id], async () => {
let replies = 0;
let reposts = 0;
let zap = 0;
const { status, data } = useQuery(['note-metadata', id], async () => {
let replies = 0;
let reposts = 0;
let zap = 0;
const filter: NDKFilter = {
"#e": [id],
kinds: [1, 6, 9735],
};
const filter: NDKFilter = {
'#e': [id],
kinds: [1, 6, 9735],
};
const events = await ndk.fetchEvents(filter);
events.forEach((event: NDKEvent) => {
switch (event.kind) {
case 1:
replies += 1;
createReplyNote(
id,
event.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at,
);
break;
case 6:
reposts += 1;
break;
case 9735: {
const bolt11 = event.tags.find((tag) => tag[0] === "bolt11")[1];
if (bolt11) {
const decoded = decode(bolt11);
const amount = decoded.sections.find(
(item) => item.name === "amount",
);
const sats = amount.value / 1000;
zap += sats;
}
break;
}
default:
break;
}
});
const events = await ndk.fetchEvents(filter);
events.forEach((event: NDKEvent) => {
switch (event.kind) {
case 1:
replies += 1;
createReplyNote(
id,
event.id,
event.pubkey,
event.kind,
event.tags,
event.content,
event.created_at
);
break;
case 6:
reposts += 1;
break;
case 9735: {
const bolt11 = event.tags.find((tag) => tag[0] === 'bolt11')[1];
if (bolt11) {
const decoded = decode(bolt11);
const amount = decoded.sections.find((item) => item.name === 'amount');
const sats = amount.value / 1000;
zap += sats;
}
break;
}
default:
break;
}
});
return { replies, reposts, zap };
});
return { replies, reposts, zap };
});
const block = useMutation({
mutationFn: (data: any) => {
return createBlock(data.kind, data.title, data.content);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["blocks"] });
},
});
const block = useMutation({
mutationFn: (data: any) => {
return createBlock(data.kind, data.title, data.content);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['blocks'] });
},
});
const openThread = (thread: string) => {
const selection = window.getSelection();
if (selection.toString().length === 0) {
block.mutate({ kind: 2, title: "Thread", content: thread });
} else {
event.stopPropagation();
}
};
const openThread = (thread: string) => {
const selection = window.getSelection();
if (selection.toString().length === 0) {
block.mutate({ kind: 2, title: 'Thread', content: thread });
} else {
event.stopPropagation();
}
};
if (status === "loading") {
return (
<div className="inline-flex items-center w-full h-12 mt-2">
<div className="w-20 group inline-flex items-center gap-1.5">
<ReplyIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-green-400"
/>
<LoaderIcon
width={12}
height={12}
className="animate-spin text-black dark:text-zinc-100"
/>
</div>
<div className="w-20 group inline-flex items-center gap-1.5">
<RepostIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-green-400"
/>
<LoaderIcon
width={12}
height={12}
className="animate-spin text-black dark:text-zinc-100"
/>
</div>
<div className="w-20 group inline-flex items-center gap-1.5">
<ZapIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-green-400"
/>
<LoaderIcon
width={12}
height={12}
className="animate-spin text-black dark:text-zinc-100"
/>
</div>
</div>
);
}
if (status === 'loading') {
return (
<div className="mt-2 inline-flex h-12 w-full items-center">
<div className="group inline-flex w-20 items-center gap-1.5">
<ReplyIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-green-400"
/>
<LoaderIcon
width={12}
height={12}
className="animate-spin text-black dark:text-zinc-100"
/>
</div>
<div className="group inline-flex w-20 items-center gap-1.5">
<RepostIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-green-400"
/>
<LoaderIcon
width={12}
height={12}
className="animate-spin text-black dark:text-zinc-100"
/>
</div>
<div className="group inline-flex w-20 items-center gap-1.5">
<ZapIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-green-400"
/>
<LoaderIcon
width={12}
height={12}
className="animate-spin text-black dark:text-zinc-100"
/>
</div>
</div>
);
}
return (
<Tooltip.Provider>
<div className="inline-flex items-center justify-between w-full h-12 mt-2">
<div className="inline-flex justify-between items-center">
<NoteReply
id={id}
rootID={rootID}
pubkey={eventPubkey}
replies={data.replies}
/>
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
<NoteZap zaps={data.zap} />
</div>
<Tooltip.Root delayDuration={150}>
<Tooltip.Trigger asChild>
<button
type="button"
onClick={() => openThread(id)}
className="w-6 h-6 inline-flex items-center justify-center rounded border-t border-zinc-700/50 bg-zinc-800 hover:bg-zinc-700"
>
<ThreadIcon className="w-4 h-4 text-zinc-400" />
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
sideOffset={5}
>
Open thread
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
</Tooltip.Provider>
);
return (
<Tooltip.Provider>
<div className="mt-2 inline-flex h-12 w-full items-center justify-between">
<div className="inline-flex items-center justify-between">
<NoteReply
id={id}
rootID={rootID}
pubkey={eventPubkey}
replies={data.replies}
/>
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
<NoteZap zaps={data.zap} />
</div>
<Tooltip.Root delayDuration={150}>
<Tooltip.Trigger asChild>
<button
type="button"
onClick={() => openThread(id)}
className="inline-flex h-6 w-6 items-center justify-center rounded border-t border-zinc-700/50 bg-zinc-800 hover:bg-zinc-700"
>
<ThreadIcon className="h-4 w-4 text-zinc-400" />
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
sideOffset={5}
>
Open thread
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
</Tooltip.Provider>
);
}

View File

@@ -1,41 +1,49 @@
import * as Tooltip from "@radix-ui/react-tooltip";
import { ReplyIcon } from "@shared/icons";
import { useComposer } from "@stores/composer";
import { compactNumber } from "@utils/number";
import * as Tooltip from '@radix-ui/react-tooltip';
import { ReplyIcon } from '@shared/icons';
import { useComposer } from '@stores/composer';
import { compactNumber } from '@utils/number';
export function NoteReply({
id,
rootID,
pubkey,
replies,
}: { id: string; rootID?: string; pubkey: string; replies: number }) {
const setReply = useComposer((state) => state.setReply);
id,
rootID,
pubkey,
replies,
}: {
id: string;
rootID?: string;
pubkey: string;
replies: number;
}) {
const setReply = useComposer((state) => state.setReply);
return (
<Tooltip.Root delayDuration={150}>
<button
type="button"
onClick={() => setReply(id, rootID, pubkey)}
className="group w-20 h-6 group inline-flex items-center gap-1.5"
>
<Tooltip.Trigger asChild>
<span className="inline-flex items-center justify-center w-6 h-6 rounded group-hover:bg-zinc-800">
<ReplyIcon className="w-4 h-4 text-zinc-400 group-hover:text-green-500" />
</span>
</Tooltip.Trigger>
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
{compactNumber.format(replies)}
</span>
</button>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
sideOffset={5}
>
Quick reply
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
);
return (
<Tooltip.Root delayDuration={150}>
<button
type="button"
onClick={() => setReply(id, rootID, pubkey)}
className="group group inline-flex h-6 w-20 items-center gap-1.5"
>
<Tooltip.Trigger asChild>
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
<ReplyIcon className="h-4 w-4 text-zinc-400 group-hover:text-green-500" />
</span>
</Tooltip.Trigger>
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
{compactNumber.format(replies)}
</span>
</button>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
sideOffset={5}
>
Quick reply
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
);
}

View File

@@ -1,40 +1,47 @@
import * as Tooltip from "@radix-ui/react-tooltip";
import { RepostIcon } from "@shared/icons";
import { useComposer } from "@stores/composer";
import { compactNumber } from "@utils/number";
import * as Tooltip from '@radix-ui/react-tooltip';
import { RepostIcon } from '@shared/icons';
import { useComposer } from '@stores/composer';
import { compactNumber } from '@utils/number';
export function NoteRepost({
id,
pubkey,
reposts,
}: { id: string; pubkey: string; reposts: number }) {
const setRepost = useComposer((state) => state.setRepost);
id,
pubkey,
reposts,
}: {
id: string;
pubkey: string;
reposts: number;
}) {
const setRepost = useComposer((state) => state.setRepost);
return (
<Tooltip.Root delayDuration={150}>
<button
type="button"
onClick={() => setRepost(id, pubkey)}
className="group w-20 h-6 group inline-flex items-center gap-1.5"
>
<Tooltip.Trigger asChild>
<span className="inline-flex items-center justify-center w-6 h-6 rounded group-hover:bg-zinc-800">
<RepostIcon className="w-4 h-4 text-zinc-400 group-hover:text-blue-400" />
</span>
</Tooltip.Trigger>
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
{compactNumber.format(reposts)}
</span>
</button>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
sideOffset={5}
>
Repost
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
);
return (
<Tooltip.Root delayDuration={150}>
<button
type="button"
onClick={() => setRepost(id, pubkey)}
className="group group inline-flex h-6 w-20 items-center gap-1.5"
>
<Tooltip.Trigger asChild>
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
<RepostIcon className="h-4 w-4 text-zinc-400 group-hover:text-blue-400" />
</span>
</Tooltip.Trigger>
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
{compactNumber.format(reposts)}
</span>
</button>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
sideOffset={5}
>
Repost
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
);
}

View File

@@ -1,32 +1,34 @@
import * as Tooltip from "@radix-ui/react-tooltip";
import { ZapIcon } from "@shared/icons";
import { compactNumber } from "@utils/number";
import * as Tooltip from '@radix-ui/react-tooltip';
import { ZapIcon } from '@shared/icons';
import { compactNumber } from '@utils/number';
export function NoteZap({ zaps }: { zaps: number }) {
return (
<Tooltip.Root delayDuration={150}>
<button
type="button"
className="group w-20 h-6 group inline-flex items-center gap-1.5"
>
<Tooltip.Trigger asChild>
<span className="inline-flex items-center justify-center w-6 h-6 rounded group-hover:bg-zinc-800">
<ZapIcon className="w-4 h-4 text-zinc-400 group-hover:text-orange-400" />
</span>
</Tooltip.Trigger>
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
{compactNumber.format(zaps)}
</span>
</button>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade select-none text-sm rounded-md text-zinc-100 bg-zinc-800/80 backdrop-blur-lg px-3.5 py-1.5 leading-none will-change-[transform,opacity]"
sideOffset={5}
>
Coming Soon
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
);
return (
<Tooltip.Root delayDuration={150}>
<button
type="button"
className="group group inline-flex h-6 w-20 items-center gap-1.5"
>
<Tooltip.Trigger asChild>
<span className="inline-flex h-6 w-6 items-center justify-center rounded group-hover:bg-zinc-800">
<ZapIcon className="h-4 w-4 text-zinc-400 group-hover:text-orange-400" />
</span>
</Tooltip.Trigger>
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
{compactNumber.format(zaps)}
</span>
</button>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 select-none rounded-md bg-zinc-800/80 px-3.5 py-1.5 text-sm leading-none text-zinc-100 backdrop-blur-lg will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
sideOffset={5}
>
Coming Soon
<Tooltip.Arrow className="fill-zinc-800/80 backdrop-blur-lg" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
);
}

View File

@@ -1,89 +1,86 @@
import { Kind1 } from "@shared/notes/contents/kind1";
import { Kind1063 } from "@shared/notes/contents/kind1063";
import { NoteMetadata } from "@shared/notes/metadata";
import { NoteParent } from "@shared/notes/parent";
import { Repost } from "@shared/notes/repost";
import { User } from "@shared/user";
import { parser } from "@utils/parser";
import { LumeEvent } from "@utils/types";
import { useMemo } from "react";
import { useMemo } from 'react';
import { Kind1 } from '@shared/notes/contents/kind1';
import { Kind1063 } from '@shared/notes/contents/kind1063';
import { NoteMetadata } from '@shared/notes/metadata';
import { NoteParent } from '@shared/notes/parent';
import { Repost } from '@shared/notes/repost';
import { User } from '@shared/user';
import { parser } from '@utils/parser';
import { LumeEvent } from '@utils/types';
interface Note {
event: LumeEvent;
block?: number;
event: LumeEvent;
}
export function Note({ event, block }: Note) {
const isRepost = event.kind === 6;
export function Note({ event }: Note) {
const isRepost = event.kind === 6;
const renderParent = useMemo(() => {
if (!isRepost && event.parent_id && event.parent_id !== event.event_id) {
return <NoteParent id={event.parent_id} />;
} else {
return null;
}
}, [event.parent_id]);
const renderParent = useMemo(() => {
if (!isRepost && event.parent_id && event.parent_id !== event.event_id) {
return <NoteParent id={event.parent_id} />;
} else {
return null;
}
}, [event.parent_id]);
const renderRepost = useMemo(() => {
if (isRepost) {
return <Repost event={event} />;
} else {
return null;
}
}, [event.kind]);
const renderRepost = useMemo(() => {
if (isRepost) {
return <Repost event={event} />;
} else {
return null;
}
}, [event.kind]);
const renderContent = useMemo(() => {
switch (event.kind) {
case 1: {
const content = parser(event);
return <Kind1 content={content} />;
}
case 6:
return null;
case 1063:
return <Kind1063 metadata={event.tags} />;
default:
return (
<div className="flex flex-col gap-2">
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
<span className="text-zinc-500 text-sm font-medium leading-none">
Kind: {event.kind}
</span>
<p className="text-fuchsia-500 text-sm leading-none">
Lume isn't fully support this kind in newsfeed
</p>
</div>
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{event.content}</p>
</div>
</div>
);
}
}, [event.kind]);
const renderContent = useMemo(() => {
switch (event.kind) {
case 1: {
const content = parser(event);
return <Kind1 content={content} />;
}
case 6:
return null;
case 1063:
return <Kind1063 metadata={event.tags} />;
default:
return (
<div className="flex flex-col gap-2">
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
<span className="text-sm font-medium leading-none text-zinc-500">
Kind: {event.kind}
</span>
<p className="text-sm leading-none text-fuchsia-500">
Lume isn&apos;t fully support this kind in newsfeed
</p>
</div>
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{event.content}</p>
</div>
</div>
);
}
}, [event.kind]);
return (
<div className="h-min w-full px-3 py-1.5">
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
{renderParent}
<div className="flex flex-col">
<User
pubkey={event.pubkey}
time={event.created_at}
repost={isRepost}
/>
<div className="-mt-6 pl-[49px]">
{renderContent}
{!isRepost && (
<NoteMetadata
id={event.event_id}
rootID={event.parent_id}
eventPubkey={event.pubkey}
/>
)}
</div>
</div>
{renderRepost}
</div>
</div>
);
return (
<div className="h-min w-full px-3 py-1.5">
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-3 pt-3">
{renderParent}
<div className="flex flex-col">
<User pubkey={event.pubkey} time={event.created_at} repost={isRepost} />
<div className="-mt-6 pl-[49px]">
{renderContent}
{!isRepost && (
<NoteMetadata
id={event.event_id}
rootID={event.parent_id}
eventPubkey={event.pubkey}
/>
)}
</div>
</div>
{renderRepost}
</div>
</div>
);
}

View File

@@ -1,48 +1,46 @@
import { Kind1 } from "@shared/notes/contents/kind1";
import { Kind1063 } from "@shared/notes/contents/kind1063";
import { NoteMetadata } from "@shared/notes/metadata";
import { NoteSkeleton } from "@shared/notes/skeleton";
import { User } from "@shared/user";
import { useEvent } from "@utils/hooks/useEvent";
import { Kind1 } from '@shared/notes/contents/kind1';
import { Kind1063 } from '@shared/notes/contents/kind1063';
import { NoteMetadata } from '@shared/notes/metadata';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { User } from '@shared/user';
import { useEvent } from '@utils/hooks/useEvent';
export function NoteParent({ id }: { id: string }) {
const { status, data } = useEvent(id);
const { status, data } = useEvent(id);
return (
<div className="relative flex flex-col pb-6">
<div className="absolute left-[18px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
{status === "loading" ? (
<NoteSkeleton />
) : status === "success" ? (
<>
<User pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-6 pl-[49px]">
{data.kind === 1 && <Kind1 content={data.content} />}
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
{data.kind !== 1 && data.kind !== 1063 && (
<div className="flex flex-col gap-2">
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
<span className="text-zinc-500 text-sm font-medium leading-none">
Kind: {data.kind}
</span>
<p className="text-fuchsia-500 text-sm leading-none">
Lume isn't fully support this kind in newsfeed
</p>
</div>
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{data.content || data.toString()}</p>
</div>
</div>
)}
<NoteMetadata
id={data.event_id || data.id}
eventPubkey={data.pubkey}
/>
</div>
</>
) : (
<p>Failed to fetch event</p>
)}
</div>
);
return (
<div className="relative flex flex-col pb-6">
<div className="absolute left-[18px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
{status === 'loading' ? (
<NoteSkeleton />
) : status === 'success' ? (
<>
<User pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-6 pl-[49px]">
{data.kind === 1 && <Kind1 content={data.content} />}
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
{data.kind !== 1 && data.kind !== 1063 && (
<div className="flex flex-col gap-2">
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
<span className="text-sm font-medium leading-none text-zinc-500">
Kind: {data.kind}
</span>
<p className="text-sm leading-none text-fuchsia-500">
Lume isn&apos;t fully support this kind in newsfeed
</p>
</div>
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{data.content || data.toString()}</p>
</div>
</div>
)}
<NoteMetadata id={data.event_id || data.id} eventPubkey={data.pubkey} />
</div>
</>
) : (
<p>Failed to fetch event</p>
)}
</div>
);
}

View File

@@ -1,25 +1,22 @@
import { Image } from "@shared/image";
import { Image } from '@shared/image';
export function ImagePreview({
urls,
truncate,
}: { urls: string[]; truncate?: boolean }) {
return (
<div className="mt-3 max-w-[420px] overflow-hidden">
<div className="flex flex-col gap-2">
{urls.map((url) => (
<div key={url} className="min-w-0 grow-0 shrink-0 basis-full">
<Image
src={url}
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
alt="image"
className={`${
truncate ? "h-auto max-h-[300px]" : "h-auto"
} w-full border border-zinc-800/50 rounded-lg object-cover`}
/>
</div>
))}
</div>
</div>
);
export function ImagePreview({ urls, truncate }: { urls: string[]; truncate?: boolean }) {
return (
<div className="mt-3 max-w-[420px] overflow-hidden">
<div className="flex flex-col gap-2">
{urls.map((url) => (
<div key={url} className="min-w-0 shrink-0 grow-0 basis-full">
<Image
src={url}
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
alt="image"
className={`${
truncate ? 'h-auto max-h-[300px]' : 'h-auto'
} w-full rounded-lg border border-zinc-800/50 object-cover`}
/>
</div>
))}
</div>
</div>
);
}

View File

@@ -1,64 +1,62 @@
import { Image } from "@shared/image";
import { useOpenGraph } from "@utils/hooks/useOpenGraph";
import { Image } from '@shared/image';
import { useOpenGraph } from '@utils/hooks/useOpenGraph';
export function LinkPreview({ urls }: { urls: string[] }) {
const domain = new URL(urls[0]);
const { status, data, error } = useOpenGraph(urls[0]);
const domain = new URL(urls[0]);
const { status, data, error } = useOpenGraph(urls[0]);
return (
<div className="mt-3 max-w-[420px] overflow-hidden rounded-lg bg-zinc-800">
{status === "loading" ? (
<div className="flex flex-col">
<div className="w-full h-44 bg-zinc-700 animate-pulse" />
<div className="flex flex-col gap-2 px-3 py-3">
<div className="w-2/3 h-3 rounded bg-zinc-700 animate-pulse" />
<div className="w-3/4 h-3 rounded bg-zinc-700 animate-pulse" />
<span className="mt-2.5 leading-none text-sm text-zinc-500">
{domain.hostname}
</span>
</div>
</div>
) : (
<a
className="flex flex-col rounded-lg border border-zinc-800/50"
href={urls[0]}
target="_blank"
rel="noreferrer"
>
{error ? (
<div className="px-3 py-3">
<p className="text-sm text-zinc-400 break-all line-clamp-3">
Can't fetch open graph, click to open webpage
</p>
</div>
) : (
<>
<Image
src={
data.images?.[0] ||
"https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
}
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
alt={urls[0]}
className="w-full h-44 object-cover rounded-t-lg"
/>
<div className="flex flex-col gap-2 px-3 py-3">
<h5 className="leading-none font-medium text-zinc-200 line-clamp-1">
{data.title}
</h5>
{data.description && (
<p className="text-sm text-zinc-400 break-all line-clamp-3">
{data.description}
</p>
)}
<span className="mt-2.5 leading-none text-sm text-zinc-500">
{domain.hostname}
</span>
</div>
</>
)}
</a>
)}
</div>
);
return (
<div className="mt-3 max-w-[420px] overflow-hidden rounded-lg bg-zinc-800">
{status === 'loading' ? (
<div className="flex flex-col">
<div className="h-44 w-full animate-pulse bg-zinc-700" />
<div className="flex flex-col gap-2 px-3 py-3">
<div className="h-3 w-2/3 animate-pulse rounded bg-zinc-700" />
<div className="h-3 w-3/4 animate-pulse rounded bg-zinc-700" />
<span className="mt-2.5 text-sm leading-none text-zinc-500">
{domain.hostname}
</span>
</div>
</div>
) : (
<a
className="flex flex-col rounded-lg border border-zinc-800/50"
href={urls[0]}
target="_blank"
rel="noreferrer"
>
{error ? (
<div className="px-3 py-3">
<p className="line-clamp-3 break-all text-sm text-zinc-400">
Can&apos;t fetch open graph, click to open webpage
</p>
</div>
) : (
<>
<Image
src={data.images?.[0] || 'https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW'}
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
alt={urls[0]}
className="h-44 w-full rounded-t-lg object-cover"
/>
<div className="flex flex-col gap-2 px-3 py-3">
<h5 className="line-clamp-1 font-medium leading-none text-zinc-200">
{data.title}
</h5>
{data.description && (
<p className="line-clamp-3 break-all text-sm text-zinc-400">
{data.description}
</p>
)}
<span className="mt-2.5 text-sm leading-none text-zinc-500">
{domain.hostname}
</span>
</div>
</>
)}
</a>
)}
</div>
);
}

View File

@@ -1,19 +1,19 @@
import ReactPlayer from "react-player/es6";
import ReactPlayer from 'react-player/es6';
export function VideoPreview({ urls }: { urls: string[] }) {
return (
<div className="relative mt-3 max-w-[420px] flex w-full flex-col gap-2">
{urls.map((url) => (
<ReactPlayer
key={url}
url={url}
width="100%"
height="auto"
className="!h-auto object-fill rounded-lg overflow-hidden"
controls={true}
pip={true}
/>
))}
</div>
);
return (
<div className="relative mt-3 flex w-full max-w-[420px] flex-col gap-2">
{urls.map((url) => (
<ReactPlayer
key={url}
url={url}
width="100%"
height="auto"
className="!h-auto overflow-hidden rounded-lg object-fill"
controls={true}
pip={true}
/>
))}
</div>
);
}

View File

@@ -1,77 +1,82 @@
import { usePublish } from "@libs/ndk";
import { Button } from "@shared/button";
import { Image } from "@shared/image";
import { DEFAULT_AVATAR, FULL_RELAYS } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import { useState } from "react";
import { useState } from 'react';
import { usePublish } from '@libs/ndk';
import { Button } from '@shared/button';
import { Image } from '@shared/image';
import { DEFAULT_AVATAR, FULL_RELAYS } from '@stores/constants';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
export function NoteReplyForm({
rootID,
userPubkey,
}: { rootID: string; userPubkey: string }) {
const publish = usePublish();
const { status, user } = useProfile(userPubkey);
const [value, setValue] = useState("");
rootID,
userPubkey,
}: {
rootID: string;
userPubkey: string;
}) {
const publish = usePublish();
const { status, user } = useProfile(userPubkey);
const [value, setValue] = useState('');
const submit = () => {
const tags = [["e", rootID, FULL_RELAYS[0], "root"]];
const submit = () => {
const tags = [['e', rootID, FULL_RELAYS[0], 'root']];
// publish event
publish({ content: value, kind: 1, tags });
// publish event
publish({ content: value, kind: 1, tags });
// reset form
setValue("");
};
// reset form
setValue('');
};
return (
<div className="flex flex-col">
<div className="relative w-full flex-1 overflow-hidden">
<textarea
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Reply to this thread..."
className="relative h-20 w-full resize-none rounded-md px-5 py-3 text-base bg-transparent !outline-none placeholder:text-zinc-400 dark:text-zinc-100 dark:placeholder:text-zinc-500"
spellCheck={false}
/>
</div>
<div className="border-t border-zinc-800 w-full py-3 px-5">
{status === "loading" ? (
<div>
<p>Loading...</p>
</div>
) : (
<div className="flex w-full items-center justify-between">
<div className="inline-flex items-center gap-2">
<div className="relative h-9 w-9 shrink-0 rounded">
<Image
src={user.image}
fallback={DEFAULT_AVATAR}
alt={userPubkey}
className="h-9 w-9 rounded-md bg-white object-cover"
/>
</div>
<div>
<p className="mb-px leading-none text-sm text-zinc-400">
Reply as
</p>
<p className="leading-none text-sm font-medium text-zinc-100">
{user.nip05 || user.name || shortenKey(userPubkey)}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
onClick={() => submit()}
disabled={value.length === 0 ? true : false}
preset="publish"
>
Reply
</Button>
</div>
</div>
)}
</div>
</div>
);
return (
<div className="flex flex-col">
<div className="relative w-full flex-1 overflow-hidden">
<textarea
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Reply to this thread..."
className="relative h-20 w-full resize-none rounded-md bg-transparent px-5 py-3 text-base !outline-none placeholder:text-zinc-400 dark:text-zinc-100 dark:placeholder:text-zinc-500"
spellCheck={false}
/>
</div>
<div className="w-full border-t border-zinc-800 px-5 py-3">
{status === 'loading' ? (
<div>
<p>Loading...</p>
</div>
) : (
<div className="flex w-full items-center justify-between">
<div className="inline-flex items-center gap-2">
<div className="relative h-9 w-9 shrink-0 rounded">
<Image
src={user.image}
fallback={DEFAULT_AVATAR}
alt={userPubkey}
className="h-9 w-9 rounded-md bg-white object-cover"
/>
</div>
<div>
<p className="mb-px text-sm leading-none text-zinc-400">Reply as</p>
<p className="text-sm font-medium leading-none text-zinc-100">
{user.nip05 || user.name || shortenKey(userPubkey)}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button
onClick={() => submit()}
disabled={value.length === 0 ? true : false}
preset="publish"
>
Reply
</Button>
</div>
</div>
)}
</div>
</div>
);
}

View File

@@ -1,20 +1,21 @@
import { Kind1 } from "@shared/notes/contents/kind1";
import { NoteMetadata } from "@shared/notes/metadata";
import { User } from "@shared/user";
import { parser } from "@utils/parser";
import { Kind1 } from '@shared/notes/contents/kind1';
import { NoteMetadata } from '@shared/notes/metadata';
import { User } from '@shared/user';
import { parser } from '@utils/parser';
export function Reply({ data }: { data: any }) {
const content = parser(data);
const content = parser(data);
return (
<div className="flex h-min min-h-min w-full select-text flex-col px-3 pt-5 mb-3 rounded-md bg-zinc-900">
<div className="flex flex-col">
<User pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-[20px] pl-[50px]">
<Kind1 content={content} />
<NoteMetadata id={data.event_id} eventPubkey={data.pubkey} />
</div>
</div>
</div>
);
return (
<div className="mb-3 flex h-min min-h-min w-full select-text flex-col rounded-md bg-zinc-900 px-3 pt-5">
<div className="flex flex-col">
<User pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-[20px] pl-[50px]">
<Kind1 content={content} />
<NoteMetadata id={data.event_id} eventPubkey={data.pubkey} />
</div>
</div>
</div>
);
}

View File

@@ -1,44 +1,44 @@
import { getReplies } from "@libs/storage";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { Reply } from "@shared/notes/replies/item";
import { useQuery } from "@tanstack/react-query";
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { getReplies } from '@libs/storage';
import { Reply } from '@shared/notes/replies/item';
export function RepliesList({ parent_id }: { parent_id: string }) {
const { status, data } = useQuery(["replies", parent_id], async () => {
return await getReplies(parent_id);
});
const { status, data } = useQuery(['replies', parent_id], async () => {
return await getReplies(parent_id);
});
return (
<div className="mt-5">
<div className="mb-2">
<h5 className="text-lg font-semibold text-zinc-300">Replies</h5>
</div>
<div className="flex flex-col">
{status === "loading" ? (
<div className="flex gap-2 px-3 py-4">
<div className="relative h-9 w-9 shrink animate-pulse rounded-md bg-zinc-800" />
<div className="flex w-full flex-1 flex-col justify-center gap-1">
<div className="flex items-baseline gap-2 text-base">
<div className="h-2.5 w-20 animate-pulse rounded-sm bg-zinc-800" />
</div>
<div className="h-4 w-44 animate-pulse rounded-sm bg-zinc-800" />
</div>
</div>
) : data.length === 0 ? (
<div className="px=3">
<div className="w-full flex items-center justify-center rounded-md bg-zinc-900">
<div className="py-6 flex flex-col items-center justify-center gap-2">
<h3 className="text-3xl">👋</h3>
<p className="leading-none text-zinc-400">
Share your thought on it...
</p>
</div>
</div>
</div>
) : (
data.map((event: NDKEvent) => <Reply key={event.id} data={event} />)
)}
</div>
</div>
);
return (
<div className="mt-5">
<div className="mb-2">
<h5 className="text-lg font-semibold text-zinc-300">Replies</h5>
</div>
<div className="flex flex-col">
{status === 'loading' ? (
<div className="flex gap-2 px-3 py-4">
<div className="relative h-9 w-9 shrink animate-pulse rounded-md bg-zinc-800" />
<div className="flex w-full flex-1 flex-col justify-center gap-1">
<div className="flex items-baseline gap-2 text-base">
<div className="h-2.5 w-20 animate-pulse rounded-sm bg-zinc-800" />
</div>
<div className="h-4 w-44 animate-pulse rounded-sm bg-zinc-800" />
</div>
</div>
) : data.length === 0 ? (
<div className="px=3">
<div className="flex w-full items-center justify-center rounded-md bg-zinc-900">
<div className="flex flex-col items-center justify-center gap-2 py-6">
<h3 className="text-3xl">👋</h3>
<p className="leading-none text-zinc-400">Share your thought on it...</p>
</div>
</div>
</div>
) : (
data.map((event: NDKEvent) => <Reply key={event.id} data={event} />)
)}
</div>
</div>
);
}

View File

@@ -1,51 +1,49 @@
import { Kind1 } from "@shared/notes/contents/kind1";
import { Kind1063 } from "@shared/notes/contents/kind1063";
import { NoteMetadata } from "@shared/notes/metadata";
import { NoteSkeleton } from "@shared/notes/skeleton";
import { User } from "@shared/user";
import { useEvent } from "@utils/hooks/useEvent";
import { getRepostID } from "@utils/transform";
import { LumeEvent } from "@utils/types";
import { Kind1 } from '@shared/notes/contents/kind1';
import { Kind1063 } from '@shared/notes/contents/kind1063';
import { NoteMetadata } from '@shared/notes/metadata';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { User } from '@shared/user';
import { useEvent } from '@utils/hooks/useEvent';
import { getRepostID } from '@utils/transform';
import { LumeEvent } from '@utils/types';
export function Repost({ event }: { event: LumeEvent }) {
const repostID = getRepostID(event.tags);
const { status, data } = useEvent(repostID);
const repostID = getRepostID(event.tags);
const { status, data } = useEvent(repostID);
return (
<div className="relative flex flex-col mt-12">
<div className="absolute left-[18px] -top-10 h-[50px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
{status === "loading" ? (
<NoteSkeleton />
) : status === "success" ? (
<>
<User pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-6 pl-[49px]">
{data.kind === 1 && <Kind1 content={data.content} />}
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
{data.kind !== 1 && data.kind !== 1063 && (
<div className="flex flex-col gap-2">
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
<span className="text-zinc-500 text-sm font-medium leading-none">
Kind: {data.kind}
</span>
<p className="text-fuchsia-500 text-sm leading-none">
Lume isn't fully support this kind in newsfeed
</p>
</div>
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{data.content || data.toString()}</p>
</div>
</div>
)}
<NoteMetadata
id={data.event_id || data.id}
eventPubkey={data.pubkey}
/>
</div>
</>
) : (
<p>Failed to fetch event</p>
)}
</div>
);
return (
<div className="relative mt-12 flex flex-col">
<div className="absolute -top-10 left-[18px] h-[50px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
{status === 'loading' ? (
<NoteSkeleton />
) : status === 'success' ? (
<>
<User pubkey={data.pubkey} time={data.created_at} />
<div className="-mt-6 pl-[49px]">
{data.kind === 1 && <Kind1 content={data.content} />}
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
{data.kind !== 1 && data.kind !== 1063 && (
<div className="flex flex-col gap-2">
<div className="inline-flex flex-col gap-1 rounded-md bg-zinc-800 px-2 py-2">
<span className="text-sm font-medium leading-none text-zinc-500">
Kind: {data.kind}
</span>
<p className="text-sm leading-none text-fuchsia-500">
Lume isn&apos;t fully support this kind in newsfeed
</p>
</div>
<div className="select-text whitespace-pre-line break-words text-base text-zinc-100">
<p>{data.content || data.toString()}</p>
</div>
</div>
)}
<NoteMetadata id={data.event_id || data.id} eventPubkey={data.pubkey} />
</div>
</>
) : (
<p>Failed to fetch event</p>
)}
</div>
);
}

View File

@@ -1,19 +1,19 @@
export function NoteSkeleton() {
return (
<div className="flex h-min flex-col pb-3">
<div className="flex items-start gap-3">
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
<div className="flex flex-col gap-0.5">
<div className="h-4 w-20 rounded bg-zinc-700" />
</div>
</div>
<div className="-mt-5 animate-pulse pl-[49px]">
<div className="flex flex-col gap-1">
<div className="h-4 w-full rounded-sm bg-zinc-700" />
<div className="h-3 w-2/3 rounded-sm bg-zinc-700" />
<div className="h-3 w-1/2 rounded-sm bg-zinc-700" />
</div>
</div>
</div>
);
return (
<div className="flex h-min flex-col pb-3">
<div className="flex items-start gap-3">
<div className="relative h-11 w-11 shrink overflow-hidden rounded-md bg-zinc-700" />
<div className="flex flex-col gap-0.5">
<div className="h-4 w-20 rounded bg-zinc-700" />
</div>
</div>
<div className="-mt-5 animate-pulse pl-[49px]">
<div className="flex flex-col gap-1">
<div className="h-4 w-full rounded-sm bg-zinc-700" />
<div className="h-3 w-2/3 rounded-sm bg-zinc-700" />
<div className="h-3 w-1/2 rounded-sm bg-zinc-700" />
</div>
</div>
</div>
);
}

View File

@@ -1,165 +1,167 @@
import { NotificationUser } from "./user";
import { Dialog, Transition } from "@headlessui/react";
import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
import { BellIcon, CancelIcon, LoaderIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider";
import { User } from "@shared/user";
import { useQuery } from "@tanstack/react-query";
import { dateToUnix, getHourAgo } from "@utils/date";
import { Fragment, useContext, useRef, useState } from "react";
import { Dialog, Transition } from '@headlessui/react';
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { Fragment, useContext, useRef, useState } from 'react';
import { BellIcon, CancelIcon, LoaderIcon } from '@shared/icons';
import { RelayContext } from '@shared/relayProvider';
import { User } from '@shared/user';
import { dateToUnix, getHourAgo } from '@utils/date';
import { NotificationUser } from './user';
export function NotificationModal({ pubkey }: { pubkey: string }) {
const ndk = useContext(RelayContext);
const now = useRef(new Date());
const ndk = useContext(RelayContext);
const now = useRef(new Date());
const [isOpen, setIsOpen] = useState(false);
const [refresh, setRefresh] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const { status, data } = useQuery(
["user-notification", pubkey],
async () => {
const filter: NDKFilter = {
"#p": [pubkey],
kinds: [1, 6, 7, 9735],
since: dateToUnix(getHourAgo(48, now.current)),
};
const events = await ndk.fetchEvents(filter);
return [...events];
},
{
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
staleTime: Infinity,
},
);
const { status, data } = useQuery(
['user-notification', pubkey],
async () => {
const filter: NDKFilter = {
'#p': [pubkey],
kinds: [1, 6, 7, 9735],
since: dateToUnix(getHourAgo(48, now.current)),
};
const events = await ndk.fetchEvents(filter);
return [...events];
},
{
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
staleTime: Infinity,
}
);
const closeModal = () => {
setIsOpen(false);
};
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
const openModal = () => {
setIsOpen(true);
};
const renderItem = (event: NDKEvent) => {
if (event.kind === 1) {
return (
<div key={event.id} className="flex flex-col px-5 py-2">
<User pubkey={event.pubkey} time={event.created_at} isChat={true} />
<div className="-mt-[20px] pl-[49px]">
<p className="select-text whitespace-pre-line break-words text-base text-zinc-100">
{event.content}
</p>
</div>
</div>
);
}
const renderItem = (event: NDKEvent) => {
if (event.kind === 1) {
return (
<div key={event.id} className="flex flex-col px-5 py-2">
<User pubkey={event.pubkey} time={event.created_at} isChat={true} />
<div className="-mt-[20px] pl-[49px]">
<p className="select-text whitespace-pre-line break-words text-base text-zinc-100">
{event.content}
</p>
</div>
</div>
);
}
if (event.kind === 6) {
return (
<div key={event.id} className="flex flex-col px-5 py-2">
<NotificationUser pubkey={event.pubkey} desc="repost your post" />
</div>
);
}
if (event.kind === 6) {
return (
<div key={event.id} className="flex flex-col px-5 py-2">
<NotificationUser pubkey={event.pubkey} desc="repost your post" />
</div>
);
}
if (event.kind === 7) {
return (
<div key={event.id} className="flex flex-col px-5 py-2">
<NotificationUser pubkey={event.pubkey} desc="liked your post" />
</div>
);
}
if (event.kind === 7) {
return (
<div key={event.id} className="flex flex-col px-5 py-2">
<NotificationUser pubkey={event.pubkey} desc="liked your post" />
</div>
);
}
if (event.kind === 9735) {
return (
<div key={event.id} className="flex flex-col px-5 py-2">
<NotificationUser pubkey={event.pubkey} desc="zapped your post" />
</div>
);
}
if (event.kind === 9735) {
return (
<div key={event.id} className="flex flex-col px-5 py-2">
<NotificationUser pubkey={event.pubkey} desc="zapped your post" />
</div>
);
}
return <div className="flex flex-col px-5 py-2">{event.content}</div>;
};
return <div className="flex flex-col px-5 py-2">{event.content}</div>;
};
return (
<>
<button
type="button"
onClick={() => openModal()}
aria-label="Notification"
className="inline-flex items-center justify-center w-9 h-9 rounded-md border-t bg-zinc-800 border-zinc-700/50 transform active:translate-y-1"
>
<BellIcon className="w-4 h-4 text-zinc-400" />
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-lg border-t border-zinc-800/50 bg-zinc-900">
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between">
<Dialog.Title
as="h3"
className="text-lg font-semibold leading-none text-zinc-100"
>
Notification
</Dialog.Title>
<button
type="button"
onClick={closeModal}
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<CancelIcon className="w-5 h-5 text-zinc-300" />
</button>
</div>
<Dialog.Description className="text-sm leading-tight text-zinc-400">
All things happen when you rest in 48 hours ago
</Dialog.Description>
</div>
</div>
<div className="h-[500px] flex flex-col pb-5 overflow-x-hidden overflow-y-auto">
{status === "loading" ? (
<div className="px-4 py-3 inline-flex items-center justify-center">
<LoaderIcon className="h-5 w-5 animate-spin text-black dark:text-zinc-100" />
</div>
) : data.length < 1 ? (
<div className="flex flex-col items-center justify-center w-full h-full">
<p className="text-4xl mb-1">🎉</p>
<p className="text-zinc-500 font-medium">
Yo!, you've no new notifications
</p>
</div>
) : (
data.map((event) => renderItem(event))
)}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
return (
<>
<button
type="button"
onClick={() => openModal()}
aria-label="Notification"
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md border-t border-zinc-700/50 bg-zinc-800 active:translate-y-1"
>
<BellIcon className="h-4 w-4 text-zinc-400" />
</button>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
</Transition.Child>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex h-min w-full max-w-lg flex-col gap-2 rounded-lg border-t border-zinc-800/50 bg-zinc-900">
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5">
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between">
<Dialog.Title
as="h3"
className="text-lg font-semibold leading-none text-zinc-100"
>
Notification
</Dialog.Title>
<button
type="button"
onClick={closeModal}
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-900"
>
<CancelIcon className="h-5 w-5 text-zinc-300" />
</button>
</div>
<Dialog.Description className="text-sm leading-tight text-zinc-400">
All things happen when you rest in 48 hours ago
</Dialog.Description>
</div>
</div>
<div className="flex h-[500px] flex-col overflow-y-auto overflow-x-hidden pb-5">
{status === 'loading' ? (
<div className="inline-flex items-center justify-center px-4 py-3">
<LoaderIcon className="h-5 w-5 animate-spin text-black dark:text-zinc-100" />
</div>
) : data.length < 1 ? (
<div className="flex h-full w-full flex-col items-center justify-center">
<p className="mb-1 text-4xl">🎉</p>
<p className="font-medium text-zinc-500">
Yo!, you&apos;ve no new notifications
</p>
</div>
) : (
data.map((event) => renderItem(event))
)}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
}

View File

@@ -1,42 +1,41 @@
import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import { Image } from '@shared/image';
export function NotificationUser({
pubkey,
desc,
}: { pubkey: string; desc: string }) {
const { status, user } = useProfile(pubkey);
import { DEFAULT_AVATAR } from '@stores/constants';
if (status === "loading") {
return (
<div className="flex items-center gap-2">
<div className="relative h-11 w-11 shrink-0 rounded-md bg-zinc-800 animate-pulse" />
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start">
<span className="w-1/2 h-4 rounded bg-zinc-800 animate-pulse" />
<span className="w-1/3 h-3 rounded bg-zinc-800 animate-pulse" />
</div>
</div>
);
}
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
return (
<div className="flex items-center justify-start gap-2">
<div className="relative h-11 w-11 shrink rounded-md">
<Image
src={user.image}
fallback={DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>
</div>
<div className="flex-1 inline-flex items-center justify-start text-start gap-1">
<span className="leading-none text-zinc-400">
{user.nip05 || user.name || user.displayName || shortenKey(pubkey)}
</span>
<span className="leading-none text-zinc-400">{desc}</span>
</div>
</div>
);
export function NotificationUser({ pubkey, desc }: { pubkey: string; desc: string }) {
const { status, user } = useProfile(pubkey);
if (status === 'loading') {
return (
<div className="flex items-center gap-2">
<div className="relative h-11 w-11 shrink-0 animate-pulse rounded-md bg-zinc-800" />
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start">
<span className="h-4 w-1/2 animate-pulse rounded bg-zinc-800" />
<span className="h-3 w-1/3 animate-pulse rounded bg-zinc-800" />
</div>
</div>
);
}
return (
<div className="flex items-center justify-start gap-2">
<div className="relative h-11 w-11 shrink rounded-md">
<Image
src={user.image}
fallback={DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 rounded-md object-cover"
/>
</div>
<div className="inline-flex flex-1 items-center justify-start gap-1 text-start">
<span className="leading-none text-zinc-400">
{user.nip05 || user.name || user.displayName || shortenKey(pubkey)}
</span>
<span className="leading-none text-zinc-400">{desc}</span>
</div>
</div>
);
}

View File

@@ -1,13 +1,14 @@
import { useAccount } from "@utils/hooks/useAccount";
import { ReactNode } from "react";
import { Navigate } from "react-router-dom";
import { ReactNode } from 'react';
import { Navigate } from 'react-router-dom';
import { useAccount } from '@utils/hooks/useAccount';
export function Protected({ children }: { children: ReactNode }) {
const { status, account } = useAccount();
const { status, account } = useAccount();
if (status === "success" && !account) {
return <Navigate to="/auth/welcome" replace />;
}
if (status === 'success' && !account) {
return <Navigate to="/auth/welcome" replace />;
}
return children;
return children;
}

View File

@@ -1,14 +1,15 @@
import { initNDK } from "@libs/ndk";
import { getSetting } from "@libs/storage";
import NDK from "@nostr-dev-kit/ndk";
import { createContext } from "react";
import NDK from '@nostr-dev-kit/ndk';
import { createContext } from 'react';
import { initNDK } from '@libs/ndk';
import { getSetting } from '@libs/storage';
export const RelayContext = createContext<NDK>(null);
const relays = await getSetting("relays");
const relays = await getSetting('relays');
const relaysArray = JSON.parse(relays);
const ndk = await initNDK(relaysArray);
export function RelayProvider({ children }: { children: React.ReactNode }) {
return <RelayContext.Provider value={ndk}>{children}</RelayContext.Provider>;
return <RelayContext.Provider value={ndk}>{children}</RelayContext.Provider>;
}

View File

@@ -1,63 +1,64 @@
import { AppHeader } from "@shared/appHeader";
import { NavLink, Outlet, ScrollRestoration } from "react-router-dom";
import { twMerge } from "tailwind-merge";
import { NavLink, Outlet, ScrollRestoration } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { AppHeader } from '@shared/appHeader';
export function SettingsLayout() {
return (
<div className="flex w-screen h-screen">
<div className="relative flex flex-row shrink-0">
<div className="relative flex w-[232px] flex-col gap-3 border-r border-zinc-900">
<AppHeader />
<div className="pb-20 flex flex-col gap-5 overflow-y-auto scrollbar-hide">
<div className="flex flex-col gap-0.5 px-1.5">
<div className="px-2.5">
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
Settings
</h3>
</div>
<div className="flex flex-col">
<NavLink
to="/settings/general"
className={({ isActive }) =>
twMerge(
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
isActive ? "bg-zinc-900/50" : "",
)
}
>
<span className="font-medium">General</span>
</NavLink>
<NavLink
to="/settings/shortcuts"
className={({ isActive }) =>
twMerge(
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
isActive ? "bg-zinc-900/50" : "",
)
}
>
<span className="font-medium">Shortcuts</span>
</NavLink>
<NavLink
to="/settings/account"
className={({ isActive }) =>
twMerge(
"flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200",
isActive ? "bg-zinc-900/50" : "",
)
}
>
<span className="font-medium">Account</span>
</NavLink>
</div>
</div>
</div>
</div>
</div>
<div className="w-full h-full">
<Outlet />
<ScrollRestoration />
</div>
</div>
);
return (
<div className="flex h-screen w-screen">
<div className="relative flex shrink-0 flex-row">
<div className="relative flex w-[232px] flex-col gap-3 border-r border-zinc-900">
<AppHeader />
<div className="scrollbar-hide flex flex-col gap-5 overflow-y-auto pb-20">
<div className="flex flex-col gap-0.5 px-1.5">
<div className="px-2.5">
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">
Settings
</h3>
</div>
<div className="flex flex-col">
<NavLink
to="/settings/general"
className={({ isActive }) =>
twMerge(
'flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200',
isActive ? 'bg-zinc-900/50' : ''
)
}
>
<span className="font-medium">General</span>
</NavLink>
<NavLink
to="/settings/shortcuts"
className={({ isActive }) =>
twMerge(
'flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200',
isActive ? 'bg-zinc-900/50' : ''
)
}
>
<span className="font-medium">Shortcuts</span>
</NavLink>
<NavLink
to="/settings/account"
className={({ isActive }) =>
twMerge(
'flex h-9 items-center gap-2.5 rounded-md px-2.5 text-zinc-200',
isActive ? 'bg-zinc-900/50' : ''
)
}
>
<span className="font-medium">Account</span>
</NavLink>
</div>
</div>
</div>
</div>
</div>
<div className="h-full w-full">
<Outlet />
<ScrollRestoration />
</div>
</div>
);
}

View File

@@ -1,27 +1,30 @@
import { CancelIcon } from "@shared/icons";
import { CancelIcon } from '@shared/icons';
export function TitleBar({
title,
onClick = undefined,
}: { title: string; onClick?: () => void }) {
return (
<div
data-tauri-drag-region
className="group overflow-hidden shrink-0 h-11 w-full flex items-center justify-between px-3 border-b border-zinc-900"
>
<div className="w-6" />
<h3 className="text-sm font-medium text-zinc-200">{title}</h3>
{onClick ? (
<button
type="button"
onClick={onClick}
className="inline-flex h-6 w-6 shrink items-center justify-center rounded hover:bg-zinc-900 transform translate-y-8 group-hover:translate-y-0 transition-transform ease-in-out duration-150"
>
<CancelIcon width={12} height={12} className="text-zinc-300" />
</button>
) : (
<div className="w-6" />
)}
</div>
);
title,
onClick = undefined,
}: {
title: string;
onClick?: () => void;
}) {
return (
<div
data-tauri-drag-region
className="group flex h-11 w-full shrink-0 items-center justify-between overflow-hidden border-b border-zinc-900 px-3"
>
<div className="w-6" />
<h3 className="text-sm font-medium text-zinc-200">{title}</h3>
{onClick ? (
<button
type="button"
onClick={onClick}
className="inline-flex h-6 w-6 shrink translate-y-8 transform items-center justify-center rounded transition-transform duration-150 ease-in-out hover:bg-zinc-900 group-hover:translate-y-0"
>
<CancelIcon width={12} height={12} className="text-zinc-300" />
</button>
) : (
<div className="w-6" />
)}
</div>
);
}

View File

@@ -1,58 +1,58 @@
import {
autoUpdate,
offset,
shift,
useFloating,
useFocus,
useHover,
useInteractions,
} from "@floating-ui/react";
import { useState } from "react";
autoUpdate,
offset,
shift,
useFloating,
useFocus,
useHover,
useInteractions,
} from '@floating-ui/react';
import { useState } from 'react';
export function Tooltip({
children,
message,
}: { children: React.ReactNode; message: string }) {
const [isOpen, setIsOpen] = useState(false);
children,
message,
}: {
children: React.ReactNode;
message: string;
}) {
const [isOpen, setIsOpen] = useState(false);
const { x, y, strategy, refs, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
placement: "top",
middleware: [offset(8), shift()],
whileElementsMounted(...args) {
const cleanup = autoUpdate(...args, { animationFrame: true });
return cleanup;
},
});
const { x, y, strategy, refs, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
placement: 'top',
middleware: [offset(8), shift()],
whileElementsMounted(...args) {
const cleanup = autoUpdate(...args, { animationFrame: true });
return cleanup;
},
});
const hover = useHover(context);
const focus = useFocus(context);
const hover = useHover(context);
const focus = useFocus(context);
const { getReferenceProps, getFloatingProps } = useInteractions([
hover,
focus,
]);
const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus]);
return (
<>
<div ref={refs.setReference} {...getReferenceProps()}>
{children}
</div>
{isOpen && (
<div
ref={refs.setFloating}
className="w-max select-none rounded-md bg-zinc-800 border border-zinc-700 px-4 py-2 text-sm font-medium leading-none text-zinc-100"
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
}}
{...getFloatingProps()}
>
{message}
</div>
)}
</>
);
return (
<>
<div ref={refs.setReference} {...getReferenceProps()}>
{children}
</div>
{isOpen && (
<div
ref={refs.setFloating}
className="w-max select-none rounded-md border border-zinc-700 bg-zinc-800 px-4 py-2 text-sm font-medium leading-none text-zinc-100"
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
}}
{...getFloatingProps()}
>
{message}
</div>
)}
</>
);
}

View File

@@ -1,138 +1,138 @@
import { Popover, Transition } from "@headlessui/react";
import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants";
import { formatCreatedAt } from "@utils/createdAt";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import { Fragment } from "react";
import { Link } from "react-router-dom";
import { Popover, Transition } from '@headlessui/react';
import { Fragment } from 'react';
import { Link } from 'react-router-dom';
import { Image } from '@shared/image';
import { DEFAULT_AVATAR } from '@stores/constants';
import { formatCreatedAt } from '@utils/createdAt';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
export function User({
pubkey,
time,
size,
repost,
isChat = false,
pubkey,
time,
size,
repost,
isChat = false,
}: {
pubkey: string;
time: number;
size?: string;
repost?: boolean;
isChat?: boolean;
pubkey: string;
time: number;
size?: string;
repost?: boolean;
isChat?: boolean;
}) {
const { status, user } = useProfile(pubkey);
const createdAt = formatCreatedAt(time, isChat);
const { status, user } = useProfile(pubkey);
const createdAt = formatCreatedAt(time, isChat);
const avatarWidth = size === "small" ? "w-6" : "w-11";
const avatarHeight = size === "small" ? "h-6" : "h-11";
const avatarWidth = size === 'small' ? 'w-6' : 'w-11';
const avatarHeight = size === 'small' ? 'h-6' : 'h-11';
if (status === "loading") {
return (
<div
className={`relative flex gap-3 ${
size === "small" ? "items-center" : "items-start"
}`}
>
<div
className={`${avatarWidth} ${avatarHeight} ${
size === "small" ? "rounded" : "rounded-lg"
} relative z-10 bg-zinc-800 animate-pulse shrink-0 overflow-hidden`}
/>
<div className="flex flex-wrap items-baseline gap-1">
<div className="w-36 h-3.5 rounded bg-zinc-800 animate-pulse" />
</div>
</div>
);
}
if (status === 'loading') {
return (
<div
className={`relative flex gap-3 ${
size === 'small' ? 'items-center' : 'items-start'
}`}
>
<div
className={`${avatarWidth} ${avatarHeight} ${
size === 'small' ? 'rounded' : 'rounded-lg'
} relative z-10 shrink-0 animate-pulse overflow-hidden bg-zinc-800`}
/>
<div className="flex flex-wrap items-baseline gap-1">
<div className="h-3.5 w-36 animate-pulse rounded bg-zinc-800" />
</div>
</div>
);
}
return (
<Popover
className={`relative flex gap-3 ${
size === "small" ? "items-center" : "items-start"
}`}
>
<Popover.Button
className={`${avatarWidth} ${avatarHeight} relative z-10 bg-zinc-900 shrink-0 overflow-hidden`}
>
<Image
src={user?.image}
fallback={DEFAULT_AVATAR}
alt={pubkey}
className={`${avatarWidth} ${avatarHeight} ${
size === "small" ? "rounded" : "rounded-lg"
} object-cover`}
/>
</Popover.Button>
<div className="flex flex-wrap items-baseline gap-1">
<h5
className={`text-zinc-100 font-semibold leading-none truncate ${
size === "small" ? "max-w-[8rem]" : "max-w-[12rem]"
}`}
>
{user?.nip05 || user?.name || user?.displayName || shortenKey(pubkey)}
</h5>
{repost && (
<span className="font-semibold leading-none text-fuchsia-500">
{" "}
reposted
</span>
)}
<span className="leading-none text-zinc-500">·</span>
<span className="leading-none text-zinc-500">{createdAt}</span>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute z-50 top-10 mt-3">
<div className="w-[250px] overflow-hidden rounded-md border border-zinc-800/50 bg-zinc-900/90 backdrop-blur-lg">
<div className="flex gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={user?.image}
fallback={DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 shrink-0 rounded-lg object-cover"
/>
<div className="flex-1 flex flex-col gap-2">
<div className="inline-flex flex-col gap-1">
<h5 className="font-semibold text-sm leading-none">
{user?.displayName || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
</h5>
<span className="max-w-[15rem] text-sm truncate leading-none text-zinc-500">
{user?.nip05 || shortenKey(pubkey)}
</span>
</div>
<div>
<p className="line-clamp-3 break-words leading-tight text-zinc-100">
{user?.about}
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2 px-3 py-3">
<Link
to={`/app/user/${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-700 hover:bg-fuchsia-500 text-sm font-medium"
>
View profile
</Link>
<Link
to={`/app/chat/${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-700 hover:bg-fuchsia-500 text-sm font-medium"
>
Message
</Link>
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
);
return (
<Popover
className={`relative flex gap-3 ${
size === 'small' ? 'items-center' : 'items-start'
}`}
>
<Popover.Button
className={`${avatarWidth} ${avatarHeight} relative z-10 shrink-0 overflow-hidden bg-zinc-900`}
>
<Image
src={user?.image}
fallback={DEFAULT_AVATAR}
alt={pubkey}
className={`${avatarWidth} ${avatarHeight} ${
size === 'small' ? 'rounded' : 'rounded-lg'
} object-cover`}
/>
</Popover.Button>
<div className="flex flex-wrap items-baseline gap-1">
<h5
className={`truncate font-semibold leading-none text-zinc-100 ${
size === 'small' ? 'max-w-[8rem]' : 'max-w-[12rem]'
}`}
>
{user?.nip05 || user?.name || user?.displayName || shortenKey(pubkey)}
</h5>
{repost && (
<span className="font-semibold leading-none text-fuchsia-500"> reposted</span>
)}
<span className="leading-none text-zinc-500">·</span>
<span className="leading-none text-zinc-500">{createdAt}</span>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute top-10 z-50 mt-3">
<div className="w-[250px] overflow-hidden rounded-md border border-zinc-800/50 bg-zinc-900/90 backdrop-blur-lg">
<div className="flex gap-2.5 border-b border-zinc-800 px-3 py-3">
<Image
src={user?.image}
fallback={DEFAULT_AVATAR}
alt={pubkey}
className="h-11 w-11 shrink-0 rounded-lg object-cover"
/>
<div className="flex flex-1 flex-col gap-2">
<div className="inline-flex flex-col gap-1">
<h5 className="text-sm font-semibold leading-none">
{user?.displayName || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
</h5>
<span className="max-w-[15rem] truncate text-sm leading-none text-zinc-500">
{user?.nip05 || shortenKey(pubkey)}
</span>
</div>
<div>
<p className="line-clamp-3 break-words leading-tight text-zinc-100">
{user?.about}
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2 px-3 py-3">
<Link
to={`/app/user/${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-700 text-sm font-medium hover:bg-fuchsia-500"
>
View profile
</Link>
<Link
to={`/app/chat/${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-zinc-700 text-sm font-medium hover:bg-fuchsia-500"
>
Message
</Link>
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
);
}