Files
timebank-cc-public/references/CALL_CARD_DISPLAY_REFERENCE.md
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

14 KiB
Raw Permalink Blame History

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 = true is always hardcoded — cannot be overridden by config
  • No profile context → no locality filtering applied
  • CallCarouselScorer is instantiated with null for 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. When like_count = 0, the reaction button is hidden entirely.

Authenticated (Main dashboard)

Components: MainPage\CallCardCarousel, MainPage\CallCardHalf

  • is_public enforcement controlled by calls.carousel.exclude_non_public (platform config)
  • Profile resolved via getActiveProfile() → location extracted for locality filtering
  • CallCarouselScorer receives 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:

  1. City levellocation.city is set → expands to all sibling cities in the same division (when related = true)
  2. Division levellocation.division set but no city → expands to all sibling divisions in same parent (when related = true)
  3. 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

'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,
],
'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

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/50 overlay
  • 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)