Compare commits
4 Commits
73cf07a396
...
d9f8cd58b0
| Author | SHA1 | Date |
|---|---|---|
|
|
d9f8cd58b0 | |
|
|
fcb7da1c68 | |
|
|
e8b83910a7 | |
|
|
566765df51 |
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
// import OAuthGoogle from "./OAuthGoogle";
|
// import OAuthGoogle from "./OAuthGoogle";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
@ -8,6 +9,8 @@ import { Bounce, toast } from "react-toastify";
|
||||||
// import Cookies from "js-cookie";
|
// import Cookies from "js-cookie";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { AppDispatch } from "../../store/store";
|
import { AppDispatch } from "../../store/store";
|
||||||
|
import { BsEye } from "react-icons/bs";
|
||||||
|
import { FiEyeOff } from "react-icons/fi";
|
||||||
// import api from "../../config/api";
|
// import api from "../../config/api";
|
||||||
|
|
||||||
interface FormDataState {
|
interface FormDataState {
|
||||||
|
|
@ -16,43 +19,167 @@ interface FormDataState {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function LoginForm() {
|
|
||||||
const [formData, setForm] = useState<FormDataState>({ email: "", password: "" });
|
|
||||||
const [cacheKey, setCacheKey] = useState<string>('');
|
|
||||||
const [nextClick, setNextClick] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const LoginForm = ({ onSwitchToRegister }) => {
|
||||||
const navigate = useNavigate();
|
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 = () => {
|
if (!trimmedEmail) {
|
||||||
|
return "Email address is required";
|
||||||
// Validation Starts
|
}
|
||||||
if (formData.email === "") {
|
|
||||||
return;
|
if (trimmedEmail.length > 254) {
|
||||||
|
return "Email address is too long";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate Email Format
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// Validation Ends
|
|
||||||
|
|
||||||
// Checking user exists getting cacheKey
|
setIsLoading(true);
|
||||||
axios.get(`${import.meta.env.VITE_BASE_URI}/api/auth/user?email=${formData.email}`, { headers: { "ngrok-skip-browser-warning": "69420" } })
|
|
||||||
.then((res) => {
|
|
||||||
|
|
||||||
|
try {
|
||||||
if (res.status === 200) {
|
const res = await axios.get(
|
||||||
toast.dismiss();
|
`${import.meta.env.VITE_BASE_URI}/api/auth/user?email=${formData.email}`,
|
||||||
setCacheKey(res.data);
|
{
|
||||||
setNextClick(true);
|
headers: {
|
||||||
|
"ngrok-skip-browser-warning": "69420",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
);
|
||||||
.catch((err) => {
|
|
||||||
|
if (res.status === 200) {
|
||||||
toast.dismiss();
|
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",
|
position: "top-center",
|
||||||
autoClose: 5000,
|
autoClose: 5000,
|
||||||
hideProgressBar: false,
|
hideProgressBar: false,
|
||||||
|
|
@ -63,140 +190,157 @@ function LoginForm() {
|
||||||
theme: "light",
|
theme: "light",
|
||||||
transition: Bounce,
|
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": "*"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
} catch (err) {
|
||||||
.then((res) => {
|
toast.dismiss();
|
||||||
if (res.status === 200) {
|
|
||||||
|
|
||||||
toast.dismiss();
|
if (err.response?.status === 400 || err.response?.status === 401) {
|
||||||
// Cookies.set("authorization", res.headers.authorization, {
|
toast.error("Credentials Not Match", {
|
||||||
// path: "/",
|
position: "top-center",
|
||||||
// secure: true, // Required for HTTPS
|
autoClose: 5000,
|
||||||
// sameSite: "Lax", // Required for cross-origin requests
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// dispatch(updateUser({ ...res.data, authorized: true, accessToken: res.headers.authorization }));
|
|
||||||
setTimeout(() => navigate('/'), 1000); // Redirect after 1 second
|
const handleGoogleLogin = () => {
|
||||||
toast.success("Login Successful", {
|
// Your Google OAuth logic here
|
||||||
position: "top-center",
|
console.log("Google login clicked");
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="bg-white p-8 rounded-lg w-full max-w-md">
|
||||||
<h1>Login</h1>
|
<div className="text-center mb-6">
|
||||||
|
<span className="text-orange-500 text-sm">WELCOME BACK 👋</span>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800 mt-2">Login to your Account</h2>
|
||||||
<form action="#" onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
}}>
|
|
||||||
|
|
||||||
<div className={`${nextClick ? 'hidden' : ''}`}>
|
|
||||||
<div className="input-box">
|
|
||||||
<input type="email" placeholder="Email" required
|
|
||||||
onChange={(e) => setForm({ ...formData, email: e.target.value })} />
|
|
||||||
<i className='bx bxs-user'></i>
|
|
||||||
</div>
|
|
||||||
<button type="submit" className="btn" onClick={handleNextClick}>Next</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
<form action="#" onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleFormLoginClick();
|
|
||||||
}}>
|
|
||||||
<div className={`${!nextClick ? 'hidden' : ''}`}>
|
|
||||||
<div className="input-box">
|
|
||||||
<input type="password" placeholder="Password" required
|
|
||||||
onChange={(e) => setForm({ ...formData, password: e.target.value })}
|
|
||||||
/>
|
|
||||||
<i className='bx bxs-lock-alt' ></i>
|
|
||||||
</div>
|
|
||||||
<div className="forgot-link">
|
|
||||||
<a href="#">Forgot Password?</a>
|
|
||||||
</div>
|
|
||||||
<button type="submit" className="btn">Login</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p>or login with social platforms</p>
|
|
||||||
|
|
||||||
<div className="social-icons">
|
|
||||||
<OAuthGoogle isLogin={true} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="flex justify-center">
|
||||||
<p>Create an Account. <Link to={'/register'} className="text-blue-400">Register</Link></p>
|
<OAuthGoogle onGoogleLogin={handleGoogleLogin} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center my-6">
|
||||||
|
<hr className="flex-1 border-gray-300" />
|
||||||
|
<span className="px-4 text-gray-500 text-sm">or</span>
|
||||||
|
<hr className="flex-1 border-gray-300" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Email Address
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleEmailChange}
|
||||||
|
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={nextClick || isLoading}
|
||||||
|
/>
|
||||||
|
{errors.email && (
|
||||||
|
<p className="text-red-500 text-sm mt-1">{errors.email}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* {nextClick && ( */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
value={formData.password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
placeholder="Enter your password"
|
||||||
|
className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none pr-12 ${errors.password
|
||||||
|
? "border-red-500 focus:ring-red-500"
|
||||||
|
: "border-gray-300 focus:ring-blue-500"
|
||||||
|
}`}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{showPassword ? <FiEyeOff size={20} /> : <BsEye size={20} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{errors.password && (
|
||||||
|
<p className="text-red-500 text-sm mt-1">{errors.password}</p>
|
||||||
|
)}
|
||||||
|
{/* <div className="text-right mt-2">
|
||||||
|
<a href="#" className="text-blue-500 text-sm hover:underline">
|
||||||
|
Forgot password?
|
||||||
|
</a>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
{/* )} */}
|
||||||
|
|
||||||
|
{/* {!nextClick ? (
|
||||||
|
<button
|
||||||
|
onClick={handleNextClick}
|
||||||
|
disabled={isLoading}
|
||||||
|
className={`w-full py-2 rounded-lg font-medium transition-colors ${isLoading
|
||||||
|
? "bg-gray-400 cursor-not-allowed"
|
||||||
|
: "bg-blue-500 hover:bg-blue-600 text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isLoading ? "Verifying..." : "Next"}
|
||||||
|
</button>
|
||||||
|
) : ( */}
|
||||||
|
<button
|
||||||
|
onClick={handleFormLoginClick}
|
||||||
|
disabled={isLoading}
|
||||||
|
className={`w-full py-2 rounded-lg font-medium transition-colors ${isLoading
|
||||||
|
? "bg-gray-400 cursor-not-allowed"
|
||||||
|
: "bg-blue-500 hover:bg-blue-600 text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isLoading ? "Logging in..." : "Login"}
|
||||||
|
</button>
|
||||||
|
{/* )} */}
|
||||||
|
|
||||||
|
<div className="text-center text-sm text-gray-600">
|
||||||
|
Create an Account?{" "}
|
||||||
|
<button
|
||||||
|
onClick={onSwitchToRegister}
|
||||||
|
className="text-blue-500 font-medium hover:underline"
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default LoginForm
|
export default LoginForm
|
||||||
|
|
@ -10,6 +10,8 @@ import { Bounce, toast } from "react-toastify";
|
||||||
import { updateUser, UserState } from "../../store/slices/userSlice";
|
import { updateUser, UserState } from "../../store/slices/userSlice";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { AppDispatch, RootState } from "../../store/store";
|
import { AppDispatch, RootState } from "../../store/store";
|
||||||
|
import { BsEye } from "react-icons/bs";
|
||||||
|
import { FiEyeOff } from "react-icons/fi";
|
||||||
// import api from "../../config/api";
|
// import api from "../../config/api";
|
||||||
|
|
||||||
interface FormDataState {
|
interface FormDataState {
|
||||||
|
|
@ -21,158 +23,410 @@ interface FormDataState {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function RegisterForm() {
|
const RegisterForm = ({ onSwitchToLogin }) => {
|
||||||
const [formData, setForm] = useState<FormDataState>({ email: "", password: "", confirmPassword: "", firstName: "", lastName: "" });
|
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<AppDispatch>();
|
const validateFirstName = (name) => {
|
||||||
const navigate = useNavigate();
|
const trimmedName = name.trim();
|
||||||
|
|
||||||
const handleFormRegisterClick = () => {
|
if (!trimmedName) {
|
||||||
formData.firstName = formData.firstName.trim();
|
return "First name is required";
|
||||||
formData.lastName = formData.lastName.trim();
|
}
|
||||||
// Validation Starts
|
|
||||||
if (formData.email === "" || formData.password === "" || formData.confirmPassword === "" || formData.firstName === "" || formData.lastName === "") {
|
if (trimmedName.length < 2) {
|
||||||
console.log('All Fields are Required');
|
return "First name must be at least 2 characters long";
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
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@]+$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
if (!emailRegex.test(formData.email)) {
|
if (!emailRegex.test(trimmedEmail)) {
|
||||||
console.log("Invalid email format");
|
return "Please enter a valid email address";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// Validation Ends
|
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
// Register API Call
|
const validatePassword = (password) => {
|
||||||
console.dir(formData);
|
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 }, {
|
if (password.length < 8) {
|
||||||
withCredentials: true,
|
return "Password must be at least 8 characters long";
|
||||||
headers: {
|
}
|
||||||
"ngrok-skip-browser-warning": "69420",
|
|
||||||
"Access-Control-Allow-Origin": "*"
|
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();
|
const handleFormRegisterClick = async () => {
|
||||||
// dispatch(updateUser({ ...res.data, authorized: true, accessToken: res.headers.authorization }));
|
const firstNameError = validateFirstName(formData.firstName);
|
||||||
// dispatch(updateUser({ ...res.data, authorized: true }));
|
const lastNameError = validateLastName(formData.lastName);
|
||||||
navigate('/')
|
const emailError = validateEmail(formData.email);
|
||||||
}
|
const passwordError = validatePassword(formData.password);
|
||||||
})
|
const confirmPasswordError = validateConfirmPassword(formData.confirmPassword, formData.password);
|
||||||
.catch((err) => {
|
|
||||||
if (err.status === 409) {
|
const newErrors = {
|
||||||
toast.dismiss();
|
firstName: firstNameError,
|
||||||
toast.error("User Already Exists", {
|
lastName: lastNameError,
|
||||||
position: "top-center",
|
email: emailError,
|
||||||
autoClose: 5000,
|
password: passwordError,
|
||||||
hideProgressBar: false,
|
confirmPassword: confirmPasswordError,
|
||||||
closeOnClick: false,
|
};
|
||||||
pauseOnHover: true,
|
|
||||||
draggable: true,
|
setErrors(newErrors);
|
||||||
progress: undefined,
|
|
||||||
theme: "light",
|
// If any errors exist, stop
|
||||||
transition: Bounce,
|
if (Object.values(newErrors).some((error) => error !== "")) {
|
||||||
});
|
return;
|
||||||
} else {
|
}
|
||||||
toast.dismiss();
|
|
||||||
toast.error("Some Error Occurred", {
|
setIsLoading(true);
|
||||||
position: "top-center",
|
|
||||||
autoClose: 5000,
|
const trimmedData = {
|
||||||
hideProgressBar: false,
|
firstName: formData.firstName.trim(),
|
||||||
closeOnClick: false,
|
lastName: formData.lastName.trim(),
|
||||||
pauseOnHover: true,
|
email: formData.email.trim(),
|
||||||
draggable: true,
|
password: formData.password,
|
||||||
progress: undefined,
|
confirmPassword: formData.confirmPassword,
|
||||||
theme: "light",
|
};
|
||||||
transition: Bounce,
|
|
||||||
});
|
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 (
|
return (
|
||||||
<div>
|
<div className="bg-white p-5 rounded-lg w-full max-w-lg">
|
||||||
<form action="#" onSubmit={(e) => {
|
<div className="text-center mb-2">
|
||||||
e.preventDefault();
|
<h2 className="text-2xl font-bold text-gray-800">Create an Account</h2>
|
||||||
handleFormRegisterClick();
|
</div>
|
||||||
}}>
|
|
||||||
<h1>Registration</h1>
|
|
||||||
{/* <div className="input-box">
|
|
||||||
<input type="text" placeholder="Username" required />
|
|
||||||
<i className='bx bxs-user'></i>
|
|
||||||
</div> */}
|
|
||||||
<div className="fl-container flex gap-2">
|
|
||||||
<div className="input-box">
|
|
||||||
<input
|
|
||||||
onChange={(e) => setForm({ ...formData, firstName: e.target.value })}
|
|
||||||
type="text" placeholder="First Name" pattern="^(?!\s*$).+"
|
|
||||||
required
|
|
||||||
title="Input cannot be empty or just spaces"
|
|
||||||
/>
|
|
||||||
<i className='bx bxs-envelope' ></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="input-box">
|
<div className="flex justify-center">
|
||||||
<input
|
<OAuthGoogle isLogin={false} />
|
||||||
onChange={(e) => setForm({ ...formData, lastName: e.target.value })}
|
</div>
|
||||||
type="text"
|
|
||||||
placeholder="Last Name"
|
<div className="flex items-center my-1.5">
|
||||||
pattern="^(?!\s*$).+"
|
<hr className="flex-1 border-gray-300" />
|
||||||
required
|
<span className="px-4 text-gray-500 text-sm">or</span>
|
||||||
title="Input cannot be empty or just spaces" />
|
<hr className="flex-1 border-gray-300" />
|
||||||
<i className='bx bxs-envelope' ></i>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Full Name
|
||||||
|
</label>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.firstName}
|
||||||
|
onChange={(e) => 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 && (
|
||||||
|
<p className="text-red-500 text-xs mt-0.5">{errors.firstName}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.lastName}
|
||||||
|
onChange={(e) => 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 && (
|
||||||
|
<p className="text-red-500 text-xs mt-0.5">{errors.lastName}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="input-box">
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Email Address
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
onChange={(e) => setForm({ ...formData, email: e.target.value })}
|
type="email"
|
||||||
type="email" placeholder="Email" required />
|
value={formData.email}
|
||||||
<i className='bx bxs-envelope' ></i>
|
onChange={(e) => handleInputChange("email", e.target.value)}
|
||||||
</div>
|
placeholder="Enter your email"
|
||||||
<div className="input-box">
|
className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none ${errors.email
|
||||||
<input
|
? "border-red-500 focus:ring-red-500"
|
||||||
onChange={(e) => setForm({ ...formData, password: e.target.value })}
|
: "border-gray-300 focus:ring-blue-500"
|
||||||
type="password" placeholder="Password"
|
}`}
|
||||||
pattern="^.{8,}$"
|
disabled={isLoading}
|
||||||
title="Password must be at least 8 digits"
|
|
||||||
required />
|
|
||||||
<i className='bx bxs-lock-alt' ></i>
|
|
||||||
</div>
|
|
||||||
<div className="input-box">
|
|
||||||
<input
|
|
||||||
onChange={(e) => setForm({ ...formData, confirmPassword: e.target.value })}
|
|
||||||
type="password"
|
|
||||||
placeholder="Confirm Password"
|
|
||||||
style={{
|
|
||||||
border: (formData.confirmPassword != "" && formData.password !== formData.confirmPassword) ? "1px solid red" : "none"
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<i className='bx bxs-lock-alt' ></i>
|
{errors.email && (
|
||||||
|
<p className="text-red-500 text-sm mt-1">{errors.email}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit"
|
<div>
|
||||||
className={`btn ${(formData.email != "" && formData.password != "" && formData.password === formData.confirmPassword) ? "opacity-100" : "opacity-60"} transition-all duration-150`}>
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Register
|
Password
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
value={formData.password}
|
||||||
|
onChange={(e) => handleInputChange("password", e.target.value)}
|
||||||
|
placeholder="Enter your password"
|
||||||
|
className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none pr-12 ${errors.password
|
||||||
|
? "border-red-500 focus:ring-red-500"
|
||||||
|
: "border-gray-300 focus:ring-blue-500"
|
||||||
|
}`}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{showPassword ? <FiEyeOff size={20} /> : <BsEye size={20} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{errors.password && (
|
||||||
|
<p className="text-red-500 text-sm mt-1">{errors.password}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Confirm Password
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showConfirmPassword ? "text" : "password"}
|
||||||
|
value={formData.confirmPassword}
|
||||||
|
onChange={(e) => 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}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{showConfirmPassword ? <FiEyeOff size={20} /> : <BsEye size={20} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{errors.confirmPassword && (
|
||||||
|
<p className="text-red-500 text-sm mt-1">{errors.confirmPassword}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleFormRegisterClick}
|
||||||
|
disabled={isLoading}
|
||||||
|
className={`w-full py-2 rounded-lg font-medium transition-colors ${isLoading
|
||||||
|
? "bg-gray-400 cursor-not-allowed"
|
||||||
|
: "bg-blue-500 hover:bg-blue-600 text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isLoading ? "Creating Account..." : "Create Account"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p>or register with social platforms</p>
|
<div className="text-center text-sm text-gray-600">
|
||||||
<div className="social-icons">
|
Already have an Account?{" "}
|
||||||
<OAuthGoogle isLogin={false} />
|
<button
|
||||||
|
onClick={onSwitchToLogin}
|
||||||
|
className="text-blue-500 font-medium hover:underline"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p>Already! have an Account. <Link to={'/login'} className="text-blue-400">Login</Link></p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default RegisterForm
|
export default RegisterForm
|
||||||
|
|
|
||||||
|
|
@ -1,87 +1,82 @@
|
||||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import "../styles/style.css";
|
||||||
import "../styles/style.css"
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import LoginForm from "./LoginForm";
|
import LoginForm from "./LoginForm";
|
||||||
import RegisterForm from "./RegisterForm";
|
import RegisterForm from "./RegisterForm";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { AppDispatch } from "../../store/store";
|
import { AppDispatch } from "../../store/store";
|
||||||
import { fetchUserDetails } from "../../store/slices/userSlice";
|
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 navigate = useNavigate();
|
||||||
|
const location = useLocation(); // Hook to get the current URL path
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
// Sync isLogin state with the current route
|
||||||
dispatch(fetchUserDetails());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
|
||||||
const elementRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pg === 'login') {
|
const path = location.pathname;
|
||||||
elementRef.current?.classList.add('active')
|
if (path === "/login") {
|
||||||
setTimeout(() => {
|
setIsLogin(true);
|
||||||
elementRef.current?.classList.remove('active')
|
} else if (path === "/register") {
|
||||||
}, 0)
|
setIsLogin(false);
|
||||||
} else {
|
|
||||||
setTimeout(() => {
|
|
||||||
elementRef.current?.classList.add('active')
|
|
||||||
}, 0)
|
|
||||||
}
|
}
|
||||||
}, [])
|
}, [location.pathname]);
|
||||||
|
|
||||||
// const [activeToggle, setActiveToggle] = useState<string>('');
|
const handleLogin = (loginData) => {
|
||||||
|
console.log("Login data:", loginData);
|
||||||
|
// Your login API call logic here
|
||||||
|
};
|
||||||
|
|
||||||
// const [page, setPage] = useState(pg || 'login');
|
const handleRegister = (registerData) => {
|
||||||
function handleToggleForm(val: string) {
|
console.log("Register data:", registerData);
|
||||||
navigate(`/${val}`);
|
// Your register API call logic here
|
||||||
// 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])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="max-h-screen bg-white flex">
|
||||||
<div className="auth-ui-con main-container-l">
|
{/* Left Side - Branding */}
|
||||||
<div ref={elementRef} className={`container-l`}>
|
<div className="hidden lg:flex lg:w-1/2 bg-[var(--primary)] text-white p-12">
|
||||||
<div className={`form-box login flex-col`}>
|
<div className="flex flex-col items-center justify-center text-center w-full h-full gap-6">
|
||||||
<LoginForm />
|
{/* Logos */}
|
||||||
|
<div className="flex flex-col items-center gap-2 space-y-3">
|
||||||
|
<img src={SMELogo} alt="SME Logo" className="h-10 w-auto mt-10" />
|
||||||
|
<img
|
||||||
|
src={LoginRegisterImg}
|
||||||
|
alt="Illustration"
|
||||||
|
className="h-[50vh] w-auto object-contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`form-box register flex-col`}>
|
{/* Text Section */}
|
||||||
<RegisterForm />
|
<div className="flex flex-col items-center max-w-md gap-4">
|
||||||
</div>
|
<h2 className="text-xl font-bold">Create, eSign, and Connect!</h2>
|
||||||
|
<p className="text-blue-100 text-xs leading-relaxed">
|
||||||
<div className="toggle-box">
|
A Powerful eSign solution for Salesforce, powered by Adobe Advanced
|
||||||
<div className="toggle-panel toggle-left">
|
Trust List (AATL), to elevate your digital signature experience and
|
||||||
<h1>Sign Made Easy</h1>
|
foster agreements closures.
|
||||||
<p>Don't have an account?</p>
|
</p>
|
||||||
<button className="btn register-btn" onClick={() => handleToggleForm("register")}>Register</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="toggle-panel toggle-right">
|
|
||||||
<h1>Welcome Back!</h1>
|
|
||||||
<p>Already have an account?</p>
|
|
||||||
<button className="btn login-btn" onClick={() => handleToggleForm("login")}>Login</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)
|
{/* Right Side - Form */}
|
||||||
|
<div className="flex-1 flex items-center justify-center p-8 lg:p-12">
|
||||||
|
{isLogin ? (
|
||||||
|
<LoginForm
|
||||||
|
onLogin={handleLogin}
|
||||||
|
onSwitchToRegister={() => navigate("/register")} // Navigate to /register
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<RegisterForm
|
||||||
|
onRegister={handleRegister}
|
||||||
|
onSwitchToLogin={() => navigate("/login")} // Navigate to /login
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UserAuthUI
|
export default UserAuthUI;
|
||||||
|
|
@ -3,7 +3,7 @@ import UserAuthUI from "../auth/UsertAuthUI"
|
||||||
function Login() {
|
function Login() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-center items-center w-full h-[100vh]">
|
<div className="">
|
||||||
<UserAuthUI pg={"login"} />
|
<UserAuthUI pg={"login"} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,14 @@ import { useParams } from 'react-router-dom';
|
||||||
import { toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice';
|
import { toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice';
|
||||||
|
|
||||||
|
|
||||||
const SelectedDocuments = ({transactionTemplateToggle}) => {
|
const SelectedDocuments = ({ transactionTemplateToggle }) => {
|
||||||
// let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
// let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||||
|
|
||||||
// const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
// const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
// await sleep(5000);
|
// await sleep(5000);
|
||||||
|
|
||||||
const documents = useSelector((state: RootState) => state.documents.documents);
|
const documents = useSelector((state: RootState) => state.documents.documents);
|
||||||
|
const docCount = documents.length;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
|
@ -50,7 +51,7 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
|
||||||
{ name: 'Dropbox', icon: <FaDropbox /> },
|
{ name: 'Dropbox', icon: <FaDropbox /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleUpload = (filesToUpload: File[]) => {
|
const handleUpload = (filesToUpload: File[]) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
filesToUpload.forEach(file => {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Upload Card First */}
|
{/* Upload Card First */}
|
||||||
<div
|
<div className='space-y-3'>
|
||||||
id='upload-doc-step-one'
|
<div id='upload-doc-step-one' className="grid grid-cols-5 gap-2">
|
||||||
className="flex flex-wrap gap-3">
|
{/* Upload Document Card - Always First */}
|
||||||
{/* Uploaded documents */}
|
|
||||||
<div
|
<div
|
||||||
onClick={() => setModalOpen(true)}
|
onClick={() => 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`}
|
||||||
>
|
>
|
||||||
<FaPlus className="text-[var(--primary)] text-3xl mb-2" />
|
<FaPlus className="text-[var(--primary)] text-3xl mb-2" />
|
||||||
<p className="text-sm text-gray-600 text-center px-2">Upload Document</p>
|
<p className="text-sm text-gray-600 text-center px-2">Upload Document</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Document Show */}
|
{/* First Row Documents */}
|
||||||
<ReactSortable
|
<ReactSortable
|
||||||
className="flex flex-wrap gap-3"
|
className="contents" // Use 'contents' to make children participate in parent grid
|
||||||
list={documents.map(doc => ({ ...doc }))}
|
list={documents.slice(0, firstRowDocCount).map(doc => ({ ...doc }))}
|
||||||
setList={(newState) => dispatch(setDocuments(newState))}
|
setList={(newState) => {
|
||||||
|
const updatedDocs = [
|
||||||
|
...newState,
|
||||||
|
...documents.slice(firstRowDocCount)
|
||||||
|
];
|
||||||
|
dispatch(setDocuments(updatedDocs));
|
||||||
|
}}
|
||||||
handle=".drag-handle"
|
handle=".drag-handle"
|
||||||
animation={200}
|
animation={200}
|
||||||
ghostClass="sortable-ghost"
|
ghostClass="sortable-ghost"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
>
|
>
|
||||||
{documents.map((doc) => (
|
{documents.slice(0, firstRowDocCount).map((doc) => (
|
||||||
<DocumentCard key={doc.id} id={doc.id} name={doc.name} isCompleted={("isCompleted" in doc || ("status" in doc && doc.status === "UPLOADED")) ? true : false} transactionTemplateToggle={transactionTemplateToggle}/>
|
<div key={doc.id} className="col-span-1">
|
||||||
|
<DocumentCard
|
||||||
|
id={doc.id}
|
||||||
|
name={doc.name}
|
||||||
|
isCompleted={
|
||||||
|
("isCompleted" in doc || ("status" in doc && doc.status === "UPLOADED"))
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
transactionTemplateToggle={transactionTemplateToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</ReactSortable>
|
</ReactSortable>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Additional Rows - Remaining Documents (5th, 6th, 7th, etc.) */}
|
||||||
|
{remainingDocs > 0 && (
|
||||||
|
<ReactSortable
|
||||||
|
className="grid grid-cols-5 gap-2"
|
||||||
|
list={documents.slice(firstRowDocCount).map(doc => ({ ...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) => (
|
||||||
|
<div key={doc.id} className="col-span-1">
|
||||||
|
<DocumentCard
|
||||||
|
id={doc.id}
|
||||||
|
name={doc.name}
|
||||||
|
isCompleted={
|
||||||
|
("isCompleted" in doc || ("status" in doc && doc.status === "UPLOADED"))
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
transactionTemplateToggle={transactionTemplateToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ReactSortable>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-100/60 backdrop-blur-sm px-4">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-100/60 backdrop-blur-sm px-4">
|
||||||
|
|
@ -181,6 +253,10 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,13 @@ import RightSideBar, { MASKTYPE, TEXT } from './rightSideBar/RightSideBar';
|
||||||
import { DroppedItem, TagsTypes } from '../../../utlity/types';
|
import { DroppedItem, TagsTypes } from '../../../utlity/types';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { IoMdClose } from 'react-icons/io';
|
|
||||||
|
|
||||||
import { fetchRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice';
|
import { fetchRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice';
|
||||||
import { fetchDocuments } from '../../../store/slices/documentSlice';
|
import { fetchDocuments } from '../../../store/slices/documentSlice';
|
||||||
import { FaQuestionCircle } from 'react-icons/fa';
|
import { FaQuestionCircle } from 'react-icons/fa';
|
||||||
import typeToIconMap from '../../../helper/iconMap';
|
import typeToIconMap from '../../../helper/iconMap';
|
||||||
|
|
||||||
import { IoAdd } from 'react-icons/io5';
|
|
||||||
import Loader from '../../dashboard/Loader';
|
import Loader from '../../dashboard/Loader';
|
||||||
// import { TRANSACTION_TEMPLATE_TOGGLE } from "../../../store/slices/selectedRecipientsSlice";
|
import { hideLoader } from '../../../store/slices/loaderSlice';
|
||||||
import { handleGroupBorderPosition } from '../../../utlity/tagGroupConUtils';
|
|
||||||
import { hideLoader, showLoader } from '../../../store/slices/loaderSlice';
|
|
||||||
import { IsSendInterface } from './ProcessStepTwo';
|
|
||||||
import DroppedItemsTagsCon from './DroppedItemsTagsCon';
|
import DroppedItemsTagsCon from './DroppedItemsTagsCon';
|
||||||
|
|
||||||
export interface DroppedItemsObj {
|
export interface DroppedItemsObj {
|
||||||
|
|
@ -73,6 +67,44 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
||||||
const [droppedItems, setDroppedItems] = useState<DroppedItemsObj>({});
|
const [droppedItems, setDroppedItems] = useState<DroppedItemsObj>({});
|
||||||
|
|
||||||
|
|
||||||
|
interface HotKeysObj {
|
||||||
|
isActive: boolean;
|
||||||
|
key: string | null;
|
||||||
|
tagId?: string | null;
|
||||||
|
hotKey?: string | null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// const hotKeysObjInitial: HotKeysObj = {
|
||||||
|
// "CtrlA": {
|
||||||
|
// enabled: false,
|
||||||
|
// key: null
|
||||||
|
// },
|
||||||
|
// "Delete": {
|
||||||
|
// enabled: false,
|
||||||
|
// key: null
|
||||||
|
// },
|
||||||
|
// "CtrlC": {
|
||||||
|
// enabled: false,
|
||||||
|
// key: null,
|
||||||
|
// tagId: null
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
const hotKeysObjInitial: HotKeysObj = {
|
||||||
|
isActive: false,
|
||||||
|
key: null,
|
||||||
|
tagId: null,
|
||||||
|
hotKey: null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const [hotKeysObj, setHotKeysObj] = useState<HotKeysObj>(hotKeysObjInitial);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
|
selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
|
||||||
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
|
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
|
||||||
|
|
@ -97,105 +129,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// let updatedTags = { ...recTags }
|
|
||||||
// let tagsData = []
|
|
||||||
|
|
||||||
// for (const dropPageIds of Object.keys(updatedTags)) {
|
|
||||||
|
|
||||||
// for (const dropTag of updatedTags[dropPageIds]) {
|
|
||||||
// let mask = null
|
|
||||||
|
|
||||||
// // console.log(dropTag, "line 132")
|
|
||||||
|
|
||||||
// // if (dropTag.subType?.toLowerCase() === "password") {
|
|
||||||
// // console.log(dropTag)
|
|
||||||
// mask = {
|
|
||||||
// "type": dropTag.maskType ? `${dropTag.maskType}`.toUpperCase() : null,
|
|
||||||
// "character": dropTag.maskCharacter ? `${dropTag.maskCharacter}`.toUpperCase() : null
|
|
||||||
// }
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // console.log(dropTag, "line 204")
|
|
||||||
|
|
||||||
// const isDeleted = "isDeleted" in dropTag ? dropTag.isDeleted : false
|
|
||||||
|
|
||||||
// const updatedTagData = {
|
|
||||||
// "id": "id" in dropTag ? dropTag.id : null,
|
|
||||||
// "label": dropTag.label,
|
|
||||||
// "dimensions": dropTag.dimensions,
|
|
||||||
// "type": dropTag.type.toUpperCase(),
|
|
||||||
// // "subType": (dropTag.subType ? dropTag.subType.toUpperCase() : null),
|
|
||||||
// "minLength": "minLength" in dropTag ? dropTag.minLength : 0,
|
|
||||||
// "maxLength": "maxLength" in dropTag ? dropTag.maxLength : 0,
|
|
||||||
// "error": ("error" in dropTag && dropTag.error) ? `${dropTag.error}`.toUpperCase() : "error",
|
|
||||||
// "regex": "regex" in dropTag ? dropTag.regex : []
|
|
||||||
// // }
|
|
||||||
// ,
|
|
||||||
// "documentId": dropTag.documentId,
|
|
||||||
// "options": ("options" in dropTag) ? dropTag.options : [],
|
|
||||||
// "pageIndex": dropTag.pageIndex,
|
|
||||||
// "recipientId": dropTag.recipientId,
|
|
||||||
// "styles": "styles" in dropTag ? dropTag.styles : {
|
|
||||||
// "font": "",
|
|
||||||
// "italic": false,
|
|
||||||
// "bold": false,
|
|
||||||
// "underline": false,
|
|
||||||
// "color": "black",
|
|
||||||
// "textSize": 0
|
|
||||||
// },
|
|
||||||
// "required": (dropTag.type.toUpperCase() !== "SIGNATURE" && dropTag.type.toUpperCase() !== "INITIALS") ? dropTag.required : true,
|
|
||||||
// // "required": "required" in dropTag ? dropTag.required : ,
|
|
||||||
// "update": "update" in dropTag ? dropTag.update : false,
|
|
||||||
// "readOnly": false,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // if ("format" in dropTag) {
|
|
||||||
// // updatedTagData.format = `${dropTag.subType}`.toUpperCase();
|
|
||||||
// // updatedTagData.dateFormat = dropTag.format;
|
|
||||||
// // updatedTagData.currentTimeStamp = dropTag.currentTimeStamp
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// if("format" in dropTag && dropTag.format) {
|
|
||||||
// updatedTagData.subType = dropTag.format.toLowerCase();
|
|
||||||
// updatedTagData.format = dropTag.dateFormat;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (`${dropTag.type}`.toLowerCase() === "number") {
|
|
||||||
// updatedTagData.decimalPlace = dropTag.decimalPlace
|
|
||||||
// updatedTagData.isCurrency = dropTag.isCurrency
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (dropTag.type === "TEXT") {
|
|
||||||
// // updatedTagData.multiLine = dropTag.subType === "paragraph"
|
|
||||||
|
|
||||||
// if(dropTag.multiLine) {
|
|
||||||
// updatedTagData.subtype = TEXT.PARAGRAPH
|
|
||||||
// } else {
|
|
||||||
// updatedTagData.subtype = TEXT.SINGLE_LINE
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if ("maskType" in dropTag) {
|
|
||||||
// updatedTagData.maskCharacter = mask.character
|
|
||||||
// updatedTagData.maskType = mask.type
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // if ("required" in updatedTagData && !updatedTagData.required) delete updatedTagData.required;
|
|
||||||
|
|
||||||
// if (updatedTagData.subType === 'SINGLE SELECT') updatedTagData.subType = "SINGLE_SELECT"
|
|
||||||
// if (updatedTagData.subType === 'MULTI SELECT') updatedTagData.subType = "MULTI_SELECT"
|
|
||||||
|
|
||||||
// if (updatedTagData.options && !updatedTagData.options.length) delete updatedTagData.options;
|
|
||||||
|
|
||||||
// if (!updatedTagData.id) delete updatedTagData.id;
|
|
||||||
|
|
||||||
// if (!isDeleted) {
|
|
||||||
// // sendTags.mod.push(updatedTagData);
|
|
||||||
// tagsData.push(updatedTagData)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
setDroppedItems(recTags)
|
setDroppedItems(recTags)
|
||||||
// Handle your response here
|
// Handle your response here
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -293,104 +226,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
||||||
if (Object.keys(droppedItems).length === 0) return;
|
if (Object.keys(droppedItems).length === 0) return;
|
||||||
if (!save && isSend === null) return;
|
if (!save && isSend === null) return;
|
||||||
|
|
||||||
// !save && dispatch(showLoader("Loading ..."))
|
|
||||||
|
|
||||||
// let updatedTags = { ...droppedItems }
|
|
||||||
|
|
||||||
// const sendTags = {
|
|
||||||
// "mod": [],
|
|
||||||
// "remove": []
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let tagsData = []
|
|
||||||
|
|
||||||
// const modified = []
|
|
||||||
// const remove = []
|
|
||||||
|
|
||||||
// console.log(updatedTags, "updatedTags");
|
|
||||||
// for (const dropPageIds of Object.keys(updatedTags)) {
|
|
||||||
// for (const dropTag of updatedTags[dropPageIds]) {
|
|
||||||
// let mask = null
|
|
||||||
|
|
||||||
// // console.log(dropTag, "line 132")
|
|
||||||
|
|
||||||
// // if (dropTag.subType?.toLowerCase() === "password") {
|
|
||||||
// // console.log(dropTag)
|
|
||||||
// mask = {
|
|
||||||
// "type": dropTag.maskType ? `${dropTag.maskType}`.toUpperCase() : null,
|
|
||||||
// "character": dropTag.maskCharacter ? `${dropTag.maskCharacter}`.toUpperCase() : null
|
|
||||||
// }
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // console.log(dropTag, "line 204")
|
|
||||||
|
|
||||||
// const isDeleted = "isDeleted" in dropTag ? dropTag.isDeleted : false
|
|
||||||
|
|
||||||
// const updatedTagData = {
|
|
||||||
// "id": "id" in dropTag ? dropTag.id : null,
|
|
||||||
// "label": dropTag.label,
|
|
||||||
// "dimensions": dropTag.dimensions,
|
|
||||||
// "type": dropTag.type.toUpperCase(),
|
|
||||||
// // "subType": (dropTag.subType ? dropTag.subType.toUpperCase() : null),
|
|
||||||
// "minLength": "minLength" in dropTag ? dropTag.minLength : 0,
|
|
||||||
// "maxLength": "maxLength" in dropTag ? dropTag.maxLength : 0,
|
|
||||||
// "error": ("error" in dropTag && dropTag.error) ? `${dropTag.error}`.toUpperCase() : "error",
|
|
||||||
// "regex": "regex" in dropTag ? dropTag.regex : []
|
|
||||||
// // }
|
|
||||||
// ,
|
|
||||||
// "documentId": dropTag.documentId,
|
|
||||||
// "options": ("options" in dropTag) ? dropTag.options : [],
|
|
||||||
// "pageIndex": dropTag.pageIndex,
|
|
||||||
// "recipientId": dropTag.recipientId,
|
|
||||||
// "styles": "styles" in dropTag ? dropTag.styles : {
|
|
||||||
// "font": "",
|
|
||||||
// "italic": false,
|
|
||||||
// "bold": false,
|
|
||||||
// "underline": false,
|
|
||||||
// "color": "black",
|
|
||||||
// "textSize": 0
|
|
||||||
// },
|
|
||||||
// "required": (dropTag.type.toUpperCase() !== "SIGNATURE" && dropTag.type.toUpperCase() !== "INITIALS") ? dropTag.required : true,
|
|
||||||
// // "required": "required" in dropTag ? dropTag.required : ,
|
|
||||||
// "update": "update" in dropTag ? dropTag.update : false,
|
|
||||||
// "readOnly": false,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if ("format" in dropTag) {
|
|
||||||
// updatedTagData.format = `${dropTag.subType}`.toUpperCase();
|
|
||||||
// updatedTagData.dateFormat = dropTag.format;
|
|
||||||
// updatedTagData.currentTimeStamp = dropTag.currentTimeStamp
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (`${dropTag.type}`.toLowerCase() === "number") {
|
|
||||||
// updatedTagData.decimalPlace = dropTag.decimalPlace
|
|
||||||
// updatedTagData.isCurrency = dropTag.isCurrency
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (updatedTagData.type === "TEXT") {
|
|
||||||
// updatedTagData.multiLine = dropTag.subType === "paragraph"
|
|
||||||
// if (dropTag.subType === "password") {
|
|
||||||
// updatedTagData.maskCharacter = mask.character
|
|
||||||
// updatedTagData.maskType = mask.type
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // if ("required" in updatedTagData && !updatedTagData.required) delete updatedTagData.required;
|
|
||||||
|
|
||||||
// if (updatedTagData.subType === 'SINGLE SELECT') updatedTagData.subType = "SINGLE_SELECT"
|
|
||||||
// if (updatedTagData.subType === 'MULTI SELECT') updatedTagData.subType = "MULTI_SELECT"
|
|
||||||
|
|
||||||
// if (updatedTagData.options && !updatedTagData.options.length) delete updatedTagData.options;
|
|
||||||
|
|
||||||
// if (!updatedTagData.id) delete updatedTagData.id;
|
|
||||||
|
|
||||||
// if (!isDeleted) {
|
|
||||||
// // sendTags.mod.push(updatedTagData);
|
|
||||||
// tagsData.push(updatedTagData)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
let updatedTags: any = [];
|
let updatedTags: any = [];
|
||||||
|
|
||||||
for (const dropPagesId of Object.keys(droppedItems)) {
|
for (const dropPagesId of Object.keys(droppedItems)) {
|
||||||
|
|
@ -415,17 +250,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
||||||
}, [isSend])
|
}, [isSend])
|
||||||
// Save Tags - END
|
// Save Tags - END
|
||||||
|
|
||||||
// handleTransactionSaving - START
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// const interval = setInterval(async () => {
|
|
||||||
// setTransactionSaving(true)
|
|
||||||
// }, 8000)
|
|
||||||
// return () => clearInterval(interval);
|
|
||||||
// }, [droppedItems])
|
|
||||||
|
|
||||||
// handleTransactionSaving - END
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (transactionSaving) {
|
if (transactionSaving) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -494,12 +318,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
||||||
prepData.group = dragElemData.group;
|
prepData.group = dragElemData.group;
|
||||||
prepData.isGroupExist = false;
|
prepData.isGroupExist = false;
|
||||||
|
|
||||||
// if(uniqGroupIds.includes(dragElemData.group)) {
|
|
||||||
// prepData.isGroupExist = true;
|
|
||||||
// } else {
|
|
||||||
// prepData.isGroupExist = false;
|
|
||||||
// setUniqGroupIds([...uniqGroupIds, dragElemData.group]);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prepData.type === "temporal") {
|
if (prepData.type === "temporal") {
|
||||||
|
|
@ -519,301 +337,104 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
||||||
prepData],
|
prepData],
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("data311", dataToSet)
|
|
||||||
setDroppedItems(dataToSet);
|
setDroppedItems(dataToSet);
|
||||||
setDroppedItemsCounter((prev) => prev + 1);
|
setDroppedItemsCounter((prev) => prev + 1);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const tagIndex = key in droppedItems ? droppedItems[key].length : 0;
|
const tagIndex = key in droppedItems ? droppedItems[key].length : 0;
|
||||||
|
console.log(tagIndex, "line 307", key);
|
||||||
|
|
||||||
setSelectedTag({ key, tagIndex })
|
setSelectedTag({ key, tagIndex })
|
||||||
}, 0)
|
}, 0)
|
||||||
};
|
};
|
||||||
// HANDLE DROP - END
|
// HANDLE DROP - END
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// HANDLE EXPAND TAG - START
|
|
||||||
const handleExpandTag = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
|
|
||||||
console.log("HANDLE EXPAND TAG - START")
|
|
||||||
event.preventDefault(); // Prevent text selection issues
|
|
||||||
|
|
||||||
const expandingElemIcon = event.currentTarget;
|
|
||||||
const targetElement = expandingElemIcon.nextElementSibling?.parentElement;
|
|
||||||
const targetContainer = targetElement?.parentElement;
|
|
||||||
|
|
||||||
if (!targetContainer || !targetElement) return;
|
|
||||||
|
|
||||||
const elementRect = targetElement.getBoundingClientRect();
|
|
||||||
|
|
||||||
const startX = event.clientX;
|
|
||||||
const startY = event.clientY;
|
|
||||||
|
|
||||||
const startWidth = elementRect.width;
|
|
||||||
const startHeight = elementRect.height;
|
|
||||||
|
|
||||||
const handleMouseMove = (event: MouseEvent) => {
|
|
||||||
|
|
||||||
let newWidth = startWidth + (event.clientX - startX);
|
|
||||||
let newHeight = startHeight + (event.clientY - startY);
|
|
||||||
|
|
||||||
if (newHeight > 200 || newWidth > 400) {
|
|
||||||
if (newHeight > 200) {
|
|
||||||
newHeight = 200
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newWidth > 400) {
|
|
||||||
newWidth = 400
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the new width & height
|
|
||||||
setDroppedItems({
|
|
||||||
...droppedItems,
|
|
||||||
[key]: Array.isArray(droppedItems[key]) // Ensure it's an array
|
|
||||||
? droppedItems[key].map((item, i) =>
|
|
||||||
i === index ? { ...item, dimensions: { ...item.dimensions, width: newWidth, height: newHeight }, update: true } : item
|
|
||||||
)
|
|
||||||
: [], // Provide a fallback in case it's undefined or not an array
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
|
||||||
document.removeEventListener("pointermove", handleMouseMove);
|
|
||||||
document.removeEventListener("pointerup", handleMouseUp);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("pointermove", handleMouseMove);
|
|
||||||
document.addEventListener("pointerup", handleMouseUp);
|
|
||||||
};
|
|
||||||
// HANDLE EXPAND TAG - END
|
|
||||||
|
|
||||||
|
|
||||||
// Helper function to remove group border elements from DOM
|
|
||||||
const removeGroupBorders = (groupId: string) => {
|
|
||||||
console.log(`Removing borders for group: ${groupId}`);
|
|
||||||
|
|
||||||
const borderPositions = ['top', 'right', 'bottom', 'left'];
|
|
||||||
|
|
||||||
borderPositions.forEach(position => {
|
|
||||||
const borderElement = document.querySelector(`[border-group='${groupId}'][border-pos='${position}']`);
|
|
||||||
if (borderElement && borderElement.parentNode) {
|
|
||||||
borderElement.parentNode.removeChild(borderElement);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// const handleAddRadioClick = (key:string) => {
|
|
||||||
// console.log("Handle Radio Clicled", key)
|
|
||||||
// console.log(droppedItems, "line 525")
|
|
||||||
// }
|
|
||||||
|
|
||||||
const handleIGroupBorderPosition = (groupId: string) => {
|
|
||||||
console.log("handleIGroupBorderPosition for group:", groupId);
|
|
||||||
|
|
||||||
const groupElements: NodeListOf<HTMLElement> = document.querySelectorAll(`div[group="${groupId}"]`);
|
|
||||||
|
|
||||||
if (groupElements.length === 0) {
|
|
||||||
console.log("No group elements found for:", groupId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let targetContainer = groupElements[0].parentElement?.parentElement;
|
|
||||||
|
|
||||||
if (!targetContainer) {
|
|
||||||
console.log("No target container found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerRect = targetContainer.getBoundingClientRect();
|
|
||||||
|
|
||||||
// Use consistent offset values
|
|
||||||
const offsetX = 10;
|
|
||||||
const offsetY = 10;
|
|
||||||
|
|
||||||
// Ensure DOM is fully updated before positioning
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Enhanced handleGroupBorderPosition with better positioning logic
|
|
||||||
const handleGroupBorderPosition = (groupElements: NodeListOf<HTMLElement>, containerRect: DOMRect, offsetX: number, offsetY: number) => {
|
|
||||||
if (!groupElements || groupElements.length === 0) return;
|
|
||||||
|
|
||||||
const groupId = groupElements[0].getAttribute('group');
|
|
||||||
if (!groupId) return;
|
|
||||||
|
|
||||||
// Calculate the bounding box of all group elements
|
|
||||||
let minLeft = Infinity, minTop = Infinity, maxRight = -Infinity, maxBottom = -Infinity;
|
|
||||||
|
|
||||||
groupElements.forEach(element => {
|
|
||||||
const parentElement = element.parentElement;
|
|
||||||
if (!parentElement) return;
|
|
||||||
|
|
||||||
const rect = parentElement.getBoundingClientRect();
|
|
||||||
const relativeLeft = rect.left - containerRect.left;
|
|
||||||
const relativeTop = rect.top - containerRect.top;
|
|
||||||
const relativeRight = relativeLeft + rect.width;
|
|
||||||
const relativeBottom = relativeTop + rect.height;
|
|
||||||
|
|
||||||
minLeft = Math.min(minLeft, relativeLeft);
|
|
||||||
minTop = Math.min(minTop, relativeTop);
|
|
||||||
maxRight = Math.max(maxRight, relativeRight);
|
|
||||||
maxBottom = Math.max(maxBottom, relativeBottom);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add some padding around the group
|
|
||||||
const padding = 10;
|
|
||||||
minLeft -= padding;
|
|
||||||
minTop -= padding;
|
|
||||||
maxRight += padding;
|
|
||||||
maxBottom += padding;
|
|
||||||
|
|
||||||
const width = maxRight - minLeft;
|
|
||||||
const height = maxBottom - minTop;
|
|
||||||
|
|
||||||
// Update or create border elements
|
|
||||||
const borderPositions = ['top', 'right', 'bottom', 'left'];
|
|
||||||
|
|
||||||
borderPositions.forEach(position => {
|
|
||||||
let borderElement = document.querySelector(`[border-group='${groupId}'][border-pos='${position}']`) as HTMLElement;
|
|
||||||
|
|
||||||
if (!borderElement) {
|
|
||||||
// Create border element if it doesn't exist
|
|
||||||
borderElement = document.createElement('div');
|
|
||||||
borderElement.className = `group-border ${position === 'bottom' ? 'bottom-border' : ''}`;
|
|
||||||
borderElement.setAttribute('border-group', groupId);
|
|
||||||
borderElement.setAttribute('border-pos', position);
|
|
||||||
|
|
||||||
// Add the + icon for bottom border
|
|
||||||
if (position === 'bottom') {
|
|
||||||
const addIcon = document.createElement('div');
|
|
||||||
addIcon.innerHTML = '<IoAdd />'; // You might need to adjust this based on your icon system
|
|
||||||
addIcon.onclick = () => {
|
|
||||||
// Find the original item to pass to handleAddGroupItemClicked
|
|
||||||
const originalItem = droppedItems[key]?.find(item => 'group' in item && item.group === groupId);
|
|
||||||
if (originalItem) {
|
|
||||||
handleAddGroupItemClicked(key, originalItem);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
borderElement.appendChild(addIcon);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append to container
|
|
||||||
groupElements[0].parentElement?.parentElement?.appendChild(borderElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position the border element
|
|
||||||
borderElement.style.position = 'absolute';
|
|
||||||
borderElement.style.pointerEvents = position === 'bottom' ? 'auto' : 'none';
|
|
||||||
|
|
||||||
switch (position) {
|
|
||||||
case 'top':
|
|
||||||
borderElement.style.left = `${minLeft}px`;
|
|
||||||
borderElement.style.top = `${minTop}px`;
|
|
||||||
borderElement.style.width = `${width}px`;
|
|
||||||
borderElement.style.height = '2px';
|
|
||||||
break;
|
|
||||||
case 'right':
|
|
||||||
borderElement.style.left = `${maxRight - 2}px`;
|
|
||||||
borderElement.style.top = `${minTop}px`;
|
|
||||||
borderElement.style.width = '2px';
|
|
||||||
borderElement.style.height = `${height}px`;
|
|
||||||
break;
|
|
||||||
case 'bottom':
|
|
||||||
borderElement.style.left = `${minLeft}px`;
|
|
||||||
borderElement.style.top = `${maxBottom - 2}px`;
|
|
||||||
borderElement.style.width = `${width}px`;
|
|
||||||
borderElement.style.height = '2px';
|
|
||||||
break;
|
|
||||||
case 'left':
|
|
||||||
borderElement.style.left = `${minLeft}px`;
|
|
||||||
borderElement.style.top = `${minTop}px`;
|
|
||||||
borderElement.style.width = '2px';
|
|
||||||
borderElement.style.height = `${height}px`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddGroupItemClicked = (key: string, item: any) => {
|
|
||||||
console.log("handleAddGroupItemClicked", item);
|
|
||||||
|
|
||||||
const groupId = item.group;
|
|
||||||
|
|
||||||
// Find all existing items in this group to determine the bottom-most position
|
|
||||||
const existingGroupItems = droppedItems[key]?.filter(existingItem =>
|
|
||||||
'group' in existingItem && existingItem.group === groupId
|
|
||||||
) || [];
|
|
||||||
|
|
||||||
const existingGroupItem = droppedItems[key]?.filter(existingItem =>
|
|
||||||
'group' in existingItem && existingItem.group === groupId
|
|
||||||
) || [];
|
|
||||||
|
|
||||||
existingGroupItem[0].options
|
|
||||||
|
|
||||||
// Find the bottom-most item in the group
|
|
||||||
let bottomMostItem = item;
|
|
||||||
if (existingGroupItems.length > 0) {
|
|
||||||
bottomMostItem = existingGroupItems.reduce((lowest, current) => {
|
|
||||||
const currentBottom = current.dimensions.top + current.dimensions.height;
|
|
||||||
const lowestBottom = lowest.dimensions.top + lowest.dimensions.height;
|
|
||||||
return currentBottom > lowestBottom ? current : lowest;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate new position: place below the bottom-most item
|
|
||||||
const spacing = 10; // Adjust spacing as needed
|
|
||||||
const newTop = bottomMostItem.dimensions.top + bottomMostItem.dimensions.height + spacing;
|
|
||||||
|
|
||||||
// Use the same left position as the parent (original) item
|
|
||||||
const newItem = {
|
|
||||||
...item,
|
|
||||||
dimensions: {
|
|
||||||
...item.dimensions,
|
|
||||||
left: item.dimensions.left, // Keep same horizontal position as parent
|
|
||||||
top: newTop // Position below the bottom-most item
|
|
||||||
},
|
|
||||||
isGroupExist: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update the dropped items
|
|
||||||
setDroppedItems({
|
|
||||||
...droppedItems,
|
|
||||||
[key]: [...droppedItems[key], newItem],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update group borders after adding the new item
|
|
||||||
// Use setTimeout to ensure state update is processed
|
|
||||||
setTimeout(() => {
|
|
||||||
handleIGroupBorderPosition(groupId);
|
|
||||||
}, 50); // Slightly longer timeout to ensure DOM updates
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialize group borders for all existing groups after component updates
|
if (!hotKeysObj.isActive) return
|
||||||
const timeoutId = setTimeout(() => {
|
|
||||||
const allGroupElements = document.querySelectorAll('[group]');
|
|
||||||
const groupIds = new Set<string>();
|
|
||||||
|
|
||||||
allGroupElements.forEach((element) => {
|
const docCon = document.getElementById("doc-con");
|
||||||
const groupId = element.getAttribute('group');
|
|
||||||
if (groupId) {
|
switch (hotKeysObj.hotKey) {
|
||||||
groupIds.add(groupId);
|
case "Ctrl+c":
|
||||||
}
|
// Handle Ctrl+C
|
||||||
|
break;
|
||||||
|
case "Ctrl+v":
|
||||||
|
// Handle Ctrl+V
|
||||||
|
break;
|
||||||
|
case "Ctrl+x":
|
||||||
|
// Handle Ctrl+V
|
||||||
|
break;
|
||||||
|
case "Ctrl+z":
|
||||||
|
// Handle Ctrl+V
|
||||||
|
break;
|
||||||
|
case "Ctrl+y":
|
||||||
|
// Handle Ctrl+V
|
||||||
|
break;
|
||||||
|
case "Ctrl+a":
|
||||||
|
docCon?.querySelectorAll("[data-tag-id]").forEach(tag => {
|
||||||
|
tag.classList.add("border-2", "border-slate-800")
|
||||||
|
tag.setAttribute("all-select-group", "true")
|
||||||
|
|
||||||
|
docCon.addEventListener("click", () => {
|
||||||
|
console.log("focus in", tag)
|
||||||
|
tag.classList.remove("border-2", "border-slate-800")
|
||||||
|
tag.removeAttribute("all-select-group")
|
||||||
|
})
|
||||||
|
// docCon.addEventListener("focusout", () => {
|
||||||
|
// console.log("focus out", tag)
|
||||||
|
// tag.classList.remove("border-2", "border-slate-800")
|
||||||
|
// tag.removeAttribute("all-select-group")
|
||||||
|
// })
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
docCon?.querySelectorAll("[all-select-group]").forEach((tag) => {
|
||||||
|
console.log(tag, "TAGID")
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [hotKeysObj])
|
||||||
|
|
||||||
|
const handleDocumentFocus = (key: string) => {
|
||||||
|
const id = key.replace("_", "");
|
||||||
|
const targetCon = document.getElementById(id);
|
||||||
|
|
||||||
|
targetCon?.addEventListener("keydown", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const ctrl = event.ctrlKey;
|
||||||
|
const alt = event.altKey;
|
||||||
|
const metaKey = event.metaKey;
|
||||||
|
const shift = event.shiftKey;
|
||||||
|
const pressedKey = event.key.toLowerCase();
|
||||||
|
|
||||||
|
let hotKey = pressedKey
|
||||||
|
|
||||||
|
|
||||||
|
if (ctrl) {
|
||||||
|
hotKey = "Ctrl+" + pressedKey;
|
||||||
|
} else if (alt) {
|
||||||
|
hotKey = "Alt" + pressedKey;
|
||||||
|
} else if (metaKey) {
|
||||||
|
hotKey = "Meta+" + pressedKey;
|
||||||
|
} else if (shift) {
|
||||||
|
hotKey = "Shift+" + pressedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHotKeysObj({
|
||||||
|
isActive: true,
|
||||||
|
key: pressedKey,
|
||||||
|
tagId: null,
|
||||||
|
hotKey: hotKey
|
||||||
});
|
});
|
||||||
|
|
||||||
groupIds.forEach((groupId) => {
|
});
|
||||||
handleIGroupBorderPosition(groupId);
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
return () => clearTimeout(timeoutId);
|
|
||||||
}, [droppedItems]); // Re-run when droppedItems changes
|
// return () => targetCon?.removeEventListener("keydown");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -824,7 +445,8 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
||||||
selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => {
|
selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => {
|
||||||
return (
|
return (
|
||||||
<div key={indxDoc} className='flex flex-col'>
|
<div key={indxDoc} className='flex flex-col'>
|
||||||
<div className='py-2 text-sm'>
|
<div
|
||||||
|
className='py-2 text-sm'>
|
||||||
<p className='text-center'>{doc.name}</p>
|
<p className='text-center'>{doc.name}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -834,8 +456,9 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
||||||
const key = `${doc.id}_${page.index}`;
|
const key = `${doc.id}_${page.index}`;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
tabIndex={-1}
|
||||||
|
onFocus={() => handleDocumentFocus(key)}
|
||||||
key={indx}
|
key={indx}
|
||||||
|
|
||||||
id={`${doc.id}${indx}`}
|
id={`${doc.id}${indx}`}
|
||||||
onDrop={(e) => handleDrop(e, doc.id, indxDoc, page.index)}
|
onDrop={(e) => handleDrop(e, doc.id, indxDoc, page.index)}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
|
|
@ -844,11 +467,14 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
||||||
|
|
||||||
<DroppedItemsTagsCon
|
<DroppedItemsTagsCon
|
||||||
droppedItems={droppedItems}
|
droppedItems={droppedItems}
|
||||||
key={key}
|
pageIdkey={key}
|
||||||
selectedTag={selectedTag}
|
selectedTag={selectedTag}
|
||||||
|
selectedRecipients={selectedRecipients}
|
||||||
setSelectedTag={setSelectedTag}
|
setSelectedTag={setSelectedTag}
|
||||||
|
setDroppedItems={setDroppedItems}
|
||||||
accountId={accountId}
|
accountId={accountId}
|
||||||
transaction
|
transactionId={transactionId}
|
||||||
|
transactionTemplateToggle={transactionTemplateToggle}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,47 @@
|
||||||
// Functions - START
|
// Functions - START
|
||||||
// handleTagClick
|
// -- handleTagClick
|
||||||
|
// -- handleRemoveTag
|
||||||
|
// -- handleTagMouseDown
|
||||||
|
// -- handleExpandTag
|
||||||
|
// -- removeGroupBorders
|
||||||
|
// -- handleIGroupBorderPosition
|
||||||
|
// -- handleGroupBorderPosition
|
||||||
|
// -- handleAddGroupItemClicked
|
||||||
|
|
||||||
|
import React, { useEffect, useRef } from 'react'
|
||||||
import React from 'react'
|
|
||||||
import { DroppedItemsObj } from './DocumentContainer';
|
import { DroppedItemsObj } from './DocumentContainer';
|
||||||
import typeToIconMap from '../../../helper/iconMap';
|
import typeToIconMap from '../../../helper/iconMap';
|
||||||
import { IoMdClose } from 'react-icons/io';
|
import { IoMdClose } from 'react-icons/io';
|
||||||
import { IoAdd } from 'react-icons/io5';
|
import { IoAdd } from 'react-icons/io5';
|
||||||
import { DroppedItem } from '../../../utlity/types';
|
|
||||||
import { FaQuestionCircle } from 'react-icons/fa';
|
import { FaQuestionCircle } from 'react-icons/fa';
|
||||||
import api from '../../../config/api';
|
import api from '../../../config/api';
|
||||||
|
import { Recipient } from '../../../store/slices/selectedRecipientsSlice';
|
||||||
|
|
||||||
interface DroppedItemsTagsConProps {
|
interface DroppedItemsTagsConProps {
|
||||||
droppedItems: DroppedItemsObj;
|
droppedItems: DroppedItemsObj;
|
||||||
key: string;
|
pageIdkey: string;
|
||||||
selectedTag: DroppedItem
|
selectedTag: { key: string; index: number } | null;
|
||||||
setSelectedTag: React.Dispatch<React.SetStateAction<DroppedItem>>;
|
selectedRecipients: Recipient[];
|
||||||
|
setSelectedTag: React.Dispatch<React.SetStateAction<{ key: string; index: number } | null>>;
|
||||||
setDroppedItems: React.Dispatch<React.SetStateAction<DroppedItemsObj>>;
|
setDroppedItems: React.Dispatch<React.SetStateAction<DroppedItemsObj>>;
|
||||||
|
accountId: string;
|
||||||
|
transactionId: string;
|
||||||
|
transactionTemplateToggle: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems, key, selectedTag, setSelectedTag }) => {
|
const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({
|
||||||
|
droppedItems,
|
||||||
|
pageIdkey,
|
||||||
|
selectedTag,
|
||||||
|
selectedRecipients,
|
||||||
|
setSelectedTag,
|
||||||
|
setDroppedItems,
|
||||||
|
accountId,
|
||||||
|
transactionId,
|
||||||
|
transactionTemplateToggle
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const positionRef = useRef<{ left: number; top: number }>({ left: 0, top: 0 });
|
||||||
|
|
||||||
const handleTagClick = (key: string, index: number) => {
|
const handleTagClick = (key: string, index: number) => {
|
||||||
setSelectedTag({ key, index })
|
setSelectedTag({ key, index })
|
||||||
|
|
@ -127,10 +149,13 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems,
|
||||||
const containerRect = targetContainer.getBoundingClientRect();
|
const containerRect = targetContainer.getBoundingClientRect();
|
||||||
const elementRect = targetElement.getBoundingClientRect();
|
const elementRect = targetElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
|
||||||
// Calculate the initial offset (where the mouse was clicked inside the element)
|
// Calculate the initial offset (where the mouse was clicked inside the element)
|
||||||
const offsetX = event.clientX - elementRect.left;
|
const offsetX = event.clientX - elementRect.left;
|
||||||
const offsetY = event.clientY - elementRect.top;
|
const offsetY = event.clientY - elementRect.top;
|
||||||
|
|
||||||
|
positionRef.current = { left: event.clientX - containerRect.left - offsetX, top: event.clientY - containerRect.top - offsetY };
|
||||||
|
|
||||||
const conScrollWidth = targetContainer?.scrollWidth;
|
const conScrollWidth = targetContainer?.scrollWidth;
|
||||||
const conScrollHeight = targetContainer?.scrollHeight;
|
const conScrollHeight = targetContainer?.scrollHeight;
|
||||||
|
|
||||||
|
|
@ -147,7 +172,7 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems,
|
||||||
handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
|
handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeOut = null
|
// let timeOut = null
|
||||||
const handleMouseMove = (event: MouseEvent) => {
|
const handleMouseMove = (event: MouseEvent) => {
|
||||||
// Get new X and Y positions relative to the container
|
// Get new X and Y positions relative to the container
|
||||||
let left = event.clientX - containerRect.left - offsetX;
|
let left = event.clientX - containerRect.left - offsetX;
|
||||||
|
|
@ -157,15 +182,23 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems,
|
||||||
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width));
|
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width));
|
||||||
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height));
|
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height));
|
||||||
|
|
||||||
|
// Save in ref (fast, no re-render)
|
||||||
|
positionRef.current = { left, top };
|
||||||
|
|
||||||
|
// Update DOM directly
|
||||||
|
targetElement!.style.top = `${top}px`;
|
||||||
|
targetElement!.style.left = `${left}px`;
|
||||||
|
|
||||||
// Apply the movement to the element first
|
// Apply the movement to the element first
|
||||||
setDroppedItems(prevState => ({
|
// setDroppedItems(prevState => ({
|
||||||
...prevState,
|
// ...prevState,
|
||||||
[key]: Array.isArray(prevState[key])
|
// [key]: Array.isArray(prevState[key])
|
||||||
? prevState[key].map((item, i) =>
|
// ? prevState[key].map((item, i) =>
|
||||||
i === index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item
|
// i === index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item
|
||||||
)
|
// )
|
||||||
: [],
|
// : [],
|
||||||
}));
|
// }));
|
||||||
|
|
||||||
// Update group border position during drag if it's a group element
|
// Update group border position during drag if it's a group element
|
||||||
if (groupElements != null && groupElements.length) {
|
if (groupElements != null && groupElements.length) {
|
||||||
// Use a small timeout to ensure DOM is updated before recalculating borders
|
// Use a small timeout to ensure DOM is updated before recalculating borders
|
||||||
|
|
@ -198,6 +231,17 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { left, top } = positionRef.current;
|
||||||
|
setDroppedItems(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
[key]: Array.isArray(prevState[key])
|
||||||
|
? prevState[key].map((item, i) =>
|
||||||
|
i === index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item
|
||||||
|
)
|
||||||
|
: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
document.removeEventListener("pointermove", handleMouseMove);
|
document.removeEventListener("pointermove", handleMouseMove);
|
||||||
document.removeEventListener("pointerup", handleMouseUp);
|
document.removeEventListener("pointerup", handleMouseUp);
|
||||||
};
|
};
|
||||||
|
|
@ -208,10 +252,279 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems,
|
||||||
};
|
};
|
||||||
// HANDLE TAG MOUSE DOWN - END
|
// HANDLE TAG MOUSE DOWN - END
|
||||||
|
|
||||||
|
// HANDLE EXPAND TAG - START
|
||||||
|
const handleExpandTag = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
|
||||||
|
event.preventDefault(); // Prevent text selection issues
|
||||||
|
|
||||||
|
const expandingElemIcon = event.currentTarget;
|
||||||
|
const targetElement = expandingElemIcon.nextElementSibling?.parentElement;
|
||||||
|
const targetContainer = targetElement?.parentElement;
|
||||||
|
|
||||||
|
if (!targetContainer || !targetElement) return;
|
||||||
|
|
||||||
|
const elementRect = targetElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
const startX = event.clientX;
|
||||||
|
const startY = event.clientY;
|
||||||
|
|
||||||
|
const startWidth = elementRect.width;
|
||||||
|
const startHeight = elementRect.height;
|
||||||
|
|
||||||
|
const handleMouseMove = (event: MouseEvent) => {
|
||||||
|
|
||||||
|
let newWidth = startWidth + (event.clientX - startX);
|
||||||
|
let newHeight = startHeight + (event.clientY - startY);
|
||||||
|
|
||||||
|
if (newHeight > 200 || newWidth > 400) {
|
||||||
|
if (newHeight > 200) {
|
||||||
|
newHeight = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newWidth > 400) {
|
||||||
|
newWidth = 400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the new width & height
|
||||||
|
setDroppedItems({
|
||||||
|
...droppedItems,
|
||||||
|
[key]: Array.isArray(droppedItems[key]) // Ensure it's an array
|
||||||
|
? droppedItems[key].map((item, i) =>
|
||||||
|
i === index ? { ...item, dimensions: { ...item.dimensions, width: newWidth, height: newHeight }, update: true } : item
|
||||||
|
)
|
||||||
|
: [], // Provide a fallback in case it's undefined or not an array
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
document.removeEventListener("pointermove", handleMouseMove);
|
||||||
|
document.removeEventListener("pointerup", handleMouseUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("pointermove", handleMouseMove);
|
||||||
|
document.addEventListener("pointerup", handleMouseUp);
|
||||||
|
};
|
||||||
|
// HANDLE EXPAND TAG - END
|
||||||
|
|
||||||
|
const removeGroupBorders = (groupId: string) => {
|
||||||
|
console.log(`Removing borders for group: ${groupId}`);
|
||||||
|
|
||||||
|
const borderPositions = ['top', 'right', 'bottom', 'left'];
|
||||||
|
|
||||||
|
borderPositions.forEach(position => {
|
||||||
|
const borderElement = document.querySelector(`[border-group='${groupId}'][border-pos='${position}']`);
|
||||||
|
if (borderElement && borderElement.parentNode) {
|
||||||
|
borderElement.parentNode.removeChild(borderElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIGroupBorderPosition = (groupId: string) => {
|
||||||
|
const groupElements: NodeListOf<HTMLElement> = document.querySelectorAll(`div[group="${groupId}"]`);
|
||||||
|
|
||||||
|
if (groupElements.length === 0) {
|
||||||
|
console.log("No group elements found for:", groupId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetContainer = groupElements[0].parentElement?.parentElement;
|
||||||
|
|
||||||
|
if (!targetContainer) {
|
||||||
|
console.log("No target container found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerRect = targetContainer.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Use consistent offset values
|
||||||
|
const offsetX = 10;
|
||||||
|
const offsetY = 10;
|
||||||
|
|
||||||
|
// Ensure DOM is fully updated before positioning
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGroupBorderPosition = (groupElements: NodeListOf<HTMLElement>, containerRect: DOMRect, offsetX: number, offsetY: number) => {
|
||||||
|
if (!groupElements || groupElements.length === 0) return;
|
||||||
|
|
||||||
|
const groupId = groupElements[0].getAttribute('group');
|
||||||
|
if (!groupId) return;
|
||||||
|
|
||||||
|
// Calculate the bounding box of all group elements
|
||||||
|
let minLeft = Infinity, minTop = Infinity, maxRight = -Infinity, maxBottom = -Infinity;
|
||||||
|
|
||||||
|
groupElements.forEach(element => {
|
||||||
|
const parentElement = element.parentElement;
|
||||||
|
if (!parentElement) return;
|
||||||
|
|
||||||
|
const rect = parentElement.getBoundingClientRect();
|
||||||
|
const relativeLeft = rect.left - containerRect.left;
|
||||||
|
const relativeTop = rect.top - containerRect.top;
|
||||||
|
const relativeRight = relativeLeft + rect.width;
|
||||||
|
const relativeBottom = relativeTop + rect.height;
|
||||||
|
|
||||||
|
minLeft = Math.min(minLeft, relativeLeft);
|
||||||
|
minTop = Math.min(minTop, relativeTop);
|
||||||
|
maxRight = Math.max(maxRight, relativeRight);
|
||||||
|
maxBottom = Math.max(maxBottom, relativeBottom);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add some padding around the group
|
||||||
|
const padding = 10;
|
||||||
|
minLeft -= padding;
|
||||||
|
minTop -= padding;
|
||||||
|
maxRight += padding;
|
||||||
|
maxBottom += padding;
|
||||||
|
|
||||||
|
const width = maxRight - minLeft;
|
||||||
|
const height = maxBottom - minTop;
|
||||||
|
|
||||||
|
// Update or create border elements
|
||||||
|
const borderPositions = ['top', 'right', 'bottom', 'left'];
|
||||||
|
|
||||||
|
borderPositions.forEach(position => {
|
||||||
|
let borderElement = document.querySelector(`[border-group='${groupId}'][border-pos='${position}']`) as HTMLElement;
|
||||||
|
|
||||||
|
if (!borderElement) {
|
||||||
|
// Create border element if it doesn't exist
|
||||||
|
borderElement = document.createElement('div');
|
||||||
|
borderElement.className = `group-border ${position === 'bottom' ? 'bottom-border' : ''}`;
|
||||||
|
borderElement.setAttribute('border-group', groupId);
|
||||||
|
borderElement.setAttribute('border-pos', position);
|
||||||
|
|
||||||
|
// Add the + icon for bottom border
|
||||||
|
if (position === 'bottom') {
|
||||||
|
const addIcon = document.createElement('div');
|
||||||
|
addIcon.innerHTML = '<IoAdd />'; // You might need to adjust this based on your icon system
|
||||||
|
addIcon.onclick = () => {
|
||||||
|
// Find the original item to pass to handleAddGroupItemClicked
|
||||||
|
const originalItem = droppedItems[key]?.find(item => 'group' in item && item.group === groupId);
|
||||||
|
if (originalItem) {
|
||||||
|
handleAddGroupItemClicked(key, originalItem);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
borderElement.appendChild(addIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append to container
|
||||||
|
groupElements[0].parentElement?.parentElement?.appendChild(borderElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position the border element
|
||||||
|
borderElement.style.position = 'absolute';
|
||||||
|
borderElement.style.pointerEvents = position === 'bottom' ? 'auto' : 'none';
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case 'top':
|
||||||
|
borderElement.style.left = `${minLeft}px`;
|
||||||
|
borderElement.style.top = `${minTop}px`;
|
||||||
|
borderElement.style.width = `${width}px`;
|
||||||
|
borderElement.style.height = '2px';
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
borderElement.style.left = `${maxRight - 2}px`;
|
||||||
|
borderElement.style.top = `${minTop}px`;
|
||||||
|
borderElement.style.width = '2px';
|
||||||
|
borderElement.style.height = `${height}px`;
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
borderElement.style.left = `${minLeft}px`;
|
||||||
|
borderElement.style.top = `${maxBottom - 2}px`;
|
||||||
|
borderElement.style.width = `${width}px`;
|
||||||
|
borderElement.style.height = '2px';
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
borderElement.style.left = `${minLeft}px`;
|
||||||
|
borderElement.style.top = `${minTop}px`;
|
||||||
|
borderElement.style.width = '2px';
|
||||||
|
borderElement.style.height = `${height}px`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddGroupItemClicked = (key: string, item: any) => {
|
||||||
|
const groupId = item.group;
|
||||||
|
|
||||||
|
// Find all existing items in this group to determine the bottom-most position
|
||||||
|
const existingGroupItems = droppedItems[key]?.filter(existingItem =>
|
||||||
|
'group' in existingItem && existingItem.group === groupId
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
const existingGroupItem = droppedItems[key]?.filter(existingItem =>
|
||||||
|
'group' in existingItem && existingItem.group === groupId
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
existingGroupItem[0].options
|
||||||
|
|
||||||
|
// Find the bottom-most item in the group
|
||||||
|
let bottomMostItem = item;
|
||||||
|
if (existingGroupItems.length > 0) {
|
||||||
|
bottomMostItem = existingGroupItems.reduce((lowest, current) => {
|
||||||
|
const currentBottom = current.dimensions.top + current.dimensions.height;
|
||||||
|
const lowestBottom = lowest.dimensions.top + lowest.dimensions.height;
|
||||||
|
return currentBottom > lowestBottom ? current : lowest;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new position: place below the bottom-most item
|
||||||
|
const spacing = 10; // Adjust spacing as needed
|
||||||
|
const newTop = bottomMostItem.dimensions.top + bottomMostItem.dimensions.height + spacing;
|
||||||
|
|
||||||
|
// Use the same left position as the parent (original) item
|
||||||
|
const newItem = {
|
||||||
|
...item,
|
||||||
|
dimensions: {
|
||||||
|
...item.dimensions,
|
||||||
|
left: item.dimensions.left, // Keep same horizontal position as parent
|
||||||
|
top: newTop // Position below the bottom-most item
|
||||||
|
},
|
||||||
|
isGroupExist: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the dropped items
|
||||||
|
setDroppedItems({
|
||||||
|
...droppedItems,
|
||||||
|
[key]: [...droppedItems[key], newItem],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update group borders after adding the new item
|
||||||
|
// Use setTimeout to ensure state update is processed
|
||||||
|
setTimeout(() => {
|
||||||
|
handleIGroupBorderPosition(groupId);
|
||||||
|
}, 50); // Slightly longer timeout to ensure DOM updates
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Initialize group borders for all existing groups after component updates
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
const allGroupElements = document.querySelectorAll('[group]');
|
||||||
|
const groupIds = new Set<string>();
|
||||||
|
|
||||||
|
allGroupElements.forEach((element) => {
|
||||||
|
const groupId = element.getAttribute('group');
|
||||||
|
if (groupId) {
|
||||||
|
groupIds.add(groupId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
groupIds.forEach((groupId) => {
|
||||||
|
handleIGroupBorderPosition(groupId);
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
|
}, [droppedItems]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* DROPPED ITEMS - Render only the dropped items for this specific docId and page - STARTS */}
|
{/* DROPPED ITEMS - Render only the dropped items for this specific docId and page - STARTS */}
|
||||||
{droppedItems[key]?.map((item, index) => {
|
{droppedItems[pageIdkey]?.map((item, index) => {
|
||||||
if (item.isDeleted) return;
|
if (item.isDeleted) return;
|
||||||
|
|
||||||
const type = item.type.toUpperCase();
|
const type = item.type.toUpperCase();
|
||||||
|
|
@ -222,8 +535,10 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems,
|
||||||
<div
|
<div
|
||||||
// key={index}
|
// key={index}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
|
data-tag-id={index}
|
||||||
|
data-tag-selected={index === selectedTag?.index}
|
||||||
className="absolute rounded dropped-item"
|
className="absolute rounded dropped-item"
|
||||||
onClick={() => handleTagClick(key, index)}
|
onClick={() => handleTagClick(pageIdkey, index)}
|
||||||
style={{
|
style={{
|
||||||
left: `${item.dimensions.left}px`,
|
left: `${item.dimensions.left}px`,
|
||||||
top: `${item.dimensions.top}px`,
|
top: `${item.dimensions.top}px`,
|
||||||
|
|
@ -234,7 +549,7 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems,
|
||||||
|
|
||||||
|
|
||||||
{/* If Selected Tag Recipient Info- START */}
|
{/* If Selected Tag Recipient Info- START */}
|
||||||
{selectedTag?.key && selectedTag.key === key && selectedTag.index === index &&
|
{selectedTag?.key && selectedTag.key === pageIdkey && selectedTag.index === index &&
|
||||||
<div className='absolute flex justify-between items-center w-full max-w-max bg-white top-[-22px] shadow z-20 text-sm rounded-t-sm' >
|
<div className='absolute flex justify-between items-center w-full max-w-max bg-white top-[-22px] shadow z-20 text-sm rounded-t-sm' >
|
||||||
<div className='flex justify-center items-center gap-1 bg-white px-2 shadow'>
|
<div className='flex justify-center items-center gap-1 bg-white px-2 shadow'>
|
||||||
<p className='w-2.5 h-2.5 rounded'
|
<p className='w-2.5 h-2.5 rounded'
|
||||||
|
|
@ -246,18 +561,16 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems,
|
||||||
<p className='capitalize text-ellipsis whitespace-nowrap'>{item.label}</p>
|
<p className='capitalize text-ellipsis whitespace-nowrap'>{item.label}</p>
|
||||||
<IoMdClose onClick={(e) => {
|
<IoMdClose onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleRemoveTag(key, index)
|
handleRemoveTag(pageIdkey, index)
|
||||||
}} className='cursor-pointer' />
|
}} className='cursor-pointer' />
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
{/* If Selected Tag Recipient Info- END */}
|
{/* If Selected Tag Recipient Info- END */}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Expand Circle - STARTS */}
|
{/* Expand Circle - STARTS */}
|
||||||
{item.type !== "radio" && item.type !== "checkbox" && <div
|
{item.type !== "radio" && item.type !== "checkbox" && <div
|
||||||
onPointerDown={(e) => handleExpandTag(e, key, index)}
|
onPointerDown={(e) => handleExpandTag(e, pageIdkey, index)}
|
||||||
className='w-2.5 h-2.5 absolute bg-red-400 rounded-full bottom-[-8px] right-[-8px] cursor-nw-resize hover:bg-blue-400'>
|
className='w-2.5 h-2.5 absolute bg-red-400 rounded-full bottom-[-8px] right-[-8px] cursor-nw-resize hover:bg-blue-400'>
|
||||||
</div>}
|
</div>}
|
||||||
{/* Expand Circle - ENDS */}
|
{/* Expand Circle - ENDS */}
|
||||||
|
|
@ -266,7 +579,7 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems,
|
||||||
{
|
{
|
||||||
<div
|
<div
|
||||||
{...("group" in item && item.group ? { "group": item.group } : {})}
|
{...("group" in item && item.group ? { "group": item.group } : {})}
|
||||||
onPointerDown={(e) => handleTagMouseDown(e, key, index)}
|
onPointerDown={(e) => handleTagMouseDown(e, pageIdkey, index)}
|
||||||
className='p-2 rounded-sm h-full shadow opacity-40 cursor-grab w-full'
|
className='p-2 rounded-sm h-full shadow opacity-40 cursor-grab w-full'
|
||||||
style={{ background: selectedRecipient[0]?.color }}
|
style={{ background: selectedRecipient[0]?.color }}
|
||||||
>
|
>
|
||||||
|
|
@ -290,7 +603,7 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({ droppedItems,
|
||||||
<div className="group-border" border-group={item.group} border-pos="right"></div>
|
<div className="group-border" border-group={item.group} border-pos="right"></div>
|
||||||
<div className="group-border bottom-border" border-group={item.group} border-pos="bottom">
|
<div className="group-border bottom-border" border-group={item.group} border-pos="bottom">
|
||||||
<div
|
<div
|
||||||
onClick={() => handleAddGroupItemClicked(key, item)}>
|
onClick={() => handleAddGroupItemClicked(pageIdkey, item)}>
|
||||||
<IoAdd />
|
<IoAdd />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx
|
||||||
const priorityOrder = ['SIGNATURE', 'INITIALS', 'STAMP'];
|
const priorityOrder = ['SIGNATURE', 'INITIALS', 'STAMP'];
|
||||||
|
|
||||||
const data = res.data.filter((obj, indx) => {
|
const data = res.data.filter((obj, indx) => {
|
||||||
if (obj.type === "STAMP" || obj.type === "RADIO" || obj.type === "CHECKBOX" || obj.type === "ATTACHMENT") return false
|
// if (obj.type === "STAMP" || obj.type === "RADIO" || obj.type === "CHECKBOX" || obj.type === "ATTACHMENT") return false
|
||||||
return true
|
return true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,10 +128,10 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) {
|
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) {
|
||||||
if (isSignSend) {
|
if (isSignSend) {
|
||||||
// window.open(`./../../../sign/${selfUserSign.recipientId}`, "_self");
|
window.open(`./../../../sign/${selfUserSign.recipientId}`, "_self");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// navigate(`/document-completion-screen/${accountId}/${transactionId}`, { replace: true })
|
navigate(`/document-completion-screen/${accountId}/${transactionId}`, { replace: true })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
navigate(`/templates`);
|
navigate(`/templates`);
|
||||||
|
|
|
||||||
|
|
@ -119,11 +119,6 @@ const RightSideBar: React.FC<DroppedItemProps> = ({ droppedItems, selectedTag, s
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// setDroppedItems((prev) => {
|
|
||||||
// const updated = [...prev];
|
|
||||||
// updated[selTagIndx] = { ...updated[selTagIndx], [changeTo]: changeWith, update: true }
|
|
||||||
// return updated;
|
|
||||||
// })
|
|
||||||
setDroppedItems((prev) => {
|
setDroppedItems((prev) => {
|
||||||
return (prev.map((item, idx) => {
|
return (prev.map((item, idx) => {
|
||||||
if (idx === selTagIndx) {
|
if (idx === selTagIndx) {
|
||||||
|
|
@ -297,18 +292,20 @@ const RightSideBar: React.FC<DroppedItemProps> = ({ droppedItems, selectedTag, s
|
||||||
<RecipientsAttrCon selectedRecipientId={selectedTag.recipientId} handlePropertyChange={handlePropertyChange} />
|
<RecipientsAttrCon selectedRecipientId={selectedTag.recipientId} handlePropertyChange={handlePropertyChange} />
|
||||||
<DirectionalControls {...DirectionalControlsProps} selectedType={selectedTag.dimensions} />
|
<DirectionalControls {...DirectionalControlsProps} selectedType={selectedTag.dimensions} />
|
||||||
|
|
||||||
{selectedTag.label &&
|
|
||||||
<LabelAttr label={selectedTag.label} handlePropertyChange={handlePropertyChange} />}
|
|
||||||
|
|
||||||
<ToolTipAttr tooltip={selectedTag.tooltip ?? ""} handlePropertyChange={handlePropertyChange} />
|
|
||||||
|
|
||||||
{
|
{
|
||||||
(selectedTag.type !== TagsTypes.SIGNATURE && selectedTag.type !== TagsTypes.INITIALS) &&
|
(selectedTag.type !== TagsTypes.SIGNATURE && selectedTag.type !== TagsTypes.INITIALS) &&
|
||||||
<RequiredAttr required={selectedTag.required ? selectedTag.required : false} handlePropertyChange={handlePropertyChange} />
|
<RequiredAttr required={selectedTag.required ? selectedTag.required : false} handlePropertyChange={handlePropertyChange} />
|
||||||
}
|
}
|
||||||
|
|
||||||
{selectedTag.type.toLowerCase() === TagsTypes.TEMPORAL &&
|
{
|
||||||
<CurrTimeStampAttr type={selectedTag.type} handlePropertyChange={handlePropertyChange} />}
|
selectedTag.type.toLowerCase() === TagsTypes.TEMPORAL &&
|
||||||
|
<CurrTimeStampAttr type={selectedTag.type} handlePropertyChange={handlePropertyChange} />
|
||||||
|
}
|
||||||
|
|
||||||
|
{selectedTag.label &&
|
||||||
|
<LabelAttr label={selectedTag.label} handlePropertyChange={handlePropertyChange} />}
|
||||||
|
|
||||||
|
<ToolTipAttr tooltip={selectedTag.tooltip ?? ""} handlePropertyChange={handlePropertyChange} />
|
||||||
|
|
||||||
{
|
{
|
||||||
selectedType && Object.keys(selectedType).length > 0 &&
|
selectedType && Object.keys(selectedType).length > 0 &&
|
||||||
|
|
@ -361,7 +358,7 @@ const RightSideBar: React.FC<DroppedItemProps> = ({ droppedItems, selectedTag, s
|
||||||
{
|
{
|
||||||
selectedTag?.type.toLowerCase() === TagsTypes.NUMBER &&
|
selectedTag?.type.toLowerCase() === TagsTypes.NUMBER &&
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="" className="">Decimal Places</label>
|
<label htmlFor="" className="font-medium">Decimal Places</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
onChange={(e) => handleDecimalPlace(e)}
|
onChange={(e) => handleDecimalPlace(e)}
|
||||||
|
|
@ -392,7 +389,7 @@ const RightSideBar: React.FC<DroppedItemProps> = ({ droppedItems, selectedTag, s
|
||||||
onClick={() => setStyleOpen(!styleOpen)}
|
onClick={() => setStyleOpen(!styleOpen)}
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="block mb-2">Styles</label>
|
className="block mb-2 font-medium">Styles</label>
|
||||||
<IoIosArrowDown className={`${styleOpen ? "rotate-[-90deg]" : ""} transition-all`} />
|
<IoIosArrowDown className={`${styleOpen ? "rotate-[-90deg]" : ""} transition-all`} />
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ const CommonAttrDropDown: React.FC<CommonAttrDropDownInt> = ({ selectedType, sel
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<label htmlFor="" className="">Type</label>
|
<label htmlFor="" className="font-medium">Type</label>
|
||||||
<div
|
<div
|
||||||
onClick={() => setDropDownToggle(dropDownToggle === DROPDOWNTOGGLE.TYPE ? null : DROPDOWNTOGGLE.TYPE)}
|
onClick={() => setDropDownToggle(dropDownToggle === DROPDOWNTOGGLE.TYPE ? null : DROPDOWNTOGGLE.TYPE)}
|
||||||
className="flex justify-between items-center border-1 rounded px-2 py-1 border-slate-200">
|
className="flex justify-between items-center border-1 rounded px-2 py-1 border-slate-200">
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ const CurrTimeStampAttr: React.FC<CurrTimeStampAttrInt> = ({ type, handlePropert
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-left items-center gap-1.5 mt-0.5">
|
<div className="flex justify-between items-center border-1 p-1.5 rounded-sm border-slate-200">
|
||||||
<label
|
<label
|
||||||
className={`text-xs ${isCurrentTimeStamp ? "text-slate-600" : "text-slate-400"} transitions-all duration-150`}
|
className="text-slate-600 transitions-all duration-150 font-medium"
|
||||||
htmlFor="use_current_timestamp"
|
htmlFor="use_current_timestamp"
|
||||||
>
|
>
|
||||||
Use Current Timestamp
|
Use Current Timestamp
|
||||||
|
|
|
||||||
|
|
@ -73,24 +73,27 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
|
||||||
if (disabled) return;
|
|
||||||
|
|
||||||
switch (event.key) {
|
// Move ON Key Down - START
|
||||||
case 'ArrowUp':
|
// useEffect(() => {
|
||||||
case 'ArrowDown':
|
// const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
case 'ArrowLeft':
|
// if (disabled) return;
|
||||||
case 'ArrowRight':
|
|
||||||
event.preventDefault();
|
|
||||||
handleMove(event.key.replace('Arrow', '').toLowerCase() as any);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
// switch (event.key) {
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
// case 'ArrowUp':
|
||||||
}, [disabled, stepSize]);
|
// case 'ArrowDown':
|
||||||
|
// case 'ArrowLeft':
|
||||||
|
// case 'ArrowRight':
|
||||||
|
// event.preventDefault();
|
||||||
|
// handleMove(event.key.replace('Arrow', '').toLowerCase() as any);
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// window.addEventListener('keydown', handleKeyDown);
|
||||||
|
// return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
// }, [disabled, stepSize]);
|
||||||
|
// Move ON Key Down - END
|
||||||
|
|
||||||
const buttonClass = `
|
const buttonClass = `
|
||||||
w-8 h-8 flex items-center justify-center rounded-full border shadow-sm text-[12px]
|
w-8 h-8 flex items-center justify-center rounded-full border shadow-sm text-[12px]
|
||||||
|
|
@ -124,7 +127,7 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={leftValue}
|
value={leftValue}
|
||||||
onChange={(e) => {e.preventDefault; handlePositionInputChange('left', e.target.value)}}
|
onChange={(e) => { e.preventDefault; handlePositionInputChange('left', e.target.value) }}
|
||||||
onBlur={() => handlePositionInputBlur('left')}
|
onBlur={() => handlePositionInputBlur('left')}
|
||||||
onKeyPress={(e) => handleKeyPress(e, 'left')}
|
onKeyPress={(e) => handleKeyPress(e, 'left')}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
@ -134,7 +137,7 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={topValue}
|
value={topValue}
|
||||||
onChange={(e) => {e.preventDefault; handlePositionInputChange('top', e.target.value)}}
|
onChange={(e) => { e.preventDefault; handlePositionInputChange('top', e.target.value) }}
|
||||||
onBlur={() => handlePositionInputBlur('top')}
|
onBlur={() => handlePositionInputBlur('top')}
|
||||||
onKeyPress={(e) => handleKeyPress(e, 'top')}
|
onKeyPress={(e) => handleKeyPress(e, 'top')}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const LabelAttr: React.FC<LabelAttrInt> = ({ label, handlePropertyChange }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<label>Label</label>
|
<label className='font-medium'>Label</label>
|
||||||
<input
|
<input
|
||||||
value={label}
|
value={label}
|
||||||
onChange={(e) => handlePropertyChange('label', e.target.value)}
|
onChange={(e) => handlePropertyChange('label', e.target.value)}
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ const MaxMinLenAttr: React.FC<MaxMinLenAttrIntr> = ({ maxLength, minLength, hand
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div className="flex flex-col">
|
||||||
<label htmlFor="maxLength">Max Length</label>
|
<label className="font-medium" htmlFor="maxLength">Max Length</label>
|
||||||
<input
|
<input
|
||||||
id="maxLength"
|
id="maxLength"
|
||||||
value={maxLength}
|
value={maxLength}
|
||||||
|
|
@ -50,8 +50,8 @@ const MaxMinLenAttr: React.FC<MaxMinLenAttrIntr> = ({ maxLength, minLength, hand
|
||||||
onChange={handleMaxChange}
|
onChange={handleMaxChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
<div className="flex flex-col">
|
||||||
<label htmlFor="minLength">Min Length</label>
|
<label className="font-medium" htmlFor="minLength">Min Length</label>
|
||||||
<input
|
<input
|
||||||
id="minLength"
|
id="minLength"
|
||||||
value={minLength}
|
value={minLength}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ interface RequiredAttrInt {
|
||||||
|
|
||||||
const RequiredAttr : React.FC<RequiredAttrInt> = ({required, handlePropertyChange}) => {
|
const RequiredAttr : React.FC<RequiredAttrInt> = ({required, handlePropertyChange}) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center border-1 p-1.5 rounded-sm border-slate-200">
|
||||||
<label htmlFor="required" className="text-xs text-slate-600 transitions-all duration-150">Required</label>
|
<label htmlFor="required" className="text-slate-600 transitions-all duration-150 font-medium">Required</label>
|
||||||
<input
|
<input
|
||||||
id="required"
|
id="required"
|
||||||
checked={required ? required : false}
|
checked={required ? required : false}
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ const SelectTagAttr: React.FC<SelectTagAttrInt> = ({ options, selTagKey, selTagI
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="" className="">Add Options</label>
|
<label htmlFor="" className="font-medium">Add Options</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
onChange={(e) => setSelectedOption({ label: e.target.value, value: e.target.value })}
|
onChange={(e) => setSelectedOption({ label: e.target.value, value: e.target.value })}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ const TemporalDateAttr: React.FC<TemporalDateAttrInt> = ({ dateFormat, format, s
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<label htmlFor="" className="">Formats</label>
|
<label htmlFor="" className="font-medium">Formats</label>
|
||||||
{(dateFormat === TEMPORAL.DATE_TIME || dateFormat === TEMPORAL.TIME) &&
|
{(dateFormat === TEMPORAL.DATE_TIME || dateFormat === TEMPORAL.TIME) &&
|
||||||
<div className="flex justify-center items-center gap-1.5">
|
<div className="flex justify-center items-center gap-1.5">
|
||||||
<input
|
<input
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const ToolTipAttr: React.FC<ToolTipAttrInt> = ({ tooltip, handlePropertyChange }
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<label>ToolTip</label>
|
<label className='font-medium'>ToolTip</label>
|
||||||
<input
|
<input
|
||||||
value={tooltip}
|
value={tooltip}
|
||||||
onChange={(e) => handlePropertyChange('tooltip', e.target.value)}
|
onChange={(e) => handlePropertyChange('tooltip', e.target.value)}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import UserAuthUI from "../auth/UsertAuthUI"
|
||||||
function Register() {
|
function Register() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-center items-center w-full h-[100vh]">
|
<div className="">
|
||||||
<UserAuthUI pg={"register"} />
|
<UserAuthUI pg={"register"} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,13 @@ html {
|
||||||
.auth-ui-con .container-l {
|
.auth-ui-con .container-l {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
width: 850px;
|
width: 1280px;
|
||||||
height: 550px;
|
height: 599px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
/* margin-top: 20px; */
|
/* margin-top: 20px; */
|
||||||
margin-left: 50%;
|
margin-left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
border-radius: 30px;
|
/* border-radius: 30px; */
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, .2);
|
box-shadow: 0 0 30px rgba(0, 0, 0, .2);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue