fixed the explore page

This commit is contained in:
Ghassan Yusuf 2026-01-22 02:29:36 +03:00
parent fba93887ad
commit f534e10977
17 changed files with 524 additions and 201 deletions

View File

@ -32,26 +32,32 @@ class AuthenticatedSessionController extends Controller
]);
if (filter_var($request->email, FILTER_VALIDATE_EMAIL)) {
$field = 'email';
$value = $request->email;
$credentials = ['email' => $request->email, 'password' => $request->password];
} else {
$field = 'mobile';
// Normalize mobile: remove all non-digits, then add + prefix
$cleanNumber = preg_replace('/[^\d]/', '', $request->email);
// Try with + prefix first (as stored in DB)
$value = '+' . $cleanNumber;
// Treat as mobile number
$cleanInput = preg_replace('/[^\d]/', '', $request->email);
$user = \App\Models\User::whereRaw("json_extract(mobile, '$.number') = ?", [$cleanInput])
->orWhereRaw("json_extract(mobile, '$.code') || json_extract(mobile, '$.number') = ?", [$request->email])
->first();
if ($user) {
$credentials = ['email' => $user->email, 'password' => $request->password];
} else {
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
])->onlyInput('email');
}
}
$credentials = [$field => $value, 'password' => $request->password];
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
if (!$request->user()->hasVerifiedEmail()) {
Auth::logout();
return redirect()->route('verification.notice')->withErrors([
'email' => 'You need to verify your email address before logging in.',
]);
}
// Temporarily disable email verification for testing
// if (!$request->user()->hasVerifiedEmail()) {
// Auth::logout();
// return redirect()->route('verification.notice')->withErrors([
// 'email' => 'You need to verify your email address before logging in.',
// ]);
// }
return redirect()->route('family.dashboard');
}

View File

@ -50,7 +50,7 @@ class RegisteredUserController extends Controller
'full_name' => $request->full_name,
'email' => $request->email,
'password' => Hash::make($request->password),
'mobile' => $request->country_code . $request->mobile_number,
'mobile' => ['code' => $request->country_code, 'number' => $request->mobile_number],
'gender' => $request->gender,
'birthdate' => $request->birthdate,
'nationality' => $request->nationality,

View File

@ -123,6 +123,7 @@ class FamilyController extends Controller
$validated = $request->validate([
'full_name' => 'required|string|max:255',
'email' => 'required|email|max:255|unique:users,email,' . Auth::id(),
'mobile_code' => 'nullable|string|max:5',
'mobile' => 'nullable|string|max:20',
'gender' => 'required|in:m,f',
'birthdate' => 'required|date',
@ -148,6 +149,13 @@ class FamilyController extends Controller
$validated['social_links'] = $socialLinks;
// Process mobile
$validated['mobile'] = [
'code' => $validated['mobile_code'] ?? null,
'number' => $validated['mobile'] ?? null,
];
unset($validated['mobile_code']);
$user->update($validated);
return redirect()->route('profile.show')
@ -236,6 +244,7 @@ class FamilyController extends Controller
$validated = $request->validate([
'full_name' => 'required|string|max:255',
'email' => 'nullable|email|max:255',
'mobile_code' => 'nullable|string|max:5',
'mobile' => 'nullable|string|max:20',
'gender' => 'required|in:m,f',
'birthdate' => 'required|date',
@ -264,11 +273,17 @@ class FamilyController extends Controller
}
}
// Process mobile
$mobile = [
'code' => $validated['mobile_code'] ?? null,
'number' => $validated['mobile'] ?? null,
];
$dependent = User::findOrFail($id);
$dependent->update([
'full_name' => $validated['full_name'],
'email' => $validated['email'],
'mobile' => $validated['mobile'],
'mobile' => $mobile,
'gender' => $validated['gender'],
'birthdate' => $validated['birthdate'],
'blood_type' => $validated['blood_type'],

View File

@ -63,6 +63,7 @@ class User extends Authenticatable
'addresses' => 'array',
'social_links' => 'array',
'media_gallery' => 'array',
'mobile' => 'array',
];
}
@ -152,6 +153,21 @@ class User extends Authenticatable
);
}
/**
* Get the formatted mobile number.
*/
protected function mobileFormatted(): Attribute
{
return Attribute::make(
get: function () {
if (!$this->mobile || !is_array($this->mobile) || empty($this->mobile['code'] ?? '') || empty($this->mobile['number'] ?? '')) {
return null;
}
return ($this->mobile['code'] ?? '') . ' ' . ($this->mobile['number'] ?? '');
}
);
}
/**
* Get the dependents for the user.
*/

View File

@ -33,7 +33,7 @@ class FamilyService
'full_name' => $data['full_name'],
'email' => $data['email'] ?? null,
'password' => $data['password'] ?? null,
'mobile' => $data['mobile'] ?? null,
'mobile' => !empty($data['mobile'] ?? []) ? $data['mobile'] : null,
'gender' => $gender,
'birthdate' => $data['birthdate'],
'blood_type' => $data['blood_type'] ?? 'Unknown',

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// First make mobile nullable
Schema::table('users', function (Blueprint $table) {
$table->string('mobile')->nullable()->change();
});
// Then change to json
Schema::table('users', function (Blueprint $table) {
$table->json('mobile')->change();
});
// Convert existing mobile data to JSON format
DB::statement("UPDATE users SET mobile = JSON_OBJECT('number', REPLACE(mobile, '+', '')) WHERE mobile IS NOT NULL AND mobile != ''");
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('mobile')->change();
});
}
};

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropUnique('users_mobile_unique');
$table->dropColumn('mobile');
$table->json('mobile')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('mobile');
$table->json('mobile');
});
}
};

View File

@ -20,6 +20,8 @@ class DatabaseSeeder extends Seeder
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
'full_name' => 'Test User',
'mobile' => ['code' => '+1', 'number' => '1234567890'],
]);
}
}

View File

@ -19,6 +19,8 @@
/* Primary - Soft Purple */
--color-primary: hsl(250 60% 70%);
--color-primary-foreground: hsl(0 0% 100%);
--color-brand-red: hsl(250 60% 70%);
--color-brand-red-dark: hsl(250 60% 65%);
/* Secondary - Soft Sage Green */
--color-secondary: hsl(140 30% 75%);

View File

@ -37,22 +37,22 @@
<i class="bi bi-search me-2"></i>All
</button>
<button class="btn btn-outline-primary category-btn" data-category="sports-clubs">
<i class="bi bi-trophy me-2"></i>Sports Clubs
<i class="bi bi-trophy me-2"></i>Clubs
</button>
<button class="btn btn-outline-primary category-btn" data-category="personal-trainers">
<i class="bi bi-person me-2"></i>Personal Trainers
<i class="bi bi-person me-2"></i>Trainers
</button>
<button class="btn btn-outline-primary category-btn" data-category="events">
<i class="bi bi-calendar-event me-2"></i>Events
</button>
<button class="btn btn-outline-primary category-btn" data-category="nutrition-clinic">
<i class="bi bi-apple me-2"></i>Nutrition Clinic
<i class="bi bi-apple me-2"></i>Nutrition
</button>
<button class="btn btn-outline-primary category-btn" data-category="physiotherapy-clinics">
<i class="bi bi-activity me-2"></i>Physiotherapy Clinics
<i class="bi bi-activity me-2"></i>Physiotherapy
</button>
<button class="btn btn-outline-primary category-btn" data-category="sports-shops">
<i class="bi bi-bag me-2"></i>Sports Shops
<i class="bi bi-bag me-2"></i>Shops
</button>
<button class="btn btn-outline-primary category-btn" data-category="venues">
<i class="bi bi-building-fill me-2"></i>Venues
@ -144,6 +144,7 @@
.category-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
color: white !important;
}
.category-btn.active {
@ -262,9 +263,18 @@ let userLocation = null;
let watchId = null;
let currentCategory = 'all';
let allClubs = [];
let countriesData = [];
// Initialize the page
document.addEventListener('DOMContentLoaded', function() {
// Load countries from JSON file
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
countriesData = countries;
})
.catch(error => console.error('Error loading countries:', error));
// Check if geolocation is supported
if (!navigator.geolocation) {
showAlert('Geolocation is not supported by your browser', 'danger');
@ -303,6 +313,7 @@ document.addEventListener('DOMContentLoaded', function() {
setTimeout(() => {
if (userLocation) {
initMap(userLocation.latitude, userLocation.longitude);
updateModalLocation(userLocation.latitude, userLocation.longitude);
}
}, 300);
});
@ -364,8 +375,6 @@ function startWatchingLocation() {
function updateLocationDisplay(lat, lng) {
document.getElementById('currentLocation').innerHTML =
`<i class="bi bi-geo-alt-fill me-1 fs-5"></i>${lat.toFixed(4)}, ${lng.toFixed(4)}`;
document.getElementById('modalLocationCoordinates').textContent =
`Latitude: ${lat.toFixed(6)}, Longitude: ${lng.toFixed(6)}`;
}
// Initialize map in modal
@ -400,6 +409,7 @@ function initMap(lat, lng) {
longitude: position.lng
};
updateLocationDisplay(position.lat, position.lng);
updateModalLocation(position.lat, position.lng);
});
// Search radius circle (removed - no red tint on map)
@ -462,50 +472,87 @@ function displayClubs(clubs) {
const card = document.createElement('div');
card.className = 'col-md-6 col-lg-4';
card.innerHTML = `
<div class="card club-card shadow-sm h-100">
<div class="position-relative">
<img src="https://via.placeholder.com/400x200?text=${encodeURIComponent(club.club_name)}"
class="club-card-img" alt="${club.club_name}">
<span class="club-badge">Sports Club</span>
</div>
<div class="card-body">
<h5 class="card-title mb-2">${club.club_name}</h5>
<p class="text-danger mb-2">
<i class="bi bi-geo-alt-fill me-1"></i>${club.distance} km away
</p>
<p class="text-muted small mb-3">
<i class="bi bi-geo me-1"></i>${club.owner_name || 'N/A'}
</p>
<div class="row g-2 mb-3">
<div class="col-4">
<div class="stat-box">
<i class="bi bi-people"></i>
<div class="stat-number">0</div>
<div class="stat-label">Members</div>
<div class="rounded-none border bg-card text-card-foreground shadow-sm hover:shadow-elevated transition-all cursor-pointer overflow-hidden group">
<div class="relative h-64 overflow-hidden">
<img src="https://via.placeholder.com/400x200?text=${encodeURIComponent(club.club_name)}" alt="${club.club_name}" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300">
<div class="absolute bottom-3 right-3 z-10">
<div class="w-14 h-14 rounded-full border-2 border-white/90 shadow-lg overflow-hidden bg-white/95 backdrop-blur">
<img src="https://via.placeholder.com/50x50?text=Logo" alt="${club.club_name} logo" class="w-full h-full object-contain rounded-full">
</div>
</div>
<div class="col-4">
<div class="stat-box">
<i class="bi bi-box"></i>
<div class="stat-number">0</div>
<div class="stat-label">Packages</div>
<div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent absolute top-4 right-4 bg-brand-red text-white hover:bg-brand-red-dark">Sports Club</div>
</div>
<div class="px-5 pt-4 pb-2">
<h3 class="text-2xl font-bold text-foreground leading-tight line-clamp-2">${club.club_name}</h3>
</div>
<div class="p-6 px-5 pb-5 pt-2 space-y-3">
<div>
<div class="flex items-center gap-1 text-sm text-brand-red mb-1">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-navigation w-4 h-4">
<polygon points="3 11 22 2 13 21 11 13 3 11"></polygon>
</svg>
<span class="font-medium">${club.distance} km away</span>
</div>
<div class="flex items-center gap-1 text-sm text-muted-foreground">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-building w-4 h-4">
<rect width="16" height="20" x="4" y="2" rx="2" ry="2"></rect>
<path d="M9 22v-4h6v4"></path>
<path d="M8 6h.01"></path>
<path d="M16 6h.01"></path>
<path d="M12 6h.01"></path>
<path d="M12 10h.01"></path>
<path d="M12 14h.01"></path>
<path d="M16 10h.01"></path>
<path d="M16 14h.01"></path>
<path d="M8 10h.01"></path>
<path d="M8 14h.01"></path>
</svg>
<span>${club.owner_name || 'N/A'}</span>
</div>
</div>
<div class="col-4">
<div class="stat-box">
<i class="bi bi-person-badge"></i>
<div class="stat-number">0</div>
<div class="stat-label">Trainers</div>
<div class="grid grid-cols-3 gap-2 border-t pt-3">
<div class="bg-brand-red/5 rounded-lg p-2 border border-brand-red/10 hover:border-brand-red/20 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users w-4 h-4 mx-auto mb-1 text-brand-red">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
<p class="text-lg font-bold text-center">0</p>
<p class="text-[10px] text-muted-foreground text-center font-medium">Members</p>
</div>
<div class="bg-brand-red/5 rounded-lg p-2 border border-brand-red/10 hover:border-brand-red/20 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-package w-4 h-4 mx-auto mb-1 text-brand-red">
<path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"></path>
<path d="M12 22V12"></path>
<path d="m3.3 7 7.703 4.734a2 2 0 0 0 1.994 0L20.7 7"></path>
<path d="m7.5 4.27 9 5.15"></path>
</svg>
<p class="text-lg font-bold text-center">0</p>
<p class="text-[10px] text-muted-foreground text-center font-medium">Packages</p>
</div>
<div class="bg-brand-red/5 rounded-lg p-2 border border-brand-red/10 hover:border-brand-red/20 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user w-4 h-4 mx-auto mb-1 text-brand-red">
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
<p class="text-lg font-bold text-center">0</p>
<p class="text-[10px] text-muted-foreground text-center font-medium">Trainers</p>
</div>
</div>
</div>
<div class="d-grid gap-2">
<button class="btn btn-primary">
<i class="bi bi-person-plus me-2"></i>Join Club
<div class="flex gap-2">
<button class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm ring-offset-background transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:shadow-medium hover:scale-105 h-10 px-4 py-2 flex-1 bg-brand-red hover:bg-brand-red-dark text-white font-semibold shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user-plus w-4 h-4 mr-2">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<line x1="19" x2="19" y1="8" y2="14"></line>
<line x1="22" x2="16" y1="11" y2="11"></line>
</svg>
Join Club
</button>
<button class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm ring-offset-background transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:text-accent-foreground h-10 px-4 py-2 flex-1 text-brand-red hover:bg-brand-red/10 font-semibold">
View Details
</button>
<button class="btn btn-outline-primary">View Details</button>
</div>
</div>
</div>
@ -538,6 +585,44 @@ function showAlert(message, type = 'danger') {
alert.className = `alert alert-${type} alert-dismissible fade show`;
messageSpan.textContent = message;
}
// Reverse geocode to get address
async function reverseGeocode(lat, lng) {
try {
const response = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=10`);
const data = await response.json();
return data.address || null;
} catch (error) {
console.error('Reverse geocoding error:', error);
return null;
}
}
// Get country info from address
function getCountryInfo(address) {
if (!address || !countriesData.length) return null;
const iso2 = address.country_code?.toUpperCase();
if (!iso2) return null;
const country = countriesData.find(c => c.iso2 === iso2);
if (country) {
const flag = iso2.split('').map(char => String.fromCodePoint(127397 + char.charCodeAt(0))).join('');
return { flag, name: country.name };
}
return null;
}
// Update modal location display with country and area
async function updateModalLocation(lat, lng) {
const address = await reverseGeocode(lat, lng);
const info = getCountryInfo(address);
const coords = `Latitude: ${lat.toFixed(6)}, Longitude: ${lng.toFixed(6)}`;
const area = address?.city || address?.state || address?.county || '';
if (info) {
document.getElementById('modalLocationCoordinates').innerHTML = `${info.flag} ${info.name}${area ? ', ' + area : ''} - ${coords}`;
} else {
document.getElementById('modalLocationCoordinates').textContent = `${area ? area + ' - ' : ''}${coords}`;
}
}
</script>
@endpush
@endsection

View File

@ -140,7 +140,7 @@
<div class="info-box">
<h3>Your Family Information</h3>
<p><strong>Guardian:</strong> {{ $guardian->full_name }}</p>
<p><strong>Relationship:</strong> {{ ucfirst($relationship->relationship_type) }}</p>
<p><strong>Relationship:</strong> {{ $relationship->relationship_type === 'spouse' ? 'Wife' : ucfirst($relationship->relationship_type) }}</p>
@if($user->birthdate)
<p><strong>Birthdate:</strong> {{ \Carbon\Carbon::parse($user->birthdate)->format('F j, Y') }}</p>
@endif

View File

@ -85,7 +85,7 @@
<option value="">Select Relationship</option>
<option value="son" {{ old('relationship_type') == 'son' ? 'selected' : '' }}>Son</option>
<option value="daughter" {{ old('relationship_type') == 'daughter' ? 'selected' : '' }}>Daughter</option>
<option value="spouse" {{ old('relationship_type') == 'spouse' ? 'selected' : '' }}>Spouse</option>
<option value="spouse" {{ old('relationship_type') == 'spouse' ? 'selected' : '' }}>Wife</option>
<option value="sponsor" {{ old('relationship_type') == 'sponsor' ? 'selected' : '' }}>Sponsor</option>
<option value="other" {{ old('relationship_type') == 'other' ? 'selected' : '' }}>Other</option>
</select>

View File

@ -18,14 +18,14 @@
<a href="{{ route('profile.show') }}" class="text-decoration-none">
<div class="card h-100 shadow-sm border overflow-hidden d-flex flex-column family-card">
<!-- Header with gradient background -->
<div class="p-4 pb-3" style="background: linear-gradient(135deg, {{ $user->gender == 'm' ? 'rgba(13, 110, 253, 0.1) 0%, rgba(13, 110, 253, 0.05) 50%' : 'rgba(214, 51, 132, 0.1) 0%, rgba(214, 51, 132, 0.05) 50%' }}, transparent 100%);">
<div class="p-4 pb-3" style="background: linear-gradient(135deg, {{ $user->gender == 'm' ? 'rgba(147, 51, 234, 0.1) 0%, rgba(147, 51, 234, 0.05) 50%' : 'rgba(214, 51, 132, 0.1) 0%, rgba(214, 51, 132, 0.05) 50%' }}, transparent 100%);">
<div class="d-flex align-items-start gap-3">
<div class="position-relative">
<div class="rounded-circle border border-4 border-white shadow" style="width: 80px; height: 80px; overflow: hidden; box-shadow: 0 0 0 2px {{ $user->gender == 'm' ? 'rgba(13, 110, 253, 0.3)' : 'rgba(214, 51, 132, 0.3)' }} !important;">
<div class="rounded-circle border border-4 border-white shadow" style="width: 80px; height: 80px; overflow: hidden; box-shadow: 0 0 0 2px {{ $user->gender == 'm' ? 'rgba(147, 51, 234, 0.3)' : 'rgba(214, 51, 132, 0.3)' }} !important;">
@if($user->media_gallery[0] ?? false)
<img src="{{ $user->media_gallery[0] }}" alt="{{ $user->full_name }}" class="w-100 h-100" style="object-fit: cover;">
@else
<div class="w-100 h-100 d-flex align-items-center justify-content-center text-white fw-bold fs-4" style="background: linear-gradient(135deg, {{ $user->gender == 'm' ? '#0d6efd 0%, #0a58ca 100%' : '#d63384 0%, #a61e4d 100%' }});">
<div class="w-100 h-100 d-flex align-items-center justify-content-center text-white fw-bold fs-4" style="background: linear-gradient(135deg, {{ $user->gender == 'm' ? '#8b5cf6 0%, #7c3aed 100%' : '#d63384 0%, #a61e4d 100%' }});">
{{ strtoupper(substr($user->full_name, 0, 1)) }}
</div>
@endif
@ -43,12 +43,10 @@
<!-- Contact Info -->
<div class="px-4 py-3 bg-light border-top border-bottom">
@if($user->mobile)
<div class="d-flex align-items-center gap-2 small mb-2">
<i class="bi bi-telephone-fill {{ $user->gender == 'm' ? 'text-primary' : 'text-danger' }}"></i>
<span class="fw-medium text-muted">{{ $user->mobile }}</span>
<span class="fw-medium text-muted">{{ $user->mobile_formatted ?: 'Not provided' }}</span>
</div>
@endif
@if($user->email)
<div class="d-flex align-items-center gap-2 small">
<i class="bi bi-envelope-fill {{ $user->gender == 'm' ? 'text-primary' : 'text-danger' }}"></i>
@ -118,9 +116,8 @@
</div>
<!-- Guardian/Sponsor Info - Footer -->
<div class="px-4 py-2 {{ $user->gender == 'm' ? 'bg-primary' : 'bg-danger' }} bg-opacity-10 border-top">
<div class="d-flex align-items-center justify-content-center gap-2 small">
<i class="bi bi-person-badge {{ $user->gender == 'm' ? 'text-primary' : 'text-danger' }}"></i>
<span class="fw-medium {{ $user->gender == 'm' ? 'text-primary' : 'text-danger' }}">
<div class="d-flex align-items-center justify-content-center small">
<span class="fw-medium text-white">
GUARDIAN
</span>
</div>
@ -135,14 +132,14 @@
<a href="{{ route('family.show', $relationship->dependent->id) }}" class="text-decoration-none">
<div class="card h-100 shadow-sm border overflow-hidden d-flex flex-column family-card">
<!-- Header with gradient background -->
<div class="p-4 pb-3" style="background: linear-gradient(135deg, {{ $relationship->dependent->gender == 'm' ? 'rgba(13, 110, 253, 0.1) 0%, rgba(13, 110, 253, 0.05) 50%' : 'rgba(214, 51, 132, 0.1) 0%, rgba(214, 51, 132, 0.05) 50%' }}, transparent 100%);">
<div class="p-4 pb-3" style="background: linear-gradient(135deg, {{ $relationship->dependent->gender == 'm' ? 'rgba(147, 51, 234, 0.1) 0%, rgba(147, 51, 234, 0.05) 50%' : 'rgba(214, 51, 132, 0.1) 0%, rgba(214, 51, 132, 0.05) 50%' }}, transparent 100%);">
<div class="d-flex align-items-start gap-3">
<div class="position-relative">
<div class="rounded-circle border border-4 border-white shadow" style="width: 80px; height: 80px; overflow: hidden; box-shadow: 0 0 0 2px {{ $relationship->dependent->gender == 'm' ? 'rgba(13, 110, 253, 0.3)' : 'rgba(214, 51, 132, 0.3)' }} !important;">
<div class="rounded-circle border border-4 border-white shadow" style="width: 80px; height: 80px; overflow: hidden; box-shadow: 0 0 0 2px {{ $relationship->dependent->gender == 'm' ? 'rgba(147, 51, 234, 0.3)' : 'rgba(214, 51, 132, 0.3)' }} !important;">
@if($relationship->dependent->media_gallery[0] ?? false)
<img src="{{ $relationship->dependent->media_gallery[0] }}" alt="{{ $relationship->dependent->full_name }}" class="w-100 h-100" style="object-fit: cover;">
@else
<div class="w-100 h-100 d-flex align-items-center justify-content-center text-white fw-bold fs-4" style="background: linear-gradient(135deg, {{ $relationship->dependent->gender == 'm' ? '#0d6efd 0%, #0a58ca 100%' : '#d63384 0%, #a61e4d 100%' }});">
<div class="w-100 h-100 d-flex align-items-center justify-content-center text-white fw-bold fs-4" style="background: linear-gradient(135deg, {{ $relationship->dependent->gender == 'm' ? '#8b5cf6 0%, #7c3aed 100%' : '#d63384 0%, #a61e4d 100%' }});">
{{ strtoupper(substr($relationship->dependent->full_name, 0, 1)) }}
</div>
@endif
@ -181,18 +178,13 @@
<!-- Contact Info -->
<div class="px-4 py-3 bg-light border-top border-bottom">
@if($relationship->dependent->mobile)
<div class="d-flex align-items-center gap-2 small mb-2">
<i class="bi bi-telephone-fill {{ $relationship->dependent->gender == 'm' ? 'text-primary' : 'text-danger' }}"></i>
<span class="fw-medium text-muted">{{ $relationship->dependent->mobile }}</span>
</div>
@elseif($user->mobile)
<div class="d-flex align-items-center gap-2 small mb-2">
<i class="bi bi-telephone-fill {{ $relationship->dependent->gender == 'm' ? 'text-primary' : 'text-danger' }}"></i>
<span class="fw-medium text-muted">{{ $user->mobile }}</span>
<span class="fw-medium text-muted">{{ $relationship->dependent->mobile_formatted ?: ($user->mobile_formatted ?: 'Not provided') }}</span>
@if(!$relationship->dependent->mobile_formatted && $user->mobile_formatted)
<span class="badge {{ $relationship->dependent->gender == 'm' ? 'bg-info' : 'bg-danger' }} {{ $relationship->dependent->gender == 'm' ? 'text-dark' : 'text-white' }} ms-auto">Guardian's</span>
</div>
@endif
</div>
@if($relationship->dependent->email)
<div class="d-flex align-items-center gap-2 small">
<i class="bi bi-envelope-fill {{ $relationship->dependent->gender == 'm' ? 'text-primary' : 'text-danger' }}"></i>
@ -268,9 +260,8 @@
<!-- Sponsor/Guardian Info - Footer -->
<div class="px-4 py-2 {{ $relationship->dependent->gender == 'm' ? 'bg-primary' : 'bg-danger' }} bg-opacity-10 border-top">
<div class="d-flex align-items-center justify-content-center gap-2 small">
<i class="bi bi-person-badge {{ $relationship->dependent->gender == 'm' ? 'text-primary' : 'text-danger' }}"></i>
<span class="fw-medium {{ $relationship->dependent->gender == 'm' ? 'text-primary' : 'text-danger' }}">
{{ strtoupper($relationship->relationship_type) }} : {{ strtoupper($user->full_name) }}
<span class="fw-medium text-white">
{{ $relationship->relationship_type === 'spouse' ? 'WIFE' : strtoupper($relationship->relationship_type) }}
</span>
</div>
</div>

View File

@ -44,9 +44,23 @@
<div class="mb-3">
<label for="mobile" class="form-label">Mobile Number</label>
<input type="text" class="form-control @error('mobile') is-invalid @enderror" id="mobile" name="mobile" value="{{ old('mobile', $relationship->dependent->mobile) }}">
<div class="input-group" onclick="event.stopPropagation()">
<button class="btn btn-outline-secondary dropdown-toggle country-dropdown-btn d-flex align-items-center" type="button" id="country_codeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<span class="fi fi-bh me-2" id="country_codeSelectedFlag"></span>
<span class="country-label" id="country_codeSelectedCountry">{{ old('mobile_code', $relationship->dependent->mobile['code'] ?? '+973') }}</span>
</button>
<div class="dropdown-menu p-2" aria-labelledby="country_codeDropdown" style="min-width: 200px;" onclick="event.stopPropagation()">
<input type="text" class="form-control form-control-sm mb-2" placeholder="Search country..." id="country_codeSearch" onkeydown="event.stopPropagation()">
<div class="country-list" id="country_codeList" style="max-height: 300px; overflow-y: auto;"></div>
</div>
<input type="hidden" id="country_code" name="mobile_code" value="{{ old('mobile_code', $relationship->dependent->mobile['code'] ?? '+973') }}">
<input id="mobile_number" type="tel" class="form-control @error('mobile') is-invalid @enderror" name="mobile" value="{{ old('mobile', $relationship->dependent->mobile['number'] ?? '') }}" autocomplete="tel" placeholder="Phone number">
</div>
@error('mobile')
<div class="invalid-feedback">{{ $message }}</div>
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
@error('mobile_code')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
@ -179,7 +193,7 @@
<option value="">Select Relationship</option>
<option value="son" {{ old('relationship_type', $relationship->relationship_type) == 'son' ? 'selected' : '' }}>Son</option>
<option value="daughter" {{ old('relationship_type', $relationship->relationship_type) == 'daughter' ? 'selected' : '' }}>Daughter</option>
<option value="spouse" {{ old('relationship_type', $relationship->relationship_type) == 'spouse' ? 'selected' : '' }}>Spouse</option>
<option value="spouse" {{ old('relationship_type', $relationship->relationship_type) == 'spouse' ? 'selected' : '' }}>Wife</option>
<option value="sponsor" {{ old('relationship_type', $relationship->relationship_type) == 'sponsor' ? 'selected' : '' }}>Sponsor</option>
<option value="other" {{ old('relationship_type', $relationship->relationship_type) == 'other' ? 'selected' : '' }}>Other</option>
</select>
@ -200,7 +214,7 @@
<button type="button" class="btn btn-danger me-2" data-bs-toggle="modal" data-bs-target="#deleteModal">
Remove
</button>
<button type="submit" class="btn btn-primary">Update Family Member</button>
<button type="submit" class="btn btn-primary">Update</button>
</div>
</div>
</form>
@ -308,4 +322,73 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
</script>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// Load countries for mobile code dropdown
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
const listElement = document.getElementById('country_codeList');
const selectedFlag = document.getElementById('country_codeSelectedFlag');
const selectedCountry = document.getElementById('country_codeSelectedCountry');
const hiddenInput = document.getElementById('country_code');
const searchInput = document.getElementById('country_codeSearch');
if (!listElement) return;
// Populate dropdown
countries.forEach(country => {
const button = document.createElement('button');
button.className = 'dropdown-item d-flex align-items-center';
button.type = 'button';
button.setAttribute('data-country-code', country.call_code);
button.setAttribute('data-country-name', country.name);
button.setAttribute('data-flag-code', country.flag);
button.setAttribute('data-search', `${country.name.toLowerCase()} ${country.call_code}`);
button.innerHTML = `<span class="fi fi-${country.flag.toLowerCase()} me-2"></span><span>${country.name} (${country.call_code})</span>`;
button.addEventListener('click', function() {
const code = this.getAttribute('data-country-code');
const flag = this.getAttribute('data-flag-code');
const name = this.getAttribute('data-country-name');
selectedFlag.className = `fi fi-${flag.toLowerCase()} me-2`;
selectedCountry.textContent = code;
hiddenInput.value = code;
// Close dropdown
const dropdown = bootstrap.Dropdown.getInstance(document.getElementById('country_codeDropdown'));
if (dropdown) dropdown.hide();
});
listElement.appendChild(button);
});
// Set initial value
const initialValue = '{{ old('mobile_code', $relationship->dependent->mobile['code'] ?? '+973') }}';
if (initialValue) {
hiddenInput.value = initialValue;
selectedCountry.textContent = initialValue;
const country = countries.find(c => c.call_code === initialValue);
if (country) {
selectedFlag.className = `fi fi-${country.flag.toLowerCase()} me-2`;
}
}
// Search functionality
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const items = listElement.querySelectorAll('.dropdown-item');
items.forEach(item => {
const searchData = item.getAttribute('data-search');
if (searchData.includes(searchTerm)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
});
})
.catch(error => console.error('Error loading countries:', error));
});
</script>
@endpush
@endsection

View File

@ -44,9 +44,23 @@
<div class="mb-3">
<label for="mobile" class="form-label">Mobile Number</label>
<input type="text" class="form-control @error('mobile') is-invalid @enderror" id="mobile" name="mobile" value="{{ old('mobile', $user->mobile) }}">
<div class="input-group" onclick="event.stopPropagation()">
<button class="btn btn-outline-secondary dropdown-toggle country-dropdown-btn d-flex align-items-center" type="button" id="country_codeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<span class="fi fi-bh me-2" id="country_codeSelectedFlag"></span>
<span class="country-label" id="country_codeSelectedCountry">{{ old('mobile_code', $user->mobile_code ?? '+973') }}</span>
</button>
<div class="dropdown-menu p-2" aria-labelledby="country_codeDropdown" style="min-width: 200px;" onclick="event.stopPropagation()">
<input type="text" class="form-control form-control-sm mb-2" placeholder="Search country..." id="country_codeSearch" onkeydown="event.stopPropagation()">
<div class="country-list" id="country_codeList" style="max-height: 300px; overflow-y: auto;"></div>
</div>
<input type="hidden" id="country_code" name="mobile_code" value="{{ old('mobile_code', $user->mobile['code'] ?? '+973') }}" required="">
<input id="mobile_number" type="tel" class="form-control @error('mobile') is-invalid @enderror" name="mobile" value="{{ old('mobile', $user->mobile['number'] ?? '') }}" required="" autocomplete="tel" placeholder="Phone number">
</div>
@error('mobile')
<div class="invalid-feedback">{{ $message }}</div>
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
@error('mobile_code')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
@ -174,7 +188,7 @@
<div class="d-flex justify-content-between">
<a href="{{ route('profile.show') }}" class="btn btn-outline-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">Update Profile</button>
<button type="submit" class="btn btn-primary">Update</button>
</div>
</form>
</div>
@ -192,8 +206,72 @@
/>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// Load countries for mobile code dropdown
fetch('/data/countries.json')
.then(response => response.json())
.then(countries => {
const listElement = document.getElementById('country_codeList');
const selectedFlag = document.getElementById('country_codeSelectedFlag');
const selectedCountry = document.getElementById('country_codeSelectedCountry');
const hiddenInput = document.getElementById('country_code');
const searchInput = document.getElementById('country_codeSearch');
if (!listElement) return;
// Populate dropdown
countries.forEach(country => {
const button = document.createElement('button');
button.className = 'dropdown-item d-flex align-items-center';
button.type = 'button';
button.setAttribute('data-country-code', country.call_code);
button.setAttribute('data-country-name', country.name);
button.setAttribute('data-flag-code', country.flag);
button.setAttribute('data-search', `${country.name.toLowerCase()} ${country.call_code}`);
button.innerHTML = `<span class="fi fi-${country.flag.toLowerCase()} me-2"></span><span>${country.name} (${country.call_code})</span>`;
button.addEventListener('click', function() {
const code = this.getAttribute('data-country-code');
const flag = this.getAttribute('data-flag-code');
const name = this.getAttribute('data-country-name');
selectedFlag.className = `fi fi-${flag.toLowerCase()} me-2`;
selectedCountry.textContent = code;
hiddenInput.value = code;
// Close dropdown
const dropdown = bootstrap.Dropdown.getInstance(document.getElementById('country_codeDropdown'));
if (dropdown) dropdown.hide();
});
listElement.appendChild(button);
});
// Set initial value
const initialValue = '{{ old('mobile_code', $user->mobile['code'] ?? '+973') }}';
if (initialValue) {
hiddenInput.value = initialValue;
selectedCountry.textContent = initialValue;
const country = countries.find(c => c.call_code === initialValue);
if (country) {
selectedFlag.className = `fi fi-${country.flag.toLowerCase()} me-2`;
}
}
// Search functionality
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const items = listElement.querySelectorAll('.dropdown-item');
items.forEach(item => {
const searchData = item.getAttribute('data-search');
if (searchData.includes(searchTerm)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
});
})
.catch(error => console.error('Error loading countries:', error));
let socialLinkIndex = {{ count($formLinks ?? []) }};
// Add new social link row
@ -258,4 +336,5 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
</script>
@endpush
@endsection

View File

@ -9,18 +9,22 @@
<p class="text-muted mb-0">Comprehensive member information and analytics</p>
</div>
<div>
@if($relationship->relationship_type == 'self')
<a href="{{ route('profile.edit') }}" class="btn btn-primary me-2">
<i class="bi bi-pencil me-1"></i>Edit Profile
</a>
@else
<a href="{{ route('family.edit', $relationship->dependent->id) }}" class="btn btn-primary me-2">
<i class="bi bi-pencil me-1"></i>Edit Member
</a>
@endif
<a href="{{ route('family.dashboard') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>Back to Family
</a>
<div class="dropdown">
<button class="btn btn-primary rounded-pill dropdown-toggle" type="button" id="actionDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-lightning me-1"></i>Action
</button>
<ul class="dropdown-menu" aria-labelledby="actionDropdown">
<li><a class="dropdown-item" href="@if($relationship->relationship_type == 'self'){{ route('profile.edit') }}@else{{ route('family.edit', $relationship->dependent->id) }}@endif">
<i class="bi bi-pencil me-2"></i>Edit Info
</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-calendar-check me-2"></i>Add Attendance Record</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-heart-pulse me-2"></i>Add Health Update</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-bullseye me-2"></i>Set a Goal</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-trophy me-2"></i>Add Achievement</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-award me-2"></i>Add Tournament Participation</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-calendar-event me-2"></i>Add Event Participation</a></li>
</ul>
</div>
</div>
</div>
@ -474,80 +478,7 @@
</div>
</div>
<!-- Club Memberships -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-body p-4">
<h5 class="fw-bold mb-3">Club Memberships</h5>
@if($relationship->dependent->memberClubs->count() > 0)
<div class="row g-3">
@foreach($relationship->dependent->memberClubs as $club)
<div class="col-md-6">
<div class="border rounded p-3">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="fw-bold mb-1">{{ $club->club_name }}</h6>
<small class="text-muted">Status: {{ ucfirst($club->pivot->status) }}</small>
</div>
<span class="badge bg-{{ $club->pivot->status === 'active' ? 'success' : 'secondary' }}">
{{ ucfirst($club->pivot->status) }}
</span>
</div>
</div>
</div>
@endforeach
</div>
@else
<p class="text-center text-muted my-4">No club memberships found.</p>
@endif
</div>
</div>
<!-- Recent Invoices -->
<div class="card shadow-sm border-0">
<div class="card-body p-4">
<h5 class="fw-bold mb-3">Recent Invoices</h5>
@if($relationship->dependent->studentInvoices->count() > 0)
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Club</th>
<th>Amount</th>
<th>Status</th>
<th>Due Date</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach($relationship->dependent->studentInvoices->take(5) as $invoice)
<tr>
<td>{{ $invoice->tenant->club_name }}</td>
<td>${{ number_format($invoice->amount, 2) }}</td>
<td>
@if($invoice->status === 'paid')
<span class="badge bg-success">Paid</span>
@elseif($invoice->status === 'pending')
<span class="badge bg-warning text-dark">Pending</span>
@else
<span class="badge bg-danger">Overdue</span>
@endif
</td>
<td>{{ $invoice->due_date->format('M j, Y') }}</td>
<td>
<a href="{{ route('invoices.show', $invoice->id) }}" class="btn btn-sm btn-outline-primary">
View
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="text-center text-muted my-4">No invoices found.</p>
@endif
</div>
</div>
</div>
<!-- Attendance Tab -->

View File

@ -244,6 +244,15 @@
.notification-item.unread {
background-color: hsl(var(--accent));
}
.dropdown-item:hover {
background-color: hsl(var(--primary)) !important;
color: white !important;
}
.nav-icon-btn.dropdown-toggle::after {
display: none;
}
</style>
@stack('styles')
@ -274,6 +283,18 @@
</a>
</li>
<!-- Messages Dropdown -->
<li class="nav-item dropdown">
<a class="nav-link nav-icon-btn dropdown-toggle" href="#" id="messagesDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Messages">
<i class="bi bi-chat" style="font-size: 1.25rem;"></i>
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="messagesDropdown">
<h6 class="dropdown-header small">Messages</h6>
<div class="dropdown-divider"></div>
<a class="dropdown-item small" href="#">No new messages</a>
</div>
</li>
<!-- Notifications Dropdown -->
<li class="nav-item dropdown">
<a class="nav-link nav-icon-btn dropdown-toggle" href="#" id="notificationDropdown" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Notifications">
@ -337,21 +358,44 @@
{{ strtoupper(substr(Auth::user()->full_name, 0, 1)) }}
</span>
@endif
{{ Auth::user()->full_name }}
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('family.dashboard') }}">
<i class="bi bi-people me-2"></i>Family
<h6 class="dropdown-header small"><strong>{{ Auth::user()->full_name }}</strong><br><small>{{ Auth::user()->email }}</small></h6>
<div class="dropdown-divider"></div>
<a class="dropdown-item small" href="{{ url('profile') }}">
<i class="bi bi-person me-2"></i>My Profile
</a>
<a class="dropdown-item" href="{{ route('invoices.index') }}">
<i class="bi bi-receipt me-2"></i>Invoices
<a class="dropdown-item small" href="#">
<i class="bi bi-diagram-3 me-2"></i>Affiliations
</a>
<a class="dropdown-item small" href="#">
<i class="bi bi-calendar-event me-2"></i>My Sessions
</a>
<a class="dropdown-item small" href="{{ route('family.dashboard') }}">
<i class="bi bi-people me-2"></i>My Family
</a>
<a class="dropdown-item small" href="#">
<i class="bi bi-gear me-2"></i>Settings
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{{ route('logout') }}"
@if(Auth::user()->has_business ?? false)
<a class="dropdown-item small" href="#">
<i class="bi bi-building me-2"></i>Manage My Business
</a>
@endif
@if((Auth::user()->is_super_admin ?? false) || (Auth::user()->is_moderator ?? false))
<a class="dropdown-item small" href="#">
<i class="bi bi-shield me-2"></i>Admin Panel
</a>
@endif
@if((Auth::user()->has_business ?? false) || ((Auth::user()->is_super_admin ?? false) || (Auth::user()->is_moderator ?? false)))
<div class="dropdown-divider"></div>
@endif
<a class="dropdown-item small" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
<i class="bi bi-box-arrow-right me-2"></i>{{ __('Logout') }}
<i class="bi bi-box-arrow-right me-2"></i>Sign Out
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">