Hello everyone, welcome to Webzone Tech Tips I am Zidane, (my nickname: ιΈ‘θ)
Good day to you. This series will share with you how to build a web application with server, client side, and you will learn many useful things on this project.
As the title you will learn how to
- Use NestJs build a server connect with a MongoDB (use JWT for authentication, how to query on server, create schema, entity, controller, service)
- Use NextJS build a client side with TailwindCss fast build component, Zustand state management (easy manage state), Axios Request API.
After check all this full series, I believe you will have a mindset how to build a website app from backend to frontend. You will understand how to become a full stack developer through this todo web app
[Project Day 6] Full Tutorial TodoList Apps with NextJS Zustand Axios TailwindCSS React NestJS Mongoose JWT - Add Toast message Component
On day 6: We will focus on client side - how to work with Toast Message Component on NextJS
First you need to build a NextJS structure
I will create a next app with tailwindcss show i wil type below command
npx create-next-app -e with-tailwindcss my-project
cd my-project
Next we will setup Toast Message Component
Location add file components/TDToastMessage.tsx
I use TD for prefix is Todo list project name
TDToastMessage.tsx
import React, { useEffect } from "react";
import { Info, infoTypes } from "./TDInfo";
export enum msgVariants {
NORMAL,
TRANSPARENT,
}
interface IToastMessageProps {
message: {
isError: boolean;
msg: string;
};
setMessage: Function;
afterSuccess?: Function;
variant?: msgVariants;
}
export const ToastMessage = ({
message,
setMessage,
afterSuccess,
variant = msgVariants.NORMAL,
}: IToastMessageProps) => {
useEffect(() => {
const timer = setTimeout(() => {
if (message.isError === false && message.msg) {
afterSuccess && afterSuccess();
}
setMessage({ isError: false, msg: "" });
}, 2000);
return () => clearTimeout(timer);
}, [message]);
return (
<div
className={`text-left flex justify-start items-center
${ message.msg ? "h-16 px-3 my-0 rounded-sm" : "h-0 p-0 mt-0" }
${ message.isError ? "text-red bg-red-300" : "text-green-leaf bg-green-300" }`}
style={{ transition: "height .3s linear" }}
>
{message?.msg && (
<>
<Info
type={message?.isError ? infoTypes.INFO : infoTypes.CHECKED}
className="mr-0 min-w-content"
/>
<p className=" text-white uppercase font-bold">{message?.msg}</p>
</>
)}
</div>
);
};
How to use it
import { ToastMessage } from '.'
I use { } because on ToastMessage.tsx Component I use export const, not export default
On Login Form I useState message, setMessage for show up ToastMessage on client side
Call Toast Message when u use it
<ToastMessage
message={message}
setMessage={setMessage}
afterSuccess={handleAccessTodoPages} // if login succeed will auto redirect
/>
Full source code here:
import React, { useState } from "react";
import { Formik, Field, Form, ErrorMessage } from "formik";
import * as Yup from "yup";
import Image from "next/image";
import OpenEye from "/public/icon/open-eye.svg";
import CloseEye from "/public/icon/close-eye.svg";
import useStore from "./../../store/login";
import { useRouter } from "next/router";
import { ROUTES } from "../../utils/contants";
import { ToastMessage } from "../../components/TDToastMessage";
import { signIn } from "next-auth/react";
import Link from "next/link";
const loginForm = () => {
const { is_password, changeEye } = useStore();
const router = useRouter();
const [message, setMessage] = useState({
isError: true,
msg: "",
});
const handleAccessTodoPages = async () => {
await router.push(ROUTES.TODO_LIST);
};
return (
<>
<h3 className="font-bold text-center mt-4">
Welcome to Todo List management by <a href="https://learn-tech-tips.blogspot.com" className="text-blue-600"> Learn Tech Tips - Zidane </a>
</h3>
<h6 className="font-bold mt-2 text-center">
huuvi168@gmail.com
</h6>
<div className="text-center bg-orange-700 text-white p-4">
LOGIN INTO TODO APPS
</div>
<Formik
initialValues={{ username: "", password: "" }}
validationSchema={Yup.object({
username: Yup.string()
.min(2, 'Too Short!')
.max(30, "Must be 30 characters or less")
.required("Username is Required"),
password: Yup.string().required("Password is Required"),
})}
onSubmit={(values, { setSubmitting }) => {
// console.log(values['username'])
signIn("credentials", {
redirect: false,
username: values["username"],
password: values["password"],
})
.then((result) => {
if (!result.error) {
setMessage({
isError: false,
msg: "Success",
});
} else {
let rel = JSON.parse(result.error);
setMessage({
isError: true,
msg: rel?.message,
});
}
})
.catch((error) => console.log(error));
}}
>
<Form>
<div className="flex item-center">
<div className="bg-sky-200 p-[10px] rounded-lg mx-auto w-[500px] mt-[50px]">
<div>
<label
htmlFor="username"
className="flex items-center text-right font-bold uppercase text-sky-600"
>
Username <span className="required-star"> (*) </span>
</label>
<Field name="username" type="text" />
<div className="error-message">
<ErrorMessage name="username" />
</div>
</div>
<div>
<label
htmlFor="password"
className="flex items-center text-right font-bold uppercase text-sky-600"
>
Password <span className="required-star"> (*) </span>
</label>
<div className="relative">
<Field
name="password"
type={is_password ? "password" : "text"} // formik, up ko can value, ko can onchange
/>
<span
className="absolute right-[10px] bottom-[10px] hover:cursor-pointer"
onClick={changeEye}
>
<Image
src={is_password ? CloseEye : OpenEye}
alt={is_password ? CloseEye : OpenEye}
/>
</span>
</div>
<div className="error-message">
<ErrorMessage name="password" />
</div>
</div>
<div className="flex justify-center space-x-4 mt-8">
<button
type="submit"
className="bg-yellow-300 px-4 py-2 rounded-md text-[#00F] font-bold"
>
Login
</button>
<button type="button" className=" px-4 py-2 rounded-md text-[#00F] font-bold">
<Link href="/auth/register">
I don't have account
</Link>
</button>
</div>
</div>
</div>
<div className="flex justify-center space-x-4 mx-auto mt-4">
<ToastMessage
message={message}
setMessage={setMessage}
afterSuccess={handleAccessTodoPages} // if login succeed will auto redirect
/>
</div>
</Form>
</Formik>
</div>
</>
);
};
export default loginForm;