Introduce per-video language support and multiple audio tracks (VideoAudioTrack model + migrations for language, description, title), a reusable language-select component, and a track-editor form. Bundle the self-hosted flag-icons v7.2.3 library and a NAS auto-sync command. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
Reusable Select Component Usage
This file tracks every page/partial that uses <x-phone-code-select>, <x-country-select>, <x-timezone-select>, or <x-language-select>.
Update this file whenever you add or remove a component from a view.
When modifying any component or its data source, check all pages in the relevant section below and verify the change works correctly in each context.
Data sources
app/Data/Countries.php — App\Data\Countries
| Method | Used by component |
|---|---|
Countries::forPhoneCode() |
<x-phone-code-select> |
Countries::forCountry() |
<x-country-select> |
Countries::forTimezone() |
<x-timezone-select> |
Countries::all() |
All three (via the above methods) |
Adding or renaming a field in Countries::all() requires updating the corresponding for*() method too.
app/Data/Languages.php — App\Data\Languages
| Method | Used by component |
|---|---|
Languages::forLanguage() |
<x-language-select> |
Languages::all() |
Via forLanguage() |
Arabic and English are pinned to the top of the list; all others are sorted alphabetically by English name. Stored value is the ISO 639-1 code (e.g. "ar", "en").
Shared CSS / JS
The .csd-* CSS rules and the window.CSD class are duplicated across all four component files inside @once guards. If you change the look or behaviour of the dropdown, update all four component files:
resources/views/components/phone-code-select.blade.phpresources/views/components/country-select.blade.phpresources/views/components/timezone-select.blade.phpresources/views/components/language-select.blade.php
The @once Blade directive ensures the browser only receives one copy of the CSS/JS even when multiple components are on the same page.
language-select also emits an extra @once('lsd-badge-styles') block for the .lsd-code ISO badge that appears in place of a flag emoji.
<x-video-insights>
File: resources/views/components/video-insights.blade.php
Props: :video — Video model instance.
Behaviour: Renders the Insights tab panel (<div class="vdb-panel" id="vdb-insights">), the drill-down modal, all .ins-* CSS, and all insights JS (loadInsights, renderInsights, modal openers, country/day/downloader drill-downs). Only renders if Auth::id() === $video->user_id. Must be placed inside .vdb-wrap, after the About panel, so the tab-switch CSS applies. The parent view must call loadInsights() (global, defined by this component) when the Insights tab is activated.
Data source: GET /videos/{video}/insights (JSON) + drill-down routes /insights/country/{code}, /insights/day/{date}, /insights/downloader/{userId}.
| View file | Placement | Notes |
|---|---|---|
resources/views/videos/partials/description-box.blade.php |
Inside .vdb-wrap, after About panel |
Used by all three video type views (generic, match, music) |
resources/views/videos/show.blade.php |
Inside .vdb-wrap, after About panel |
Legacy view (not rendered by controller — kept in sync) |
<x-social-links-editor>
File: resources/views/components/social-links-editor.blade.php
Props: existing — associative array keyed by platform name (e.g. ['twitter' => 'handle', 'whatsapp' => '97312345678']).
Behaviour: Dynamic add/remove rows; each row has a custom icon dropdown to pick the platform and a text input for the value. Supported platforms: twitter, instagram, facebook, youtube, linkedin, tiktok, whatsapp, website, google_location, social_phone, social_email. Hidden clear inputs ensure removed entries are cleared on save. Must be placed inside a <form>.
DB columns: twitter, instagram, facebook, youtube, linkedin, tiktok, website (legacy), whatsapp, google_location, social_phone, social_email.
| View file | Placement | Notes |
|---|---|---|
resources/views/user/profile.blade.php |
Social tab of Edit Profile modal | $socialExisting array passed from @php block above @section('scripts') |
<x-date-picker>
File: resources/views/components/date-picker.blade.php
Stored value: YYYY-MM-DD string in a hidden input (same format as <input type="date">).
Props: name, id, value, label, required, class, style, minYear (default 1900), maxYear (default current year).
Behaviour: Day grid (5 columns, 1–31), month list (January–December), year searchable list (descending). Days auto-constrain on month/year change; invalid selected day resets automatically.
| View file | Field name | Notes |
|---|---|---|
resources/views/user/profile.blade.php |
birthday |
Replaces <input type="date"> |
resources/views/auth/register.blade.php |
birthday |
Registration form — mandatory |
<x-image-cropper>
File: resources/views/components/image-cropper.blade.php
Props: id (unique, required), width (px, default 300), height (px, default 300), shape (circle|square, default circle), folder (storage subfolder), filename (base name without extension), callback (JS function name called with URL on success), update-url (endpoint to POST {path} after crop to update DB), title (modal heading).
Behaviour: Renders a full-screen dark-themed modal with Cropme.js. Shows camera icon on avatar/banner hover (owner only). After crop: POSTs base64 to /image-upload, optionally POSTs the path to update-url, then calls callback(url). Uses local assets (public/js/cropme.min.js, public/css/cropme.min.css). No jQuery required.
Assets needed: public/js/cropme.min.js, public/css/cropme.min.css.
Routes needed: image.upload (POST /image-upload).
| View file | id / use | Notes |
|---|---|---|
resources/views/user/channel.blade.php |
avatar — circle 300×300 |
Owner only; update-url = profile.updateAvatar; callback onAvatarSaved |
resources/views/user/channel.blade.php |
banner — square 500×160 |
Owner only; update-url = profile.updateBanner; callback onBannerSaved |
resources/views/layouts/partials/upload-modal.blade.php |
thumb_upload — square 448×252 |
Form mode; target-input=thumbnail-modal; output 1280px |
resources/views/layouts/partials/edit-video-modal.blade.php |
thumb_edit — square 448×252 |
Form mode; target-input=edit-t1-thumbnail-input; output 1280px |
resources/views/videos/create.blade.php |
thumb_create_mobile — square 448×252 |
Mobile; target-input=thumbnail; output 1280px |
resources/views/videos/edit.blade.php |
thumb_edit_mobile — square 448×252 |
Mobile; target-input=edit-thumbnail; output 1280px |
resources/views/playlists/index.blade.php |
thumb_pl_create — square 448×252 |
Form mode; target-input=playlist-thumbnail-input; output 1280px |
resources/views/playlists/show.blade.php |
thumb_pl_edit — square 448×252 |
Form mode; target-input=playlistThumbnailInput; output 1280px |
<x-gender-select>
File: resources/views/components/gender-select.blade.php
Props: name, id, value (ISO string: "male" or "female"), label, required, class, style.
Behaviour: Custom dropdown with blue ♂ (male) and pink ♀ (female) symbols. No search needed. Stores value as "male" or "female".
| View file | Field name | Notes |
|---|---|---|
resources/views/auth/register.blade.php |
gender |
Registration form — mandatory |
<x-phone-code-select>
Stored value format: "+973|BH" (dial_code + pipe + ISO2).
To read only the dial code from a stored value: explode('|', $value)[0].
| View file | Field name | Notes |
|---|---|---|
resources/views/user/profile.blade.php |
phone_code |
Paired with phone_number text input |
<x-country-select>
Stored value: ISO2 code (e.g. "BH").
| View file | Field name | Notes |
|---|---|---|
resources/views/user/profile.blade.php |
nationality |
Edit Profile form |
resources/views/auth/register.blade.php |
nationality |
Registration form — mandatory |
<x-timezone-select>
Stored value: IANA timezone string (e.g. "Asia/Bahrain").
| View file | Field name | Notes |
|---|---|---|
resources/views/user/profile.blade.php |
timezone |
Edit Profile form |
<x-track-editor-form>
File: resources/views/components/track-editor-form.blade.php
Props: prefix (default 't1'), isPrimary (bool, default false), languageName, languageId, titleName, titleId, descName, descId, videoFileInputId.
Behaviour: Renders the full track editor form panel shown inside the Track Editor popup. Contains: optional Primary Track banner (when :is-primary="true"), language dropdown (<x-language-select>), title input, description textarea, video+thumbnail zone (hidden, shown for video/match type via _editApplyMode), and audio+slides zone (hidden, shown for music type). All element IDs are prefixed with edit-{prefix}-*. JS functions editHandleThumbnail(input, prefix), editRemoveThumbnail(event, prefix), editSlidesZoneClick(event, tid), editHandleSlides(files, tid), editClearSlides(event, tid) all accept the prefix/tid param.
| View file | Prefix used | Notes |
|---|---|---|
resources/views/layouts/partials/edit-video-modal.blade.php |
t1 |
Primary track only; secondary tracks are built via JS (_editAddExistingTrack) |
<x-language-select>
File: resources/views/components/language-select.blade.php
Data source: app/Data/Languages.php — Languages::forLanguage()
Stored value: ISO 639-1 code (e.g. "ar", "en", "fr").
Props: name, id, value, label, placeholder, required, class, style.
Icon: 2-letter uppercase ISO code rendered as a monospace badge (.lsd-code) — no flag emoji.
Arabic and English are always pinned to the top of the list; all other languages are alphabetical by English name.
Rule: This component must be used for every language picker in the application. Never build a custom <select> or inline list for language selection.
| View file | Field name | Notes |
|---|---|---|
resources/views/layouts/partials/upload-modal.blade.php |
primary_language |
Inside accordion track 1 body (id="lang-tracks-section-modal"); extra track language rows use LANG_OPTIONS_MODAL JS constant for inline dynamic CSD |
resources/views/videos/create.blade.php |
primary_language |
Inside accordion track 1 body (id="lang-tracks-section-create"); extra track language rows use LANG_OPTIONS_CREATE JS constant for inline dynamic CSD |
resources/views/components/track-editor-form.blade.php |
$languageName (prop) |
Rendered inside the track editor form; used for primary track in edit-video-modal (prefix t1) |
resources/views/videos/edit.blade.php |
primary_language |
Inside @else (audio only) block; pre-populated with value="{{ $video->language ?? '' }}" |
Usage example
{{-- Phone code + number side by side --}}
<div style="display:flex; gap:8px;">
<x-phone-code-select
name="phone_code"
value="+973|BH"
label="Phone"
required
style="width:140px; flex-shrink:0;"
/>
<input type="tel" name="phone_number" class="form-control">
</div>
{{-- Country / nationality --}}
<x-country-select
name="nationality"
label="Nationality"
placeholder="Select nationality"
value="{{ old('nationality', $user->nationality) }}"
required
/>
{{-- Timezone --}}
<x-timezone-select
name="timezone"
label="Timezone"
value="{{ old('timezone', $user->timezone ?? 'Asia/Bahrain') }}"
required
/>
{{-- Language --}}
<x-language-select
name="language"
label="Language"
placeholder="Select language"
value="{{ old('language', $video->language ?? '') }}"
required
/>
Modification checklist
When you modify any of these components, work through this list:
- Update
app/Data/Countries.phpif the data structure changes - Update all three
.blade.phpcomponent files if shared CSS/JS changes - Update the
for*()method inCountries.phpthat feeds the changed component - Re-test every page listed in the usage tables above
- Add/remove rows from the usage tables if views were added or removed