wip: port more component to new ui

This commit is contained in:
Ren Amamiya 2023-08-02 14:48:51 +07:00
parent a85bcf917b
commit babcd8698e
20 changed files with 410 additions and 559 deletions

View File

@ -102,74 +102,64 @@ export function ResetScreen() {
return ( return (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">
<div className="mx-auto w-full max-w-md"> <div className="mx-auto w-full max-w-md">
<div className="mb-8 text-center"> <div className="mb-6 text-center">
<h1 className="text-xl font-semibold text-zinc-100">Reset unlock password</h1> <h1 className="text-2xl font-semibold text-white">Reset unlock password</h1>
</div> </div>
<div className="flex flex-col gap-4"> <form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col gap-3">
<form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col gap-3"> <div className="flex flex-col gap-1">
<div className="flex flex-col gap-1"> <label htmlFor="privkey" className="font-medium text-white/50">
<label htmlFor="privkey" className="font-medium text-zinc-200"> Private key
Private key </label>
</label> <div className="relative">
<div className="relative"> <input
<input {...register('privkey', { required: true })}
{...register('privkey', { required: true })} type="text"
type="text" placeholder="nsec..."
placeholder="nsec..." className="relative h-12 w-full rounded-lg bg-white/10 px-3.5 py-1 text-white !outline-none placeholder:text-white/10"
className="relative w-full rounded-lg bg-zinc-800 px-3.5 py-3 text-zinc-100 !outline-none placeholder:text-zinc-400" />
/>
</div>
</div> </div>
<div className="flex flex-col gap-1"> </div>
<label htmlFor="password" className="font-medium text-zinc-200"> <div className="flex flex-col gap-1">
Set a new password to protect your key <label htmlFor="password" className="font-medium text-white/50">
</label> Set a new password to protect your key
<div className="relative"> </label>
<input <div className="relative">
{...register('password', { required: true })} <input
type={passwordInput} {...register('password', { required: true })}
placeholder="min. 4 characters" type={passwordInput}
className="relative w-full rounded-lg bg-zinc-800 py-3 pl-3.5 pr-11 text-zinc-100 !outline-none placeholder:text-zinc-400" placeholder="min. 4 characters"
/> className="relative h-12 w-full rounded-lg bg-white/10 px-3.5 py-1 text-white !outline-none placeholder:text-white/10"
<button />
type="button"
onClick={() => showPassword()}
className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-zinc-700"
>
{passwordInput === 'password' ? (
<EyeOffIcon
width={20}
height={20}
className="text-zinc-500 group-hover:text-zinc-100"
/>
) : (
<EyeOnIcon
width={20}
height={20}
className="text-zinc-500 group-hover:text-zinc-100"
/>
)}
</button>
</div>
<span className="text-sm text-red-400">
{errors.password && <p>{errors.password.message}</p>}
</span>
</div>
<div className="flex items-center justify-center">
<button <button
type="submit" type="button"
disabled={!isDirty || !isValid} onClick={() => showPassword()}
className="mt-3 inline-flex h-11 w-full items-center justify-center rounded-md bg-fuchsia-500 font-medium text-zinc-100 hover:bg-fuchsia-600 disabled:pointer-events-none disabled:opacity-50" className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-white/10"
> >
{loading ? ( {passwordInput === 'password' ? (
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" /> <EyeOffIcon className="h-5 w-5 text-white/50 group-hover:text-white" />
) : ( ) : (
'Continue →' <EyeOnIcon className="h-5 w-5 text-white/50 group-hover:text-white" />
)} )}
</button> </button>
</div> </div>
</form> <span className="text-sm text-red-400">
</div> {errors.password && <p>{errors.password.message}</p>}
</span>
</div>
<div className="flex items-center justify-center">
<button
type="submit"
disabled={!isDirty || !isValid}
className="inline-flex h-12 w-full items-center justify-center rounded-md bg-fuchsia-500 font-medium text-white hover:bg-fuchsia-600 disabled:pointer-events-none disabled:opacity-50"
>
{loading ? (
<LoaderIcon className="h-4 w-4 animate-spin text-white" />
) : (
'Continue →'
)}
</button>
</div>
</form>
</div> </div>
</div> </div>
); );

View File

@ -84,7 +84,7 @@ export function UnlockScreen() {
<div className="mb-6 text-center"> <div className="mb-6 text-center">
<h1 className="text-2xl font-semibold text-white">Enter password to unlock</h1> <h1 className="text-2xl font-semibold text-white">Enter password to unlock</h1>
</div> </div>
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-3"> <form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col gap-3">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="relative"> <div className="relative">
<input <input
@ -98,17 +98,9 @@ export function UnlockScreen() {
className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-white/10" className="group absolute right-2 top-1/2 -translate-y-1/2 transform rounded p-1 hover:bg-white/10"
> >
{passwordInput === 'password' ? ( {passwordInput === 'password' ? (
<EyeOffIcon <EyeOffIcon className="h-5 w-5 text-white/50 group-hover:text-white" />
width={20}
height={20}
className="text-white/50 group-hover:text-white"
/>
) : ( ) : (
<EyeOnIcon <EyeOnIcon className="h-5 w-5 text-white/50 group-hover:text-white" />
width={20}
height={20}
className="text-white/50 group-hover:text-white"
/>
)} )}
</button> </button>
</div> </div>

View File

@ -1,5 +1,5 @@
import { Dialog, Transition } from '@headlessui/react'; import * as Dialog from '@radix-ui/react-dialog';
import { Fragment, useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { User } from '@app/auth/components/user'; import { User } from '@app/auth/components/user';
@ -10,114 +10,79 @@ import { useAccount } from '@utils/hooks/useAccount';
export function NewMessageModal() { export function NewMessageModal() {
const navigate = useNavigate(); const navigate = useNavigate();
const [isOpen, setIsOpen] = useState(false);
const [open, setOpen] = useState(false);
const { status, account } = useAccount(); const { status, account } = useAccount();
const follows = account ? JSON.parse(account.follows as string) : []; const follows = account ? JSON.parse(account.follows as string) : [];
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
const openChat = (pubkey: string) => { const openChat = (pubkey: string) => {
closeModal(); setOpen(false);
navigate(`/app/chats/${pubkey}`); navigate(`/app/chats/${pubkey}`);
}; };
return ( return (
<> <Dialog.Root open={open} onOpenChange={setOpen}>
<button <Dialog.Trigger asChild>
type="button" <button
onClick={() => openModal()} type="button"
className="inline-flex h-9 items-center gap-2.5 rounded-md px-2" className="inline-flex h-9 items-center gap-2.5 rounded-md px-2"
> >
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10"> <div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10">
<PlusIcon className="h-3 w-3 text-white" /> <PlusIcon className="h-3 w-3 text-white" />
</div> </div>
<div> <div>
<h5 className="text-white/50">New chat</h5> <h5 className="text-white/50">New chat</h5>
</div> </div>
</button> </button>
<Transition appear show={isOpen} as={Fragment}> </Dialog.Trigger>
<Dialog as="div" className="relative z-10" onClose={closeModal}> <Dialog.Portal className="relative z-10">
<Transition.Child <Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-xl" />
as={Fragment} <Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
enter="ease-out duration-300" <div className="relative h-min w-full max-w-xl rounded-xl bg-white/10">
enterFrom="opacity-0" <div className="h-min w-full shrink-0 border-b border-white/10 bg-white/5 px-5 py-5">
enterTo="opacity-100" <div className="flex flex-col gap-1">
leave="ease-in duration-200" <div className="flex items-center justify-between">
leaveFrom="opacity-100" <Dialog.Title className="text-lg font-semibold leading-none text-zinc-100">
leaveTo="opacity-0" New chat
> </Dialog.Title>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" /> <Dialog.Close className="inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-white/10">
</Transition.Child> <CancelIcon className="h-4 w-4 text-white/50" />
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center"> </Dialog.Close>
<Transition.Child </div>
as={Fragment} <Dialog.Description className="text-sm leading-none text-zinc-400">
enter="ease-out duration-300" All messages will be encrypted, but anyone can see who you chat
enterFrom="opacity-0 scale-95" </Dialog.Description>
enterTo="opacity-100 scale-100" </div>
leave="ease-in duration-200" </div>
leaveFrom="opacity-100 scale-100" <div className="flex h-[500px] flex-col overflow-y-auto overflow-x-hidden pb-2 pt-2">
leaveTo="opacity-0 scale-95" {status === 'loading' ? (
> <div className="inline-flex items-center justify-center px-4 py-3">
<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"> <LoaderIcon className="h-5 w-5 animate-spin text-white" />
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5"> </div>
<div className="flex flex-col gap-1"> ) : (
<div className="flex items-center justify-between"> follows.map((follow) => (
<Dialog.Title <div
as="h3" key={follow}
className="text-lg font-semibold leading-none text-zinc-100" className="group flex items-center justify-between px-4 py-2 hover:bg-white/10"
> >
New chat <User pubkey={follow} />
</Dialog.Title> <div>
<button <button
type="button" type="button"
onClick={closeModal} onClick={() => openChat(follow)}
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-800" className="hidden w-max rounded bg-white/10 px-3 py-1 text-sm font-medium hover:bg-fuchsia-500 group-hover:inline-flex"
> >
<CancelIcon className="h-4 w-4 text-zinc-300" /> Chat
</button> </button>
</div> </div>
<Dialog.Description className="text-sm leading-tight text-zinc-400">
All messages will be encrypted, but anyone can see who you chat
</Dialog.Description>
</div> </div>
</div> ))
<div className="flex h-[500px] flex-col overflow-y-auto overflow-x-hidden pb-5"> )}
{status === 'loading' ? ( </div>
<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>
) : (
follows.map((follow) => (
<div
key={follow}
className="group flex items-center justify-between px-4 py-3 hover:bg-zinc-800"
>
<User pubkey={follow} />
<div>
<button
type="button"
onClick={() => openChat(follow)}
className="hidden w-max rounded-md bg-zinc-700 px-3 py-1 text-sm font-medium hover:bg-fuchsia-500 group-hover:inline-flex"
>
Chat
</button>
</div>
</div>
))
)}
</div>
</Dialog.Panel>
</Transition.Child>
</div> </div>
</Dialog> </Dialog.Content>
</Transition> </Dialog.Portal>
</> </Dialog.Root>
); );
} }

View File

@ -1,115 +1,81 @@
import { Dialog, Transition } from '@headlessui/react'; import * as Dialog from '@radix-ui/react-dialog';
import { Fragment, useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { User } from '@app/auth/components/user'; import { User } from '@app/auth/components/user';
import { CancelIcon, StrangersIcon } from '@shared/icons'; import { CancelIcon, PlusIcon } from '@shared/icons';
import { compactNumber } from '@utils/number'; import { compactNumber } from '@utils/number';
import { Chats } from '@utils/types'; import { Chats } from '@utils/types';
export function UnknownsModal({ data }: { data: Chats[] }) { export function UnknownsModal({ data }: { data: Chats[] }) {
const navigate = useNavigate(); const navigate = useNavigate();
const [isOpen, setIsOpen] = useState(false); const [open, setOpen] = useState(false);
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
const openChat = (pubkey: string) => { const openChat = (pubkey: string) => {
closeModal(); setOpen(false);
navigate(`/app/chats/${pubkey}`); navigate(`/app/chats/${pubkey}`);
}; };
return ( return (
<> <Dialog.Root open={open} onOpenChange={setOpen}>
<button <Dialog.Trigger asChild>
type="button" <button
onClick={() => openModal()} type="button"
className="inline-flex h-9 items-center gap-2.5 rounded-md px-2" className="inline-flex h-9 items-center gap-2.5 rounded-md px-2"
> >
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10"> <div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10">
<StrangersIcon className="h-3 w-3 text-white" /> <PlusIcon className="h-3 w-3 text-white" />
</div> </div>
<div> <div>
<h5 className="text-white/50">{compactNumber.format(data.length)} unknowns</h5> <h5 className="text-white/50">
</div> {compactNumber.format(data.length)} unknowns
</button> </h5>
<Transition appear show={isOpen} as={Fragment}> </div>
<Dialog as="div" className="relative z-10" onClose={closeModal}> </button>
<Transition.Child </Dialog.Trigger>
as={Fragment} <Dialog.Portal className="relative z-10">
enter="ease-out duration-300" <Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-xl" />
enterFrom="opacity-0" <Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
enterTo="opacity-100" <div className="relative h-min w-full max-w-xl rounded-xl bg-white/10">
leave="ease-in duration-200" <div className="h-min w-full shrink-0 border-b border-white/10 bg-white/5 px-5 py-5">
leaveFrom="opacity-100" <div className="flex flex-col gap-1">
leaveTo="opacity-0" <div className="flex items-center justify-between">
> <Dialog.Title className="text-lg font-semibold leading-none text-zinc-100">
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" /> {data.length} unknowns
</Transition.Child> </Dialog.Title>
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center"> <Dialog.Close className="inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-white/10">
<Transition.Child <CancelIcon className="h-4 w-4 text-white/50" />
as={Fragment} </Dialog.Close>
enter="ease-out duration-300" </div>
enterFrom="opacity-0 scale-95" <Dialog.Description className="text-sm leading-none text-zinc-400">
enterTo="opacity-100 scale-100" All messages from people you not follow
leave="ease-in duration-200" </Dialog.Description>
leaveFrom="opacity-100 scale-100" </div>
leaveTo="opacity-0 scale-95" </div>
> <div className="flex h-[500px] flex-col overflow-y-auto overflow-x-hidden pb-2 pt-2">
<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"> {data.map((user) => (
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5"> <div
<div className="flex flex-col gap-1"> key={user.sender_pubkey}
<div className="flex items-center justify-between"> className="group flex items-center justify-between px-4 py-2 hover:bg-white/10"
<Dialog.Title >
as="h3" <User pubkey={user.sender_pubkey} />
className="text-lg font-semibold leading-none text-zinc-100" <div>
> <button
{data.length} unknowns type="button"
</Dialog.Title> onClick={() => openChat(user.sender_pubkey)}
<button className="hidden w-max rounded bg-white/10 px-3 py-1 text-sm font-medium hover:bg-fuchsia-500 group-hover:inline-flex"
type="button" >
onClick={closeModal} Chat
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-800" </button>
>
<CancelIcon className="h-4 w-4 text-zinc-300" />
</button>
</div>
<Dialog.Description className="text-sm leading-tight text-zinc-400">
All messages from people you not follow
</Dialog.Description>
</div> </div>
</div> </div>
<div className="flex h-[500px] flex-col overflow-y-auto overflow-x-hidden pb-5"> ))}
{data.map((user) => ( </div>
<div
key={user.sender_pubkey}
className="group flex items-center justify-between px-4 py-3 hover:bg-zinc-800"
>
<User pubkey={user.sender_pubkey} />
<div>
<button
type="button"
onClick={() => openChat(user.sender_pubkey)}
className="hidden w-max rounded-md bg-zinc-700 px-3 py-1 text-sm font-medium hover:bg-fuchsia-500 group-hover:inline-flex"
>
Chat
</button>
</div>
</div>
))}
</div>
</Dialog.Panel>
</Transition.Child>
</div> </div>
</Dialog> </Dialog.Content>
</Transition> </Dialog.Portal>
</> </Dialog.Root>
); );
} }

View File

@ -85,7 +85,7 @@ export function SpaceScreen() {
<AddBlock /> <AddBlock />
</div> </div>
</div> </div>
<div className="w-[350px] shrink-0" /> <div className="w-[250px] shrink-0" />
</div> </div>
); );
} }

View File

@ -58,21 +58,19 @@ export function Profile({ data }: { data: any }) {
); );
return ( return (
<div className="rounded-xl border-t border-zinc-800/50 bg-zinc-900 px-5 py-5"> <div className="rounded-xl bg-white/10 px-5 py-5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="inline-flex items-center gap-2"> <div className="inline-flex items-center gap-2">
<div className="h-11 w-11 shrink-0"> <Image
<Image src={profile.picture}
src={profile.picture} fallback={DEFAULT_AVATAR}
fallback={DEFAULT_AVATAR} className="h-11 w-11 shrink-0 rounded-lg object-cover"
className="h-11 w-11 rounded-lg object-cover" />
/>
</div>
<div className="inline-flex flex-col gap-1"> <div className="inline-flex flex-col gap-1">
<h3 className="max-w-[15rem] truncate font-semibold leading-none text-zinc-100"> <h3 className="max-w-[15rem] truncate font-semibold leading-none text-white">
{profile.display_name || profile.name} {profile.display_name || profile.name}
</h3> </h3>
<p className="max-w-[10rem] truncate text-sm leading-none text-zinc-400"> <p className="max-w-[10rem] truncate text-sm leading-none text-white/50">
{profile.nip05 || shortenKey(data.pubkey)} {profile.nip05 || shortenKey(data.pubkey)}
</p> </p>
</div> </div>
@ -81,15 +79,15 @@ export function Profile({ data }: { data: any }) {
{socialStatus === 'loading' ? ( {socialStatus === 'loading' ? (
<button <button
type="button" type="button"
className="inline-flex h-8 w-8 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500" className="inline-flex h-8 w-8 items-center justify-center rounded-md bg-white/10 hover:bg-fuchsia-500"
> >
<LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" /> <LoaderIcon className="h-4 w-4 animate-spin text-white" />
</button> </button>
) : followed ? ( ) : followed ? (
<button <button
type="button" type="button"
onClick={() => unfollowUser(data.pubkey)} onClick={() => unfollowUser(data.pubkey)}
className="inline-flex h-8 w-8 items-center justify-center rounded-md bg-zinc-800 text-zinc-400 hover:bg-fuchsia-500 hover:text-white" className="inline-flex h-8 w-8 items-center justify-center rounded-md bg-white/10 text-white hover:bg-fuchsia-500 hover:text-white"
> >
<UnfollowIcon className="h-4 w-4" /> <UnfollowIcon className="h-4 w-4" />
</button> </button>
@ -97,7 +95,7 @@ export function Profile({ data }: { data: any }) {
<button <button
type="button" type="button"
onClick={() => followUser(data.pubkey)} onClick={() => followUser(data.pubkey)}
className="inline-flex h-8 w-8 items-center justify-center rounded-md bg-zinc-800 text-zinc-400 hover:bg-fuchsia-500 hover:text-white" className="inline-flex h-8 w-8 items-center justify-center rounded-md bg-white/10 text-white hover:bg-fuchsia-500 hover:text-white"
> >
<FollowIcon className="h-4 w-4" /> <FollowIcon className="h-4 w-4" />
</button> </button>
@ -105,7 +103,7 @@ export function Profile({ data }: { data: any }) {
</div> </div>
</div> </div>
<div className="mt-2"> <div className="mt-2">
<p className="whitespace-pre-line break-words text-zinc-100"> <p className="whitespace-pre-line break-words text-white">
{profile.about || profile.bio} {profile.about || profile.bio}
</p> </p>
</div> </div>
@ -115,26 +113,26 @@ export function Profile({ data }: { data: any }) {
) : ( ) : (
<div className="flex w-full items-center gap-8"> <div className="flex w-full items-center gap-8">
<div className="inline-flex flex-col gap-1"> <div className="inline-flex flex-col gap-1">
<span className="font-semibold leading-none text-zinc-100"> <span className="font-semibold leading-none text-white">
{userStats.stats[data.pubkey].followers_pubkey_count ?? 0} {userStats.stats[data.pubkey].followers_pubkey_count ?? 0}
</span> </span>
<span className="text-sm leading-none text-zinc-400">Followers</span> <span className="text-sm leading-none text-white/50">Followers</span>
</div> </div>
<div className="inline-flex flex-col gap-1"> <div className="inline-flex flex-col gap-1">
<span className="font-semibold leading-none text-zinc-100"> <span className="font-semibold leading-none text-white">
{userStats.stats[data.pubkey].pub_following_pubkey_count ?? 0} {userStats.stats[data.pubkey].pub_following_pubkey_count ?? 0}
</span> </span>
<span className="text-sm leading-none text-zinc-400">Following</span> <span className="text-sm leading-none text-white/50">Following</span>
</div> </div>
<div className="inline-flex flex-col gap-1"> <div className="inline-flex flex-col gap-1">
<span className="font-semibold leading-none text-zinc-100"> <span className="font-semibold leading-none text-white">
{userStats.stats[data.pubkey].zaps_received {userStats.stats[data.pubkey].zaps_received
? compactNumber.format( ? compactNumber.format(
userStats.stats[data.pubkey].zaps_received.msats / 1000 userStats.stats[data.pubkey].zaps_received.msats / 1000
) )
: 0} : 0}
</span> </span>
<span className="text-sm leading-none text-zinc-400">Zaps received</span> <span className="text-sm leading-none text-white/50">Zaps received</span>
</div> </div>
</div> </div>
)} )}

View File

@ -14,18 +14,18 @@ export function TrendingNotes() {
}); });
return ( return (
<div className="flex w-[360px] shrink-0 flex-col border-r border-zinc-900"> <div className="scrollbar-hide relative h-full w-[400px] shrink-0 overflow-y-auto bg-white/10 pb-20">
<TitleBar title="Trending Posts" /> <TitleBar title="Trending Posts" />
<div className="scrollbar-hide flex h-full w-full flex-col justify-between gap-1.5 overflow-y-auto pb-20 pt-1.5"> <div className="h-full">
{error && <p>Failed to fetch</p>} {error && <p>Failed to fetch</p>}
{status === 'loading' ? ( {status === 'loading' ? (
<div className="px-3 py-1.5"> <div className="px-3 py-1.5">
<div className="shadow-input rounded-md bg-zinc-900 px-3 py-3 shadow-black/20"> <div className="shadow-input rounded-md px-3 py-3">
<NoteSkeleton /> <NoteSkeleton />
</div> </div>
</div> </div>
) : ( ) : (
<div className="relative flex w-full flex-col pt-1.5"> <div className="relative flex w-full flex-col">
{data.notes.map((item) => ( {data.notes.map((item) => (
<NoteKind_1 key={item.id} event={item.event} skipMetadata={true} /> <NoteKind_1 key={item.id} event={item.event} skipMetadata={true} />
))} ))}

View File

@ -15,9 +15,9 @@ export function TrendingProfiles() {
}); });
return ( return (
<div className="flex w-[360px] shrink-0 flex-col border-r border-zinc-900"> <div className="scrollbar-hide relative h-full w-[400px] shrink-0 overflow-y-auto bg-white/10 pb-20">
<TitleBar title="Trending Profiles" /> <TitleBar title="Trending Profiles" />
<div className="scrollbar-hide flex h-full w-full flex-col justify-between gap-1.5 overflow-y-auto pb-20 pt-1.5"> <div className="h-full">
{error && <p>Failed to fetch</p>} {error && <p>Failed to fetch</p>}
{status === 'loading' ? ( {status === 'loading' ? (
<div className="px-3 py-1.5"> <div className="px-3 py-1.5">
@ -26,7 +26,7 @@ export function TrendingProfiles() {
</div> </div>
</div> </div>
) : ( ) : (
<div className="relative flex w-full flex-col gap-3 px-3 pt-3"> <div className="relative flex w-full flex-col gap-3 px-3 pt-1.5">
{data.profiles.map((item) => ( {data.profiles.map((item) => (
<Profile key={item.pubkey} data={item} /> <Profile key={item.pubkey} data={item} />
))} ))}

View File

@ -3,7 +3,7 @@ import { TrendingProfiles } from '@app/trending/components/trendingProfiles';
export function TrendingScreen() { export function TrendingScreen() {
return ( return (
<div className="scrollbar-hide flex h-full w-full flex-nowrap overflow-x-auto overflow-y-hidden"> <div className="scrollbar-hide flex h-full w-full flex-nowrap divide-x divide-white/5 overflow-x-auto overflow-y-hidden">
<TrendingProfiles /> <TrendingProfiles />
<TrendingNotes /> <TrendingNotes />
</div> </div>

View File

@ -19,7 +19,7 @@ button {
} }
.ProseMirror p.is-empty::before { .ProseMirror p.is-empty::before {
@apply text-zinc-500; @apply text-white/50;
content: attr(data-placeholder); content: attr(data-placeholder);
float: left; float: left;
height: 0; height: 0;

View File

@ -14,7 +14,6 @@ import { CancelIcon, LoaderIcon, PlusCircleIcon } from '@shared/icons';
import { MentionNote } from '@shared/notes'; import { MentionNote } from '@shared/notes';
import { useComposer } from '@stores/composer'; import { useComposer } from '@stores/composer';
import { FULL_RELAYS } from '@stores/constants';
import { usePublish } from '@utils/hooks/usePublish'; import { usePublish } from '@utils/hooks/usePublish';
import { useImageUploader } from '@utils/hooks/useUploader'; import { useImageUploader } from '@utils/hooks/useUploader';
@ -45,7 +44,7 @@ export function Composer() {
Image.configure({ Image.configure({
HTMLAttributes: { HTMLAttributes: {
class: class:
'rounded-lg w-2/3 h-auto border border-zinc-800 outline outline-2 outline-offset-0 outline-zinc-700 ml-1', 'rounded-lg w-2/3 h-auto border border-white/10 outline outline-2 outline-offset-0 outline-white/20 ml-1',
}, },
}), }),
], ],
@ -54,7 +53,7 @@ export function Composer() {
attributes: { attributes: {
class: twMerge( class: twMerge(
'scrollbar-hide markdown break-all max-h-[500px] overflow-y-auto outline-none pr-2', 'scrollbar-hide markdown break-all max-h-[500px] overflow-y-auto outline-none pr-2',
`${reply.id ? '!min-h-42' : '!min-h-[100px]'}` `${reply.id ? '!min-h-42' : '!min-h-[120px]'}`
), ),
}, },
}, },
@ -88,13 +87,13 @@ export function Composer() {
if (reply.id && reply.pubkey) { if (reply.id && reply.pubkey) {
if (reply.root && reply.root.length > 1) { if (reply.root && reply.root.length > 1) {
tags = [ tags = [
['e', reply.root, FULL_RELAYS[0], 'root'], ['e', reply.root, 'wss://relayable.org', 'root'],
['e', reply.id, FULL_RELAYS[0], 'reply'], ['e', reply.id, 'wss://relayable.org', 'reply'],
['p', reply.pubkey], ['p', reply.pubkey],
]; ];
} else { } else {
tags = [ tags = [
['e', reply.id, FULL_RELAYS[0], 'reply'], ['e', reply.id, 'wss://relayable.org', 'reply'],
['p', reply.pubkey], ['p', reply.pubkey],
]; ];
} }
@ -131,7 +130,7 @@ export function Composer() {
<div className="flex h-full flex-col px-4 pb-4"> <div className="flex h-full flex-col px-4 pb-4">
<div className="flex h-full w-full gap-3"> <div className="flex h-full w-full gap-3">
<div className="flex w-8 shrink-0 items-center justify-center"> <div className="flex w-8 shrink-0 items-center justify-center">
<div className="h-full w-[2px] bg-zinc-800" /> <div className="h-full w-[2px] bg-white/10" />
</div> </div>
<div className="w-full"> <div className="w-full">
<EditorContent <EditorContent
@ -147,9 +146,9 @@ export function Composer() {
<button <button
type="button" type="button"
onClick={() => clearReply()} onClick={() => clearReply()}
className="absolute right-3 top-3 inline-flex h-6 w-6 items-center justify-center gap-2 rounded bg-zinc-800 px-2 hover:bg-zinc-700" className="absolute right-3 top-3 inline-flex h-6 w-6 items-center justify-center rounded bg-white/10 px-2"
> >
<CancelIcon className="h-4 w-4 text-zinc-100" /> <CancelIcon className="h-4 w-4 text-white" />
</button> </button>
</div> </div>
)} )}
@ -159,15 +158,15 @@ export function Composer() {
<button <button
type="button" type="button"
onClick={() => uploadImage()} onClick={() => uploadImage()}
className="inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-zinc-800" className="inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-white/10"
> >
<PlusCircleIcon className="h-5 w-5 text-zinc-500" /> <PlusCircleIcon className="h-5 w-5 text-white/50" />
</button> </button>
<Button onClick={() => submit()} preset="publish"> <Button onClick={() => submit()} preset="publish">
{status === 'loading' ? ( {status === 'loading' ? (
<LoaderIcon className="h-4 w-4 animate-spin text-zinc-100" /> <LoaderIcon className="h-4 w-4 animate-spin text-white" />
) : ( ) : (
'Publish' 'Postr'
)} )}
</Button> </Button>
</div> </div>

View File

@ -1,5 +1,4 @@
import { Dialog, Transition } from '@headlessui/react'; import * as Dialog from '@radix-ui/react-dialog';
import { Fragment } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { Button } from '@shared/button'; import { Button } from '@shared/button';
@ -27,64 +26,39 @@ export function ComposerModal() {
useHotkeys(COMPOSE_SHORTCUT, () => toggle(true)); useHotkeys(COMPOSE_SHORTCUT, () => toggle(true));
return ( return (
<> <Dialog.Root open={open}>
<Button onClick={() => toggle(true)} preset="small"> <Dialog.Trigger asChild>
<ComposeIcon className="h-4 w-4" /> <Button preset="small">
Postr <ComposeIcon className="h-4 w-4" />
</Button> Postr
<Transition appear show={open} as={Fragment}> </Button>
<Dialog as="div" className="relative z-10" onClose={closeModal}> </Dialog.Trigger>
<Transition.Child <Dialog.Portal className="relative z-10">
as={Fragment} <Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-xl" />
enter="ease-out duration-300" <Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
enterFrom="opacity-0" <div className="relative h-min w-full max-w-2xl rounded-xl bg-white/10">
enterTo="opacity-100" <div className="flex items-center justify-between px-4 py-4">
leave="ease-in duration-200" <div className="flex items-center gap-2">
leaveFrom="opacity-100" {account && <ComposerUser pubkey={account.pubkey} />}
leaveTo="opacity-0" <span>
> <ChevronRightIcon className="h-4 w-4 text-white/50" />
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" /> </span>
</Transition.Child> <div className="inline-flex h-7 w-max items-center justify-center gap-0.5 rounded bg-white/10 pl-3 pr-1.5 text-sm font-medium text-white">
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center"> New Post
<Transition.Child <ChevronDownIcon className="h-4 w-4" />
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">
{account && <ComposerUser pubkey={account.pubkey} />}
<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 className="h-4 w-4" />
</div>
</div>
<button
onClick={closeModal}
type="button"
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" />
</button>
</div> </div>
<Composer /> </div>
</Dialog.Panel> <Dialog.Close
</Transition.Child> onClick={() => closeModal()}
className="inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-zinc-800"
>
<CancelIcon className="h-5 w-5 text-white/50" />
</Dialog.Close>
</div>
<Composer />
</div> </div>
</Dialog> </Dialog.Content>
</Transition> </Dialog.Portal>
</> </Dialog.Root>
); );
} }

View File

@ -9,15 +9,13 @@ export function ComposerUser({ pubkey }: { pubkey: string }) {
return ( return (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="h-8 w-8 shrink-0 overflow-hidden rounded-md bg-zinc-900"> <Image
<Image src={user?.picture || user?.image}
src={user?.picture || user?.image} fallback={DEFAULT_AVATAR}
fallback={DEFAULT_AVATAR} alt={pubkey}
alt={pubkey} className="h-8 w-8 shrink-0 rounded-md object-cover"
className="h-8 w-8 object-cover" />
/> <h5 className="text-base font-semibold leading-none text-white">
</div>
<h5 className="text-base font-semibold leading-none text-zinc-100">
{user?.nip05 || user?.name || ( {user?.nip05 || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" /> <div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)} )}

View File

@ -1,4 +1,4 @@
import { Dialog, Transition } from '@headlessui/react'; import * as Dialog from '@radix-ui/react-dialog';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { relaunch } from '@tauri-apps/plugin-process'; import { relaunch } from '@tauri-apps/plugin-process';
import { Fragment, useState } from 'react'; import { Fragment, useState } from 'react';
@ -29,91 +29,63 @@ export function Logout() {
}; };
return ( return (
<> <Dialog.Root>
<button <Dialog.Trigger asChild>
type="button" <button
onClick={() => openModal()} type="button"
aria-label="Logout" aria-label="Logout"
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md bg-white/20 active:translate-y-1" className="inline-flex h-9 w-9 transform items-center justify-center rounded-md bg-white/20 active:translate-y-1"
> >
<LogoutIcon className="h-4 w-4 text-white" /> <LogoutIcon className="h-4 w-4 text-white" />
</button> </button>
<Transition appear show={isOpen} as={Fragment}> </Dialog.Trigger>
<Dialog as="div" className="relative z-10" onClose={closeModal}> <Dialog.Portal className="relative z-10">
<Transition.Child <Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-xl" />
as={Fragment} <Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
enter="ease-out duration-300" <div className="relative h-min w-full max-w-xl rounded-xl bg-white/10">
enterFrom="opacity-0" <div className="h-min w-full shrink-0 border-b border-white/10 bg-white/5 px-5 py-6">
enterTo="opacity-100" <div className="flex flex-col gap-2">
leave="ease-in duration-200" <div className="flex items-center justify-between">
leaveFrom="opacity-100" <Dialog.Title className="text-lg font-semibold leading-none text-white">
leaveTo="opacity-0" Are you sure!
> </Dialog.Title>
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" /> <Dialog.Close className="inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-white/10">
</Transition.Child> <CancelIcon className="h-4 w-4 text-white/50" />
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center"> </Dialog.Close>
<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>
<div className="flex h-full w-full flex-col items-end justify-center overflow-y-auto px-5 py-2.5"> <Dialog.Description className="text-sm leading-tight text-white/50">
<div className="flex items-center gap-2"> <p className="mb-2">
<button When logout, all local data will be wiped, and restart app then you
type="button" need to start onboarding process again when you log in.
onClick={closeModal} </p>
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" <p>
> In the next version, Lume will support multi account, then you can
Cancel switch between all account s instead of logout
</button> </p>
<button </Dialog.Description>
type="button" </div>
onClick={() => logout()} </div>
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" <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">
Confirm <button
</button> type="button"
</div> onClick={closeModal}
</div> className="inline-flex h-9 items-center justify-center rounded-md px-3 text-sm font-medium text-white/50 hover:bg-white/10"
</Dialog.Panel> >
</Transition.Child> 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-white hover:bg-red-600"
>
Confirm
</button>
</div>
</div>
</div> </div>
</Dialog> </Dialog.Content>
</Transition> </Dialog.Portal>
</> </Dialog.Root>
); );
} }

View File

@ -23,6 +23,7 @@ export function Navigation() {
return ( return (
<div className="relative h-full w-[232px] bg-black/80"> <div className="relative h-full w-[232px] bg-black/80">
<div className="absolute left-0 top-0 h-8 w-full" data-tauri-drag-region />
<div className="scrollbar-hide flex flex-col gap-5 overflow-y-auto pb-20"> <div className="scrollbar-hide flex flex-col gap-5 overflow-y-auto pb-20">
<div className="inline-flex h-8 items-center justify-between px-2 pb-4 pt-14"> <div className="inline-flex h-8 items-center justify-between px-2 pb-4 pt-14">
<ComposerModal /> <ComposerModal />
@ -53,7 +54,7 @@ export function Navigation() {
open ? '' : 'rotate-180' open ? '' : 'rotate-180'
)} )}
> >
<NavArrowDownIcon width={12} height={12} className="text-white/50" /> <NavArrowDownIcon className="h-3 w-3 text-white/50" />
</div> </div>
<h3 className="text-[11px] font-bold uppercase tracking-widest text-white/50"> <h3 className="text-[11px] font-bold uppercase tracking-widest text-white/50">
Feeds Feeds
@ -106,7 +107,7 @@ export function Navigation() {
open ? '' : 'rotate-180' open ? '' : 'rotate-180'
)} )}
> >
<NavArrowDownIcon width={12} height={12} className="text-white/50" /> <NavArrowDownIcon className="h-3 w-3 text-white/50" />
</div> </div>
<h3 className="text-[11px] font-bold uppercase tracking-widest text-white/50"> <h3 className="text-[11px] font-bold uppercase tracking-widest text-white/50">
Chats Chats

View File

@ -80,7 +80,7 @@ export function NoteReaction({ id, pubkey }: { id: string; pubkey: string }) {
<button <button
type="button" type="button"
onClick={() => react('👏')} onClick={() => react('👏')}
className="inline-flex h-8 w-8 items-center justify-center rounded hover:bg-zinc-600" className="inline-flex h-8 w-8 items-center justify-center rounded hover:bg-white/10"
> >
<img <img
src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Hand%20gestures/Clapping%20Hands.png" src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Hand%20gestures/Clapping%20Hands.png"
@ -91,7 +91,7 @@ export function NoteReaction({ id, pubkey }: { id: string; pubkey: string }) {
<button <button
type="button" type="button"
onClick={() => react('🤪')} onClick={() => react('🤪')}
className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-600" className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-white/10"
> >
<img <img
src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Smilies/Face%20with%20Tongue.png" src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Smilies/Face%20with%20Tongue.png"
@ -102,7 +102,7 @@ export function NoteReaction({ id, pubkey }: { id: string; pubkey: string }) {
<button <button
type="button" type="button"
onClick={() => react('😮')} onClick={() => react('😮')}
className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-600" className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-white/10"
> >
<img <img
src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Smilies/Face%20with%20Open%20Mouth.png" src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Smilies/Face%20with%20Open%20Mouth.png"
@ -113,7 +113,7 @@ export function NoteReaction({ id, pubkey }: { id: string; pubkey: string }) {
<button <button
type="button" type="button"
onClick={() => react('😢')} onClick={() => react('😢')}
className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-600" className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-white/10"
> >
<img <img
src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Smilies/Crying%20Face.png" src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Smilies/Crying%20Face.png"
@ -124,7 +124,7 @@ export function NoteReaction({ id, pubkey }: { id: string; pubkey: string }) {
<button <button
type="button" type="button"
onClick={() => react('🤡')} onClick={() => react('🤡')}
className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-600" className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-white/10"
> >
<img <img
src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Smilies/Clown%20Face.png" src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Smilies/Clown%20Face.png"

View File

@ -29,7 +29,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
onKeyDown={(e) => openThread(e, id)} onKeyDown={(e) => openThread(e, id)}
role="button" role="button"
tabIndex={0} tabIndex={0}
className="mb-2 mt-3 cursor-default rounded-lg border-t border-zinc-700/50 bg-zinc-800/50 px-3 py-3" className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3"
> >
{status === 'loading' ? ( {status === 'loading' ? (
<NoteSkeleton /> <NoteSkeleton />

View File

@ -27,10 +27,11 @@ export function LinkPreview({ urls }: { urls: string[] }) {
rel="noreferrer" rel="noreferrer"
> >
{error ? ( {error ? (
<div className="px-3 py-3"> <div className="flex flex-col gap-2 px-3 py-3">
<p className="line-clamp-3 break-all text-sm text-zinc-400"> <p className="text-sm text-white/50">
Can&apos;t fetch open graph, click to open webpage Can&apos;t fetch open graph, click to open webpage
</p> </p>
<span className="text-sm leading-none text-white">{domain.hostname}</span>
</div> </div>
) : ( ) : (
<> <>

View File

@ -57,10 +57,10 @@ export function NotificationModal({ pubkey }: { pubkey: string }) {
</button> </button>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Portal className="relative z-10"> <Dialog.Portal className="relative z-10">
<Dialog.Overlay className="fixed inset-0 z-[1000px] bg-black bg-opacity-30 backdrop-blur-md data-[state=open]:animate-overlayShow" /> <Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-xl" />
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center data-[state=open]:animate-contentShow"> <Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Dialog.Content 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="relative h-min w-full max-w-xl rounded-xl bg-white/10">
<div className="h-min w-full shrink-0 border-b border-zinc-800 px-5 py-5"> <div className="h-min w-full shrink-0 border-b border-white/10 bg-white/5 px-5 py-5">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Dialog.Title className="text-lg font-semibold leading-none text-zinc-100"> <Dialog.Title className="text-lg font-semibold leading-none text-zinc-100">
@ -96,8 +96,8 @@ export function NotificationModal({ pubkey }: { pubkey: string }) {
data.map((event) => renderItem(event)) data.map((event) => renderItem(event))
)} )}
</div> </div>
</Dialog.Content> </div>
</div> </Dialog.Content>
</Dialog.Portal> </Dialog.Portal>
</Dialog.Root> </Dialog.Root>
); );

View File

@ -1,6 +1,6 @@
import { Popover, Transition } from '@headlessui/react'; import * as Popover from '@radix-ui/react-popover';
import { Fragment } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { VerticalDotsIcon } from '@shared/icons'; import { VerticalDotsIcon } from '@shared/icons';
import { Image } from '@shared/image'; import { Image } from '@shared/image';
@ -9,7 +9,7 @@ import { DEFAULT_AVATAR } from '@stores/constants';
import { formatCreatedAt } from '@utils/createdAt'; import { formatCreatedAt } from '@utils/createdAt';
import { useProfile } from '@utils/hooks/useProfile'; import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey'; import { displayNpub, shortenKey } from '@utils/shortenKey';
export function User({ export function User({
pubkey, pubkey,
@ -50,99 +50,94 @@ export function User({
} }
return ( return (
<Popover <Popover.Root>
className={`flex ${size === 'small' ? 'items-center gap-2' : 'items-start gap-3'}`}
>
<Popover.Button
className={`${avatarWidth} ${avatarHeight} relative z-10 shrink-0 overflow-hidden bg-zinc-900`}
>
<Image
src={user?.picture || user?.image}
fallback={DEFAULT_AVATAR}
alt={pubkey}
className={`${
isRepost ? 'ring-1 ring-zinc-800' : ''
} ${avatarWidth} ${avatarHeight} ${
size === 'small' ? 'rounded' : 'rounded-lg'
} object-cover`}
/>
</Popover.Button>
<div <div
className={`${isRepost ? 'mt-4' : ''} flex flex-1 items-baseline justify-between`} className={twMerge(
'relative flex',
size === 'small' ? 'items-center gap-2' : 'items-start gap-3'
)}
> >
<h5 <Popover.Trigger asChild>
className={`truncate font-semibold leading-none text-zinc-100 ${
size === 'small' ? 'max-w-[10rem]' : 'max-w-[15rem]'
}`}
>
{user?.nip05?.toLowerCase() ||
user?.name ||
user?.display_name ||
shortenKey(pubkey)}
</h5>
<div className="inline-flex items-center gap-2">
<span className="leading-none text-white/50">{createdAt}</span>
<button <button
type="button" type="button"
className="inline-flex h-5 w-max items-center justify-center rounded px-1 hover:bg-white/20" className={`${avatarWidth} ${avatarHeight} relative z-40 shrink-0 overflow-hidden`}
> >
<VerticalDotsIcon className="h-4 w-4 rotate-90 transform text-white/50" /> <Image
src={user?.picture || user?.image}
fallback={DEFAULT_AVATAR}
alt={pubkey}
className={twMerge(
`object-cover ${avatarWidth} ${avatarHeight}`,
size === 'small' ? 'rounded' : 'rounded-lg',
isRepost ? 'ring-1 ring-zinc-800' : ''
)}
/>
</button> </button>
</Popover.Trigger>
<div
className={twMerge(
'flex flex-1 items-baseline justify-between',
isRepost ? 'mt-4' : ''
)}
>
<h5
className={twMerge(
'truncate font-semibold leading-none text-white',
size === 'small' ? 'max-w-[10rem]' : 'max-w-[15rem]'
)}
>
{user?.nip05?.toLowerCase() ||
user?.name ||
user?.display_name ||
shortenKey(pubkey)}
</h5>
<span className="leading-none text-white/50">{createdAt}</span>
</div> </div>
</div> </div>
<Transition <Popover.Portal>
as={Fragment} <Popover.Content
enter="transition ease-out duration-200" className="w-[300px] overflow-hidden rounded-md bg-white/10 backdrop-blur-xl"
enterFrom="opacity-0 translate-y-1" sideOffset={5}
enterTo="opacity-100 translate-y-0" >
leave="transition ease-in duration-150" <div className="flex gap-2.5 border-b border-white/5 px-3 py-3">
leaveFrom="opacity-100 translate-y-0" <Image
leaveTo="opacity-0 translate-y-1" src={user?.picture || user?.image}
> fallback={DEFAULT_AVATAR}
<Popover.Panel className="absolute top-10 z-50 mt-3"> alt={pubkey}
<div className="w-[250px] overflow-hidden rounded-md border border-zinc-800/50 bg-zinc-900/90 backdrop-blur-lg"> className="h-11 w-11 shrink-0 rounded-lg object-cover"
<div className="flex gap-2.5 border-b border-zinc-800 px-3 py-3"> />
<Image <div className="flex flex-1 flex-col gap-2">
src={user?.picture || user?.image} <div className="inline-flex flex-col gap-1">
fallback={DEFAULT_AVATAR} <h5 className="text-sm font-semibold leading-none">
alt={pubkey} {user?.displayName || user?.name}
className="h-11 w-11 shrink-0 rounded-lg object-cover" </h5>
/> <span className="max-w-[10rem] truncate text-sm leading-none text-white/50">
<div className="flex flex-1 flex-col gap-2"> {user?.nip05 || displayNpub(pubkey, 16)}
<div className="inline-flex flex-col gap-1"> </span>
<h5 className="text-sm font-semibold leading-none"> </div>
{user?.displayName || user?.name || ( <div>
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" /> <p className="line-clamp-3 break-all leading-tight text-white">
)} {user?.about}
</h5> </p>
<span className="max-w-[10rem] truncate text-sm leading-none text-zinc-500">
{user?.nip05 || shortenKey(pubkey)}
</span>
</div>
<div>
<p className="line-clamp-3 break-all leading-tight text-zinc-100">
{user?.about}
</p>
</div>
</div> </div>
</div> </div>
<div className="flex items-center gap-2 px-3 py-3">
<Link
to={`/app/users/${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/chats/${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> </div>
</Popover.Panel> <div className="flex items-center gap-2 px-3 py-3">
</Transition> <Link
</Popover> to={`/app/users/${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-white/10 text-sm font-medium hover:bg-fuchsia-500"
>
View profile
</Link>
<Link
to={`/app/chats/${pubkey}`}
className="inline-flex h-10 flex-1 items-center justify-center rounded-md bg-white/10 text-sm font-medium hover:bg-fuchsia-500"
>
Message
</Link>
</div>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
); );
} }