@ -1,11 +1,72 @@
'use client' ;
import { useState , useEffect } from 'react' ;
import { Loader2 , AlertCircle , CheckCircle , Info } from 'lucide-react' ;
import { Loader2 , AlertCircle , CheckCircle , Info , Pencil } from 'lucide-react' ;
import { getClubProfile , updateClubProfile } from '@/src/lib/api/courts' ;
import type { ClubProfile , ClubProfileUpdateRequest } from '@/src/types/courts' ;
import { COMMON_TIMEZONES , isValidEmail , isValidUrl } from '@/src/types/courts' ;
/ * *
* Editable Field Component - Click to edit with pencil icon
* /
interface EditableFieldProps {
fieldName : string ;
value : string ;
onChange : ( value : string ) = > void ;
isEditing : boolean ;
onToggleEdit : ( ) = > void ;
placeholder? : string ;
maxLength? : number ;
type ? : 'text' | 'email' | 'tel' | 'url' ;
error? : string ;
disabled? : boolean ;
}
function EditableField ( {
fieldName ,
value ,
onChange ,
isEditing ,
onToggleEdit ,
placeholder ,
maxLength ,
type = 'text' ,
error ,
disabled = false ,
} : EditableFieldProps ) {
const hasValue = value . trim ( ) . length > 0 ;
if ( isEditing || ! hasValue ) {
return (
< input
type = { type }
value = { value }
onChange = { ( e ) = > onChange ( e . target . value ) }
onBlur = { ( ) = > hasValue && onToggleEdit ( ) }
autoFocus
placeholder = { placeholder }
maxLength = { maxLength }
className = { ` w-full px-4 py-3 border-2 rounded-lg font-medium transition-colors ${
error
? 'border-red-300 focus:border-red-500'
: 'border-slate-200 focus:border-slate-900'
} focus :outline - none ` }
disabled = { disabled }
/ >
) ;
}
return (
< div
onClick = { onToggleEdit }
className = "flex items-center justify-between px-4 py-3 border-2 border-slate-200 rounded-lg cursor-pointer hover:border-slate-300 hover:bg-slate-50 transition-colors group"
>
< span className = "font-medium text-slate-900" > { value } < / span >
< Pencil className = "w-4 h-4 text-slate-400 opacity-0 group-hover:opacity-100 transition-opacity" / >
< / div >
) ;
}
interface ClubProfileTabProps {
clubId : number ;
onUpdate ? : ( ) = > void ;
@ -31,6 +92,9 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
const [ email , setEmail ] = useState ( '' ) ;
const [ website , setWebsite ] = useState ( '' ) ;
// Edit mode tracking for each field
const [ editingFields , setEditingFields ] = useState < Set < string > > ( new Set ( ) ) ;
useEffect ( ( ) = > {
loadProfile ( ) ;
} , [ clubId ] ) ;
@ -168,6 +232,27 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
setWebsite ( profile . settings ? . contact ? . website || '' ) ;
setErrors ( { } ) ;
setError ( null ) ;
setEditingFields ( new Set ( ) ) ;
}
function toggleEditField ( fieldName : string ) {
setEditingFields ( prev = > {
const next = new Set ( prev ) ;
if ( next . has ( fieldName ) ) {
next . delete ( fieldName ) ;
} else {
next . add ( fieldName ) ;
}
return next ;
} ) ;
}
function isFieldEditing ( fieldName : string ) : boolean {
return editingFields . has ( fieldName ) ;
}
function hasFieldValue ( value : string ) : boolean {
return value . trim ( ) . length > 0 ;
}
// Loading state
@ -226,17 +311,29 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
< label className = "block text-sm font-semibold text-slate-900 mb-2" >
Name < span className = "text-red-600" > * < / span >
< / label >
< input
type = "text"
value = { name }
onChange = { ( e ) = > setName ( e . target . value ) }
className = { ` w-full px-4 py-3 border-2 rounded-lg font-medium transition-colors ${
errors . name
? 'border-red-300 focus:border-red-500'
: 'border-slate-200 focus:border-slate-900'
} focus :outline - none ` }
disabled = { saving }
/ >
{ isFieldEditing ( 'name' ) || ! hasFieldValue ( name ) ? (
< input
type = "text"
value = { name }
onChange = { ( e ) = > setName ( e . target . value ) }
onBlur = { ( ) = > hasFieldValue ( name ) && toggleEditField ( 'name' ) }
autoFocus
className = { ` w-full px-4 py-3 border-2 rounded-lg font-medium transition-colors ${
errors . name
? 'border-red-300 focus:border-red-500'
: 'border-slate-200 focus:border-slate-900'
} focus :outline - none ` }
disabled = { saving }
/ >
) : (
< div
onClick = { ( ) = > toggleEditField ( 'name' ) }
className = "flex items-center justify-between px-4 py-3 border-2 border-slate-200 rounded-lg cursor-pointer hover:border-slate-300 hover:bg-slate-50 transition-colors group"
>
< span className = "font-medium text-slate-900" > { name } < / span >
< Pencil className = "w-4 h-4 text-slate-400 opacity-0 group-hover:opacity-100 transition-opacity" / >
< / div >
) }
{ errors . name && (
< p className = "mt-1 text-sm text-red-600" > { errors . name } < / p >
) }
@ -247,22 +344,36 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
< label className = "block text-sm font-semibold text-slate-900 mb-2" >
Timezone < span className = "text-red-600" > * < / span >
< / label >
< select
value = { timezone }
onChange = { ( e ) = > setTimezone ( e . target . value ) }
className = { ` w-full px-4 py-3 border-2 rounded-lg font-medium transition-colors ${
errors . timezone
? 'border-red-300 focus:border-red-500'
: 'border-slate-200 focus:border-slate-900'
} focus :outline - none ` }
disabled = { saving }
>
{ COMMON_TIMEZONES . map ( ( tz ) = > (
< option key = { tz . value } value = { tz . value } >
{ tz . label }
< / option >
) ) }
< / select >
{ isFieldEditing ( 'timezone' ) ? (
< select
value = { timezone }
onChange = { ( e ) = > setTimezone ( e . target . value ) }
onBlur = { ( ) = > toggleEditField ( 'timezone' ) }
autoFocus
className = { ` w-full px-4 py-3 border-2 rounded-lg font-medium transition-colors ${
errors . timezone
? 'border-red-300 focus:border-red-500'
: 'border-slate-200 focus:border-slate-900'
} focus :outline - none ` }
disabled = { saving }
>
{ COMMON_TIMEZONES . map ( ( tz ) = > (
< option key = { tz . value } value = { tz . value } >
{ tz . label }
< / option >
) ) }
< / select >
) : (
< div
onClick = { ( ) = > toggleEditField ( 'timezone' ) }
className = "flex items-center justify-between px-4 py-3 border-2 border-slate-200 rounded-lg cursor-pointer hover:border-slate-300 hover:bg-slate-50 transition-colors group"
>
< span className = "font-medium text-slate-900" >
{ COMMON_TIMEZONES . find ( tz = > tz . value === timezone ) ? . label || timezone }
< / span >
< Pencil className = "w-4 h-4 text-slate-400 opacity-0 group-hover:opacity-100 transition-opacity" / >
< / div >
) }
{ errors . timezone && (
< p className = "mt-1 text-sm text-red-600" > { errors . timezone } < / p >
) }
@ -288,13 +399,14 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
< label className = "block text-sm font-semibold text-slate-900 mb-2" >
Address Line 1
< / label >
< inpu t
type = "text "
< Ed itableField
fieldName = "addressLine1 "
value = { addressLine1 }
onChange = { ( e ) = > setAddressLine1 ( e . target . value ) }
onChange = { setAddressLine1 }
isEditing = { isFieldEditing ( 'addressLine1' ) }
onToggleEdit = { ( ) = > toggleEditField ( 'addressLine1' ) }
placeholder = "123 High Street"
maxLength = { 200 }
className = "w-full px-4 py-3 border-2 border-slate-200 rounded-lg font-medium focus:border-slate-900 focus:outline-none transition-colors"
disabled = { saving }
/ >
< / div >
@ -304,13 +416,14 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
< label className = "block text-sm font-semibold text-slate-900 mb-2" >
Address Line 2
< / label >
< inpu t
type = "text "
< Ed itableField
fieldName = "addressLine2 "
value = { addressLine2 }
onChange = { ( e ) = > setAddressLine2 ( e . target . value ) }
onChange = { setAddressLine2 }
isEditing = { isFieldEditing ( 'addressLine2' ) }
onToggleEdit = { ( ) = > toggleEditField ( 'addressLine2' ) }
placeholder = "Building A"
maxLength = { 200 }
className = "w-full px-4 py-3 border-2 border-slate-200 rounded-lg font-medium focus:border-slate-900 focus:outline-none transition-colors"
disabled = { saving }
/ >
< / div >
@ -321,13 +434,14 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
< label className = "block text-sm font-semibold text-slate-900 mb-2" >
City
< / label >
< inpu t
type = "text "
< Ed itableField
fieldName = "city "
value = { city }
onChange = { ( e ) = > setCity ( e . target . value ) }
onChange = { setCity }
isEditing = { isFieldEditing ( 'city' ) }
onToggleEdit = { ( ) = > toggleEditField ( 'city' ) }
placeholder = "London"
maxLength = { 100 }
className = "w-full px-4 py-3 border-2 border-slate-200 rounded-lg font-medium focus:border-slate-900 focus:outline-none transition-colors"
disabled = { saving }
/ >
< / div >
@ -336,13 +450,14 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
< label className = "block text-sm font-semibold text-slate-900 mb-2" >
Postal Code
< / label >
< inpu t
type = "text "
< Ed itableField
fieldName = "postalCode "
value = { postalCode }
onChange = { ( e ) = > setPostalCode ( e . target . value ) }
onChange = { setPostalCode }
isEditing = { isFieldEditing ( 'postalCode' ) }
onToggleEdit = { ( ) = > toggleEditField ( 'postalCode' ) }
placeholder = "SW1A 1AA"
maxLength = { 20 }
className = "w-full px-4 py-3 border-2 border-slate-200 rounded-lg font-medium focus:border-slate-900 focus:outline-none transition-colors"
disabled = { saving }
/ >
< / div >
@ -353,13 +468,14 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
< label className = "block text-sm font-semibold text-slate-900 mb-2" >
Country
< / label >
< inpu t
type = "text "
< Ed itableField
fieldName = "country "
value = { country }
onChange = { ( e ) = > setCountry ( e . target . value ) }
onChange = { setCountry }
isEditing = { isFieldEditing ( 'country' ) }
onToggleEdit = { ( ) = > toggleEditField ( 'country' ) }
placeholder = "United Kingdom"
maxLength = { 100 }
className = "w-full px-4 py-3 border-2 border-slate-200 rounded-lg font-medium focus:border-slate-900 focus:outline-none transition-colors"
disabled = { saving }
/ >
< / div >
@ -384,13 +500,15 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
< label className = "block text-sm font-semibold text-slate-900 mb-2" >
Phone
< / label >
< inpu t
type = "tel "
< Ed itableField
fieldName = "phone "
value = { phone }
onChange = { ( e ) = > setPhone ( e . target . value ) }
onChange = { setPhone }
isEditing = { isFieldEditing ( 'phone' ) }
onToggleEdit = { ( ) = > toggleEditField ( 'phone' ) }
placeholder = "+44 20 1234 5678"
maxLength = { 50 }
className = "w-full px-4 py-3 border-2 border-slate-200 rounded-lg font-medium focus:border-slate-900 focus:outline-none transition-colors "
type = "tel "
disabled = { saving }
/ >
< / div >
@ -400,17 +518,16 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
< label className = "block text-sm font-semibold text-slate-900 mb-2" >
Email
< / label >
< inpu t
typ e= "email"
< Ed itableField
fieldNam e= "email"
value = { email }
onChange = { ( e ) = > setEmail ( e . target . value ) }
onChange = { setEmail }
isEditing = { isFieldEditing ( 'email' ) }
onToggleEdit = { ( ) = > toggleEditField ( 'email' ) }
placeholder = "info@centralpadel.com"
maxLength = { 100 }
className = { ` w-full px-4 py-3 border-2 rounded-lg font-medium transition-colors ${
errors . email
? 'border-red-300 focus:border-red-500'
: 'border-slate-200 focus:border-slate-900'
} focus :outline - none ` }
type = "email"
error = { errors . email }
disabled = { saving }
/ >
{ errors . email && (
@ -423,17 +540,16 @@ export default function ClubProfileTab({ clubId, onUpdate }: ClubProfileTabProps
< label className = "block text-sm font-semibold text-slate-900 mb-2" >
Website
< / label >
< inpu t
type = "url "
< Ed itableField
fieldName = "website "
value = { website }
onChange = { ( e ) = > setWebsite ( e . target . value ) }
onChange = { setWebsite }
isEditing = { isFieldEditing ( 'website' ) }
onToggleEdit = { ( ) = > toggleEditField ( 'website' ) }
placeholder = "https://www.centralpadel.com"
maxLength = { 200 }
className = { ` w-full px-4 py-3 border-2 rounded-lg font-medium transition-colors ${
errors . website
? 'border-red-300 focus:border-red-500'
: 'border-slate-200 focus:border-slate-900'
} focus :outline - none ` }
type = "url"
error = { errors . website }
disabled = { saving }
/ >
{ errors . website && (