14 KiB
Call Card Display Reference
This document describes when and how call cards are displayed on the platform, covering guest vs authenticated contexts, component variants, layout options, scoring logic, and platform configuration.
Overview
Call cards appear in two page contexts:
| Page | Audience | Components used |
|---|---|---|
Welcome page (/) |
Guests (unauthenticated) | WelcomePage\CallCardHalf, WelcomePage\CallCardCarousel (in CTA post) |
| Main dashboard | Authenticated users | MainPage\CallCardCarousel, MainPage\CallCardHalf, MainPage\CallCardFull |
Component Map
welcome.blade.php
├── @livewire('welcome-page.call-card-half', ['random' => true, 'rows' => 2])
│ └── WelcomePage\CallCardHalf
│ └── view: livewire/main-page/call-card-half.blade.php
│ └── x-call-card (components/call-card.blade.php) × N
│
└── @livewire('welcome.cta-post')
└── cta-post.blade.php
└── @livewire('welcome-page.call-card-carousel', ['random' => false])
└── WelcomePage\CallCardCarousel
└── view: livewire/main-page/call-card-carousel.blade.php
main-page.blade.php (authenticated)
├── @livewire('main-page.call-card-carousel', ['related' => true, 'random' => false])
│ └── MainPage\CallCardCarousel
│ └── view: livewire/main-page/call-card-carousel.blade.php
│
├── @livewire('main-page.call-card-half', ['related' => false, 'random' => true, 'rows' => 2])
│ └── MainPage\CallCardHalf
│ └── view: livewire/main-page/call-card-half.blade.php
│ └── x-call-card (components/call-card.blade.php) × N
│
└── [individual card sections use livewire/main-page/call-card-full.blade.php]
Guest vs Authenticated Distinction
Guest (Welcome page)
Components: WelcomePage\CallCardCarousel, WelcomePage\CallCardHalf
is_public = trueis always hardcoded — cannot be overridden by config- No profile context → no locality filtering applied
CallCarouselScoreris instantiated withnullfor all location IDs- Uses config block
calls.welcome_carousel - Location proximity boost factors have no effect (no profile location to compare against)
- Reaction buttons: displayed as read-only (no click to react). When
like_count > 0, a solid filled icon is shown in white. Whenlike_count = 0, the reaction button is hidden entirely.
Authenticated (Main dashboard)
Components: MainPage\CallCardCarousel, MainPage\CallCardHalf
is_publicenforcement controlled bycalls.carousel.exclude_non_public(platform config)- Profile resolved via
getActiveProfile()→ location extracted for locality filtering CallCarouselScorerreceives profile city/division/country IDs → location proximity boosts apply- Uses config block
calls.carousel - Own calls excluded when
calls.carousel.exclude_own_calls = true - Reaction buttons: fully interactive (like/unlike)
Shared Query Filters (all variants)
The following filters are always hardcoded regardless of config or auth state:
->whereNull('deleted_at')
->where('is_paused', false)
->where('is_suppressed', false)
->where(fn ($q) => $q->whereNull('till')->orWhere('till', '>=', now()))
Locality Filtering (authenticated only)
When a profile has a location, the auth carousel and half-card components build location ID arrays and add a WHERE clause to restrict calls to nearby locations.
Location resolution hierarchy:
- City level —
location.cityis set → expands to all sibling cities in the same division (whenrelated = true) - Division level —
location.divisionset but no city → expands to all sibling divisions in same parent (whenrelated = true) - Country level — only country set → all countries (when
related = true) or just the profile's country
Locality config keys (under calls.carousel):
| Key | Default | Effect |
|---|---|---|
include_unknown_location |
true |
Also include calls with unknown/unspecified location (country_id = 10) |
include_same_division |
true |
Include calls in same division as profile |
include_same_country |
true |
Include calls in same country as profile |
Scoring — CallCarouselScorer
All variants use App\Http\Livewire\Calls\CallCarouselScorer. The scorer multiplies a base score of 1.0 by a series of boost factors read from config.
Location proximity boosts (authenticated only — null IDs → no effect)
| Config key | Default | Applied when |
|---|---|---|
boost_same_district |
3.0 | Call location city matches profile city |
boost_location_city |
2.0 | Call has city-level location |
boost_location_division |
1.5 | Call has division-level location |
boost_location_country |
1.1 | Call has country-level location |
boost_location_unknown |
0.8 (default) / 2.5 (timebank_cc) | Call has unknown location |
Engagement boosts
| Config key | Default | Applied when |
|---|---|---|
boost_like_count |
0.05 | Multiplied by call's like count |
boost_star_count |
0.10 | Multiplied by callable's star count |
Recency / urgency boosts
| Config key | Default | Applied when |
|---|---|---|
boost_recent_from |
1.3 | Call was created within recent_days (14) days |
boost_soon_till |
1.2 | Call expires within soon_days (7) days |
Callable type boosts
| Config key | Default | Applied when |
|---|---|---|
boost_callable_user |
1.0 | Call posted by a User |
boost_callable_organization |
1.2 | Call posted by an Organization |
boost_callable_bank |
1.0 | Call posted by a Bank |
Random jitter
When the component is mounted with random = true, the final score is multiplied by random_int(85, 115) / 100, introducing ±15% variation to shuffle order on each page load.
Pool and Selection Logic
All components fetch a candidate pool larger than the number of cards to display, score all candidates in PHP, then take the top N by score.
pool_size = display_limit × pool_multiplier
| Component | display_limit | pool_multiplier config key |
|---|---|---|
CallCardCarousel (auth) |
max_cards |
calls.carousel.pool_multiplier |
CallCardCarousel (guest) |
max_cards |
calls.welcome_carousel.pool_multiplier |
CallCardHalf (auth) |
rows × 2 |
calls.carousel.pool_multiplier |
CallCardHalf (guest) |
rows × 2 |
calls.welcome_carousel.pool_multiplier |
Platform Configuration
calls.carousel — authenticated main-page carousel and half cards
'carousel' => [
'max_cards' => 12,
'pool_multiplier' => 5,
'exclude_non_public' => true, // false in timebank_cc (shows private calls to auth users)
'exclude_own_calls' => true,
'include_unknown_location' => true,
'include_same_division' => true,
'include_same_country' => true,
// Scoring boosts
'boost_same_district' => 3.0,
'boost_location_city' => 2.0,
'boost_location_division' => 1.5,
'boost_location_country' => 1.1,
'boost_location_unknown' => 0.8, // 2.5 in timebank_cc
'boost_like_count' => 0.05,
'boost_star_count' => 0.10,
'boost_recent_from' => 1.3,
'recent_days' => 14,
'boost_soon_till' => 1.2,
'soon_days' => 7,
'boost_callable_user' => 1.0,
'boost_callable_organization' => 1.2,
'boost_callable_bank' => 1.0,
'show_score' => false,
'show_score_for_admins' => true,
],
calls.welcome_carousel — guest welcome page carousel and half cards
'welcome_carousel' => [
'max_cards' => 12,
'pool_multiplier' => 5,
// No location boosts — no profile context for guests
'boost_like_count' => 0.05,
'boost_star_count' => 0.10,
'boost_recent_from' => 1.3,
'recent_days' => 14,
'boost_soon_till' => 1.2,
'soon_days' => 7,
'boost_callable_user' => 1.0,
'boost_callable_organization' => 1.2,
'boost_callable_bank' => 1.0,
'show_score' => false,
'show_score_for_admins' => true,
],
Key difference: welcome_carousel has no exclude_non_public key because is_public = true is hardcoded in the component. It also has no location keys because locality filtering is never applied on the guest page.
Visual Layouts
Carousel (call-card-carousel.blade.php)
Horizontal scrollable strip. Cards are small (170px tall, ~1/3 viewport wide, min 200px, max 320px). Intended for quick browsing. Alpine.js handles smooth scroll with left/right navigation buttons that appear on hover and hide at scroll boundaries.
Each card shows:
- Deepest (leaf) tag category badge
- Call title (truncated)
- Location and expiry badges
- Callable avatar and name
- Reaction button (top-right,
w-5 h-5) - Score (bottom-right, admin-only)
Half cards (call-card-half.blade.php + x-call-card)
Responsive grid: 1 column on mobile, 2 columns on md+. Each row contains 2 cards; rows prop controls how many rows are shown.
The x-call-card Blade component (components/call-card.blade.php) is used. Props:
| Prop | Type | Default | Description |
|---|---|---|---|
result |
array | [] |
Call data array |
index |
int | 0 |
Used to key nested Livewire components |
href |
string|null | null |
Override link URL |
wireClick |
string|null | null |
Use wire:click instead of href |
showScore |
bool | false |
Show score badge (bottom-right) |
showCallable |
bool | true |
Show callable avatar + name section |
showReactions |
bool | true |
Show reaction (like) button |
heightClass |
string | h-[430px] md:h-[550px] lg:h-[430px] |
Tailwind height classes |
truncateExcerpt |
bool | false |
Clamp excerpt to 2 lines |
Each card shows:
- Tag color background with
bg-black/50overlay - Deepest (leaf) tag category badge
- Call title (2-line clamp)
- Location and expiry badges
- Excerpt (2-line clamp when
truncateExcerpt = true) - Callable avatar, name, location (when
showCallable = true) - Reaction button top-right (when
showReactions = true)
Full card (call-card-full.blade.php)
Large hero-style card, full width. Used on the main dashboard for featured individual calls. Taller than half cards with larger typography (text-3xl/text-4xl title). Like button is absolutely positioned at top-14 right-4 with w-10 h-10 size.
Call Data Array Structure
All components produce a uniform array for each call:
[
'id' => int,
'model' => 'App\Models\Call',
'title' => string, // tag name in active locale
'excerpt' => string, // call translation content
'photo' => string, // callable profile photo URL
'location' => string, // "City, COUNTRY" or null
'tag_color' => string, // Tailwind color name (e.g. 'green', 'blue')
'tag_categories' => [ // ancestor chain from root to leaf
['name' => string, 'color' => string],
...
],
'callable_name' => string,
'callable_location' => string, // built from callable's profile location
'till' => datetime|null,
'expiry_badge_text' => string|null, // e.g. "Expires in 5 days"
'like_count' => int,
'score' => float,
]
Only the deepest (last) entry in tag_categories is displayed as a badge on the card.
Reaction Button Behaviour on Call Cards
| Context | State | Behaviour |
|---|---|---|
Guest, like_count = 0 |
Hidden | Entire reaction button hidden |
Guest, like_count > 0 |
Read-only | Solid filled icon + count, white (inverseColors) |
| Authenticated, not reacted | Interactive | Outline icon, clickable to like |
| Authenticated, reacted | Interactive | Solid filled icon, clickable to unlike |
On the call show page (calls/show-guest.blade.php) for guests, the reaction button links to the login page with the current URL as redirect, so clicking the icon takes the guest to login.
File Reference
| File | Role |
|---|---|
app/Http/Livewire/MainPage/CallCardCarousel.php |
Auth carousel — fetches, scores, filters by location |
app/Http/Livewire/MainPage/CallCardHalf.php |
Auth half-grid — same logic as carousel, rows prop |
app/Http/Livewire/WelcomePage/CallCardCarousel.php |
Guest carousel — public only, no location filter |
app/Http/Livewire/WelcomePage/CallCardHalf.php |
Guest half-grid — public only, no location filter |
app/Http/Livewire/Calls/CallCarouselScorer.php |
Scoring engine used by all four components |
app/Http/Livewire/Calls/ProfileCalls.php |
Helpers: buildCallableLocation(), buildExpiryBadgeText() |
resources/views/livewire/main-page/call-card-carousel.blade.php |
Carousel UI (shared by auth + guest carousel components) |
resources/views/livewire/main-page/call-card-half.blade.php |
Half-grid UI (shared by auth + guest half components) |
resources/views/livewire/main-page/call-card-full.blade.php |
Full-width card (main dashboard only) |
resources/views/components/call-card.blade.php |
Reusable card component used by half-grid |
resources/views/welcome.blade.php |
Guest welcome page layout |
resources/views/livewire/welcome/cta-post.blade.php |
CTA section — embeds guest carousel |
config/timebank-default.php |
Default calls.carousel and calls.welcome_carousel config |
config/timebank_cc.php |
Platform overrides (exclude_non_public, boost_location_unknown) |