+
Our Mission
+
+ We are passionate about sharing knowledge and fostering a community of learners. Our blog covers a range of topics from design and technology to programming, all with the goal of providing valuable insights and resources to our readers.
+
+
+
Our Values
+
+ Commitment to Quality
+ Community Engagement
+ Continuous Learning
+ Integrity and Transparency
+
+
+
Meet the Team
+
+
+
+
Sheraldo Halim
+
Full Stack Developer
+
+
+
+
+
diff --git a/resources/views/components/header.blade.php b/resources/views/components/header.blade.php
new file mode 100644
index 0000000..5136ea0
--- /dev/null
+++ b/resources/views/components/header.blade.php
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/resources/views/components/layout.blade.php b/resources/views/components/layout.blade.php
new file mode 100644
index 0000000..adbe995
--- /dev/null
+++ b/resources/views/components/layout.blade.php
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+ @vite(['resources/css/app.css','resources/js/app.js'])
+
+
+
Home Page
+
+
+
+
+
+
+
{{ $title }}
+
+
+
+ {{ $slot }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/components/nav-link.blade.php b/resources/views/components/nav-link.blade.php
new file mode 100644
index 0000000..81ccd70
--- /dev/null
+++ b/resources/views/components/nav-link.blade.php
@@ -0,0 +1,11 @@
+@props(['active'])
+
+@php
+$classes = $active
+ ? 'text-white bg-gray-900 px-3 py-2 rounded-md text-sm font-medium'
+ : 'text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium';
+@endphp
+
+
merge(['class' => $classes]) }}>
+ {{ $slot }}
+
diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php
new file mode 100644
index 0000000..73d128e
--- /dev/null
+++ b/resources/views/components/navbar.blade.php
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+ Home
+ Blog
+ About
+ Contact
+
+
+
+
+
+
+
+ Open main menu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/views/contact.blade.php b/resources/views/contact.blade.php
new file mode 100644
index 0000000..f4e715a
--- /dev/null
+++ b/resources/views/contact.blade.php
@@ -0,0 +1,65 @@
+
+ {{ 'Contact Us - Blog Website' }}
+
+ {{-- Hero Section --}}
+
+
+
+
Contact Us
+
We'd love to hear from you! Please fill out the form below to get in touch.
+
+
+
+
+ {{-- Contact Form --}}
+
+
+
+
Get in Touch
+
+ {{-- Validation Errors --}}
+ @if (session('success'))
+
+ {{ session('success') }}
+
+ @endif
+
+ @if ($errors->any())
+
+
+ @foreach ($errors->all() as $error)
+ {{ $error }}
+ @endforeach
+
+
+ @endif
+
+
+
+
+
+
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php
new file mode 100644
index 0000000..66028f2
--- /dev/null
+++ b/resources/views/dashboard.blade.php
@@ -0,0 +1,17 @@
+
+
+
+ {{ __('Dashboard') }}
+
+
+
+
+
+
+
+ {{ __("You're logged in!") }}
+
+
+
+
+
diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php
new file mode 100644
index 0000000..cd8fb12
--- /dev/null
+++ b/resources/views/home.blade.php
@@ -0,0 +1,75 @@
+
+ {{ 'Home Page' }}
+
+ {{-- Hero Section --}}
+
+
+
+
Welcome to the Blog
+
Explore our latest articles on design, technology, and programming
+
+
+
+
+ {{-- Latest Posts --}}
+
+
+
Latest Posts
+
+
+ {{-- See More Button --}}
+
+
+
+
+ {{-- Featured Categories --}}
+
+
+
Featured Categories
+
+
+
+
+
+
+
+
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php
new file mode 100644
index 0000000..c5ff315
--- /dev/null
+++ b/resources/views/layouts/app.blade.php
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
{{ config('app.name', 'Laravel') }}
+
+
+
+
+
+
+ @vite(['resources/css/app.css', 'resources/js/app.js'])
+
+
+
+ @include('layouts.navigation')
+
+
+ @isset($header)
+
+ @endisset
+
+
+
+ {{ $slot }}
+
+
+
+
diff --git a/resources/views/layouts/guest.blade.php b/resources/views/layouts/guest.blade.php
new file mode 100644
index 0000000..11feb47
--- /dev/null
+++ b/resources/views/layouts/guest.blade.php
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
{{ config('app.name', 'Laravel') }}
+
+
+
+
+
+
+ @vite(['resources/css/app.css', 'resources/js/app.js'])
+
+
+
+
+
diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php
new file mode 100644
index 0000000..c2d3a65
--- /dev/null
+++ b/resources/views/layouts/navigation.blade.php
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ __('Dashboard') }}
+
+
+
+
+
+
+
+
+
+ {{ Auth::user()->name }}
+
+
+
+
+
+
+
+ {{ __('Profile') }}
+
+
+
+
+ @csrf
+
+
+ {{ __('Log Out') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ __('Dashboard') }}
+
+
+
+
+
+
+
{{ Auth::user()->name }}
+
{{ Auth::user()->email }}
+
+
+
+
+ {{ __('Profile') }}
+
+
+
+
+ @csrf
+
+
+ {{ __('Log Out') }}
+
+
+
+
+
+
diff --git a/resources/views/post.blade.php b/resources/views/post.blade.php
new file mode 100644
index 0000000..278988a
--- /dev/null
+++ b/resources/views/post.blade.php
@@ -0,0 +1,31 @@
+
+ {{ $title }}
+
+
+
+
+
+
+
+ {{ $post->body }}
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/posts.blade.php b/resources/views/posts.blade.php
new file mode 100644
index 0000000..805de08
--- /dev/null
+++ b/resources/views/posts.blade.php
@@ -0,0 +1,74 @@
+
+ {{ $title }}
+
+
+
+
+ @if(request('category'))
+
+ @endif
+ @if(request('author'))
+
+ @endif
+
+
+
+
+
+
+
+
diff --git a/resources/views/profile/edit.blade.php b/resources/views/profile/edit.blade.php
new file mode 100644
index 0000000..e0e1d38
--- /dev/null
+++ b/resources/views/profile/edit.blade.php
@@ -0,0 +1,29 @@
+
+
+
+ {{ __('Profile') }}
+
+
+
+
+
+
+
+ @include('profile.partials.update-profile-information-form')
+
+
+
+
+
+ @include('profile.partials.update-password-form')
+
+
+
+
+
+ @include('profile.partials.delete-user-form')
+
+
+
+
+
diff --git a/resources/views/profile/partials/delete-user-form.blade.php b/resources/views/profile/partials/delete-user-form.blade.php
new file mode 100644
index 0000000..edeeb4a
--- /dev/null
+++ b/resources/views/profile/partials/delete-user-form.blade.php
@@ -0,0 +1,55 @@
+
+
+
+ {{ __('Delete Account') }}
+
+
+
+ {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }}
+
+
+
+ {{ __('Delete Account') }}
+
+
+
+ @csrf
+ @method('delete')
+
+
+ {{ __('Are you sure you want to delete your account?') }}
+
+
+
+ {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ __('Cancel') }}
+
+
+
+ {{ __('Delete Account') }}
+
+
+
+
+
diff --git a/resources/views/profile/partials/update-password-form.blade.php b/resources/views/profile/partials/update-password-form.blade.php
new file mode 100644
index 0000000..eaca1ac
--- /dev/null
+++ b/resources/views/profile/partials/update-password-form.blade.php
@@ -0,0 +1,48 @@
+
+
+
+
+ @csrf
+ @method('put')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ __('Save') }}
+
+ @if (session('status') === 'password-updated')
+
{{ __('Saved.') }}
+ @endif
+
+
+
diff --git a/resources/views/profile/partials/update-profile-information-form.blade.php b/resources/views/profile/partials/update-profile-information-form.blade.php
new file mode 100644
index 0000000..5ae3d35
--- /dev/null
+++ b/resources/views/profile/partials/update-profile-information-form.blade.php
@@ -0,0 +1,64 @@
+
+
+
+
+ @csrf
+
+
+
+ @csrf
+ @method('patch')
+
+
+
+
+
+
+
+
+
+
+
+
+ @if ($user instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! $user->hasVerifiedEmail())
+
+
+ {{ __('Your email address is unverified.') }}
+
+
+ {{ __('Click here to re-send the verification email.') }}
+
+
+
+ @if (session('status') === 'verification-link-sent')
+
+ {{ __('A new verification link has been sent to your email address.') }}
+
+ @endif
+
+ @endif
+
+
+
+
{{ __('Save') }}
+
+ @if (session('status') === 'profile-updated')
+
{{ __('Saved.') }}
+ @endif
+
+
+
diff --git a/routes/auth.php b/routes/auth.php
new file mode 100644
index 0000000..1040b51
--- /dev/null
+++ b/routes/auth.php
@@ -0,0 +1,59 @@
+group(function () {
+ Route::get('register', [RegisteredUserController::class, 'create'])
+ ->name('register');
+
+ Route::post('register', [RegisteredUserController::class, 'store']);
+
+ Route::get('login', [AuthenticatedSessionController::class, 'create'])
+ ->name('login');
+
+ Route::post('login', [AuthenticatedSessionController::class, 'store']);
+
+ Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
+ ->name('password.request');
+
+ Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
+ ->name('password.email');
+
+ Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
+ ->name('password.reset');
+
+ Route::post('reset-password', [NewPasswordController::class, 'store'])
+ ->name('password.store');
+});
+
+Route::middleware('auth')->group(function () {
+ Route::get('verify-email', EmailVerificationPromptController::class)
+ ->name('verification.notice');
+
+ Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
+ ->middleware(['signed', 'throttle:6,1'])
+ ->name('verification.verify');
+
+ Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
+ ->middleware('throttle:6,1')
+ ->name('verification.send');
+
+ Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
+ ->name('password.confirm');
+
+ Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
+
+ Route::put('password', [PasswordController::class, 'update'])->name('password.update');
+
+ Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
+ ->name('logout');
+});
diff --git a/routes/console.php b/routes/console.php
new file mode 100644
index 0000000..eff2ed2
--- /dev/null
+++ b/routes/console.php
@@ -0,0 +1,8 @@
+comment(Inspiring::quote());
+})->purpose('Display an inspiring quote')->hourly();
diff --git a/routes/web.php b/routes/web.php
new file mode 100644
index 0000000..5900b9f
--- /dev/null
+++ b/routes/web.php
@@ -0,0 +1,44 @@
+take(3)->get();
+ $categories = Category::all();
+ return view('home', [
+ 'title' => 'Home',
+ 'posts' => $posts,
+ 'categories' => $categories
+ ]);
+});
+
+Route::get('/about', function () {
+ return view('about', ['title' => 'About Page']);
+});
+
+Route::get('/posts', function () {
+ return view('posts', ['title' => 'Blog Page', 'posts' => Post::filter(request(['search', 'category', 'author']))->latest()->get()]);
+});
+
+Route::get('/posts/{post:slug}', function(Post $post){
+ return view('post', ['title' => 'Single Post', 'post' => $post]);
+});
+
+Route::get('authors/{user:username}', function(User $user){
+ return view('posts', ['title' => count($user->posts) . ' Articles by ' . $user->name, 'posts' => $user->posts]);
+});
+
+Route::get('categories/{category:slug}', function(Category $category){
+ return view('posts', ['title' => 'Articles in: ' . $category->name, 'posts' => $category->posts]);
+});
+
+Route::get('/contact', function () {
+ return view('contact' , ['title' => 'Contact Page']);
+});
+
+Route::post('/contact', [ContactController::class, 'store'])->name('contact.store');
diff --git a/storage/app/.gitignore b/storage/app/.gitignore
new file mode 100644
index 0000000..fedb287
--- /dev/null
+++ b/storage/app/.gitignore
@@ -0,0 +1,4 @@
+*
+!private/
+!public/
+!.gitignore
diff --git a/storage/app/private/.gitignore b/storage/app/private/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/storage/app/private/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/storage/app/public/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/storage/debugbar/.gitignore b/storage/debugbar/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/storage/debugbar/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore
new file mode 100644
index 0000000..05c4471
--- /dev/null
+++ b/storage/framework/.gitignore
@@ -0,0 +1,9 @@
+compiled.php
+config.php
+down
+events.scanned.php
+maintenance.php
+routes.php
+routes.scanned.php
+schedule-*
+services.json
diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore
new file mode 100644
index 0000000..01e4a6c
--- /dev/null
+++ b/storage/framework/cache/.gitignore
@@ -0,0 +1,3 @@
+*
+!data/
+!.gitignore
diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/storage/framework/cache/data/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/storage/framework/sessions/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/storage/framework/testing/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/storage/framework/views/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/storage/logs/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..ac5045f
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,60 @@
+/** @type {import('tailwindcss').Config} */
+const defaultTheme = require('tailwindcss/defaultTheme')
+
+export default {
+ darkMode: 'class',
+ content: [
+ "./resources/**/*.blade.php",
+ "./resources/**/*.js",
+ "./resources/**/*.vue",
+ "./node_modules/flowbite/**/*.js"
+ ],
+ theme: {
+ extend: {
+ fontFamily: {
+ sans: ['Inter var', ...defaultTheme.fontFamily.sans],
+ body: [
+ 'Inter',
+ 'ui-sans-serif',
+ 'system-ui',
+ '-apple-system',
+ 'Segoe UI',
+ 'Roboto',
+ 'Helvetica Neue',
+ 'Arial',
+ 'Noto Sans',
+ 'sans-serif',
+ 'Apple Color Emoji',
+ 'Segoe UI Emoji',
+ 'Segoe UI Symbol',
+ 'Noto Color Emoji'
+ ],
+ },
+ colors: {
+ primary: {
+ "50": "#eff6ff",
+ "100": "#dbeafe",
+ "200": "#bfdbfe",
+ "300": "#93c5fd",
+ "400": "#60a5fa",
+ "500": "#3b82f6",
+ "600": "#2563eb",
+ "700": "#1d4ed8",
+ "800": "#1e40af",
+ "900": "#1e3a8a",
+ "950": "#172554"
+ }
+ }
+ },
+ },
+ plugins: [
+ require("flowbite/plugin",
+ require("flowbite-typography")),
+ ],
+ safelist: [
+ "bg-red-100",
+ "bg-green-100",
+ "bg-blue-100",
+ "bg-yellow-100"
+ ],
+};
diff --git a/tests/Feature/Auth/AuthenticationTest.php b/tests/Feature/Auth/AuthenticationTest.php
new file mode 100644
index 0000000..a272b9d
--- /dev/null
+++ b/tests/Feature/Auth/AuthenticationTest.php
@@ -0,0 +1,41 @@
+get('/login');
+
+ $response->assertStatus(200);
+});
+
+test('users can authenticate using the login screen', function () {
+ $user = User::factory()->create();
+
+ $response = $this->post('/login', [
+ 'email' => $user->email,
+ 'password' => 'password',
+ ]);
+
+ $this->assertAuthenticated();
+ $response->assertRedirect(route('dashboard', absolute: false));
+});
+
+test('users can not authenticate with invalid password', function () {
+ $user = User::factory()->create();
+
+ $this->post('/login', [
+ 'email' => $user->email,
+ 'password' => 'wrong-password',
+ ]);
+
+ $this->assertGuest();
+});
+
+test('users can logout', function () {
+ $user = User::factory()->create();
+
+ $response = $this->actingAs($user)->post('/logout');
+
+ $this->assertGuest();
+ $response->assertRedirect('/');
+});
diff --git a/tests/Feature/Auth/EmailVerificationTest.php b/tests/Feature/Auth/EmailVerificationTest.php
new file mode 100644
index 0000000..f282dff
--- /dev/null
+++ b/tests/Feature/Auth/EmailVerificationTest.php
@@ -0,0 +1,46 @@
+unverified()->create();
+
+ $response = $this->actingAs($user)->get('/verify-email');
+
+ $response->assertStatus(200);
+});
+
+test('email can be verified', function () {
+ $user = User::factory()->unverified()->create();
+
+ Event::fake();
+
+ $verificationUrl = URL::temporarySignedRoute(
+ 'verification.verify',
+ now()->addMinutes(60),
+ ['id' => $user->id, 'hash' => sha1($user->email)]
+ );
+
+ $response = $this->actingAs($user)->get($verificationUrl);
+
+ Event::assertDispatched(Verified::class);
+ expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
+ $response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
+});
+
+test('email is not verified with invalid hash', function () {
+ $user = User::factory()->unverified()->create();
+
+ $verificationUrl = URL::temporarySignedRoute(
+ 'verification.verify',
+ now()->addMinutes(60),
+ ['id' => $user->id, 'hash' => sha1('wrong-email')]
+ );
+
+ $this->actingAs($user)->get($verificationUrl);
+
+ expect($user->fresh()->hasVerifiedEmail())->toBeFalse();
+});
diff --git a/tests/Feature/Auth/PasswordConfirmationTest.php b/tests/Feature/Auth/PasswordConfirmationTest.php
new file mode 100644
index 0000000..8a42902
--- /dev/null
+++ b/tests/Feature/Auth/PasswordConfirmationTest.php
@@ -0,0 +1,32 @@
+create();
+
+ $response = $this->actingAs($user)->get('/confirm-password');
+
+ $response->assertStatus(200);
+});
+
+test('password can be confirmed', function () {
+ $user = User::factory()->create();
+
+ $response = $this->actingAs($user)->post('/confirm-password', [
+ 'password' => 'password',
+ ]);
+
+ $response->assertRedirect();
+ $response->assertSessionHasNoErrors();
+});
+
+test('password is not confirmed with invalid password', function () {
+ $user = User::factory()->create();
+
+ $response = $this->actingAs($user)->post('/confirm-password', [
+ 'password' => 'wrong-password',
+ ]);
+
+ $response->assertSessionHasErrors();
+});
diff --git a/tests/Feature/Auth/PasswordResetTest.php b/tests/Feature/Auth/PasswordResetTest.php
new file mode 100644
index 0000000..0504276
--- /dev/null
+++ b/tests/Feature/Auth/PasswordResetTest.php
@@ -0,0 +1,60 @@
+get('/forgot-password');
+
+ $response->assertStatus(200);
+});
+
+test('reset password link can be requested', function () {
+ Notification::fake();
+
+ $user = User::factory()->create();
+
+ $this->post('/forgot-password', ['email' => $user->email]);
+
+ Notification::assertSentTo($user, ResetPassword::class);
+});
+
+test('reset password screen can be rendered', function () {
+ Notification::fake();
+
+ $user = User::factory()->create();
+
+ $this->post('/forgot-password', ['email' => $user->email]);
+
+ Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
+ $response = $this->get('/reset-password/'.$notification->token);
+
+ $response->assertStatus(200);
+
+ return true;
+ });
+});
+
+test('password can be reset with valid token', function () {
+ Notification::fake();
+
+ $user = User::factory()->create();
+
+ $this->post('/forgot-password', ['email' => $user->email]);
+
+ Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
+ $response = $this->post('/reset-password', [
+ 'token' => $notification->token,
+ 'email' => $user->email,
+ 'password' => 'password',
+ 'password_confirmation' => 'password',
+ ]);
+
+ $response
+ ->assertSessionHasNoErrors()
+ ->assertRedirect(route('login'));
+
+ return true;
+ });
+});
diff --git a/tests/Feature/Auth/PasswordUpdateTest.php b/tests/Feature/Auth/PasswordUpdateTest.php
new file mode 100644
index 0000000..e3d1278
--- /dev/null
+++ b/tests/Feature/Auth/PasswordUpdateTest.php
@@ -0,0 +1,40 @@
+create();
+
+ $response = $this
+ ->actingAs($user)
+ ->from('/profile')
+ ->put('/password', [
+ 'current_password' => 'password',
+ 'password' => 'new-password',
+ 'password_confirmation' => 'new-password',
+ ]);
+
+ $response
+ ->assertSessionHasNoErrors()
+ ->assertRedirect('/profile');
+
+ $this->assertTrue(Hash::check('new-password', $user->refresh()->password));
+});
+
+test('correct password must be provided to update password', function () {
+ $user = User::factory()->create();
+
+ $response = $this
+ ->actingAs($user)
+ ->from('/profile')
+ ->put('/password', [
+ 'current_password' => 'wrong-password',
+ 'password' => 'new-password',
+ 'password_confirmation' => 'new-password',
+ ]);
+
+ $response
+ ->assertSessionHasErrorsIn('updatePassword', 'current_password')
+ ->assertRedirect('/profile');
+});
diff --git a/tests/Feature/Auth/RegistrationTest.php b/tests/Feature/Auth/RegistrationTest.php
new file mode 100644
index 0000000..352ca78
--- /dev/null
+++ b/tests/Feature/Auth/RegistrationTest.php
@@ -0,0 +1,19 @@
+get('/register');
+
+ $response->assertStatus(200);
+});
+
+test('new users can register', function () {
+ $response = $this->post('/register', [
+ 'name' => 'Test User',
+ 'email' => 'test@example.com',
+ 'password' => 'password',
+ 'password_confirmation' => 'password',
+ ]);
+
+ $this->assertAuthenticated();
+ $response->assertRedirect(route('dashboard', absolute: false));
+});
diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php
new file mode 100644
index 0000000..8b5843f
--- /dev/null
+++ b/tests/Feature/ExampleTest.php
@@ -0,0 +1,7 @@
+get('/');
+
+ $response->assertStatus(200);
+});
diff --git a/tests/Feature/ProfileTest.php b/tests/Feature/ProfileTest.php
new file mode 100644
index 0000000..1536458
--- /dev/null
+++ b/tests/Feature/ProfileTest.php
@@ -0,0 +1,85 @@
+create();
+
+ $response = $this
+ ->actingAs($user)
+ ->get('/profile');
+
+ $response->assertOk();
+});
+
+test('profile information can be updated', function () {
+ $user = User::factory()->create();
+
+ $response = $this
+ ->actingAs($user)
+ ->patch('/profile', [
+ 'name' => 'Test User',
+ 'email' => 'test@example.com',
+ ]);
+
+ $response
+ ->assertSessionHasNoErrors()
+ ->assertRedirect('/profile');
+
+ $user->refresh();
+
+ $this->assertSame('Test User', $user->name);
+ $this->assertSame('test@example.com', $user->email);
+ $this->assertNull($user->email_verified_at);
+});
+
+test('email verification status is unchanged when the email address is unchanged', function () {
+ $user = User::factory()->create();
+
+ $response = $this
+ ->actingAs($user)
+ ->patch('/profile', [
+ 'name' => 'Test User',
+ 'email' => $user->email,
+ ]);
+
+ $response
+ ->assertSessionHasNoErrors()
+ ->assertRedirect('/profile');
+
+ $this->assertNotNull($user->refresh()->email_verified_at);
+});
+
+test('user can delete their account', function () {
+ $user = User::factory()->create();
+
+ $response = $this
+ ->actingAs($user)
+ ->delete('/profile', [
+ 'password' => 'password',
+ ]);
+
+ $response
+ ->assertSessionHasNoErrors()
+ ->assertRedirect('/');
+
+ $this->assertGuest();
+ $this->assertNull($user->fresh());
+});
+
+test('correct password must be provided to delete account', function () {
+ $user = User::factory()->create();
+
+ $response = $this
+ ->actingAs($user)
+ ->from('/profile')
+ ->delete('/profile', [
+ 'password' => 'wrong-password',
+ ]);
+
+ $response
+ ->assertSessionHasErrorsIn('userDeletion', 'password')
+ ->assertRedirect('/profile');
+
+ $this->assertNotNull($user->fresh());
+});
diff --git a/tests/Pest.php b/tests/Pest.php
new file mode 100644
index 0000000..40d096b
--- /dev/null
+++ b/tests/Pest.php
@@ -0,0 +1,47 @@
+extend(Tests\TestCase::class)
+ ->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
+ ->in('Feature');
+
+/*
+|--------------------------------------------------------------------------
+| Expectations
+|--------------------------------------------------------------------------
+|
+| When you're writing tests, you often need to check that values meet certain conditions. The
+| "expect()" function gives you access to a set of "expectations" methods that you can use
+| to assert different things. Of course, you may extend the Expectation API at any time.
+|
+*/
+
+expect()->extend('toBeOne', function () {
+ return $this->toBe(1);
+});
+
+/*
+|--------------------------------------------------------------------------
+| Functions
+|--------------------------------------------------------------------------
+|
+| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
+| project that you don't want to repeat in every file. Here you can also expose helpers as
+| global functions to help you to reduce the number of lines of code in your test files.
+|
+*/
+
+function something()
+{
+ // ..
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
new file mode 100644
index 0000000..fe1ffc2
--- /dev/null
+++ b/tests/TestCase.php
@@ -0,0 +1,10 @@
+toBeTrue();
+});
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..421b569
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite';
+import laravel from 'laravel-vite-plugin';
+
+export default defineConfig({
+ plugins: [
+ laravel({
+ input: ['resources/css/app.css', 'resources/js/app.js'],
+ refresh: true,
+ }),
+ ],
+});