Skip to content

Commit

Permalink
feature: add toasters (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
micpst authored Feb 4, 2024
1 parent 4d1ff7b commit e8d7481
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 61 deletions.
90 changes: 54 additions & 36 deletions app/components/input/input.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import cn from "clsx";
import { motion } from "framer-motion";
import React, { useState, useRef } from "react";
import Link from "next/link";
import { nip19 } from "nostr-tools";
import { useState, useRef } from "react";
import toast from "react-hot-toast";
import type { Variants } from "framer-motion";
import type { ReactNode, FormEvent, ChangeEvent } from "react";
import type { ChangeEvent, FormEvent, JSX, ReactNode } from "react";
import InputForm from "@/app/components/input/input-form";
import InputOptions from "@/app/components/input/input-options";
import UserAvatar from "@/app/components/user/user-avatar";
Expand Down Expand Up @@ -35,53 +38,70 @@ function Input({
children,
replyModal,
closeModal,
}: InputProps): JSX.Element | null {
const [inputValue, setInputValue] = useState("");
const [loading, setLoading] = useState(false);
const [visited, setVisited] = useState(false);

const inputRef = useRef<HTMLTextAreaElement>(null);

}: InputProps): JSX.Element {
const { publicKey } = useAuth();
const { profiles, isLoading } = useProfile();
const { profiles } = useProfile();
const { relays } = useRelay();

if (!publicKey) return null;
const [inputValue, setInputValue] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);

const user = profiles.get(publicKey);
const inputRef = useRef<HTMLTextAreaElement>(null);

const { about, name, picture, banner, nip05 } = user as User;
const { picture } = profiles.get(publicKey || "") as User;

const isReplying = reply ?? replyModal;

const publishNote = async (): Promise<void> => {
if (!inputValue || !user) return;
if (!inputValue || !publicKey) return;

setIsLoading(true);

inputRef.current?.blur();

const content = inputValue.trim();

if (isReplying && parentId) {
await noteService.createNoteReplyAsync({
relays: Array.from(relays.values()),
pubkey: publicKey,
content,
parentId,
});
const note =
isReplying && parentId
? await noteService.createNoteReplyAsync({
relays: Array.from(relays.values()),
pubkey: publicKey,
content,
parentId,
})
: await noteService.createNoteAsync({
relays: Array.from(relays.values()),
pubkey: publicKey,
content,
});

if (note.relays.length > 0) {
if (!modal && !replyModal) {
discardNote();
setIsLoading(false);
}
if (closeModal) {
closeModal();
}

toast.success(() => (
<span className="flex gap-2">
Your note was sent
<Link
className="custom-underline font-bold"
href={`/n/${nip19.noteEncode(note.id)}`}
>
View
</Link>
</span>
));
} else {
await noteService.createNoteAsync({
relays: Array.from(relays.values()),
pubkey: publicKey,
content,
});
setIsLoading(false);
toast.error("Your note was not sent to any relay.");
}

closeModal();
};

const discardNote = (): void => {
setInputValue("");
setVisited(false);

inputRef.current?.blur();
};
Expand All @@ -95,6 +115,8 @@ function Input({
void publishNote();
};

if (!publicKey) return <></>;

return (
<form
className={cn("flex flex-col", {
Expand All @@ -104,7 +126,7 @@ function Input({
})}
onSubmit={handleSubmit}
>
{loading && (
{isLoading && (
<motion.i className="h-1 animate-pulse bg-main-accent" {...variants} />
)}
{children}
Expand All @@ -116,12 +138,8 @@ function Input({
: replyModal
? "pt-0"
: "border-b-2 border-light-border dark:border-dark-border",
(disabled || loading) && "pointer-events-none opacity-50",
(disabled || isLoading) && "pointer-events-none opacity-50",
)}
// className={cn(
// "hover-animation grid w-full grid-cols-[auto,1fr] gap-3 p-4 border-b-2 border-light-border",
// (disabled || loading) && "pointer-events-none opacity-50"
// )}
>
<UserAvatar src={picture} />
<div className="flex w-full flex-col gap-4">
Expand Down
14 changes: 12 additions & 2 deletions app/components/modal/edit-profile-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import type { ReactNode } from "react";
import cn from "clsx";
import type { JSX, ReactNode } from "react";
import Header from "@/app/components/common/header";
import Button from "@/app/components/ui/button";

type EditProfileModalProps = {
children: ReactNode;
isLoading: boolean;
closeModal: () => void;
updateData: () => void;
};

export function EditProfileModal({
children,
isLoading,
closeModal,
updateData,
}: EditProfileModalProps): JSX.Element {
Expand All @@ -22,18 +25,25 @@ export function EditProfileModal({
className="absolute flex w-full items-center gap-6 rounded-tl-2xl"
title="Edit profile"
action={closeModal}
disableSticky
>
<div className="ml-auto flex items-center gap-3">
<Button
className="bg-light-primary py-1 px-4 font-bold text-white focus-visible:bg-light-primary/90
enabled:hover:bg-light-primary/90 enabled:active:bg-light-primary/80 disabled:brightness-75"
onClick={updateData}
disabled={isLoading}
>
Save
</Button>
</div>
</Header>
<section className="h-full transition-opacity">
<section
className={cn(
"h-full overflow-y-auto transition-opacity",
isLoading && "pointer-events-none opacity-50",
)}
>
<div className="relative flex flex-col gap-6 px-4 pt-3 pb-4">
{children}
</div>
Expand Down
48 changes: 33 additions & 15 deletions app/components/user/user-edit-profile.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import Button from "@/app/components/ui/button";
import { useModal } from "@/app/lib/hooks/useModal";
import type { EditableData, User } from "@/app/lib/types/user";
import { useState } from "react";
import type { EditableUserData } from "@/app/lib/types/user";
import cn from "clsx";
import Modal from "@/app/components/modal/modal";
import { EditProfileModal } from "@/app/components/modal/edit-profile-modal";
import type { InputFieldProps } from "@/app/components/input/input-field";
import { useState } from "react";
import toast from "react-hot-toast";
import type { ChangeEvent, KeyboardEvent } from "react";
import { InputField } from "@/app/components/input/input-field";
import { useRelay } from "@/app/lib/context/relay-provider";
import { EditProfileModal } from "@/app/components/modal/edit-profile-modal";
import Modal from "@/app/components/modal/modal";
import Button from "@/app/components/ui/button";
import { useProfile } from "@/app/lib/context/profile-provider";
import type { ChangeEvent, KeyboardEvent } from "react";
import NostrService from "@/app/lib/services/nostrService";
import { useModal } from "@/app/lib/hooks/useModal";
import type { InputFieldProps } from "@/app/components/input/input-field";
import type {
EditableData,
EditableUserData,
User,
} from "@/app/lib/types/user";

type RequiredInputFieldProps = Omit<InputFieldProps, "handleChange"> & {
inputId: EditableData;
Expand All @@ -26,6 +28,7 @@ function UserEditProfile({ hide, user }: UserEditProfileProps): JSX.Element {
const { open, openModal, closeModal } = useModal();
const { setProfile } = useProfile();
const { about, name, picture, banner, nip05 } = user;
const [isLoading, setIsLoading] = useState<boolean>(false);
const [editUserData, setEditUserData] = useState<EditableUserData>({
about,
name,
Expand Down Expand Up @@ -80,13 +83,15 @@ function UserEditProfile({ hide, user }: UserEditProfileProps): JSX.Element {
ctrlKey,
}: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
if (ctrlKey && key === "Enter") {
updateData();
void updateData();
}
};

const updateData = (): void => {
const updateData = async (): Promise<void> => {
if (!user) return;

setIsLoading(true);

const trimmedKeys: Readonly<EditableData[]> = [
"name",
"about",
Expand All @@ -99,7 +104,16 @@ function UserEditProfile({ hide, user }: UserEditProfileProps): JSX.Element {
{} as EditableUserData,
);

setProfile(newUserData);
const profileEvent = await setProfile(newUserData);

setIsLoading(false);

if (!profileEvent || profileEvent.relays.length === 0) {
toast.error("Failed to update profile");
return;
}

toast.success("Profile updated successfully");
closeModal();
};

Expand All @@ -110,7 +124,11 @@ function UserEditProfile({ hide, user }: UserEditProfileProps): JSX.Element {
open={open}
closeModal={closeModal}
>
<EditProfileModal closeModal={closeModal} updateData={updateData}>
<EditProfileModal
isLoading={isLoading}
closeModal={closeModal}
updateData={updateData}
>
{inputFields.map((inputData) => (
<InputField
{...inputData}
Expand Down
19 changes: 19 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Toaster } from "react-hot-toast";
import type { Metadata } from "next";
import type { ReactNode } from "react";
import type { DefaultToastOptions } from "react-hot-toast";
import BottomBar from "@/app/components/bottombar/bottombar";
import Loader from "@/app/components/common/loader";
import Main from "@/app/components/common/main";
Expand All @@ -21,6 +23,18 @@ export const metadata: Metadata = {
authors: [{ name: "Michał Pstrąg" }],
};

const toastOptions: DefaultToastOptions = {
style: {
color: "white",
borderRadius: "4px",
padding: "16px",
backgroundColor: "rgb(var(--main-accent))",
},
success: {
duration: 4000,
},
};

function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
Expand All @@ -32,6 +46,11 @@ function RootLayout({ children }: { children: ReactNode }) {
<Main>{children}</Main>
<BottomBar />
</Loader>
<Toaster
position="bottom-center"
toastOptions={toastOptions}
containerClassName="mb-12 xs:mb-0"
/>
</Providers>
</div>
</body>
Expand Down
13 changes: 9 additions & 4 deletions app/lib/actions/profilesActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
ListProfilesRequest,
PublishProfileRequest,
} from "@/app/lib/services/profileService";
import type { RelayEvent } from "@/app/lib/types/event";
import type { User } from "@/app/lib/types/user";

function addProfiles(pubkeys: string[]): ProfileAction {
Expand Down Expand Up @@ -66,21 +67,25 @@ export function updateProfileAsync({
}: PublishProfileRequest): (
dispatch: any,
getState: () => ProfileState,
) => void {
return async (dispatch, getState) => {
) => Promise<RelayEvent> {
return async (dispatch) => {
const pointer = await nip05.queryProfile(data.nip05 || "");
const extendedData = {
...data,
verified: pointer?.pubkey === pubkey,
};

await profilesService.publishProfileAsync({
const profileEvent = await profilesService.publishProfileAsync({
relays,
pubkey,
data: extendedData,
});

dispatch(updateProfiles([{ ...extendedData, pubkey }]));
if (profileEvent.relays.length > 0) {
dispatch(updateProfiles([{ ...extendedData, pubkey }]));
}

return profileEvent;
};
}

Expand Down
12 changes: 8 additions & 4 deletions app/lib/context/profile-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
ProfileAction,
ProfileState,
} from "@/app/lib/reducers/profilesReducer";
import type { RelayEvent } from "@/app/lib/types/event";
import type { EditableUserData, User } from "@/app/lib/types/user";

type ProfileContext = {
Expand All @@ -26,7 +27,7 @@ type ProfileContext = {
addProfiles: (pubkeys: string[]) => void;
removeProfiles: (pubkeys: string[]) => void;
reloadProfiles: (pubkeys: string[]) => void;
setProfile: (profile: EditableUserData) => void;
setProfile: (data: EditableUserData) => Promise<RelayEvent | null>;
};

const initialState: ProfileState = {
Expand Down Expand Up @@ -60,9 +61,12 @@ export default function ProfileProvider({ children }: ProviderProps) {
}
}, [Array.from(relays.keys())]);

const setProfile = (data: EditableUserData): void => {
if (!publicKey) return;
dispatch(
const setProfile = async (
data: EditableUserData,
): Promise<RelayEvent | null> => {
if (!publicKey) return null;

return await dispatch(
updateProfileAsync({
relays: Array.from(relays.values()),
pubkey: publicKey,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"prettier": "^3.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hot-toast": "^2.4.1",
"react-player": "^2.14.1",
"react-textarea-autosize": "^8.5.0",
"react-use": "^17.5.0",
Expand Down
Loading

0 comments on commit e8d7481

Please sign in to comment.