From af8f64b0115a8d5952911a993e7c379d49104113 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 28 May 2026 17:36:23 -0400 Subject: [PATCH 1/5] updates --- controllers/UserAPIRoutes.js | 311 ++++++++++++++++++++++++++++++++--- 1 file changed, 290 insertions(+), 21 deletions(-) diff --git a/controllers/UserAPIRoutes.js b/controllers/UserAPIRoutes.js index 48a0da7..390d1c1 100644 --- a/controllers/UserAPIRoutes.js +++ b/controllers/UserAPIRoutes.js @@ -48,71 +48,340 @@ router.get("/view/:id", (req, res) => { }); }); +// router.put("/update/:id", async (req, res) => { +// try { +// // 1️⃣ Find user +// const user = await db.User.findByPk(req.params.id); +// if (!user) { +// return res.status(404).json({ +// success: false, +// message: "User not found", +// }); +// } + +// let updateData = { ...req.body }; + +// // 2️⃣ Handle password update +// if (updateData.newPassword) { +// const hashedPassword = await bcrypt.hash(updateData.newPassword, 10); +// updateData.password = hashedPassword; +// delete updateData.newPassword; +// } + +// // 3️⃣ Cloudinary image upload (base64) +// if (updateData.profileImage) { +// try { +// let imageBase64 = updateData.profileImage.trim(); + +// // If it's raw base64, add data URI prefix +// if ( +// !imageBase64.startsWith("data:") && +// !imageBase64.startsWith("http") +// ) { +// imageBase64 = `data:image/jpeg;base64,${imageBase64}`; +// } + +// const uploadResult = await cloudinary.uploader.upload(imageBase64, { +// folder: "users", +// public_id: `user_${user.id}_${Date.now()}`, +// transformation: [{ width: 500, height: 500, crop: "fill" }], +// }); + +// updateData.profileImage = uploadResult.secure_url; +// } catch (err) { +// console.error("Cloudinary Upload Error:", err); +// return res.status(400).json({ +// success: false, +// message: "Image upload failed", +// }); +// } +// } + +// // 4️⃣ Update user +// await db.User.update(updateData, { where: { id: req.params.id } }); + +// // 5️⃣ Fetch updated user +// const updatedUser = await db.User.findByPk(req.params.id); + +// return res.status(200).json({ +// success: true, +// message: "User updated successfully", +// user: updatedUser, +// }); +// } catch (err) { +// console.error("Update Error:", err); +// res.status(500).json({ +// success: false, +// message: "Internal server error", +// }); +// } +// }); router.put("/update/:id", async (req, res) => { try { - // 1️⃣ Find user - const user = await db.User.findByPk(req.params.id); + // ===================================================== + // VALIDATE USER ID + // ===================================================== + + const userId = req.params.id; + + if (!userId || userId === "undefined" || userId === "null") { + return res.status(400).json({ + success: false, + + field: "id", + + message: "A valid user ID is required to update this account.", + + details: + "The update request was missing the account ID. Please refresh the page and try again.", + }); + } + + // ===================================================== + // FIND USER + // ===================================================== + + const user = await db.User.findByPk(userId); + if (!user) { return res.status(404).json({ success: false, - message: "User not found", + + field: "user", + + message: "User account not found.", + + details: "We could not find an account matching this request.", }); } + // ===================================================== + // PREP UPDATE DATA + // ===================================================== + let updateData = { ...req.body }; - // 2️⃣ Handle password update + // ===================================================== + // CLEAN INPUTS + // ===================================================== + + Object.keys(updateData).forEach((key) => { + if (typeof updateData[key] === "string") { + updateData[key] = updateData[key].trim(); + } + }); + + // ===================================================== + // REQUIRED VALIDATION + // ===================================================== + + const validationErrors = []; + + // NAME VALIDATION + + if (updateData.name !== undefined && updateData.name.length < 2) { + validationErrors.push({ + field: "name", + + message: "Your name must contain at least 2 characters.", + + fix: "Please enter your full name.", + }); + } + + // EMAIL VALIDATION + + if (updateData.email !== undefined) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + + if (!updateData.email) { + validationErrors.push({ + field: "email", + + message: "Email address is required.", + + fix: "Please enter your email address.", + }); + } else if (!emailRegex.test(updateData.email)) { + validationErrors.push({ + field: "email", + + message: "This email address is not valid.", + + fix: "Example: john@example.com", + }); + } else { + // CHECK DUPLICATE EMAIL + + const existingEmailUser = await db.User.findOne({ + where: { + email: updateData.email, + }, + }); + + if (existingEmailUser && existingEmailUser.id !== user.id) { + validationErrors.push({ + field: "email", + + message: "This email address is already being used.", + + fix: "Use a different email address.", + }); + } + } + } + + // PASSWORD VALIDATION + + if (updateData.newPassword !== undefined) { + if (!updateData.newPassword) { + validationErrors.push({ + field: "newPassword", + + message: "Password field cannot be empty.", + + fix: "Enter a secure password.", + }); + } else if (updateData.newPassword.length < 8) { + validationErrors.push({ + field: "newPassword", + + message: "Password must be at least 8 characters long.", + + fix: "Use at least 8 characters with numbers and symbols.", + }); + } else { + const strongPasswordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/; + + if (!strongPasswordRegex.test(updateData.newPassword)) { + validationErrors.push({ + field: "newPassword", + + message: "Password is too weak.", + + fix: "Include at least 1 uppercase letter, 1 lowercase letter, and 1 number.", + }); + } + } + } + + // ===================================================== + // RETURN VALIDATION ERRORS + // ===================================================== + + if (validationErrors.length > 0) { + return res.status(400).json({ + success: false, + + message: "Please correct the highlighted fields and try again.", + + errors: validationErrors, + }); + } + + // ===================================================== + // HANDLE PASSWORD UPDATE + // ===================================================== + if (updateData.newPassword) { const hashedPassword = await bcrypt.hash(updateData.newPassword, 10); + updateData.password = hashedPassword; + delete updateData.newPassword; } - // 3️⃣ Cloudinary image upload (base64) - if (updateData.profileImage) { + // ===================================================== + // HANDLE PROFILE IMAGE + // ===================================================== + + if ( + updateData.profileImage && + !updateData.profileImage.startsWith("http") + ) { try { let imageBase64 = updateData.profileImage.trim(); - // If it's raw base64, add data URI prefix - if ( - !imageBase64.startsWith("data:") && - !imageBase64.startsWith("http") - ) { + // ADD DATA URI IF RAW BASE64 + + if (!imageBase64.startsWith("data:")) { imageBase64 = `data:image/jpeg;base64,${imageBase64}`; } const uploadResult = await cloudinary.uploader.upload(imageBase64, { folder: "users", + public_id: `user_${user.id}_${Date.now()}`, - transformation: [{ width: 500, height: 500, crop: "fill" }], + + transformation: [ + { + width: 500, + height: 500, + crop: "fill", + }, + ], }); updateData.profileImage = uploadResult.secure_url; } catch (err) { console.error("Cloudinary Upload Error:", err); + return res.status(400).json({ success: false, - message: "Image upload failed", + + field: "profileImage", + + message: "Profile image upload failed.", + + details: "Please try another image or use a smaller file size.", }); } } - // 4️⃣ Update user - await db.User.update(updateData, { where: { id: req.params.id } }); + // ===================================================== + // REMOVE UNSAFE FIELDS + // ===================================================== + + delete updateData.id; + delete updateData.createdAt; + delete updateData.updatedAt; + + // ===================================================== + // UPDATE USER + // ===================================================== + + await db.User.update(updateData, { + where: { + id: userId, + }, + }); + + // ===================================================== + // FETCH UPDATED USER + // ===================================================== - // 5️⃣ Fetch updated user - const updatedUser = await db.User.findByPk(req.params.id); + const updatedUser = await db.User.findByPk(userId); + + // ===================================================== + // SUCCESS RESPONSE + // ===================================================== return res.status(200).json({ success: true, - message: "User updated successfully", + + message: "Your account has been updated successfully.", + user: updatedUser, }); } catch (err) { - console.error("Update Error:", err); - res.status(500).json({ + console.error("User Update Route Error:", err); + + return res.status(500).json({ success: false, - message: "Internal server error", + + message: "Something went wrong while updating your account.", + + details: "Please try again in a few moments.", }); } }); From 4a4c7bb440400de1b6bc7f9c4edfc8685d2fee54 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 28 May 2026 18:01:16 -0400 Subject: [PATCH 2/5] updates --- controllers/UserAPIRoutes.js | 508 +++++++++++++++++------------------ 1 file changed, 254 insertions(+), 254 deletions(-) diff --git a/controllers/UserAPIRoutes.js b/controllers/UserAPIRoutes.js index 390d1c1..cc5f075 100644 --- a/controllers/UserAPIRoutes.js +++ b/controllers/UserAPIRoutes.js @@ -48,343 +48,343 @@ router.get("/view/:id", (req, res) => { }); }); -// router.put("/update/:id", async (req, res) => { -// try { -// // 1️⃣ Find user -// const user = await db.User.findByPk(req.params.id); -// if (!user) { -// return res.status(404).json({ -// success: false, -// message: "User not found", -// }); -// } +router.put("/update/:id", async (req, res) => { + try { + // 1️⃣ Find user + const user = await db.User.findByPk(req.params.id); + if (!user) { + return res.status(404).json({ + success: false, + message: "User not found", + }); + } -// let updateData = { ...req.body }; + let updateData = { ...req.body }; -// // 2️⃣ Handle password update -// if (updateData.newPassword) { -// const hashedPassword = await bcrypt.hash(updateData.newPassword, 10); -// updateData.password = hashedPassword; -// delete updateData.newPassword; -// } + // 2️⃣ Handle password update + if (updateData.newPassword) { + const hashedPassword = await bcrypt.hash(updateData.newPassword, 10); + updateData.password = hashedPassword; + delete updateData.newPassword; + } -// // 3️⃣ Cloudinary image upload (base64) -// if (updateData.profileImage) { -// try { -// let imageBase64 = updateData.profileImage.trim(); + // 3️⃣ Cloudinary image upload (base64) + if (updateData.profileImage) { + try { + let imageBase64 = updateData.profileImage.trim(); -// // If it's raw base64, add data URI prefix -// if ( -// !imageBase64.startsWith("data:") && -// !imageBase64.startsWith("http") -// ) { -// imageBase64 = `data:image/jpeg;base64,${imageBase64}`; -// } + // If it's raw base64, add data URI prefix + if ( + !imageBase64.startsWith("data:") && + !imageBase64.startsWith("http") + ) { + imageBase64 = `data:image/jpeg;base64,${imageBase64}`; + } -// const uploadResult = await cloudinary.uploader.upload(imageBase64, { -// folder: "users", -// public_id: `user_${user.id}_${Date.now()}`, -// transformation: [{ width: 500, height: 500, crop: "fill" }], -// }); + const uploadResult = await cloudinary.uploader.upload(imageBase64, { + folder: "users", + public_id: `user_${user.id}_${Date.now()}`, + transformation: [{ width: 500, height: 500, crop: "fill" }], + }); -// updateData.profileImage = uploadResult.secure_url; -// } catch (err) { -// console.error("Cloudinary Upload Error:", err); -// return res.status(400).json({ -// success: false, -// message: "Image upload failed", -// }); -// } -// } + updateData.profileImage = uploadResult.secure_url; + } catch (err) { + console.error("Cloudinary Upload Error:", err); + return res.status(400).json({ + success: false, + message: "Image upload failed", + }); + } + } -// // 4️⃣ Update user -// await db.User.update(updateData, { where: { id: req.params.id } }); + // 4️⃣ Update user + await db.User.update(updateData, { where: { id: req.params.id } }); -// // 5️⃣ Fetch updated user -// const updatedUser = await db.User.findByPk(req.params.id); + // 5️⃣ Fetch updated user + const updatedUser = await db.User.findByPk(req.params.id); -// return res.status(200).json({ -// success: true, -// message: "User updated successfully", -// user: updatedUser, -// }); -// } catch (err) { -// console.error("Update Error:", err); -// res.status(500).json({ -// success: false, -// message: "Internal server error", -// }); -// } -// }); -router.put("/update/:id", async (req, res) => { - try { - // ===================================================== - // VALIDATE USER ID - // ===================================================== + return res.status(200).json({ + success: true, + message: "User updated successfully", + user: updatedUser, + }); + } catch (err) { + console.error("Update Error:", err); + res.status(500).json({ + success: false, + message: "Internal server error", + }); + } +}); +// router.put("/update/:id", async (req, res) => { +// try { +// // ===================================================== +// // VALIDATE USER ID +// // ===================================================== - const userId = req.params.id; +// const userId = req.params.id; - if (!userId || userId === "undefined" || userId === "null") { - return res.status(400).json({ - success: false, +// if (!userId || userId === "undefined" || userId === "null") { +// return res.status(400).json({ +// success: false, - field: "id", +// field: "id", - message: "A valid user ID is required to update this account.", +// message: "A valid user ID is required to update this account.", - details: - "The update request was missing the account ID. Please refresh the page and try again.", - }); - } +// details: +// "The update request was missing the account ID. Please refresh the page and try again.", +// }); +// } - // ===================================================== - // FIND USER - // ===================================================== +// // ===================================================== +// // FIND USER +// // ===================================================== - const user = await db.User.findByPk(userId); +// const user = await db.User.findByPk(userId); - if (!user) { - return res.status(404).json({ - success: false, +// if (!user) { +// return res.status(404).json({ +// success: false, - field: "user", +// field: "user", - message: "User account not found.", +// message: "User account not found.", - details: "We could not find an account matching this request.", - }); - } +// details: "We could not find an account matching this request.", +// }); +// } - // ===================================================== - // PREP UPDATE DATA - // ===================================================== +// // ===================================================== +// // PREP UPDATE DATA +// // ===================================================== - let updateData = { ...req.body }; +// let updateData = { ...req.body }; - // ===================================================== - // CLEAN INPUTS - // ===================================================== +// // ===================================================== +// // CLEAN INPUTS +// // ===================================================== - Object.keys(updateData).forEach((key) => { - if (typeof updateData[key] === "string") { - updateData[key] = updateData[key].trim(); - } - }); +// Object.keys(updateData).forEach((key) => { +// if (typeof updateData[key] === "string") { +// updateData[key] = updateData[key].trim(); +// } +// }); - // ===================================================== - // REQUIRED VALIDATION - // ===================================================== +// // ===================================================== +// // REQUIRED VALIDATION +// // ===================================================== - const validationErrors = []; +// const validationErrors = []; - // NAME VALIDATION +// // NAME VALIDATION - if (updateData.name !== undefined && updateData.name.length < 2) { - validationErrors.push({ - field: "name", +// if (updateData.name !== undefined && updateData.name.length < 2) { +// validationErrors.push({ +// field: "name", - message: "Your name must contain at least 2 characters.", +// message: "Your name must contain at least 2 characters.", - fix: "Please enter your full name.", - }); - } +// fix: "Please enter your full name.", +// }); +// } - // EMAIL VALIDATION +// // EMAIL VALIDATION - if (updateData.email !== undefined) { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; +// if (updateData.email !== undefined) { +// const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!updateData.email) { - validationErrors.push({ - field: "email", +// if (!updateData.email) { +// validationErrors.push({ +// field: "email", - message: "Email address is required.", +// message: "Email address is required.", - fix: "Please enter your email address.", - }); - } else if (!emailRegex.test(updateData.email)) { - validationErrors.push({ - field: "email", +// fix: "Please enter your email address.", +// }); +// } else if (!emailRegex.test(updateData.email)) { +// validationErrors.push({ +// field: "email", - message: "This email address is not valid.", +// message: "This email address is not valid.", - fix: "Example: john@example.com", - }); - } else { - // CHECK DUPLICATE EMAIL +// fix: "Example: john@example.com", +// }); +// } else { +// // CHECK DUPLICATE EMAIL - const existingEmailUser = await db.User.findOne({ - where: { - email: updateData.email, - }, - }); +// const existingEmailUser = await db.User.findOne({ +// where: { +// email: updateData.email, +// }, +// }); - if (existingEmailUser && existingEmailUser.id !== user.id) { - validationErrors.push({ - field: "email", +// if (existingEmailUser && existingEmailUser.id !== user.id) { +// validationErrors.push({ +// field: "email", - message: "This email address is already being used.", +// message: "This email address is already being used.", - fix: "Use a different email address.", - }); - } - } - } +// fix: "Use a different email address.", +// }); +// } +// } +// } - // PASSWORD VALIDATION +// // PASSWORD VALIDATION - if (updateData.newPassword !== undefined) { - if (!updateData.newPassword) { - validationErrors.push({ - field: "newPassword", +// if (updateData.newPassword !== undefined) { +// if (!updateData.newPassword) { +// validationErrors.push({ +// field: "newPassword", - message: "Password field cannot be empty.", +// message: "Password field cannot be empty.", - fix: "Enter a secure password.", - }); - } else if (updateData.newPassword.length < 8) { - validationErrors.push({ - field: "newPassword", +// fix: "Enter a secure password.", +// }); +// } else if (updateData.newPassword.length < 8) { +// validationErrors.push({ +// field: "newPassword", - message: "Password must be at least 8 characters long.", +// message: "Password must be at least 8 characters long.", - fix: "Use at least 8 characters with numbers and symbols.", - }); - } else { - const strongPasswordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/; +// fix: "Use at least 8 characters with numbers and symbols.", +// }); +// } else { +// const strongPasswordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/; - if (!strongPasswordRegex.test(updateData.newPassword)) { - validationErrors.push({ - field: "newPassword", +// if (!strongPasswordRegex.test(updateData.newPassword)) { +// validationErrors.push({ +// field: "newPassword", - message: "Password is too weak.", +// message: "Password is too weak.", - fix: "Include at least 1 uppercase letter, 1 lowercase letter, and 1 number.", - }); - } - } - } +// fix: "Include at least 1 uppercase letter, 1 lowercase letter, and 1 number.", +// }); +// } +// } +// } - // ===================================================== - // RETURN VALIDATION ERRORS - // ===================================================== +// // ===================================================== +// // RETURN VALIDATION ERRORS +// // ===================================================== - if (validationErrors.length > 0) { - return res.status(400).json({ - success: false, +// if (validationErrors.length > 0) { +// return res.status(400).json({ +// success: false, - message: "Please correct the highlighted fields and try again.", +// message: "Please correct the highlighted fields and try again.", - errors: validationErrors, - }); - } +// errors: validationErrors, +// }); +// } - // ===================================================== - // HANDLE PASSWORD UPDATE - // ===================================================== +// // ===================================================== +// // HANDLE PASSWORD UPDATE +// // ===================================================== - if (updateData.newPassword) { - const hashedPassword = await bcrypt.hash(updateData.newPassword, 10); +// if (updateData.newPassword) { +// const hashedPassword = await bcrypt.hash(updateData.newPassword, 10); - updateData.password = hashedPassword; +// updateData.password = hashedPassword; - delete updateData.newPassword; - } +// delete updateData.newPassword; +// } - // ===================================================== - // HANDLE PROFILE IMAGE - // ===================================================== +// // ===================================================== +// // HANDLE PROFILE IMAGE +// // ===================================================== - if ( - updateData.profileImage && - !updateData.profileImage.startsWith("http") - ) { - try { - let imageBase64 = updateData.profileImage.trim(); +// if ( +// updateData.profileImage && +// !updateData.profileImage.startsWith("http") +// ) { +// try { +// let imageBase64 = updateData.profileImage.trim(); - // ADD DATA URI IF RAW BASE64 +// // ADD DATA URI IF RAW BASE64 - if (!imageBase64.startsWith("data:")) { - imageBase64 = `data:image/jpeg;base64,${imageBase64}`; - } +// if (!imageBase64.startsWith("data:")) { +// imageBase64 = `data:image/jpeg;base64,${imageBase64}`; +// } - const uploadResult = await cloudinary.uploader.upload(imageBase64, { - folder: "users", +// const uploadResult = await cloudinary.uploader.upload(imageBase64, { +// folder: "users", - public_id: `user_${user.id}_${Date.now()}`, +// public_id: `user_${user.id}_${Date.now()}`, - transformation: [ - { - width: 500, - height: 500, - crop: "fill", - }, - ], - }); +// transformation: [ +// { +// width: 500, +// height: 500, +// crop: "fill", +// }, +// ], +// }); - updateData.profileImage = uploadResult.secure_url; - } catch (err) { - console.error("Cloudinary Upload Error:", err); +// updateData.profileImage = uploadResult.secure_url; +// } catch (err) { +// console.error("Cloudinary Upload Error:", err); - return res.status(400).json({ - success: false, +// return res.status(400).json({ +// success: false, - field: "profileImage", +// field: "profileImage", - message: "Profile image upload failed.", +// message: "Profile image upload failed.", - details: "Please try another image or use a smaller file size.", - }); - } - } +// details: "Please try another image or use a smaller file size.", +// }); +// } +// } - // ===================================================== - // REMOVE UNSAFE FIELDS - // ===================================================== +// // ===================================================== +// // REMOVE UNSAFE FIELDS +// // ===================================================== - delete updateData.id; - delete updateData.createdAt; - delete updateData.updatedAt; +// delete updateData.id; +// delete updateData.createdAt; +// delete updateData.updatedAt; - // ===================================================== - // UPDATE USER - // ===================================================== +// // ===================================================== +// // UPDATE USER +// // ===================================================== - await db.User.update(updateData, { - where: { - id: userId, - }, - }); +// await db.User.update(updateData, { +// where: { +// id: userId, +// }, +// }); - // ===================================================== - // FETCH UPDATED USER - // ===================================================== +// // ===================================================== +// // FETCH UPDATED USER +// // ===================================================== - const updatedUser = await db.User.findByPk(userId); +// const updatedUser = await db.User.findByPk(userId); - // ===================================================== - // SUCCESS RESPONSE - // ===================================================== +// // ===================================================== +// // SUCCESS RESPONSE +// // ===================================================== - return res.status(200).json({ - success: true, +// return res.status(200).json({ +// success: true, - message: "Your account has been updated successfully.", +// message: "Your account has been updated successfully.", - user: updatedUser, - }); - } catch (err) { - console.error("User Update Route Error:", err); +// user: updatedUser, +// }); +// } catch (err) { +// console.error("User Update Route Error:", err); - return res.status(500).json({ - success: false, +// return res.status(500).json({ +// success: false, - message: "Something went wrong while updating your account.", +// message: "Something went wrong while updating your account.", - details: "Please try again in a few moments.", - }); - } -}); +// details: "Please try again in a few moments.", +// }); +// } +// }); router.post("/signUp", async (req, res) => { const { email, password } = req.body; From 5af6d6f471dd015310628a20fcdc9304fd82715f Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 28 May 2026 18:14:27 -0400 Subject: [PATCH 3/5] updates --- .../User/IT-Ticket/CreateTicket.tsx | 803 ++++++++++++++---- 1 file changed, 639 insertions(+), 164 deletions(-) diff --git a/client/src/containers/User/IT-Ticket/CreateTicket.tsx b/client/src/containers/User/IT-Ticket/CreateTicket.tsx index 2e3edf2..d08f710 100644 --- a/client/src/containers/User/IT-Ticket/CreateTicket.tsx +++ b/client/src/containers/User/IT-Ticket/CreateTicket.tsx @@ -29,12 +29,20 @@ interface Ticket { } const CreateTicket = () => { + // ===================================================== + // USER INFO + // ===================================================== + const [name, setName] = useState(""); const [email, setEmail] = useState(""); + // ===================================================== + // FORM + // ===================================================== + const [subject, setSubject] = useState(""); @@ -46,6 +54,10 @@ const CreateTicket = () => { const [priority, setPriority] = useState("normal"); + // ===================================================== + // UI STATE + // ===================================================== + const [loading, setLoading] = useState(false); @@ -55,6 +67,13 @@ const CreateTicket = () => { const [error, setError] = useState(""); + const [infoMessage, setInfoMessage] = + useState(""); + + // ===================================================== + // TICKETS + // ===================================================== + const [tickets, setTickets] = useState([]); @@ -66,34 +85,172 @@ const CreateTicket = () => { setSortBy, ] = useState("newest"); - // ----------------------------------- + // ===================================================== + // AUTO LOAD USER + // ===================================================== + + useEffect(() => { + const storedUser = + localStorage.getItem("user"); + + if (!storedUser) return; + + try { + const parsed = + JSON.parse(storedUser); + + setName( + parsed.name || + parsed.fullName || + "" + ); + + setEmail( + parsed.email || "" + ); + } catch (err) { + console.error( + "User Parse Error:", + err + ); + } + }, []); + + // ===================================================== // FETCH USER TICKETS - // ----------------------------------- + // ===================================================== const fetchTickets = async ( userEmail: string ) => { try { + if ( + !userEmail || + !userEmail.trim() + ) { + return; + } + + const cleanEmail = + userEmail + .toLowerCase() + .trim(); + const res = await axios.get( - "/api/it-help/view" + "/api/it-help/view", + { + withCredentials: true, + } ); + const allTickets = + Array.isArray(res.data) + ? res.data + : []; + const filtered = - res.data.filter( + allTickets.filter( (ticket: Ticket) => - ticket.email === - userEmail + ticket.email + ?.toLowerCase() + .trim() === + cleanEmail ); setTickets(filtered); } catch (err) { - console.error(err); + console.error( + "Fetch Tickets Error:", + err + ); + } + }; + + // ===================================================== + // LOAD TICKETS WHEN EMAIL EXISTS + // ===================================================== + + useEffect(() => { + if (email.trim()) { + fetchTickets(email); + } + }, [email]); + + // ===================================================== + // VALIDATION + // ===================================================== + + const validateForm = () => { + if (!name.trim()) { + setError( + "Your full name is required before creating a support ticket." + ); + + return false; + } + + if (!email.trim()) { + setError( + "Your email address is required so support can identify and track your tickets." + ); + + return false; + } + + if ( + !email.includes("@") || + !email.includes(".") + ) { + setError( + "Please enter a valid email address." + ); + + return false; + } + + if (!subject.trim()) { + setError( + "A ticket subject is required." + ); + + return false; + } + + if ( + subject.trim().length < 5 + ) { + setError( + "Ticket subject must be at least 5 characters long." + ); + + return false; + } + + if (!description.trim()) { + setError( + "Please explain the issue before submitting your ticket." + ); + + return false; + } + + if ( + description.trim() + .length < 15 + ) { + setError( + "Please provide a more detailed explanation of the issue." + ); + + return false; } + + return true; }; - // ----------------------------------- + // ===================================================== // SUBMIT - // ----------------------------------- + // ===================================================== const submitTicket = async ( @@ -102,73 +259,136 @@ const CreateTicket = () => { e.preventDefault(); setLoading(true); + setSuccess(""); + setError(""); + setInfoMessage(""); + try { + const valid = + validateForm(); + + if (!valid) { + setLoading(false); + return; + } + + const payload = { + name: + name.trim(), + + email: + email + .toLowerCase() + .trim(), + + subject: + subject.trim(), + + description: + description.trim(), + + priority, + + status: "open", + }; + await axios.post( "/api/it-help/post-ticket", + payload, { - name, - email, - subject, - description, - priority, - status: "open", + withCredentials: true, } ); setSuccess( - "Ticket created successfully." + "Support ticket created successfully. Your request has been submitted and is now being tracked." + ); + + setInfoMessage( + "Your tickets are linked to your email address. Use the same email every time to view your ticket history." ); setSubject(""); + setDescription(""); - setPriority("normal"); - fetchTickets(email); - } catch (err) { - console.error(err); + setPriority( + "normal" + ); - setError( - "Unable to create ticket." + await fetchTickets( + email + ); + } catch (err: any) { + console.error( + "Create Ticket Error:", + err ); + + if ( + err?.response?.status === + 400 + ) { + setError( + err.response.data + ?.message || + "Some required ticket information is missing." + ); + } else if ( + err?.response?.status === + 401 + ) { + setError( + "Your session expired. Please sign in again." + ); + } else if ( + err?.response?.status === + 500 + ) { + setError( + "The server encountered an error while creating your ticket. Please try again." + ); + } else { + setError( + "Unable to create ticket right now. Please try again." + ); + } } finally { setLoading(false); } }; - // ----------------------------------- - // LOAD TICKETS AFTER EMAIL - // ----------------------------------- - - useEffect(() => { - if (email.trim()) { - fetchTickets(email); - } - }, [email]); - - // ----------------------------------- + // ===================================================== // FILTER + SORT - // ----------------------------------- + // ===================================================== const filteredTickets = useMemo(() => { - let filtered = tickets.filter( - (ticket) => - ticket.subject - ?.toLowerCase() - .includes( - search.toLowerCase() - ) || - ticket.description - ?.toLowerCase() - .includes( - search.toLowerCase() - ) - ); + let filtered = [ + ...tickets, + ]; + + filtered = + filtered.filter( + (ticket) => + ticket.subject + ?.toLowerCase() + .includes( + search.toLowerCase() + ) || + ticket.description + ?.toLowerCase() + .includes( + search.toLowerCase() + ) + ); - if (sortBy === "newest") { + if ( + sortBy === "newest" + ) { filtered.sort( (a, b) => new Date( @@ -180,7 +400,9 @@ const CreateTicket = () => { ); } - if (sortBy === "oldest") { + if ( + sortBy === "oldest" + ) { filtered.sort( (a, b) => new Date( @@ -192,16 +414,40 @@ const CreateTicket = () => { ); } + if ( + sortBy === "priority" + ) { + const order: any = { + urgent: 4, + high: 3, + normal: 2, + low: 1, + }; + + filtered.sort( + (a, b) => + (order[ + b.priority || "" + ] || 0) - + (order[ + a.priority || "" + ] || 0) + ); + } + return filtered; - }, [tickets, search, sortBy]); + }, [ + tickets, + search, + sortBy, + ]); - // ----------------------------------- + // ===================================================== // RENDER - // ----------------------------------- + // ===================================================== return (
-
@@ -209,11 +455,9 @@ const CreateTicket = () => {
- {/* HERO */}
-

Support Command Center

@@ -224,17 +468,14 @@ const CreateTicket = () => { and manage your issues in real time.

-
{/* GRID */}
- {/* LEFT */}
-
{

-
+
+ Important Ticket Information +
- {/* NAME + EMAIL GRID */} +
    +
  • + Your email + address is used + to load your + tickets. +
  • + +
  • + Always use the + same email to + access your + ticket history. +
  • + +
  • + Ticket subjects + should clearly + explain the + issue. +
  • + +
  • + Detailed + descriptions + help support + resolve issues + faster. +
  • +
+
-
+ {/* FORM */} -
+ + {/* NAME + EMAIL */} -