first commit
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import NProgress from "nprogress";
|
||||
import "nprogress/nprogress.css";
|
||||
|
||||
function LoadingTopBar() {
|
||||
useEffect(() => {
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
const handleClick = (e: any) => {
|
||||
const target = e.target.closest("a");
|
||||
|
||||
if (target?.href && target.href.startsWith(window.location.origin)) {
|
||||
NProgress.start();
|
||||
}
|
||||
};
|
||||
|
||||
const handleStop = () => {
|
||||
NProgress.done();
|
||||
};
|
||||
|
||||
window.addEventListener("click", handleClick);
|
||||
window.addEventListener("load", handleStop);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("click", handleClick);
|
||||
window.removeEventListener("load", handleStop);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default LoadingTopBar;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./LoadingTopBar";
|
||||
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { PATH } from "@/constant/config";
|
||||
import { RootState } from "@/redux/store";
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
interface IRequireAuthProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function RequireAuth(props: IRequireAuthProps) {
|
||||
const router = useRouter();
|
||||
|
||||
const { loading } = useSelector((state: RootState) => state.site);
|
||||
const { isLogin } = useSelector((state: RootState) => state.auth);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLogin && !loading) {
|
||||
router.replace(PATH.LOGIN);
|
||||
}
|
||||
}, [isLogin, loading, router]);
|
||||
|
||||
if (isLogin && !loading) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
|
||||
return <div className="loading-page" />;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//**********************
|
||||
//* COMPONENT PROTECTED SCREEN THEN LOGIN
|
||||
//**********************
|
||||
|
||||
"use client";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { RootState } from "@/redux/store";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSelector } from "react-redux";
|
||||
import { PATH } from "@/constant/config";
|
||||
|
||||
interface props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function RequiredLogout({ children }: props) {
|
||||
const { replace } = useRouter();
|
||||
const { isLogin } = useSelector((state: RootState) => state.auth);
|
||||
const { loading } = useSelector((state: RootState) => state.site);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLogin && !loading) replace(PATH.HOME);
|
||||
}, [isLogin, loading, replace]);
|
||||
|
||||
if (!isLogin && !loading) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return <div className="loading-page"></div>;
|
||||
}
|
||||
|
||||
export default RequiredLogout;
|
||||
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { RootState, store } from "@/redux/store";
|
||||
import { useSelector } from "react-redux";
|
||||
import clsx from "clsx";
|
||||
import Lottie from "lottie-react";
|
||||
|
||||
import { getItemStorage, setItemStorage } from "@/common/funcs/localStorage";
|
||||
import { KEY_STORE } from "@/constant/config";
|
||||
|
||||
import { setLoading, setRememberPassword } from "@/redux/reducer/site";
|
||||
import {
|
||||
setDataLoginStorage,
|
||||
setStateLogin,
|
||||
setToken,
|
||||
} from "@/redux/reducer/auth";
|
||||
import { setInfoUser } from "@/redux/reducer/user";
|
||||
|
||||
import loadingAnim from "@/../public/static/anim/loading_screen.json";
|
||||
|
||||
function SplashScreen() {
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
|
||||
const { loading, isRememberPassword } = useSelector(
|
||||
(state: RootState) => state.site,
|
||||
);
|
||||
const { infoUser } = useSelector((state: RootState) => state.user);
|
||||
const { token, isLogin, dataLoginStorage } = useSelector(
|
||||
(state: RootState) => state.auth,
|
||||
);
|
||||
|
||||
// 🔥 Load state từ localStorage
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const encryptedState = await getItemStorage(KEY_STORE);
|
||||
const SECRET_KEY = process.env.NEXT_PUBLIC_SECRET_KEY || "default_key";
|
||||
|
||||
const decode = (data: string): any => {
|
||||
try {
|
||||
const decoded = decodeURIComponent(atob(data));
|
||||
const clean = decoded.startsWith(SECRET_KEY)
|
||||
? decoded.slice(SECRET_KEY.length)
|
||||
: null;
|
||||
return clean ? JSON.parse(clean) : null;
|
||||
} catch (err) {
|
||||
console.error("Decode error:", err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const state =
|
||||
typeof encryptedState === "string" ? decode(encryptedState) : null;
|
||||
|
||||
if (state) {
|
||||
store.dispatch(setToken(state.token));
|
||||
store.dispatch(setStateLogin(state.isLogin));
|
||||
store.dispatch(setInfoUser(state.infoUser));
|
||||
store.dispatch(setRememberPassword(state.isRememberPassword));
|
||||
store.dispatch(setDataLoginStorage(state.dataLoginStorage));
|
||||
}
|
||||
|
||||
store.dispatch(setLoading(false));
|
||||
})();
|
||||
}, []);
|
||||
|
||||
// 🔥 Save lại state
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
const SECRET_KEY = process.env.NEXT_PUBLIC_SECRET_KEY || "default_key";
|
||||
|
||||
const encode = (data: string): string => {
|
||||
const textToEncode = SECRET_KEY + data;
|
||||
return btoa(encodeURIComponent(textToEncode));
|
||||
};
|
||||
|
||||
const hashedData = encode(
|
||||
JSON.stringify({
|
||||
isLogin,
|
||||
token,
|
||||
infoUser,
|
||||
isRememberPassword,
|
||||
dataLoginStorage,
|
||||
}),
|
||||
);
|
||||
|
||||
setItemStorage(KEY_STORE, hashedData);
|
||||
|
||||
// 👉 trigger animation fade out
|
||||
setTimeout(() => setIsClosing(true), 0);
|
||||
}
|
||||
}, [loading, isLogin, token, infoUser, isRememberPassword, dataLoginStorage]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"fixed inset-0 flex items-center justify-center z-[1000000] bg-white transition-opacity duration-300",
|
||||
{
|
||||
"opacity-0 invisible": !loading && isClosing,
|
||||
"opacity-100 visible": loading,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className="w-[40vw] max-w-[320px] max-h-[320px]">
|
||||
<Lottie animationData={loadingAnim} loop autoplay />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SplashScreen;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./SplashScreen";
|
||||
Reference in New Issue
Block a user