Skip to content

Commit

Permalink
FE: React Query Cache Bug fixes + typewritter Animation + Toaster add…
Browse files Browse the repository at this point in the history
…itons (#59)

* FE: add react toaster to show BE errors

* FE: Fix the Backend toaster message

* FE: fix nested button validation issue + fix the login message

* FE: minor front end fix-ups

* FE: Login and register components padding additions fix

* FE: Minor styling fixes

* FE: add scrolling into the written text

* FE: Fix the Caching Continue conversation issue

* FE: Fix the Caching Continue conversation issue

* FE: Fix linting issues

* FE: adding minor TODOs
  • Loading branch information
Mgrdich authored May 13, 2024
1 parent 2109292 commit 467c00f
Show file tree
Hide file tree
Showing 18 changed files with 181 additions and 37 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.4",
"react-hot-toast": "^2.4.1",
"react-router-dom": "^6.23.0",
"tailwind-merge": "^2.3.0",
"zod": "^3.23.8"
Expand Down
26 changes: 25 additions & 1 deletion frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { Toaster } from "react-hot-toast";
import { AuthProvider } from "./context/AuthContext.tsx";
import Routes from "./router/Routes.tsx";

Expand All @@ -11,6 +12,13 @@ function App() {
<AuthProvider>
<Routes />
<ReactQueryDevtools initialIsOpen={false} />
<Toaster
position="top-right"
reverseOrder={false}
toastOptions={{
duration: 5000,
}}
/>
</AuthProvider>
</QueryClientProvider>
);
Expand Down
19 changes: 12 additions & 7 deletions frontend/src/hooks/api/useContinueConversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import useApi from "hooks/useApi.ts";
import { ConversationId } from "models/Id.ts";
import { Conversation } from "models/Conversation.ts";
import { Discussion, PromptRole } from "models/Discussion.ts";
import toast from "react-hot-toast";
import { getContinueConversationPath, Queries } from "./constants.ts";

// TODO need to check the context switch case
export default function useContinueConversation(id: ConversationId) {
export default function useContinueConversation() {
const callApi = useApi();
const queryClient = useQueryClient();
const tempId = "tempId";

return useMutation({
mutationFn: async (text: string) => {
return useMutation<Discussion[], void, { text: string; id: ConversationId }>({
mutationFn: async ({ text, id }) => {
const discussions = await callApi<Discussion[]>({
url: getContinueConversationPath(id),
method: "PUT",
Expand All @@ -24,7 +25,7 @@ export default function useContinueConversation(id: ConversationId) {
return discussions;
},
onMutate: async (variables) => {
const queryKey = [Queries.Conversation, id];
const queryKey = [Queries.Conversation, variables.id];
queryClient.setQueryData(queryKey, (old: Conversation) => {
const newData = {
...old,
Expand All @@ -33,7 +34,7 @@ export default function useContinueConversation(id: ConversationId) {
...old.discussions,
{
id: tempId,
text: variables,
text: variables.text,
promptRole: PromptRole.User,
lastUpdatedOn: new Date().toDateString(),
createdOn: new Date().toDateString(),
Expand All @@ -43,8 +44,8 @@ export default function useContinueConversation(id: ConversationId) {
return newData;
});
},
onSuccess: async (data) => {
const queryKey = [Queries.Conversation, id];
onSuccess: async (data, variables) => {
const queryKey = [Queries.Conversation, variables.id];
queryClient.setQueryData(queryKey, (old: Conversation) => {
const newData = {
...old,
Expand All @@ -60,5 +61,9 @@ export default function useContinueConversation(id: ConversationId) {
return newData;
});
},
onError: () => {
// TODO integrate with BE error message
toast.error("Modal could not answer your question");
},
});
}
5 changes: 5 additions & 0 deletions frontend/src/hooks/api/useDeleteConversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { ConversationCompact } from "models/Conversation.ts";
import useApi from "hooks/useApi.ts";
import { ConversationId } from "models/Id.ts";
import toast from "react-hot-toast";
import { getConversationPath, Queries } from "./constants.ts";

export default function useDeleteConversation() {
Expand All @@ -20,5 +21,9 @@ export default function useDeleteConversation() {
const queryKey = [Queries.Conversation];
queryClient.setQueryData(queryKey, (old: ConversationCompact[]) => old?.filter((item) => item.id !== id));
},
onError: () => {
// TODO integrate with BE error message
toast.error("could not delete the conversion");
},
});
}
5 changes: 5 additions & 0 deletions frontend/src/hooks/api/useEditConversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { ConversationCompact } from "models/Conversation.ts";
import useApi from "hooks/useApi.ts";
import { ConversationId } from "models/Id.ts";
import toast from "react-hot-toast";
import { getConversationPath, Queries } from "./constants.ts";

export default function useEditConversation() {
Expand Down Expand Up @@ -32,5 +33,9 @@ export default function useEditConversation() {
return conversations;
});
},
onError: () => {
// TODO integrate with BE error message
toast.error("could not update the title of the conversion");
},
});
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useMutation } from "@tanstack/react-query";
import toast from "react-hot-toast";
import useAuth from "context/useAuth.ts";
import useApi from "./useApi.ts";
import { LoginPath } from "./api/constants.ts";
import useApi from "hooks/useApi.ts";
import { ApiError } from "models/Errors.ts";
import { LoginPath } from "./constants.ts";

interface LoginRequest {
username: string;
Expand All @@ -16,7 +18,16 @@ export default function useLogin() {
mutationFn: ({ username, password }: LoginRequest) =>
callApi<{ token: string }>({ url: LoginPath, body: { username, password }, method: "POST" }),
onSuccess: (res) => {
toast.dismiss();
setToken(res.token);
},
onError: (err) => {
if (err instanceof ApiError) {
toast.error(err.message);
return;
}

toast.error("Something went wrong with the login");
},
});
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useMutation } from "@tanstack/react-query";
import toast from "react-hot-toast";
import { RolesType } from "models/User.ts";
import useApi from "./useApi.ts";
import { RegisterPath } from "./api/constants.ts";
import useApi from "hooks/useApi.ts";
import { ApiError } from "models/Errors.ts";
import { RegisterPath } from "./constants.ts";

interface RegisterRequest {
username: string;
Expand All @@ -20,5 +22,16 @@ export default function useRegister() {
body: { username, password, firstName, lastName, role },
method: "POST",
}),
onSuccess: () => {
toast.success("User created successfully");
},
onError: (err) => {
if (err instanceof ApiError) {
toast.error(err.message);
return;
}

toast.error("Something went wrong with user registration");
},
});
}
4 changes: 2 additions & 2 deletions frontend/src/pages/Auth/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import InputWithLabel from "ui/InputWithLabel.tsx";
import LinkText from "ui/LinkText.tsx";
import useLogin from "hooks/useLogin.ts";
import { useNavigate } from "react-router-dom";
import { SubmitHandler, useForm } from "react-hook-form";
import ErrorLabel from "ui/ErrorLabel.tsx";
import FormSubmitButton from "ui/FormSubmitButton.tsx";
import { z, ZodType } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import useLogin from "hooks/api/useLogin.ts";

type LoginForm = {
username: string;
Expand Down Expand Up @@ -36,7 +36,7 @@ export default function Login() {
};

return (
<div className="bg-neutral-50 min-h-screen flex flex-col items-center justify-center dark:bg-neutral-900">
<div className="bg-neutral-50 min-h-screen flex flex-col items-center justify-center dark:bg-neutral-900 px-4">
<h1 className="mt-0 mb-16 text-5xl text-white font-bold tracking-tight md:text-5xl xl:text-5xl self-center">
Welcome Back :)
</h1>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/Auth/Register.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import InputWithLabel from "ui/InputWithLabel.tsx";
import LinkText from "ui/LinkText.tsx";
import { useNavigate } from "react-router-dom";
import useRegister from "hooks/useRegister.ts";
import Checkbox from "ui/Checkbox.tsx";
import { Roles } from "models/User.ts";
import { SubmitHandler, useForm } from "react-hook-form";
import ErrorLabel from "ui/ErrorLabel.tsx";
import FormSubmitButton from "ui/FormSubmitButton.tsx";
import { z, ZodType } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import useRegister from "hooks/api/useRegister.ts";

type RegisterForm = {
username: string;
Expand Down Expand Up @@ -66,7 +66,7 @@ function Register() {
};

return (
<div className="bg-neutral-50 min-h-screen flex flex-col items-center justify-center dark:bg-neutral-900">
<div className="bg-neutral-50 min-h-screen flex flex-col items-center justify-center dark:bg-neutral-900 px-4">
<h1 className="mb-10 mt-10 text-5xl text-white font-bold tracking-tight md:text-5xl xl:text-5xl self-center">
Register Now
</h1>
Expand Down
19 changes: 16 additions & 3 deletions frontend/src/pages/Conversation/Discussions/Discussion.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import { PromptRole, PromptRoleType } from "models/Discussion.ts";
import { clsx } from "clsx";
import AnimateText from "ui/AnimateText.tsx";

interface DiscussionProps {
role: PromptRoleType;
text: string;
animate?: boolean;
}

export default function Discussion({ role, text }: DiscussionProps) {
export default function Discussion({ role, text, animate }: DiscussionProps) {
return (
<div className="flex flex-col text-white">
<div className="font-bold">{role === PromptRole.User ? "YOU" : "Chatto"}</div>
<div>{text}</div>
<div
className={clsx(
{
"text-blue-600": role === PromptRole.Assistant,
"text-green-600": role === PromptRole.User,
},
"font-bold",
)}
>
{role === PromptRole.User ? "YOU" : "Chatto"}
</div>
{animate ? <AnimateText text={text} speed={10} /> : <div>{text}</div>}
</div>
);
}
26 changes: 22 additions & 4 deletions frontend/src/pages/Conversation/Discussions/Discussions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ConversationId } from "models/Id.ts";
import useGetConversation from "hooks/api/useGetConversation.ts";
import SpinLoader from "ui/SpinLoader.tsx";
import { useEffect, useRef } from "react";
import { PromptRole } from "models/Discussion.ts";
import Discussion from "./Discussion.tsx";

interface DiscussionProps {
Expand All @@ -9,17 +11,33 @@ interface DiscussionProps {

export default function Discussions({ id }: DiscussionProps) {
const { data, isLoading, isError } = useGetConversation(id);
const containerRef = useRef<HTMLDivElement>(null);
const cacheLength = useRef(data?.discussions.length || 0);

useEffect(() => {
if (cacheLength.current !== data?.discussions?.length) {
containerRef.current?.scrollIntoView({ behavior: "smooth", block: "end" });
}

cacheLength.current = data?.discussions.length || 0;
});

return (
<div className="p-4 flex flex-col gap-6 bg-neutral-50 dark:bg-neutral-800">
<div className="p-4 flex flex-col gap-6 bg-neutral-50 dark:bg-neutral-800" ref={containerRef}>
{isLoading && !data && <SpinLoader size="l" />}
{isError && <div className="text-3xl text-red text-center">Something Went Wrong</div>}
{isError && <div className="text-3xl text-red-600 text-center">Something Went Wrong</div>}
{data?.discussions?.length === 0 && !isError && !isLoading && (
<div className="text-3xl text-white text-center">Ask me Questions</div>
)}
{data?.discussions?.map((discussion) => (
<Discussion key={discussion.id} text={discussion.text} role={discussion.promptRole} />
{data?.discussions?.map((discussion, index) => (
<Discussion
key={discussion.id}
text={discussion.text}
role={discussion.promptRole}
animate={index + 1 === data.discussions.length && discussion.promptRole === PromptRole.Assistant}
/>
))}
<div className="h-52 bg-transparent" />
</div>
);
}
Loading

0 comments on commit 467c00f

Please sign in to comment.