fix errors

This commit is contained in:
Ren Amamiya 2023-07-01 15:18:21 +07:00
parent 332dbf608d
commit 8a92813211
9 changed files with 186 additions and 123 deletions

View File

@ -3,40 +3,42 @@ import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile"; import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey"; import { shortenKey } from "@utils/shortenKey";
export function User({ pubkey }: { pubkey: string }) { export function User({
const { status, user } = useProfile(pubkey); pubkey,
fallback,
}: { pubkey: string; fallback?: string }) {
const { status, user } = useProfile(pubkey, fallback);
if (status === "loading") {
return (
<div className="flex items-center gap-2">
<div className="relative h-10 w-10 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>
);
}
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{status === "loading" ? ( <div className="relative h-10 w-10 shrink rounded-md">
<> <Image
<div className="relative h-11 w-11 shrink-0 rounded-md bg-zinc-800 animate-pulse" /> src={user.picture || user.image}
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start"> fallback={DEFAULT_AVATAR}
<span className="w-1/2 h-4 rounded bg-zinc-800 animate-pulse" /> alt={pubkey}
<span className="w-1/3 h-3 rounded bg-zinc-800 animate-pulse" /> className="h-10 w-10 rounded-md object-cover"
</div> />
</> </div>
) : ( <div className="flex w-full flex-1 flex-col items-start text-start">
<> <span className="truncate font-medium leading-tight text-zinc-100">
<div className="relative h-11 w-11 shrink rounded-md"> {user.name || user.displayName || user.display_name}
<Image </span>
src={user.image} <span className="text-base leading-tight text-zinc-400">
fallback={DEFAULT_AVATAR} {user.nip05?.toLowerCase() || shortenKey(pubkey)}
alt={pubkey} </span>
className="h-11 w-11 rounded-md object-cover" </div>
decoding="async"
/>
</div>
<div className="flex w-full flex-1 flex-col items-start text-start">
<span className="truncate font-medium leading-tight text-zinc-100">
{user.displayName || user.name}
</span>
<span className="text-base leading-tight text-zinc-400">
{user.nip05?.toLowerCase() || shortenKey(pubkey)}
</span>
</div>
</>
)}
</div> </div>
); );
} }

View File

@ -1,9 +1,10 @@
import { AvatarUploader } from "@shared/avatarUploader"; import { AvatarUploader } from "@shared/avatarUploader";
import { BannerUploader } from "@shared/bannerUploader";
import { LoaderIcon } from "@shared/icons"; import { LoaderIcon } from "@shared/icons";
import { Image } from "@shared/image"; import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants"; import { DEFAULT_AVATAR } from "@stores/constants";
import { useOnboarding } from "@stores/onboarding"; import { useOnboarding } from "@stores/onboarding";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
@ -11,32 +12,36 @@ export function CreateStep2Screen() {
const navigate = useNavigate(); const navigate = useNavigate();
const createProfile = useOnboarding((state: any) => state.createProfile); const createProfile = useOnboarding((state: any) => state.createProfile);
const [image, setImage] = useState(DEFAULT_AVATAR); const [picture, setPicture] = useState(DEFAULT_AVATAR);
const [banner, setBanner] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { const {
register, register,
handleSubmit, handleSubmit,
setValue,
formState: { isDirty, isValid }, formState: { isDirty, isValid },
} = useForm(); } = useForm();
const onSubmit = (data: any) => { const onSubmit = (data: any) => {
setLoading(true); setLoading(true);
try { try {
const profile = { ...data, name: data.displayName }; const profile = {
...data,
username: data.name,
display_name: data.name,
bio: data.about,
};
createProfile(profile); createProfile(profile);
// redirect to step 3 // redirect to next step
navigate("/auth/create/step-3"); setTimeout(
() => navigate("/auth/create/step-3", { replace: true }),
1200,
);
} catch { } catch {
console.log("error"); console.log("error");
} }
}; };
useEffect(() => {
setValue("picture", image);
}, [setValue, image]);
return ( return (
<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-8 text-center">
@ -44,55 +49,84 @@ export function CreateStep2Screen() {
Create your profile Create your profile
</h1> </h1>
</div> </div>
<div className="w-full rounded-xl border-t border-zinc-800/50 bg-zinc-900 p-5"> <div className="w-full rounded-xl border-t border-zinc-800/50 bg-zinc-900 overflow-hidden">
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4"> <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col mb-0">
<input <input
type={"hidden"} type={"hidden"}
{...register("picture")} {...register("picture")}
value={image} 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" 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="flex flex-col gap-1"> <input
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400"> type={"hidden"}
Avatar {...register("banner")}
</label> value={banner}
<div className="relative inline-flex h-36 w-full items-center justify-center overflow-hidden rounded-lg border border-zinc-900 bg-zinc-950"> 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 <Image
src={image} src={banner}
fallback={DEFAULT_AVATAR} fallback="https://void.cat/d/QY1myro5tkHVs2nY7dy74b.jpg"
alt="avatar" alt="user's banner"
className="relative z-10 h-11 w-11 rounded-md" className="h-full w-full object-cover"
/> />
<div className="absolute bottom-3 right-3 z-10"> <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 valueState={setImage} /> <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>
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-4 px-4 pb-4">
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400"> <div className="flex flex-col gap-1">
Display Name * <label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
</label> Name *
<input </label>
type={"text"} <input
{...register("displayName", { type={"text"}
required: true, {...register("name", {
minLength: 4, 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" 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"> </div>
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400"> <div className="flex flex-col gap-1">
Bio <label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
</label> Bio
<textarea </label>
{...register("about")} <textarea
spellCheck={false} {...register("about")}
className="resize-none relative h-20 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500" spellCheck={false}
/> className="resize-none relative h-20 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
</div> />
<div> </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>
<button <button
type="submit" type="submit"
disabled={!isDirty || !isValid} disabled={!isDirty || !isValid}

View File

@ -3,13 +3,13 @@ import { updateAccount } from "@libs/storage";
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { CheckCircleIcon, LoaderIcon } from "@shared/icons"; import { CheckCircleIcon, LoaderIcon } from "@shared/icons";
import { RelayContext } from "@shared/relayProvider"; import { RelayContext } from "@shared/relayProvider";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useAccount } from "@utils/hooks/useAccount"; import { useAccount } from "@utils/hooks/useAccount";
import { arrayToNIP02 } from "@utils/transform"; import { arrayToNIP02 } from "@utils/transform";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
const initialList = [ const INITIAL_LIST = [
{ {
pubkey: "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", pubkey: "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2",
}, },
@ -117,6 +117,13 @@ export function CreateStep4Screen() {
const [follows, setFollows] = useState([]); const [follows, setFollows] = useState([]);
const { account } = useAccount(); const { account } = useAccount();
const { status, data } = useQuery(["trending-profiles"], async () => {
const res = await fetch("https://api.nostr.band/v0/trending/profiles");
if (!res.ok) {
throw new Error("Error");
}
return res.json();
});
// toggle follow state // toggle follow state
const toggleFollow = (pubkey: string) => { const toggleFollow = (pubkey: string) => {
@ -140,7 +147,7 @@ export function CreateStep4Screen() {
try { try {
setLoading(true); setLoading(true);
const tags = arrayToNIP02(follows); const tags = arrayToNIP02([...follows, account.pubkey]);
const signer = new NDKPrivateKeySigner(account.privkey); const signer = new NDKPrivateKeySigner(account.privkey);
ndk.signer = signer; ndk.signer = signer;
@ -154,7 +161,7 @@ export function CreateStep4Screen() {
event.publish(); event.publish();
// update // update
update.mutate(follows); update.mutate([...follows, account.pubkey]);
// redirect to next step // redirect to next step
setTimeout(() => navigate("/auth/onboarding", { replace: true }), 1200); setTimeout(() => navigate("/auth/onboarding", { replace: true }), 1200);
@ -163,6 +170,8 @@ export function CreateStep4Screen() {
} }
}; };
const list = data ? data.profiles.concat(INITIAL_LIST) : [];
return ( return (
<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-8 text-center">
@ -179,27 +188,34 @@ export function CreateStep4Screen() {
</span>{" "} </span>{" "}
plebs plebs
</div> </div>
<div className="scrollbar-hide flex h-96 flex-col overflow-y-auto py-2"> {status === "loading" ? (
{initialList.map((item: { pubkey: string }, index: number) => ( <div className="py-2 px-4 w-full h-11 inline-flex items-center justify-center">
<button <LoaderIcon className="h-4 w-4 animate-spin text-black dark:text-zinc-100" />
key={`item-${index}`} </div>
type="button" ) : (
onClick={() => toggleFollow(item.pubkey)} <div className="scrollbar-hide flex h-96 flex-col overflow-y-auto py-2">
className="inline-flex transform items-center justify-between bg-zinc-900 px-4 py-2 hover:bg-zinc-800 active:translate-y-1" {list.map(
> (item: { pubkey: string; profile: { content: string } }) => (
<User pubkey={item.pubkey} /> <button
{follows.includes(item.pubkey) && ( key={item.pubkey}
<div> type="button"
<CheckCircleIcon onClick={() => toggleFollow(item.pubkey)}
width={16} className="inline-flex transform items-center justify-between bg-zinc-900 px-4 py-2 hover:bg-zinc-800 active:translate-y-1"
height={16} >
className="text-green-400" <User
pubkey={item.pubkey}
fallback={item.profile?.content}
/> />
</div> {follows.includes(item.pubkey) && (
)} <div>
</button> <CheckCircleIcon className="w-4 h-4 text-green-400" />
))} </div>
</div> )}
</button>
),
)}
</div>
)}
</div> </div>
{follows.length >= 10 && ( {follows.length >= 10 && (
<button <button

View File

@ -38,7 +38,7 @@ export function ImportStep2Screen() {
const followsList = setToArray(follows); const followsList = setToArray(follows);
// update // update
update.mutate(followsList); update.mutate([...followsList, account.pubkey]);
// redirect to next step // redirect to next step
setTimeout(() => navigate("/auth/onboarding", { replace: true }), 1200); setTimeout(() => navigate("/auth/onboarding", { replace: true }), 1200);
@ -47,8 +47,6 @@ export function ImportStep2Screen() {
} }
}; };
console.log(account);
return ( return (
<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-8 text-center">

View File

@ -9,6 +9,7 @@ import { NoteSkeleton } from "@shared/notes/skeleton";
import { TitleBar } from "@shared/titleBar"; import { TitleBar } from "@shared/titleBar";
import { User } from "@shared/user"; import { User } from "@shared/user";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useAccount } from "@utils/hooks/useAccount";
import { parser } from "@utils/parser"; import { parser } from "@utils/parser";
export function ThreadBlock({ params }: { params: any }) { export function ThreadBlock({ params }: { params: any }) {
@ -16,6 +17,7 @@ export function ThreadBlock({ params }: { params: any }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { account } = useAccount();
const { status, data } = useQuery(["thread", params.content], async () => { const { status, data } = useQuery(["thread", params.content], async () => {
const res = await getNoteByID(params.content); const res = await getNoteByID(params.content);
res["content"] = parser(res); res["content"] = parser(res);
@ -55,7 +57,12 @@ export function ThreadBlock({ params }: { params: any }) {
</div> </div>
</div> </div>
<div className="mt-3 bg-zinc-900 rounded-md"> <div className="mt-3 bg-zinc-900 rounded-md">
<NoteReplyForm rootID={params.content} /> {account && (
<NoteReplyForm
rootID={params.content}
userPubkey={account.pubkey}
/>
)}
</div> </div>
</div> </div>
)} )}

View File

@ -60,7 +60,7 @@ export function AvatarUploader({ setPicture }: { setPicture: any }) {
className="w-full h-full inline-flex items-center justify-center bg-zinc-900/40" className="w-full h-full inline-flex items-center justify-center bg-zinc-900/40"
> >
{loading ? ( {loading ? (
<LoaderIcon className="h-6 w-6 animate-spintext-zinc-100" /> <LoaderIcon className="h-6 w-6 animate-spin text-zinc-100" />
) : ( ) : (
<PlusIcon className="h-6 w-6 text-zinc-100" /> <PlusIcon className="h-6 w-6 text-zinc-100" />
)} )}

View File

@ -60,7 +60,7 @@ export function BannerUploader({ setBanner }: { setBanner: any }) {
className="w-full h-full inline-flex items-center justify-center bg-zinc-900/40" className="w-full h-full inline-flex items-center justify-center bg-zinc-900/40"
> >
{loading ? ( {loading ? (
<LoaderIcon className="h-8 w-8 animate-spintext-zinc-100" /> <LoaderIcon className="h-8 w-8 animate-spin text-zinc-100" />
) : ( ) : (
<PlusIcon className="h-8 w-8 text-zinc-100" /> <PlusIcon className="h-8 w-8 text-zinc-100" />
)} )}

View File

@ -2,16 +2,16 @@ import { usePublish } from "@libs/ndk";
import { Button } from "@shared/button"; import { Button } from "@shared/button";
import { Image } from "@shared/image"; import { Image } from "@shared/image";
import { DEFAULT_AVATAR, FULL_RELAYS } from "@stores/constants"; import { DEFAULT_AVATAR, FULL_RELAYS } from "@stores/constants";
import { useAccount } from "@utils/hooks/useAccount";
import { useProfile } from "@utils/hooks/useProfile"; import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import { useState } from "react"; import { useState } from "react";
export function NoteReplyForm({ rootID }: { rootID: string }) { export function NoteReplyForm({
rootID,
userPubkey,
}: { rootID: string; userPubkey: string }) {
const publish = usePublish(); const publish = usePublish();
const { status, user } = useProfile(userPubkey);
const { account } = useAccount();
const { status, user } = useProfile(account.npub);
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const submit = () => { const submit = () => {
@ -45,9 +45,9 @@ export function NoteReplyForm({ rootID }: { rootID: string }) {
<div className="inline-flex items-center gap-2"> <div className="inline-flex items-center gap-2">
<div className="relative h-9 w-9 shrink-0 rounded"> <div className="relative h-9 w-9 shrink-0 rounded">
<Image <Image
src={user?.image} src={user.image}
fallback={DEFAULT_AVATAR} fallback={DEFAULT_AVATAR}
alt={account.npub} alt={userPubkey}
className="h-9 w-9 rounded-md bg-white object-cover" className="h-9 w-9 rounded-md bg-white object-cover"
/> />
</div> </div>
@ -56,7 +56,7 @@ export function NoteReplyForm({ rootID }: { rootID: string }) {
Reply as Reply as
</p> </p>
<p className="leading-none text-sm font-medium text-zinc-100"> <p className="leading-none text-sm font-medium text-zinc-100">
{user?.nip05 || user?.name} {user.nip05 || user.name || shortenKey(userPubkey)}
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,8 +1,9 @@
import { NDKUser } from "@nostr-dev-kit/ndk";
import { RelayContext } from "@shared/relayProvider"; import { RelayContext } from "@shared/relayProvider";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useContext } from "react"; import { useContext } from "react";
export function useProfile(pubkey: string) { export function useProfile(pubkey: string, fallback?: string) {
const ndk = useContext(RelayContext); const ndk = useContext(RelayContext);
const { const {
status, status,
@ -12,10 +13,15 @@ export function useProfile(pubkey: string) {
} = useQuery( } = useQuery(
["user", pubkey], ["user", pubkey],
async () => { async () => {
const user = ndk.getUser({ hexpubkey: pubkey }); if (fallback) {
await user.fetchProfile(); const profile = JSON.parse(fallback);
return profile;
} else {
const user = ndk.getUser({ hexpubkey: pubkey });
await user.fetchProfile();
return user.profile; return user.profile;
}
}, },
{ {
staleTime: Infinity, staleTime: Infinity,