From dbc115998890c46d6d95068387dff6a116bffc3c Mon Sep 17 00:00:00 2001 From: mohi14 Date: Wed, 31 Jan 2024 18:51:17 +0600 Subject: [PATCH 1/4] authentication flow implemented --- .env | 2 + middleware.js | 19 ++++ next.config.js | 14 ++- package-lock.json | 112 ++++++++++++++++++- package.json | 6 +- src/Components/Auth/Forgot.js | 57 ++++++++-- src/Components/Auth/Reset.js | 25 +++-- src/Components/Auth/SignIn.js | 63 ++++++----- src/Components/Auth/SignUp.js | 140 ++++++++++++++++++++---- src/Components/Shared/Header.js | 49 ++++++--- src/Layout/DashboardLayout.js | 3 + src/Layout/Main.js | 9 ++ src/Utils/Validate.js | 128 ++++++++++++++++++++-- src/app/store.js | 14 +++ src/features/api/apiSlice.js | 10 ++ src/features/auth/authApi.js | 101 +++++++++++++++++ src/features/auth/authSlice.js | 24 +++++ src/hooks/useAuthCheck.js | 39 +++++++ src/hooks/useIsAuthenticated.js | 24 +++++ src/pages/_app.js | 13 ++- src/pages/dashboard/index.js | 30 +++--- src/pages/forgot-password.js | 8 +- src/pages/index.js | 4 +- src/pages/reset-password.js | 4 +- src/pages/sign-up-user.js | 162 ++++++++++++++++++++++++++++ src/pages/validate-email.js | 12 --- src/pages/validate-email/[email].js | 16 +++ src/privateRoutes/PrivateRoutes.js | 19 ++++ 28 files changed, 991 insertions(+), 116 deletions(-) create mode 100644 .env create mode 100644 middleware.js create mode 100644 src/Layout/Main.js create mode 100644 src/app/store.js create mode 100644 src/features/api/apiSlice.js create mode 100644 src/features/auth/authApi.js create mode 100644 src/features/auth/authSlice.js create mode 100644 src/hooks/useAuthCheck.js create mode 100644 src/hooks/useIsAuthenticated.js create mode 100644 src/pages/sign-up-user.js delete mode 100644 src/pages/validate-email.js create mode 100644 src/pages/validate-email/[email].js create mode 100644 src/privateRoutes/PrivateRoutes.js diff --git a/.env b/.env new file mode 100644 index 0000000..36e64dc --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +NEXT_PUBLIC_API_URL=http://localhost:8000 +NEXT_PUBLIC_MAIN_URL=http://localhost:3000 \ No newline at end of file diff --git a/middleware.js b/middleware.js new file mode 100644 index 0000000..fb901ca --- /dev/null +++ b/middleware.js @@ -0,0 +1,19 @@ +export async function checkAuth(req, res, next) { + // Your authentication logic here + const verify = req.cookies.quickSilverAuth; + + console.log("From middleware!"); + + if (!verify && req.url.includes("/dashboard")) { + res.redirect(302, `http://localhost:3000`); + return; + } + + if (verify && (req.url.includes("/sign-up") || req.url === "/")) { + res.redirect(302, `${process.env.NEXT_PUBLIC_MAIN_URL}/dashboard`); + return; + } + + // If authentication is successful, call the next function + next(); +} diff --git a/next.config.js b/next.config.js index a843cbe..c06a737 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,16 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, -} + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "assets.example.com", + port: "", + pathname: "/account123/**", + }, + ], + }, +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json index 5c92043..6e3828c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,15 @@ "name": "quick-silver", "version": "0.1.0", "dependencies": { + "@reduxjs/toolkit": "^2.1.0", + "js-cookie": "^3.0.5", "next": "14.0.4", "react": "^18", "react-dom": "^18", "react-icons": "^5.0.1", - "recharts": "^2.10.4" + "react-redux": "^9.1.0", + "recharts": "^2.10.4", + "sweetalert2": "^11.10.4" }, "devDependencies": { "autoprefixer": "^10.4.16", @@ -293,6 +297,29 @@ "node": ">=14" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.1.0.tgz", + "integrity": "sha512-nfJ/b4ZhzUevQ1ZPKjlDL6CMYxO4o7ZL7OSsvSOxzT/EN11LsBDgTqP7aedHtBrFSVoK7oTP1SbMWUwGb30NLg==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@swc/helpers": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", @@ -355,6 +382,11 @@ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -985,6 +1017,15 @@ "node": ">= 0.4" } }, + "node_modules/immer": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -1089,6 +1130,14 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1605,6 +1654,32 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "node_modules/react-redux": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz", + "integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-smooth": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.5.tgz", @@ -1686,11 +1761,29 @@ "decimal.js-light": "^2.4.1" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -1950,6 +2043,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sweetalert2": { + "version": "11.10.4", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.4.tgz", + "integrity": "sha512-MOVRuEW/yQsyzgkaiHqAJcYKxW3vhtE5o3Skp6vZdyJejCOWo4FOicbjRfvqHAXTyTMuwDHA+0lYbO6BiHl1Gw==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" + } + }, "node_modules/tailwindcss": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", @@ -2066,6 +2168,14 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 8496b88..945e6f9 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,15 @@ "lint": "next lint" }, "dependencies": { + "@reduxjs/toolkit": "^2.1.0", + "js-cookie": "^3.0.5", "next": "14.0.4", "react": "^18", "react-dom": "^18", "react-icons": "^5.0.1", - "recharts": "^2.10.4" + "react-redux": "^9.1.0", + "recharts": "^2.10.4", + "sweetalert2": "^11.10.4" }, "devDependencies": { "autoprefixer": "^10.4.16", diff --git a/src/Components/Auth/Forgot.js b/src/Components/Auth/Forgot.js index 7037fb4..9d8ba67 100644 --- a/src/Components/Auth/Forgot.js +++ b/src/Components/Auth/Forgot.js @@ -1,8 +1,46 @@ - import Link from "next/link"; import AuthLayout from "../../Layout/AuthLayout"; +import { useRouter } from "next/router"; +import Swal from "sweetalert2"; + +const Forgot = ({ sendResetPasswordLink, isLoading }) => { + const { push } = useRouter(); + const handleSubmit = async (e) => { + e.preventDefault(); -const Forgot = () => { + const email = e.target.email.value; + try { + const res = await sendResetPasswordLink({ email }); + if (res?.error?.error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.error}`, + }); + } + if (res?.error?.data?.message) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.data?.message}`, + }); + } + if (res?.data?.success) { + push("/"); + Swal.fire({ + icon: "success", + title: "Successful!", + text: `${res?.data?.message}`, + }); + } + } catch (error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${error?.message}`, + }); + } + }; return (
@@ -12,7 +50,7 @@ const Forgot = () => { Sign Up
-
+

Forgot Password

Enter your email and a password reset will be sent to you @@ -22,18 +60,21 @@ const Forgot = () => { * Email Address -

Invalid Email Address

+ {/*

Invalid Email Address

*/} - - - + {/* */} + + {/* */}
diff --git a/src/Components/Auth/Reset.js b/src/Components/Auth/Reset.js index f95a1b6..5d547e0 100644 --- a/src/Components/Auth/Reset.js +++ b/src/Components/Auth/Reset.js @@ -3,10 +3,21 @@ import AuthLayout from "../../Layout/AuthLayout"; import eyeOff from "../../assets/eye-off.png"; import eye from "../../assets/eye.png"; import { useState } from "react"; +import { useRouter } from "next/router"; -const Reset = () => { +const Reset = ({ updatePassword, isLoading }) => { const [showPassword, setShowPassword] = useState(false); const [showConPassword, setShowConPassword] = useState(false); + const { query } = useRouter(); + + console.log(query?.user, "ggg"); + + const handleSubmit = async (e) => { + e.preventDefault(); + const password = e.target.password.value; + const confirmPassword = e.target.confirmPassword.value; + }; + return (
@@ -16,7 +27,7 @@ const Reset = () => { Sign In
-
+

Reset Password

Enter a new Password and Confirm to reset your password @@ -43,21 +54,21 @@ const Reset = () => { cursor: "pointer", }} className="absolute " - src={showPassword ? eye : eyeOff} + src={showPassword ? eye.src : eyeOff.src} alt="" />

-
-

{errorMessage}

+ {/*

{errorMessage}

*/} Forgot password?
- +
diff --git a/src/Components/Auth/SignUp.js b/src/Components/Auth/SignUp.js index 147ff06..8e5c5af 100644 --- a/src/Components/Auth/SignUp.js +++ b/src/Components/Auth/SignUp.js @@ -3,10 +3,98 @@ import eyeOff from "../../assets/eye-off.png"; import eye from "../../assets/eye.png"; import AuthLayout from "@/Layout/AuthLayout"; import Link from "next/link"; +import Swal from "sweetalert2"; +import { useRegisterMutation } from "@/features/auth/authApi"; +import { useRouter } from "next/router"; const SignUp = () => { const [showPassword, setShowPassword] = useState(false); const [showConPassword, setShowConPassword] = useState(false); + + const [isLoading, setIsLoading] = useState(false); + + const [register, { isLoading: registering }] = useRegisterMutation(); + + const { push } = useRouter(); + + const passwordRegex = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}/; + + const handleSubmit = async (e) => { + e.preventDefault(); + + setIsLoading(true); + + const form = e.target; + + const referralId = form.referralId.value; + const firstName = form.firstName.value; + const lastName = form.lastName.value; + const companyName = form.companyName.value; + const phoneNumber = form.phoneNumber.value; + const email = form.email.value; + const password = form.password.value; + const confirmPassword = form.confirmPassword.value; + + if (!passwordRegex.test(password)) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: "Password must contain at least one uppercase, one lowercase, one special character, one digit and it should be at least 8 characters long.", + }); + setIsLoading(false); + return; + } + + if (password !== confirmPassword) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: "Password doesn't matched!", + }); + setIsLoading(false); + return; + } + + try { + const data = { + referralId, + firstName, + lastName, + companyName, + phoneNumber, + email, + password, + }; + const res = await register(data); + + if (res?.error?.error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.error}`, + }); + } + if (res?.error?.data?.message) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.data?.message}`, + }); + } + if (res?.data?.success) { + push(`/validate-email/${email}`); + } + } catch (error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${error?.message}`, + }); + } finally { + setIsLoading(false); + } + }; return (
@@ -16,7 +104,7 @@ const SignUp = () => { Sign In
-
+

Sign Up

To prepare to log into the system

@@ -29,6 +117,7 @@ const SignUp = () => { name="referralId" id="referralId" placeholder="Paste your referral ID or enter None if you were not refered" + required />
@@ -39,9 +128,10 @@ const SignUp = () => {
@@ -51,9 +141,10 @@ const SignUp = () => {
@@ -64,9 +155,10 @@ const SignUp = () => {
@@ -79,6 +171,7 @@ const SignUp = () => { name="email" id="semail" placeholder="Enter your email address" + required />
@@ -88,22 +181,24 @@ const SignUp = () => {
-
+
setShowPassword(!showPassword)} @@ -113,23 +208,24 @@ const SignUp = () => { right: "20px", cursor: "pointer", }} - className="position-absolute " - src={showPassword ? eye : eyeOff} + className="absolute " + src={showPassword ? eye.src : eyeOff.src} alt="" />
-
diff --git a/src/Components/Shared/Header.js b/src/Components/Shared/Header.js index 105bad2..ed45ef9 100644 --- a/src/Components/Shared/Header.js +++ b/src/Components/Shared/Header.js @@ -9,6 +9,9 @@ import signOut from "../../assets/sign-out.png"; import CustomModal from "./Modal/CustomModal"; import Profile from "./Modal/Profile"; import { useRouter } from "next/router"; +import { useDispatch, useSelector } from "react-redux"; +import { userLoggedOut } from "@/features/auth/authSlice"; +import Cookies from "js-cookie"; const Header = ({ handleSidebar }) => { const profileRef = useRef(); @@ -17,9 +20,22 @@ const Header = ({ handleSidebar }) => { const [show, setShow] = useState(false); const [modalOpen, setModalOpen] = useState(false); + const { user } = useSelector((state) => state.auth); + + console.log(user, "user"); + const openModal = () => setModalOpen(true); const closeModal = () => setModalOpen(false); + const dispatch = useDispatch(); + + const hanldeSignOut = () => { + dispatch(userLoggedOut()); + + Cookies.remove("quickSilverAuth"); + router.push("/"); + }; + useEffect(() => { const handleClickOutside = (event) => { if (profileRef.current && !profileRef.current.contains(event.target)) { @@ -72,7 +88,9 @@ const Header = ({ handleSidebar }) => { src={profile.src} alt="" /> -

John Doe

+

+ {user?.firstName} {user?.lastName} +

@@ -91,25 +109,30 @@ const Header = ({ handleSidebar }) => { />
-

John Doe

-

john.doe@gmail.com

+

+ {user?.firstName} {user?.lastName} +

+

{user?.email}

- Manage account - - - - Sign Out - + +
diff --git a/src/Layout/DashboardLayout.js b/src/Layout/DashboardLayout.js index d075c76..e647450 100644 --- a/src/Layout/DashboardLayout.js +++ b/src/Layout/DashboardLayout.js @@ -1,5 +1,6 @@ import Header from "@/Components/Shared/Header"; import Sidebar from "@/Components/Sidebar"; +import PrivateRoutes from "@/privateRoutes/PrivateRoutes"; import { useState } from "react"; const DashboardLayout = ({ children }) => { @@ -10,6 +11,7 @@ const DashboardLayout = ({ children }) => { }; return ( + //
@@ -20,6 +22,7 @@ const DashboardLayout = ({ children }) => { {children}
+ //
); }; diff --git a/src/Layout/Main.js b/src/Layout/Main.js new file mode 100644 index 0000000..f8d8268 --- /dev/null +++ b/src/Layout/Main.js @@ -0,0 +1,9 @@ +import useAuthCheck from "@/hooks/useAuthCheck"; +import React from "react"; + +const Main = ({ children }) => { + const authChecked = useAuthCheck(); + return !authChecked ? "" : <>{children}; +}; + +export default Main; diff --git a/src/Utils/Validate.js b/src/Utils/Validate.js index 0ab9e5b..181b2fa 100644 --- a/src/Utils/Validate.js +++ b/src/Utils/Validate.js @@ -1,27 +1,139 @@ +import { useParams } from "next/navigation"; import logo from "../assets/logo-black.png"; +import { + useResendEmailVerifictionCodeMutation, + useVerifyEmailWithOtpMutation, +} from "@/features/auth/authApi"; +import { useRouter } from "next/router"; +import Swal from "sweetalert2"; -const Validate = ({ title, veriation }) => { +const Validate = ({ title, veriation, email }) => { + const { push } = useRouter(); + + const [verifyEmailWithOtp, { isLoading }] = useVerifyEmailWithOtpMutation(); + const [resendEmailVerifictionCode, { isLoading: Resending }] = + useResendEmailVerifictionCodeMutation(); + + const handleSubmit = async (e) => { + e.preventDefault(); + + const otp = e.target.otp.value; + + try { + const data = { + email, + otp, + }; + + const res = await verifyEmailWithOtp(data); + + if (res?.error?.error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.error}`, + }); + } + if (res?.error?.data?.message) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.data?.message}`, + }); + } + if (res?.data?.success) { + push("/dashboard"); + + Swal.fire({ + icon: "success", + title: "Successfull!", + text: `${res?.data?.message}`, + showConfirmButton: false, + timer: 1500, + }); + } + } catch (error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${error?.message}`, + }); + } + }; + + const handleResendEmail = async (e) => { + try { + const res = await resendEmailVerifictionCode({ email }); + + if (res?.error?.error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.error}`, + }); + } + if (res?.error?.data?.message) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.data?.message}`, + }); + } + if (res?.data?.success) { + // push("/dashboard"); + + Swal.fire({ + icon: "success", + title: "Successfull!", + text: `${res?.data?.message}`, + // showConfirmButton: false, + // timer: 1500, + }); + } + } catch (error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${error?.message}`, + }); + } + }; return (
-
+

Validate {title}

A unique 6 digit code has been sent

-

+ {/*

Invalid Code - You have 2 more attempts left before your account is locked! -

+

*/}
- - + +
-
+
diff --git a/src/app/store.js b/src/app/store.js new file mode 100644 index 0000000..13ff462 --- /dev/null +++ b/src/app/store.js @@ -0,0 +1,14 @@ +import { apiSlice } from "@/features/api/apiSlice"; +import authReducer from "@/features/auth/authSlice"; +import { configureStore } from "@reduxjs/toolkit"; + +export const store = configureStore({ + reducer: { + auth: authReducer, + [apiSlice.reducerPath]: apiSlice.reducer, + }, + + devTools: process.env.NODE_ENV !== "production", + middleware: (getDefaultMiddlewares) => + getDefaultMiddlewares().concat(apiSlice.middleware), +}); diff --git a/src/features/api/apiSlice.js b/src/features/api/apiSlice.js new file mode 100644 index 0000000..5c9db04 --- /dev/null +++ b/src/features/api/apiSlice.js @@ -0,0 +1,10 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const apiSlice = createApi({ + reducerPath: "api", + baseQuery: fetchBaseQuery({ + baseUrl: `${process.env.NEXT_PUBLIC_API_URL}`, + }), + tagTypes: [], + endpoints: (builder) => ({}), +}); diff --git a/src/features/auth/authApi.js b/src/features/auth/authApi.js new file mode 100644 index 0000000..c4f1c20 --- /dev/null +++ b/src/features/auth/authApi.js @@ -0,0 +1,101 @@ +import Cookies from "js-cookie"; + +import { apiSlice } from "../api/apiSlice"; +import { userLoggedIn } from "./authSlice"; + +export const authAPi = apiSlice.injectEndpoints({ + endpoints: (builder) => ({ + register: builder.mutation({ + query: (data) => ({ + url: "/api/user/register", + method: "POST", + body: data, + }), + }), + login: builder.mutation({ + query: (data) => ({ + url: "/api/user/login", + method: "POST", + body: data, + }), + async onQueryStarted(arg, { queryFulfilled, dispatch }) { + try { + const result = await queryFulfilled; + + Cookies.set( + "quickSilverAuth", + JSON.stringify({ + accessToken: result.data.accessToken, + user: result.data.user, + }) + ); + + dispatch( + userLoggedIn({ + accessToken: result.data.accessToken, + user: result.data.user, + }) + ); + } catch (error) {} + }, + }), + verifyEmailWithOtp: builder.mutation({ + query: (data) => ({ + url: "/api/user/verifyEmail", + method: "PATCH", + body: data, + }), + async onQueryStarted(arg, { queryFulfilled, dispatch }) { + try { + const result = await queryFulfilled; + + Cookies.set( + "quickSilverAuth", + JSON.stringify({ + accessToken: result.data.accessToken, + user: result.data.user, + }) + ); + + dispatch( + userLoggedIn({ + accessToken: result.data.accessToken, + user: result.data.user, + }) + ); + } catch (error) {} + }, + }), + + sendResetPasswordLink: builder.mutation({ + query: (data) => ({ + url: "/api/user/resetPassword", + method: "POST", + body: data, + }), + }), + resendEmailVerifictionCode: builder.mutation({ + query: (data) => ({ + url: "/api/user/resendVerificationCode", + method: "POST", + body: data, + }), + }), + updatePassword: builder.mutation({ + query: (data) => ({ + url: "/api/user/updatePassword", + method: "PATCH", + body: data, + }), + }), + }), +}); + +export const { + useRegisterMutation, + useVerifyEmailWithOtpMutation, + useLoginMutation, + useSendResetPasswordLinkMutation, + useResendEmailVerifictionCodeMutation, + useUpdatePasswordMutation, +} = authAPi; diff --git a/src/features/auth/authSlice.js b/src/features/auth/authSlice.js new file mode 100644 index 0000000..5e8683e --- /dev/null +++ b/src/features/auth/authSlice.js @@ -0,0 +1,24 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + accessToken: undefined, + user: undefined, +}; + +const authSlice = createSlice({ + name: "auth", + initialState, + reducers: { + userLoggedIn: (state, action) => { + state.accessToken = action.payload.accessToken; + state.user = action.payload.user; + }, + userLoggedOut: (state) => { + state.accessToken = undefined; + state.user = undefined; + }, + }, +}); + +export default authSlice.reducer; +export const { userLoggedIn, userLoggedOut } = authSlice.actions; diff --git a/src/hooks/useAuthCheck.js b/src/hooks/useAuthCheck.js new file mode 100644 index 0000000..1c4f13e --- /dev/null +++ b/src/hooks/useAuthCheck.js @@ -0,0 +1,39 @@ +import { userLoggedIn } from "@/features/auth/authSlice"; +import React, { useEffect, useState } from "react"; +import { useDispatch } from "react-redux"; + +const useAuthCheck = () => { + const dispatch = useDispatch(); + const [authChecked, setAuthChecked] = useState(false); + + useEffect(() => { + const getCookie = (name) => { + const value = "; " + document.cookie; + const parts = value.split("; " + name + "="); + if (parts.length === 2) return parts.pop().split(";").shift(); + }; + + const cookieValue = getCookie("quickSilverAuth"); + + // console.log(cookieValue, "ddd"); + + if (cookieValue) { + const urlDecodedString = decodeURIComponent(cookieValue); + // console.log(urlDecodedString, "ddd"); + const userDataFromCookies = JSON.parse(urlDecodedString); + console.log(userDataFromCookies, "ddd"); + if (userDataFromCookies?.accessToken && userDataFromCookies?.user) { + dispatch( + userLoggedIn({ + accessToken: userDataFromCookies.accessToken, + user: userDataFromCookies.user, + }) + ); + } + } + setAuthChecked(true); + }, [dispatch]); + return authChecked; +}; + +export default useAuthCheck; diff --git a/src/hooks/useIsAuthenticated.js b/src/hooks/useIsAuthenticated.js new file mode 100644 index 0000000..8a4b371 --- /dev/null +++ b/src/hooks/useIsAuthenticated.js @@ -0,0 +1,24 @@ +import { useEffect, useState } from "react"; + +const useIsAuthenticated = () => { + const [authChecked, setAuthChecked] = useState(false); + + useEffect(() => { + const getCookie = (name) => { + const value = "; " + document.cookie; + const parts = value.split("; " + name + "="); + if (parts.length === 2) return parts.pop().split(";").shift(); + }; + + const cookieValue = getCookie("quickSilverAuth"); + + if (cookieValue) { + setAuthChecked(true); + } else { + setAuthChecked(false); + } + }, []); + return authChecked; +}; + +export default useIsAuthenticated; diff --git a/src/pages/_app.js b/src/pages/_app.js index b9fba83..2b910f4 100644 --- a/src/pages/_app.js +++ b/src/pages/_app.js @@ -1,10 +1,17 @@ import TypeContextProvider from "@/Context/TypeProvider"; +import Main from "@/Layout/Main"; +import { store } from "@/app/store"; import "@/styles/globals.css"; +import { Provider } from "react-redux"; export default function App({ Component, pageProps }) { return ( - - - + +
+ + + +
+
); } diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index c1e3cfe..20e4c58 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -12,22 +12,22 @@ const Dashboard = () => { setSelectedDashboard(event.target.value); }; return ( - -
-
- + +
+
+ +
+ {selectedDashboard === "Office Dashboard" && } + {selectedDashboard === "Location Dashboard" && } +
- {selectedDashboard === "Office Dashboard" && } - {selectedDashboard === "Location Dashboard" && } - -
- + ); }; diff --git a/src/pages/forgot-password.js b/src/pages/forgot-password.js index 29fc7e2..40c9c12 100644 --- a/src/pages/forgot-password.js +++ b/src/pages/forgot-password.js @@ -1,10 +1,16 @@ import Forgot from "@/Components/Auth/Forgot"; +import { useSendResetPasswordLinkMutation } from "@/features/auth/authApi"; import React from "react"; const ForgetPassword = () => { + const [sendResetPasswordLink, { isLoading }] = + useSendResetPasswordLinkMutation(); return (
- +
); }; diff --git a/src/pages/index.js b/src/pages/index.js index 1463537..70a44db 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,11 +1,13 @@ import AuthLayout from "@/Layout/AuthLayout"; import SignIn from "@/Components/Auth/SignIn"; +import { useLoginMutation } from "@/features/auth/authApi"; export default function Home() { + const [login, { isLoading }] = useLoginMutation(); return ( - + ); } diff --git a/src/pages/reset-password.js b/src/pages/reset-password.js index 59dad57..b9add9b 100644 --- a/src/pages/reset-password.js +++ b/src/pages/reset-password.js @@ -1,10 +1,12 @@ import Reset from "@/Components/Auth/Reset"; +import { useUpdatePasswordMutation } from "@/features/auth/authApi"; import React from "react"; const ResetPassword = () => { + const [updatePassword, { isLoading }] = useUpdatePasswordMutation(); return (
- +
); }; diff --git a/src/pages/sign-up-user.js b/src/pages/sign-up-user.js new file mode 100644 index 0000000..b550ba1 --- /dev/null +++ b/src/pages/sign-up-user.js @@ -0,0 +1,162 @@ +import AuthLayout from "@/Layout/AuthLayout"; +import Link from "next/link"; +import React, { useState } from "react"; +import eyeOff from "@/assets/eye-off.png"; +import eye from "@/assets/eye.png"; + +const SignUpUserPage = () => { + const [showPassword, setShowPassword] = useState(false); + const [showConPassword, setShowConPassword] = useState(false); + return ( + +
+
+

Already have any account?

{" "} + + Sign In + +
+
+

Sign Up

+

To prepare to log into the system

+
+ + +
+ +
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+ + setShowPassword(!showPassword)} + style={{ + top: "50%", + transform: "translateY(-50%)", + right: "20px", + cursor: "pointer", + }} + className="position-absolute " + src={showPassword ? eye : eyeOff} + alt="" + /> +
+
+
+ +
+ + setShowConPassword(!showConPassword)} + style={{ + top: "50%", + transform: "translateY(-50%)", + right: "20px", + cursor: "pointer", + }} + className="position-absolute " + src={showConPassword ? eye : eyeOff} + alt="" + /> +
+
+

+ Email already exists in the system you must enter the Referral ID or + contact customer service at (800) 123-1234 +

+ + + +
+
+
+
+ ); +}; + +export default SignUpUserPage; diff --git a/src/pages/validate-email.js b/src/pages/validate-email.js deleted file mode 100644 index 34b3971..0000000 --- a/src/pages/validate-email.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import Validate from '../Utils/Validate'; - -const ValidEmail = () => { - return ( -
- -
- ); -}; - -export default ValidEmail; \ No newline at end of file diff --git a/src/pages/validate-email/[email].js b/src/pages/validate-email/[email].js new file mode 100644 index 0000000..bb43ea0 --- /dev/null +++ b/src/pages/validate-email/[email].js @@ -0,0 +1,16 @@ +import Validate from "@/Utils/Validate"; +import { useParams } from "next/navigation"; + +import React from "react"; + +const ValidEmail = () => { + const { email } = useParams() || {}; + + return ( +
+ +
+ ); +}; + +export default ValidEmail; diff --git a/src/privateRoutes/PrivateRoutes.js b/src/privateRoutes/PrivateRoutes.js new file mode 100644 index 0000000..06895bc --- /dev/null +++ b/src/privateRoutes/PrivateRoutes.js @@ -0,0 +1,19 @@ +import useIsAuthenticated from "@/hooks/useIsAuthenticated"; +import { useRouter } from "next/router"; +import React, { useEffect } from "react"; + +const PrivateRoutes = ({ children }) => { + const router = useRouter(); + + const isAuthenticated = useIsAuthenticated(); + + useEffect(() => { + if (!isAuthenticated) { + router.push("/"); + } + }, []); + + return isAuthenticated ? children : null; +}; + +export default PrivateRoutes; From 28d5bd1eb77a3144aeda818f4b76598408492658 Mon Sep 17 00:00:00 2001 From: mohi14 Date: Thu, 1 Feb 2024 10:56:18 +0600 Subject: [PATCH 2/4] reset password done --- src/Components/Auth/Reset.js | 62 +++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/Components/Auth/Reset.js b/src/Components/Auth/Reset.js index 5d547e0..3fdcc7b 100644 --- a/src/Components/Auth/Reset.js +++ b/src/Components/Auth/Reset.js @@ -4,18 +4,78 @@ import eyeOff from "../../assets/eye-off.png"; import eye from "../../assets/eye.png"; import { useState } from "react"; import { useRouter } from "next/router"; +import Swal from "sweetalert2"; +import { useUpdatePasswordMutation } from "@/features/auth/authApi"; const Reset = ({ updatePassword, isLoading }) => { const [showPassword, setShowPassword] = useState(false); const [showConPassword, setShowConPassword] = useState(false); - const { query } = useRouter(); + const { query, push } = useRouter(); console.log(query?.user, "ggg"); + const passwordRegex = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}/; + const handleSubmit = async (e) => { e.preventDefault(); const password = e.target.password.value; const confirmPassword = e.target.confirmPassword.value; + + if (!passwordRegex.test(password)) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: "Password must contain at least one uppercase, one lowercase, one special character, one digit and it should be at least 8 characters long.", + }); + return; + } + + if (password !== confirmPassword) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: "Password doesn't matched!", + }); + return; + } + + try { + const data = { + id: query?.user, + password, + }; + const res = await updatePassword(data); + + if (res?.error?.error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.error}`, + }); + } + if (res?.error?.data?.message) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.data?.message}`, + }); + } + if (res?.data?.success) { + push("/"); + Swal.fire({ + icon: "success", + title: "Successfull!", + text: `${res?.data?.message}`, + }); + } + } catch (error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${error?.message}`, + }); + } }; return ( From bdbdf47466f7b9b75922c6789f5dc2e6b4812dfb Mon Sep 17 00:00:00 2001 From: mohi14 Date: Thu, 8 Feb 2024 19:10:19 +0600 Subject: [PATCH 3/4] fronend --- src/Components/Auth/SignIn.js | 2 +- src/Components/Inspection/InspectionTab.js | 5 +- src/Components/Inspection/NewInspectionTab.js | 16 +- src/Components/Inspection/NewInsured.js | 153 +++++++++++++++--- src/Components/Inspection/Results.js | 7 +- src/Components/Inspection/SelectType.js | 12 +- src/Components/Shared/Header.js | 3 +- src/Context/TypeProvider.js | 15 -- src/app/store.js | 2 + src/features/Inspection/inspectionApi.js | 15 ++ src/features/Inspection/inspectionSlice.js | 22 +++ src/features/api/apiSlice.js | 7 + src/features/auth/authApi.js | 4 + src/pages/_app.js | 5 +- src/pages/index.js | 24 +-- src/pages/sign-in.js | 15 ++ 16 files changed, 237 insertions(+), 70 deletions(-) delete mode 100644 src/Context/TypeProvider.js create mode 100644 src/features/Inspection/inspectionApi.js create mode 100644 src/features/Inspection/inspectionSlice.js create mode 100644 src/pages/sign-in.js diff --git a/src/Components/Auth/SignIn.js b/src/Components/Auth/SignIn.js index 0dc68f4..5aa1102 100644 --- a/src/Components/Auth/SignIn.js +++ b/src/Components/Auth/SignIn.js @@ -10,7 +10,7 @@ import Swal from "sweetalert2"; const SignIn = ({ isLoading, login }) => { const [showPassword, setShowPassword] = useState(false); - console.log(isLoading, "ffkfjkf"); + // console.log(isLoading, "ffkfjkf"); const { push } = useRouter(); const handleSubmit = async (e) => { diff --git a/src/Components/Inspection/InspectionTab.js b/src/Components/Inspection/InspectionTab.js index 42e11a3..03f3a34 100644 --- a/src/Components/Inspection/InspectionTab.js +++ b/src/Components/Inspection/InspectionTab.js @@ -25,10 +25,11 @@ import Insured from "./Insured"; import OutBuildings from "./OutBuildings"; import Property from "./Property"; import Automobile from "./Automobile"; -import { TypeContext } from "@/Context/TypeProvider"; + +import { useSelector } from "react-redux"; const InspectionTab = () => { - const { type } = useContext(TypeContext); + const { type } = useSelector((state) => state.inspection); const [activeTab, setActiveTab] = useState("Insured"); const tabs = [ { diff --git a/src/Components/Inspection/NewInspectionTab.js b/src/Components/Inspection/NewInspectionTab.js index 41850c0..984d2c5 100644 --- a/src/Components/Inspection/NewInspectionTab.js +++ b/src/Components/Inspection/NewInspectionTab.js @@ -12,13 +12,14 @@ import NewInsured from "./NewInsured"; import NewProperty from "./NewProperty"; import CustomModal from "../Shared/Modal/CustomModal"; import SelectType from "./SelectType"; -import { TypeContext } from "@/Context/TypeProvider"; + import NewAutomobile from "./NewAutomobile"; +import { useSelector } from "react-redux"; const NewInspectionTab = () => { const [activeTab, setActiveTab] = useState("Insured"); const [modalOpen, setModalOpen] = useState(false); - const { type } = useContext(TypeContext); + const { type } = useSelector((state) => state.inspection); const openModal = () => setModalOpen(true); const closeModal = () => setModalOpen(false); @@ -33,13 +34,13 @@ const NewInspectionTab = () => { pic: property, activePic: propertyActive, title: "Property", - condition: type === "property", + condition: type === "Property", }, { pic: automobile, activePic: automobileActive, title: "Automobile", - condition: type === "automobile", + condition: type === "Automobile", }, ]; @@ -47,7 +48,6 @@ const NewInspectionTab = () => { openModal(); }, []); - console.log(activeTab, "active"); return (
@@ -59,7 +59,7 @@ const NewInspectionTab = () => { data.condition !== undefined ? ( data.condition ? (
setActiveTab(data.title)} + // onClick={() => setActiveTab(data.title)} style={{ cursor: "pointer" }} className={`flex gap-1 items-center tab ${ activeTab === data.title && "active" @@ -75,7 +75,7 @@ const NewInspectionTab = () => { ) : null ) : (
setActiveTab(data.title)} + // onClick={() => setActiveTab(data.title)} style={{ cursor: "pointer" }} className={`flex gap-1 items-center tab ${ activeTab === data.title && "active" @@ -93,7 +93,7 @@ const NewInspectionTab = () => {
- {activeTab === "Insured" && } + {activeTab === "Insured" && } {activeTab === "Property" && } {activeTab === "Automobile" && }
diff --git a/src/Components/Inspection/NewInsured.js b/src/Components/Inspection/NewInsured.js index e4dd9bd..b0826fe 100644 --- a/src/Components/Inspection/NewInsured.js +++ b/src/Components/Inspection/NewInsured.js @@ -1,9 +1,92 @@ +import { useAddInsuredMutation } from "@/features/Inspection/inspectionApi"; +import { setNewInsuredId } from "@/features/Inspection/inspectionSlice"; +import { useGetFieldAdjusterQuery } from "@/features/auth/authApi"; import React from "react"; +import { useDispatch, useSelector } from "react-redux"; +import Swal from "sweetalert2"; + +const NewInsured = ({ setActiveTab }) => { + const { user } = useSelector((state) => state.auth); + const { type } = useSelector((state) => state.inspection); + + const dispatch = useDispatch(); + + const { data } = useGetFieldAdjusterQuery(user?.companyId); + const [addInsured, { isLoading }] = useAddInsuredMutation(); + + const today = new Date(); + + const handleSubmit = async (e) => { + e.preventDefault(); + + const form = e.target; + + const companyId = user?.companyId; + const Date = form.Date.value; + const PolicyHolder = form.PolicyHolder.value; + const PolicyNumber = form.PolicyNumber.value; + const InspectionType = form.InspectionType.value; + const AssignedTo = form.AssignedTo.value; + const Insured = form.Insured.value; + const Email = form.Email.value; + const CellPhone = form.CellPhone.value; + const Address01 = form.Address01.value; + const Address02 = form.Address02.value; + const City = form.City.value; + const State = form.State.value; + const ZipCode = form.ZipCode.value; + const Type = type; + + const formdData = { + companyId, + Date, + PolicyHolder, + PolicyNumber, + InspectionType, + AssignedTo, + Insured, + Email, + CellPhone, + Address01, + Address02, + City, + State, + ZipCode, + Type, + }; + + try { + const res = await addInsured(formdData); + if (res?.error?.error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.error}`, + }); + } + if (res?.error?.data?.message) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.data?.message}`, + }); + } + if (res?.data?.success) { + dispatch(setNewInsuredId(res?.data?.data?.id)); + setActiveTab(type); + } + } catch (error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${error?.message}`, + }); + } + }; -const NewInsured = () => { return (
-
+
- + +
@@ -31,25 +116,26 @@ const NewInsured = () => {
- + +
- @@ -64,9 +150,10 @@ const NewInsured = () => {
@@ -75,7 +162,7 @@ const NewInsured = () => { @@ -86,9 +173,10 @@ const NewInsured = () => {
@@ -98,16 +186,23 @@ const NewInsured = () => {
- +
@@ -115,13 +210,19 @@ const NewInsured = () => { - +
- @@ -130,13 +231,23 @@ const NewInsured = () => { - +
- - + +
diff --git a/src/Components/Inspection/Results.js b/src/Components/Inspection/Results.js index b636221..777bd5d 100644 --- a/src/Components/Inspection/Results.js +++ b/src/Components/Inspection/Results.js @@ -3,13 +3,16 @@ import { useRouter } from "next/router"; import React, { useContext } from "react"; import home from "../../assets/home-icon.png"; import car from "../../assets/car.png"; -import { TypeContext } from "@/Context/TypeProvider"; + +import { useDispatch } from "react-redux"; +import { setType } from "@/features/Inspection/inspectionSlice"; const Results = () => { - const { setType } = useContext(TypeContext); + const dispatch = useDispatch(); const Router = useRouter(); const handleNavigate = (type) => { setType(type); + dispatch(setType(type)); Router.push("/dashboard/edit-inspection"); }; diff --git a/src/Components/Inspection/SelectType.js b/src/Components/Inspection/SelectType.js index bd71cbf..45d29ad 100644 --- a/src/Components/Inspection/SelectType.js +++ b/src/Components/Inspection/SelectType.js @@ -1,12 +1,14 @@ -import { TypeContext } from "@/Context/TypeProvider"; +import { setType } from "@/features/Inspection/inspectionSlice"; import React, { useContext } from "react"; +import { useDispatch } from "react-redux"; const SelectType = ({ onSave }) => { - const { setType } = useContext(TypeContext); + const dispatch = useDispatch(); const handleTypeChange = (event) => { const selectedType = event.target.value; - setType(selectedType); + + dispatch(setType(selectedType)); }; return ( @@ -15,8 +17,8 @@ const SelectType = ({ onSave }) => { onChange={handleTypeChange} className="max-w-[425px] h-[55px] mb-[30px]" > - - + +
- + +
- +
diff --git a/src/Components/Inspection/NewInsured.js b/src/Components/Inspection/NewInsured.js index b0826fe..3251710 100644 --- a/src/Components/Inspection/NewInsured.js +++ b/src/Components/Inspection/NewInsured.js @@ -1,6 +1,7 @@ import { useAddInsuredMutation } from "@/features/Inspection/inspectionApi"; import { setNewInsuredId } from "@/features/Inspection/inspectionSlice"; import { useGetFieldAdjusterQuery } from "@/features/auth/authApi"; +import { useRouter } from "next/router"; import React from "react"; import { useDispatch, useSelector } from "react-redux"; import Swal from "sweetalert2"; @@ -11,6 +12,8 @@ const NewInsured = ({ setActiveTab }) => { const dispatch = useDispatch(); + const { push } = useRouter(); + const { data } = useGetFieldAdjusterQuery(user?.companyId); const [addInsured, { isLoading }] = useAddInsuredMutation(); @@ -96,7 +99,7 @@ const NewInsured = ({ setActiveTab }) => { type="date" name="Date" id="date" - value={today.toISOString().split("T")[0]} + defaultValue={today.toISOString().split("T")[0]} placeholder="Enter your email address" required /> @@ -242,7 +245,11 @@ const NewInsured = ({ setActiveTab }) => {
-
- - + +
- +
diff --git a/src/Components/Inspection/Results.js b/src/Components/Inspection/Results.js index 777bd5d..5a01edc 100644 --- a/src/Components/Inspection/Results.js +++ b/src/Components/Inspection/Results.js @@ -4,10 +4,14 @@ import React, { useContext } from "react"; import home from "../../assets/home-icon.png"; import car from "../../assets/car.png"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { setType } from "@/features/Inspection/inspectionSlice"; +import { useGetAllinsuranceQuery } from "@/features/Inspection/inspectionApi"; +import { formateDate } from "@/Utils/FormateDate"; const Results = () => { + const { user } = useSelector((state) => state.auth); + const dispatch = useDispatch(); const Router = useRouter(); const handleNavigate = (type) => { @@ -16,6 +20,10 @@ const Results = () => { Router.push("/dashboard/edit-inspection"); }; + const { data, isLoading } = useGetAllinsuranceQuery(user?.id); + + console.log("fff", data); + return (

{ - {inspectionResult.map((data, index) => ( - handleNavigate(data.type)} - className="cursor-pointer" - key={index} - > - -

{data.insured}

- - - - - {data.assigned} - {data.DueDate} - {data.statusUpdate} - {data.attachment} - {data.status} - - ))} + {data && + data?.length > 0 && + data?.map((data, index) => ( + handleNavigate(data.type)} + className="cursor-pointer" + key={index} + > + +

{data.Insured}

+ + + + + {data.AssignedTo} + {formateDate(data.Date)} + {data.statusUpdate} + {data.attachment} + {data.Status} + + ))}

Showing 5 to 5 of 5 entries

diff --git a/src/Components/administration/Entities.js b/src/Components/administration/Entities.js index 1e5c63e..cc198fb 100644 --- a/src/Components/administration/Entities.js +++ b/src/Components/administration/Entities.js @@ -1,5 +1,6 @@ import React from "react"; import deleteIcon from "@/assets/deleteIcon.svg"; +import { useGetAllCompanyQuery } from "@/features/Company/companyApi"; const datas = [ { @@ -35,6 +36,8 @@ const datas = [ ]; const Entities = () => { + const { data } = useGetAllCompanyQuery(); + console.log(data, "company"); return (
@@ -48,19 +51,21 @@ const Entities = () => { - {datas.map((data, index) => ( - - - - - - - - ))} + {data && + data?.length > 0 && + data.map((item, index) => ( + + + + + + + + ))}
{index + 1}{data.companyName}{data.address}{data.phone} - -
{index + 1}{item.companyName}{item?.firstAddress ? item?.firstAddress : "N/A"}{item.phone} + +

Showing 5 to 5 of 5 entries

diff --git a/src/Components/administration/company/CompanyInfo.js b/src/Components/administration/company/CompanyInfo.js index 10dceb0..de1e6a2 100644 --- a/src/Components/administration/company/CompanyInfo.js +++ b/src/Components/administration/company/CompanyInfo.js @@ -1,6 +1,78 @@ -import React from "react"; +import { useUpdateCompanyInfoMutation } from "@/features/Company/companyApi"; +import { useRouter } from "next/router"; +import React, { useEffect, useState } from "react"; +import Swal from "sweetalert2"; + +const CompanyInfo = ({ user, data }) => { + const [updateCompanyInfo, { isLoading }] = useUpdateCompanyInfoMutation(); + + const { push } = useRouter(); + + const [companyBasicInfo, setCompanyBasicInfo] = useState({}); + const [editInfo, setEditInfo] = useState({}); + + const handleInputChange = (e) => { + const { name, value } = e.target; + setEditInfo({ ...editInfo, [name]: value }); + setCompanyBasicInfo({ ...companyBasicInfo, [name]: value }); + }; + + const handleSave = async () => { + try { + var formData = new FormData(); + + Object.entries(editInfo)?.forEach(([key, value]) => { + formData.append(key, value); + }); + const res = await updateCompanyInfo({ + id: user?.companyId, + data: formData, + }); + if (res?.error?.error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.error}`, + }); + } + if (res?.error?.data?.message) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${res?.error?.data?.message}`, + }); + } + if (res?.data?.success) { + Swal.fire({ + icon: "success", + title: "Susscessfull!", + text: `${res?.data?.message}`, + }); + } + } catch (error) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: `${error?.message}`, + }); + } + }; + + useEffect(() => { + const info = { + companyName: data?.companyInfo?.companyName, + currentCredits: data?.companyInfo?.currentCredits, + city: data?.companyInfo?.city, + firstAddress: data?.companyInfo?.firstAddress, + lastAddress: data?.companyInfo?.lastAddress, + logo: data?.companyInfo?.logo, + phone: data?.companyInfo?.phone, + state: data?.companyInfo?.state, + zipCode: data?.companyInfo?.zipCode, + }; + setCompanyBasicInfo(info); + }, [data]); -const CompanyInfo = () => { return (
@@ -9,26 +81,45 @@ const CompanyInfo = () => { * Company Name:
-
+ +
-
- + {/*
*/} + {/*
*/} +
+ + +
@@ -36,10 +127,11 @@ const CompanyInfo = () => { * Address 2:
@@ -47,10 +139,11 @@ const CompanyInfo = () => { * City:
@@ -58,24 +151,39 @@ const CompanyInfo = () => { * State:
- +
- -
diff --git a/src/Components/administration/company/CompanyTable.js b/src/Components/administration/company/CompanyTable.js index bdd4e4e..990248e 100644 --- a/src/Components/administration/company/CompanyTable.js +++ b/src/Components/administration/company/CompanyTable.js @@ -40,9 +40,11 @@ const datas = [ }, ]; -const CompanyTable = () => { +const CompanyTable = ({ user, data }) => { const [checkedItems, setCheckItems] = useState([]); + console.log(data, "ggg"); + const handleCheck = (item) => { if (checkedItems.includes(item)) { const newItems = checkedItems.filter((i) => i !== item); @@ -65,32 +67,40 @@ const CompanyTable = () => { - {datas.map((data, index) => ( - - {index + 1} - {data.name} - {data.email} - {data.phone} - {data.role} - - {/* 0 && + data?.companyUsers?.map((item, index) => ( + + {index + 1} + + {item.firstName} {item.lastName} + + {item.email} + {item.phoneNumber} + {item.userRole} + + {/* */} -
handleCheck(index)} - className="flex justify-center" - > - {checkedItems.includes(index) ? ( - - ) : ( - - )} -
- - - ))} +
handleCheck(index)} + className="flex justify-center" + > + {checkedItems.includes(index) ? ( + + ) : ( + + )} +
+ + + ))}

Showing 5 to 5 of 5 entries

diff --git a/src/Utils/FormateDate.js b/src/Utils/FormateDate.js new file mode 100644 index 0000000..6d4467b --- /dev/null +++ b/src/Utils/FormateDate.js @@ -0,0 +1,9 @@ +export const formateDate = (dateString) => { + const inputDate = new Date(dateString); + const formattedDate = inputDate.toLocaleDateString("en-US", { + month: "numeric", + day: "numeric", + year: "numeric", + }); + return formattedDate; +}; diff --git a/src/features/Company/companyApi.js b/src/features/Company/companyApi.js new file mode 100644 index 0000000..a19e9f5 --- /dev/null +++ b/src/features/Company/companyApi.js @@ -0,0 +1,28 @@ +import { apiSlice } from "../api/apiSlice"; + +export const companyApi = apiSlice.injectEndpoints({ + endpoints: (builder) => ({ + getAllCompany: builder.query({ + query: () => "/api/company/", + providesTags: ["Companies"], + }), + getMyCompanyInfo: builder.query({ + query: (id) => `/api/company/info/${id}`, + providesTags: ["MyCompany"], + }), + updateCompanyInfo: builder.mutation({ + query: ({ id, data }) => ({ + url: `/api/company/update/${id}`, + method: "PATCH", + body: data, + }), + invalidatesTags: ["MyCompany"], + }), + }), +}); + +export const { + useGetAllCompanyQuery, + useGetMyCompanyInfoQuery, + useUpdateCompanyInfoMutation, +} = companyApi; diff --git a/src/features/Company/companySlice.js b/src/features/Company/companySlice.js new file mode 100644 index 0000000..e69de29 diff --git a/src/features/Inspection/inspectionApi.js b/src/features/Inspection/inspectionApi.js index 242719f..ab90e7d 100644 --- a/src/features/Inspection/inspectionApi.js +++ b/src/features/Inspection/inspectionApi.js @@ -2,14 +2,46 @@ import { apiSlice } from "../api/apiSlice"; export const inspectionAPi = apiSlice.injectEndpoints({ endpoints: (builder) => ({ + // insured start addInsured: builder.mutation({ query: (data) => ({ url: "/api/inspection/addInsurance", method: "POST", body: data, }), + invalidatesTags: ["Inspections"], }), + getAllinsurance: builder.query({ + query: (id) => `/api/inspection/insurance/${id}`, + providesTags: ["Inspections"], + }), + // insured end + // property start + addProperty: builder.mutation({ + query: (data) => ({ + url: "/api/inspection/addProperty", + method: "POST", + body: data, + }), + invalidatesTags: ["Inspections"], + }), + // property end + // automobile start + addAutomobile: builder.mutation({ + query: (data) => ({ + url: "/api/inspection/addAutomobile", + method: "POST", + body: data, + }), + invalidatesTags: ["Inspections"], + }), + // automobile end }), }); -export const { useAddInsuredMutation } = inspectionAPi; +export const { + useAddInsuredMutation, + useAddPropertyMutation, + useGetAllinsuranceQuery, + useAddAutomobileMutation, +} = inspectionAPi; diff --git a/src/features/api/apiSlice.js b/src/features/api/apiSlice.js index 97f7c43..9718446 100644 --- a/src/features/api/apiSlice.js +++ b/src/features/api/apiSlice.js @@ -12,6 +12,6 @@ export const apiSlice = createApi({ return headers; }, }), - tagTypes: [], + tagTypes: ["Inspections", "Companies", "MyCompany"], endpoints: (builder) => ({}), }); diff --git a/src/pages/dashboard/administration/company/index.js b/src/pages/dashboard/administration/company/index.js index b391107..1c40c69 100644 --- a/src/pages/dashboard/administration/company/index.js +++ b/src/pages/dashboard/administration/company/index.js @@ -3,11 +3,16 @@ import Buttons from "@/Components/administration/company/Buttons"; import CompanyInfo from "@/Components/administration/company/CompanyInfo"; import CompanyTable from "@/Components/administration/company/CompanyTable"; import CustomModal from "@/Components/Shared/Modal/CustomModal"; +import { useGetMyCompanyInfoQuery } from "@/features/Company/companyApi"; import DashboardLayout from "@/Layout/DashboardLayout"; import React, { useState } from "react"; +import { useSelector } from "react-redux"; const CompanyPage = () => { const [activePage, setActivePage] = useState("main"); + const { user } = useSelector((state) => state.auth); + + const { data } = useGetMyCompanyInfoQuery(user?.companyId); // refer new company modal const [modalOpen, setModalOpen] = useState(false); @@ -23,7 +28,7 @@ const CompanyPage = () => { closeModal(); }; - // invite new user modal + // invite new user modal const [InviteModalOpen, setInviteModalOpen] = useState(false); const openInviteModal = () => setInviteModalOpen(true); @@ -41,8 +46,8 @@ const CompanyPage = () => { {activePage === "main" && (
- - + + {
-
- +
+ {
-
- +
+ { />
- - + +