diff --git a/src/assets/illustration_1_.png b/src/assets/illustration_1_.png new file mode 100644 index 0000000..b81f51d Binary files /dev/null and b/src/assets/illustration_1_.png differ diff --git a/src/components/auth/LoginForm.tsx b/src/components/auth/LoginForm.tsx index 733933c..730d857 100644 --- a/src/components/auth/LoginForm.tsx +++ b/src/components/auth/LoginForm.tsx @@ -1,3 +1,4 @@ + // import OAuthGoogle from "./OAuthGoogle"; import axios from "axios"; import { useState } from "react"; @@ -8,6 +9,8 @@ import { Bounce, toast } from "react-toastify"; // import Cookies from "js-cookie"; import { useDispatch } from "react-redux"; import { AppDispatch } from "../../store/store"; +import { BsEye } from "react-icons/bs"; +import { FiEyeOff } from "react-icons/fi"; // import api from "../../config/api"; interface FormDataState { @@ -16,43 +19,167 @@ interface FormDataState { } -function LoginForm() { - const [formData, setForm] = useState({ email: "", password: "" }); - const [cacheKey, setCacheKey] = useState(''); - const [nextClick, setNextClick] = useState(false); - const dispatch = useDispatch(); - const navigate = useNavigate(); +const LoginForm = ({ onSwitchToRegister }) => { + const [formData, setForm] = useState({ email: "", password: "" }); + const [cacheKey, setCacheKey] = useState(""); + const [nextClick, setNextClick] = useState(false); + const [showPassword, setShowPassword] = useState(false); + const [errors, setErrors] = useState({ email: "", password: "" }); + const [isLoading, setIsLoading] = useState(false); + const navigate = useNavigate() + const validateEmail = (email) => { + const trimmedEmail = email.trim(); - const handleNextClick = () => { - - // Validation Starts - if (formData.email === "") { - return; + if (!trimmedEmail) { + return "Email address is required"; + } + + if (trimmedEmail.length > 254) { + return "Email address is too long"; } - // Validate Email Format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(formData.email)) { + if (!emailRegex.test(trimmedEmail)) { + return "Please enter a valid email address"; + } + + // Check for common typos + const commonDomains = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com']; + const domain = trimmedEmail.split('@')[1]; + const commonTypos = { + 'gmial.com': 'gmail.com', + 'gmai.com': 'gmail.com', + 'yahooo.com': 'yahoo.com', + 'hotmial.com': 'hotmail.com' + }; + + if (commonTypos[domain]) { + return `Did you mean ${trimmedEmail.replace(domain, commonTypos[domain])}?`; + } + + return ""; + }; + + const validatePassword = (password) => { + if (!password) { + return "Password is required"; + } + + // if (password.length < 6) { + // return "Password must be at least 6 characters long"; + // } + + // if (password.length > 128) { + // return "Password is too long"; + // } + + return ""; + }; + + const handleEmailChange = (e) => { + const email = e.target.value; + setForm({ ...formData, email }); + + // Clear error when user starts typing + if (errors.email) { + setErrors({ ...errors, email: "" }); + } + }; + + const handlePasswordChange = (e) => { + const password = e.target.value; + setForm({ ...formData, password }); + + // Clear error when user starts typing + if (errors.password) { + setErrors({ ...errors, password: "" }); + } + }; + + const handleNextClick = async () => { + const emailError = validateEmail(formData.email); + + if (emailError) { + setErrors({ ...errors, email: emailError }); return; } - // Validation Ends - // Checking user exists getting cacheKey - axios.get(`${import.meta.env.VITE_BASE_URI}/api/auth/user?email=${formData.email}`, { headers: { "ngrok-skip-browser-warning": "69420" } }) - .then((res) => { + setIsLoading(true); - - if (res.status === 200) { - toast.dismiss(); - setCacheKey(res.data); - setNextClick(true); + try { + const res = await axios.get( + `${import.meta.env.VITE_BASE_URI}/api/auth/user?email=${formData.email}`, + { + headers: { + "ngrok-skip-browser-warning": "69420", + } } - }) - .catch((err) => { + ); + + if (res.status === 200) { toast.dismiss(); - toast.error("User Does Not Exists", { + setCacheKey(res.data); + setNextClick(true); + } + } catch (err: any) { + toast.dismiss(); + toast.error("User Does Not Exist", { + position: "top-center", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: false, + pauseOnHover: true, + draggable: true, + theme: "light", + transition: Bounce, + }); + } finally { + setIsLoading(false); + } + }; + + + const handleFormLoginClick = async () => { + const emailError = validateEmail(formData.email); + const passwordError = validatePassword(formData.password); + + if (emailError || passwordError) { + setErrors({ email: emailError, password: passwordError }); + return; + } + + setIsLoading(true); + setErrors({ email: "", password: "" }); + + try { + const res = await axios.post( + `${import.meta.env.VITE_BASE_URI}/api/auth/login`, + { email: formData.email, password: formData.password }, + { + withCredentials: true, + headers: { + "ngrok-skip-browser-warning": "69420", + "Access-Control-Allow-Origin": "*", + }, + } + ); + + if (res.status === 200) { + toast.dismiss(); + + // Example if you want cookies / redux: + // Cookies.set("authorization", res.headers.authorization, { + // path: "/", + // secure: true, + // sameSite: "Lax", + // }); + // dispatch(updateUser({ ...res.data, authorized: true, accessToken: res.headers.authorization })); + + navigate("/"); // Redirect after 1 second + + toast.success("Login Successful", { position: "top-center", autoClose: 5000, hideProgressBar: false, @@ -63,140 +190,157 @@ function LoginForm() { theme: "light", transition: Bounce, }); - }); - - } - - const handleFormLoginClick = () => { - - // Validation Starts - if (formData.email === "" || formData.password === "") { - return; - } - - // Validate Email Format - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(formData.email)) { - return; - } - // Validation Ends - - // Login API Call - axios.post(`${import.meta.env.VITE_BASE_URI}/api/auth/login`, - { cacheKey: cacheKey, password: formData.password }, - { - withCredentials: true, - headers: { - "ngrok-skip-browser-warning": "69420", - "Access-Control-Allow-Origin": "*" - } } - ) - .then((res) => { - if (res.status === 200) { - - toast.dismiss(); - // Cookies.set("authorization", res.headers.authorization, { - // path: "/", - // secure: true, // Required for HTTPS - // sameSite: "Lax", // Required for cross-origin requests - // }); + } catch (err) { + toast.dismiss(); - // dispatch(updateUser({ ...res.data, authorized: true, accessToken: res.headers.authorization })); - setTimeout(() => navigate('/'), 1000); // Redirect after 1 second - toast.success("Login Successful", { - position: "top-center", - autoClose: 5000, - hideProgressBar: false, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - progress: undefined, - theme: "light", - transition: Bounce, - }); - } - }) - .catch((err) => { - if (err.status === 401) { - toast.dismiss(); - toast.error("Credentials Not Match", { - position: "top-center", - autoClose: 5000, - hideProgressBar: false, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - progress: undefined, - theme: "light", - transition: Bounce, - }); - } else { - toast.dismiss(); - toast.error("Some Error Occurred", { - position: "top-center", - autoClose: 5000, - hideProgressBar: false, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - progress: undefined, - theme: "light", - transition: Bounce, - }); - } - }); - } + if (err.response?.status === 400 || err.response?.status === 401) { + toast.error("Credentials Not Match", { + position: "top-center", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: false, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + transition: Bounce, + }); + } else { + toast.error("Some Error Occurred", { + position: "top-center", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: false, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + transition: Bounce, + }); + } + } finally { + setIsLoading(false); + } + }; + + + const handleGoogleLogin = () => { + // Your Google OAuth logic here + console.log("Google login clicked"); + }; return ( -
-

Login

- - -
{ - e.preventDefault(); - }}> - -
-
- setForm({ ...formData, email: e.target.value })} /> - -
- -
-
- - -
{ - e.preventDefault(); - handleFormLoginClick(); - }}> -
-
- setForm({ ...formData, password: e.target.value })} - /> - -
- - -
-
- -

or login with social platforms

- -
- +
+
+ WELCOME BACK 👋 +

Login to your Account

-
-

Create an Account. Register

+
+ +
+ +
+
+ or +
+
+ +
+
+ + + {errors.email && ( +

{errors.email}

+ )} +
+ + {/* {nextClick && ( */} +
+ +
+ + +
+ {errors.password && ( +

{errors.password}

+ )} + {/* */} +
+ {/* )} */} + + {/* {!nextClick ? ( + + ) : ( */} + + {/* )} */} + +
+ Create an Account?{" "} + +
- ) -} + ); +}; -export default LoginForm +export default LoginForm \ No newline at end of file diff --git a/src/components/auth/RegisterForm.tsx b/src/components/auth/RegisterForm.tsx index 6e02a69..67656c4 100644 --- a/src/components/auth/RegisterForm.tsx +++ b/src/components/auth/RegisterForm.tsx @@ -10,6 +10,8 @@ import { Bounce, toast } from "react-toastify"; import { updateUser, UserState } from "../../store/slices/userSlice"; import { useDispatch, useSelector } from "react-redux"; import { AppDispatch, RootState } from "../../store/store"; +import { BsEye } from "react-icons/bs"; +import { FiEyeOff } from "react-icons/fi"; // import api from "../../config/api"; interface FormDataState { @@ -21,158 +23,410 @@ interface FormDataState { } -function RegisterForm() { - const [formData, setForm] = useState({ email: "", password: "", confirmPassword: "", firstName: "", lastName: "" }); +const RegisterForm = ({ onSwitchToLogin }) => { + const [formData, setForm] = useState({ + email: "", + password: "", + confirmPassword: "", + firstName: "", + lastName: "" + }); + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); + const [errors, setErrors] = useState({ + firstName: "", + lastName: "", + email: "", + password: "", + confirmPassword: "" + }); + const [isLoading, setIsLoading] = useState(false); + const navigate = useNavigate() - const dispatch = useDispatch(); - const navigate = useNavigate(); + const validateFirstName = (name) => { + const trimmedName = name.trim(); - const handleFormRegisterClick = () => { - formData.firstName = formData.firstName.trim(); - formData.lastName = formData.lastName.trim(); - // Validation Starts - if (formData.email === "" || formData.password === "" || formData.confirmPassword === "" || formData.firstName === "" || formData.lastName === "") { - console.log('All Fields are Required'); - return; + if (!trimmedName) { + return "First name is required"; + } + + if (trimmedName.length < 2) { + return "First name must be at least 2 characters long"; + } + + if (trimmedName.length > 50) { + return "First name is too long"; + } + + const nameRegex = /^[a-zA-Z\s'-]+$/; + if (!nameRegex.test(trimmedName)) { + return "First name can only contain letters, spaces, hyphens, and apostrophes"; + } + + return ""; + }; + + const validateLastName = (name) => { + const trimmedName = name.trim(); + + if (!trimmedName) { + return "Last name is required"; + } + + if (trimmedName.length < 2) { + return "Last name must be at least 2 characters long"; + } + + if (trimmedName.length > 50) { + return "Last name is too long"; + } + + const nameRegex = /^[a-zA-Z\s'-]+$/; + if (!nameRegex.test(trimmedName)) { + return "Last name can only contain letters, spaces, hyphens, and apostrophes"; + } + + return ""; + }; + + const validateEmail = (email) => { + const trimmedEmail = email.trim(); + + if (!trimmedEmail) { + return "Email address is required"; + } + + if (trimmedEmail.length > 254) { + return "Email address is too long"; } - // Validate Email Format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(formData.email)) { - console.log("Invalid email format"); - return; + if (!emailRegex.test(trimmedEmail)) { + return "Please enter a valid email address"; } - // Validation Ends + return ""; + }; - // Register API Call - console.dir(formData); + const validatePassword = (password) => { + if (!password) { + return "Password is required"; + } - axios.post(`${import.meta.env.VITE_BASE_URI}/api/auth/signup`, { firstName: formData.firstName, lastName: formData.lastName, email: formData.email, password: formData.password }, { - withCredentials: true, - headers: { - "ngrok-skip-browser-warning": "69420", - "Access-Control-Allow-Origin": "*" + if (password.length < 8) { + return "Password must be at least 8 characters long"; + } + + if (password.length > 128) { + return "Password is too long"; + } + + const hasUpperCase = /[A-Z]/.test(password); + const hasLowerCase = /[a-z]/.test(password); + const hasNumbers = /\d/.test(password); + // const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password); + + // if (!hasUpperCase) { + // return "Password must contain at least one uppercase letter"; + // } + + // if (!hasLowerCase) { + // return "Password must contain at least one lowercase letter"; + // } + + // if (!hasNumbers) { + // return "Password must contain at least one number"; + // } + + // if (!hasSpecialChar) { + // return "Password must contain at least one special character"; + // } + + return ""; + }; + + const validateConfirmPassword = (confirmPassword, password) => { + if (!confirmPassword) { + return "Please confirm your password"; + } + + if (confirmPassword !== password) { + return "Passwords do not match"; + } + + return ""; + }; + + const handleInputChange = (field, value) => { + setForm({ ...formData, [field]: value }); + + // Clear error when user starts typing + if (errors[field]) { + setErrors({ ...errors, [field]: "" }); + } + + // If confirming password, also check if it matches + if (field === "password" && formData.confirmPassword && errors.confirmPassword) { + const confirmError = validateConfirmPassword(formData.confirmPassword, value); + if (!confirmError) { + setErrors({ ...errors, [field]: "", confirmPassword: "" }); } - }) - .then((res) => { - if (res.status === 201) { + } + }; - toast.dismiss(); - // dispatch(updateUser({ ...res.data, authorized: true, accessToken: res.headers.authorization })); - // dispatch(updateUser({ ...res.data, authorized: true })); - navigate('/') - } - }) - .catch((err) => { - if (err.status === 409) { - toast.dismiss(); - toast.error("User Already Exists", { - position: "top-center", - autoClose: 5000, - hideProgressBar: false, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - progress: undefined, - theme: "light", - transition: Bounce, - }); - } else { - toast.dismiss(); - toast.error("Some Error Occurred", { - position: "top-center", - autoClose: 5000, - hideProgressBar: false, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - progress: undefined, - theme: "light", - transition: Bounce, - }); - } - }); + const handleFormRegisterClick = async () => { + const firstNameError = validateFirstName(formData.firstName); + const lastNameError = validateLastName(formData.lastName); + const emailError = validateEmail(formData.email); + const passwordError = validatePassword(formData.password); + const confirmPasswordError = validateConfirmPassword(formData.confirmPassword, formData.password); + + const newErrors = { + firstName: firstNameError, + lastName: lastNameError, + email: emailError, + password: passwordError, + confirmPassword: confirmPasswordError, + }; + + setErrors(newErrors); + + // If any errors exist, stop + if (Object.values(newErrors).some((error) => error !== "")) { + return; + } + + setIsLoading(true); + + const trimmedData = { + firstName: formData.firstName.trim(), + lastName: formData.lastName.trim(), + email: formData.email.trim(), + password: formData.password, + confirmPassword: formData.confirmPassword, + }; + + try { + const res = await axios.post( + `${import.meta.env.VITE_BASE_URI}/api/auth/signup`, + { + firstName: trimmedData.firstName, + lastName: trimmedData.lastName, + email: trimmedData.email, + password: trimmedData.password, + }, + { + withCredentials: true, + headers: { + "ngrok-skip-browser-warning": "69420", + "Access-Control-Allow-Origin": "*", + }, + } + ); + + if (res.status === 201) { + toast.dismiss(); + // dispatch(updateUser({ ...res.data, authorized: true, accessToken: res.headers.authorization })); + // dispatch(updateUser({ ...res.data, authorized: true })); + navigate("/"); } + } catch (err: any) { + toast.dismiss(); + + if (err.response?.status === 409) { + toast.error("User Already Exists", { + position: "top-center", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: false, + pauseOnHover: true, + draggable: true, + theme: "light", + transition: Bounce, + }); + } else { + toast.error("Some Error Occurred", { + position: "top-center", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: false, + pauseOnHover: true, + draggable: true, + theme: "light", + transition: Bounce, + }); + } + } finally { + setIsLoading(false); + } +}; + + + const handleGoogleLogin = () => { + // Your Google OAuth logic here + console.log("Google signup clicked"); + }; return ( -
-
{ - e.preventDefault(); - handleFormRegisterClick(); - }}> -

Registration

- {/*
- - -
*/} -
-
- setForm({ ...formData, firstName: e.target.value })} - type="text" placeholder="First Name" pattern="^(?!\s*$).+" - required - title="Input cannot be empty or just spaces" - /> - -
+
+
+

Create an Account

+
-
- setForm({ ...formData, lastName: e.target.value })} - type="text" - placeholder="Last Name" - pattern="^(?!\s*$).+" - required - title="Input cannot be empty or just spaces" /> - +
+ +
+ +
+
+ or +
+
+ +
+
+ +
+
+ handleInputChange("firstName", e.target.value)} + placeholder="First name" + className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none ${errors.firstName + ? "border-red-500 focus:ring-red-500" + : "border-gray-300 focus:ring-blue-500" + }`} + disabled={isLoading} + /> + {errors.firstName && ( +

{errors.firstName}

+ )} +
+
+ handleInputChange("lastName", e.target.value)} + placeholder="Last name" + className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none ${errors.lastName + ? "border-red-500 focus:ring-red-500" + : "border-gray-300 focus:ring-blue-500" + }`} + disabled={isLoading} + /> + {errors.lastName && ( +

{errors.lastName}

+ )} +
-
+
+ setForm({ ...formData, email: e.target.value })} - type="email" placeholder="Email" required /> - -
-
- setForm({ ...formData, password: e.target.value })} - type="password" placeholder="Password" - pattern="^.{8,}$" - title="Password must be at least 8 digits" - required /> - -
-
- setForm({ ...formData, confirmPassword: e.target.value })} - type="password" - placeholder="Confirm Password" - style={{ - border: (formData.confirmPassword != "" && formData.password !== formData.confirmPassword) ? "1px solid red" : "none" - }} - required + type="email" + value={formData.email} + onChange={(e) => handleInputChange("email", e.target.value)} + placeholder="Enter your email" + className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none ${errors.email + ? "border-red-500 focus:ring-red-500" + : "border-gray-300 focus:ring-blue-500" + }`} + disabled={isLoading} /> - + {errors.email && ( +

{errors.email}

+ )}
- +
+ {errors.password && ( +

{errors.password}

+ )} +
+ +
+ +
+ handleInputChange("confirmPassword", e.target.value)} + placeholder="Confirm your password" + className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none pr-12 ${errors.confirmPassword + ? "border-red-500 focus:ring-red-500" + : "border-gray-300 focus:ring-blue-500" + }`} + disabled={isLoading} + /> + +
+ {errors.confirmPassword && ( +

{errors.confirmPassword}

+ )} +
+ + -

or register with social platforms

-
- +
+ Already have an Account?{" "} +
- - -
-

Already! have an Account. Login

- ) -} + ); +}; export default RegisterForm diff --git a/src/components/auth/UsertAuthUI.tsx b/src/components/auth/UsertAuthUI.tsx index 12693ff..44224dc 100644 --- a/src/components/auth/UsertAuthUI.tsx +++ b/src/components/auth/UsertAuthUI.tsx @@ -1,87 +1,82 @@ -import { useEffect, useLayoutEffect, useRef } from "react"; - -import "../styles/style.css" -import { useNavigate } from "react-router-dom"; +import { useEffect, useState } from "react"; +import "../styles/style.css"; +import { useNavigate, useLocation } from "react-router-dom"; import LoginForm from "./LoginForm"; import RegisterForm from "./RegisterForm"; import { useDispatch } from "react-redux"; import { AppDispatch } from "../../store/store"; import { fetchUserDetails } from "../../store/slices/userSlice"; +import LoginRegisterImg from "../../assets/illustration_1_.png"; +import SMELogo from "../../assets/SMELogo.png"; -function UserAuthUI({ pg }: { pg: string }) { +function UserAuthUI() { + const [isLogin, setIsLogin] = useState(true); const navigate = useNavigate(); + const location = useLocation(); // Hook to get the current URL path - useLayoutEffect(() => { - dispatch(fetchUserDetails()); - }, []); - - const dispatch = useDispatch(); - const elementRef = useRef(null); - + // Sync isLogin state with the current route useEffect(() => { - if (pg === 'login') { - elementRef.current?.classList.add('active') - setTimeout(() => { - elementRef.current?.classList.remove('active') - }, 0) - } else { - setTimeout(() => { - elementRef.current?.classList.add('active') - }, 0) + const path = location.pathname; + if (path === "/login") { + setIsLogin(true); + } else if (path === "/register") { + setIsLogin(false); } - }, []) + }, [location.pathname]); - // const [activeToggle, setActiveToggle] = useState(''); + const handleLogin = (loginData) => { + console.log("Login data:", loginData); + // Your login API call logic here + }; - // const [page, setPage] = useState(pg || 'login'); - function handleToggleForm(val: string) { - navigate(`/${val}`); - // setPage((prev) => prev === "login" ? "register" : "login") - } - - // const user: UserState = useSelector((state: RootState) => state.user); - - // useEffect(() => { - // if (page === 'register') { - // setActiveToggle('active') - // } else { - // setActiveToggle('') - // } - // }, [page]) - - // useEffect(() => { - // setPage(pg); - // }, [pg]) + const handleRegister = (registerData) => { + console.log("Register data:", registerData); + // Your register API call logic here + }; return ( - <> -
-
-
- +
+ {/* Left Side - Branding */} +
+
+ {/* Logos */} +
+ SME Logo + Illustration
-
- -
- -
-
-

Sign Made Easy

-

Don't have an account?

- -
- -
-

Welcome Back!

-

Already have an account?

- -
+ {/* Text Section */} +
+

Create, eSign, and Connect!

+

+ A Powerful eSign solution for Salesforce, powered by Adobe Advanced + Trust List (AATL), to elevate your digital signature experience and + foster agreements closures. +

- - ) + + {/* Right Side - Form */} +
+ {isLogin ? ( + navigate("/register")} // Navigate to /register + /> + ) : ( + navigate("/login")} // Navigate to /login + /> + )} +
+
+ ); } -export default UserAuthUI +export default UserAuthUI; \ No newline at end of file diff --git a/src/components/login/Login.tsx b/src/components/login/Login.tsx index ddf7096..3ab73e6 100644 --- a/src/components/login/Login.tsx +++ b/src/components/login/Login.tsx @@ -3,7 +3,7 @@ import UserAuthUI from "../auth/UsertAuthUI" function Login() { return ( <> -
+
diff --git a/src/components/process/processStepOne/SelectedDocuments.tsx b/src/components/process/processStepOne/SelectedDocuments.tsx index b78a6ea..b479bb1 100644 --- a/src/components/process/processStepOne/SelectedDocuments.tsx +++ b/src/components/process/processStepOne/SelectedDocuments.tsx @@ -14,16 +14,17 @@ import { useParams } from 'react-router-dom'; import { toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice'; -const SelectedDocuments = ({transactionTemplateToggle}) => { +const SelectedDocuments = ({ transactionTemplateToggle }) => { // let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS; // const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); // await sleep(5000); const documents = useSelector((state: RootState) => state.documents.documents); + const docCount = documents.length; useEffect(() => { - + console.log("Documents updated:", documents); }, [documents]); @@ -50,7 +51,7 @@ const SelectedDocuments = ({transactionTemplateToggle}) => { { name: 'Dropbox', icon: }, ]; - const handleUpload = (filesToUpload: File[]) => { + const handleUpload = (filesToUpload: File[]) => { const formData = new FormData(); filesToUpload.forEach(file => { @@ -87,39 +88,110 @@ const SelectedDocuments = ({transactionTemplateToggle}) => { } }; + // Get proper Tailwind class names for upload card span + const getUploadCardSpanClass = () => { + switch (docCount) { + case 0: return 'col-span-5'; // Full width when no docs + case 1: return 'col-span-4'; // 4 cols for upload, 1 for doc + case 2: return 'col-span-3'; // 3 cols for upload, 2 for docs + case 3: return 'col-span-2'; // 2 cols for upload, 3 for docs + case 4: return 'col-span-1'; // 1 col for upload, 4 for docs + default: return 'col-span-1'; // 1 col for upload, rest for docs + } + }; + + // Calculate how many documents should be in the first row + const getFirstRowDocCount = () => { + if (docCount === 0) return 0; + if (docCount <= 4) return docCount; // All docs fit in first row + return 4; // Max 4 docs in first row when upload card takes 1 column + }; + + const firstRowDocCount = getFirstRowDocCount(); + const remainingDocs = Math.max(0, docCount - firstRowDocCount); + return ( <> {/* Upload Card First */} -
- {/* Uploaded documents */} +
+
+ {/* Upload Document Card - Always First */}
setModalOpen(true)} - className="cursor-pointer w-[160px] h-[170px] flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-xl bg-white hover:bg-gray-50 transition shadow-sm hover:shadow-md" + className={`${getUploadCardSpanClass()} cursor-pointer h-[170px] flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-xl bg-white hover:bg-gray-50 transition shadow-sm hover:shadow-md`} >

Upload Document

- {/* Document Show */} + {/* First Row Documents */} ({ ...doc }))} - setList={(newState) => dispatch(setDocuments(newState))} + className="contents" // Use 'contents' to make children participate in parent grid + list={documents.slice(0, firstRowDocCount).map(doc => ({ ...doc }))} + setList={(newState) => { + const updatedDocs = [ + ...newState, + ...documents.slice(firstRowDocCount) + ]; + dispatch(setDocuments(updatedDocs)); + }} handle=".drag-handle" animation={200} ghostClass="sortable-ghost" disabled={false} > - {documents.map((doc) => ( - + {documents.slice(0, firstRowDocCount).map((doc) => ( +
+ +
))}
- -
+ {/* Additional Rows - Remaining Documents (5th, 6th, 7th, etc.) */} + {remainingDocs > 0 && ( + ({ ...doc }))} + setList={(newState) => { + const updatedDocs = [ + ...documents.slice(0, firstRowDocCount), + ...newState + ]; + dispatch(setDocuments(updatedDocs)); + }} + handle=".drag-handle" + animation={200} + ghostClass="sortable-ghost" + disabled={false} + > + {documents.slice(firstRowDocCount).map((doc) => ( +
+ +
+ ))} +
+ )} + + {/* Modal */} {isModalOpen && (
@@ -181,6 +253,10 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
)} + +
+ + ); }; diff --git a/src/components/register/Register.tsx b/src/components/register/Register.tsx index adb8365..fe9ae53 100644 --- a/src/components/register/Register.tsx +++ b/src/components/register/Register.tsx @@ -3,7 +3,7 @@ import UserAuthUI from "../auth/UsertAuthUI" function Register() { return ( <> -
+
diff --git a/src/components/styles/style.css b/src/components/styles/style.css index 21f3e0e..310944f 100644 --- a/src/components/styles/style.css +++ b/src/components/styles/style.css @@ -30,13 +30,13 @@ html { .auth-ui-con .container-l { position: relative; - width: 850px; - height: 550px; + width: 1280px; + height: 599px; background: #fff; /* margin-top: 20px; */ margin-left: 50%; transform: translateX(-50%); - border-radius: 30px; + /* border-radius: 30px; */ box-shadow: 0 0 30px rgba(0, 0, 0, .2); overflow-y: auto; overflow-x: hidden;