20 | */
21 | public function definition(): array
22 | {
23 | return [
24 | 'name' => fake()->name(),
25 | 'email' => fake()->unique()->safeEmail(),
26 | 'email_verified_at' => now(),
27 | 'password' => static::$password ??= Hash::make('password'),
28 | 'remember_token' => Str::random(10),
29 | ];
30 | }
31 |
32 | /**
33 | * Indicate that the model's email address should be unverified.
34 | */
35 | public function unverified(): static
36 | {
37 | return $this->state(fn (array $attributes) => [
38 | 'email_verified_at' => null,
39 | ]);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/config/view.php:
--------------------------------------------------------------------------------
1 | [
17 | resource_path('views'),
18 | ],
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Compiled View Path
23 | |--------------------------------------------------------------------------
24 | |
25 | | This option determines where all the compiled Blade templates will be
26 | | stored for your application. Typically, this is within the storage
27 | | directory. However, as usual, you are free to change this value.
28 | |
29 | */
30 |
31 | 'compiled' => env(
32 | 'VIEW_COMPILED_PATH',
33 | realpath(storage_path('framework/views'))
34 | ),
35 |
36 | ];
37 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | tests/Unit
10 |
11 |
12 | tests/Feature
13 |
14 |
15 |
16 |
17 | app
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | by($request->user()?->id ?: $request->ip());
29 | });
30 |
31 | $this->routes(function () {
32 | Route::middleware('api')
33 | ->prefix('api')
34 | ->group(base_path('routes/api.php'));
35 |
36 | Route::middleware('web')
37 | ->group(base_path('routes/web.php'));
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordConfirmationTest.php:
--------------------------------------------------------------------------------
1 | create();
16 |
17 | $response = $this->actingAs($user)->get('/confirm-password');
18 |
19 | $response->assertStatus(200);
20 | }
21 |
22 | public function test_password_can_be_confirmed(): void
23 | {
24 | $user = User::factory()->create();
25 |
26 | $response = $this->actingAs($user)->post('/confirm-password', [
27 | 'password' => 'password',
28 | ]);
29 |
30 | $response->assertRedirect();
31 | $response->assertSessionHasNoErrors();
32 | }
33 |
34 | public function test_password_is_not_confirmed_with_invalid_password(): void
35 | {
36 | $user = User::factory()->create();
37 |
38 | $response = $this->actingAs($user)->post('/confirm-password', [
39 | 'password' => 'wrong-password',
40 | ]);
41 |
42 | $response->assertSessionHasErrors();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ConfirmablePasswordController.php:
--------------------------------------------------------------------------------
1 | validate([
30 | 'email' => $request->user()->email,
31 | 'password' => $request->password,
32 | ])) {
33 | throw ValidationException::withMessages([
34 | 'password' => __('auth.password'),
35 | ]);
36 | }
37 |
38 | $request->session()->put('auth.password_confirmed_at', time());
39 |
40 | return redirect()->intended(RouteServiceProvider::HOME);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_URL=http://localhost
6 |
7 | LOG_CHANNEL=stack
8 | LOG_DEPRECATIONS_CHANNEL=null
9 | LOG_LEVEL=debug
10 |
11 | DB_CONNECTION=mysql
12 | DB_HOST=127.0.0.1
13 | DB_PORT=3306
14 | DB_DATABASE=laravel
15 | DB_USERNAME=root
16 | DB_PASSWORD=
17 |
18 | BROADCAST_DRIVER=log
19 | CACHE_DRIVER=file
20 | FILESYSTEM_DISK=local
21 | QUEUE_CONNECTION=sync
22 | SESSION_DRIVER=file
23 | SESSION_LIFETIME=120
24 |
25 | MEMCACHED_HOST=127.0.0.1
26 |
27 | REDIS_HOST=127.0.0.1
28 | REDIS_PASSWORD=null
29 | REDIS_PORT=6379
30 |
31 | MAIL_MAILER=smtp
32 | MAIL_HOST=mailpit
33 | MAIL_PORT=1025
34 | MAIL_USERNAME=null
35 | MAIL_PASSWORD=null
36 | MAIL_ENCRYPTION=null
37 | MAIL_FROM_ADDRESS="hello@example.com"
38 | MAIL_FROM_NAME="${APP_NAME}"
39 |
40 | AWS_ACCESS_KEY_ID=
41 | AWS_SECRET_ACCESS_KEY=
42 | AWS_DEFAULT_REGION=us-east-1
43 | AWS_BUCKET=
44 | AWS_USE_PATH_STYLE_ENDPOINT=false
45 |
46 | PUSHER_APP_ID=
47 | PUSHER_APP_KEY=
48 | PUSHER_APP_SECRET=
49 | PUSHER_HOST=
50 | PUSHER_PORT=443
51 | PUSHER_SCHEME=https
52 | PUSHER_APP_CLUSTER=mt1
53 |
54 | VITE_APP_NAME="${APP_NAME}"
55 | VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
56 | VITE_PUSHER_HOST="${PUSHER_HOST}"
57 | VITE_PUSHER_PORT="${PUSHER_PORT}"
58 | VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
59 | VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
60 |
--------------------------------------------------------------------------------
/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * We'll load the axios HTTP library which allows us to easily issue requests
3 | * to our Laravel back-end. This library automatically handles sending the
4 | * CSRF token as a header based on the value of the "XSRF" token cookie.
5 | */
6 |
7 | import axios from 'axios';
8 | window.axios = axios;
9 |
10 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
11 |
12 | /**
13 | * Echo exposes an expressive API for subscribing to channels and listening
14 | * for events that are broadcast by Laravel. Echo and event broadcasting
15 | * allows your team to easily build robust real-time web applications.
16 | */
17 |
18 | // import Echo from 'laravel-echo';
19 |
20 | // import Pusher from 'pusher-js';
21 | // window.Pusher = Pusher;
22 |
23 | // window.Echo = new Echo({
24 | // broadcaster: 'pusher',
25 | // key: import.meta.env.VITE_PUSHER_APP_KEY,
26 | // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
27 | // wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
28 | // wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
29 | // wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
30 | // forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
31 | // enabledTransports: ['ws', 'wss'],
32 | // });
33 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/AuthenticatedSessionController.php:
--------------------------------------------------------------------------------
1 | Route::has('password.request'),
24 | 'status' => session('status'),
25 | ]);
26 | }
27 |
28 | /**
29 | * Handle an incoming authentication request.
30 | */
31 | public function store(LoginRequest $request): RedirectResponse
32 | {
33 | $request->authenticate();
34 |
35 | $request->session()->regenerate();
36 |
37 | return redirect()->intended(RouteServiceProvider::HOME);
38 | }
39 |
40 | /**
41 | * Destroy an authenticated session.
42 | */
43 | public function destroy(Request $request): RedirectResponse
44 | {
45 | Auth::guard('web')->logout();
46 |
47 | $request->session()->invalidate();
48 |
49 | $request->session()->regenerateToken();
50 |
51 | return redirect('/');
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/AuthenticationTest.php:
--------------------------------------------------------------------------------
1 | get('/login');
17 |
18 | $response->assertStatus(200);
19 | }
20 |
21 | public function test_users_can_authenticate_using_the_login_screen(): void
22 | {
23 | $user = User::factory()->create();
24 |
25 | $response = $this->post('/login', [
26 | 'email' => $user->email,
27 | 'password' => 'password',
28 | ]);
29 |
30 | $this->assertAuthenticated();
31 | $response->assertRedirect(RouteServiceProvider::HOME);
32 | }
33 |
34 | public function test_users_can_not_authenticate_with_invalid_password(): void
35 | {
36 | $user = User::factory()->create();
37 |
38 | $this->post('/login', [
39 | 'email' => $user->email,
40 | 'password' => 'wrong-password',
41 | ]);
42 |
43 | $this->assertGuest();
44 | }
45 |
46 | public function test_users_can_logout(): void
47 | {
48 | $user = User::factory()->create();
49 |
50 | $response = $this->actingAs($user)->post('/logout');
51 |
52 | $this->assertGuest();
53 | $response->assertRedirect('/');
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/RegisteredUserController.php:
--------------------------------------------------------------------------------
1 | validate([
35 | 'name' => 'required|string|max:255',
36 | 'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
37 | 'password' => ['required', 'confirmed', Rules\Password::defaults()],
38 | ]);
39 |
40 | $user = User::create([
41 | 'name' => $request->name,
42 | 'email' => $request->email,
43 | 'password' => Hash::make($request->password),
44 | ]);
45 |
46 | event(new Registered($user));
47 |
48 | Auth::login($user);
49 |
50 | return redirect(RouteServiceProvider::HOME);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordUpdateTest.php:
--------------------------------------------------------------------------------
1 | create();
17 |
18 | $response = $this
19 | ->actingAs($user)
20 | ->from('/profile')
21 | ->put('/password', [
22 | 'current_password' => 'password',
23 | 'password' => 'new-password',
24 | 'password_confirmation' => 'new-password',
25 | ]);
26 |
27 | $response
28 | ->assertSessionHasNoErrors()
29 | ->assertRedirect('/profile');
30 |
31 | $this->assertTrue(Hash::check('new-password', $user->refresh()->password));
32 | }
33 |
34 | public function test_correct_password_must_be_provided_to_update_password(): void
35 | {
36 | $user = User::factory()->create();
37 |
38 | $response = $this
39 | ->actingAs($user)
40 | ->from('/profile')
41 | ->put('/password', [
42 | 'current_password' => 'wrong-password',
43 | 'password' => 'new-password',
44 | 'password_confirmation' => 'new-password',
45 | ]);
46 |
47 | $response
48 | ->assertSessionHasErrors('current_password')
49 | ->assertRedirect('/profile');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Edit.jsx:
--------------------------------------------------------------------------------
1 | import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
2 | import DeleteUserForm from './Partials/DeleteUserForm';
3 | import UpdatePasswordForm from './Partials/UpdatePasswordForm';
4 | import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm';
5 | import { Head } from '@inertiajs/react';
6 |
7 | export default function Edit({ auth, mustVerifyEmail, status }) {
8 | return (
9 | Profile}
12 | >
13 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/PasswordResetLinkController.php:
--------------------------------------------------------------------------------
1 | session('status'),
22 | ]);
23 | }
24 |
25 | /**
26 | * Handle an incoming password reset link request.
27 | *
28 | * @throws \Illuminate\Validation\ValidationException
29 | */
30 | public function store(Request $request): RedirectResponse
31 | {
32 | $request->validate([
33 | 'email' => 'required|email',
34 | ]);
35 |
36 | // We will send the password reset link to this user. Once we have attempted
37 | // to send the link, we will examine the response then see the message we
38 | // need to show to the user. Finally, we'll send out a proper response.
39 | $status = Password::sendResetLink(
40 | $request->only('email')
41 | );
42 |
43 | if ($status == Password::RESET_LINK_SENT) {
44 | return back()->with('status', __($status));
45 | }
46 |
47 | throw ValidationException::withMessages([
48 | 'email' => [trans($status)],
49 | ]);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | Route::has('login'),
23 | 'canRegister' => Route::has('register'),
24 | 'laravelVersion' => Application::VERSION,
25 | 'phpVersion' => PHP_VERSION,
26 | ]);
27 | });
28 |
29 | Route::get('/dashboard', function () {
30 | return Inertia::render('Dashboard');
31 | })->middleware(['auth', 'verified'])->name('dashboard');
32 |
33 | Route::middleware('auth')->group(function () {
34 | Route::get("/tv-streams", [StreamController::class, "tv"])->name("tv-streams");
35 | Route::get("/radio-streams", [StreamController::class, "radio"])->name("radio-streams");
36 |
37 | Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
38 | Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
39 | Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
40 | });
41 |
42 | require __DIR__.'/auth.php';
43 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ProfileController.php:
--------------------------------------------------------------------------------
1 | $request->user() instanceof MustVerifyEmail,
23 | 'status' => session('status'),
24 | ]);
25 | }
26 |
27 | /**
28 | * Update the user's profile information.
29 | */
30 | public function update(ProfileUpdateRequest $request): RedirectResponse
31 | {
32 | $request->user()->fill($request->validated());
33 |
34 | if ($request->user()->isDirty('email')) {
35 | $request->user()->email_verified_at = null;
36 | }
37 |
38 | $request->user()->save();
39 |
40 | return Redirect::route('profile.edit');
41 | }
42 |
43 | /**
44 | * Delete the user's account.
45 | */
46 | public function destroy(Request $request): RedirectResponse
47 | {
48 | $request->validate([
49 | 'password' => ['required', 'current_password'],
50 | ]);
51 |
52 | $user = $request->user();
53 |
54 | Auth::logout();
55 |
56 | $user->delete();
57 |
58 | $request->session()->invalidate();
59 | $request->session()->regenerateToken();
60 |
61 | return Redirect::to('/');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/resources/js/Components/TVStreamItem.jsx:
--------------------------------------------------------------------------------
1 | import Hls from 'hls.js';
2 | import { useState } from 'react';
3 |
4 | const TVStreamItem = (props) => {
5 | const [isError, setIsError] = useState(false)
6 |
7 | const element = (
8 | <>
9 |
10 |
11 |
{props.channel}
12 |
13 | >
14 | )
15 |
16 | if (Hls.isSupported()) {
17 | var video = document.getElementById(`${props.id}`);
18 | var videoContainer = document.getElementById("container");
19 |
20 | if (video != null) {
21 | var hls = new Hls();
22 | hls.on(Hls.Events.MEDIA_ATTACHED, function () {
23 | console.log('video and hls.js are now bound together !');
24 | });
25 | hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
26 | console.log(
27 | 'manifest loaded, found ' + data.levels.length + ' quality level',
28 | );
29 | });
30 | hls.on(Hls.Events.ERROR, function (event, data) {
31 | if (data.fatal) {
32 | switch (data.type) {
33 | case 1:
34 | break;
35 | default:
36 | videoContainer.innerHTML = ""
37 | hls.destroy();
38 | break;
39 | }
40 | }
41 | });
42 | hls.loadSource(props.url);
43 | // bind them together
44 | hls.attachMedia(video);
45 |
46 | video.play();
47 | }
48 | }
49 |
50 | return element
51 | }
52 |
53 | export default TVStreamItem
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | singleton(
30 | Illuminate\Contracts\Http\Kernel::class,
31 | App\Http\Kernel::class
32 | );
33 |
34 | $app->singleton(
35 | Illuminate\Contracts\Console\Kernel::class,
36 | App\Console\Kernel::class
37 | );
38 |
39 | $app->singleton(
40 | Illuminate\Contracts\Debug\ExceptionHandler::class,
41 | App\Exceptions\Handler::class
42 | );
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Return The Application
47 | |--------------------------------------------------------------------------
48 | |
49 | | This script returns the application instance. The instance is given to
50 | | the calling script so we can separate the building of the instances
51 | | from the actual running of the application and sending responses.
52 | |
53 | */
54 |
55 | return $app;
56 |
--------------------------------------------------------------------------------
/config/hashing.php:
--------------------------------------------------------------------------------
1 | 'bcrypt',
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Bcrypt Options
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may specify the configuration options that should be used when
26 | | passwords are hashed using the Bcrypt algorithm. This will allow you
27 | | to control the amount of time it takes to hash the given password.
28 | |
29 | */
30 |
31 | 'bcrypt' => [
32 | 'rounds' => env('BCRYPT_ROUNDS', 12),
33 | 'verify' => true,
34 | ],
35 |
36 | /*
37 | |--------------------------------------------------------------------------
38 | | Argon Options
39 | |--------------------------------------------------------------------------
40 | |
41 | | Here you may specify the configuration options that should be used when
42 | | passwords are hashed using the Argon algorithm. These will allow you
43 | | to control the amount of time it takes to hash the given password.
44 | |
45 | */
46 |
47 | 'argon' => [
48 | 'memory' => 65536,
49 | 'threads' => 1,
50 | 'time' => 4,
51 | 'verify' => true,
52 | ],
53 |
54 | ];
55 |
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(Illuminate\Contracts\Console\Kernel::class);
34 |
35 | $status = $kernel->handle(
36 | $input = new Symfony\Component\Console\Input\ArgvInput,
37 | new Symfony\Component\Console\Output\ConsoleOutput
38 | );
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | Shutdown The Application
43 | |--------------------------------------------------------------------------
44 | |
45 | | Once Artisan has finished running, we will fire off the shutdown events
46 | | so that any final work may be done by the application before we shut
47 | | down the process. This is the last thing to happen to the request.
48 | |
49 | */
50 |
51 | $kernel->terminate($input, $status);
52 |
53 | exit($status);
54 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/VerifyEmail.jsx:
--------------------------------------------------------------------------------
1 | import GuestLayout from '@/Layouts/GuestLayout';
2 | import PrimaryButton from '@/Components/PrimaryButton';
3 | import { Head, Link, useForm } from '@inertiajs/react';
4 |
5 | export default function VerifyEmail({ status }) {
6 | const { post, processing } = useForm({});
7 |
8 | const submit = (e) => {
9 | e.preventDefault();
10 |
11 | post(route('verification.send'));
12 | };
13 |
14 | return (
15 |
16 |
17 |
18 |
19 | Thanks for signing up! Before getting started, could you verify your email address by clicking on the
20 | link we just emailed to you? If you didn't receive the email, we will gladly send you another.
21 |
22 |
23 | {status === 'verification-link-sent' && (
24 |
25 | A new verification link has been sent to the email address you provided during registration.
26 |
27 | )}
28 |
29 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ForgotPassword.jsx:
--------------------------------------------------------------------------------
1 | import GuestLayout from '@/Layouts/GuestLayout';
2 | import InputError from '@/Components/InputError';
3 | import PrimaryButton from '@/Components/PrimaryButton';
4 | import TextInput from '@/Components/TextInput';
5 | import { Head, useForm } from '@inertiajs/react';
6 |
7 | export default function ForgotPassword({ status }) {
8 | const { data, setData, post, processing, errors } = useForm({
9 | email: '',
10 | });
11 |
12 | const submit = (e) => {
13 | e.preventDefault();
14 |
15 | post(route('password.email'));
16 | };
17 |
18 | return (
19 |
20 |
21 |
22 |
23 | Forgot your password? No problem. Just let us know your email address and we will email you a password
24 | reset link that will allow you to choose a new one.
25 |
26 |
27 | {status && {status}
}
28 |
29 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class);
50 |
51 | $response = $kernel->handle(
52 | $request = Request::capture()
53 | )->send();
54 |
55 | $kernel->terminate($request, $response);
56 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/EmailVerificationTest.php:
--------------------------------------------------------------------------------
1 | create([
20 | 'email_verified_at' => null,
21 | ]);
22 |
23 | $response = $this->actingAs($user)->get('/verify-email');
24 |
25 | $response->assertStatus(200);
26 | }
27 |
28 | public function test_email_can_be_verified(): void
29 | {
30 | $user = User::factory()->create([
31 | 'email_verified_at' => null,
32 | ]);
33 |
34 | Event::fake();
35 |
36 | $verificationUrl = URL::temporarySignedRoute(
37 | 'verification.verify',
38 | now()->addMinutes(60),
39 | ['id' => $user->id, 'hash' => sha1($user->email)]
40 | );
41 |
42 | $response = $this->actingAs($user)->get($verificationUrl);
43 |
44 | Event::assertDispatched(Verified::class);
45 | $this->assertTrue($user->fresh()->hasVerifiedEmail());
46 | $response->assertRedirect(RouteServiceProvider::HOME.'?verified=1');
47 | }
48 |
49 | public function test_email_is_not_verified_with_invalid_hash(): void
50 | {
51 | $user = User::factory()->create([
52 | 'email_verified_at' => null,
53 | ]);
54 |
55 | $verificationUrl = URL::temporarySignedRoute(
56 | 'verification.verify',
57 | now()->addMinutes(60),
58 | ['id' => $user->id, 'hash' => sha1('wrong-email')]
59 | );
60 |
61 | $this->actingAs($user)->get($verificationUrl);
62 |
63 | $this->assertFalse($user->fresh()->hasVerifiedEmail());
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ConfirmPassword.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import GuestLayout from '@/Layouts/GuestLayout';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import PrimaryButton from '@/Components/PrimaryButton';
6 | import TextInput from '@/Components/TextInput';
7 | import { Head, useForm } from '@inertiajs/react';
8 |
9 | export default function ConfirmPassword() {
10 | const { data, setData, post, processing, errors, reset } = useForm({
11 | password: '',
12 | });
13 |
14 | useEffect(() => {
15 | return () => {
16 | reset('password');
17 | };
18 | }, []);
19 |
20 | const submit = (e) => {
21 | e.preventDefault();
22 |
23 | post(route('password.confirm'));
24 | };
25 |
26 | return (
27 |
28 |
29 |
30 |
31 | This is a secure area of the application. Please confirm your password before continuing.
32 |
33 |
34 |
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordResetTest.php:
--------------------------------------------------------------------------------
1 | get('/forgot-password');
18 |
19 | $response->assertStatus(200);
20 | }
21 |
22 | public function test_reset_password_link_can_be_requested(): void
23 | {
24 | Notification::fake();
25 |
26 | $user = User::factory()->create();
27 |
28 | $this->post('/forgot-password', ['email' => $user->email]);
29 |
30 | Notification::assertSentTo($user, ResetPassword::class);
31 | }
32 |
33 | public function test_reset_password_screen_can_be_rendered(): void
34 | {
35 | Notification::fake();
36 |
37 | $user = User::factory()->create();
38 |
39 | $this->post('/forgot-password', ['email' => $user->email]);
40 |
41 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
42 | $response = $this->get('/reset-password/'.$notification->token);
43 |
44 | $response->assertStatus(200);
45 |
46 | return true;
47 | });
48 | }
49 |
50 | public function test_password_can_be_reset_with_valid_token(): void
51 | {
52 | Notification::fake();
53 |
54 | $user = User::factory()->create();
55 |
56 | $this->post('/forgot-password', ['email' => $user->email]);
57 |
58 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
59 | $response = $this->post('/reset-password', [
60 | 'token' => $notification->token,
61 | 'email' => $user->email,
62 | 'password' => 'password',
63 | 'password_confirmation' => 'password',
64 | ]);
65 |
66 | $response->assertSessionHasNoErrors();
67 |
68 | return true;
69 | });
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/laravel",
3 | "type": "project",
4 | "description": "The skeleton application for the Laravel framework.",
5 | "keywords": ["laravel", "framework"],
6 | "license": "MIT",
7 | "require": {
8 | "php": "^8.1",
9 | "guzzlehttp/guzzle": "^7.2",
10 | "inertiajs/inertia-laravel": "^0.6.3",
11 | "laravel/framework": "^10.10",
12 | "laravel/sanctum": "^3.2",
13 | "laravel/tinker": "^2.8",
14 | "tightenco/ziggy": "^1.0"
15 | },
16 | "require-dev": {
17 | "fakerphp/faker": "^1.9.1",
18 | "laravel/breeze": "^1.26",
19 | "laravel/pint": "^1.0",
20 | "laravel/sail": "^1.18",
21 | "mockery/mockery": "^1.4.4",
22 | "nunomaduro/collision": "^7.0",
23 | "phpunit/phpunit": "^10.1",
24 | "spatie/laravel-ignition": "^2.0"
25 | },
26 | "autoload": {
27 | "psr-4": {
28 | "App\\": "app/",
29 | "Database\\Factories\\": "database/factories/",
30 | "Database\\Seeders\\": "database/seeders/"
31 | }
32 | },
33 | "autoload-dev": {
34 | "psr-4": {
35 | "Tests\\": "tests/"
36 | }
37 | },
38 | "scripts": {
39 | "post-autoload-dump": [
40 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
41 | "@php artisan package:discover --ansi"
42 | ],
43 | "post-update-cmd": [
44 | "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
45 | ],
46 | "post-root-package-install": [
47 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
48 | ],
49 | "post-create-project-cmd": [
50 | "@php artisan key:generate --ansi"
51 | ]
52 | },
53 | "extra": {
54 | "laravel": {
55 | "dont-discover": []
56 | }
57 | },
58 | "config": {
59 | "optimize-autoloader": true,
60 | "preferred-install": "dist",
61 | "sort-packages": true,
62 | "allow-plugins": {
63 | "pestphp/pest-plugin": true,
64 | "php-http/discovery": true
65 | }
66 | },
67 | "minimum-stability": "stable",
68 | "prefer-stable": true
69 | }
70 |
--------------------------------------------------------------------------------
/resources/js/Components/Modal.jsx:
--------------------------------------------------------------------------------
1 | import { Fragment } from 'react';
2 | import { Dialog, Transition } from '@headlessui/react';
3 |
4 | export default function Modal({ children, show = false, maxWidth = '2xl', closeable = true, onClose = () => {} }) {
5 | const close = () => {
6 | if (closeable) {
7 | onClose();
8 | }
9 | };
10 |
11 | const maxWidthClass = {
12 | sm: 'sm:max-w-sm',
13 | md: 'sm:max-w-md',
14 | lg: 'sm:max-w-lg',
15 | xl: 'sm:max-w-xl',
16 | '2xl': 'sm:max-w-2xl',
17 | }[maxWidth];
18 |
19 | return (
20 |
21 |
27 |
36 |
37 |
38 |
39 |
48 |
51 | {children}
52 |
53 |
54 |
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/config/broadcasting.php:
--------------------------------------------------------------------------------
1 | env('BROADCAST_DRIVER', 'null'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Broadcast Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the broadcast connections that will be used
26 | | to broadcast events to other systems or over websockets. Samples of
27 | | each available type of connection are provided inside this array.
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'pusher' => [
34 | 'driver' => 'pusher',
35 | 'key' => env('PUSHER_APP_KEY'),
36 | 'secret' => env('PUSHER_APP_SECRET'),
37 | 'app_id' => env('PUSHER_APP_ID'),
38 | 'options' => [
39 | 'cluster' => env('PUSHER_APP_CLUSTER'),
40 | 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
41 | 'port' => env('PUSHER_PORT', 443),
42 | 'scheme' => env('PUSHER_SCHEME', 'https'),
43 | 'encrypted' => true,
44 | 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
45 | ],
46 | 'client_options' => [
47 | // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
48 | ],
49 | ],
50 |
51 | 'ably' => [
52 | 'driver' => 'ably',
53 | 'key' => env('ABLY_KEY'),
54 | ],
55 |
56 | 'redis' => [
57 | 'driver' => 'redis',
58 | 'connection' => 'default',
59 | ],
60 |
61 | 'log' => [
62 | 'driver' => 'log',
63 | ],
64 |
65 | 'null' => [
66 | 'driver' => 'null',
67 | ],
68 |
69 | ],
70 |
71 | ];
72 |
--------------------------------------------------------------------------------
/app/Http/Requests/Auth/LoginRequest.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | public function rules(): array
28 | {
29 | return [
30 | 'email' => ['required', 'string', 'email'],
31 | 'password' => ['required', 'string'],
32 | ];
33 | }
34 |
35 | /**
36 | * Attempt to authenticate the request's credentials.
37 | *
38 | * @throws \Illuminate\Validation\ValidationException
39 | */
40 | public function authenticate(): void
41 | {
42 | $this->ensureIsNotRateLimited();
43 |
44 | if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
45 | RateLimiter::hit($this->throttleKey());
46 |
47 | throw ValidationException::withMessages([
48 | 'email' => trans('auth.failed'),
49 | ]);
50 | }
51 |
52 | RateLimiter::clear($this->throttleKey());
53 | }
54 |
55 | /**
56 | * Ensure the login request is not rate limited.
57 | *
58 | * @throws \Illuminate\Validation\ValidationException
59 | */
60 | public function ensureIsNotRateLimited(): void
61 | {
62 | if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
63 | return;
64 | }
65 |
66 | event(new Lockout($this));
67 |
68 | $seconds = RateLimiter::availableIn($this->throttleKey());
69 |
70 | throw ValidationException::withMessages([
71 | 'email' => trans('auth.throttle', [
72 | 'seconds' => $seconds,
73 | 'minutes' => ceil($seconds / 60),
74 | ]),
75 | ]);
76 | }
77 |
78 | /**
79 | * Get the rate limiting throttle key for the request.
80 | */
81 | public function throttleKey(): string
82 | {
83 | return Str::transliterate(Str::lower($this->input('email')).'|'.$this->ip());
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/NewPasswordController.php:
--------------------------------------------------------------------------------
1 | $request->email,
26 | 'token' => $request->route('token'),
27 | ]);
28 | }
29 |
30 | /**
31 | * Handle an incoming new password request.
32 | *
33 | * @throws \Illuminate\Validation\ValidationException
34 | */
35 | public function store(Request $request): RedirectResponse
36 | {
37 | $request->validate([
38 | 'token' => 'required',
39 | 'email' => 'required|email',
40 | 'password' => ['required', 'confirmed', Rules\Password::defaults()],
41 | ]);
42 |
43 | // Here we will attempt to reset the user's password. If it is successful we
44 | // will update the password on an actual user model and persist it to the
45 | // database. Otherwise we will parse the error and return the response.
46 | $status = Password::reset(
47 | $request->only('email', 'password', 'password_confirmation', 'token'),
48 | function ($user) use ($request) {
49 | $user->forceFill([
50 | 'password' => Hash::make($request->password),
51 | 'remember_token' => Str::random(60),
52 | ])->save();
53 |
54 | event(new PasswordReset($user));
55 | }
56 | );
57 |
58 | // If the password was successfully reset, we will redirect the user back to
59 | // the application's home authenticated view. If there is an error we can
60 | // redirect them back to where they came from with their error message.
61 | if ($status == Password::PASSWORD_RESET) {
62 | return redirect()->route('login')->with('status', __($status));
63 | }
64 |
65 | throw ValidationException::withMessages([
66 | 'email' => [trans($status)],
67 | ]);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DISK', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Filesystem Disks
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure as many filesystem "disks" as you wish, and you
24 | | may even configure multiple disks of the same driver. Defaults have
25 | | been set up for each driver as an example of the required values.
26 | |
27 | | Supported Drivers: "local", "ftp", "sftp", "s3"
28 | |
29 | */
30 |
31 | 'disks' => [
32 |
33 | 'local' => [
34 | 'driver' => 'local',
35 | 'root' => storage_path('app'),
36 | 'throw' => false,
37 | ],
38 |
39 | 'public' => [
40 | 'driver' => 'local',
41 | 'root' => storage_path('app/public'),
42 | 'url' => env('APP_URL').'/storage',
43 | 'visibility' => 'public',
44 | 'throw' => false,
45 | ],
46 |
47 | 's3' => [
48 | 'driver' => 's3',
49 | 'key' => env('AWS_ACCESS_KEY_ID'),
50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
51 | 'region' => env('AWS_DEFAULT_REGION'),
52 | 'bucket' => env('AWS_BUCKET'),
53 | 'url' => env('AWS_URL'),
54 | 'endpoint' => env('AWS_ENDPOINT'),
55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
56 | 'throw' => false,
57 | ],
58 |
59 | ],
60 |
61 | /*
62 | |--------------------------------------------------------------------------
63 | | Symbolic Links
64 | |--------------------------------------------------------------------------
65 | |
66 | | Here you may configure the symbolic links that will be created when the
67 | | `storage:link` Artisan command is executed. The array keys should be
68 | | the locations of the links and the values should be their targets.
69 | |
70 | */
71 |
72 | 'links' => [
73 | public_path('storage') => storage_path('app/public'),
74 | ],
75 |
76 | ];
77 |
--------------------------------------------------------------------------------
/routes/auth.php:
--------------------------------------------------------------------------------
1 | group(function () {
15 | Route::get('register', [RegisteredUserController::class, 'create'])
16 | ->name('register');
17 |
18 | Route::post('register', [RegisteredUserController::class, 'store']);
19 |
20 | Route::get('login', [AuthenticatedSessionController::class, 'create'])
21 | ->name('login');
22 |
23 | Route::post('login', [AuthenticatedSessionController::class, 'store']);
24 |
25 | Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
26 | ->name('password.request');
27 |
28 | Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
29 | ->name('password.email');
30 |
31 | Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
32 | ->name('password.reset');
33 |
34 | Route::post('reset-password', [NewPasswordController::class, 'store'])
35 | ->name('password.store');
36 | });
37 |
38 | Route::middleware('auth')->group(function () {
39 | Route::get('verify-email', EmailVerificationPromptController::class)
40 | ->name('verification.notice');
41 |
42 | Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
43 | ->middleware(['signed', 'throttle:6,1'])
44 | ->name('verification.verify');
45 |
46 | Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
47 | ->middleware('throttle:6,1')
48 | ->name('verification.send');
49 |
50 | Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
51 | ->name('password.confirm');
52 |
53 | Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
54 |
55 | Route::put('password', [PasswordController::class, 'update'])->name('password.update');
56 |
57 | Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
58 | ->name('logout');
59 | });
60 |
--------------------------------------------------------------------------------
/tests/Feature/ProfileTest.php:
--------------------------------------------------------------------------------
1 | create();
16 |
17 | $response = $this
18 | ->actingAs($user)
19 | ->get('/profile');
20 |
21 | $response->assertOk();
22 | }
23 |
24 | public function test_profile_information_can_be_updated(): void
25 | {
26 | $user = User::factory()->create();
27 |
28 | $response = $this
29 | ->actingAs($user)
30 | ->patch('/profile', [
31 | 'name' => 'Test User',
32 | 'email' => 'test@example.com',
33 | ]);
34 |
35 | $response
36 | ->assertSessionHasNoErrors()
37 | ->assertRedirect('/profile');
38 |
39 | $user->refresh();
40 |
41 | $this->assertSame('Test User', $user->name);
42 | $this->assertSame('test@example.com', $user->email);
43 | $this->assertNull($user->email_verified_at);
44 | }
45 |
46 | public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged(): void
47 | {
48 | $user = User::factory()->create();
49 |
50 | $response = $this
51 | ->actingAs($user)
52 | ->patch('/profile', [
53 | 'name' => 'Test User',
54 | 'email' => $user->email,
55 | ]);
56 |
57 | $response
58 | ->assertSessionHasNoErrors()
59 | ->assertRedirect('/profile');
60 |
61 | $this->assertNotNull($user->refresh()->email_verified_at);
62 | }
63 |
64 | public function test_user_can_delete_their_account(): void
65 | {
66 | $user = User::factory()->create();
67 |
68 | $response = $this
69 | ->actingAs($user)
70 | ->delete('/profile', [
71 | 'password' => 'password',
72 | ]);
73 |
74 | $response
75 | ->assertSessionHasNoErrors()
76 | ->assertRedirect('/');
77 |
78 | $this->assertGuest();
79 | $this->assertNull($user->fresh());
80 | }
81 |
82 | public function test_correct_password_must_be_provided_to_delete_account(): void
83 | {
84 | $user = User::factory()->create();
85 |
86 | $response = $this
87 | ->actingAs($user)
88 | ->from('/profile')
89 | ->delete('/profile', [
90 | 'password' => 'wrong-password',
91 | ]);
92 |
93 | $response
94 | ->assertSessionHasErrors('password')
95 | ->assertRedirect('/profile');
96 |
97 | $this->assertNotNull($user->fresh());
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/app/Http/Controllers/StreamController.php:
--------------------------------------------------------------------------------
1 | addMinutes(60), function () {
19 | $url = "https://iptv-org.github.io/api/streams.json";
20 |
21 | $client = new Client();
22 | $response = $client->get($url);
23 |
24 | return json_decode($response->getBody(), true);
25 | });
26 |
27 | foreach ($data as $dd) {
28 | if ($dd["channel"] != "") {
29 | $channel_domain = substr($dd["channel"], -2);
30 |
31 | if($request->has("country")) {
32 | if ($channel_domain == $request->input("country")) {
33 | array_push($clean_data, $dd);
34 | }
35 | } else {
36 | if ($channel_domain == "id") {
37 | array_push($clean_data, $dd);
38 | }
39 | }
40 | }
41 | }
42 |
43 | $countries = Cache::remember('data_from_countries', now()->addHours(1), function () {
44 | return Country::all();
45 | });
46 |
47 | return Inertia::render("TVStreams", [
48 | "streaming_url_links" => $clean_data,
49 | "country" => $request->input("country"),
50 | "countries" => $countries
51 | ]);
52 | }
53 |
54 | public function radio(Request $request) {
55 | $cacheKey = 'radio_streaming_url_from_api_ID';
56 |
57 | if ($request->has("country")) {
58 | $cacheKey = 'radio_streaming_url_from_api_'.$request->input("country");
59 | }
60 |
61 | $data = Cache::remember($cacheKey, now()->addMinutes(60), function () use ($request) {
62 | $url = "https://de1.api.radio-browser.info/json/stations/search?countrycode=ID&hidebroken=true&order=clickcount&reverse=true";
63 |
64 | if ($request->has("country")) {
65 | $url = "https://de1.api.radio-browser.info/json/stations/search?countrycode=".$request->input("country")."&hidebroken=true&order=clickcount&reverse=true";
66 | }
67 |
68 | $client = new Client();
69 | $response = $client->get($url);
70 |
71 | return json_decode($response->getBody(), true);
72 | });
73 |
74 | $countries = Cache::remember('data_from_countries', now()->addHours(1), function () {
75 | return Country::all();
76 | });
77 |
78 | return Inertia::render("RadioStreams", [
79 | "streaming_url_links" => $data,
80 | "countries" => $countries
81 | ]);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/resources/js/Components/Dropdown.jsx:
--------------------------------------------------------------------------------
1 | import { useState, createContext, useContext, Fragment } from 'react';
2 | import { Link } from '@inertiajs/react';
3 | import { Transition } from '@headlessui/react';
4 |
5 | const DropDownContext = createContext();
6 |
7 | const Dropdown = ({ children }) => {
8 | const [open, setOpen] = useState(false);
9 |
10 | const toggleOpen = () => {
11 | setOpen((previousState) => !previousState);
12 | };
13 |
14 | return (
15 |
16 | {children}
17 |
18 | );
19 | };
20 |
21 | const Trigger = ({ children }) => {
22 | const { open, setOpen, toggleOpen } = useContext(DropDownContext);
23 |
24 | return (
25 | <>
26 | {children}
27 |
28 | {open && setOpen(false)}>
}
29 | >
30 | );
31 | };
32 |
33 | const Content = ({ align = 'right', width = '48', contentClasses = 'py-1 bg-white', children }) => {
34 | const { open, setOpen } = useContext(DropDownContext);
35 |
36 | let alignmentClasses = 'origin-top';
37 |
38 | if (align === 'left') {
39 | alignmentClasses = 'origin-top-left left-0';
40 | } else if (align === 'right') {
41 | alignmentClasses = 'origin-top-right right-0';
42 | }
43 |
44 | let widthClasses = '';
45 |
46 | if (width === '48') {
47 | widthClasses = 'w-48';
48 | }
49 |
50 | return (
51 | <>
52 |
62 | setOpen(false)}
65 | >
66 |
{children}
67 |
68 |
69 | >
70 | );
71 | };
72 |
73 | const DropdownLink = ({ className = '', children, ...props }) => {
74 | return (
75 |
82 | {children}
83 |
84 | );
85 | };
86 |
87 | Dropdown.Trigger = Trigger;
88 | Dropdown.Content = Content;
89 | Dropdown.Link = DropdownLink;
90 |
91 | export default Dropdown;
92 |
--------------------------------------------------------------------------------
/resources/js/Pages/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
2 | import { Head, Link } from '@inertiajs/react';
3 | import WatchTVImg from '../../../public/watch-tv.webp';
4 | import { useState, useEffect } from 'react';
5 | import {AiOutlineClose} from 'react-icons/ai';
6 |
7 |
8 | export default function Dashboard({ auth }) {
9 | const [showStream, setShowStream] = useState(localStorage.getItem("showStream") || null)
10 | const [radioName, setRadioName] = useState(localStorage.getItem("radioName") || "-");
11 | const [radioStatus, setRadioStatus] = useState(localStorage.getItem("radioStatus") || "WAITING")
12 | const [radioLogo, setRadioLogo] = useState(localStorage.getItem("radioLogo") || "");
13 |
14 | const stopRadio = () => {
15 | var audioPlayer = document.getElementsByTagName('audio')[0];
16 | audioPlayer.pause();
17 | localStorage.removeItem("showStream")
18 | localStorage.removeItem("radioName")
19 | localStorage.removeItem("radioStatus")
20 | setShowStream(null);
21 | setRadioStatus("WAITING")
22 | setRadioName("-")
23 | }
24 |
25 | return (
26 | {`Hello, ${auth.user.name}!`}}
29 | >
30 |
31 |
32 |
33 |
34 |
35 |
36 | {/* ABANDONED-TODO: CHANNEL TV ATAU RADIO BISA DIMASUKKAN KE DASHBOARD SUPAYA USER BISA PLAY ULANG LANGSUNG DARI DASHBOARD */}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
Now playing: {radioName}
45 |
46 |
47 |
48 |
49 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/resources/js/Components/ApplicationLogo.jsx:
--------------------------------------------------------------------------------
1 | export default function ApplicationLogo(props) {
2 | return (
3 | //
4 | //
5 | //
6 |
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/app/Http/Kernel.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | protected $middleware = [
17 | // \App\Http\Middleware\TrustHosts::class,
18 | \App\Http\Middleware\TrustProxies::class,
19 | \Illuminate\Http\Middleware\HandleCors::class,
20 | \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
21 | \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
22 | \App\Http\Middleware\TrimStrings::class,
23 | \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
24 | ];
25 |
26 | /**
27 | * The application's route middleware groups.
28 | *
29 | * @var array>
30 | */
31 | protected $middlewareGroups = [
32 | 'web' => [
33 | \App\Http\Middleware\EncryptCookies::class,
34 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
35 | \Illuminate\Session\Middleware\StartSession::class,
36 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
37 | \App\Http\Middleware\VerifyCsrfToken::class,
38 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
39 | \App\Http\Middleware\HandleInertiaRequests::class,
40 | \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
41 | ],
42 |
43 | 'api' => [
44 | // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
45 | \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
46 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
47 | ],
48 | ];
49 |
50 | /**
51 | * The application's middleware aliases.
52 | *
53 | * Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
54 | *
55 | * @var array
56 | */
57 | protected $middlewareAliases = [
58 | 'auth' => \App\Http\Middleware\Authenticate::class,
59 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
60 | 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
61 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
62 | 'can' => \Illuminate\Auth\Middleware\Authorize::class,
63 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
64 | 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
65 | 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
66 | 'signed' => \App\Http\Middleware\ValidateSignature::class,
67 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
68 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
69 | ];
70 | }
71 |
--------------------------------------------------------------------------------
/config/sanctum.php:
--------------------------------------------------------------------------------
1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
19 | '%s%s',
20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
21 | Sanctum::currentApplicationUrlWithPort()
22 | ))),
23 |
24 | /*
25 | |--------------------------------------------------------------------------
26 | | Sanctum Guards
27 | |--------------------------------------------------------------------------
28 | |
29 | | This array contains the authentication guards that will be checked when
30 | | Sanctum is trying to authenticate a request. If none of these guards
31 | | are able to authenticate the request, Sanctum will use the bearer
32 | | token that's present on an incoming request for authentication.
33 | |
34 | */
35 |
36 | 'guard' => ['web'],
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Expiration Minutes
41 | |--------------------------------------------------------------------------
42 | |
43 | | This value controls the number of minutes until an issued token will be
44 | | considered expired. This will override any values set in the token's
45 | | "expires_at" attribute, but first-party sessions are not affected.
46 | |
47 | */
48 |
49 | 'expiration' => null,
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Token Prefix
54 | |--------------------------------------------------------------------------
55 | |
56 | | Sanctum can prefix new tokens in order to take advantage of numerous
57 | | security scanning initiatives maintained by open source platforms
58 | | that notify developers if they commit tokens into repositories.
59 | |
60 | | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
61 | |
62 | */
63 |
64 | 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
65 |
66 | /*
67 | |--------------------------------------------------------------------------
68 | | Sanctum Middleware
69 | |--------------------------------------------------------------------------
70 | |
71 | | When authenticating your first-party SPA with Sanctum you may need to
72 | | customize some of the middleware Sanctum uses while processing the
73 | | request. You may change the middleware listed below as required.
74 | |
75 | */
76 |
77 | 'middleware' => [
78 | 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
79 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
80 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
81 | ],
82 |
83 | ];
84 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ResetPassword.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import GuestLayout from '@/Layouts/GuestLayout';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import PrimaryButton from '@/Components/PrimaryButton';
6 | import TextInput from '@/Components/TextInput';
7 | import { Head, useForm } from '@inertiajs/react';
8 |
9 | export default function ResetPassword({ token, email }) {
10 | const { data, setData, post, processing, errors, reset } = useForm({
11 | token: token,
12 | email: email,
13 | password: '',
14 | password_confirmation: '',
15 | });
16 |
17 | useEffect(() => {
18 | return () => {
19 | reset('password', 'password_confirmation');
20 | };
21 | }, []);
22 |
23 | const submit = (e) => {
24 | e.preventDefault();
25 |
26 | post(route('password.store'));
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_DRIVER', 'file'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Cache Stores
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the cache "stores" for your application as
26 | | well as their drivers. You may even define multiple stores for the
27 | | same cache driver to group types of items stored in your caches.
28 | |
29 | | Supported drivers: "apc", "array", "database", "file",
30 | | "memcached", "redis", "dynamodb", "octane", "null"
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'apc' => [
37 | 'driver' => 'apc',
38 | ],
39 |
40 | 'array' => [
41 | 'driver' => 'array',
42 | 'serialize' => false,
43 | ],
44 |
45 | 'database' => [
46 | 'driver' => 'database',
47 | 'table' => 'cache',
48 | 'connection' => null,
49 | 'lock_connection' => null,
50 | ],
51 |
52 | 'file' => [
53 | 'driver' => 'file',
54 | 'path' => storage_path('framework/cache/data'),
55 | 'lock_path' => storage_path('framework/cache/data'),
56 | ],
57 |
58 | 'memcached' => [
59 | 'driver' => 'memcached',
60 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
61 | 'sasl' => [
62 | env('MEMCACHED_USERNAME'),
63 | env('MEMCACHED_PASSWORD'),
64 | ],
65 | 'options' => [
66 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
67 | ],
68 | 'servers' => [
69 | [
70 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
71 | 'port' => env('MEMCACHED_PORT', 11211),
72 | 'weight' => 100,
73 | ],
74 | ],
75 | ],
76 |
77 | 'redis' => [
78 | 'driver' => 'redis',
79 | 'connection' => 'cache',
80 | 'lock_connection' => 'default',
81 | ],
82 |
83 | 'dynamodb' => [
84 | 'driver' => 'dynamodb',
85 | 'key' => env('AWS_ACCESS_KEY_ID'),
86 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
87 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
88 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
89 | 'endpoint' => env('DYNAMODB_ENDPOINT'),
90 | ],
91 |
92 | 'octane' => [
93 | 'driver' => 'octane',
94 | ],
95 |
96 | ],
97 |
98 | /*
99 | |--------------------------------------------------------------------------
100 | | Cache Key Prefix
101 | |--------------------------------------------------------------------------
102 | |
103 | | When utilizing the APC, database, memcached, Redis, or DynamoDB cache
104 | | stores there might be other applications using the same cache. For
105 | | that reason, you may prefix every cache key to avoid collisions.
106 | |
107 | */
108 |
109 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
110 |
111 | ];
112 |
--------------------------------------------------------------------------------
/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_CONNECTION', 'sync'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection information for each server that
24 | | is used by your application. A default configuration has been added
25 | | for each back-end shipped with Laravel. You are free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'table' => 'jobs',
40 | 'queue' => 'default',
41 | 'retry_after' => 90,
42 | 'after_commit' => false,
43 | ],
44 |
45 | 'beanstalkd' => [
46 | 'driver' => 'beanstalkd',
47 | 'host' => 'localhost',
48 | 'queue' => 'default',
49 | 'retry_after' => 90,
50 | 'block_for' => 0,
51 | 'after_commit' => false,
52 | ],
53 |
54 | 'sqs' => [
55 | 'driver' => 'sqs',
56 | 'key' => env('AWS_ACCESS_KEY_ID'),
57 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
58 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
59 | 'queue' => env('SQS_QUEUE', 'default'),
60 | 'suffix' => env('SQS_SUFFIX'),
61 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
62 | 'after_commit' => false,
63 | ],
64 |
65 | 'redis' => [
66 | 'driver' => 'redis',
67 | 'connection' => 'default',
68 | 'queue' => env('REDIS_QUEUE', 'default'),
69 | 'retry_after' => 90,
70 | 'block_for' => null,
71 | 'after_commit' => false,
72 | ],
73 |
74 | ],
75 |
76 | /*
77 | |--------------------------------------------------------------------------
78 | | Job Batching
79 | |--------------------------------------------------------------------------
80 | |
81 | | The following options configure the database and table that store job
82 | | batching information. These options can be updated to any database
83 | | connection and table which has been defined by your application.
84 | |
85 | */
86 |
87 | 'batching' => [
88 | 'database' => env('DB_CONNECTION', 'mysql'),
89 | 'table' => 'job_batches',
90 | ],
91 |
92 | /*
93 | |--------------------------------------------------------------------------
94 | | Failed Queue Jobs
95 | |--------------------------------------------------------------------------
96 | |
97 | | These options configure the behavior of failed queue job logging so you
98 | | can control which database and table are used to store the jobs that
99 | | have failed. You may change them to any database / table you wish.
100 | |
101 | */
102 |
103 | 'failed' => [
104 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
105 | 'database' => env('DB_CONNECTION', 'mysql'),
106 | 'table' => 'failed_jobs',
107 | ],
108 |
109 | ];
110 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Partials/DeleteUserForm.jsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react';
2 | import DangerButton from '@/Components/DangerButton';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import Modal from '@/Components/Modal';
6 | import SecondaryButton from '@/Components/SecondaryButton';
7 | import TextInput from '@/Components/TextInput';
8 | import { useForm } from '@inertiajs/react';
9 |
10 | export default function DeleteUserForm({ className = '' }) {
11 | const [confirmingUserDeletion, setConfirmingUserDeletion] = useState(false);
12 | const passwordInput = useRef();
13 |
14 | const {
15 | data,
16 | setData,
17 | delete: destroy,
18 | processing,
19 | reset,
20 | errors,
21 | } = useForm({
22 | password: '',
23 | });
24 |
25 | const confirmUserDeletion = () => {
26 | setConfirmingUserDeletion(true);
27 | };
28 |
29 | const deleteUser = (e) => {
30 | e.preventDefault();
31 |
32 | destroy(route('profile.destroy'), {
33 | preserveScroll: true,
34 | onSuccess: () => closeModal(),
35 | onError: () => passwordInput.current.focus(),
36 | onFinish: () => reset(),
37 | });
38 | };
39 |
40 | const closeModal = () => {
41 | setConfirmingUserDeletion(false);
42 |
43 | reset();
44 | };
45 |
46 | return (
47 |
48 |
49 | Delete Account
50 |
51 |
52 | Once your account is deleted, all of its resources and data will be permanently deleted. Before
53 | deleting your account, please download any data or information that you wish to retain.
54 |
55 |
56 |
57 | Delete Account
58 |
59 |
60 |
96 |
97 |
98 | );
99 | }
100 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/Login.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import Checkbox from '@/Components/Checkbox';
3 | import GuestLayout from '@/Layouts/GuestLayout';
4 | import InputError from '@/Components/InputError';
5 | import InputLabel from '@/Components/InputLabel';
6 | import PrimaryButton from '@/Components/PrimaryButton';
7 | import TextInput from '@/Components/TextInput';
8 | import { Head, Link, useForm } from '@inertiajs/react';
9 |
10 | export default function Login({ status, canResetPassword }) {
11 | const { data, setData, post, processing, errors, reset } = useForm({
12 | email: '',
13 | password: '',
14 | remember: false,
15 | });
16 |
17 | useEffect(() => {
18 | return () => {
19 | reset('password');
20 | };
21 | }, []);
22 |
23 | const submit = (e) => {
24 | e.preventDefault();
25 |
26 | post(route('login'));
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 | {status && {status}
}
34 |
35 |
95 |
96 |
97 |
Not registered? Click here.
98 |
99 |
100 | );
101 | }
102 |
--------------------------------------------------------------------------------
/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => 'web',
18 | 'passwords' => 'users',
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Authentication Guards
24 | |--------------------------------------------------------------------------
25 | |
26 | | Next, you may define every authentication guard for your application.
27 | | Of course, a great default configuration has been defined for you
28 | | here which uses session storage and the Eloquent user provider.
29 | |
30 | | All authentication drivers have a user provider. This defines how the
31 | | users are actually retrieved out of your database or other storage
32 | | mechanisms used by this application to persist your user's data.
33 | |
34 | | Supported: "session"
35 | |
36 | */
37 |
38 | 'guards' => [
39 | 'web' => [
40 | 'driver' => 'session',
41 | 'provider' => 'users',
42 | ],
43 | ],
44 |
45 | /*
46 | |--------------------------------------------------------------------------
47 | | User Providers
48 | |--------------------------------------------------------------------------
49 | |
50 | | All authentication drivers have a user provider. This defines how the
51 | | users are actually retrieved out of your database or other storage
52 | | mechanisms used by this application to persist your user's data.
53 | |
54 | | If you have multiple user tables or models you may configure multiple
55 | | sources which represent each model / table. These sources may then
56 | | be assigned to any extra authentication guards you have defined.
57 | |
58 | | Supported: "database", "eloquent"
59 | |
60 | */
61 |
62 | 'providers' => [
63 | 'users' => [
64 | 'driver' => 'eloquent',
65 | 'model' => App\Models\User::class,
66 | ],
67 |
68 | // 'users' => [
69 | // 'driver' => 'database',
70 | // 'table' => 'users',
71 | // ],
72 | ],
73 |
74 | /*
75 | |--------------------------------------------------------------------------
76 | | Resetting Passwords
77 | |--------------------------------------------------------------------------
78 | |
79 | | You may specify multiple password reset configurations if you have more
80 | | than one user table or model in the application and you want to have
81 | | separate password reset settings based on the specific user types.
82 | |
83 | | The expiry time is the number of minutes that each reset token will be
84 | | considered valid. This security feature keeps tokens short-lived so
85 | | they have less time to be guessed. You may change this as needed.
86 | |
87 | | The throttle setting is the number of seconds a user must wait before
88 | | generating more password reset tokens. This prevents the user from
89 | | quickly generating a very large amount of password reset tokens.
90 | |
91 | */
92 |
93 | 'passwords' => [
94 | 'users' => [
95 | 'provider' => 'users',
96 | 'table' => 'password_reset_tokens',
97 | 'expire' => 60,
98 | 'throttle' => 60,
99 | ],
100 | ],
101 |
102 | /*
103 | |--------------------------------------------------------------------------
104 | | Password Confirmation Timeout
105 | |--------------------------------------------------------------------------
106 | |
107 | | Here you may define the amount of seconds before a password confirmation
108 | | times out and the user is prompted to re-enter their password via the
109 | | confirmation screen. By default, the timeout lasts for three hours.
110 | |
111 | */
112 |
113 | 'password_timeout' => 10800,
114 |
115 | ];
116 |
--------------------------------------------------------------------------------
/resources/js/Pages/Welcome.jsx:
--------------------------------------------------------------------------------
1 | import ApplicationLogo from '@/Components/ApplicationLogo';
2 | import { Link, Head } from '@inertiajs/react';
3 | import WatchTVImg from '../../../public/watch-tv.webp';
4 |
5 |
6 | export default function Welcome({ auth, laravelVersion, phpVersion }) {
7 | return (
8 | <>
9 |
10 |
11 |
12 |
16 | Login
17 |
18 |
19 |
23 | Register
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
Welcome to ValeTV!
34 |
35 |
36 |
ValeTV is a website application for you to watch live TV and radio broadcasts from various countries in the world for free.
37 |
38 |
39 | Watch Live TV
40 | Listen to Radio
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
63 | >
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_MAILER', 'smtp'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Mailer Configurations
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure all of the mailers used by your application plus
24 | | their respective settings. Several examples have been configured for
25 | | you and you are free to add your own as your application requires.
26 | |
27 | | Laravel supports a variety of mail "transport" drivers to be used while
28 | | sending an e-mail. You will specify which one you are using for your
29 | | mailers below. You are free to add additional mailers as required.
30 | |
31 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
32 | | "postmark", "log", "array", "failover"
33 | |
34 | */
35 |
36 | 'mailers' => [
37 | 'smtp' => [
38 | 'transport' => 'smtp',
39 | 'url' => env('MAIL_URL'),
40 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
41 | 'port' => env('MAIL_PORT', 587),
42 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
43 | 'username' => env('MAIL_USERNAME'),
44 | 'password' => env('MAIL_PASSWORD'),
45 | 'timeout' => null,
46 | 'local_domain' => env('MAIL_EHLO_DOMAIN'),
47 | ],
48 |
49 | 'ses' => [
50 | 'transport' => 'ses',
51 | ],
52 |
53 | 'mailgun' => [
54 | 'transport' => 'mailgun',
55 | // 'client' => [
56 | // 'timeout' => 5,
57 | // ],
58 | ],
59 |
60 | 'postmark' => [
61 | 'transport' => 'postmark',
62 | // 'message_stream_id' => null,
63 | // 'client' => [
64 | // 'timeout' => 5,
65 | // ],
66 | ],
67 |
68 | 'sendmail' => [
69 | 'transport' => 'sendmail',
70 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
71 | ],
72 |
73 | 'log' => [
74 | 'transport' => 'log',
75 | 'channel' => env('MAIL_LOG_CHANNEL'),
76 | ],
77 |
78 | 'array' => [
79 | 'transport' => 'array',
80 | ],
81 |
82 | 'failover' => [
83 | 'transport' => 'failover',
84 | 'mailers' => [
85 | 'smtp',
86 | 'log',
87 | ],
88 | ],
89 | ],
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Global "From" Address
94 | |--------------------------------------------------------------------------
95 | |
96 | | You may wish for all e-mails sent by your application to be sent from
97 | | the same address. Here, you may specify a name and address that is
98 | | used globally for all e-mails that are sent by your application.
99 | |
100 | */
101 |
102 | 'from' => [
103 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
104 | 'name' => env('MAIL_FROM_NAME', 'Example'),
105 | ],
106 |
107 | /*
108 | |--------------------------------------------------------------------------
109 | | Markdown Mail Settings
110 | |--------------------------------------------------------------------------
111 | |
112 | | If you are using Markdown based email rendering, you may configure your
113 | | theme and component paths here, allowing you to customize the design
114 | | of the emails. Or, you may simply stick with the Laravel defaults!
115 | |
116 | */
117 |
118 | 'markdown' => [
119 | 'theme' => 'default',
120 |
121 | 'paths' => [
122 | resource_path('views/vendor/mail'),
123 | ],
124 | ],
125 |
126 | ];
127 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.jsx:
--------------------------------------------------------------------------------
1 | import InputError from '@/Components/InputError';
2 | import InputLabel from '@/Components/InputLabel';
3 | import PrimaryButton from '@/Components/PrimaryButton';
4 | import TextInput from '@/Components/TextInput';
5 | import { Link, useForm, usePage } from '@inertiajs/react';
6 | import { Transition } from '@headlessui/react';
7 |
8 | export default function UpdateProfileInformation({ mustVerifyEmail, status, className = '' }) {
9 | const user = usePage().props.auth.user;
10 |
11 | const { data, setData, patch, errors, processing, recentlySuccessful } = useForm({
12 | name: user.name,
13 | email: user.email,
14 | });
15 |
16 | const submit = (e) => {
17 | e.preventDefault();
18 |
19 | patch(route('profile.update'));
20 | };
21 |
22 | return (
23 |
102 | );
103 | }
104 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/Register.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import GuestLayout from '@/Layouts/GuestLayout';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import PrimaryButton from '@/Components/PrimaryButton';
6 | import TextInput from '@/Components/TextInput';
7 | import { Head, Link, useForm } from '@inertiajs/react';
8 |
9 | export default function Register() {
10 | const { data, setData, post, processing, errors, reset } = useForm({
11 | name: '',
12 | email: '',
13 | password: '',
14 | password_confirmation: '',
15 | });
16 |
17 | useEffect(() => {
18 | return () => {
19 | reset('password', 'password_confirmation');
20 | };
21 | }, []);
22 |
23 | const submit = (e) => {
24 | e.preventDefault();
25 |
26 | post(route('register'));
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 |
115 |
116 | );
117 | }
118 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Partials/UpdatePasswordForm.jsx:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | import InputError from '@/Components/InputError';
3 | import InputLabel from '@/Components/InputLabel';
4 | import PrimaryButton from '@/Components/PrimaryButton';
5 | import TextInput from '@/Components/TextInput';
6 | import { useForm } from '@inertiajs/react';
7 | import { Transition } from '@headlessui/react';
8 |
9 | export default function UpdatePasswordForm({ className = '' }) {
10 | const passwordInput = useRef();
11 | const currentPasswordInput = useRef();
12 |
13 | const { data, setData, errors, put, reset, processing, recentlySuccessful } = useForm({
14 | current_password: '',
15 | password: '',
16 | password_confirmation: '',
17 | });
18 |
19 | const updatePassword = (e) => {
20 | e.preventDefault();
21 |
22 | put(route('password.update'), {
23 | preserveScroll: true,
24 | onSuccess: () => reset(),
25 | onError: (errors) => {
26 | if (errors.password) {
27 | reset('password', 'password_confirmation');
28 | passwordInput.current.focus();
29 | }
30 |
31 | if (errors.current_password) {
32 | reset('current_password');
33 | currentPasswordInput.current.focus();
34 | }
35 | },
36 | });
37 | };
38 |
39 | return (
40 |
112 | );
113 | }
114 |
--------------------------------------------------------------------------------
/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Deprecations Log Channel
26 | |--------------------------------------------------------------------------
27 | |
28 | | This option controls the log channel that should be used to log warnings
29 | | regarding deprecated PHP and library features. This allows you to get
30 | | your application ready for upcoming major versions of dependencies.
31 | |
32 | */
33 |
34 | 'deprecations' => [
35 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
36 | 'trace' => false,
37 | ],
38 |
39 | /*
40 | |--------------------------------------------------------------------------
41 | | Log Channels
42 | |--------------------------------------------------------------------------
43 | |
44 | | Here you may configure the log channels for your application. Out of
45 | | the box, Laravel uses the Monolog PHP logging library. This gives
46 | | you a variety of powerful log handlers / formatters to utilize.
47 | |
48 | | Available Drivers: "single", "daily", "slack", "syslog",
49 | | "errorlog", "monolog",
50 | | "custom", "stack"
51 | |
52 | */
53 |
54 | 'channels' => [
55 | 'stack' => [
56 | 'driver' => 'stack',
57 | 'channels' => ['single'],
58 | 'ignore_exceptions' => false,
59 | ],
60 |
61 | 'single' => [
62 | 'driver' => 'single',
63 | 'path' => storage_path('logs/laravel.log'),
64 | 'level' => env('LOG_LEVEL', 'debug'),
65 | 'replace_placeholders' => true,
66 | ],
67 |
68 | 'daily' => [
69 | 'driver' => 'daily',
70 | 'path' => storage_path('logs/laravel.log'),
71 | 'level' => env('LOG_LEVEL', 'debug'),
72 | 'days' => 14,
73 | 'replace_placeholders' => true,
74 | ],
75 |
76 | 'slack' => [
77 | 'driver' => 'slack',
78 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
79 | 'username' => 'Laravel Log',
80 | 'emoji' => ':boom:',
81 | 'level' => env('LOG_LEVEL', 'critical'),
82 | 'replace_placeholders' => true,
83 | ],
84 |
85 | 'papertrail' => [
86 | 'driver' => 'monolog',
87 | 'level' => env('LOG_LEVEL', 'debug'),
88 | 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
89 | 'handler_with' => [
90 | 'host' => env('PAPERTRAIL_URL'),
91 | 'port' => env('PAPERTRAIL_PORT'),
92 | 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
93 | ],
94 | 'processors' => [PsrLogMessageProcessor::class],
95 | ],
96 |
97 | 'stderr' => [
98 | 'driver' => 'monolog',
99 | 'level' => env('LOG_LEVEL', 'debug'),
100 | 'handler' => StreamHandler::class,
101 | 'formatter' => env('LOG_STDERR_FORMATTER'),
102 | 'with' => [
103 | 'stream' => 'php://stderr',
104 | ],
105 | 'processors' => [PsrLogMessageProcessor::class],
106 | ],
107 |
108 | 'syslog' => [
109 | 'driver' => 'syslog',
110 | 'level' => env('LOG_LEVEL', 'debug'),
111 | 'facility' => LOG_USER,
112 | 'replace_placeholders' => true,
113 | ],
114 |
115 | 'errorlog' => [
116 | 'driver' => 'errorlog',
117 | 'level' => env('LOG_LEVEL', 'debug'),
118 | 'replace_placeholders' => true,
119 | ],
120 |
121 | 'null' => [
122 | 'driver' => 'monolog',
123 | 'handler' => NullHandler::class,
124 | ],
125 |
126 | 'emergency' => [
127 | 'path' => storage_path('logs/laravel.log'),
128 | ],
129 | ],
130 |
131 | ];
132 |
--------------------------------------------------------------------------------
/config/database.php:
--------------------------------------------------------------------------------
1 | env('DB_CONNECTION', 'mysql'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Database Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here are each of the database connections setup for your application.
26 | | Of course, examples of configuring each database platform that is
27 | | supported by Laravel is shown below to make development simple.
28 | |
29 | |
30 | | All database work in Laravel is done through the PHP PDO facilities
31 | | so make sure you have the driver for your particular database of
32 | | choice installed on your machine before you begin development.
33 | |
34 | */
35 |
36 | 'connections' => [
37 |
38 | 'sqlite' => [
39 | 'driver' => 'sqlite',
40 | 'url' => env('DATABASE_URL'),
41 | 'database' => env('DB_DATABASE', database_path('database.sqlite')),
42 | 'prefix' => '',
43 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
44 | ],
45 |
46 | 'mysql' => [
47 | 'driver' => 'mysql',
48 | 'url' => env('DATABASE_URL'),
49 | 'host' => env('DB_HOST', '127.0.0.1'),
50 | 'port' => env('DB_PORT', '3306'),
51 | 'database' => env('DB_DATABASE', 'forge'),
52 | 'username' => env('DB_USERNAME', 'forge'),
53 | 'password' => env('DB_PASSWORD', ''),
54 | 'unix_socket' => env('DB_SOCKET', ''),
55 | 'charset' => 'utf8mb4',
56 | 'collation' => null,
57 | 'prefix' => '',
58 | 'prefix_indexes' => true,
59 | 'strict' => true,
60 | 'engine' => null,
61 | 'options' => extension_loaded('pdo_mysql') ? array_filter([
62 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
63 | ]) : [],
64 | ],
65 |
66 | 'pgsql' => [
67 | 'driver' => 'pgsql',
68 | 'url' => env('DATABASE_URL'),
69 | 'host' => env('DB_HOST', '127.0.0.1'),
70 | 'port' => env('DB_PORT', '5432'),
71 | 'database' => env('DB_DATABASE', 'forge'),
72 | 'username' => env('DB_USERNAME', 'forge'),
73 | 'password' => env('DB_PASSWORD', ''),
74 | 'charset' => 'utf8',
75 | 'prefix' => '',
76 | 'prefix_indexes' => true,
77 | 'search_path' => 'public',
78 | 'sslmode' => 'prefer',
79 | ],
80 |
81 | 'sqlsrv' => [
82 | 'driver' => 'sqlsrv',
83 | 'url' => env('DATABASE_URL'),
84 | 'host' => env('DB_HOST', 'localhost'),
85 | 'port' => env('DB_PORT', '1433'),
86 | 'database' => env('DB_DATABASE', 'forge'),
87 | 'username' => env('DB_USERNAME', 'forge'),
88 | 'password' => env('DB_PASSWORD', ''),
89 | 'charset' => 'utf8',
90 | 'prefix' => '',
91 | 'prefix_indexes' => true,
92 | // 'encrypt' => env('DB_ENCRYPT', 'yes'),
93 | // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
94 | ],
95 |
96 | ],
97 |
98 | /*
99 | |--------------------------------------------------------------------------
100 | | Migration Repository Table
101 | |--------------------------------------------------------------------------
102 | |
103 | | This table keeps track of all the migrations that have already run for
104 | | your application. Using this information, we can determine which of
105 | | the migrations on disk haven't actually been run in the database.
106 | |
107 | */
108 |
109 | 'migrations' => 'migrations',
110 |
111 | /*
112 | |--------------------------------------------------------------------------
113 | | Redis Databases
114 | |--------------------------------------------------------------------------
115 | |
116 | | Redis is an open source, fast, and advanced key-value store that also
117 | | provides a richer body of commands than a typical key-value system
118 | | such as APC or Memcached. Laravel makes it easy to dig right in.
119 | |
120 | */
121 |
122 | 'redis' => [
123 |
124 | 'client' => env('REDIS_CLIENT', 'phpredis'),
125 |
126 | 'options' => [
127 | 'cluster' => env('REDIS_CLUSTER', 'redis'),
128 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
129 | ],
130 |
131 | 'default' => [
132 | 'url' => env('REDIS_URL'),
133 | 'host' => env('REDIS_HOST', '127.0.0.1'),
134 | 'username' => env('REDIS_USERNAME'),
135 | 'password' => env('REDIS_PASSWORD'),
136 | 'port' => env('REDIS_PORT', '6379'),
137 | 'database' => env('REDIS_DB', '0'),
138 | ],
139 |
140 | 'cache' => [
141 | 'url' => env('REDIS_URL'),
142 | 'host' => env('REDIS_HOST', '127.0.0.1'),
143 | 'username' => env('REDIS_USERNAME'),
144 | 'password' => env('REDIS_PASSWORD'),
145 | 'port' => env('REDIS_PORT', '6379'),
146 | 'database' => env('REDIS_CACHE_DB', '1'),
147 | ],
148 |
149 | ],
150 |
151 | ];
152 |
--------------------------------------------------------------------------------
/config/app.php:
--------------------------------------------------------------------------------
1 | env('APP_NAME', 'Laravel'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Application Environment
24 | |--------------------------------------------------------------------------
25 | |
26 | | This value determines the "environment" your application is currently
27 | | running in. This may determine how you prefer to configure various
28 | | services the application utilizes. Set this in your ".env" file.
29 | |
30 | */
31 |
32 | 'env' => env('APP_ENV', 'production'),
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Application Debug Mode
37 | |--------------------------------------------------------------------------
38 | |
39 | | When your application is in debug mode, detailed error messages with
40 | | stack traces will be shown on every error that occurs within your
41 | | application. If disabled, a simple generic error page is shown.
42 | |
43 | */
44 |
45 | 'debug' => (bool) env('APP_DEBUG', false),
46 |
47 | /*
48 | |--------------------------------------------------------------------------
49 | | Application URL
50 | |--------------------------------------------------------------------------
51 | |
52 | | This URL is used by the console to properly generate URLs when using
53 | | the Artisan command line tool. You should set this to the root of
54 | | your application so that it is used when running Artisan tasks.
55 | |
56 | */
57 |
58 | 'url' => env('APP_URL', 'http://localhost'),
59 |
60 | 'asset_url' => env('ASSET_URL'),
61 |
62 | /*
63 | |--------------------------------------------------------------------------
64 | | Application Timezone
65 | |--------------------------------------------------------------------------
66 | |
67 | | Here you may specify the default timezone for your application, which
68 | | will be used by the PHP date and date-time functions. We have gone
69 | | ahead and set this to a sensible default for you out of the box.
70 | |
71 | */
72 |
73 | 'timezone' => 'UTC',
74 |
75 | /*
76 | |--------------------------------------------------------------------------
77 | | Application Locale Configuration
78 | |--------------------------------------------------------------------------
79 | |
80 | | The application locale determines the default locale that will be used
81 | | by the translation service provider. You are free to set this value
82 | | to any of the locales which will be supported by the application.
83 | |
84 | */
85 |
86 | 'locale' => 'en',
87 |
88 | /*
89 | |--------------------------------------------------------------------------
90 | | Application Fallback Locale
91 | |--------------------------------------------------------------------------
92 | |
93 | | The fallback locale determines the locale to use when the current one
94 | | is not available. You may change the value to correspond to any of
95 | | the language folders that are provided through your application.
96 | |
97 | */
98 |
99 | 'fallback_locale' => 'en',
100 |
101 | /*
102 | |--------------------------------------------------------------------------
103 | | Faker Locale
104 | |--------------------------------------------------------------------------
105 | |
106 | | This locale will be used by the Faker PHP library when generating fake
107 | | data for your database seeds. For example, this will be used to get
108 | | localized telephone numbers, street address information and more.
109 | |
110 | */
111 |
112 | 'faker_locale' => 'en_US',
113 |
114 | /*
115 | |--------------------------------------------------------------------------
116 | | Encryption Key
117 | |--------------------------------------------------------------------------
118 | |
119 | | This key is used by the Illuminate encrypter service and should be set
120 | | to a random, 32 character string, otherwise these encrypted strings
121 | | will not be safe. Please do this before deploying an application!
122 | |
123 | */
124 |
125 | 'key' => env('APP_KEY'),
126 |
127 | 'cipher' => 'AES-256-CBC',
128 |
129 | /*
130 | |--------------------------------------------------------------------------
131 | | Maintenance Mode Driver
132 | |--------------------------------------------------------------------------
133 | |
134 | | These configuration options determine the driver used to determine and
135 | | manage Laravel's "maintenance mode" status. The "cache" driver will
136 | | allow maintenance mode to be controlled across multiple machines.
137 | |
138 | | Supported drivers: "file", "cache"
139 | |
140 | */
141 |
142 | 'maintenance' => [
143 | 'driver' => 'file',
144 | // 'store' => 'redis',
145 | ],
146 |
147 | /*
148 | |--------------------------------------------------------------------------
149 | | Autoloaded Service Providers
150 | |--------------------------------------------------------------------------
151 | |
152 | | The service providers listed here will be automatically loaded on the
153 | | request to your application. Feel free to add your own services to
154 | | this array to grant expanded functionality to your applications.
155 | |
156 | */
157 |
158 | 'providers' => ServiceProvider::defaultProviders()->merge([
159 | /*
160 | * Package Service Providers...
161 | */
162 |
163 | /*
164 | * Application Service Providers...
165 | */
166 | App\Providers\AppServiceProvider::class,
167 | App\Providers\AuthServiceProvider::class,
168 | // App\Providers\BroadcastServiceProvider::class,
169 | App\Providers\EventServiceProvider::class,
170 | App\Providers\RouteServiceProvider::class,
171 | ])->toArray(),
172 |
173 | /*
174 | |--------------------------------------------------------------------------
175 | | Class Aliases
176 | |--------------------------------------------------------------------------
177 | |
178 | | This array of class aliases will be registered when this application
179 | | is started. However, feel free to register as many as you wish as
180 | | the aliases are "lazy" loaded so they don't hinder performance.
181 | |
182 | */
183 |
184 | 'aliases' => Facade::defaultAliases()->merge([
185 | // 'Example' => App\Facades\Example::class,
186 | ])->toArray(),
187 |
188 | ];
189 |
--------------------------------------------------------------------------------
/config/session.php:
--------------------------------------------------------------------------------
1 | env('SESSION_DRIVER', 'file'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Session Lifetime
26 | |--------------------------------------------------------------------------
27 | |
28 | | Here you may specify the number of minutes that you wish the session
29 | | to be allowed to remain idle before it expires. If you want them
30 | | to immediately expire on the browser closing, set that option.
31 | |
32 | */
33 |
34 | 'lifetime' => env('SESSION_LIFETIME', 120),
35 |
36 | 'expire_on_close' => false,
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Session Encryption
41 | |--------------------------------------------------------------------------
42 | |
43 | | This option allows you to easily specify that all of your session data
44 | | should be encrypted before it is stored. All encryption will be run
45 | | automatically by Laravel and you can use the Session like normal.
46 | |
47 | */
48 |
49 | 'encrypt' => false,
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Session File Location
54 | |--------------------------------------------------------------------------
55 | |
56 | | When using the native session driver, we need a location where session
57 | | files may be stored. A default has been set for you but a different
58 | | location may be specified. This is only needed for file sessions.
59 | |
60 | */
61 |
62 | 'files' => storage_path('framework/sessions'),
63 |
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Session Database Connection
67 | |--------------------------------------------------------------------------
68 | |
69 | | When using the "database" or "redis" session drivers, you may specify a
70 | | connection that should be used to manage these sessions. This should
71 | | correspond to a connection in your database configuration options.
72 | |
73 | */
74 |
75 | 'connection' => env('SESSION_CONNECTION'),
76 |
77 | /*
78 | |--------------------------------------------------------------------------
79 | | Session Database Table
80 | |--------------------------------------------------------------------------
81 | |
82 | | When using the "database" session driver, you may specify the table we
83 | | should use to manage the sessions. Of course, a sensible default is
84 | | provided for you; however, you are free to change this as needed.
85 | |
86 | */
87 |
88 | 'table' => 'sessions',
89 |
90 | /*
91 | |--------------------------------------------------------------------------
92 | | Session Cache Store
93 | |--------------------------------------------------------------------------
94 | |
95 | | While using one of the framework's cache driven session backends you may
96 | | list a cache store that should be used for these sessions. This value
97 | | must match with one of the application's configured cache "stores".
98 | |
99 | | Affects: "apc", "dynamodb", "memcached", "redis"
100 | |
101 | */
102 |
103 | 'store' => env('SESSION_STORE'),
104 |
105 | /*
106 | |--------------------------------------------------------------------------
107 | | Session Sweeping Lottery
108 | |--------------------------------------------------------------------------
109 | |
110 | | Some session drivers must manually sweep their storage location to get
111 | | rid of old sessions from storage. Here are the chances that it will
112 | | happen on a given request. By default, the odds are 2 out of 100.
113 | |
114 | */
115 |
116 | 'lottery' => [2, 100],
117 |
118 | /*
119 | |--------------------------------------------------------------------------
120 | | Session Cookie Name
121 | |--------------------------------------------------------------------------
122 | |
123 | | Here you may change the name of the cookie used to identify a session
124 | | instance by ID. The name specified here will get used every time a
125 | | new session cookie is created by the framework for every driver.
126 | |
127 | */
128 |
129 | 'cookie' => env(
130 | 'SESSION_COOKIE',
131 | Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
132 | ),
133 |
134 | /*
135 | |--------------------------------------------------------------------------
136 | | Session Cookie Path
137 | |--------------------------------------------------------------------------
138 | |
139 | | The session cookie path determines the path for which the cookie will
140 | | be regarded as available. Typically, this will be the root path of
141 | | your application but you are free to change this when necessary.
142 | |
143 | */
144 |
145 | 'path' => '/',
146 |
147 | /*
148 | |--------------------------------------------------------------------------
149 | | Session Cookie Domain
150 | |--------------------------------------------------------------------------
151 | |
152 | | Here you may change the domain of the cookie used to identify a session
153 | | in your application. This will determine which domains the cookie is
154 | | available to in your application. A sensible default has been set.
155 | |
156 | */
157 |
158 | 'domain' => env('SESSION_DOMAIN'),
159 |
160 | /*
161 | |--------------------------------------------------------------------------
162 | | HTTPS Only Cookies
163 | |--------------------------------------------------------------------------
164 | |
165 | | By setting this option to true, session cookies will only be sent back
166 | | to the server if the browser has a HTTPS connection. This will keep
167 | | the cookie from being sent to you when it can't be done securely.
168 | |
169 | */
170 |
171 | 'secure' => env('SESSION_SECURE_COOKIE'),
172 |
173 | /*
174 | |--------------------------------------------------------------------------
175 | | HTTP Access Only
176 | |--------------------------------------------------------------------------
177 | |
178 | | Setting this value to true will prevent JavaScript from accessing the
179 | | value of the cookie and the cookie will only be accessible through
180 | | the HTTP protocol. You are free to modify this option if needed.
181 | |
182 | */
183 |
184 | 'http_only' => true,
185 |
186 | /*
187 | |--------------------------------------------------------------------------
188 | | Same-Site Cookies
189 | |--------------------------------------------------------------------------
190 | |
191 | | This option determines how your cookies behave when cross-site requests
192 | | take place, and can be used to mitigate CSRF attacks. By default, we
193 | | will set this value to "lax" since this is a secure default value.
194 | |
195 | | Supported: "lax", "strict", "none", null
196 | |
197 | */
198 |
199 | 'same_site' => 'lax',
200 |
201 | ];
202 |
--------------------------------------------------------------------------------
/resources/js/Layouts/AuthenticatedLayout.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import ApplicationLogo from '@/Components/ApplicationLogo';
3 | import Dropdown from '@/Components/Dropdown';
4 | import NavLink from '@/Components/NavLink';
5 | import ResponsiveNavLink from '@/Components/ResponsiveNavLink';
6 | import { Link } from '@inertiajs/react';
7 |
8 | export default function Authenticated({ user, header, children }) {
9 | const [showingNavigationDropdown, setShowingNavigationDropdown] = useState(false);
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
22 |
23 |
24 |
25 | Dashboard
26 |
27 |
28 |
29 |
30 |
31 | TV Streams
32 |
33 |
34 |
35 |
36 |
37 | Radio Streams
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
51 | {user.name}
52 |
53 |
59 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Profile
71 |
72 | Log Out
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
setShowingNavigationDropdown((previousState) => !previousState)}
82 | className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"
83 | >
84 |
85 |
92 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | Dashboard
109 |
110 |
111 |
112 |
113 |
114 | TV Streams
115 |
116 |
117 |
118 |
119 |
120 | Radio Streams
121 |
122 |
123 |
124 |
125 |
126 |
{user.name}
127 |
{user.email}
128 |
129 |
130 |
131 | Profile
132 |
133 | Log Out
134 |
135 |
136 |
137 |
138 |
139 |
140 | {header && (
141 |
144 | )}
145 |
146 |
{children}
147 |
148 | );
149 | }
150 |
--------------------------------------------------------------------------------
/resources/js/Pages/TVStreams.jsx:
--------------------------------------------------------------------------------
1 | import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
2 | import { Head, Link } from '@inertiajs/react';
3 | import axios from 'axios';
4 | import Hls from 'hls.js';
5 | import { useEffect, useState } from 'react';
6 | import {AiOutlineClose} from 'react-icons/ai';
7 |
8 |
9 | export default function TVStreams({ auth, streaming_url_links, country }) {
10 | const [streamingUrlLinks, setStreamingUrlLinks] = useState(streaming_url_links)
11 | const [countryCode, setCountryCode] = useState("id")
12 | const [showStream, setShowStream] = useState(null)
13 | const [isStreamingProviderWorksFine, setIsStreamingProviderWorksFine] = useState(false);
14 | const [currentChannelName, setCurrentChannelName] = useState("-");
15 | const [recommendedCountries, setRecommendedCountries] = useState(null);
16 | const [radioName, setRadioName] = useState(localStorage.getItem("radioName") || "-");
17 | const [radioStatus, setRadioStatus] = useState(localStorage.getItem("radioStatus") || "WAITING")
18 | const [radioShowStream, setRadioShowStream] = useState(localStorage.getItem("showStream") || null)
19 |
20 | const RenderStatus = () => {
21 | if (isStreamingProviderWorksFine == true) {
22 | return ACTIVE
23 | } else if(isStreamingProviderWorksFine == null) {
24 | return WAITING
25 | } else {
26 | return INACTIVE
27 | }
28 | }
29 |
30 | const stopRadio = () => {
31 | var audioPlayer = document.getElementsByTagName('audio')[0];
32 | audioPlayer.pause();
33 | localStorage.removeItem("showStream")
34 | localStorage.removeItem("radioName")
35 | localStorage.removeItem("radioStatus")
36 | setShowStream(null);
37 | setRadioStatus("WAITING")
38 | setRadioName("-")
39 | }
40 |
41 | const getBadgeColor = (url, channel) => {
42 |
43 | if (showStream == url || channel == showStream) {
44 | if(isStreamingProviderWorksFine == null) {
45 | return "text-yellow-500"
46 | } else if(isStreamingProviderWorksFine == false) {
47 | return "text-red-600"
48 | } else {
49 | return "text-green-600"
50 | }
51 | }
52 | }
53 |
54 | const attachStream = (url, name) => {
55 | setIsStreamingProviderWorksFine(null)
56 | setCurrentChannelName(name)
57 |
58 | var video = document.getElementById('video');
59 |
60 | if (Hls.isSupported()) {
61 | var video = document.getElementById('video');
62 | var hls = new Hls();
63 | hls.on(Hls.Events.MEDIA_ATTACHED, function () {
64 | console.log('Video and HLS bounded!');
65 | });
66 |
67 | hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
68 | setCurrentChannelName(name)
69 | console.log(
70 | 'Manifest loaded, found ' + data.levels.length + ' quality level!',
71 | );
72 | });
73 |
74 | video.oncanplay = () => {
75 | setIsStreamingProviderWorksFine(true)
76 | }
77 |
78 | hls.on(Hls.Events.ERROR, function (event, data) {
79 |
80 | if (data.fatal) {
81 | switch (data.type) {
82 | case Hls.ErrorTypes.MEDIA_ERROR:
83 | setIsStreamingProviderWorksFine(false)
84 | break;
85 | case Hls.ErrorTypes.NETWORK_ERROR:
86 | setIsStreamingProviderWorksFine(false)
87 | break;
88 | default:
89 | // cannot recover
90 | hls.destroy();
91 | break;
92 | }
93 | }
94 | });
95 |
96 | hls.loadSource(url);
97 |
98 | hls.attachMedia(video);
99 |
100 | video.play();
101 | }
102 | }
103 |
104 | const getCountryRecommendations = (inputCountry) => {
105 | if(inputCountry != "") {
106 | axios.get("/api/countries", {
107 | params: { input: inputCountry }
108 | }).then((response) => {
109 | setRecommendedCountries(response.data)
110 | })
111 | } else {
112 | setRecommendedCountries(null)
113 | }
114 | }
115 |
116 | let i = 0;
117 |
118 | return (
119 | Country: {!country ? countryCode.toUpperCase() : country.toUpperCase()}}
122 | >
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
getCountryRecommendations(e.target.value)} placeholder='Search country...' />
131 | {
132 | recommendedCountries != null ?
133 |
134 | {recommendedCountries.map((c) => {
135 | return (
136 | <>
137 |
138 |
{c["name"]}
139 |
140 | >
141 | )
142 | })}
143 |
144 | :
145 | ""
146 | }
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
{currentChannelName}
156 |
157 |
158 |
Channel status:
159 |
160 |
161 |
162 |
163 |
164 | {
165 | streamingUrlLinks.length === 0 ?
166 |
No channel
167 | :
168 | ""
169 | }
170 |
171 |
172 | {streamingUrlLinks.map((s) => {
173 | i++;
174 |
175 | return (
176 |
177 |
{
178 | if (showStream != s.url) {
179 | setShowStream(s.url);
180 | attachStream(s.url, s.channel);
181 | }
182 | }
183 | } className={`badge badge-outline ${getBadgeColor(s.url, s.channel)} text-lg p-4 cursor-pointer font-bold`}>{s.channel}
184 |
185 | )
186 | })}
187 |
188 |
189 |
190 |
191 |
192 |
Now playing: {radioName}
193 |
194 |
195 |
196 |
197 |
200 |
201 |
202 |
203 |
204 |
205 | );
206 | }
--------------------------------------------------------------------------------