takeone-youtube-clone/.claude/component-usage.md
ghassan 6aae6f86b6 Add upload type chooser and redesign upload modal
Clicking Create now opens a card-based chooser (Generic / Music / Sports)
before the upload modal; the chosen type is applied and its Content Type
dropdown is hidden as redundant.

Per type:
- Generic/Match show their fields inline in the modal (no card/popup);
  Music keeps the track-card + Track Editor popup for multi-language tracks.
- "Language Track" wording stays music-only; a single Language field is now
  available for generic/match too (mirrored on the mobile create page with
  name-swapping so only the active picker submits).

Also unifies all modal controls (dropdowns, selects, inputs) to one larger,
red-accented dark style scoped to #uploadModal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 14:12:08 +03:00

17 KiB
Raw Blame History

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.phpApp\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.phpApp\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.php
  • resources/views/components/country-select.blade.php
  • resources/views/components/timezone-select.blade.php
  • resources/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: :videoVideo 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)

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, 131), month list (JanuaryDecember), 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), target-input (form mode: ID of file input the cropped File is set on), preview-img (ID of <img> updated with the cropped preview), output-width (final output px width), result-callback (callback mode: name of a global JS fn given the cropped File).
Three operating modes (mutually exclusive, checked in this order): (1) callback mode — when result-callback is set, both "Crop & Save" and "Upload as-is" hand the resulting File to window[resultCallback](file) and do not auto-close; the host fn decides when to close (closeCropperModal(id)) or load the next image. Used for multi-image queues (cover slides). (2) form mode — when target-input is set, the cropped File is placed on that file input (DataTransfer) and a change event is dispatched. (3) server mode — otherwise POSTs base64 to /image-upload, optionally POSTs path to update-url, then calls callback(url).
Behaviour: Renders a full-screen dark-themed modal with Cropme.js. Shows camera icon on avatar/banner hover (owner only). Exposes per-id globals: openCropperModal_{id}(), tcPreload_{id}(file), closeCropperModal(id). 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
resources/views/layouts/partials/edit-video-modal.blade.php slides_edit — square 448×252 Callback mode; result-callback=editSlidesCropDone; crops each cover slide before it enters the strip (queues multiple)
resources/views/layouts/partials/upload-modal.blade.php slides_upload — square 448×252 Callback mode; result-callback=uploadSlidesCropDone; cover-slide crop queue
resources/views/videos/create.blade.php slides_create_mobile — square 448×252 Mobile; callback mode; result-callback=cSlidesCropDone; cover-slide crop queue
resources/views/videos/edit.blade.php slides_edit_mobile — square 448×252 Mobile; callback mode; result-callback=epSlidesCropDone; cover-slide crop queue

<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 rich-text editor (<x-rich-text-editor>), 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. Adding cover slides routes through the slides_edit image-cropper (callback mode editSlidesCropDone) — each picked/dropped image is cropped to 16:9 before entering _editSlidesData; the live <x-image-cropper> instances are defined in edit-video-modal.blade.php.

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.phpLanguages::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/videos/create.blade.php primary_language (id="video_language_create") Video-mode language field inside #basic-fields-create (generic/match). setAudioMode() swaps name="primary_language" between this and primary_language_create so only the active mode's picker submits
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 ?? '' }}"

<x-rich-text-editor>

File: resources/views/components/rich-text-editor.blade.php
Props: name, id, value (initial HTML), placeholder, class, style, minHeight (default 110px).
Server sanitizer: app/Support/HtmlSanitizer.phpHtmlSanitizer::clean() (allowlist, on save) and HtmlSanitizer::render() (display; upgrades legacy plain text).
Stored value: sanitized HTML. Allowed tags: p, br, div, span, b/strong, i/em, u, s, h2, h3, ul, ol, li, blockquote, a. <a> may carry href (http/https/mailto only), target="_blank" (auto rel=noopener), and class limited to .action-btn variants (button links). style limited to text-align.
Behaviour: Renders a hidden <textarea class="rte-source" name id> as the form field (source of truth) wrapped in .rte-wrap. window.RTE builds the toolbar + contenteditable editor in JS (so Blade-rendered and JS-generated rows share one implementation) and a MutationObserver auto-inits any .rte-wrap added later (modals, cloned track rows). Toolbar: bold, italic, underline, strikethrough, heading (H2), bullet/numbered list, quote, align left/center/right, link, button-link (.action-btn), emoji, clear formatting. Editor↔textarea stay synced via input; external code that sets textarea.value must dispatch new Event('rte:refresh') to update the editor.
Rendering: display HTML via {!! \App\Support\HtmlSanitizer::render($value) !!}; truncation is CSS-clamp (.vdb-clamp) + JS overflow check, never character-truncation (would break tags).

View file Field name / id Notes
resources/views/components/track-editor-form.blade.php $descName / $descId Description in the Track Editor popup; primary + JS-cloned template tracks (edit-video-modal)
resources/views/layouts/partials/upload-modal.blade.php (no name) lt-track1-desc-modal + extra_track_descriptions[] Primary desc collected manually into FormData; extra-track rows generated via JS template string (.rte-wrap markup)
resources/views/videos/create.blade.php description video-description, (no name) lt-track1-desc-create, extra_track_descriptions[] Mobile upload; extra rows are JS template literal markup
resources/views/videos/edit.blade.php description edit-description, track_description_updates[{id}] Mobile edit; per-track rows rendered via Blade @foreach

Render sites (display): resources/views/videos/partials/description-box.blade.php (generic/match, also music), resources/views/videos/partials/audio-player.blade.php (_updateDescriptionBox per-track switch). SPA swaps re-run _vdbCheckOverflow() in generic.blade.php / match.blade.php.


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.php if the data structure changes
  • Update all three .blade.php component files if shared CSS/JS changes
  • Update the for*() method in Countries.php that 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