takeone-youtube-clone/.claude/component-usage.md
ghassan 66fd78c10f Add multi-language audio tracks and self-hosted flag-icons
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>
2026-05-22 21:32:52 +03:00

240 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.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:** `: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, 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).
**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
```blade
{{-- 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