diff --git a/app/Actions/Auth/CreateZohoLeadAction.php b/app/Actions/Auth/CreateZohoLeadAction.php new file mode 100644 index 0000000..e89ad8b --- /dev/null +++ b/app/Actions/Auth/CreateZohoLeadAction.php @@ -0,0 +1,32 @@ + $data['first_name'], + 'Last_Name' => $data['last_name'], + 'Email' => $data['email'], + 'Phone' => $data['phone'], + 'Lead_Source' => $data['lead_source'], + ]); + } catch (\Throwable $e) { + Log::channel('zoho_sync')->error('ZohoLead creation failed', [ + 'email' => $data['email'] ?? null, + 'error' => $e->getMessage(), + ]); + + return null; + } + } +} diff --git a/app/Actions/Books/GenerateBookDownloadUrl.php b/app/Actions/Books/GenerateBookDownloadUrl.php new file mode 100644 index 0000000..6da34da --- /dev/null +++ b/app/Actions/Books/GenerateBookDownloadUrl.php @@ -0,0 +1,62 @@ +file_src) { + Log::warning('Book file_src is empty', ['book_id' => $book->id]); + return null; + } + + $path = $this->extractPath($book->file_src); + + if (!$path) { + Log::warning('Could not extract path from file_src', [ + 'book_id' => $book->id, + 'file_src' => $book->file_src + ]); + return null; + } + + return Storage::disk('s3_public')->temporaryUrl( + $path, + now()->addMinutes($minutesValid) + ); + + } catch (\Exception $e) { + Log::error('Failed to generate book download URL', [ + 'book_id' => $book->id, + 'error' => $e->getMessage() + ]); + return null; + } + } + + /** + * Extract path from file_src (supports both JSON and plain string) + */ + private function extractPath(string $fileSrc): ?string + { + // Try to decode as JSON first + $decoded = json_decode($fileSrc); + + if (json_last_error() === JSON_ERROR_NONE && is_object($decoded) && isset($decoded->path)) { + return $decoded->path; + } + + // If not JSON, treat as plain path + return trim($fileSrc) ?: null; + } +} diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index c5a5fac..b7aa4aa 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -3,20 +3,22 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; +use App\Http\Requests\Auth\RegisterUserRequest; use App\Models\User; +use App\Services\UserRegistrationService; use Illuminate\Auth\Events\Registered; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Hash; -use Illuminate\Validation\Rules; use Inertia\Inertia; use Inertia\Response; -use Asciisd\ZohoV8\Models\ZohoLead; -use Illuminate\Support\Facades\Log; class RegisteredUserController extends Controller { + public function __construct( + private UserRegistrationService $registrationService + ) {} + /** * Show the registration page. */ @@ -30,71 +32,25 @@ public function create(): Response * * @throws \Illuminate\Validation\ValidationException */ - public function store(Request $request): RedirectResponse + public function store(RegisterUserRequest $request): RedirectResponse { - $request->validate([ - 'first_name' => 'required|string|max:255', - 'last_name' => 'required|string|max:255', - 'email' => 'required|string|lowercase|email|max:255|unique:'.User::class, - 'phone' => 'required||phone|max:255|unique:'.User::class, - 'password' => ['required', 'confirmed', Rules\Password::defaults()], - // 'country' => 'required|string|max:2', - // 'terms' => 'required|accepted', - ]); + $validated = $request->validated(); - $utm_data = null; - if (request()->hasCookie('utm')) { - $utm_data = json_decode(request()->cookie('utm')); - } + // Extract UTM data + $utmData = $this->registrationService->extractUtmData(); - $user = User::create([ - 'first_name' => $request->first_name, - 'last_name' => $request->last_name, - 'email' => $request->email, - 'phone' => $request->phone, - 'country' => $request->country, - 'password' => Hash::make($request->password), - 'utm_source' => $utm_data->utm_source ?? null, - 'utm_content' => $utm_data->utm_content ?? null, - 'utm_medium' => $utm_data->utm_medium ?? null, - 'utm_campaign' => $utm_data->utm_campaign ?? null, - 'utm_term' => $utm_data->utm_term ?? null, - ]); + // Register user + $user = $this->registrationService->register($validated, $utmData); - // Create Zoho Contact - try { - $this->createZohoLead([ - 'first_name' => $user->first_name, - 'last_name' => $user->last_name, - 'email' => $user->email, - 'phone' => $user->phone, - 'lead_source' => 'trader_factory', - ]); - } catch (\Throwable $e) { - Log::channel('zoho_sync')->error('ZohoContact creation failed', [ - 'user_id' => $user->id, - 'email' => $user->email, - 'error' => $e->getMessage(), - ]); - } + // Create Zoho Lead (non-blocking) + $this->registrationService->createZohoLead($user); + // Fire registered event event(new Registered($user)); + // Login user Auth::login($user); return to_route('dashboard'); } - - public function createZohoLead(array $data) - { - return ZohoLead::create([ - 'First_Name' => $data['first_name'], - 'Last_Name' => $data['last_name'], - 'Email' => $data['email'], - 'Phone' => $data['phone'], - 'Lead_Source' => $data['lead_source'], - ]); - } - - -} +} \ No newline at end of file diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index bc0affa..922809c 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -2,85 +2,72 @@ namespace App\Http\Controllers; +use App\Actions\Books\GenerateBookDownloadUrl; use App\Http\Resources\BookResource; use App\Models\Book; -use App\Models\Order; -use App\Notifications\BookInvoice; -use App\Notifications\SendBookLink; -use Illuminate\Support\Facades\Storage; +use App\Services\BookService; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; +use Illuminate\Http\RedirectResponse; use Inertia\Inertia; +use Inertia\Response; class BookController extends Controller { - public function index() + use AuthorizesRequests; + + public function __construct( + private BookService $bookService, + private GenerateBookDownloadUrl $generateDownloadUrl + ) {} + + public function index(): Response { return Inertia::render('books/Index', [ - 'books' => BookResource::collection(Book::all()) - ])->with('success', 'تم إرسال رابط التحميل على البريد الإلكتروني'); + 'books' => BookResource::collection(Book::all()), + ]); } + /** * Send download link to user's email */ - public function sendDownloadLink(Book $book) + public function sendDownloadLink(Book $book): RedirectResponse { - $user = auth()->user(); - $user->notify(new SendBookLink($book)); + $this->authorize('download', $book); + $this->bookService->sendDownloadLink($book, auth()->user()); - return redirect()->back()->with('success', 'تم إرسال رابط التحميل على البريد الإلكتروني'); + // return redirect()->back()->with('success', 'تم إرسال رابط التحميل على البريد الإلكتروني'); + return redirect()->route('books.download.page', $book->id) + ->with('success', __('locale.download_link_sent')); } /** * Display download page for a book */ - public function downloadPage(Book $book) + public function downloadPage(Book $book): Response|RedirectResponse { - // Check if the user owns the book - if (! $book->purchased()) { - return redirect()->route('books.index')->with('error', 'لا يمكنك تحميل هذا الكتاب لأنك لم تشتريه بعد'); - } - - // Get the latest order for this book - $order = Order::where([ - 'user_id' => auth()->id(), - 'orderable_type' => Book::class, - 'orderable_id' => $book->id, - 'status' => 'SUCCESS', - ])->latest()->first(); + $this->authorize('download', $book); - // Send invoice email if it's a new purchase - if ($order && $order->created_at->diffInMinutes(now()) < 5) { - auth()->user()->notify(new BookInvoice($book, $order)); - } - - // Generate temporary download URL - $url = null; - if ($book->file_src) { - $path = json_decode($book->file_src)->path; - $url = Storage::disk('s3_public')->temporaryUrl($path, now()->addMinutes(30)); - } + $data = $this->bookService->prepareDownloadData($book, auth()->user()); - return Inertia::render('Books/Download', [ - 'book' => BookResource::make($book), - 'downloadUrl' => $url, + return Inertia::render('books/Download', [ + 'book' => BookResource::make($data['book']), + 'downloadUrl' => $data['downloadUrl'], ]); } /** * Direct download of the book file */ - public function directDownload(Book $book) + public function directDownload(Book $book): RedirectResponse { - // Check if the user owns the book - if (! $book->purchased()) { - return redirect()->route('books.index')->with('error', 'لا يمكنك تحميل هذا الكتاب لأنك لم تشتريه بعد'); - } + $this->authorize('download', $book); + + $url = $this->generateDownloadUrl->execute($book, 5); - if (! $book->file_src) { + if (! $url) { return redirect()->back()->with('error', 'ملف الكتاب غير متوفر'); } - $path = json_decode($book->file_src)->path; - - return redirect(Storage::disk('s3_public')->temporaryUrl($path, now()->addMinutes(5))); + return redirect($url); } } diff --git a/app/Http/Controllers/CourseController.php b/app/Http/Controllers/CourseController.php index e885520..0e8959f 100644 --- a/app/Http/Controllers/CourseController.php +++ b/app/Http/Controllers/CourseController.php @@ -18,7 +18,14 @@ class CourseController extends Controller public function index(Course $course): InertiaResponse { return Inertia::render('courses/Index', [ - 'courses' => CourseResource::collection(Course::with('sections.lessons')->get()), + 'courses' => CourseResource::collection( + Course::query() + ->isPublished() + ->isActive() + ->hasSections() + ->with('sections.lessons') + ->get() + ), 'metadata' => MetaData::where('page_slug', 'courses')->first(), ]); } @@ -49,4 +56,4 @@ public function sections(Section $section): InertiaResponse 'metadata' => MetaData::where('page_slug', $section->slug)->first(), ]); } -} +} \ No newline at end of file diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index ec7074a..1fea2ae 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Enums\OrderStatus; use App\Http\Resources\OrderResource; use App\Models\Order; use Inertia\Inertia; @@ -10,10 +11,15 @@ class DashboardController extends Controller { public function __invoke() { + $user = auth()->user()->loadCount(['courseOrders', 'bookOrders', 'orders']); + return Inertia::render('Dashboard', [ 'orders' => OrderResource::collection( - Order::latest()->with('orderable')->get() + Order::where('status', OrderStatus::Success)->latest()->with('orderable')->get() ), + 'courses_count' => $user->course_orders_count, + 'books_count' => $user->book_orders_count, + 'orders_count' => $user->orders_count, ]); } } diff --git a/app/Http/Controllers/MagazineController.php b/app/Http/Controllers/MagazineController.php index 7219436..ec08ef0 100644 --- a/app/Http/Controllers/MagazineController.php +++ b/app/Http/Controllers/MagazineController.php @@ -2,9 +2,110 @@ namespace App\Http\Controllers; +use App\Events\EventableComplete; +use App\Events\EventableStart; +use App\Events\EventableUpdate; +use App\Http\Requests\ProgressRequest; +use App\Http\Requests\RatingRequest; +use App\Http\Resources\LessonResource; +use App\Http\Resources\MagazineResource; +use App\Models\Lesson; +use App\Models\Magazine; +use App\Models\Section; +use App\Models\User; +use App\Policies\Policy; use Illuminate\Http\Request; +use Inertia\Inertia; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; class MagazineController extends Controller { - // -} + use AuthorizesRequests; + + public function show(Magazine $magazine) + { + // $this->authorize(Policy::View, $magazine); + // return MagazineResource::make($magazine); + return Inertia::render('magazine/Show', [ + 'magazine' => MagazineResource::make($magazine), + 'lesson' => LessonResource::make($magazine->lesson), + ]); + } + + /** + * Update the specified resource in storage. + */ + public function updateProgress(ProgressRequest $request, Magazine $magazine) + { + if ($request->user()->isProgressStarted($magazine)) { + event(new EventableStart($magazine, $request->user())); + + if ($request->user()->isProgressStarted($magazine->lesson)) { + event(new EventableStart($magazine->lesson, $request->user())); + } + + if ($request->user()->isProgressStarted($magazine->section)) { + event(new EventableStart($magazine->section, $request->user())); + } + } + + $request->user()->addProgress($request->user_progress, $magazine); + event(new EventableUpdate($magazine, $request->user(), $request->user_progress)); + + if ($request->user()->isProgressCompleted($request->user_progress)) { + event(new EventableComplete($magazine, $request->user())); + + $this->updateLessonProgress($request->user(), $magazine->lesson); + $this->updateSectionProgress($request->user(), $magazine->section); + } + + return redirect()->back()->with('progress', [ + 'model' => MagazineResource::make($magazine), + 'href' => route('magazines.rates', $magazine), + ]); + } + + public function updateFavorites(Request $request, Magazine $magazine) + { + $request->user()->toggleFavorite($magazine); + + return redirect()->back(); + } + + public function updateRate(RatingRequest $request, Magazine $magazine) + { + $request->user()->rate($magazine, $request->rate); + + return redirect()->back(); + } + + /** + * Update lesson progress and trigger related events. + */ + protected function updateLessonProgress(User $user, Lesson $lesson): void + { + $lesson_progress = $lesson->calcLessonProgress($user->id); + $user->addProgress($lesson_progress, $lesson); + + event(new EventableUpdate($lesson, $user, $lesson_progress)); + + if ($user->isProgressCompleted($lesson_progress)) { + event(new EventableComplete($lesson, $user)); + } + } + + /** + * Update section progress and trigger related events. + */ + protected function updateSectionProgress(User $user, Section $section): void + { + $section_progress = $section->calcSectionProgress($user->id); + $user->addProgress($section_progress, $section); + + event(new EventableUpdate($section, $user, $section_progress)); + + if ($user->isProgressCompleted($section_progress)) { + event(new EventableComplete($section, $user)); + } + } +} \ No newline at end of file diff --git a/app/Http/Controllers/MasterController.php b/app/Http/Controllers/MasterController.php new file mode 100644 index 0000000..443163d --- /dev/null +++ b/app/Http/Controllers/MasterController.php @@ -0,0 +1,13 @@ + QuickScanResource::make($quickScan->load('quickQuestions')), + 'lesson' => LessonResource::make($quickScan->lesson), + ]); + } + + /** + * Update the specified resource in storage. + */ + public function updateProgress(ProgressRequest $request, QuickScan $quickScan) + { + if ($request->user()->isProgressStarted($quickScan)) { + event(new EventableStart($quickScan, $request->user())); + + if ($request->user()->isProgressStarted($quickScan->lesson)) { + event(new EventableStart($quickScan->lesson, $request->user())); + } + + if ($request->user()->isProgressStarted($quickScan->section)) { + event(new EventableStart($quickScan->section, $request->user())); + } + } + + $request->user()->addProgress($request->user_progress, $quickScan); + event(new EventableUpdate($quickScan, $request->user(), $request->user_progress)); + + if ($request->user()->isProgressCompleted($request->user_progress)) { + event(new EventableComplete($quickScan, $request->user())); + + $this->updateLessonProgress($request->user(), $quickScan->lesson); + $this->updateSectionProgress($request->user(), $quickScan->section); + } + + return redirect()->back()->with('progress', [ + 'model' => QuickScanResource::make($quickScan), + 'href' => route('quickScans.rates', $quickScan), + ]); + } + + public function updateFavorites(Request $request, QuickScan $quickScan) + { + $request->user()->toggleFavorite($quickScan); + + return redirect()->back(); + } + + public function updateRate(RatingRequest $request, QuickScan $quickScan) + { + $request->user()->rate($quickScan, $request->rate); + + return redirect()->back(); + } + + /** + * Update lesson progress and trigger related events. + */ + protected function updateLessonProgress(User $user, Lesson $lesson): void + { + $lesson_progress = $lesson->calcLessonProgress($user->id); + $user->addProgress($lesson_progress, $lesson); + + event(new EventableUpdate($lesson, $user, $lesson_progress)); + + if ($user->isProgressCompleted($lesson_progress)) { + event(new EventableComplete($lesson, $user)); + } + } + + /** + * Update section progress and trigger related events. + */ + protected function updateSectionProgress(User $user, Section $section): void + { + $section_progress = $section->calcSectionProgress($user->id); + $user->addProgress($section_progress, $section); + + event(new EventableUpdate($section, $user, $section_progress)); + + if ($user->isProgressCompleted($section_progress)) { + event(new EventableComplete($section, $user)); + } + } } diff --git a/app/Http/Controllers/QuizController.php b/app/Http/Controllers/QuizController.php index 4cc0568..7db1b57 100644 --- a/app/Http/Controllers/QuizController.php +++ b/app/Http/Controllers/QuizController.php @@ -2,9 +2,110 @@ namespace App\Http\Controllers; +use App\Events\EventableComplete; +use App\Events\EventableStart; +use App\Events\EventableUpdate; +use App\Http\Requests\ProgressRequest; +use App\Http\Requests\RatingRequest; +use App\Http\Resources\LessonResource; +use App\Http\Resources\QuizResource; +use App\Models\Lesson; +use App\Models\Quiz; +use App\Models\Section; +use App\Models\User; +use App\Policies\Policy; use Illuminate\Http\Request; +use Inertia\Inertia; class QuizController extends Controller { - // + /** + * Display the specified resource. + */ + public function show(Quiz $quiz) + { + // $this->authorize(Policy::View, $quiz); + // return QuizResource::make($quiz); + return Inertia::render('quiz/Show', [ + 'quiz' => QuizResource::make($quiz), + 'lesson' => LessonResource::make($quiz->lesson), + ]); + } + + /** + * Update the specified resource in storage. + */ + public function updateProgress(ProgressRequest $request, Quiz $quiz) + { + if ($request->user()->isProgressStarted($quiz)) { + event(new EventableStart($quiz, $request->user())); + + if ($request->user()->isProgressStarted($quiz->lesson)) { + event(new EventableStart($quiz->lesson, $request->user())); + } + + if ($request->user()->isProgressStarted($quiz->section)) { + event(new EventableStart($quiz->section, $request->user())); + } + } + + $request->user()->addProgress($request->user_progress, $quiz); + event(new EventableUpdate($quiz, $request->user(), $request->user_progress)); + + if ($request->user()->isProgressCompleted($request->user_progress)) { + event(new EventableComplete($quiz, $request->user())); + + $this->updateLessonProgress($request->user(), $quiz->lesson); + $this->updateSectionProgress($request->user(), $quiz->section); + } + + return redirect()->back()->with('progress', [ + 'model' => QuizResource::make($quiz), + 'href' => route('quizzes.rates', $quiz), + ]); + } + + public function updateFavorites(Request $request, Quiz $quiz) + { + $request->user()->toggleFavorite($quiz); + + return redirect()->back(); + } + + public function updateRate(RatingRequest $request, Quiz $quiz) + { + $request->user()->rate($quiz, $request->rate); + + return redirect()->back(); + } + + /** + * Update lesson progress and trigger related events. + */ + protected function updateLessonProgress(User $user, Lesson $lesson): void + { + $lesson_progress = $lesson->calcLessonProgress($user->id); + $user->addProgress($lesson_progress, $lesson); + + event(new EventableUpdate($lesson, $user, $lesson_progress)); + + if ($user->isProgressCompleted($lesson_progress)) { + event(new EventableComplete($lesson, $user)); + } + } + + /** + * Update section progress and trigger related events. + */ + protected function updateSectionProgress(User $user, Section $section): void + { + $section_progress = $section->calcSectionProgress($user->id); + $user->addProgress($section_progress, $section); + + event(new EventableUpdate($section, $user, $section_progress)); + + if ($user->isProgressCompleted($section_progress)) { + event(new EventableComplete($section, $user)); + } + } } diff --git a/app/Http/Controllers/RevisionController.php b/app/Http/Controllers/RevisionController.php index 9ab5b0f..0546901 100644 --- a/app/Http/Controllers/RevisionController.php +++ b/app/Http/Controllers/RevisionController.php @@ -2,9 +2,108 @@ namespace App\Http\Controllers; +use App\Events\EventableComplete; +use App\Events\EventableStart; +use App\Events\EventableUpdate; +use App\Http\Requests\ProgressRequest; +use App\Http\Requests\RatingRequest; +use App\Http\Resources\LessonResource; +use App\Http\Resources\RevisionResource; +use App\Models\Lesson; +use App\Models\Revision; +use App\Models\Section; +use App\Models\User; use Illuminate\Http\Request; +use Inertia\Inertia; class RevisionController extends Controller { - // -} + /** + * Display the specified resource. + */ + public function show(Revision $revision) + { + // return RevisionResource::make($revision->load('revisionItems')); + return Inertia::render('revision/Show', [ + 'revision' => RevisionResource::make($revision->load('revisionItems')), + 'lesson' => LessonResource::make($revision->lesson), + ]); + } + + /** + * Update the specified resource in storage. + */ + public function updateProgress(ProgressRequest $request, Revision $revision) + { + if ($request->user()->isProgressStarted($revision)) { + event(new EventableStart($revision, $request->user())); + + if ($request->user()->isProgressStarted($revision->lesson)) { + event(new EventableStart($revision->lesson, $request->user())); + } + + if ($request->user()->isProgressStarted($revision->section)) { + event(new EventableStart($revision->section, $request->user())); + } + } + + $request->user()->addProgress($request->user_progress, $revision); + event(new EventableUpdate($revision, $request->user(), $request->user_progress)); + + if ($request->user()->isProgressCompleted($request->user_progress)) { + event(new EventableComplete($revision, $request->user())); + + $this->updateLessonProgress($request->user(), $revision->lesson); + $this->updateSectionProgress($request->user(), $revision->section); + } + + return redirect()->back()->with('progress', [ + 'model' => RevisionResource::make($revision), + 'href' => route('revisions.rates', $revision), + ]); + } + + public function updateFavorites(Request $request, Revision $revision) + { + $request->user()->toggleFavorite($revision); + + return redirect()->back(); + } + + public function updateRate(RatingRequest $request, Revision $revision) + { + $request->user()->rate($revision, $request->rate); + + return redirect()->back(); + } + + /** + * Update lesson progress and trigger related events. + */ + protected function updateLessonProgress(User $user, Lesson $lesson): void + { + $lesson_progress = $lesson->calcLessonProgress($user->id); + $user->addProgress($lesson_progress, $lesson); + + event(new EventableUpdate($lesson, $user, $lesson_progress)); + + if ($user->isProgressCompleted($lesson_progress)) { + event(new EventableComplete($lesson, $user)); + } + } + + /** + * Update section progress and trigger related events. + */ + protected function updateSectionProgress(User $user, Section $section): void + { + $section_progress = $section->calcSectionProgress($user->id); + $user->addProgress($section_progress, $section); + + event(new EventableUpdate($section, $user, $section_progress)); + + if ($user->isProgressCompleted($section_progress)) { + event(new EventableComplete($section, $user)); + } + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Settings/BookController.php b/app/Http/Controllers/Settings/BookController.php new file mode 100644 index 0000000..43d4348 --- /dev/null +++ b/app/Http/Controllers/Settings/BookController.php @@ -0,0 +1,31 @@ +user()->id) + ->where('orderable_type', Book::class) + ->pluck('orderable_id') + ->toArray(); + + return Inertia::render('settings/Books', [ + 'books' => BookResource::collection(Book::whereIn('id', $books_orders)->get()), + 'metadata' => MetaData::where('page_slug', 'books')->first(), + ]); + } +} diff --git a/app/Http/Controllers/Settings/CourseController.php b/app/Http/Controllers/Settings/CourseController.php new file mode 100644 index 0000000..dc2d368 --- /dev/null +++ b/app/Http/Controllers/Settings/CourseController.php @@ -0,0 +1,28 @@ +id())->where('orderable_type', Course::class)->pluck('orderable_id')->toArray(); + // dd($courses_orders); + return Inertia::render('settings/Courses', [ + 'courses' => CourseResource::collection(Course::whereIn('id', $courses_orders)->with('sections.lessons')->get()), + 'metadata' => MetaData::where('page_slug', 'courses')->first(), + ]); + } +} diff --git a/app/Http/Controllers/TodoController.php b/app/Http/Controllers/TodoController.php index 9fd5492..b713886 100644 --- a/app/Http/Controllers/TodoController.php +++ b/app/Http/Controllers/TodoController.php @@ -2,9 +2,108 @@ namespace App\Http\Controllers; +use App\Events\EventableComplete; +use App\Events\EventableStart; +use App\Events\EventableUpdate; +use App\Http\Requests\ProgressRequest; +use App\Http\Requests\RatingRequest; +use App\Http\Resources\LessonResource; +use App\Http\Resources\TodoResource; +use App\Models\Lesson; +use App\Models\Section; +use App\Models\Todo; +use App\Models\User; use Illuminate\Http\Request; +use Inertia\Inertia; class TodoController extends Controller { - // + /** + * Display the specified resource. + */ + public function show(Todo $todo) + { + // return TodoResource::make($todo); + return Inertia::render('todo/Show', [ + 'todo' => TodoResource::make($todo), + 'lesson' => LessonResource::make($todo->lesson), + ]); + } + + /** + * Update the specified resource in storage. + */ + public function updateProgress(ProgressRequest $request, Todo $todo) + { + if ($request->user()->isProgressStarted($todo)) { + event(new EventableStart($todo, $request->user())); + + if ($request->user()->isProgressStarted($todo->lesson)) { + event(new EventableStart($todo->lesson, $request->user())); + } + + if ($request->user()->isProgressStarted($todo->section)) { + event(new EventableStart($todo->section, $request->user())); + } + } + + $request->user()->addProgress($request->user_progress, $todo); + event(new EventableUpdate($todo, $request->user(), $request->user_progress)); + + if ($request->user()->isProgressCompleted($request->user_progress)) { + event(new EventableComplete($todo, $request->user())); + + $this->updateLessonProgress($request->user(), $todo->lesson); + $this->updateSectionProgress($request->user(), $todo->section); + } + + return redirect()->back()->with('progress', [ + 'model' => TodoResource::make($todo), + 'href' => route('todos.rates', $todo), + ]); + } + + public function updateFavorites(Request $request, Todo $todo) + { + $request->user()->toggleFavorite($todo); + + return redirect()->back(); + } + + public function updateRate(RatingRequest $request, Todo $todo) + { + $request->user()->rate($todo, $request->rate); + + return redirect()->back(); + } + + /** + * Update lesson progress and trigger related events. + */ + protected function updateLessonProgress(User $user, Lesson $lesson): void + { + $lesson_progress = $lesson->calcLessonProgress($user->id); + $user->addProgress($lesson_progress, $lesson); + + event(new EventableUpdate($lesson, $user, $lesson_progress)); + + if ($user->isProgressCompleted($lesson_progress)) { + event(new EventableComplete($lesson, $user)); + } + } + + /** + * Update section progress and trigger related events. + */ + protected function updateSectionProgress(User $user, Section $section): void + { + $section_progress = $section->calcSectionProgress($user->id); + $user->addProgress($section_progress, $section); + + event(new EventableUpdate($section, $user, $section_progress)); + + if ($user->isProgressCompleted($section_progress)) { + event(new EventableComplete($section, $user)); + } + } } diff --git a/app/Http/Controllers/ToolsController.php b/app/Http/Controllers/ToolsController.php new file mode 100644 index 0000000..0090612 --- /dev/null +++ b/app/Http/Controllers/ToolsController.php @@ -0,0 +1,29 @@ +authorize(Policy::View, $visual); + + return Inertia::render('visual/Show', [ + 'visual' => VisualResource::make($visual), + 'lesson' => LessonResource::make($visual->lesson), + ]); + } + + /** + * Update the specified resource in storage. + */ + public function updateProgress(ProgressRequest $request, Visual $visual) + { + if ($request->user()->isProgressStarted($visual)) { + event(new EventableStart($visual, $request->user())); + + if ($request->user()->isProgressStarted($visual->lesson)) { + event(new EventableStart($visual->lesson, $request->user())); + } + + if ($request->user()->isProgressStarted($visual->section)) { + event(new EventableStart($visual->section, $request->user())); + } + } + + $request->user()->addProgress($request->user_progress, $visual); + event(new EventableUpdate($visual, $request->user(), $request->user_progress)); + + if ($request->user()->isProgressCompleted($request->user_progress)) { + event(new EventableComplete($visual, $request->user())); + + $this->updateLessonProgress($request->user(), $visual->lesson); + $this->updateSectionProgress($request->user(), $visual->section); + } + + return redirect()->back()->with('progress', [ + 'model' => VisualResource::make($visual), + 'href' => route('visuals.rates', $visual), + ]); + } + + public function updateFavorites(Request $request, Visual $visual) + { + $request->user()->toggleFavorite($visual); + + return redirect()->back(); + } + + public function updateRate(RatingRequest $request, Visual $visual) + { + $request->user()->rate($visual, $request->rate); + + return redirect()->back(); + } + + /** + * Update lesson progress and trigger related events. + */ + protected function updateLessonProgress(User $user, Lesson $lesson): void + { + $lesson_progress = $lesson->calcLessonProgress($user->id); + $user->addProgress($lesson_progress, $lesson); + + event(new EventableUpdate($lesson, $user, $lesson_progress)); + + if ($user->isProgressCompleted($lesson_progress)) { + event(new EventableComplete($lesson, $user)); + } + } + + /** + * Update section progress and trigger related events. + */ + protected function updateSectionProgress(User $user, Section $section): void + { + $section_progress = $section->calcSectionProgress($user->id); + $user->addProgress($section_progress, $section); + + event(new EventableUpdate($section, $user, $section_progress)); + + if ($user->isProgressCompleted($section_progress)) { + event(new EventableComplete($section, $user)); + } + } } diff --git a/app/Http/Controllers/WelcomeController.php b/app/Http/Controllers/WelcomeController.php index 554e815..006d054 100644 --- a/app/Http/Controllers/WelcomeController.php +++ b/app/Http/Controllers/WelcomeController.php @@ -4,6 +4,7 @@ use App\Http\Resources\BookResource; use App\Models\Book; +use App\Models\Faq; use App\Models\MetaData; use Inertia\Inertia; @@ -20,4 +21,31 @@ public function about() { return Inertia::render('about/Index'); } + + public function faq() + { + return Inertia::render('faqs/Index', [ + 'faqs' => Faq::query() + ->where(function ($q) { + $q->where('is_published', true) + ->orWhereNull('is_published'); + }) + ->orderBy('order_column') + ->get(['id','question','answer']) + ]); + } + + public function webinars() + { + return Inertia::render('webinars/Index', [ + 'webinars' => [], + ]); + } + + public function books() + { + return Inertia::render('books/Index', [ + 'books' => BookResource::collection(Book::all()), + ]); + } } diff --git a/app/Http/Middleware/HandleAppearance.php b/app/Http/Middleware/HandleAppearance.php index f1a02bb..c4ed399 100644 --- a/app/Http/Middleware/HandleAppearance.php +++ b/app/Http/Middleware/HandleAppearance.php @@ -20,4 +20,4 @@ public function handle(Request $request, Closure $next): Response return $next($request); } -} +} \ No newline at end of file diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 335bfe1..dafecdc 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -4,6 +4,8 @@ use App\Http\Resources\CourseResource; use App\Models\Course; +use App\Models\Faq; +use App\Models\Book; use Illuminate\Foundation\Application; use Illuminate\Foundation\Inspiring; use Illuminate\Http\Request; @@ -67,11 +69,15 @@ public function share(Request $request): array 'language' => fn () => $this->translations( lang_path(app()->getLocale().'.json') ), - 'courses' => CourseResource::collection(Course::with('sections.lessons')->get()), + 'courses' => CourseResource::collection(Course::with('sections.lessons')->where('active', true)->whereHas('sections')->get()), 'flash' => [ 'success' => session('success'), 'failed' => session('failed'), ], + 'appEnv' => app()->environment(), + 'hasCourses' => Course::query()->isPublished()->isActive()->whereHas('sections')->exists(), + 'hasFaqs' => Faq::query()->active()->exists(), + 'hasBooks' => Book::exists(), ]; } @@ -90,9 +96,9 @@ public function getSettings(): array return [ 'site_title' => nova_get_setting('site_title', config('app.name')), - 'site_subtitle' => nova_get_setting('site_subtitle', ''), - 'site_description' => nova_get_setting('site_description', ''), - 'start_learning' => nova_get_setting('site_cta', '#'), + 'site_subtitle' => nova_get_setting('site_subtitle', config('site.site_subtitle')), + 'site_description' => nova_get_setting('site_description', config('site.site_description')), + 'start_learning' => nova_get_setting('site_cta', config('site.site_cta')), 'header_s3' => $img ? Storage::disk('s3')->url($img) : '', ]; } diff --git a/app/Http/Requests/Auth/RegisterUserRequest.php b/app/Http/Requests/Auth/RegisterUserRequest.php new file mode 100644 index 0000000..72f7e34 --- /dev/null +++ b/app/Http/Requests/Auth/RegisterUserRequest.php @@ -0,0 +1,35 @@ +|string> + */ + public function rules(): array + { + return [ + 'first_name' => 'required|string|max:255', + 'last_name' => 'required|string|max:255', + 'email' => 'required|string|lowercase|email|max:255|unique:'.User::class, + 'phone' => ['required', 'phone', 'max:255', 'unique:'.User::class], + 'password' => ['required', 'confirmed', Rules\Password::defaults()], + 'country' => 'nullable|string|max:2', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/UpdateProfileRequest.php b/app/Http/Requests/UpdateProfileRequest.php new file mode 100644 index 0000000..b228cbc --- /dev/null +++ b/app/Http/Requests/UpdateProfileRequest.php @@ -0,0 +1,24 @@ + 'required|string|max:255', + 'last_name' => 'required|string|max:255', + 'phone' => 'required|string|max:20|unique:users,phone,'.$this->user()->id, + 'email' => 'required|email|max:255|unique:users,email,'.$this->user()->id, + 'profile_photo' => 'nullable|image|mimes:jpeg,jpg,png,gif|max:2048', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/CourseResource.php b/app/Http/Resources/CourseResource.php index 2d5519c..a559397 100644 --- a/app/Http/Resources/CourseResource.php +++ b/app/Http/Resources/CourseResource.php @@ -20,6 +20,7 @@ class CourseResource extends JsonResource public function toArray(Request $request): array { return [ + 'id' => $this->resource->id, 'title' => $this->resource->title, 'title_line2' => $this->resource->title_line2, 'description' => $this->resource->description, diff --git a/app/Http/Resources/LessonResource.php b/app/Http/Resources/LessonResource.php index 6aeea92..9635933 100644 --- a/app/Http/Resources/LessonResource.php +++ b/app/Http/Resources/LessonResource.php @@ -115,7 +115,7 @@ private function visual(): Collection { return collect([ 'model' => 'Visual', - 'item' => $this->resource->visual, + 'item' => VisualResource::make($this->resource->visual), 'title' => 'فيديو', 'href' => $this->resource->visual ? route('visuals.show', $this->resource->visual, false) : '#', diff --git a/app/Http/Resources/OrderResource.php b/app/Http/Resources/OrderResource.php index 7687b96..ecb995a 100644 --- a/app/Http/Resources/OrderResource.php +++ b/app/Http/Resources/OrderResource.php @@ -30,7 +30,7 @@ public function toArray(Request $request): array 'type' => $this->type(), 'status' => $this->resource->status->toFullArray(), 'created_at' => $this->resource->created_at, - 'href' => $this->href(), + 'href' => $this->resource->orderable?->href, 'is_book' => $this->resource->orderable_type === Book::class, 'is_downloadable' => $this->isDownloadable(), 'download_url' => $this->downloadUrl(), @@ -100,4 +100,4 @@ public function downloadUrl(): ?string return null; } -} +} \ No newline at end of file diff --git a/app/Http/Resources/QuizQuestionResource.php b/app/Http/Resources/QuizQuestionResource.php new file mode 100644 index 0000000..1969dde --- /dev/null +++ b/app/Http/Resources/QuizQuestionResource.php @@ -0,0 +1,35 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->resource->id, + 'quiz_id' => $this->resource->quiz_id, + 'question' => $this->resource->question, + 'correct_answer' => $this->resource->correct_answer, + 'background_url' => $this->resource->background_url + ? Storage::disk('s3')->url($this->resource->background_url) + : null, + 'slug' => $this->resource->slug, + 'created_at' => $this->resource->created_at, + 'updated_at' => $this->resource->updated_at, + ]; + } +} diff --git a/app/Http/Resources/QuizResource.php b/app/Http/Resources/QuizResource.php index 2641f5f..217855e 100644 --- a/app/Http/Resources/QuizResource.php +++ b/app/Http/Resources/QuizResource.php @@ -27,7 +27,7 @@ public function toArray(Request $request): array 'duration' => $this->resource->duration, 'quantity' => $this->resource->quantity, 'user_progress' => $this->resource->user_progress, - 'quiz_questions' => $this->resource->quizQuestions()->get(), + 'quiz_questions' => QuizQuestionResource::collection($this->resource->quizQuestions()->get()), 'icon' => 'QuestionMarkCircleIcon', 'href' => route('quizzes.show', $this), ]; diff --git a/app/Http/Resources/RevisionResource.php b/app/Http/Resources/RevisionResource.php index 9aec577..0d29a1a 100644 --- a/app/Http/Resources/RevisionResource.php +++ b/app/Http/Resources/RevisionResource.php @@ -31,4 +31,4 @@ public function toArray(Request $request): array 'items' => $this->resource->revisionItems()->get(), ]; } -} +} \ No newline at end of file diff --git a/app/Http/Resources/SlideResource.php b/app/Http/Resources/SlideResource.php index 72ce6a2..38e3bda 100644 --- a/app/Http/Resources/SlideResource.php +++ b/app/Http/Resources/SlideResource.php @@ -24,8 +24,8 @@ public function toArray(Request $request): array 'description' => $this->resource->description, 'background_url' => Storage::disk('s3')->url($this->resource->background_url), 'layout' => $this->resource->layout, - 'slide_items' => $this->resource->slide_items, + 'slide_items' => $this->resource->slideItems, 's3_image' => Storage::disk('s3')->url($this->resource->background_url), ]; } -} +} \ No newline at end of file diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php new file mode 100644 index 0000000..0900c06 --- /dev/null +++ b/app/Http/Resources/UserResource.php @@ -0,0 +1,21 @@ + $this->id, + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'phone' => $this->phone, + 'email' => $this->email, + 'profile_photo_path' => env('APP_URL').'/storage/'.$this->profile_photo_path, + ]; + } +} diff --git a/app/Models/Book.php b/app/Models/Book.php index 1fbd864..f10d532 100644 --- a/app/Models/Book.php +++ b/app/Models/Book.php @@ -18,6 +18,10 @@ class Book extends Model implements OrderableInterface 'file_src', 'file_cta', 'price', 'tax', 'discount' ]; + protected $appends = [ + 'href' + ]; + /** * Get the attributes that should be cast. * @@ -36,4 +40,9 @@ public function totalPrice(): float { return $this->price + $this->tax - $this->discount; } + + public function getHrefAttribute() + { + return route('books.index'); + } } diff --git a/app/Models/Course.php b/app/Models/Course.php index 0324e60..8cb2969 100644 --- a/app/Models/Course.php +++ b/app/Models/Course.php @@ -24,6 +24,10 @@ class Course extends Model implements OrderableInterface 'published_at', ]; + protected $appends = [ + 'href' + ]; + protected function casts(): array { return [ @@ -76,4 +80,21 @@ public function isFree(): bool { return $this->price <= 0; } -} + + public function getHrefAttribute() + { + $first_section = $this->sections->first(); + if(isset($first_section)){ + return route('courses.sections.section', $first_section->slug); + }else{ + return 'javascript:;'; + } + } + + public function scopeHasSections(Builder $query): Builder + { + return $query->has('sections'); + } + + +} \ No newline at end of file diff --git a/app/Models/Lesson.php b/app/Models/Lesson.php index 1861902..902a5dc 100644 --- a/app/Models/Lesson.php +++ b/app/Models/Lesson.php @@ -7,19 +7,23 @@ use App\Http\Resources\LessonResource; use App\Traits\HasEmailOnEvents; use App\Traits\Progressive; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Support\Facades\Storage; use Nagy\LaravelRating\Traits\Rateable; use Spatie\EloquentSortable\Sortable; use Spatie\EloquentSortable\SortableTrait; class Lesson extends Model implements EventableInterface, ProgressiveInterface, Sortable { + use HasEmailOnEvents, Progressive; + /** @use HasFactory<\Database\Factories\LessonFactory> */ use HasFactory; - use HasEmailOnEvents, Progressive; + use Rateable, SortableTrait; public array $sortable = [ @@ -29,7 +33,7 @@ class Lesson extends Model implements EventableInterface, ProgressiveInterface, 'sort_on_has_many' => true, ]; - protected $appends = ['user_progress']; + protected $appends = ['user_progress', 'lesson_video_url']; protected $fillable = ['name', 'duration', 'slug']; @@ -138,4 +142,17 @@ public function asResource() { return LessonResource::make($this->refresh()); } + + public function lessonVideoUrl(): Attribute + { + return Attribute::make( + get: function () { + if ($this->visual && $this->visual->video_url) { + return Storage::disk('s3')->url($this->visual->video_url); + } + + return null; + } + ); + } } diff --git a/app/Models/Order.php b/app/Models/Order.php index b17a325..92032d9 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -48,4 +48,4 @@ public function course(): BelongsTo { return $this->belongsTo(Course::class); } -} +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 6497633..dece857 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -28,10 +28,15 @@ class User extends Authenticatable implements MustVerifyEmail * * @var list */ + // protected $fillable = [ + // 'first_name', 'last_name', 'email', 'password', 'phone', 'country', 'last_login_at', + // 'utm_source', 'utm_content', 'utm_medium', 'utm_campaign', 'utm_term' + // ]; + protected $fillable = [ - 'first_name', 'last_name', 'email', 'password', 'phone', 'country', 'last_login_at', - 'utm_source', 'utm_content', 'utm_medium', 'utm_campaign', 'utm_term' - ]; + 'first_name', 'last_name', 'email', 'password', 'phone', 'country', 'last_login_at', + 'utm_source', 'utm_content', 'utm_medium', 'utm_campaign', 'utm_term', 'profile_photo_path' +]; /** * The attributes that should be hidden for serialization. @@ -93,6 +98,16 @@ public function orders(): HasMany return $this->hasMany(Order::class); } + public function courseOrders() + { + return $this->orders()->where('orderable_type', Course::class); + } + + public function bookOrders() + { + return $this->orders()->where('orderable_type', Book::class); + } + /** * Check if a user owns a specific book */ @@ -131,4 +146,6 @@ protected function name(): Attribute get: fn ($value, array $attributes) => $attributes['first_name'].' '.$attributes['last_name'], ); } -} + + +} \ No newline at end of file diff --git a/app/Notifications/SendBookLink.php b/app/Notifications/SendBookLink.php index b4edfba..84917e3 100644 --- a/app/Notifications/SendBookLink.php +++ b/app/Notifications/SendBookLink.php @@ -2,46 +2,44 @@ namespace App\Notifications; -use App\Models\Book; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; -use Illuminate\Support\Facades\Storage; class SendBookLink extends Notification implements ShouldQueue { use Queueable; - protected string $temp_link; + public $book; - /** - * Create a new notification instance. - */ - public function __construct(Book $book) + public $downloadUrl; + + public $duration; + + public function __construct($book, $downloadUrl, $duration) { - $url = json_decode($book->file_src)->path; - $this->temp_link = Storage::disk('s3_public')->temporaryUrl($url, now()->addMinutes(5)); + $this->book = $book; + $this->downloadUrl = $downloadUrl; + $this->duration = $duration; + } - /** - * Get the notification's delivery channels. - * - * @return array - */ - public function via(object $notifiable): array + public function via($notifiable): array { return ['mail']; } - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage + public function toMail($notifiable): MailMessage { return (new MailMessage) - ->line('يمكنك تحميل الكتاب من الرابط التالى') - ->action('رابط التحميل', $this->temp_link); + ->subject(__('locale.download_book_link').': '.$this->book->name) + ->view('mail.book-download-link', [ + 'book' => $this->book, + 'downloadUrl' => $this->downloadUrl, + 'duration' => $this->duration, + 'notifiable' => $notifiable, + ]); } /** diff --git a/app/PaymentGateways/KashierPaymentGateway.php b/app/PaymentGateways/KashierPaymentGateway.php index d37e159..138fd5b 100644 --- a/app/PaymentGateways/KashierPaymentGateway.php +++ b/app/PaymentGateways/KashierPaymentGateway.php @@ -42,7 +42,8 @@ public function calculateTotalAmount($rate = null, $fees = null): string public function vendorFees(): float { - return 2.5; + // return 2.5; + return 0; } public function currency(): string diff --git a/app/Policies/BookPolicy.php b/app/Policies/BookPolicy.php new file mode 100644 index 0000000..0745677 --- /dev/null +++ b/app/Policies/BookPolicy.php @@ -0,0 +1,17 @@ +isOwned($user->id); + } +} diff --git a/app/Policies/Policy.php b/app/Policies/Policy.php new file mode 100644 index 0000000..cefc2db --- /dev/null +++ b/app/Policies/Policy.php @@ -0,0 +1,85 @@ +key = class_basename(Str::remove('Policy', $this::class)); + } + + public function before(Authenticatable|User|Admin $auth): ?bool + { + return Nova::whenServing(function (NovaRequest $request) use ($auth) { + try { + if ($auth?->isSuper()) { + return true; + } + } catch (\Throwable $e) { + return null; + } + + return null; + }); + } + + public function viewAny(Authenticatable|Admin|User $auth): bool + { + return Nova::whenServing(function (NovaRequest $request) use ($auth) { + if ($auth instanceof Admin) { + return $auth->hasPermissionTo(Policy::ViewAny.$this->key, 'admin'); + } + + return false; + }, function (Request $request) use ($auth) { + return $auth->hasVerifiedEmail(); + }); + } + + public function create(Authenticatable|Admin|User $auth): bool + { + return Nova::whenServing(function (NovaRequest $request) use ($auth) { + if ($auth instanceof Admin) { + return $auth->hasPermissionTo(Policy::Create.$this->key, 'admin'); + } + + return false; + }, function (Request $request) { + return false; + }); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..ca3cbb9 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -11,7 +11,7 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - // + $this->app->useLangPath(base_path('lang')); } /** @@ -21,4 +21,4 @@ public function boot(): void { // } -} +} \ No newline at end of file diff --git a/app/Providers/NovaServiceProvider.php b/app/Providers/NovaServiceProvider.php index 716893e..f367d00 100644 --- a/app/Providers/NovaServiceProvider.php +++ b/app/Providers/NovaServiceProvider.php @@ -10,6 +10,7 @@ use Laravel\Nova\Menu\MenuSection; use Laravel\Nova\Nova; use Laravel\Nova\NovaApplicationServiceProvider; +use Outl1ne\NovaSettings\NovaSettings; use Sereny\NovaPermissions\NovaPermissions; class NovaServiceProvider extends NovaApplicationServiceProvider @@ -34,6 +35,7 @@ public function boot(): void MenuSection::make('E-Learning', [ MenuItem::resource(\App\Nova\Course::class), MenuItem::resource(\App\Nova\Section::class), + // MenuItem::resource(\App\Nova\Lesson::class), MenuItem::resource(\App\Nova\Glossary::class), ])->icon('finger-print')->collapsable(), @@ -57,9 +59,16 @@ public function boot(): void ->menu($request) ->canSee(fn () => auth('admin')->user()?->isSuper()), + // NovaSettings::make() + // ->menu($request), + LogViewer::make() ->menu($request) ->canSee(fn () => auth('admin')->user()?->isSuper()), + + (new \Bernhardh\NovaTranslationEditor\NovaTranslationEditor) + ->menu($request) + ->canSee(fn () => auth('admin')->user()?->isSuper()), ]; }); } @@ -121,7 +130,9 @@ public function tools(): array { return [ NovaPermissions::make(), - LogViewer::make() + LogViewer::make(), + NovaSettings::make(), + (new \Bernhardh\NovaTranslationEditor\NovaTranslationEditor)->canSee(fn () => auth('admin')->user()?->isSuper()), ]; } diff --git a/app/Services/BookService.php b/app/Services/BookService.php new file mode 100644 index 0000000..d3edd7d --- /dev/null +++ b/app/Services/BookService.php @@ -0,0 +1,76 @@ +generateDownloadUrl->execute($book, 30); + $user->notify(new SendBookLink($book, $downloadUrl, 30)); + } + + /** + * Get the latest successful order for a book + */ + public function getLatestOrder(Book $book, User $user): ?Order + { + return Order::where([ + 'user_id' => $user->id, + 'orderable_type' => Book::class, + 'orderable_id' => $book->id, + 'status' => OrderStatus::Success, + ])->latest()->first(); + } + + /** + * Send invoice if order is recent (within 5 minutes) + */ + public function sendInvoiceIfRecent(Book $book, Order $order, User $user): void + { + if ($order->created_at->diffInMinutes(now()) < 5) { + $user->notify(new BookInvoice($book, $order)); + } + } + + /** + * Generate temporary download URL for a book + */ + public function generateDownloadUrl(Book $book, int $minutesValid = 30): ?string + { + return $this->generateDownloadUrl->execute($book, $minutesValid); + } + + /** + * Prepare download page data + */ + public function prepareDownloadData(Book $book, User $user): array + { + // $order = $this->getLatestOrder($book, $user); + + // if ($order) { + // $this->sendInvoiceIfRecent($book, $order, $user); + // } + + return [ + 'book' => $book, + 'downloadUrl' => $this->generateDownloadUrl->execute($book, 30), + ]; + } + +} diff --git a/app/Services/UserRegistrationService.php b/app/Services/UserRegistrationService.php new file mode 100644 index 0000000..a47ff47 --- /dev/null +++ b/app/Services/UserRegistrationService.php @@ -0,0 +1,64 @@ + $data['first_name'], + 'last_name' => $data['last_name'], + 'email' => $data['email'], + 'phone' => $data['phone'], + 'country' => $data['country'] ?? null, + 'password' => Hash::make($data['password']), + 'utm_source' => $utmData['utm_source'] ?? null, + 'utm_content' => $utmData['utm_content'] ?? null, + 'utm_medium' => $utmData['utm_medium'] ?? null, + 'utm_campaign' => $utmData['utm_campaign'] ?? null, + 'utm_term' => $utmData['utm_term'] ?? null, + ]); + + return $user; + } + + /** + * Extract UTM data from cookie + */ + public function extractUtmData(): ?array + { + if (! request()->hasCookie('utm')) { + return null; + } + + $utmData = json_decode(request()->cookie('utm'), true); + + return $utmData ?: null; + } + + /** + * Create Zoho lead for registered user + */ + public function createZohoLead(User $user): void + { + $this->createZohoLeadAction->execute([ + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'email' => $user->email, + 'phone' => $user->phone, + 'lead_source' => 'trader_factory', + ]); + } +} diff --git a/app/Traits/HasOrders.php b/app/Traits/HasOrders.php index e74b2e6..fdb53b7 100644 --- a/app/Traits/HasOrders.php +++ b/app/Traits/HasOrders.php @@ -2,6 +2,7 @@ namespace App\Traits; +use App\Enums\OrderStatus; use App\Models\Order; use Illuminate\Database\Eloquent\Relations\MorphMany; @@ -20,13 +21,27 @@ public function orders(): MorphMany /** * Check if the course is owned by the authed user. */ - public function purchased(): bool + public function purchased(?int $userId = null): bool { + $userId = $userId ?? auth()->id(); + return $this->orders()->where([ - 'status' => 'SUCCESS', - 'user_id' => auth()->id(), + 'status' => OrderStatus::Success, + 'user_id' => $userId, 'orderable_type' => self::class, 'orderable_id' => $this->id, ])->count(); } -} + + public function isOwned(?int $userId = null): bool + { + $userId = $userId ?? auth()->id(); + + return $this->orders()->where([ + 'status' => OrderStatus::Success, + 'user_id' => $userId, + 'orderable_type' => self::class, + 'orderable_id' => $this->id, + ])->exists(); + } +} \ No newline at end of file diff --git a/app/Traits/HasProfilePhoto.php b/app/Traits/HasProfilePhoto.php index 7660bc4..92b8df0 100644 --- a/app/Traits/HasProfilePhoto.php +++ b/app/Traits/HasProfilePhoto.php @@ -71,6 +71,6 @@ protected function defaultProfilePhotoUrl(): string */ protected function profilePhotoDisk(): string { - return 's3'; + return 'public'; } } diff --git a/composer.json b/composer.json index d814a44..1ee3d02 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "php": "^8.3", "asciisd/kashier": "^1.0", "asciisd/zoho-v8": "^1.0", + "bernhardh/nova-translation-editor": "^2.0", "inertiajs/inertia-laravel": "^2.0", "interaction-design-foundation/nova-unlayer-field": "^2.2", "laravel/framework": "^12.0", @@ -27,6 +28,7 @@ "sereny/nova-permissions": "^1.7", "spatie/laravel-permission": "^6.17", "spatie/laravel-sitemap": "^7.3", + "spatie/laravel-translation-loader": "^2.8", "tightenco/ziggy": "^2.4" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 2c82e12..e52afb0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "87f88be1d841d778b71d8ca07d8905f8", + "content-hash": "f5901d3b8944196ac9a1c4b651379456", "packages": [ { "name": "asciisd/kashier", @@ -340,6 +340,53 @@ }, "time": "2024-10-01T13:55:55+00:00" }, + { + "name": "bernhardh/nova-translation-editor", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/bernhardh/nova-translation-editor.git", + "reference": "f0a45e994d33c9349b82bd60b5fbe39e95e1286f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bernhardh/nova-translation-editor/zipball/f0a45e994d33c9349b82bd60b5fbe39e95e1286f", + "reference": "f0a45e994d33c9349b82bd60b5fbe39e95e1286f", + "shasum": "" + }, + "require": { + "php": ">=7.3.0", + "spatie/laravel-translation-loader": ">=2.6.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Bernhardh\\NovaTranslationEditor\\ToolServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Bernhardh\\NovaTranslationEditor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Laravel Nova translation editor", + "keywords": [ + "laravel", + "nova" + ], + "support": { + "issues": "https://github.com/bernhardh/nova-translation-editor/issues", + "source": "https://github.com/bernhardh/nova-translation-editor/tree/v2.0.4" + }, + "abandoned": true, + "time": "2023-03-01T11:01:27+00:00" + }, { "name": "brick/math", "version": "0.14.0", @@ -5744,6 +5791,78 @@ ], "time": "2025-08-25T08:07:09+00:00" }, + { + "name": "spatie/laravel-translation-loader", + "version": "2.8.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-translation-loader.git", + "reference": "17fa7d211bca1423a0e424823e7fddd5c2bc4ab6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-translation-loader/zipball/17fa7d211bca1423a0e424823e7fddd5c2bc4ab6", + "reference": "17fa7d211bca1423a0e424823e7fddd5c2bc4ab6", + "shasum": "" + }, + "require": { + "illuminate/translation": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "php": "^8.0", + "spatie/laravel-package-tools": "^1.12" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.59", + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "pestphp/pest": "^1.23|^2.0|^3.7|^4.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\TranslationLoader\\TranslationServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\TranslationLoader\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Store your language lines in the database, yaml or other sources", + "homepage": "https://github.com/spatie/laravel-translation-loader", + "keywords": [ + "database", + "db", + "i8n", + "language", + "laravel", + "laravel-translation-loader", + "spatie", + "translate" + ], + "support": { + "source": "https://github.com/spatie/laravel-translation-loader/tree/2.8.3" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2026-02-21T21:44:45+00:00" + }, { "name": "spatie/robots-txt", "version": "2.5.2", diff --git a/config/kashier.php b/config/kashier.php new file mode 100644 index 0000000..c800c00 --- /dev/null +++ b/config/kashier.php @@ -0,0 +1,17 @@ + env('KASHIER_ENABLED', false), + 'title' => env('KASHIER_TITLE', 'Kashier'), + 'description' => env('KASHIER_DESCRIPTION', 'Pay with Kashier'), + 'mode' => env('KASHIER_MODE', 'test'), + 'apikey' => env('KASHIER_API_KEY', ''), + 'secretKey' => env('KASHIER_SECRET_KEY', ''), + 'mid' => env('KASHIER_MERCHANT_ID', ''), + 'currency' => env('KASHIER_CURRENCY', 'EGP'), + 'enforce_egp_payment' => env('KASHIER_ENFORCE_EGP_PAYMENT', false), + + 'baseUrl' => 'https://checkout.kashier.io', + 'serverWebhookUrl' => env('KASHIER_SERVER_WEBHOOK_URL', '/kashier/webhook'), + 'callbackUrl' => env('KASHIER_CALLBACK_URL'), +]; diff --git a/config/nova-settings.php b/config/nova-settings.php new file mode 100644 index 0000000..65e0d5b --- /dev/null +++ b/config/nova-settings.php @@ -0,0 +1,51 @@ + 'nova_settings', + + /** + * URL path of settings page + */ + 'base_path' => 'nova-settings', + + /** + * Reload the entire page on save. Useful when updating any Nova UI related settings. + */ + 'reload_page_on_save' => false, + + /** + * We need to know which eloquent model should be used to retrieve your permissions. + * Of course, it is often just the default model but you may use whatever you like. + * + * The model you want to use as a model needs to extend the original model. + */ + 'models' => [ + 'settings' => \Outl1ne\NovaSettings\Models\Settings::class, + ], + + /** + * Show the sidebar menu + */ + 'show_in_sidebar' => true, + + /* + |-------------------------------------------------------------------------- + | Cache settings + |-------------------------------------------------------------------------- + | + | Here you may specify which of the cache connection should be used to + | cache the settings. `:memory:` is the default which is a simple + | in-memory cache through a singleton service class property. + | `null` will disable caching. + | + */ + 'cache' => [ + 'store' => env('NOVA_SETTINGS_CACHE_DRIVER', ':memory:'), + + 'prefix' => 'nova-settings:', + ], +]; diff --git a/config/nova-translation-editor.php b/config/nova-translation-editor.php new file mode 100644 index 0000000..e1a7369 --- /dev/null +++ b/config/nova-translation-editor.php @@ -0,0 +1,19 @@ + $groups, + 'languages' => [ + 'ar', + 'en', + ], +]; diff --git a/config/site.php b/config/site.php new file mode 100644 index 0000000..8fd11a5 --- /dev/null +++ b/config/site.php @@ -0,0 +1,8 @@ + env('SITE_TITLE', config('app.name')), + 'site_subtitle' => env('SITE_SUBTITLE', 'أحد مصادر التدريب العلمي والعملي لأسواق المال'), + 'site_description' => env('SITE_DESCRIPTION', 'تدرب على الأستثمار في سوق العملات'), + 'site_cta' => env('SITE_CTA', '#'), +]; \ No newline at end of file diff --git a/config/translation-loader.php b/config/translation-loader.php new file mode 100644 index 0000000..19ff8ef --- /dev/null +++ b/config/translation-loader.php @@ -0,0 +1,24 @@ + [ + Spatie\TranslationLoader\TranslationLoaders\Db::class, + ], + + /* + * This is the model used by the Db Translation loader. You can put any model here + * that extends Spatie\TranslationLoader\LanguageLine. + */ + 'model' => Spatie\TranslationLoader\LanguageLine::class, + + /* + * This is the translation manager which overrides the default Laravel `translation.loader` + */ + 'translation_manager' => Spatie\TranslationLoader\TranslationLoaderManager::class, + +]; diff --git a/database/factories/BookFactory.php b/database/factories/BookFactory.php index 826dd23..1c1c8fa 100644 --- a/database/factories/BookFactory.php +++ b/database/factories/BookFactory.php @@ -17,7 +17,27 @@ class BookFactory extends Factory public function definition(): array { return [ - // + 'name' => fake()->sentence(3), + 'description' => fake()->paragraph(), + 'image_src' => 'books/covers/default.jpg', + 'image_alt' => fake()->sentence(2), + 'file_src' => 'books/pdfs/sample-book.pdf', + 'file_cta' => 'تحميل الكتاب', + 'price' => fake()->randomFloat(2, 0, 500), + 'tax' => fake()->randomFloat(2, 0, 50), + 'discount' => fake()->randomFloat(2, 0, 100), ]; } + + /** + * Indicate that the book is free. + */ + public function free(): static + { + return $this->state(fn (array $attributes) => [ + 'price' => 0, + 'tax' => 0, + 'discount' => 0, + ]); + } } diff --git a/database/factories/OrderFactory.php b/database/factories/OrderFactory.php index c122d40..96a250a 100644 --- a/database/factories/OrderFactory.php +++ b/database/factories/OrderFactory.php @@ -2,22 +2,43 @@ namespace Database\Factories; +use App\Enums\OrderStatus; +use App\Models\Book; +use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; -/** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Order> - */ class OrderFactory extends Factory { - /** - * Define the model's default state. - * - * @return array - */ public function definition(): array { return [ - // + 'user_id' => User::factory(), + 'orderable_type' => Book::class, + 'orderable_id' => Book::factory(), + 'transaction_id' => fake()->uuid(), + 'status' => OrderStatus::Success, + 'conversion_rate' => 1.0, + 'vendor_fees' => 0, + 'currency' => 'EGP', + 'method' => 'card', + 'provider' => 'KASHIER', + 'total' => fake()->randomFloat(2, 50, 500), + 'sub_total' => fake()->randomFloat(2, 50, 500), ]; } -} + + public function success(): static + { + return $this->state(fn (array $attributes) => [ + 'status' => OrderStatus::Success, + ]); + } + + + public function failed(): static + { + return $this->state(fn (array $attributes) => [ + 'status' => OrderStatus::Failure, + ]); + } +} \ No newline at end of file diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 584104c..d03e102 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -24,7 +24,8 @@ class UserFactory extends Factory public function definition(): array { return [ - 'name' => fake()->name(), + 'first_name' => fake()->firstName(), + 'last_name' => fake()->lastName(), 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => static::$password ??= Hash::make('password'), diff --git a/database/migrations/2019_08_13_000000_create_nova_settings_table.php b/database/migrations/2019_08_13_000000_create_nova_settings_table.php new file mode 100644 index 0000000..975b9c9 --- /dev/null +++ b/database/migrations/2019_08_13_000000_create_nova_settings_table.php @@ -0,0 +1,33 @@ +string('key')->unique()->primary(); + $table->text('value')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists(NovaSettings::getSettingsTableName()); + } +}; diff --git a/database/migrations/2021_02_15_000000_update_nova_settings_value_column.php b/database/migrations/2021_02_15_000000_update_nova_settings_value_column.php new file mode 100644 index 0000000..14859e8 --- /dev/null +++ b/database/migrations/2021_02_15_000000_update_nova_settings_value_column.php @@ -0,0 +1,34 @@ +text('value')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // No down because previous migration was also modified + } +}; diff --git a/database/migrations/2025_05_12_204623_create_glossaries_table.php b/database/migrations/2025_05_12_204623_create_glossaries_table.php index ef6e3aa..21e231d 100644 --- a/database/migrations/2025_05_12_204623_create_glossaries_table.php +++ b/database/migrations/2025_05_12_204623_create_glossaries_table.php @@ -13,17 +13,17 @@ public function up(): void { Schema::create('glossaries', function (Blueprint $table) { $table->id(); - $table->string('title')->nullable()->fulltext(); - $table->string('title_ar')->fulltext(); + $table->string('title')->nullable(); + $table->string('title_ar'); $table->string('slug')->unique(); $table->string('initials')->nullable(); $table->string('initials_ar'); - $table->text('body')->nullable()->fulltext(); - $table->text('body_ar')->fulltext(); - $table->string('topic')->nullable()->fulltext(); - $table->string('topic_ar')->nullable()->fulltext(); - $table->string('category')->nullable()->fulltext(); - $table->string('category_ar')->fulltext(); + $table->text('body')->nullable(); + $table->text('body_ar'); + $table->string('topic')->nullable(); + $table->string('topic_ar')->nullable(); + $table->string('category')->nullable(); + $table->string('category_ar'); $table->timestamps(); }); } @@ -35,4 +35,4 @@ public function down(): void { Schema::dropIfExists('glossaries'); } -}; +}; \ No newline at end of file diff --git a/database/migrations/2026_03_24_144855_create_language_lines_table.php b/database/migrations/2026_03_24_144855_create_language_lines_table.php new file mode 100644 index 0000000..d24d12a --- /dev/null +++ b/database/migrations/2026_03_24_144855_create_language_lines_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('group')->index(); + $table->string('key'); + $table->json('text'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('language_lines'); + } +}; diff --git a/lang/ar/locale.php b/lang/ar/locale.php new file mode 100644 index 0000000..e662eff --- /dev/null +++ b/lang/ar/locale.php @@ -0,0 +1,9 @@ + 'رابط تحميل الكتاب', + 'download_book_message' => 'يمكنك تحميل الكتاب من الرابط التالي:', + 'download_book_button' => 'تحميل الكتاب', + 'download_book_validity' => 'هذا الرابط صالح لمدة :duration دقيقة', + 'download_link_sent' => 'تم إرسال رابط التحميل على البريد الإلكتروني', +]; diff --git a/lang/ar/validation.php b/lang/ar/validation.php index 93fb6c3..18a07cc 100644 --- a/lang/ar/validation.php +++ b/lang/ar/validation.php @@ -193,6 +193,13 @@ | */ - 'attributes' => [], + 'attributes' => [ + 'phone' => 'رقم الهاتف', + 'first_name' => 'الاسم الأول', + 'last_name' => 'الاسم الأخير', + 'email' => 'البريد الإلكتروني', + 'password' => 'كلمة المرور', + 'password_confirmation' => 'تأكيد كلمة المرور' + ], ]; diff --git a/lang/ar/verify_email.php b/lang/ar/verify_email.php new file mode 100644 index 0000000..5c3d946 --- /dev/null +++ b/lang/ar/verify_email.php @@ -0,0 +1,13 @@ + 'مرحبا بك في أكاديمية تريدر فاكتوري', + 'greeting' => 'مرحباً', + 'line_1' => 'شكرًا لتسجيلك في أكاديمية تريدر فاكتوري. لإكمال عملية التسجيل وتفعيل حسابك، الرجاء الضغط على الرابط أدناه لتأكيد عنوان بريدك الإلكتروني:', + 'cta' => 'تأكيد البريد الإلكتروني', + 'line_2' => 'إذا لم تطلب انشاء حساب على أكاديمية تريدر فاكتوري ، يمكنك تجاهل هذه الرسالة.', + 'regards' => 'شكرًا لك,', + 'salutation' => 'فريق أكاديمية تريدر فاكتوري', + 'subcopy' => "إذا كنت تواجه أي مشاكل فى الضغط على \":actionText\", قم بنسخ الرابط أدناه\n". + 'فى عنوان المتصفح', +]; diff --git a/lang/en/locale.php b/lang/en/locale.php new file mode 100644 index 0000000..5c4e227 --- /dev/null +++ b/lang/en/locale.php @@ -0,0 +1,9 @@ + 'Book Download Link', + 'download_book_message' => 'You can download the book from the following link:', + 'download_book_button' => 'Download Book', + 'download_book_validity' => 'Valid for :duration minutes', + 'download_link_sent' => 'The download link has been sent to your email', +]; diff --git a/lang/en/verify_email.php b/lang/en/verify_email.php new file mode 100644 index 0000000..7f7cdb3 --- /dev/null +++ b/lang/en/verify_email.php @@ -0,0 +1,13 @@ + 'Welcome to Trader Factory Academy', + 'greeting' => 'Hello', + 'line_1' => 'Thank you for registering with Trader Factory academy, to continue registration process please click the link below to verify your email address.', + 'cta' => 'Verify Email', + 'line_2' => 'If you didn\'t create an account on Trader Factory, no further action is required.', + 'regards' => 'Regards', + 'salutation' => 'Trader Factory Academy Team', + 'subcopy' => "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\n". + 'into your web browser:', +]; \ No newline at end of file diff --git a/lang/vendor/nova-settings/ar.json b/lang/vendor/nova-settings/ar.json new file mode 100644 index 0000000..52e3e81 --- /dev/null +++ b/lang/vendor/nova-settings/ar.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "الإعدادات", + "novaSettings.saveButtonText": "حفظ الإعدادات", + "novaSettings.noSettingsFieldsText": "لم يتم اضافة حقول للإعدادات.", + "novaSettings.settingsSuccessToast": "تم حفظ الإعدادات", + "novaSettings.general": "الإعدادات العامة" +} diff --git a/lang/vendor/nova-settings/de.json b/lang/vendor/nova-settings/de.json new file mode 100644 index 0000000..bcc7158 --- /dev/null +++ b/lang/vendor/nova-settings/de.json @@ -0,0 +1,6 @@ +{ + "novaSettings.navigationItemTitle": "Einstellungen", + "novaSettings.saveButtonText": "Einstellungen speichern", + "novaSettings.noSettingsFieldsText": "Es wurden keine Einstellungs-Felder definiert.", + "novaSettings.settingsSuccessToast": "Einstellungen erfolgreich geändert." +} diff --git a/lang/vendor/nova-settings/en.json b/lang/vendor/nova-settings/en.json new file mode 100644 index 0000000..94efc4b --- /dev/null +++ b/lang/vendor/nova-settings/en.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "Settings", + "novaSettings.saveButtonText": "Save settings", + "novaSettings.noSettingsFieldsText": "No settings fields have been defined.", + "novaSettings.settingsSuccessToast": "Settings successfully updated!", + "novaSettings.general": "General" +} diff --git a/lang/vendor/nova-settings/es.json b/lang/vendor/nova-settings/es.json new file mode 100644 index 0000000..dcb814b --- /dev/null +++ b/lang/vendor/nova-settings/es.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "Configuración", + "novaSettings.saveButtonText": "Guardar configuración", + "novaSettings.noSettingsFieldsText": "No se ha definido ningún campo de configuración.", + "novaSettings.settingsSuccessToast": "¡La configuración se ha actualizado correctamente!", + "novaSettings.general": "General" +} diff --git a/lang/vendor/nova-settings/et.json b/lang/vendor/nova-settings/et.json new file mode 100644 index 0000000..64cb0f2 --- /dev/null +++ b/lang/vendor/nova-settings/et.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "Seaded", + "novaSettings.saveButtonText": "Salvesta seaded", + "novaSettings.noSettingsFieldsText": "Ühtegi seadevälja ei ole defineeritud.", + "novaSettings.settingsSuccessToast": "Seaded uuendatud!", + "novaSettings.general": "Üldine" +} diff --git a/lang/vendor/nova-settings/fa.json b/lang/vendor/nova-settings/fa.json new file mode 100644 index 0000000..8077d28 --- /dev/null +++ b/lang/vendor/nova-settings/fa.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "تنظیمات", + "novaSettings.saveButtonText": "ذخیره تنظیمات", + "novaSettings.noSettingsFieldsText": "هیچ تنظیمی تعریف نشده است.", + "novaSettings.settingsSuccessToast": "تنظیمات ذخیره شد.", + "novaSettings.general": "عمومی" +} diff --git a/lang/vendor/nova-settings/fr.json b/lang/vendor/nova-settings/fr.json new file mode 100644 index 0000000..049bc9b --- /dev/null +++ b/lang/vendor/nova-settings/fr.json @@ -0,0 +1,8 @@ +{ + "novaSettings.navigationItemTitle": "Paramètres", + "novaSettings.saveButtonText": "Enregistrer paramètres", + "novaSettings.noSettingsFieldsText": "Aucun champ paramètres n'a été défini.", + "novaSettings.settingsSuccessToast": "Paramètres mis à jour !", + "novaSettings.general": "Paramètres généraux" + } + \ No newline at end of file diff --git a/lang/vendor/nova-settings/it.json b/lang/vendor/nova-settings/it.json new file mode 100644 index 0000000..b7fc0e4 --- /dev/null +++ b/lang/vendor/nova-settings/it.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "Impostazioni", + "novaSettings.saveButtonText": "Salva impostazioni", + "novaSettings.noSettingsFieldsText": "Nessun campo Impostazioni è stato definito.", + "novaSettings.settingsSuccessToast": "Impostazioni aggiornate con successo", + "novaSettings.general": "Generale" +} diff --git a/lang/vendor/nova-settings/nl.json b/lang/vendor/nova-settings/nl.json new file mode 100644 index 0000000..e163959 --- /dev/null +++ b/lang/vendor/nova-settings/nl.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "Instellingen", + "novaSettings.saveButtonText": "Instellingen opslaan", + "novaSettings.noSettingsFieldsText": "Geen veld gedefineerd.", + "novaSettings.settingsSuccessToast": "Instellingen succesvol geupdatet", + "novaSettings.general": "Algemeen" +} diff --git a/lang/vendor/nova-settings/pt-BR.json b/lang/vendor/nova-settings/pt-BR.json new file mode 100644 index 0000000..819871c --- /dev/null +++ b/lang/vendor/nova-settings/pt-BR.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "Configurações", + "novaSettings.saveButtonText": "Salvar configurações", + "novaSettings.noSettingsFieldsText": "Nenhum campo de configuração foi definido.", + "novaSettings.settingsSuccessToast": "Configurações atualizadas com sucesso", + "novaSettings.general": "Geral" +} diff --git a/lang/vendor/nova-settings/ru.json b/lang/vendor/nova-settings/ru.json new file mode 100644 index 0000000..535647b --- /dev/null +++ b/lang/vendor/nova-settings/ru.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "Настройки", + "novaSettings.saveButtonText": "Сохранить", + "novaSettings.noSettingsFieldsText": "Поля настроек не определены.", + "novaSettings.settingsSuccessToast": "Настройки успешно обновлены", + "novaSettings.general": "Основные" +} diff --git a/lang/vendor/nova-settings/sk.json b/lang/vendor/nova-settings/sk.json new file mode 100644 index 0000000..14b42ef --- /dev/null +++ b/lang/vendor/nova-settings/sk.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "Nastavenia", + "novaSettings.saveButtonText": "Uložiť nastavenia", + "novaSettings.noSettingsFieldsText": "Neboli definované žiadne polia nastavení.", + "novaSettings.settingsSuccessToast": "Nastavenia boli úspešne aktualizované!", + "novaSettings.general": "Všeobecné" +} diff --git a/lang/vendor/nova-settings/tr.json b/lang/vendor/nova-settings/tr.json new file mode 100644 index 0000000..2c2db1e --- /dev/null +++ b/lang/vendor/nova-settings/tr.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "Ayarlar", + "novaSettings.saveButtonText": "Ayarları Kaydet", + "novaSettings.noSettingsFieldsText": "Henüz bir ayar alanı tanımlamadınız.", + "novaSettings.settingsSuccessToast": "Ayarlar başarıyla güncellendi!", + "novaSettings.general": "Genel" +} diff --git a/lang/vendor/nova-settings/uz.json b/lang/vendor/nova-settings/uz.json new file mode 100644 index 0000000..7cae462 --- /dev/null +++ b/lang/vendor/nova-settings/uz.json @@ -0,0 +1,7 @@ +{ + "novaSettings.navigationItemTitle": "Sozlamalar", + "novaSettings.saveButtonText": "Saqlash", + "novaSettings.noSettingsFieldsText": "Sozlamalar maydonlari belgilanmagan.", + "novaSettings.settingsSuccessToast": "Sozlamalar muvaffaqiyatli saqlandi", + "novaSettings.general": "Umumiy" +} diff --git a/package-lock.json b/package-lock.json index 6cc91dd..62071fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,11 +5,18 @@ "packages": { "": { "dependencies": { + "@fullcalendar/core": "^6.1.20", + "@fullcalendar/daygrid": "^6.1.20", + "@fullcalendar/interaction": "^6.1.20", + "@fullcalendar/list": "^6.1.20", + "@fullcalendar/timegrid": "^6.1.20", + "@fullcalendar/vue3": "^6.1.20", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.2.0", "@inertiajs/vue3": "^2.0.0", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.1", + "@videojs/themes": "^1.0.1", "@vitejs/plugin-vue": "^5.2.1", "@vueuse/core": "^12.8.2", "class-variance-authority": "^0.7.1", @@ -23,9 +30,11 @@ "tailwindcss": "^4.1.1", "tw-animate-css": "^1.2.5", "typescript": "^5.2.2", + "video.js": "^8.23.6", "vite": "^6.3.4", "vue": "^3.5.13", "vue-sonner": "^1.3.2", + "vue-tel-input": "^9.7.1", "ziggy-js": "^2.4.2" }, "devDependencies": { @@ -81,6 +90,15 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", @@ -751,6 +769,64 @@ } } }, + "node_modules/@fullcalendar/core": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.20.tgz", + "integrity": "sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==", + "license": "MIT", + "dependencies": { + "preact": "~10.12.1" + } + }, + "node_modules/@fullcalendar/daygrid": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.20.tgz", + "integrity": "sha512-AO9vqhkLP77EesmJzuU+IGXgxNulsA8mgQHynclJ8U70vSwAVnbcLG9qftiTAFSlZjiY/NvhE7sflve6cJelyQ==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.20" + } + }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.20.tgz", + "integrity": "sha512-p6txmc5txL0bMiPaJxe2ip6o0T384TyoD2KGdsU6UjZ5yoBlaY+dg7kxfnYKpYMzEJLG58n+URrHr2PgNL2fyA==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.20" + } + }, + "node_modules/@fullcalendar/list": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.20.tgz", + "integrity": "sha512-7Hzkbb7uuSqrXwTyD0Ld/7SwWNxPD6SlU548vtkIpH55rZ4qquwtwYdMPgorHos5OynHA4OUrZNcH51CjrCf2g==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.20" + } + }, + "node_modules/@fullcalendar/timegrid": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.20.tgz", + "integrity": "sha512-4H+/MWbz3ntA50lrPif+7TsvMeX3R1GSYjiLULz0+zEJ7/Yfd9pupZmAwUs/PBpA6aAcFmeRr0laWfcz1a9V1A==", + "license": "MIT", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.20" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.20" + } + }, + "node_modules/@fullcalendar/vue3": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/vue3/-/vue3-6.1.20.tgz", + "integrity": "sha512-8qg6pS27II9QBwFkkJC+7SfflMpWqOe7i3ii5ODq9KpLAjwQAd/zjfq8RvKR1Yryoh5UmMCmvRbMB7i4RGtqog==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.20", + "vue": "^3.0.11" + } + }, "node_modules/@headlessui/vue": { "version": "1.7.23", "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz", @@ -1630,17 +1706,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", - "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/type-utils": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -1653,7 +1729,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", + "@typescript-eslint/parser": "^8.55.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -1669,16 +1745,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", - "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3" }, "engines": { @@ -1694,14 +1770,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", + "@typescript-eslint/tsconfig-utils": "^8.55.0", + "@typescript-eslint/types": "^8.55.0", "debug": "^4.4.3" }, "engines": { @@ -1716,14 +1792,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1734,9 +1810,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", "dev": true, "license": "MIT", "engines": { @@ -1751,15 +1827,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", - "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -1776,9 +1852,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", "dev": true, "license": "MIT", "engines": { @@ -1790,16 +1866,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/project-service": "8.55.0", + "@typescript-eslint/tsconfig-utils": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -1818,16 +1894,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1842,13 +1918,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -1872,6 +1948,63 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@videojs/http-streaming": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.17.3.tgz", + "integrity": "sha512-6AteOnlpiY8ljijyCB+JszDCIVl9UcSgIiM+CfpBe3Yk2VESjKw9Jq8AkOyZyfLI4U5kDR9izHcGi6ho517n8g==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^4.1.1", + "aes-decrypter": "^4.0.2", + "global": "^4.4.0", + "m3u8-parser": "^7.2.0", + "mpd-parser": "^1.3.1", + "mux.js": "7.1.0", + "video.js": "^7 || ^8" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "video.js": "^8.19.0" + } + }, + "node_modules/@videojs/themes": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@videojs/themes/-/themes-1.0.1.tgz", + "integrity": "sha512-2b6YIIIz5x+/eSFdkSZ2RZJfHIMfP7bGODR3wDzLTqFF2kEKnJVIXxBUNzdZC/qiVETqAA2Ba6mCp+iXTUYt4A==", + "license": "MIT", + "dependencies": { + "postcss-inline-svg": "^4.1.0" + } + }, + "node_modules/@videojs/vhs-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.1.1.tgz", + "integrity": "sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/@videojs/xhr": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.7.0.tgz", + "integrity": "sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "global": "~4.4.0", + "is-function": "^1.0.1" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -1915,53 +2048,93 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", - "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.28.tgz", + "integrity": "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.27", - "entities": "^7.0.0", + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.28", + "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/@vue/compiler-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", - "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz", + "integrity": "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.27", - "@vue/shared": "3.5.27" + "@vue/compiler-core": "3.5.28", + "@vue/shared": "3.5.28" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", - "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz", + "integrity": "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.27", - "@vue/compiler-dom": "3.5.27", - "@vue/compiler-ssr": "3.5.27", - "@vue/shared": "3.5.27", + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.28", + "@vue/compiler-dom": "3.5.28", + "@vue/compiler-ssr": "3.5.28", + "@vue/shared": "3.5.28", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, + "node_modules/@vue/compiler-sfc/node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", - "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz", + "integrity": "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.27", - "@vue/shared": "3.5.27" + "@vue/compiler-dom": "3.5.28", + "@vue/shared": "3.5.28" } }, "node_modules/@vue/compiler-vue2": { @@ -2027,53 +2200,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", - "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.28.tgz", + "integrity": "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.27" + "@vue/shared": "3.5.28" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", - "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.28.tgz", + "integrity": "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.27", - "@vue/shared": "3.5.27" + "@vue/reactivity": "3.5.28", + "@vue/shared": "3.5.28" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", - "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz", + "integrity": "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.27", - "@vue/runtime-core": "3.5.27", - "@vue/shared": "3.5.27", + "@vue/reactivity": "3.5.28", + "@vue/runtime-core": "3.5.28", + "@vue/shared": "3.5.28", "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", - "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.28.tgz", + "integrity": "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.27", - "@vue/shared": "3.5.27" + "@vue/compiler-ssr": "3.5.28", + "@vue/shared": "3.5.28" }, "peerDependencies": { - "vue": "3.5.27" + "vue": "3.5.28" } }, "node_modules/@vue/shared": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", - "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.28.tgz", + "integrity": "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==", "license": "MIT" }, "node_modules/@vueuse/core": { @@ -2112,6 +2285,15 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2135,6 +2317,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/aes-decrypter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.2.tgz", + "integrity": "sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^4.1.1", + "global": "^4.4.0", + "pkcs7": "^1.0.4" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2209,13 +2403,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -2230,7 +2424,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, "license": "ISC" }, "node_modules/brace-expansion": { @@ -2434,6 +2627,39 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "node_modules/css-select/node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2508,6 +2734,46 @@ "node": ">=8" } }, + "node_modules/dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "license": "MIT", + "dependencies": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2542,16 +2808,10 @@ } }, "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "license": "BSD-2-Clause" }, "node_modules/es-define-property": { "version": "1.0.1", @@ -3243,6 +3503,16 @@ "node": ">=10.13.0" } }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "license": "MIT", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -3332,6 +3602,20 @@ "he": "bin/he" } }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "license": "MIT", + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3369,6 +3653,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3388,6 +3678,12 @@ "node": ">=8" } }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "license": "MIT" + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3514,6 +3810,13 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.39", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.39.tgz", + "integrity": "sha512-MW79m7HuOqBk8mwytiXYTMELJiBbV3Zl9Y39dCCn1yC8K+WGNSq1QGvzywbylp5vGShEztMScCWHX/XFOS0rXg==", + "license": "MIT", + "peer": true + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", @@ -3828,6 +4131,17 @@ "vue": ">=3.0.1" } }, + "node_modules/m3u8-parser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.2.0.tgz", + "integrity": "sha512-CRatFqpjVtMiMaKXxNvuI3I++vUumIXVVT/JpCpdU/FynV/ceVw1qpPyyBNindL+JlPMSesx+WX1QJaZEJSaMQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^4.1.1", + "global": "^4.4.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -3891,6 +4205,15 @@ "node": ">= 0.6" } }, + "node_modules/min-document": { + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", + "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", + "license": "MIT", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -3907,6 +4230,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/mpd-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.1.tgz", + "integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^4.0.0", + "@xmldom/xmldom": "^0.8.3", + "global": "^4.4.0" + }, + "bin": { + "mpd-to-m3u8-json": "bin/parse.js" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3921,6 +4259,23 @@ "dev": true, "license": "MIT" }, + "node_modules/mux.js": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.1.0.tgz", + "integrity": "sha512-NTxawK/BBELJrYsZThEulyUMDVlLizKdxyAsMuzoCD1eFj97BVaA8D/CvKsKu6FOLYkFojN5CbM9h++ZTZtknA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.11.2", + "global": "^4.4.0" + }, + "bin": { + "muxjs-transmux": "bin/transmux.js" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -4085,6 +4440,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkcs7": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", + "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.5.5" + }, + "bin": { + "pkcs7": "bin/cli.js" + } + }, "node_modules/playwright": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", @@ -4116,31 +4483,33 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "picocolors": "^0.2.1", + "source-map": "^0.6.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-inline-svg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-inline-svg/-/postcss-inline-svg-4.1.0.tgz", + "integrity": "sha512-0pYBJyoQ9/sJViYRc1cNOOTM7DYh0/rmASB0TBeRmWkG8YFK2tmgdkfjHkbRma1iFtBFKFHZFsHwRTDZTMKzSQ==", + "license": "MIT", + "dependencies": { + "css-select": "^2.0.2", + "dom-serializer": "^0.1.1", + "htmlparser2": "^3.10.1", + "postcss": "^7.0.17", + "postcss-value-parser": "^4.0.0" } }, "node_modules/postcss-selector-parser": { @@ -4156,6 +4525,28 @@ "node": ">=4" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/postcss/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC" + }, + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4286,6 +4677,15 @@ } } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -4350,6 +4750,20 @@ ], "license": "MIT" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/reka-ui": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.8.0.tgz", @@ -4372,14 +4786,14 @@ } }, "node_modules/reka-ui/node_modules/@vueuse/core": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.0.tgz", - "integrity": "sha512-tpjzVl7KCQNVd/qcaCE9XbejL38V6KJAEq/tVXj7mDPtl6JtzmUdnXelSS+ULRkkrDgzYVK7EerQJvd2jR794Q==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.1.tgz", + "integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==", "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "14.2.0", - "@vueuse/shared": "14.2.0" + "@vueuse/metadata": "14.2.1", + "@vueuse/shared": "14.2.1" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -4389,18 +4803,18 @@ } }, "node_modules/reka-ui/node_modules/@vueuse/metadata": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.0.tgz", - "integrity": "sha512-i3axTGjU8b13FtyR4Keeama+43iD+BwX9C2TmzBVKqjSHArF03hjkp2SBZ1m72Jk2UtrX0aYCugBq2R1fhkuAQ==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.1.tgz", + "integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/reka-ui/node_modules/@vueuse/shared": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.0.tgz", - "integrity": "sha512-Z0bmluZTlAXgUcJ4uAFaML16JcD8V0QG00Db3quR642I99JXIDRa2MI2LGxiLVhcBjVnL1jOzIvT5TT2lqJlkA==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.1.tgz", + "integrity": "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -4529,6 +4943,26 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -4649,6 +5083,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4658,6 +5101,15 @@ "node": ">=0.10.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -4876,16 +5328,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", - "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", + "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.54.0", - "@typescript-eslint/parser": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0" + "@typescript-eslint/eslint-plugin": "8.55.0", + "@typescript-eslint/parser": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4922,6 +5374,57 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/video.js": { + "version": "8.23.6", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.23.6.tgz", + "integrity": "sha512-qjS3HTDo7iapedJuso62scA303i+6CaCUnwyRr8GYd/BYAp7XGb7InUMw2Eu6zrN6IWooPOb78NzyMyjRbIN+Q==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/http-streaming": "^3.17.2", + "@videojs/vhs-utils": "^4.1.1", + "@videojs/xhr": "2.7.0", + "aes-decrypter": "^4.0.2", + "global": "4.4.0", + "m3u8-parser": "^7.2.0", + "mpd-parser": "^1.3.1", + "mux.js": "^7.0.1", + "videojs-contrib-quality-levels": "4.1.0", + "videojs-font": "4.2.0", + "videojs-vtt.js": "0.15.5" + } + }, + "node_modules/videojs-contrib-quality-levels": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.1.0.tgz", + "integrity": "sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==", + "license": "Apache-2.0", + "dependencies": { + "global": "^4.4.0" + }, + "engines": { + "node": ">=16", + "npm": ">=8" + }, + "peerDependencies": { + "video.js": "^8" + } + }, + "node_modules/videojs-font": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.2.0.tgz", + "integrity": "sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==", + "license": "Apache-2.0" + }, + "node_modules/videojs-vtt.js": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", + "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", + "license": "Apache-2.0", + "dependencies": { + "global": "^4.3.1" + } + }, "node_modules/vite": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", @@ -5049,6 +5552,34 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vite/node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", @@ -5057,16 +5588,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", - "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", + "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.27", - "@vue/compiler-sfc": "3.5.27", - "@vue/runtime-dom": "3.5.27", - "@vue/server-renderer": "3.5.27", - "@vue/shared": "3.5.27" + "@vue/compiler-dom": "3.5.28", + "@vue/compiler-sfc": "3.5.28", + "@vue/runtime-dom": "3.5.28", + "@vue/server-renderer": "3.5.28", + "@vue/shared": "3.5.28" }, "peerDependencies": { "typescript": "*" @@ -5120,6 +5651,16 @@ "integrity": "sha512-UbZ48E9VIya3ToiRHAZUbodKute/z/M1iT8/3fU8zEbwBRE11AKuHikssv18LMk2gTTr6eMQT4qf6JoLHWuj/A==", "license": "MIT" }, + "node_modules/vue-tel-input": { + "version": "9.7.1", + "resolved": "https://registry.npmjs.org/vue-tel-input/-/vue-tel-input-9.7.1.tgz", + "integrity": "sha512-duDKNzEPdNEpWEHgRvjDlfP/ZOT+qys9gN/J3/riwjDLYGW7beV8IuVVCY7vfLPVlgM6DijtT0fnShXTYkIF+w==", + "license": "MIT", + "peerDependencies": { + "libphonenumber-js": "^1.12.12", + "vue": "^3.5.4" + } + }, "node_modules/vue-tsc": { "version": "2.2.12", "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", diff --git a/package.json b/package.json index dfd97e9..529d9f6 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,18 @@ "vue3-radial-progress": "^1.1.1" }, "dependencies": { + "@fullcalendar/core": "^6.1.20", + "@fullcalendar/daygrid": "^6.1.20", + "@fullcalendar/interaction": "^6.1.20", + "@fullcalendar/list": "^6.1.20", + "@fullcalendar/timegrid": "^6.1.20", + "@fullcalendar/vue3": "^6.1.20", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.2.0", - "@tailwindcss/typography": "^0.5.16", "@inertiajs/vue3": "^2.0.0", + "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.1", + "@videojs/themes": "^1.0.1", "@vitejs/plugin-vue": "^5.2.1", "@vueuse/core": "^12.8.2", "class-variance-authority": "^0.7.1", @@ -42,9 +49,11 @@ "tailwindcss": "^4.1.1", "tw-animate-css": "^1.2.5", "typescript": "^5.2.2", + "video.js": "^8.23.6", "vite": "^6.3.4", "vue": "^3.5.13", "vue-sonner": "^1.3.2", + "vue-tel-input": "^9.7.1", "ziggy-js": "^2.4.2" }, "optionalDependencies": { @@ -52,4 +61,4 @@ "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", "lightningcss-linux-x64-gnu": "^1.29.1" } -} \ No newline at end of file +} diff --git a/public/img/GI_Logo_horizontal_white.png b/public/img/GI_Logo_horizontal_white.png new file mode 100644 index 0000000..3135bb5 Binary files /dev/null and b/public/img/GI_Logo_horizontal_white.png differ diff --git a/public/img/grey-trader-factory.png b/public/img/grey-trader-factory.png new file mode 100644 index 0000000..3135bb5 Binary files /dev/null and b/public/img/grey-trader-factory.png differ diff --git a/public/img/img.jpg b/public/img/img.jpg new file mode 100644 index 0000000..da2b25e Binary files /dev/null and b/public/img/img.jpg differ diff --git a/resources/css/app.css b/resources/css/app.css index ded4818..7772dfe 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -4,6 +4,12 @@ @import 'tw-animate-css'; +@import './tel-input-component.css'; + +@import './goals-responsive.css'; + +@import './magazine-slide-responsive.css'; + @plugin "@tailwindcss/typography"; @source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php'; @@ -124,25 +130,30 @@ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + --color-semi: var(--semi); + --color-light-success: var(--light-success); + --color-light-error: var(--light-error); + --color-dark-success: var(--dark-success); + --color-dark-error: var(--dark-error); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); --animate-accordion-down: accordion-down 0.2s ease-out; --animate-accordion-up: accordion-up 0.2s ease-out; - @keyframes accordion-down { - from { - height: 0; + @keyframes accordion-down { + from { + height: 0; } - to { - height: var(--reka-accordion-content-height); + to { + height: var(--reka-accordion-content-height); } } - @keyframes accordion-up { - from { - height: var(--reka-accordion-content-height); + @keyframes accordion-up { + from { + height: var(--reka-accordion-content-height); } - to { - height: 0; + to { + height: 0; } } } @@ -178,6 +189,7 @@ } :root { + --semi: #f9f9f9; --background: hsl(0 0% 100%); --foreground: hsl(0 0% 3.9%); --card: hsl(0 0% 100%); @@ -228,6 +240,12 @@ } .dark { + --light-success: #d1fadf; + --light-error: #fee2e2; + --dark-success: #16ad4a; + --dark-error: #c91313; + --semi: #1f1e1e; + --semi: #1f1e1e; --background: hsl(0 0% 10%); --foreground: hsl(0 0% 98%); --card: hsl(0 0% 3.9%); diff --git a/resources/css/goals-responsive.css b/resources/css/goals-responsive.css new file mode 100644 index 0000000..c98a0cf --- /dev/null +++ b/resources/css/goals-responsive.css @@ -0,0 +1,57 @@ +/* Responsive styles for Goals Show page */ + +/* Mobile-first responsive fixes */ +@media (max-width: 640px) { + .goals-show-container { + padding: 0.75rem; + } + + .goals-card { + min-width: auto !important; + max-width: 100%; + width: 100%; + } + + .goals-card-title { + font-size: 1.5rem !important; + line-height: 2rem !important; + } + + .goals-card-content { + padding: 1rem; + } + + .goals-card-content ul { + padding-left: 1.25rem; + margin: 0; + } + + .goals-card-content li { + font-size: 0.95rem; + line-height: 1.5; + margin-bottom: 0.5rem; + } +} + +/* Tablet adjustments */ +@media (min-width: 641px) and (max-width: 1024px) { + .goals-show-container { + padding: 1.5rem; + } + + .goals-card { + max-width: 42rem; + width: 90%; + } + + .goals-card-title { + font-size: 2rem !important; + } +} + +/* Desktop - maintain original styling */ +@media (min-width: 1025px) { + .goals-card { + min-width: 32rem; + } +} diff --git a/resources/css/magazine-slide-responsive.css b/resources/css/magazine-slide-responsive.css new file mode 100644 index 0000000..58ac9cb --- /dev/null +++ b/resources/css/magazine-slide-responsive.css @@ -0,0 +1,85 @@ +/* Responsive styles for Magazine Slide page */ + +/* Mobile-first responsive fixes */ +@media (max-width: 640px) { + .slide-container { + padding: 1rem !important; + padding-top: 4rem !important; + min-height: auto !important; + } + + .slide-header { + padding: 1.5rem 1rem !important; + padding-top: 1rem !important; + } + + .slide-title { + font-size: 1.25rem !important; + line-height: 1.75rem !important; + } + + .slide-description { + margin-top: 1rem !important; + font-size: 0.95rem; + } + + .slide-items-container { + flex-direction: column !important; + gap: 1rem; + margin-top: 1.5rem; + } + + .slide-item { + height: auto !important; + min-height: 12rem; + padding: 1rem !important; + } + + .slide-item-title { + font-size: 1.125rem !important; + margin-bottom: 0.5rem; + } + + .slide-item-description { + font-size: 0.9rem; + line-height: 1.5; + -webkit-line-clamp: 4 !important; + } +} + +/* Tablet adjustments */ +@media (min-width: 641px) and (max-width: 1024px) { + padding-top: 3rem !important; +} + +.slide-header { + padding: 2rem 1.5rem !important; + padding-top: + .slide-header { + padding: 2rem 1.5rem !important; + } + + .slide-title { + font-size: 1.5rem !important; + } + + .slide-items-container { + gap: 1rem; + } + + .slide-item { + height: auto !important; + min-height: 16rem; + } + + .slide-item-description { + -webkit-line-clamp: 4 !important; + } +} + +/* Desktop - maintain original styling */ +@media (min-width: 1025px) { + .slide-container { + padding: 6rem; + } +} diff --git a/resources/css/tel-input-component.css b/resources/css/tel-input-component.css index 7608363..00b151f 100644 --- a/resources/css/tel-input-component.css +++ b/resources/css/tel-input-component.css @@ -1,42 +1,95 @@ .vti__dropdown { - @apply relative flex cursor-pointer flex-col justify-center hover:dark:bg-gray-800; + @apply relative flex cursor-pointer flex-col justify-center; + min-width: 80px; + height: 100%; } .vti__dropdown.show { - @apply overflow-scroll; + @apply overflow-visible; } .vti__dropdown.open { - @apply dark:bg-gray-800; + background-color: transparent !important; +} + +/* Mobile dropdown adjustments */ +@media (max-width: 640px) { + .vti__dropdown { + min-width: 70px; + } } .vti__dropdown.disabled { - @apply cursor-no-drop bg-gray-100 outline-0 dark:bg-gray-600; + @apply cursor-not-allowed opacity-50; } .vti__selection { @apply flex items-center justify-center; + height: 100%; } .vti__selection .vti__country-code { - @apply bg-gray-600; + @apply dark:text-gray-200; } .vti__flag { - @apply mx-3; + @apply mx-2; +} + +/* RTL Support */ +[dir='rtl'] .vti__flag { + @apply mr-0 ml-2; +} + +[dir='rtl'] .vti__dropdown-item .vti__flag { + margin-right: 0; + margin-left: 5px; } .vti__dropdown-list { - @apply absolute z-10 m-0 max-h-48 w-96 overflow-y-scroll rounded-md border border-gray-300 bg-white p-0 text-left shadow-2xl dark:border-gray-700 dark:bg-gray-800; + @apply absolute z-50 m-0 max-h-60 w-80 overflow-y-auto rounded-md border border-gray-300 bg-white p-0 text-left shadow-2xl dark:border-gray-700 dark:bg-[#262626]; +} + +/* Mobile responsive dropdown */ +@media (max-width: 640px) { + .vti__dropdown-list { + @apply max-h-72; + width: calc(100vw - 2rem); + max-width: 400px; + left: 0; + right: auto; + } + + [dir='rtl'] .vti__dropdown-list { + left: auto; + right: 0; + } +} + +/* RTL Dropdown List */ +[dir='rtl'] .vti__dropdown-list { + @apply text-right; } .vti__dropdown-list.below { - top: 33px; + top: 100%; + margin-top: 4px; } .vti__dropdown-list.above { top: auto; bottom: 100%; + margin-bottom: 4px; +} + +@media (max-width: 640px) { + .vti__dropdown-list.below { + margin-top: 2px; + } + + .vti__dropdown-list.above { + margin-bottom: 2px; + } } .vti__dropdown-arrow { @@ -45,15 +98,155 @@ color: #666; } +/* Dark mode arrow */ +.dark .vti__dropdown-arrow { + @apply text-gray-400; +} + .vti__dropdown-item { - @apply hover:bg-primary-600 cursor-pointer p-1 text-sm hover:text-white; + @apply cursor-pointer p-2 text-sm hover:text-white dark:text-gray-200; + display: flex; + align-items: center; + gap: 8px; + &:hover { + background-color: #eb6600; + } +} + +/* Mobile responsive dropdown items - larger touch targets */ +@media (max-width: 640px) { + .vti__dropdown-item { + @apply p-3; + font-size: 15px; + line-height: 1.4; + } } .vti__dropdown-item.last-preferred { border-bottom: 1px solid #cacaca; } +/* Dark mode preferred border */ +.dark .vti__dropdown-item.last-preferred { + @apply border-gray-600; +} + .vti__dropdown-item .vti__flag { display: inline-block; - margin-right: 5px; + flex-shrink: 0; + width: 20px; + height: 15px; +} + +@media (max-width: 640px) { + .vti__dropdown-item .vti__flag { + width: 24px; + height: 18px; + } +} + +[dir='rtl'] .vti__dropdown-item .vti__flag { + margin-right: 0; + margin-left: 0; +} + +.vti__dropdown-item.highlighted { + @apply text-white; + background-color: #eb6600 !important; +} + +/* Input field styling for dark mode */ +.vti__input { + @apply text-foreground placeholder:text-muted-foreground w-full border-0 bg-transparent px-3 py-1 text-base focus:outline-none md:text-sm; + height: 100%; + direction: ltr; + text-align: left; +} + +/* Mobile responsive input - larger touch target */ +@media (max-width: 640px) { + .vti__input { + font-size: 16px; /* Prevents iOS zoom on focus */ + } +} + +/* Disabled input */ +.vti__input:disabled { + @apply cursor-not-allowed opacity-50; +} + +.vue-tel-input.disabled { + @apply pointer-events-none cursor-not-allowed opacity-50; +} + +/* Main wrapper styling */ +.vue-tel-input { + @apply relative flex h-9 w-full items-center rounded-md bg-transparent shadow-xs transition-[color,box-shadow]; + overflow: visible; + border: 1px solid #e5e5e5; +} + +.dark .vue-tel-input { + @apply bg-input/30; + border-color: #3f3f46; +} + +.vue-tel-input:focus-within { + @apply ring-ring/50 ring-[3px]; + border-color: #e5e5e5; +} + +@media (max-width: 640px) { + .vue-tel-input { + @apply text-base; + } +} + +/* Search input in dropdown */ +.vti__search_box { + @apply w-full border-b border-gray-300 px-3 py-2 text-sm focus:outline-none dark:border-gray-700 dark:bg-[#262626] dark:text-gray-100; +} + +@media (max-width: 640px) { + .vti__search_box { + @apply py-3 text-base; + } +} + +/* Country name text */ +.vti__dropdown-item .vti__country-name { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 4px; +} + +[dir='rtl'] .vti__dropdown-item .vti__country-name { + padding-right: 0; + padding-left: 4px; +} + +@media (max-width: 640px) { + .vti__dropdown-item .vti__country-name { + white-space: normal; + word-wrap: break-word; + overflow-wrap: break-word; + line-height: 1.3; + } +} + +/* Dial code in dropdown */ +.vti__dropdown-item .vti__dial-code { + @apply text-gray-500 dark:text-gray-400; + flex-shrink: 0; + font-size: 0.875rem; +} + +[dir='rtl'] .vti__dropdown-item .vti__dial-code { + margin-left: 0; +} + +.vue-tel-input.has-error { + @apply border-destructive ring-destructive/20; } diff --git a/resources/img/img.jpg:Zone.Identifier b/resources/img/img.jpg:Zone.Identifier new file mode 100644 index 0000000..d6c1ec6 Binary files /dev/null and b/resources/img/img.jpg:Zone.Identifier differ diff --git a/resources/js/app.ts b/resources/js/app.ts index 6f70b77..67c7232 100644 --- a/resources/js/app.ts +++ b/resources/js/app.ts @@ -6,6 +6,8 @@ import type { DefineComponent } from 'vue'; import { createApp, h } from 'vue'; import { ZiggyVue } from 'ziggy-js'; import { initializeTheme } from './composables/useAppearance'; +import VueTelInput from 'vue-tel-input'; +import 'vue-tel-input/vue-tel-input.css'; // Extend ImportMeta interface for Vite... declare module 'vite/client' { @@ -29,6 +31,7 @@ createInertiaApp({ createApp({ render: () => h(App, props) }) .use(plugin) .use(ZiggyVue) + .use(VueTelInput) .mount(el); }, progress: { diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue index 900df2a..ee1cdf5 100644 --- a/resources/js/components/AppSidebar.vue +++ b/resources/js/components/AppSidebar.vue @@ -3,12 +3,11 @@ import NavFooter from '@/components/NavFooter.vue'; import NavMain from '@/components/NavMain.vue'; import NavUser from '@/components/NavUser.vue'; import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar'; +import { $trans } from '@/lib/translator'; import { type NavItem } from '@/types'; import { Link } from '@inertiajs/vue3'; -import { ExternalLink, LayoutGrid } from 'lucide-vue-next'; +import { Book, BookOpen, ExternalLink, Heart, Key, LayoutGrid, User } from 'lucide-vue-next'; import AppLogo from './AppLogo.vue'; -import { $trans } from '@/lib/translator'; - const mainNavItems: NavItem[] = [ { @@ -16,17 +15,27 @@ const mainNavItems: NavItem[] = [ href: '/dashboard', icon: LayoutGrid, }, + { + title: $trans('My Profile'), + href: '/settings/profile', + icon: User, + }, + { + title: $trans('My Courses'), + href: '/settings/courses', + icon: BookOpen, + }, + { + title: $trans('My Books'), + href: '/settings/books', + icon: Book, + }, ]; const footerNavItems: NavItem[] = [ { - title: 'Caveo Brokerage', - href: 'https://caveo.com.kw', - icon: ExternalLink, - }, - { - title: 'ASCIISD', - href: 'https://asciisd.com', + title: $trans('Go To Home Page'), + href: '/', icon: ExternalLink, }, ]; diff --git a/resources/js/components/AppearanceTabs.vue b/resources/js/components/AppearanceTabs.vue index 43578fb..8c2e2e6 100644 --- a/resources/js/components/AppearanceTabs.vue +++ b/resources/js/components/AppearanceTabs.vue @@ -5,9 +5,9 @@ import { Monitor, Moon, Sun } from 'lucide-vue-next'; const { appearance, updateAppearance } = useAppearance(); const tabs = [ - { value: 'light', Icon: Sun, label: 'Light' }, - { value: 'dark', Icon: Moon, label: 'Dark' }, - { value: 'system', Icon: Monitor, label: 'System' }, + { value: 'light', Icon: Sun, label: 'فاتح' }, + { value: 'dark', Icon: Moon, label: 'داكن' }, + { value: 'system', Icon: Monitor, label: 'النظام' }, ] as const; @@ -25,7 +25,7 @@ const tabs = [ ]" > - {{ label }} + {{ label }} diff --git a/resources/js/components/ApplicationLogo.vue b/resources/js/components/ApplicationLogo.vue new file mode 100644 index 0000000..4cd92aa --- /dev/null +++ b/resources/js/components/ApplicationLogo.vue @@ -0,0 +1,3 @@ + diff --git a/resources/js/components/ApplicationLogoLight.vue b/resources/js/components/ApplicationLogoLight.vue new file mode 100644 index 0000000..02d5ddb --- /dev/null +++ b/resources/js/components/ApplicationLogoLight.vue @@ -0,0 +1,3 @@ + diff --git a/resources/js/components/ApplicationMark.vue b/resources/js/components/ApplicationMark.vue new file mode 100644 index 0000000..ec7e8e0 --- /dev/null +++ b/resources/js/components/ApplicationMark.vue @@ -0,0 +1,3 @@ + diff --git a/resources/js/components/Banner.vue b/resources/js/components/Banner.vue new file mode 100644 index 0000000..ff40753 --- /dev/null +++ b/resources/js/components/Banner.vue @@ -0,0 +1,88 @@ + + + diff --git a/resources/js/components/DeleteUser.vue b/resources/js/components/DeleteUser.vue index 550610c..5fce4d5 100644 --- a/resources/js/components/DeleteUser.vue +++ b/resources/js/components/DeleteUser.vue @@ -44,39 +44,38 @@ const closeModal = () => { diff --git a/resources/js/components/VideoPlayer.vue b/resources/js/components/VideoPlayer.vue new file mode 100644 index 0000000..bb213f3 --- /dev/null +++ b/resources/js/components/VideoPlayer.vue @@ -0,0 +1,37 @@ + + + diff --git a/resources/js/components/buttons/PrimaryButton.vue b/resources/js/components/buttons/PrimaryButton.vue new file mode 100644 index 0000000..c726dd5 --- /dev/null +++ b/resources/js/components/buttons/PrimaryButton.vue @@ -0,0 +1,19 @@ + + + diff --git a/resources/js/components/buttons/PrimaryLinkButton.vue b/resources/js/components/buttons/PrimaryLinkButton.vue new file mode 100644 index 0000000..d9866a4 --- /dev/null +++ b/resources/js/components/buttons/PrimaryLinkButton.vue @@ -0,0 +1,26 @@ + + + diff --git a/resources/js/components/buttons/ProgressButton.vue b/resources/js/components/buttons/ProgressButton.vue new file mode 100644 index 0000000..1454e7e --- /dev/null +++ b/resources/js/components/buttons/ProgressButton.vue @@ -0,0 +1,559 @@ + + + + + + diff --git a/resources/js/components/buttons/SecondaryLinkButton.vue b/resources/js/components/buttons/SecondaryLinkButton.vue new file mode 100644 index 0000000..aa72beb --- /dev/null +++ b/resources/js/components/buttons/SecondaryLinkButton.vue @@ -0,0 +1,25 @@ + + + diff --git a/resources/js/components/footers/FooterSocialLinksOnly.vue b/resources/js/components/footers/FooterSocialLinksOnly.vue new file mode 100644 index 0000000..746c73b --- /dev/null +++ b/resources/js/components/footers/FooterSocialLinksOnly.vue @@ -0,0 +1,99 @@ + + + diff --git a/resources/js/components/menu/BackMenu.vue b/resources/js/components/menu/BackMenu.vue new file mode 100644 index 0000000..dfeb768 --- /dev/null +++ b/resources/js/components/menu/BackMenu.vue @@ -0,0 +1,26 @@ + + + diff --git a/resources/js/components/menu/ExitMenu.vue b/resources/js/components/menu/ExitMenu.vue new file mode 100644 index 0000000..1937ccc --- /dev/null +++ b/resources/js/components/menu/ExitMenu.vue @@ -0,0 +1,23 @@ + + + diff --git a/resources/js/components/menu/Main.vue b/resources/js/components/menu/Main.vue new file mode 100644 index 0000000..9fc694e --- /dev/null +++ b/resources/js/components/menu/Main.vue @@ -0,0 +1,147 @@ + + + diff --git a/resources/js/components/modals/CenteredWithWideButtons.vue b/resources/js/components/modals/CenteredWithWideButtons.vue new file mode 100644 index 0000000..546a8be --- /dev/null +++ b/resources/js/components/modals/CenteredWithWideButtons.vue @@ -0,0 +1,95 @@ + + + + diff --git a/resources/js/components/modals/RatingModal.vue b/resources/js/components/modals/RatingModal.vue new file mode 100644 index 0000000..a838242 --- /dev/null +++ b/resources/js/components/modals/RatingModal.vue @@ -0,0 +1,196 @@ + + + + diff --git a/resources/js/components/modals/VideoModal.vue b/resources/js/components/modals/VideoModal.vue new file mode 100644 index 0000000..77652d6 --- /dev/null +++ b/resources/js/components/modals/VideoModal.vue @@ -0,0 +1,52 @@ + + + + + + diff --git a/resources/js/components/pagination/MagazinePagination.vue b/resources/js/components/pagination/MagazinePagination.vue new file mode 100644 index 0000000..44c3c01 --- /dev/null +++ b/resources/js/components/pagination/MagazinePagination.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/resources/js/components/ui/accordion/AccordionTrigger.vue b/resources/js/components/ui/accordion/AccordionTrigger.vue index f6cbea8..5b78d50 100644 --- a/resources/js/components/ui/accordion/AccordionTrigger.vue +++ b/resources/js/components/ui/accordion/AccordionTrigger.vue @@ -29,7 +29,7 @@ const delegatedProps = reactiveOmit(props, 'class') diff --git a/resources/js/components/ui/sidebar/SidebarInset.vue b/resources/js/components/ui/sidebar/SidebarInset.vue index 386c622..675697f 100644 --- a/resources/js/components/ui/sidebar/SidebarInset.vue +++ b/resources/js/components/ui/sidebar/SidebarInset.vue @@ -12,7 +12,7 @@ const props = defineProps<{ data-slot="sidebar-inset" :class="cn( 'bg-background relative flex w-full flex-1 flex-col', - 'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2', + 'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ms-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2', props.class, )" > diff --git a/resources/js/layouts/AppLayout.vue b/resources/js/layouts/AppLayout.vue index 4409628..fe93d7b 100644 --- a/resources/js/layouts/AppLayout.vue +++ b/resources/js/layouts/AppLayout.vue @@ -1,10 +1,11 @@ diff --git a/resources/js/layouts/BoxesSlideLayout.vue b/resources/js/layouts/BoxesSlideLayout.vue new file mode 100644 index 0000000..b844465 --- /dev/null +++ b/resources/js/layouts/BoxesSlideLayout.vue @@ -0,0 +1,84 @@ + + + diff --git a/resources/js/layouts/CenterContentScreenLayout.vue b/resources/js/layouts/CenterContentScreenLayout.vue new file mode 100644 index 0000000..063bc01 --- /dev/null +++ b/resources/js/layouts/CenterContentScreenLayout.vue @@ -0,0 +1,94 @@ + + + diff --git a/resources/js/layouts/FinalSlideLayout.vue b/resources/js/layouts/FinalSlideLayout.vue new file mode 100644 index 0000000..2809f2c --- /dev/null +++ b/resources/js/layouts/FinalSlideLayout.vue @@ -0,0 +1,19 @@ + + + diff --git a/resources/js/layouts/FullBackgroundScreenLayout.vue b/resources/js/layouts/FullBackgroundScreenLayout.vue new file mode 100644 index 0000000..6c0d3fe --- /dev/null +++ b/resources/js/layouts/FullBackgroundScreenLayout.vue @@ -0,0 +1,15 @@ + + + diff --git a/resources/js/layouts/GuestLayout.vue b/resources/js/layouts/GuestLayout.vue index 63303a8..7729a0f 100644 --- a/resources/js/layouts/GuestLayout.vue +++ b/resources/js/layouts/GuestLayout.vue @@ -1,21 +1,18 @@ diff --git a/resources/js/layouts/HalfScreenLayout.vue b/resources/js/layouts/HalfScreenLayout.vue new file mode 100644 index 0000000..1ee2c13 --- /dev/null +++ b/resources/js/layouts/HalfScreenLayout.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/layouts/LeftHalfScreenLayout.vue b/resources/js/layouts/LeftHalfScreenLayout.vue new file mode 100644 index 0000000..efc8c66 --- /dev/null +++ b/resources/js/layouts/LeftHalfScreenLayout.vue @@ -0,0 +1,28 @@ + + + diff --git a/resources/js/layouts/LessonLayout.vue b/resources/js/layouts/LessonLayout.vue new file mode 100644 index 0000000..8e2b47a --- /dev/null +++ b/resources/js/layouts/LessonLayout.vue @@ -0,0 +1,32 @@ + + + diff --git a/resources/js/layouts/PointsScreenLayout.vue b/resources/js/layouts/PointsScreenLayout.vue new file mode 100644 index 0000000..a45e63a --- /dev/null +++ b/resources/js/layouts/PointsScreenLayout.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/layouts/RightHalfScreenLayout.vue b/resources/js/layouts/RightHalfScreenLayout.vue new file mode 100644 index 0000000..703d7b0 --- /dev/null +++ b/resources/js/layouts/RightHalfScreenLayout.vue @@ -0,0 +1,29 @@ + + + diff --git a/resources/js/layouts/SectionLayout.vue b/resources/js/layouts/SectionLayout.vue new file mode 100644 index 0000000..35a4d75 --- /dev/null +++ b/resources/js/layouts/SectionLayout.vue @@ -0,0 +1,37 @@ + + + diff --git a/resources/js/layouts/TodoLayout.vue b/resources/js/layouts/TodoLayout.vue new file mode 100644 index 0000000..2708b62 --- /dev/null +++ b/resources/js/layouts/TodoLayout.vue @@ -0,0 +1,19 @@ + + + diff --git a/resources/js/layouts/settings/Layout.vue b/resources/js/layouts/settings/Layout.vue index 0f74bb7..0a9cb8e 100644 --- a/resources/js/layouts/settings/Layout.vue +++ b/resources/js/layouts/settings/Layout.vue @@ -7,15 +7,15 @@ import { Link, usePage } from '@inertiajs/vue3'; const sidebarNavItems: NavItem[] = [ { - title: 'Profile', + title: 'الملف الشخصي', href: '/settings/profile', }, { - title: 'Password', + title: 'كلمة المرور', href: '/settings/password', }, { - title: 'Appearance', + title: 'المظهر', href: '/settings/appearance', }, ]; @@ -27,7 +27,7 @@ const currentPath = page.props.ziggy?.location ? new URL(page.props.ziggy.locati