useAccountForm.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. import { useState, useEffect, useCallback } from "react"
  2. import { useSession } from "next-auth/react"
  3. import { useRouter } from "next/navigation"
  4. import { notifications } from "@/lib/notifications"
  5. import { useProfileImageContext } from "@/contexts/ProfileImageContext"
  6. import { validateMedicalInfo } from "@/components/account/MedicalInfoSection"
  7. interface FormData {
  8. name: string
  9. lastname: string
  10. email: string
  11. identificacion?: string
  12. currentPassword: string
  13. newPassword: string
  14. confirmPassword: string
  15. phone?: string
  16. dateOfBirth?: string
  17. gender?: string
  18. address?: string
  19. emergencyContact?: string
  20. medicalHistory?: string
  21. allergies?: string
  22. currentMedications?: string
  23. }
  24. export function useAccountForm() {
  25. const { data: session, update } = useSession()
  26. const router = useRouter()
  27. const { updateProfileImage } = useProfileImageContext()
  28. const [formData, setFormData] = useState<FormData>({
  29. name: "",
  30. lastname: "",
  31. email: "",
  32. currentPassword: "",
  33. newPassword: "",
  34. confirmPassword: "",
  35. phone: "",
  36. dateOfBirth: "",
  37. gender: "",
  38. address: "",
  39. emergencyContact: "",
  40. medicalHistory: "",
  41. allergies: "",
  42. currentMedications: ""
  43. })
  44. const [loading, setLoading] = useState(false)
  45. const [profileImage, setProfileImage] = useState<string | null>(null)
  46. const [imageFile, setImageFile] = useState<File | null>(null)
  47. const [loadingImage, setLoadingImage] = useState(false)
  48. const [imageLoaded, setImageLoaded] = useState(false)
  49. const [isUpdating, setIsUpdating] = useState(false) // Flag para prevenir race conditions
  50. const loadProfileImage = useCallback(async () => {
  51. if (!session?.user?.id || imageLoaded) return
  52. setLoadingImage(true)
  53. try {
  54. const response = await fetch("/api/account/profile-image", {
  55. method: 'GET',
  56. headers: {
  57. 'Content-Type': 'application/json',
  58. },
  59. })
  60. if (response.ok) {
  61. const data = await response.json()
  62. setProfileImage(data.profileImage)
  63. } else {
  64. console.log(`Error HTTP: ${response.status}`)
  65. setProfileImage(null)
  66. }
  67. } catch (error) {
  68. console.error("Error cargando imagen de perfil:", error)
  69. setProfileImage(null)
  70. } finally {
  71. setLoadingImage(false)
  72. setImageLoaded(true)
  73. }
  74. }, [session?.user?.id, imageLoaded])
  75. // Cargar datos del usuario cuando la sesión esté disponible
  76. useEffect(() => {
  77. // No actualizar formData si estamos en medio de una actualización manual
  78. if (session?.user && !isUpdating) {
  79. setFormData(prev => ({
  80. ...prev,
  81. name: session.user.name || "",
  82. lastname: session.user.lastname || "",
  83. email: session.user.email || "",
  84. identificacion: session.user.identificacion || "",
  85. phone: session.user.phone || "",
  86. dateOfBirth: session.user.dateOfBirth ? new Date(session.user.dateOfBirth).toISOString().split('T')[0] : "",
  87. gender: session.user.gender || "",
  88. address: session.user.address || "",
  89. emergencyContact: session.user.emergencyContact || "",
  90. medicalHistory: session.user.medicalHistory || "",
  91. allergies: session.user.allergies || "",
  92. currentMedications: session.user.currentMedications || ""
  93. }))
  94. }
  95. }, [session, isUpdating])
  96. // Cargar imagen de perfil cuando el usuario esté autenticado
  97. useEffect(() => {
  98. if (session?.user?.id && !imageLoaded) {
  99. loadProfileImage()
  100. }
  101. }, [session?.user?.id, imageLoaded, loadProfileImage])
  102. const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
  103. const { name, value } = e.target
  104. setFormData(prev => ({
  105. ...prev,
  106. [name]: value
  107. }))
  108. }
  109. const handleImageChange = (file: File | null) => {
  110. setImageFile(file)
  111. if (file) {
  112. // Crear preview
  113. const reader = new FileReader()
  114. reader.onload = (e) => {
  115. setProfileImage(e.target?.result as string)
  116. }
  117. reader.readAsDataURL(file)
  118. } else {
  119. setImageLoaded(false)
  120. }
  121. }
  122. const handleImageRemove = async () => {
  123. if (!profileImage) return
  124. setLoadingImage(true)
  125. try {
  126. const response = await fetch("/api/account/profile-image", {
  127. method: 'DELETE',
  128. headers: {
  129. 'Content-Type': 'application/json',
  130. },
  131. })
  132. if (response.ok) {
  133. setProfileImage(null)
  134. setImageFile(null)
  135. setImageLoaded(false)
  136. // Actualizar el contexto global
  137. updateProfileImage(null)
  138. // Actualizar la sesión
  139. await update({
  140. profileImage: null
  141. })
  142. notifications.profile.imageRemoved()
  143. } else {
  144. const data = await response.json()
  145. notifications.profile.imageRemoveError(data.error)
  146. }
  147. } catch (error) {
  148. console.error("Error eliminando imagen:", error)
  149. notifications.profile.imageRemoveError()
  150. } finally {
  151. setLoadingImage(false)
  152. }
  153. }
  154. const handleSubmit = async (e: React.FormEvent) => {
  155. e.preventDefault()
  156. setLoading(true)
  157. setIsUpdating(true) // Activar flag para prevenir race condition
  158. try {
  159. // Validar información médica
  160. const medicalValidation = validateMedicalInfo({
  161. phone: formData.phone,
  162. gender: formData.gender,
  163. address: formData.address,
  164. emergencyContact: formData.emergencyContact,
  165. medicalHistory: formData.medicalHistory,
  166. allergies: formData.allergies,
  167. currentMedications: formData.currentMedications
  168. })
  169. if (!medicalValidation.isValid) {
  170. const errorMessages = Object.values(medicalValidation.errors).join(', ')
  171. notifications.profile.updateError(`Errores de validación: ${errorMessages}`)
  172. setLoading(false)
  173. return
  174. }
  175. // Validaciones de contraseña
  176. if (formData.newPassword && formData.newPassword !== formData.confirmPassword) {
  177. notifications.auth.passwordMismatch()
  178. setLoading(false)
  179. return
  180. }
  181. if (formData.newPassword && formData.newPassword.length < 6) {
  182. notifications.auth.passwordTooShort()
  183. setLoading(false)
  184. return
  185. }
  186. const formDataToSend = new FormData()
  187. formDataToSend.append('name', formData.name)
  188. formDataToSend.append('lastname', formData.lastname)
  189. formDataToSend.append('email', formData.email)
  190. if (formData.currentPassword) {
  191. formDataToSend.append('currentPassword', formData.currentPassword)
  192. }
  193. if (formData.newPassword) {
  194. formDataToSend.append('newPassword', formData.newPassword)
  195. }
  196. // Agregar campos médicos y personales
  197. if (formData.phone) {
  198. formDataToSend.append('phone', formData.phone)
  199. }
  200. // dateOfBirth NO se envía - es provisto por la API UTB y no es editable
  201. if (formData.gender) {
  202. formDataToSend.append('gender', formData.gender)
  203. }
  204. if (formData.address) {
  205. formDataToSend.append('address', formData.address)
  206. }
  207. if (formData.emergencyContact) {
  208. formDataToSend.append('emergencyContact', formData.emergencyContact)
  209. }
  210. if (formData.medicalHistory) {
  211. formDataToSend.append('medicalHistory', formData.medicalHistory)
  212. }
  213. if (formData.allergies) {
  214. formDataToSend.append('allergies', formData.allergies)
  215. }
  216. if (formData.currentMedications) {
  217. formDataToSend.append('currentMedications', formData.currentMedications)
  218. }
  219. if (imageFile) {
  220. formDataToSend.append('profileImage', imageFile)
  221. }
  222. const response = await fetch("/api/account/update", {
  223. method: "POST",
  224. body: formDataToSend
  225. })
  226. const data = await response.json()
  227. if (response.ok) {
  228. notifications.profile.updated()
  229. // Actualizar formData local con los datos guardados
  230. setFormData(prev => ({
  231. ...prev,
  232. name: data.user.name || "",
  233. lastname: data.user.lastname || "",
  234. email: data.user.email || "",
  235. phone: data.user.phone || "",
  236. dateOfBirth: data.user.dateOfBirth ? new Date(data.user.dateOfBirth).toISOString().split('T')[0] : "",
  237. gender: data.user.gender || "",
  238. address: data.user.address || "",
  239. emergencyContact: data.user.emergencyContact || "",
  240. medicalHistory: data.user.medicalHistory || "",
  241. allergies: data.user.allergies || "",
  242. currentMedications: data.user.currentMedications || "",
  243. // Limpiar contraseñas
  244. currentPassword: "",
  245. newPassword: "",
  246. confirmPassword: ""
  247. }))
  248. // Actualizar la sesión de NextAuth con todos los datos nuevos
  249. await update({
  250. name: data.user.name,
  251. lastname: data.user.lastname,
  252. email: data.user.email,
  253. profileImage: data.user.profileImage,
  254. phone: data.user.phone,
  255. dateOfBirth: data.user.dateOfBirth,
  256. gender: data.user.gender,
  257. address: data.user.address,
  258. emergencyContact: data.user.emergencyContact,
  259. medicalHistory: data.user.medicalHistory,
  260. allergies: data.user.allergies,
  261. currentMedications: data.user.currentMedications
  262. })
  263. // Actualizar la imagen de perfil en el contexto global
  264. if (data.user.profileImage) {
  265. updateProfileImage(data.user.profileImage)
  266. }
  267. // Recargar imagen de perfil si se actualizó
  268. if (imageFile) {
  269. setImageLoaded(false)
  270. setTimeout(() => {
  271. loadProfileImage()
  272. }, 500)
  273. }
  274. // Desactivar flag después de que la sesión se haya actualizado
  275. setTimeout(() => {
  276. setIsUpdating(false)
  277. }, 500)
  278. } else {
  279. notifications.profile.updateError(data.error)
  280. setIsUpdating(false)
  281. }
  282. } catch (error) {
  283. console.error("Error updating profile:", error)
  284. notifications.profile.updateError()
  285. setIsUpdating(false)
  286. } finally {
  287. setLoading(false)
  288. }
  289. }
  290. return {
  291. formData,
  292. loading,
  293. profileImage,
  294. loadingImage,
  295. imageLoaded,
  296. handleInputChange,
  297. handleImageChange,
  298. handleImageRemove,
  299. handleSubmit,
  300. loadProfileImage
  301. }
  302. }