Skip to content

Commit 6597baf

Browse files
authored
Merge pull request #3650 from codeeu/fix/community-lt-country-filter-avatar
Fix community Leading Teachers country filter and avatar placeholders
2 parents f80a572 + 3d834fb commit 6597baf

5 files changed

Lines changed: 158 additions & 29 deletions

File tree

‎app/Http/Controllers/CommunityController.php‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ public function index(UserFilters $filters)
2424
->where('avatar_path', '<>', 'images/default-avatar.png')
2525
->paginate(10);
2626

27-
$teachers = User::role('leading teacher')->where('approved', 1)->with('city')->get();
27+
$teachers = User::role('leading teacher')
28+
->where('approved', 1)
29+
->filter($filters)
30+
->with(['city', 'expertises'])
31+
->get();
2832

2933
$countries = Country::withCoordinators();
3034

@@ -33,6 +37,7 @@ public function index(UserFilters $filters)
3337
'countries' => $countries,
3438
'teachers' => $teachers,
3539
'country_iso' => request()->get('country_iso'),
40+
'default_avatar' => asset('images/default.png'),
3641
]);
3742
}
3843
}

‎app/User.php‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,34 @@ public function getAvatarPathAttribute($avatar)
341341
return Storage::disk('s3')->url($avatar);
342342
}
343343

344+
/**
345+
* Public avatar URL for community pages. Falls back to a local placeholder
346+
* when the stored path is empty or a known default (S3 defaults are often inaccessible).
347+
*/
348+
public function communityAvatarUrl(?string $fallback = null): string
349+
{
350+
$fallback = $fallback ?: asset('images/default.png');
351+
$raw = $this->attributes['avatar_path'] ?? null;
352+
353+
if ($raw === null || trim((string) $raw) === '') {
354+
return $fallback;
355+
}
356+
357+
$normalized = strtolower(trim((string) $raw));
358+
$defaults = [
359+
'avatars/default_avatar.png',
360+
'avatars/default.png',
361+
'images/default-avatar.png',
362+
'images/default.png',
363+
];
364+
365+
if (in_array($normalized, $defaults, true)) {
366+
return $fallback;
367+
}
368+
369+
return $this->avatar_path;
370+
}
371+
344372
/**
345373
* Get the path to the user's avatar.
346374
*

‎resources/views/community.blade.php‎

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,10 @@ class="duration-500 transform -translate-x-6 min-w-4 group-hover:translate-x-0"
246246
<div class="relative z-50 flex h-full gap-8 max-sm:w-full"
247247
role="article" aria-labelledby="profile-name">
248248
<img
249-
class="object-cover w-32 h-32"
250-
src="{{ $ambassador->avatar_path ?? asset('images/default.png') }}"
249+
class="object-cover w-32 h-32 bg-[#E8EDF6]"
250+
src="{{ $ambassador->communityAvatarUrl(asset('images/default.png')) }}"
251251
alt="{{ $ambassador->fullName() }}"
252+
onerror="this.onerror=null;this.src='{{ asset('images/default.png') }}';"
252253
>
253254
<div class="flex flex-col justify-between flex-1">
254255
<div class="flex flex-col gap-1 mt-1">
@@ -571,6 +572,7 @@ class="text-[#1C4DA1] text-2xl md:text-4xl leading-[44px] font-medium font-['Mon
571572
var markers = {};
572573
var selectedMarker = null;
573574
var allTeachers = [];
575+
var defaultAvatar = @json($default_avatar ?? asset('images/default.png'));
574576
575577
// Function to populate teacher information in the right sidebar
576578
function populateTeacherInfo(teachers, city = null) {
@@ -642,9 +644,10 @@ function populateTeacherInfo(teachers, city = null) {
642644
</a>
643645
</div>
644646
</div>
645-
${ teacher.avatar_path
646-
? `<img src="${teacher.avatar_path}" alt="Avatar" class="flex-shrink-0 object-cover w-[88px] h-[88px] border-2 border-[#DBECF0] border-solid rounded-full">`
647-
: '' }
647+
<img src="${teacher.avatar_path || defaultAvatar}"
648+
alt=""
649+
class="flex-shrink-0 object-cover w-[88px] h-[88px] border-2 border-[#DBECF0] border-solid rounded-full bg-[#E8EDF6]"
650+
onerror="this.onerror=null;this.src=defaultAvatar;">
648651
</li>
649652
`;
650653
});
@@ -709,21 +712,21 @@ function populateTeacherInfo(teachers, city = null) {
709712
mymap.setView([centerInfo.latitude, centerInfo.longitude], centerInfo.zoom);
710713
});
711714
712-
// Populate the global teachers array from PHP data
715+
// Populate the global teachers array from PHP data (already filtered by country)
713716
@foreach ($teachers->groupBy('city_id') as $cityId => $teachersInCity)
714717
@foreach ($teachersInCity as $teacher)
715718
allTeachers.push({
716-
firstname: "{{ $teacher->firstname }}",
717-
lastname: "{{ $teacher->lastname }}",
718-
email: "{{ $teacher->email }}",
719-
country_iso: "{{ $teacher->country_iso }}",
720-
twitter: "{{ $teacher->twitter }}",
721-
website: "{{ $teacher->website }}",
719+
firstname: @json($teacher->firstname),
720+
lastname: @json($teacher->lastname),
721+
email: @json($teacher->email),
722+
country_iso: @json($teacher->country_iso),
723+
twitter: @json($teacher->twitter),
724+
website: @json($teacher->website),
722725
bio: @json($teacher->bio),
723-
avatar_path: "{{ $teacher->avatar_path }}",
724-
city: "{{ $teacher->city->city ?? 'N/A' }}",
725-
latitude: "{{ $teacher->city->latitude ?? '' }}",
726-
longitude: "{{ $teacher->city->longitude ?? '' }}",
726+
avatar_path: @json($teacher->communityAvatarUrl($default_avatar)),
727+
city: @json($teacher->city->city ?? 'N/A'),
728+
latitude: @json($teacher->city->latitude ?? ''),
729+
longitude: @json($teacher->city->longitude ?? ''),
727730
expertises: @json($teacher->expertises->pluck('name')->toArray())
728731
});
729732
@endforeach
@@ -758,15 +761,15 @@ function populateTeacherInfo(teachers, city = null) {
758761
var teacherList = [
759762
@foreach ($teachersInCity as $teacher)
760763
{
761-
firstname: "{{ $teacher->firstname }}",
762-
lastname: "{{ $teacher->lastname }}",
763-
email: "{{ $teacher->email }}",
764-
country_iso: "{{ $teacher->country_iso }}",
765-
twitter: "{{ $teacher->twitter }}",
766-
website: "{{ $teacher->website }}",
764+
firstname: @json($teacher->firstname),
765+
lastname: @json($teacher->lastname),
766+
email: @json($teacher->email),
767+
country_iso: @json($teacher->country_iso),
768+
twitter: @json($teacher->twitter),
769+
website: @json($teacher->website),
767770
bio: @json($teacher->bio),
768-
avatar_path: "{{ $teacher->avatar_path }}",
769-
city: "{{ $teacher->city->city ?? 'N/A' }}",
771+
avatar_path: @json($teacher->communityAvatarUrl($default_avatar)),
772+
city: @json($teacher->city->city ?? 'N/A'),
770773
expertises: @json($teacher->expertises->pluck('name')->toArray())
771774
},
772775
@endforeach

‎tests/Feature/CommunityAmbassadorFilteringTest.php‎

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Tests\Feature;
44

5+
use App\City;
6+
use App\Country;
7+
use App\User;
58
use Illuminate\Foundation\Testing\DatabaseMigrations;
69
use PHPUnit\Framework\Attributes\Test;
710
use Tests\TestCase;
@@ -15,9 +18,9 @@ public function community_view_does_not_include_ambassador_without_bio_or_avatar
1518
{
1619
$this->seed('RolesAndPermissionsSeeder');
1720
$this->seed('LeadingTeacherRoleSeeder');
18-
$fr = \App\Country::factory()->create(['iso' => 'FR']);
21+
$fr = Country::factory()->create(['iso' => 'FR']);
1922

20-
$bad = \App\User::factory()->create([
23+
$bad = User::factory()->create([
2124
'country_iso' => $fr->iso,
2225
'bio' => null,
2326
'avatar_path' => null,
@@ -28,8 +31,52 @@ public function community_view_does_not_include_ambassador_without_bio_or_avatar
2831

2932
$res->assertViewHas('ambassadors', function ($paginator) use ($bad) {
3033
$collection = $paginator->getCollection();
31-
return !$collection->contains('id', $bad->id);
34+
35+
return ! $collection->contains('id', $bad->id);
3236
});
3337
}
34-
}
3538

39+
#[Test]
40+
public function community_leading_teachers_are_filtered_by_selected_country(): void
41+
{
42+
$this->seed('RolesAndPermissionsSeeder');
43+
$this->seed('LeadingTeacherRoleSeeder');
44+
45+
$lt = Country::factory()->create(['iso' => 'LT']);
46+
$fr = Country::factory()->create(['iso' => 'FR']);
47+
$vilnius = City::factory()->create([
48+
'country_iso' => 'LT',
49+
'city' => 'Vilnius',
50+
'latitude' => 54.6833,
51+
'longitude' => 25.2833,
52+
]);
53+
54+
$lithuanian = User::factory()->create([
55+
'country_iso' => $lt->iso,
56+
'city_id' => $vilnius->id,
57+
'approved' => 1,
58+
'firstname' => 'Dovile',
59+
'lastname' => 'Testiene',
60+
'avatar_path' => null,
61+
])->assignRole('leading teacher');
62+
63+
$french = User::factory()->create([
64+
'country_iso' => $fr->iso,
65+
'approved' => 1,
66+
'firstname' => 'Marie',
67+
'lastname' => 'Dupont',
68+
])->assignRole('leading teacher');
69+
70+
$res = $this->get('/community?country_iso=LT');
71+
$res->assertOk();
72+
73+
$res->assertViewHas('teachers', function ($teachers) use ($lithuanian, $french) {
74+
return $teachers->contains('id', $lithuanian->id)
75+
&& ! $teachers->contains('id', $french->id);
76+
});
77+
78+
$res->assertSee('Dovile', false);
79+
$res->assertDontSee('Marie', false);
80+
$res->assertSee(asset('images/default.png'), false);
81+
}
82+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace Tests\Unit;
4+
5+
use App\User;
6+
use PHPUnit\Framework\Attributes\Test;
7+
use Tests\TestCase;
8+
9+
final class UserCommunityAvatarUrlTest extends TestCase
10+
{
11+
#[Test]
12+
public function null_avatar_uses_local_placeholder(): void
13+
{
14+
$user = new User;
15+
$user->setRawAttributes(['avatar_path' => null]);
16+
17+
$this->assertSame(
18+
asset('images/default.png'),
19+
$user->communityAvatarUrl()
20+
);
21+
}
22+
23+
#[Test]
24+
public function default_s3_avatar_uses_local_placeholder(): void
25+
{
26+
$user = new User;
27+
$user->setRawAttributes(['avatar_path' => 'avatars/default_avatar.png']);
28+
29+
$this->assertSame(
30+
asset('images/default.png'),
31+
$user->communityAvatarUrl()
32+
);
33+
}
34+
35+
#[Test]
36+
public function real_avatar_keeps_storage_url(): void
37+
{
38+
$user = new User;
39+
$user->setRawAttributes(['avatar_path' => 'avatars/200/photo.jpg']);
40+
41+
$this->assertStringContainsString(
42+
'avatars/200/photo.jpg',
43+
$user->communityAvatarUrl()
44+
);
45+
}
46+
}

0 commit comments

Comments
 (0)