Changed channel categories from DB-backed Category records to a backed PHP enum
This commit is contained in:
45
app/Enums/ChannelCategory.php
Normal file
45
app/Enums/ChannelCategory.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum ChannelCategory: string
|
||||||
|
{
|
||||||
|
case Gaming = 'gaming';
|
||||||
|
case Music = 'music';
|
||||||
|
case TalkShows = 'talk-shows';
|
||||||
|
case Education = 'education';
|
||||||
|
case Creative = 'creative';
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Gaming => __('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<array{value: string, label: string}>
|
||||||
|
*/
|
||||||
|
public static function options(): array
|
||||||
|
{
|
||||||
|
return array_map(
|
||||||
|
fn (self $category): array => $category->toOption(),
|
||||||
|
self::cases(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ class ChannelController extends Controller
|
|||||||
): Response {
|
): Response {
|
||||||
abort_if($channel->isSuspended(), 404);
|
abort_if($channel->isSuspended(), 404);
|
||||||
|
|
||||||
$channel->load(['category', 'liveBroadcast', 'user']);
|
$channel->load(['liveBroadcast', 'user']);
|
||||||
$channel->loadCount('follows');
|
$channel->loadCount('follows');
|
||||||
|
|
||||||
$currentBroadcast = $channel->liveBroadcast;
|
$currentBroadcast = $channel->liveBroadcast;
|
||||||
@@ -39,11 +39,7 @@ class ChannelController extends Controller
|
|||||||
'is_live' => $channel->is_live,
|
'is_live' => $channel->is_live,
|
||||||
'viewer_count' => $channel->is_live ? $viewerCounts->current($channel) : 0,
|
'viewer_count' => $channel->is_live ? $viewerCounts->current($channel) : 0,
|
||||||
'followers_count' => $channel->follows_count,
|
'followers_count' => $channel->follows_count,
|
||||||
'category' => $channel->category ? [
|
'category' => $channel->category?->toOption(),
|
||||||
'id' => $channel->category->id,
|
|
||||||
'name' => $channel->category->name,
|
|
||||||
'slug' => $channel->category->slug,
|
|
||||||
] : null,
|
|
||||||
'owner' => [
|
'owner' => [
|
||||||
'id' => $channel->user->id,
|
'id' => $channel->user->id,
|
||||||
'name' => $channel->user->name,
|
'name' => $channel->user->name,
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Enums\ChannelCategory;
|
||||||
use App\Models\Broadcast;
|
use App\Models\Broadcast;
|
||||||
use App\Models\Category;
|
|
||||||
use App\Models\Channel;
|
use App\Models\Channel;
|
||||||
use App\Services\Streaming\StreamKeyManager;
|
use App\Services\Streaming\StreamKeyManager;
|
||||||
use App\Services\Streaming\ViewerCountStore;
|
use App\Services\Streaming\ViewerCountStore;
|
||||||
@@ -21,7 +21,7 @@ class CreatorDashboardController extends Controller
|
|||||||
{
|
{
|
||||||
$channel = $request->user()
|
$channel = $request->user()
|
||||||
->channel()
|
->channel()
|
||||||
->with(['category', 'liveBroadcast', 'streamKey'])
|
->with(['liveBroadcast', 'streamKey'])
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
return Inertia::render('dashboard', [
|
return Inertia::render('dashboard', [
|
||||||
@@ -34,14 +34,7 @@ class CreatorDashboardController extends Controller
|
|||||||
? $streamKeys->tokenizedPath($channel, (string) session('plain_stream_key'))
|
? $streamKeys->tokenizedPath($channel, (string) session('plain_stream_key'))
|
||||||
: null,
|
: null,
|
||||||
],
|
],
|
||||||
'categories' => Category::query()
|
'categories' => ChannelCategory::options(),
|
||||||
->orderBy('name')
|
|
||||||
->get(['id', 'name', 'slug'])
|
|
||||||
->map(fn (Category $category) => [
|
|
||||||
'id' => $category->id,
|
|
||||||
'name' => $category->name,
|
|
||||||
'slug' => $category->slug,
|
|
||||||
]),
|
|
||||||
'recentBroadcasts' => $channel
|
'recentBroadcasts' => $channel
|
||||||
? $channel->broadcasts()->with('vod')->latest()->limit(10)->get()->map(fn (Broadcast $broadcast) => [
|
? $channel->broadcasts()->with('vod')->latest()->limit(10)->get()->map(fn (Broadcast $broadcast) => [
|
||||||
'id' => $broadcast->id,
|
'id' => $broadcast->id,
|
||||||
@@ -65,7 +58,7 @@ class CreatorDashboardController extends Controller
|
|||||||
'display_name' => ['required', 'string', 'max:80'],
|
'display_name' => ['required', 'string', 'max:80'],
|
||||||
'slug' => ['required', 'string', 'min:3', 'max:40', 'alpha_dash:ascii', 'lowercase', 'unique:channels,slug'],
|
'slug' => ['required', 'string', 'min:3', 'max:40', 'alpha_dash:ascii', 'lowercase', 'unique:channels,slug'],
|
||||||
'description' => ['nullable', 'string', 'max:1000'],
|
'description' => ['nullable', 'string', 'max:1000'],
|
||||||
'category_id' => ['nullable', 'exists:categories,id'],
|
'category' => ['nullable', Rule::enum(ChannelCategory::class)],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$channel = $request->user()->channel()->create([
|
$channel = $request->user()->channel()->create([
|
||||||
@@ -92,7 +85,7 @@ class CreatorDashboardController extends Controller
|
|||||||
'display_name' => ['required', 'string', 'max:80'],
|
'display_name' => ['required', 'string', 'max:80'],
|
||||||
'slug' => ['required', 'string', 'min:3', 'max:40', 'alpha_dash:ascii', 'lowercase', Rule::unique('channels', 'slug')->ignore($channel)],
|
'slug' => ['required', 'string', 'min:3', 'max:40', 'alpha_dash:ascii', 'lowercase', Rule::unique('channels', 'slug')->ignore($channel)],
|
||||||
'description' => ['nullable', 'string', 'max:1000'],
|
'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'],
|
'thumbnail' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -187,7 +180,7 @@ class CreatorDashboardController extends Controller
|
|||||||
'is_live' => $channel->is_live,
|
'is_live' => $channel->is_live,
|
||||||
'viewer_count' => $channel->viewer_count,
|
'viewer_count' => $channel->viewer_count,
|
||||||
'stream_key_last_used_at' => $channel->streamKey?->last_used_at?->toIso8601String(),
|
'stream_key_last_used_at' => $channel->streamKey?->last_used_at?->toIso8601String(),
|
||||||
'category_id' => $channel->category_id,
|
'category' => $channel->category?->value,
|
||||||
'live_broadcast' => $channel->liveBroadcast ? [
|
'live_broadcast' => $channel->liveBroadcast ? [
|
||||||
'id' => $channel->liveBroadcast->id,
|
'id' => $channel->liveBroadcast->id,
|
||||||
'title' => $channel->liveBroadcast->title,
|
'title' => $channel->liveBroadcast->title,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Category;
|
use App\Enums\ChannelCategory;
|
||||||
use App\Models\Channel;
|
use App\Models\Channel;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
@@ -17,7 +17,7 @@ class HomeController extends Controller
|
|||||||
$liveChannels = Channel::query()
|
$liveChannels = Channel::query()
|
||||||
->where('is_live', true)
|
->where('is_live', true)
|
||||||
->whereNull('suspended_at')
|
->whereNull('suspended_at')
|
||||||
->with(['category', 'liveBroadcast', 'user'])
|
->with(['liveBroadcast', 'user'])
|
||||||
->withCount('follows')
|
->withCount('follows')
|
||||||
->orderByDesc('viewer_count')
|
->orderByDesc('viewer_count')
|
||||||
->latest()
|
->latest()
|
||||||
@@ -28,14 +28,7 @@ class HomeController extends Controller
|
|||||||
return Inertia::render('welcome', [
|
return Inertia::render('welcome', [
|
||||||
'canRegister' => Features::enabled(Features::registration()),
|
'canRegister' => Features::enabled(Features::registration()),
|
||||||
'liveChannels' => $liveChannels,
|
'liveChannels' => $liveChannels,
|
||||||
'categories' => Category::query()
|
'categories' => ChannelCategory::options(),
|
||||||
->orderBy('name')
|
|
||||||
->get(['id', 'name', 'slug'])
|
|
||||||
->map(fn (Category $category) => [
|
|
||||||
'id' => $category->id,
|
|
||||||
'name' => $category->name,
|
|
||||||
'slug' => $category->slug,
|
|
||||||
]),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,11 +45,7 @@ class HomeController extends Controller
|
|||||||
'banner_url' => $this->mediaUrl($channel->banner_path),
|
'banner_url' => $this->mediaUrl($channel->banner_path),
|
||||||
'viewer_count' => $channel->viewer_count,
|
'viewer_count' => $channel->viewer_count,
|
||||||
'followers_count' => $channel->follows_count ?? 0,
|
'followers_count' => $channel->follows_count ?? 0,
|
||||||
'category' => $channel->category ? [
|
'category' => $channel->category?->toOption(),
|
||||||
'id' => $channel->category->id,
|
|
||||||
'name' => $channel->category->name,
|
|
||||||
'slug' => $channel->category->slug,
|
|
||||||
] : null,
|
|
||||||
'broadcast' => $channel->liveBroadcast ? [
|
'broadcast' => $channel->liveBroadcast ? [
|
||||||
'id' => $channel->liveBroadcast->id,
|
'id' => $channel->liveBroadcast->id,
|
||||||
'title' => $channel->liveBroadcast->title,
|
'title' => $channel->liveBroadcast->title,
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Database\Factories\CategoryFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
|
|
||||||
#[Fillable(['name', 'slug', 'description'])]
|
|
||||||
class Category extends Model
|
|
||||||
{
|
|
||||||
/** @use HasFactory<CategoryFactory> */
|
|
||||||
use HasFactory;
|
|
||||||
|
|
||||||
public function channels(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(Channel::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\ChannelCategory;
|
||||||
use Database\Factories\ChannelFactory;
|
use Database\Factories\ChannelFactory;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
@@ -12,7 +13,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
|
|||||||
|
|
||||||
#[Fillable([
|
#[Fillable([
|
||||||
'user_id',
|
'user_id',
|
||||||
'category_id',
|
'category',
|
||||||
'live_broadcast_id',
|
'live_broadcast_id',
|
||||||
'slug',
|
'slug',
|
||||||
'display_name',
|
'display_name',
|
||||||
@@ -32,6 +33,7 @@ class Channel extends Model
|
|||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'category' => ChannelCategory::class,
|
||||||
'is_live' => 'boolean',
|
'is_live' => 'boolean',
|
||||||
'viewer_count' => 'integer',
|
'viewer_count' => 'integer',
|
||||||
'suspended_at' => 'datetime',
|
'suspended_at' => 'datetime',
|
||||||
@@ -48,11 +50,6 @@ class Channel extends Model
|
|||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function category(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Category::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function streamKey(): HasOne
|
public function streamKey(): HasOne
|
||||||
{
|
{
|
||||||
return $this->hasOne(StreamKey::class);
|
return $this->hasOne(StreamKey::class);
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Factories;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class CategoryFactory extends Factory
|
|
||||||
{
|
|
||||||
public function definition(): array
|
|
||||||
{
|
|
||||||
$name = fake()->unique()->words(2, true);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'name' => Str::headline($name),
|
|
||||||
'slug' => Str::slug($name),
|
|
||||||
'description' => fake()->optional()->sentence(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
use App\Models\Category;
|
use App\Enums\ChannelCategory;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@@ -15,7 +15,7 @@ class ChannelFactory extends Factory
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'user_id' => User::factory(),
|
'user_id' => User::factory(),
|
||||||
'category_id' => Category::factory(),
|
'category' => fake()->randomElement(ChannelCategory::cases())->value,
|
||||||
'slug' => Str::slug($name),
|
'slug' => Str::slug($name),
|
||||||
'display_name' => Str::headline($name),
|
'display_name' => Str::headline($name),
|
||||||
'description' => fake()->sentence(),
|
'description' => fake()->sentence(),
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('channels', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\Category;
|
use App\Enums\ChannelCategory;
|
||||||
use App\Models\PlatformSetting;
|
use App\Models\PlatformSetting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\Streaming\StreamKeyManager;
|
use App\Services\Streaming\StreamKeyManager;
|
||||||
@@ -15,17 +15,6 @@ class DatabaseSeeder extends Seeder
|
|||||||
*/
|
*/
|
||||||
public function run(): void
|
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();
|
PlatformSetting::current();
|
||||||
|
|
||||||
$admin = User::query()->firstOrNew(['email' => 'admin@nyone.net']);
|
$admin = User::query()->firstOrNew(['email' => 'admin@nyone.net']);
|
||||||
@@ -47,7 +36,7 @@ class DatabaseSeeder extends Seeder
|
|||||||
])->save();
|
])->save();
|
||||||
|
|
||||||
$channel = $official->channel()->updateOrCreate([], [
|
$channel = $official->channel()->updateOrCreate([], [
|
||||||
'category_id' => Category::query()->where('slug', 'creative')->value('id'),
|
'category' => ChannelCategory::Creative,
|
||||||
'slug' => 'nyone',
|
'slug' => 'nyone',
|
||||||
'display_name' => 'Nyone Official',
|
'display_name' => 'Nyone Official',
|
||||||
'description' => 'Official updates and live sessions from Nyone.',
|
'description' => 'Official updates and live sessions from Nyone.',
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
"Create an account": "إنشاء حساب",
|
"Create an account": "إنشاء حساب",
|
||||||
"Create channel": "إنشاء قناة",
|
"Create channel": "إنشاء قناة",
|
||||||
"Create your channel": "أنشئ قناتك",
|
"Create your channel": "أنشئ قناتك",
|
||||||
|
"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": "التعليم",
|
||||||
"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": "الألعاب",
|
||||||
"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": "الموسيقى",
|
||||||
"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": "برامج الحوار",
|
||||||
"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": "ساحة البث هادئة",
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
"Create an account": "Create an account",
|
"Create an account": "Create an account",
|
||||||
"Create channel": "Create channel",
|
"Create channel": "Create channel",
|
||||||
"Create your channel": "Create your channel",
|
"Create your channel": "Create your channel",
|
||||||
|
"Creative": "Creative",
|
||||||
"Creator Studio": "Creator Studio",
|
"Creator Studio": "Creator Studio",
|
||||||
"Creator access grants stay in the moderation console.": "Creator access grants stay in the moderation console.",
|
"Creator access grants stay in the moderation console.": "Creator access grants stay in the moderation console.",
|
||||||
"Creator grants": "Creator grants",
|
"Creator grants": "Creator grants",
|
||||||
@@ -101,6 +102,7 @@
|
|||||||
"Don't have an account?": "Don't have an account?",
|
"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 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",
|
"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": "Email",
|
||||||
"Email address": "Email address",
|
"Email address": "Email address",
|
||||||
"Email password reset link": "Email password reset link",
|
"Email password reset link": "Email password reset link",
|
||||||
@@ -123,6 +125,7 @@
|
|||||||
"Forgot password": "Forgot password",
|
"Forgot password": "Forgot password",
|
||||||
"Forgot password?": "Forgot password?",
|
"Forgot password?": "Forgot password?",
|
||||||
"Full name": "Full name",
|
"Full name": "Full name",
|
||||||
|
"Gaming": "Gaming",
|
||||||
"Grant": "Grant",
|
"Grant": "Grant",
|
||||||
"Hide password": "Hide password",
|
"Hide password": "Hide password",
|
||||||
"Hide recovery codes": "Hide recovery codes",
|
"Hide recovery codes": "Hide recovery codes",
|
||||||
@@ -152,6 +155,7 @@
|
|||||||
"Message sent to admin.": "Message sent to admin.",
|
"Message sent to admin.": "Message sent to admin.",
|
||||||
"Moderation console": "Moderation console",
|
"Moderation console": "Moderation console",
|
||||||
"More": "More",
|
"More": "More",
|
||||||
|
"Music": "Music",
|
||||||
"NEEDS APPROVAL": "NEEDS APPROVAL",
|
"NEEDS APPROVAL": "NEEDS APPROVAL",
|
||||||
"NEEDS CHANNEL GRANT": "NEEDS CHANNEL GRANT",
|
"NEEDS CHANNEL GRANT": "NEEDS CHANNEL GRANT",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
@@ -268,6 +272,7 @@
|
|||||||
"Suspend": "Suspend",
|
"Suspend": "Suspend",
|
||||||
"Suspend channel?": "Suspend channel?",
|
"Suspend channel?": "Suspend channel?",
|
||||||
"System": "System",
|
"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 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 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",
|
"The live floor is quiet": "The live floor is quiet",
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
"Create an account": "Crear una cuenta",
|
"Create an account": "Crear una cuenta",
|
||||||
"Create channel": "Crear canal",
|
"Create channel": "Crear canal",
|
||||||
"Create your channel": "Crea tu canal",
|
"Create your channel": "Crea tu canal",
|
||||||
|
"Creative": "Creatividad",
|
||||||
"Creator Studio": "Estudio de creador",
|
"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 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",
|
"Creator grants": "Permisos de creador",
|
||||||
@@ -101,6 +102,7 @@
|
|||||||
"Don't have an account?": "¿No tienes una cuenta?",
|
"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 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",
|
"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": "Correo electrónico",
|
||||||
"Email address": "Dirección de correo electrónico",
|
"Email address": "Dirección de correo electrónico",
|
||||||
"Email password reset link": "Enviar enlace para restablecer contraseña",
|
"Email password reset link": "Enviar enlace para restablecer contraseña",
|
||||||
@@ -123,6 +125,7 @@
|
|||||||
"Forgot password": "Olvidé mi contraseña",
|
"Forgot password": "Olvidé mi contraseña",
|
||||||
"Forgot password?": "¿Olvidaste tu contraseña?",
|
"Forgot password?": "¿Olvidaste tu contraseña?",
|
||||||
"Full name": "Nombre completo",
|
"Full name": "Nombre completo",
|
||||||
|
"Gaming": "Videojuegos",
|
||||||
"Grant": "Conceder",
|
"Grant": "Conceder",
|
||||||
"Hide password": "Ocultar contraseña",
|
"Hide password": "Ocultar contraseña",
|
||||||
"Hide recovery codes": "Ocultar códigos de recuperación",
|
"Hide recovery codes": "Ocultar códigos de recuperación",
|
||||||
@@ -152,6 +155,7 @@
|
|||||||
"Message sent to admin.": "Mensaje enviado al administrador.",
|
"Message sent to admin.": "Mensaje enviado al administrador.",
|
||||||
"Moderation console": "Consola de moderación",
|
"Moderation console": "Consola de moderación",
|
||||||
"More": "Más",
|
"More": "Más",
|
||||||
|
"Music": "Música",
|
||||||
"NEEDS APPROVAL": "REQUIERE APROBACIÓN",
|
"NEEDS APPROVAL": "REQUIERE APROBACIÓN",
|
||||||
"NEEDS CHANNEL GRANT": "REQUIERE PERMISO DE CANAL",
|
"NEEDS CHANNEL GRANT": "REQUIERE PERMISO DE CANAL",
|
||||||
"Name": "Nombre",
|
"Name": "Nombre",
|
||||||
@@ -268,6 +272,7 @@
|
|||||||
"Suspend": "Suspender",
|
"Suspend": "Suspender",
|
||||||
"Suspend channel?": "¿Suspender canal?",
|
"Suspend channel?": "¿Suspender canal?",
|
||||||
"System": "Sistema",
|
"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 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 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",
|
"The live floor is quiet": "No hay actividad en vivo",
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
"Create an account": "ایجاد یک حساب",
|
"Create an account": "ایجاد یک حساب",
|
||||||
"Create channel": "ایجاد کانال",
|
"Create channel": "ایجاد کانال",
|
||||||
"Create your channel": "کانال خود را ایجاد کنید",
|
"Create your channel": "کانال خود را ایجاد کنید",
|
||||||
|
"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": "آموزش",
|
||||||
"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": "بازی",
|
||||||
"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": "موسیقی",
|
||||||
"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": "گفتوگو",
|
||||||
"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": "فضای زنده آرام است",
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export default function ChannelShow({
|
|||||||
<LiveState channel={channel} />
|
<LiveState channel={channel} />
|
||||||
{channel.category && (
|
{channel.category && (
|
||||||
<span className="rounded-md border px-2 py-1 text-xs">
|
<span className="rounded-md border px-2 py-1 text-xs">
|
||||||
{channel.category.name}
|
{channel.category.label}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import {
|
|||||||
update as updateChannelRoute,
|
update as updateChannelRoute,
|
||||||
} from '@/routes/creator/channel';
|
} from '@/routes/creator/channel';
|
||||||
import { rotate as rotateStreamKeyRoute } from '@/routes/creator/stream-key';
|
import { rotate as rotateStreamKeyRoute } from '@/routes/creator/stream-key';
|
||||||
import type { BroadcastSummary, Category } from '@/types';
|
import type { BroadcastSummary, ChannelCategory } from '@/types';
|
||||||
|
|
||||||
type CreatorChannel = {
|
type CreatorChannel = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -51,7 +51,7 @@ type CreatorChannel = {
|
|||||||
is_live: boolean;
|
is_live: boolean;
|
||||||
viewer_count: number;
|
viewer_count: number;
|
||||||
stream_key_last_used_at: string | null;
|
stream_key_last_used_at: string | null;
|
||||||
category_id: number | null;
|
category: string | null;
|
||||||
live_broadcast: BroadcastSummary | null;
|
live_broadcast: BroadcastSummary | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ type Props = {
|
|||||||
ingestServer: string;
|
ingestServer: string;
|
||||||
tokenizedPath: string | null;
|
tokenizedPath: string | null;
|
||||||
};
|
};
|
||||||
categories: Category[];
|
categories: ChannelCategory[];
|
||||||
recentBroadcasts: BroadcastSummary[];
|
recentBroadcasts: BroadcastSummary[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,14 +90,14 @@ export default function Dashboard({
|
|||||||
display_name: '',
|
display_name: '',
|
||||||
slug: '',
|
slug: '',
|
||||||
description: '',
|
description: '',
|
||||||
category_id: '',
|
category: '',
|
||||||
});
|
});
|
||||||
const channelForm = useForm({
|
const channelForm = useForm({
|
||||||
_method: 'PATCH',
|
_method: 'PATCH',
|
||||||
display_name: channel?.display_name ?? '',
|
display_name: channel?.display_name ?? '',
|
||||||
slug: channel?.slug ?? '',
|
slug: channel?.slug ?? '',
|
||||||
description: channel?.description ?? '',
|
description: channel?.description ?? '',
|
||||||
category_id: channel?.category_id ? String(channel.category_id) : '',
|
category: channel?.category ?? '',
|
||||||
thumbnail: null as File | null,
|
thumbnail: null as File | null,
|
||||||
});
|
});
|
||||||
const broadcastForm = useForm({
|
const broadcastForm = useForm({
|
||||||
@@ -235,13 +235,16 @@ export default function Dashboard({
|
|||||||
placeholder={t('nyone-live')}
|
placeholder={t('nyone-live')}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Category">
|
<Field
|
||||||
|
label="Category"
|
||||||
|
error={createForm.errors.category}
|
||||||
|
>
|
||||||
<CategorySelect
|
<CategorySelect
|
||||||
categories={categories}
|
categories={categories}
|
||||||
value={createForm.data.category_id}
|
value={createForm.data.category}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
createForm.setData(
|
createForm.setData(
|
||||||
'category_id',
|
'category',
|
||||||
value,
|
value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -440,13 +443,16 @@ export default function Dashboard({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Category">
|
<Field
|
||||||
|
label="Category"
|
||||||
|
error={channelForm.errors.category}
|
||||||
|
>
|
||||||
<CategorySelect
|
<CategorySelect
|
||||||
categories={categories}
|
categories={categories}
|
||||||
value={channelForm.data.category_id}
|
value={channelForm.data.category}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
channelForm.setData(
|
channelForm.setData(
|
||||||
'category_id',
|
'category',
|
||||||
value,
|
value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -763,7 +769,7 @@ function CategorySelect({
|
|||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
categories: Category[];
|
categories: ChannelCategory[];
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -777,8 +783,8 @@ function CategorySelect({
|
|||||||
>
|
>
|
||||||
<option value="">{t('No category')}</option>
|
<option value="">{t('No category')}</option>
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<option key={category.id} value={category.id}>
|
<option key={category.value} value={category.value}>
|
||||||
{category.name}
|
{category.label}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ import { cn } from '@/lib/utils';
|
|||||||
import { dashboard, home, login, register } from '@/routes';
|
import { dashboard, home, login, register } from '@/routes';
|
||||||
import { show as showChannel } from '@/routes/channels';
|
import { show as showChannel } from '@/routes/channels';
|
||||||
import { index as supportIndex } from '@/routes/support';
|
import { index as supportIndex } from '@/routes/support';
|
||||||
import type { Category, ChannelCard } from '@/types';
|
import type { ChannelCard, ChannelCategory } from '@/types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
canRegister: boolean;
|
canRegister: boolean;
|
||||||
liveChannels: ChannelCard[];
|
liveChannels: ChannelCard[];
|
||||||
categories: Category[];
|
categories: ChannelCategory[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Welcome({
|
export default function Welcome({
|
||||||
@@ -43,7 +43,7 @@ export default function Welcome({
|
|||||||
|
|
||||||
return liveChannels.filter((channel) => {
|
return liveChannels.filter((channel) => {
|
||||||
const matchesCategory =
|
const matchesCategory =
|
||||||
category === 'all' || channel.category?.slug === category;
|
category === 'all' || channel.category?.value === category;
|
||||||
const matchesQuery =
|
const matchesQuery =
|
||||||
normalizedQuery === '' ||
|
normalizedQuery === '' ||
|
||||||
channel.display_name.toLowerCase().includes(normalizedQuery) ||
|
channel.display_name.toLowerCase().includes(normalizedQuery) ||
|
||||||
@@ -175,7 +175,7 @@ export default function Welcome({
|
|||||||
</span>
|
</span>
|
||||||
<span className="min-w-0 break-words">
|
<span className="min-w-0 break-words">
|
||||||
{featuredChannel.category
|
{featuredChannel.category
|
||||||
?.name ??
|
?.label ??
|
||||||
t('Uncategorized')}
|
t('Uncategorized')}
|
||||||
</span>
|
</span>
|
||||||
<span className="inline-flex items-center gap-1">
|
<span className="inline-flex items-center gap-1">
|
||||||
@@ -292,8 +292,11 @@ export default function Welcome({
|
|||||||
{t('All categories')}
|
{t('All categories')}
|
||||||
</option>
|
</option>
|
||||||
{categories.map((item) => (
|
{categories.map((item) => (
|
||||||
<option key={item.id} value={item.slug}>
|
<option
|
||||||
{item.name}
|
key={item.value}
|
||||||
|
value={item.value}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -356,7 +359,7 @@ function StreamCard({ channel }: { channel: ChannelCard }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between gap-3 text-sm text-muted-foreground">
|
<div className="flex items-center justify-between gap-3 text-sm text-muted-foreground">
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{channel.category?.name ?? t('Uncategorized')}
|
{channel.category?.label ?? t('Uncategorized')}
|
||||||
</span>
|
</span>
|
||||||
<span className="shrink-0">
|
<span className="shrink-0">
|
||||||
{t(':count viewers', {
|
{t(':count viewers', {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export type Category = {
|
export type ChannelCategory = {
|
||||||
id: number;
|
value: string;
|
||||||
name: string;
|
label: string;
|
||||||
slug: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChannelCard = {
|
export type ChannelCard = {
|
||||||
@@ -15,7 +14,7 @@ export type ChannelCard = {
|
|||||||
banner_url: string | null;
|
banner_url: string | null;
|
||||||
viewer_count: number;
|
viewer_count: number;
|
||||||
followers_count: number;
|
followers_count: number;
|
||||||
category: Category | null;
|
category: ChannelCategory | null;
|
||||||
broadcast: {
|
broadcast: {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -38,7 +37,7 @@ export type ChannelDetail = {
|
|||||||
is_live: boolean;
|
is_live: boolean;
|
||||||
viewer_count: number;
|
viewer_count: number;
|
||||||
followers_count: number;
|
followers_count: number;
|
||||||
category: Category | null;
|
category: ChannelCategory | null;
|
||||||
owner: {
|
owner: {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Tests\Feature;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Category;
|
use App\Enums\ChannelCategory;
|
||||||
use App\Models\Channel;
|
use App\Models\Channel;
|
||||||
use App\Models\PlatformSetting;
|
use App\Models\PlatformSetting;
|
||||||
use App\Models\StreamKey;
|
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
|
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]);
|
$user = User::factory()->create(['can_create_channel' => true]);
|
||||||
$category = Category::factory()->create();
|
|
||||||
|
|
||||||
$response = $this->actingAs($user)->post(route('creator.channel.store'), [
|
$response = $this->actingAs($user)->post(route('creator.channel.store'), [
|
||||||
'display_name' => 'Nyone Live',
|
'display_name' => 'Nyone Live',
|
||||||
'slug' => 'nyone-live',
|
'slug' => 'nyone-live',
|
||||||
'description' => 'Live builds and demos.',
|
'description' => 'Live builds and demos.',
|
||||||
'category_id' => $category->id,
|
'category' => ChannelCategory::Creative->value,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response
|
$response
|
||||||
@@ -34,7 +33,7 @@ class CreatorChannelTest extends TestCase
|
|||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'slug' => 'nyone-live',
|
'slug' => 'nyone-live',
|
||||||
'display_name' => 'Nyone Live',
|
'display_name' => 'Nyone Live',
|
||||||
'category_id' => $category->id,
|
'category' => ChannelCategory::Creative->value,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertDatabaseCount(StreamKey::class, 1);
|
$this->assertDatabaseCount(StreamKey::class, 1);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Tests\Feature;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Enums\ChannelCategory;
|
||||||
use App\Models\Broadcast;
|
use App\Models\Broadcast;
|
||||||
use App\Models\Channel;
|
use App\Models\Channel;
|
||||||
use App\Models\User;
|
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
|
public function test_creator_dashboard_exposes_channel_display_media_props(): void
|
||||||
{
|
{
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
@@ -101,7 +121,7 @@ class DashboardTest extends TestCase
|
|||||||
'display_name' => $channel->display_name,
|
'display_name' => $channel->display_name,
|
||||||
'slug' => $channel->slug,
|
'slug' => $channel->slug,
|
||||||
'description' => $channel->description,
|
'description' => $channel->description,
|
||||||
'category_id' => $channel->category_id,
|
'category' => $channel->category?->value,
|
||||||
'thumbnail' => UploadedFile::fake()->image('thumbnail.jpg', 1280, 720),
|
'thumbnail' => UploadedFile::fake()->image('thumbnail.jpg', 1280, 720),
|
||||||
])
|
])
|
||||||
->assertRedirect()
|
->assertRedirect()
|
||||||
@@ -131,7 +151,7 @@ class DashboardTest extends TestCase
|
|||||||
'display_name' => $channel->display_name,
|
'display_name' => $channel->display_name,
|
||||||
'slug' => $channel->slug,
|
'slug' => $channel->slug,
|
||||||
'description' => $channel->description,
|
'description' => $channel->description,
|
||||||
'category_id' => $channel->category_id,
|
'category' => $channel->category?->value,
|
||||||
'thumbnail' => UploadedFile::fake()->create('thumbnail.txt', 8, 'text/plain'),
|
'thumbnail' => UploadedFile::fake()->create('thumbnail.txt', 8, 'text/plain'),
|
||||||
])
|
])
|
||||||
->assertRedirect(route('dashboard'))
|
->assertRedirect(route('dashboard'))
|
||||||
|
|||||||
Reference in New Issue
Block a user