11import { Student } from "@/models/student.model" ;
22import Link from "next/link" ;
3- import React from "react" ;
3+ import React , { useRef } from "react" ;
4+ import { Upload , X } from "lucide-react" ;
45
56interface ProfileHeaderProps {
67 student : Student | null ;
@@ -12,18 +13,22 @@ interface ProfileHeaderProps {
1213 email : string ;
1314 codeforcesHandle : string ;
1415 avatarUrl : string | null ;
16+ avatarFile : File | null ;
17+ avatarPreview : string | null ;
1518 } ;
1619 onEditToggle : ( ) => void ;
1720 onSave : ( ) => void ;
1821 onInputChange : ( field : string , value : string ) => void ;
19- onAvatarUrlChange : ( e : React . ChangeEvent < HTMLInputElement > ) => void ;
22+ onAvatarFileChange : ( file : File | null , preview : string | null ) => void ;
2023 setFormData : React . Dispatch <
2124 React . SetStateAction < {
2225 name : string ;
2326 surname : string ;
2427 email : string ;
2528 codeforcesHandle : string ;
2629 avatarUrl : string | null ;
30+ avatarFile : File | null ;
31+ avatarPreview : string | null ;
2732 } >
2833 > ;
2934}
@@ -36,9 +41,45 @@ export const ProfileHeader = ({
3641 onEditToggle,
3742 onSave,
3843 onInputChange,
39- onAvatarUrlChange ,
44+ onAvatarFileChange ,
4045 setFormData,
4146} : ProfileHeaderProps ) => {
47+ const fileInputRef = useRef < HTMLInputElement > ( null ) ;
48+
49+ const handleAvatarChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
50+ const file = e . target . files ?. [ 0 ] ;
51+ if ( file ) {
52+ // Validar tipo de archivo
53+ if ( ! file . type . startsWith ( "image/" ) ) {
54+ alert ( "Por favor, selecciona un archivo de imagen válido." ) ;
55+ return ;
56+ }
57+ // Validar tamaño (1MB)
58+ if ( file . size > 5 * 1024 * 1024 ) {
59+ alert ( "El archivo es demasiado grande. Máximo 1MB." ) ;
60+ return ;
61+ }
62+ // Crear preview
63+ const reader = new FileReader ( ) ;
64+ reader . onloadend = ( ) => {
65+ onAvatarFileChange ( file , reader . result as string ) ;
66+ } ;
67+ reader . readAsDataURL ( file ) ;
68+ }
69+ } ;
70+
71+ const handleRemoveAvatar = ( ) => {
72+ onAvatarFileChange ( null , null ) ;
73+ if ( fileInputRef . current ) {
74+ fileInputRef . current . value = "" ;
75+ }
76+ } ;
77+
78+ // Determinar qué imagen mostrar
79+ const displayImage =
80+ formData . avatarPreview ||
81+ ( isEditing ? formData . avatarUrl : student ?. avatar ) ||
82+ null ;
4283 return (
4384 < section className = "bg-(--white) dark:bg-gray-800 rounded-xl shadow-sm border border-(--azul-niebla) dark:border-gray-700" >
4485 < div className = "p-6 border-b border-(--azul-niebla) dark:border-gray-700 flex items-center justify-start" >
@@ -72,17 +113,18 @@ export const ProfileHeader = ({
72113 { /* Avatar editable */ }
73114 < div className = "flex flex-col items-center md:w-1/3" >
74115 < div className = "relative w-28 h-28 md:w-32 md:h-32 rounded-full overflow-hidden ring-4 ring-(--azul-niebla) dark:ring-blue-900" >
75- { ( isEditing ? formData . avatarUrl : student ?. avatar ) ? (
116+ { displayImage ? (
76117 < img
77- src = {
78- ( isEditing ? formData . avatarUrl : student ?. avatar ) ||
79- undefined
80- }
118+ src = { displayImage }
81119 alt = "Avatar"
82120 className = "w-full h-full object-cover"
83121 onError = { ( ) => {
84122 if ( isEditing ) {
85- setFormData ( ( prev ) => ( { ...prev , avatarUrl : null } ) ) ;
123+ setFormData ( ( prev ) => ( {
124+ ...prev ,
125+ avatarUrl : null ,
126+ avatarPreview : null ,
127+ } ) ) ;
86128 }
87129 } }
88130 />
@@ -97,17 +139,37 @@ export const ProfileHeader = ({
97139 { isEditing && (
98140 < div className = "w-full mt-4 space-y-2" >
99141 < input
100- type = "url"
101- value = { formData . avatarUrl || "" }
102- onChange = { onAvatarUrlChange }
103- placeholder = "www.example.com/avatar.png"
104- className = "w-full px-3 py-2 rounded-lg bg-(--azul-niebla) dark:bg-gray-700 text-(--azul-noche) dark:text-white border border-(--azul-niebla) dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-(--azul-electrico) text-sm"
142+ ref = { fileInputRef }
143+ type = "file"
144+ accept = "image/jpeg,image/jpg,image/png,image/webp"
145+ onChange = { handleAvatarChange }
146+ className = "hidden"
147+ id = "avatar-upload"
105148 />
106- { formData . avatarUrl && (
107- < p className = "text-xs text-gray-500 dark:text-gray-400 text-center" >
108- La imagen se previsualizará arriba
109- </ p >
149+ < label
150+ htmlFor = "avatar-upload"
151+ className = "flex items-center justify-center gap-2 w-full px-3 py-2 rounded-lg bg-(--azul-niebla) dark:bg-gray-700 text-(--azul-noche) dark:text-white border border-(--azul-niebla) dark:border-gray-600 hover:bg-(--azul-crayon) dark:hover:bg-gray-600 cursor-pointer transition-colors text-sm font-medium"
152+ >
153+ < Upload className = "h-4 w-4" />
154+ < span >
155+ { formData . avatarFile
156+ ? formData . avatarFile . name
157+ : "Seleccionar imagen" }
158+ </ span >
159+ </ label >
160+ { formData . avatarFile && (
161+ < button
162+ type = "button"
163+ onClick = { handleRemoveAvatar }
164+ className = "flex items-center justify-center gap-2 w-full px-3 py-2 rounded-lg bg-red-500 hover:bg-red-600 text-white transition-colors text-sm font-medium"
165+ >
166+ < X className = "h-4 w-4" />
167+ < span > Eliminar imagen</ span >
168+ </ button >
110169 ) }
170+ < p className = "text-xs text-gray-500 dark:text-gray-400 text-center" >
171+ Formatos: JPEG, PNG, WEBP. Máximo 1MB
172+ </ p >
111173 </ div >
112174 ) }
113175 </ div >
0 commit comments