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

View File

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

View File

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

View File

@ -38,7 +38,7 @@ export function ImportStep2Screen() {
const followsList = setToArray(follows);
// update
update.mutate(followsList);
update.mutate([...followsList, account.pubkey]);
// redirect to next step
setTimeout(() => navigate("/auth/onboarding", { replace: true }), 1200);
@ -47,8 +47,6 @@ export function ImportStep2Screen() {
}
};
console.log(account);
return (
<div className="mx-auto w-full max-w-md">
<div className="mb-8 text-center">

View File

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

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"
>
{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" />
)}

View File

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

View File

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