Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/Http/Controllers/CommunityController.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ public function index(UserFilters $filters)
->where('avatar_path', '<>', 'images/default-avatar.png')
->paginate(10);

$teachers = User::role('leading teacher')->where('approved', 1)->with('city')->get();
$teachers = User::role('leading teacher')
->where('approved', 1)
->filter($filters)
->with(['city', 'expertises'])
->get();

$countries = Country::withCoordinators();

Expand All @@ -33,6 +37,7 @@ public function index(UserFilters $filters)
'countries' => $countries,
'teachers' => $teachers,
'country_iso' => request()->get('country_iso'),
'default_avatar' => asset('images/default.png'),
]);
}
}
28 changes: 28 additions & 0 deletions app/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,34 @@ public function getAvatarPathAttribute($avatar)
return Storage::disk('s3')->url($avatar);
}

/**
* Public avatar URL for community pages. Falls back to a local placeholder
* when the stored path is empty or a known default (S3 defaults are often inaccessible).
*/
public function communityAvatarUrl(?string $fallback = null): string
{
$fallback = $fallback ?: asset('images/default.png');
$raw = $this->attributes['avatar_path'] ?? null;

if ($raw === null || trim((string) $raw) === '') {
return $fallback;
}

$normalized = strtolower(trim((string) $raw));
$defaults = [
'avatars/default_avatar.png',
'avatars/default.png',
'images/default-avatar.png',
'images/default.png',
];

if (in_array($normalized, $defaults, true)) {
return $fallback;
}

return $this->avatar_path;
}

/**
* Get the path to the user's avatar.
*
Expand Down
51 changes: 27 additions & 24 deletions resources/views/community.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,10 @@ class="duration-500 transform -translate-x-6 min-w-4 group-hover:translate-x-0"
<div class="relative z-50 flex h-full gap-8 max-sm:w-full"
role="article" aria-labelledby="profile-name">
<img
class="object-cover w-32 h-32"
src="{{ $ambassador->avatar_path ?? asset('images/default.png') }}"
class="object-cover w-32 h-32 bg-[#E8EDF6]"
src="{{ $ambassador->communityAvatarUrl(asset('images/default.png')) }}"
alt="{{ $ambassador->fullName() }}"
onerror="this.onerror=null;this.src='{{ asset('images/default.png') }}';"
>
<div class="flex flex-col justify-between flex-1">
<div class="flex flex-col gap-1 mt-1">
Expand Down Expand Up @@ -571,6 +572,7 @@ class="text-[#1C4DA1] text-2xl md:text-4xl leading-[44px] font-medium font-['Mon
var markers = {};
var selectedMarker = null;
var allTeachers = [];
var defaultAvatar = @json($default_avatar ?? asset('images/default.png'));

// Function to populate teacher information in the right sidebar
function populateTeacherInfo(teachers, city = null) {
Expand Down Expand Up @@ -642,9 +644,10 @@ function populateTeacherInfo(teachers, city = null) {
</a>
</div>
</div>
${ teacher.avatar_path
? `<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">`
: '' }
<img src="${teacher.avatar_path || defaultAvatar}"
alt=""
class="flex-shrink-0 object-cover w-[88px] h-[88px] border-2 border-[#DBECF0] border-solid rounded-full bg-[#E8EDF6]"
onerror="this.onerror=null;this.src=defaultAvatar;">
</li>
`;
});
Expand Down Expand Up @@ -709,21 +712,21 @@ function populateTeacherInfo(teachers, city = null) {
mymap.setView([centerInfo.latitude, centerInfo.longitude], centerInfo.zoom);
});

// Populate the global teachers array from PHP data
// Populate the global teachers array from PHP data (already filtered by country)
@foreach ($teachers->groupBy('city_id') as $cityId => $teachersInCity)
@foreach ($teachersInCity as $teacher)
allTeachers.push({
firstname: "{{ $teacher->firstname }}",
lastname: "{{ $teacher->lastname }}",
email: "{{ $teacher->email }}",
country_iso: "{{ $teacher->country_iso }}",
twitter: "{{ $teacher->twitter }}",
website: "{{ $teacher->website }}",
firstname: @json($teacher->firstname),
lastname: @json($teacher->lastname),
email: @json($teacher->email),
country_iso: @json($teacher->country_iso),
twitter: @json($teacher->twitter),
website: @json($teacher->website),
bio: @json($teacher->bio),
avatar_path: "{{ $teacher->avatar_path }}",
city: "{{ $teacher->city->city ?? 'N/A' }}",
latitude: "{{ $teacher->city->latitude ?? '' }}",
longitude: "{{ $teacher->city->longitude ?? '' }}",
avatar_path: @json($teacher->communityAvatarUrl($default_avatar)),
city: @json($teacher->city->city ?? 'N/A'),
latitude: @json($teacher->city->latitude ?? ''),
longitude: @json($teacher->city->longitude ?? ''),
expertises: @json($teacher->expertises->pluck('name')->toArray())
});
@endforeach
Expand Down Expand Up @@ -758,15 +761,15 @@ function populateTeacherInfo(teachers, city = null) {
var teacherList = [
@foreach ($teachersInCity as $teacher)
{
firstname: "{{ $teacher->firstname }}",
lastname: "{{ $teacher->lastname }}",
email: "{{ $teacher->email }}",
country_iso: "{{ $teacher->country_iso }}",
twitter: "{{ $teacher->twitter }}",
website: "{{ $teacher->website }}",
firstname: @json($teacher->firstname),
lastname: @json($teacher->lastname),
email: @json($teacher->email),
country_iso: @json($teacher->country_iso),
twitter: @json($teacher->twitter),
website: @json($teacher->website),
bio: @json($teacher->bio),
avatar_path: "{{ $teacher->avatar_path }}",
city: "{{ $teacher->city->city ?? 'N/A' }}",
avatar_path: @json($teacher->communityAvatarUrl($default_avatar)),
city: @json($teacher->city->city ?? 'N/A'),
expertises: @json($teacher->expertises->pluck('name')->toArray())
},
@endforeach
Expand Down
55 changes: 51 additions & 4 deletions tests/Feature/CommunityAmbassadorFilteringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Tests\Feature;

use App\City;
use App\Country;
use App\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
Expand All @@ -15,9 +18,9 @@ public function community_view_does_not_include_ambassador_without_bio_or_avatar
{
$this->seed('RolesAndPermissionsSeeder');
$this->seed('LeadingTeacherRoleSeeder');
$fr = \App\Country::factory()->create(['iso' => 'FR']);
$fr = Country::factory()->create(['iso' => 'FR']);

$bad = \App\User::factory()->create([
$bad = User::factory()->create([
'country_iso' => $fr->iso,
'bio' => null,
'avatar_path' => null,
Expand All @@ -28,8 +31,52 @@ public function community_view_does_not_include_ambassador_without_bio_or_avatar

$res->assertViewHas('ambassadors', function ($paginator) use ($bad) {
$collection = $paginator->getCollection();
return !$collection->contains('id', $bad->id);

return ! $collection->contains('id', $bad->id);
});
}
}

#[Test]
public function community_leading_teachers_are_filtered_by_selected_country(): void
{
$this->seed('RolesAndPermissionsSeeder');
$this->seed('LeadingTeacherRoleSeeder');

$lt = Country::factory()->create(['iso' => 'LT']);
$fr = Country::factory()->create(['iso' => 'FR']);
$vilnius = City::factory()->create([
'country_iso' => 'LT',
'city' => 'Vilnius',
'latitude' => 54.6833,
'longitude' => 25.2833,
]);

$lithuanian = User::factory()->create([
'country_iso' => $lt->iso,
'city_id' => $vilnius->id,
'approved' => 1,
'firstname' => 'Dovile',
'lastname' => 'Testiene',
'avatar_path' => null,
])->assignRole('leading teacher');

$french = User::factory()->create([
'country_iso' => $fr->iso,
'approved' => 1,
'firstname' => 'Marie',
'lastname' => 'Dupont',
])->assignRole('leading teacher');

$res = $this->get('/community?country_iso=LT');
$res->assertOk();

$res->assertViewHas('teachers', function ($teachers) use ($lithuanian, $french) {
return $teachers->contains('id', $lithuanian->id)
&& ! $teachers->contains('id', $french->id);
});

$res->assertSee('Dovile', false);
$res->assertDontSee('Marie', false);
$res->assertSee(asset('images/default.png'), false);
}
}
46 changes: 46 additions & 0 deletions tests/Unit/UserCommunityAvatarUrlTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Tests\Unit;

use App\User;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

final class UserCommunityAvatarUrlTest extends TestCase
{
#[Test]
public function null_avatar_uses_local_placeholder(): void
{
$user = new User;
$user->setRawAttributes(['avatar_path' => null]);

$this->assertSame(
asset('images/default.png'),
$user->communityAvatarUrl()
);
}

#[Test]
public function default_s3_avatar_uses_local_placeholder(): void
{
$user = new User;
$user->setRawAttributes(['avatar_path' => 'avatars/default_avatar.png']);

$this->assertSame(
asset('images/default.png'),
$user->communityAvatarUrl()
);
}

#[Test]
public function real_avatar_keeps_storage_url(): void
{
$user = new User;
$user->setRawAttributes(['avatar_path' => 'avatars/200/photo.jpg']);

$this->assertStringContainsString(
'avatars/200/photo.jpg',
$user->communityAvatarUrl()
);
}
}
Loading