diff --git a/app/Enums/ChannelCategory.php b/app/Enums/ChannelCategory.php new file mode 100644 index 0000000..06547ae --- /dev/null +++ b/app/Enums/ChannelCategory.php @@ -0,0 +1,45 @@ + __('Gaming'), + self::Music => __('Music'), + self::TalkShows => __('Talk Shows'), + self::Education => __('Education'), + self::Creative => __('Creative'), + }; + } + + /** + * @return array{value: string, label: string} + */ + public function toOption(): array + { + return [ + 'value' => $this->value, + 'label' => $this->label(), + ]; + } + + /** + * @return list + */ + public static function options(): array + { + return array_map( + fn (self $category): array => $category->toOption(), + self::cases(), + ); + } +} diff --git a/app/Http/Controllers/ChannelController.php b/app/Http/Controllers/ChannelController.php index cac4225..74b58a1 100644 --- a/app/Http/Controllers/ChannelController.php +++ b/app/Http/Controllers/ChannelController.php @@ -22,7 +22,7 @@ class ChannelController extends Controller ): Response { abort_if($channel->isSuspended(), 404); - $channel->load(['category', 'liveBroadcast', 'user']); + $channel->load(['liveBroadcast', 'user']); $channel->loadCount('follows'); $currentBroadcast = $channel->liveBroadcast; @@ -39,11 +39,7 @@ class ChannelController extends Controller 'is_live' => $channel->is_live, 'viewer_count' => $channel->is_live ? $viewerCounts->current($channel) : 0, 'followers_count' => $channel->follows_count, - 'category' => $channel->category ? [ - 'id' => $channel->category->id, - 'name' => $channel->category->name, - 'slug' => $channel->category->slug, - ] : null, + 'category' => $channel->category?->toOption(), 'owner' => [ 'id' => $channel->user->id, 'name' => $channel->user->name, diff --git a/app/Http/Controllers/CreatorDashboardController.php b/app/Http/Controllers/CreatorDashboardController.php index 8737481..e84d8f4 100644 --- a/app/Http/Controllers/CreatorDashboardController.php +++ b/app/Http/Controllers/CreatorDashboardController.php @@ -2,8 +2,8 @@ namespace App\Http\Controllers; +use App\Enums\ChannelCategory; use App\Models\Broadcast; -use App\Models\Category; use App\Models\Channel; use App\Services\Streaming\StreamKeyManager; use App\Services\Streaming\ViewerCountStore; @@ -21,7 +21,7 @@ class CreatorDashboardController extends Controller { $channel = $request->user() ->channel() - ->with(['category', 'liveBroadcast', 'streamKey']) + ->with(['liveBroadcast', 'streamKey']) ->first(); return Inertia::render('dashboard', [ @@ -34,14 +34,7 @@ class CreatorDashboardController extends Controller ? $streamKeys->tokenizedPath($channel, (string) session('plain_stream_key')) : null, ], - 'categories' => Category::query() - ->orderBy('name') - ->get(['id', 'name', 'slug']) - ->map(fn (Category $category) => [ - 'id' => $category->id, - 'name' => $category->name, - 'slug' => $category->slug, - ]), + 'categories' => ChannelCategory::options(), 'recentBroadcasts' => $channel ? $channel->broadcasts()->with('vod')->latest()->limit(10)->get()->map(fn (Broadcast $broadcast) => [ 'id' => $broadcast->id, @@ -65,7 +58,7 @@ class CreatorDashboardController extends Controller 'display_name' => ['required', 'string', 'max:80'], 'slug' => ['required', 'string', 'min:3', 'max:40', 'alpha_dash:ascii', 'lowercase', 'unique:channels,slug'], 'description' => ['nullable', 'string', 'max:1000'], - 'category_id' => ['nullable', 'exists:categories,id'], + 'category' => ['nullable', Rule::enum(ChannelCategory::class)], ]); $channel = $request->user()->channel()->create([ @@ -92,7 +85,7 @@ class CreatorDashboardController extends Controller 'display_name' => ['required', 'string', 'max:80'], 'slug' => ['required', 'string', 'min:3', 'max:40', 'alpha_dash:ascii', 'lowercase', Rule::unique('channels', 'slug')->ignore($channel)], 'description' => ['nullable', 'string', 'max:1000'], - 'category_id' => ['nullable', 'exists:categories,id'], + 'category' => ['nullable', Rule::enum(ChannelCategory::class)], 'thumbnail' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'], ]); @@ -187,7 +180,7 @@ class CreatorDashboardController extends Controller 'is_live' => $channel->is_live, 'viewer_count' => $channel->viewer_count, 'stream_key_last_used_at' => $channel->streamKey?->last_used_at?->toIso8601String(), - 'category_id' => $channel->category_id, + 'category' => $channel->category?->value, 'live_broadcast' => $channel->liveBroadcast ? [ 'id' => $channel->liveBroadcast->id, 'title' => $channel->liveBroadcast->title, diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 5239c69..09b37ca 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -2,7 +2,7 @@ namespace App\Http\Controllers; -use App\Models\Category; +use App\Enums\ChannelCategory; use App\Models\Channel; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; @@ -17,7 +17,7 @@ class HomeController extends Controller $liveChannels = Channel::query() ->where('is_live', true) ->whereNull('suspended_at') - ->with(['category', 'liveBroadcast', 'user']) + ->with(['liveBroadcast', 'user']) ->withCount('follows') ->orderByDesc('viewer_count') ->latest() @@ -28,14 +28,7 @@ class HomeController extends Controller return Inertia::render('welcome', [ 'canRegister' => Features::enabled(Features::registration()), 'liveChannels' => $liveChannels, - 'categories' => Category::query() - ->orderBy('name') - ->get(['id', 'name', 'slug']) - ->map(fn (Category $category) => [ - 'id' => $category->id, - 'name' => $category->name, - 'slug' => $category->slug, - ]), + 'categories' => ChannelCategory::options(), ]); } @@ -52,11 +45,7 @@ class HomeController extends Controller 'banner_url' => $this->mediaUrl($channel->banner_path), 'viewer_count' => $channel->viewer_count, 'followers_count' => $channel->follows_count ?? 0, - 'category' => $channel->category ? [ - 'id' => $channel->category->id, - 'name' => $channel->category->name, - 'slug' => $channel->category->slug, - ] : null, + 'category' => $channel->category?->toOption(), 'broadcast' => $channel->liveBroadcast ? [ 'id' => $channel->liveBroadcast->id, 'title' => $channel->liveBroadcast->title, diff --git a/app/Models/Category.php b/app/Models/Category.php deleted file mode 100644 index 526d0d3..0000000 --- a/app/Models/Category.php +++ /dev/null @@ -1,21 +0,0 @@ - */ - use HasFactory; - - public function channels(): HasMany - { - return $this->hasMany(Channel::class); - } -} diff --git a/app/Models/Channel.php b/app/Models/Channel.php index d19b726..0f04098 100644 --- a/app/Models/Channel.php +++ b/app/Models/Channel.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Enums\ChannelCategory; use Database\Factories\ChannelFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -12,7 +13,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne; #[Fillable([ 'user_id', - 'category_id', + 'category', 'live_broadcast_id', 'slug', 'display_name', @@ -32,6 +33,7 @@ class Channel extends Model protected function casts(): array { return [ + 'category' => ChannelCategory::class, 'is_live' => 'boolean', 'viewer_count' => 'integer', 'suspended_at' => 'datetime', @@ -48,11 +50,6 @@ class Channel extends Model return $this->belongsTo(User::class); } - public function category(): BelongsTo - { - return $this->belongsTo(Category::class); - } - public function streamKey(): HasOne { return $this->hasOne(StreamKey::class); diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php deleted file mode 100644 index 9e07238..0000000 --- a/database/factories/CategoryFactory.php +++ /dev/null @@ -1,20 +0,0 @@ -unique()->words(2, true); - - return [ - 'name' => Str::headline($name), - 'slug' => Str::slug($name), - 'description' => fake()->optional()->sentence(), - ]; - } -} diff --git a/database/factories/ChannelFactory.php b/database/factories/ChannelFactory.php index bc79715..729975c 100644 --- a/database/factories/ChannelFactory.php +++ b/database/factories/ChannelFactory.php @@ -2,7 +2,7 @@ namespace Database\Factories; -use App\Models\Category; +use App\Enums\ChannelCategory; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; @@ -15,7 +15,7 @@ class ChannelFactory extends Factory return [ 'user_id' => User::factory(), - 'category_id' => Category::factory(), + 'category' => fake()->randomElement(ChannelCategory::cases())->value, 'slug' => Str::slug($name), 'display_name' => Str::headline($name), 'description' => fake()->sentence(), diff --git a/database/migrations/2026_05_18_031006_replace_stream_categories_with_enum_on_channels_table.php b/database/migrations/2026_05_18_031006_replace_stream_categories_with_enum_on_channels_table.php new file mode 100644 index 0000000..b3adc12 --- /dev/null +++ b/database/migrations/2026_05_18_031006_replace_stream_categories_with_enum_on_channels_table.php @@ -0,0 +1,76 @@ +string('category')->nullable()->after('user_id')->index(); + }); + + $allowedCategories = ['gaming', 'music', 'talk-shows', 'education', 'creative']; + + DB::table('categories') + ->select(['id', 'slug']) + ->orderBy('id') + ->get() + ->each(function (object $category) use ($allowedCategories): void { + if (! in_array($category->slug, $allowedCategories, true)) { + return; + } + + DB::table('channels') + ->where('category_id', $category->id) + ->update(['category' => $category->slug]); + }); + + Schema::table('channels', function (Blueprint $table) { + $table->dropConstrainedForeignId('category_id'); + }); + + Schema::dropIfExists('categories'); + } + + public function down(): void + { + Schema::create('categories', function (Blueprint $table) { + $table->id(); + $table->string('name')->unique(); + $table->string('slug')->unique(); + $table->text('description')->nullable(); + $table->timestamps(); + }); + + DB::table('categories')->insert([ + ['name' => 'Gaming', 'slug' => 'gaming', 'created_at' => now(), 'updated_at' => now()], + ['name' => 'Music', 'slug' => 'music', 'created_at' => now(), 'updated_at' => now()], + ['name' => 'Talk Shows', 'slug' => 'talk-shows', 'created_at' => now(), 'updated_at' => now()], + ['name' => 'Education', 'slug' => 'education', 'created_at' => now(), 'updated_at' => now()], + ['name' => 'Creative', 'slug' => 'creative', 'created_at' => now(), 'updated_at' => now()], + ]); + + Schema::table('channels', function (Blueprint $table) { + $table->foreignId('category_id')->nullable()->after('user_id')->constrained()->nullOnDelete(); + }); + + DB::table('categories') + ->select(['id', 'slug']) + ->orderBy('id') + ->get() + ->each(function (object $category): void { + DB::table('channels') + ->where('category', $category->slug) + ->update(['category_id' => $category->id]); + }); + + Schema::table('channels', function (Blueprint $table) { + $table->dropIndex(['category']); + $table->dropColumn('category'); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index c855e22..28277dd 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,7 +2,7 @@ namespace Database\Seeders; -use App\Models\Category; +use App\Enums\ChannelCategory; use App\Models\PlatformSetting; use App\Models\User; use App\Services\Streaming\StreamKeyManager; @@ -15,17 +15,6 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - collect([ - ['name' => 'Gaming', 'slug' => 'gaming'], - ['name' => 'Music', 'slug' => 'music'], - ['name' => 'Talk Shows', 'slug' => 'talk-shows'], - ['name' => 'Education', 'slug' => 'education'], - ['name' => 'Creative', 'slug' => 'creative'], - ])->each(fn (array $category) => Category::query()->firstOrCreate( - ['slug' => $category['slug']], - ['name' => $category['name']], - )); - PlatformSetting::current(); $admin = User::query()->firstOrNew(['email' => 'admin@nyone.net']); @@ -47,7 +36,7 @@ class DatabaseSeeder extends Seeder ])->save(); $channel = $official->channel()->updateOrCreate([], [ - 'category_id' => Category::query()->where('slug', 'creative')->value('id'), + 'category' => ChannelCategory::Creative, 'slug' => 'nyone', 'display_name' => 'Nyone Official', 'description' => 'Official updates and live sessions from Nyone.', diff --git a/lang/ar.json b/lang/ar.json index d0a02d9..f339b4e 100644 --- a/lang/ar.json +++ b/lang/ar.json @@ -82,6 +82,7 @@ "Create an account": "إنشاء حساب", "Create channel": "إنشاء قناة", "Create your channel": "أنشئ قناتك", + "Creative": "الإبداع", "Creator Studio": "استوديو المنشئ", "Creator access grants stay in the moderation console.": "تبقى أذونات وصول المنشئ في وحدة الإشراف.", "Creator grants": "أذونات المنشئ", @@ -101,6 +102,7 @@ "Don't have an account?": "ليس لديك حساب؟", "Each account can own one public channel in this version.": "يمكن لكل حساب امتلاك قناة عامة واحدة في هذا الإصدار.", "Each recovery code can be used once to access your account and will be removed after use. If you need more, click": "يمكن استخدام كل رمز استرداد مرة واحدة للوصول إلى حسابك وسيتم حذفه بعد الاستخدام. إذا احتجت إلى المزيد، فانقر", + "Education": "التعليم", "Email": "البريد الإلكتروني", "Email address": "عنوان البريد الإلكتروني", "Email password reset link": "إرسال رابط إعادة تعيين كلمة المرور", @@ -123,6 +125,7 @@ "Forgot password": "نسيت كلمة المرور", "Forgot password?": "هل نسيت كلمة المرور؟", "Full name": "الاسم الكامل", + "Gaming": "الألعاب", "Grant": "منح", "Hide password": "إخفاء كلمة المرور", "Hide recovery codes": "إخفاء رموز الاسترداد", @@ -152,6 +155,7 @@ "Message sent to admin.": "تم إرسال الرسالة إلى المسؤول.", "Moderation console": "وحدة الإشراف", "More": "المزيد", + "Music": "الموسيقى", "NEEDS APPROVAL": "يتطلب موافقة", "NEEDS CHANNEL GRANT": "يتطلب إذن قناة", "Name": "الاسم", @@ -268,6 +272,7 @@ "Suspend": "تعليق", "Suspend channel?": "تعليق القناة؟", "System": "النظام", + "Talk Shows": "برامج الحوار", "The channel will be hidden from public browsing and watch pages.": "سيتم إخفاء القناة من التصفح العام وصفحات المشاهدة.", "The channel will be visible again if the owning account is active.": "ستظهر القناة مرة أخرى إذا كان حساب المالك نشطًا.", "The live floor is quiet": "ساحة البث هادئة", diff --git a/lang/en.json b/lang/en.json index 8ea7dd1..f4eb977 100644 --- a/lang/en.json +++ b/lang/en.json @@ -82,6 +82,7 @@ "Create an account": "Create an account", "Create channel": "Create channel", "Create your channel": "Create your channel", + "Creative": "Creative", "Creator Studio": "Creator Studio", "Creator access grants stay in the moderation console.": "Creator access grants stay in the moderation console.", "Creator grants": "Creator grants", @@ -101,6 +102,7 @@ "Don't have an account?": "Don't have an account?", "Each account can own one public channel in this version.": "Each account can own one public channel in this version.", "Each recovery code can be used once to access your account and will be removed after use. If you need more, click": "Each recovery code can be used once to access your account and will be removed after use. If you need more, click", + "Education": "Education", "Email": "Email", "Email address": "Email address", "Email password reset link": "Email password reset link", @@ -123,6 +125,7 @@ "Forgot password": "Forgot password", "Forgot password?": "Forgot password?", "Full name": "Full name", + "Gaming": "Gaming", "Grant": "Grant", "Hide password": "Hide password", "Hide recovery codes": "Hide recovery codes", @@ -152,6 +155,7 @@ "Message sent to admin.": "Message sent to admin.", "Moderation console": "Moderation console", "More": "More", + "Music": "Music", "NEEDS APPROVAL": "NEEDS APPROVAL", "NEEDS CHANNEL GRANT": "NEEDS CHANNEL GRANT", "Name": "Name", @@ -268,6 +272,7 @@ "Suspend": "Suspend", "Suspend channel?": "Suspend channel?", "System": "System", + "Talk Shows": "Talk Shows", "The channel will be hidden from public browsing and watch pages.": "The channel will be hidden from public browsing and watch pages.", "The channel will be visible again if the owning account is active.": "The channel will be visible again if the owning account is active.", "The live floor is quiet": "The live floor is quiet", diff --git a/lang/es.json b/lang/es.json index 8ef8a8b..1b91d9e 100644 --- a/lang/es.json +++ b/lang/es.json @@ -82,6 +82,7 @@ "Create an account": "Crear una cuenta", "Create channel": "Crear canal", "Create your channel": "Crea tu canal", + "Creative": "Creatividad", "Creator Studio": "Estudio de creador", "Creator access grants stay in the moderation console.": "Los permisos de acceso de creador permanecen en la consola de moderación.", "Creator grants": "Permisos de creador", @@ -101,6 +102,7 @@ "Don't have an account?": "¿No tienes una cuenta?", "Each account can own one public channel in this version.": "Cada cuenta puede tener un canal público en esta versión.", "Each recovery code can be used once to access your account and will be removed after use. If you need more, click": "Cada código de recuperación puede usarse una vez para acceder a tu cuenta y se eliminará después de usarlo. Si necesitas más, haz clic", + "Education": "Educación", "Email": "Correo electrónico", "Email address": "Dirección de correo electrónico", "Email password reset link": "Enviar enlace para restablecer contraseña", @@ -123,6 +125,7 @@ "Forgot password": "Olvidé mi contraseña", "Forgot password?": "¿Olvidaste tu contraseña?", "Full name": "Nombre completo", + "Gaming": "Videojuegos", "Grant": "Conceder", "Hide password": "Ocultar contraseña", "Hide recovery codes": "Ocultar códigos de recuperación", @@ -152,6 +155,7 @@ "Message sent to admin.": "Mensaje enviado al administrador.", "Moderation console": "Consola de moderación", "More": "Más", + "Music": "Música", "NEEDS APPROVAL": "REQUIERE APROBACIÓN", "NEEDS CHANNEL GRANT": "REQUIERE PERMISO DE CANAL", "Name": "Nombre", @@ -268,6 +272,7 @@ "Suspend": "Suspender", "Suspend channel?": "¿Suspender canal?", "System": "Sistema", + "Talk Shows": "Programas de conversación", "The channel will be hidden from public browsing and watch pages.": "El canal se ocultará de la navegación pública y de las páginas de visualización.", "The channel will be visible again if the owning account is active.": "El canal volverá a estar visible si la cuenta propietaria está activa.", "The live floor is quiet": "No hay actividad en vivo", diff --git a/lang/fa.json b/lang/fa.json index 10629b2..cc81632 100644 --- a/lang/fa.json +++ b/lang/fa.json @@ -82,6 +82,7 @@ "Create an account": "ایجاد یک حساب", "Create channel": "ایجاد کانال", "Create your channel": "کانال خود را ایجاد کنید", + "Creative": "خلاقیت", "Creator Studio": "استودیوی سازنده", "Creator access grants stay in the moderation console.": "مجوزهای دسترسی سازنده در کنسول مدیریت باقی می‌مانند.", "Creator grants": "مجوزهای سازنده", @@ -101,6 +102,7 @@ "Don't have an account?": "حساب ندارید؟", "Each account can own one public channel in this version.": "در این نسخه هر حساب می‌تواند یک کانال عمومی داشته باشد.", "Each recovery code can be used once to access your account and will be removed after use. If you need more, click": "هر کد بازیابی فقط یک‌بار برای دسترسی به حساب شما قابل استفاده است و پس از استفاده حذف می‌شود. اگر به کدهای بیشتری نیاز دارید، کلیک کنید", + "Education": "آموزش", "Email": "ایمیل", "Email address": "آدرس ایمیل", "Email password reset link": "ارسال لینک بازنشانی رمز عبور", @@ -123,6 +125,7 @@ "Forgot password": "فراموشی رمز عبور", "Forgot password?": "رمز عبور را فراموش کرده‌اید؟", "Full name": "نام کامل", + "Gaming": "بازی", "Grant": "اعطا", "Hide password": "پنهان کردن رمز عبور", "Hide recovery codes": "پنهان کردن کدهای بازیابی", @@ -152,6 +155,7 @@ "Message sent to admin.": "پیام برای مدیر ارسال شد.", "Moderation console": "کنسول مدیریت", "More": "بیشتر", + "Music": "موسیقی", "NEEDS APPROVAL": "نیازمند تأیید", "NEEDS CHANNEL GRANT": "نیازمند مجوز کانال", "Name": "نام", @@ -268,6 +272,7 @@ "Suspend": "تعلیق", "Suspend channel?": "کانال تعلیق شود؟", "System": "سیستم", + "Talk Shows": "گفت‌وگو", "The channel will be hidden from public browsing and watch pages.": "کانال از مرور عمومی و صفحات تماشا پنهان خواهد شد.", "The channel will be visible again if the owning account is active.": "اگر حساب مالک فعال باشد، کانال دوباره قابل مشاهده خواهد شد.", "The live floor is quiet": "فضای زنده آرام است", diff --git a/resources/js/pages/channels/show.tsx b/resources/js/pages/channels/show.tsx index 07abd90..8624f5f 100644 --- a/resources/js/pages/channels/show.tsx +++ b/resources/js/pages/channels/show.tsx @@ -150,7 +150,7 @@ export default function ChannelShow({ {channel.category && ( - {channel.category.name} + {channel.category.label} )} diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx index a764805..bedfee7 100644 --- a/resources/js/pages/dashboard.tsx +++ b/resources/js/pages/dashboard.tsx @@ -38,7 +38,7 @@ import { update as updateChannelRoute, } from '@/routes/creator/channel'; import { rotate as rotateStreamKeyRoute } from '@/routes/creator/stream-key'; -import type { BroadcastSummary, Category } from '@/types'; +import type { BroadcastSummary, ChannelCategory } from '@/types'; type CreatorChannel = { id: number; @@ -51,7 +51,7 @@ type CreatorChannel = { is_live: boolean; viewer_count: number; stream_key_last_used_at: string | null; - category_id: number | null; + category: string | null; live_broadcast: BroadcastSummary | null; }; @@ -63,7 +63,7 @@ type Props = { ingestServer: string; tokenizedPath: string | null; }; - categories: Category[]; + categories: ChannelCategory[]; recentBroadcasts: BroadcastSummary[]; }; @@ -90,14 +90,14 @@ export default function Dashboard({ display_name: '', slug: '', description: '', - category_id: '', + category: '', }); const channelForm = useForm({ _method: 'PATCH', display_name: channel?.display_name ?? '', slug: channel?.slug ?? '', description: channel?.description ?? '', - category_id: channel?.category_id ? String(channel.category_id) : '', + category: channel?.category ?? '', thumbnail: null as File | null, }); const broadcastForm = useForm({ @@ -235,13 +235,16 @@ export default function Dashboard({ placeholder={t('nyone-live')} /> - + createForm.setData( - 'category_id', + 'category', value, ) } @@ -440,13 +443,16 @@ export default function Dashboard({ } /> - + channelForm.setData( - 'category_id', + 'category', value, ) } @@ -763,7 +769,7 @@ function CategorySelect({ value, onChange, }: { - categories: Category[]; + categories: ChannelCategory[]; value: string; onChange: (value: string) => void; }) { @@ -777,8 +783,8 @@ function CategorySelect({ > {categories.map((category) => ( - ))} diff --git a/resources/js/pages/welcome.tsx b/resources/js/pages/welcome.tsx index 5fe7eeb..aca3053 100644 --- a/resources/js/pages/welcome.tsx +++ b/resources/js/pages/welcome.tsx @@ -19,12 +19,12 @@ import { cn } from '@/lib/utils'; import { dashboard, home, login, register } from '@/routes'; import { show as showChannel } from '@/routes/channels'; import { index as supportIndex } from '@/routes/support'; -import type { Category, ChannelCard } from '@/types'; +import type { ChannelCard, ChannelCategory } from '@/types'; type Props = { canRegister: boolean; liveChannels: ChannelCard[]; - categories: Category[]; + categories: ChannelCategory[]; }; export default function Welcome({ @@ -43,7 +43,7 @@ export default function Welcome({ return liveChannels.filter((channel) => { const matchesCategory = - category === 'all' || channel.category?.slug === category; + category === 'all' || channel.category?.value === category; const matchesQuery = normalizedQuery === '' || channel.display_name.toLowerCase().includes(normalizedQuery) || @@ -175,7 +175,7 @@ export default function Welcome({ {featuredChannel.category - ?.name ?? + ?.label ?? t('Uncategorized')} @@ -292,8 +292,11 @@ export default function Welcome({ {t('All categories')} {categories.map((item) => ( - ))} @@ -356,7 +359,7 @@ function StreamCard({ channel }: { channel: ChannelCard }) {
- {channel.category?.name ?? t('Uncategorized')} + {channel.category?.label ?? t('Uncategorized')} {t(':count viewers', { diff --git a/resources/js/types/streaming.ts b/resources/js/types/streaming.ts index 5318ed3..100bfae 100644 --- a/resources/js/types/streaming.ts +++ b/resources/js/types/streaming.ts @@ -1,7 +1,6 @@ -export type Category = { - id: number; - name: string; - slug: string; +export type ChannelCategory = { + value: string; + label: string; }; export type ChannelCard = { @@ -15,7 +14,7 @@ export type ChannelCard = { banner_url: string | null; viewer_count: number; followers_count: number; - category: Category | null; + category: ChannelCategory | null; broadcast: { id: number; title: string; @@ -38,7 +37,7 @@ export type ChannelDetail = { is_live: boolean; viewer_count: number; followers_count: number; - category: Category | null; + category: ChannelCategory | null; owner: { id: number; name: string; diff --git a/tests/Feature/CreatorChannelTest.php b/tests/Feature/CreatorChannelTest.php index 5fbd18a..07f72d3 100644 --- a/tests/Feature/CreatorChannelTest.php +++ b/tests/Feature/CreatorChannelTest.php @@ -2,7 +2,7 @@ namespace Tests\Feature; -use App\Models\Category; +use App\Enums\ChannelCategory; use App\Models\Channel; use App\Models\PlatformSetting; use App\Models\StreamKey; @@ -17,13 +17,12 @@ class CreatorChannelTest extends TestCase public function test_user_with_creator_access_can_create_one_channel_and_receives_one_time_stream_key(): void { $user = User::factory()->create(['can_create_channel' => true]); - $category = Category::factory()->create(); $response = $this->actingAs($user)->post(route('creator.channel.store'), [ 'display_name' => 'Nyone Live', 'slug' => 'nyone-live', 'description' => 'Live builds and demos.', - 'category_id' => $category->id, + 'category' => ChannelCategory::Creative->value, ]); $response @@ -34,7 +33,7 @@ class CreatorChannelTest extends TestCase 'user_id' => $user->id, 'slug' => 'nyone-live', 'display_name' => 'Nyone Live', - 'category_id' => $category->id, + 'category' => ChannelCategory::Creative->value, ]); $this->assertDatabaseCount(StreamKey::class, 1); diff --git a/tests/Feature/DashboardTest.php b/tests/Feature/DashboardTest.php index 2768aaf..5d0756e 100644 --- a/tests/Feature/DashboardTest.php +++ b/tests/Feature/DashboardTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature; +use App\Enums\ChannelCategory; use App\Models\Broadcast; use App\Models\Channel; use App\Models\User; @@ -64,6 +65,25 @@ class DashboardTest extends TestCase ); } + public function test_home_page_exposes_translated_channel_category_labels(): void + { + config(['app.locale' => 'es']); + + Channel::factory()->create([ + 'is_live' => true, + 'category' => ChannelCategory::Music, + ]); + + $this->get(route('home')) + ->assertOk() + ->assertInertia(fn (Assert $page) => $page + ->component('welcome') + ->where('liveChannels.0.category.value', ChannelCategory::Music->value) + ->where('liveChannels.0.category.label', __('Music', [], 'es')) + ->where('categories', fn ($categories) => $categories->firstWhere('value', ChannelCategory::Music->value)['label'] === __('Music', [], 'es')), + ); + } + public function test_creator_dashboard_exposes_channel_display_media_props(): void { $user = User::factory()->create(); @@ -101,7 +121,7 @@ class DashboardTest extends TestCase 'display_name' => $channel->display_name, 'slug' => $channel->slug, 'description' => $channel->description, - 'category_id' => $channel->category_id, + 'category' => $channel->category?->value, 'thumbnail' => UploadedFile::fake()->image('thumbnail.jpg', 1280, 720), ]) ->assertRedirect() @@ -131,7 +151,7 @@ class DashboardTest extends TestCase 'display_name' => $channel->display_name, 'slug' => $channel->slug, 'description' => $channel->description, - 'category_id' => $channel->category_id, + 'category' => $channel->category?->value, 'thumbnail' => UploadedFile::fake()->create('thumbnail.txt', 8, 'text/plain'), ]) ->assertRedirect(route('dashboard'))