add edit profile

This commit is contained in:
Ren Amamiya
2023-06-29 10:37:35 +07:00
parent ec1ff9ab87
commit 1ba7f823cb
20 changed files with 965 additions and 184 deletions

View File

@@ -2,7 +2,7 @@ import { Dialog, Transition } from "@headlessui/react";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { CancelIcon, HideIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider";
import { Tooltip } from "@shared/tooltip";
import { Tooltip } from "@shared/tooltip_dep";
import { useChannelMessages } from "@stores/channels";
import { dateToUnix } from "@utils/date";
import { useAccount } from "@utils/hooks/useAccount";

View File

@@ -2,7 +2,7 @@ import { Dialog, Transition } from "@headlessui/react";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { CancelIcon, MuteIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider";
import { Tooltip } from "@shared/tooltip";
import { Tooltip } from "@shared/tooltip_dep";
import { useChannelMessages } from "@stores/channels";
import { dateToUnix } from "@utils/date";
import { useAccount } from "@utils/hooks/useAccount";

View File

@@ -1,5 +1,5 @@
import { ReplyMessageIcon } from "@shared/icons";
import { Tooltip } from "@shared/tooltip";
import { Tooltip } from "@shared/tooltip_dep";
import { useChannelMessages } from "@stores/channels";
export function MessageReplyButton({

View File

@@ -1,9 +1,11 @@
import { UserFeed } from "@app/user/components/feed";
import { UserMetadata } from "@app/user/components/metadata";
import { Tab } from "@headlessui/react";
import { EditProfileModal } from "@shared/editProfileModal";
import { ThreadsIcon, ZapIcon } from "@shared/icons";
import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useAccount } from "@utils/hooks/useAccount";
import { useProfile } from "@utils/hooks/useProfile";
import { useSocial } from "@utils/hooks/useSocial";
import { shortenKey } from "@utils/shortenKey";
@@ -13,6 +15,7 @@ import { Link, useParams } from "react-router-dom";
export function UserScreen() {
const { pubkey } = useParams();
const { user } = useProfile(pubkey);
const { account } = useAccount();
const { status, userFollows, follow, unfollow } = useSocial();
const [followed, setFollowed] = useState(false);
@@ -116,6 +119,8 @@ export function UserScreen() {
>
<ZapIcon className="w-5 h-5" />
</button>
<span className="inline-flex mx-2 w-px h-4 bg-zinc-900" />
{account && account.pubkey === pubkey && <EditProfileModal />}
</div>
</div>
<div className="flex flex-col gap-8">

View File

@@ -8,6 +8,7 @@ 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";
const lastLogin = await getLastLogin();
@@ -92,7 +93,10 @@ export function ActiveAccount({ data }: { data: any }) {
}
return (
<div className="relative inline-block h-9 w-9">
<Link
to={`/app/user/${data.pubkey}`}
className="relative inline-block h-9 w-9"
>
<Image
src={user.image}
fallback={DEFAULT_AVATAR}
@@ -100,6 +104,6 @@ export function ActiveAccount({ data }: { data: any }) {
className="h-9 w-9 rounded-md object-cover"
/>
<NetworkStatusIndicator />
</div>
</Link>
);
}

View File

@@ -1,11 +1,10 @@
import { createBlobFromFile } from "@utils/createBlobFromFile";
import { LoaderIcon } from "./icons";
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";
export function AvatarUploader({ valueState }: { valueState: any }) {
export function AvatarUploader({ setPicture }: { setPicture: any }) {
const [loading, setLoading] = useState(false);
const openFileDialog = async () => {
@@ -44,23 +43,26 @@ export function AvatarUploader({ valueState }: { valueState: any }) {
body: Body.bytes(buf),
},
);
const webpImage = `https://void.cat/d/${res.data.file.id}.webp`;
const image = `https://void.cat/d/${res.data.file.id}.jpg`;
valueState(webpImage);
// update parent state
setPicture(image);
// disable loader
setLoading(false);
}
};
return (
<button
onClick={() => openFileDialog()}
type="button"
className="inline-flex h-7 items-center justify-center rounded bg-zinc-900 px-3 text-sm font-medium text-zinc-200 hover:bg-zinc-800"
onClick={() => openFileDialog()}
className="w-full h-full inline-flex items-center justify-center bg-zinc-900/40"
>
{loading ? (
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
<LoaderIcon className="h-6 w-6 animate-spintext-zinc-100" />
) : (
<span className="leading-none">Upload</span>
<PlusIcon className="h-6 w-6 text-zinc-100" />
)}
</button>
);

View File

@@ -0,0 +1,69 @@
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";
export function BannerUploader({ setBanner }: { setBanner: any }) {
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 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`;
// update parent state
setBanner(image);
// 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-spintext-zinc-100" />
) : (
<PlusIcon className="h-8 w-8 text-zinc-100" />
)}
</button>
);
}

View File

@@ -0,0 +1,226 @@
import { Dialog, Transition } from "@headlessui/react";
import { usePublish } from "@libs/ndk";
import { getPleb } from "@libs/storage";
import { AvatarUploader } from "@shared/avatarUploader";
import { BannerUploader } from "@shared/bannerUploader";
import { CancelIcon, LoaderIcon } from "@shared/icons";
import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useAccount } from "@utils/hooks/useAccount";
import { Fragment, useState } from "react";
import { useForm } from "react-hook-form";
export function EditProfileModal() {
const publish = usePublish();
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [picture, setPicture] = useState(DEFAULT_AVATAR);
const [banner, setBanner] = useState(null);
const { account } = useAccount();
const {
register,
handleSubmit,
reset,
formState: { isValid },
} = useForm({
defaultValues: async () => {
const res = await getPleb(account.npub);
if (res.picture) {
setPicture(res.image);
}
if (res.banner) {
setBanner(res.banner);
}
return res;
},
});
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
const onSubmit = (data: any) => {
// start loading
setLoading(true);
// publish
const event = publish({
content: JSON.stringify({
...data,
display_name: data.name,
bio: data.about,
image: data.picture,
}),
kind: 0,
tags: [],
});
if (event) {
setTimeout(() => {
// reset form
reset();
// reset state
setLoading(false);
setIsOpen(false);
setPicture(DEFAULT_AVATAR);
setBanner(null);
}, 1200);
}
};
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">
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>
</>
);
}

View File

@@ -13,8 +13,11 @@ export function ThreadIcon(
{...props}
>
<path
fill="currentColor"
d="M7.5 5.75a1.75 1.75 0 113.5 0 1.75 1.75 0 01-3.5 0zM13 5.75a1.75 1.75 0 113.5 0 1.75 1.75 0 01-3.5 0zM7.5 18.25a1.75 1.75 0 113.5 0 1.75 1.75 0 01-3.5 0zM13 18.25a1.75 1.75 0 113.5 0 1.75 1.75 0 01-3.5 0zM7.5 11.9a1.75 1.75 0 113.5 0v.1a1.75 1.75 0 11-3.5 0v-.1zM13 11.9a1.75 1.75 0 113.5 0v.1a1.75 1.75 0 11-3.5 0v-.1z"
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,5 +1,5 @@
import { MediaIcon } from "@shared/icons";
import { Tooltip } from "@shared/tooltip";
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";
@@ -56,42 +56,35 @@ export function MediaUploader({ setState }: { setState: any }) {
};
return (
<Tooltip message="Upload media">
<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 ? (
<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"
<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"
>
<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>
) : (
<MediaIcon
width={14}
height={14}
className="text-zinc-400 group-hover:text-zinc-200"
/>
)}
</button>
</Tooltip>
{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>
);
}

View File

@@ -1,11 +1,13 @@
import { createReplyNote } from "@libs/storage";
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 { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { decode } from "light-bolt11-decoder";
import { useContext } from "react";
@@ -19,6 +21,8 @@ export function NoteMetadata({
eventPubkey: string;
}) {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const { status, data } = useQuery(["note-metadata", id], async () => {
let replies = 0;
let reposts = 0;
@@ -67,49 +71,71 @@ export function NoteMetadata({
return { replies, reposts, zap };
});
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();
}
};
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>
);
}
return (
<div className="inline-flex items-center w-full h-12 mt-2">
{status === "loading" ? (
<>
<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>
</>
) : (
<>
<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}
@@ -118,8 +144,28 @@ export function NoteMetadata({
/>
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
<NoteZap zaps={data.zap} />
</>
)}
</div>
</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>
);
}

View File

@@ -1,3 +1,4 @@
import * as Tooltip from "@radix-ui/react-tooltip";
import { ReplyIcon } from "@shared/icons";
import { useComposer } from "@stores/composer";
import { compactNumber } from "@utils/number";
@@ -11,19 +12,30 @@ export function NoteReply({
const setReply = useComposer((state) => state.setReply);
return (
<button
type="button"
onClick={() => setReply(id, rootID, pubkey)}
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"
/>
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
{compactNumber.format(replies)}
</span>
</button>
<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>
);
}

View File

@@ -1,3 +1,4 @@
import * as Tooltip from "@radix-ui/react-tooltip";
import { RepostIcon } from "@shared/icons";
import { useComposer } from "@stores/composer";
import { compactNumber } from "@utils/number";
@@ -10,19 +11,30 @@ export function NoteRepost({
const setRepost = useComposer((state) => state.setRepost);
return (
<button
type="button"
onClick={() => setRepost(id, pubkey)}
className="w-20 group inline-flex items-center gap-1.5"
>
<RepostIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-blue-400"
/>
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
{compactNumber.format(reposts)}
</span>
</button>
<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>
);
}

View File

@@ -1,20 +1,32 @@
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 (
<button
type="button"
className="w-20 group inline-flex items-center gap-1.5"
>
<ZapIcon
width={16}
height={16}
className="text-zinc-400 group-hover:text-blue-400"
/>
<span className="text-base leading-none text-zinc-400 group-hover:text-zinc-100">
{compactNumber.format(zaps)}
</span>
</button>
<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>
);
}

View File

@@ -24,12 +24,10 @@ export function useProfile(id: string) {
const result = await getPleb(npub);
if (result && parseInt(result.created_at) + 86400 >= current) {
console.log("cache", result);
return result;
} else {
const user = ndk.getUser({ npub });
await user.fetchProfile();
console.log("new", user);
await createPleb(id, user.profile);
return user.profile;