Skip to content

Commit eb596cb

Browse files
authored
Login (#4)
* Update app.js * Add separate Guest and App layout components * Create LoginController.php * Add Vue page component for logging in * Add tests for LoginController
1 parent 14e2911 commit eb596cb

File tree

11 files changed

+409
-7
lines changed

11 files changed

+409
-7
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Http\Requests\Login\LoginStore;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Support\Facades\App;
8+
use Illuminate\Support\Facades\Auth;
9+
use Illuminate\Support\Facades\Redirect;
10+
use Illuminate\Validation\ValidationException;
11+
use Inertia\Inertia;
12+
13+
class LoginController extends Controller
14+
{
15+
public function show(Request $request)
16+
{
17+
$isProd = App::environment('production');
18+
19+
return Inertia::render('Login', [
20+
'email' => !$isProd ? \env('SEED_ADMIN_EMAIL') : '',
21+
'password' => !$isProd ? '12345' : '',
22+
'remember' => !$isProd ? true : false,
23+
'redirect' => $request->query('redirect', ''),
24+
]);
25+
}
26+
27+
public function store(LoginStore $request)
28+
{
29+
$attempt = Auth::attempt([
30+
'email' => $request->validated('email'),
31+
'password' => $request->validated('password'),
32+
], $request->validated('remember'));
33+
34+
if (!$attempt) {
35+
throw ValidationException::withMessages([
36+
'email' => ['The provided credentials are incorrect.'],
37+
]);
38+
}
39+
40+
$request->session()->regenerate();
41+
42+
if ($request->validated('redirect')) {
43+
return Redirect::to($request->validated('redirect'));
44+
}
45+
46+
return Redirect::route('home');
47+
}
48+
49+
public function destroy(Request $request)
50+
{
51+
Auth::logout();
52+
53+
$request->session()->invalidate();
54+
$request->session()->regenerateToken();
55+
56+
return Redirect::route('login');
57+
}
58+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace App\Http\Requests\Login;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
7+
class LoginStore extends FormRequest
8+
{
9+
public function rules()
10+
{
11+
return [
12+
'email' => ['required', 'email', 'exists:users'],
13+
'password' => ['required'],
14+
'remember' => ['boolean'],
15+
'redirect' => ['nullable', 'string'],
16+
];
17+
}
18+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<template>
2+
<button :class="getCssClasses()" @click="$emit('activated', true)">
3+
<span class="btn__text" v-text="text"></span>
4+
</button>
5+
</template>
6+
7+
<script>
8+
export default {
9+
emits: ["activated"],
10+
11+
props: {
12+
text: {
13+
type: String,
14+
default: "Submit",
15+
},
16+
},
17+
18+
data() {
19+
return {
20+
cssClasses: ["btn"],
21+
};
22+
},
23+
24+
methods: {
25+
getCssClasses() {
26+
return this.cssClasses.join(" ");
27+
},
28+
},
29+
};
30+
</script>

resources/js/Layouts/App.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<template>
2+
<Head>
3+
<title></title>
4+
</Head>
5+
6+
<div class="app-page">
7+
<div class="app-page__inner">
8+
<slot />
9+
</div>
10+
</div>
11+
</template>
12+
13+
<script>
14+
export default {
15+
name: "App Layout",
16+
17+
data() {
18+
return {};
19+
},
20+
};
21+
</script>

resources/js/Layouts/Guest.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<template>
2+
<Head>
3+
<title></title>
4+
</Head>
5+
6+
<div class="guest-page">
7+
<div class="guest-page__inner">
8+
<slot />
9+
</div>
10+
</div>
11+
</template>
12+
13+
<script>
14+
export default {
15+
name: "Guest Layout",
16+
17+
data() {
18+
return {};
19+
},
20+
};
21+
</script>

resources/js/Pages/Login.vue

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<template>
2+
<Head :title="title" />
3+
4+
<h1 v-text="title"></h1>
5+
6+
<form class="form" @submit.prevent="login">
7+
<div class="form__section">
8+
<div class="form__row">
9+
<div class="form__item">
10+
<label class="form__label" for="email">Email</label>
11+
<input
12+
id="email"
13+
type="email"
14+
v-model="form.email"
15+
tabindex="1"
16+
required
17+
/>
18+
</div>
19+
</div>
20+
21+
<div class="form__row">
22+
<div class="form__item">
23+
<label class="form__label" for="password">Password</label>
24+
<input
25+
id="password"
26+
type="password"
27+
v-model="form.password"
28+
tabindex="2"
29+
required
30+
/>
31+
</div>
32+
</div>
33+
34+
<div class="form__row">
35+
<div class="form__item">
36+
<label
37+
class="form__label form__label--inline"
38+
for="remember"
39+
>
40+
<input
41+
id="remember"
42+
type="checkbox"
43+
v-model="form.remember"
44+
tabindex="3"
45+
/>
46+
Remember
47+
</label>
48+
</div>
49+
</div>
50+
51+
<div class="form__row">
52+
<div class="form__item">
53+
<AppButton
54+
text="Login"
55+
tabindex="4"
56+
:disabled="form.processing"
57+
/>
58+
</div>
59+
</div>
60+
</div>
61+
</form>
62+
</template>
63+
64+
<script>
65+
import GuestLayout from "@js/layouts/Guest.vue";
66+
import AppButton from "@js/components/AppButton.vue";
67+
68+
import { useForm } from "@inertiajs/inertia-vue3";
69+
70+
export default {
71+
layout: GuestLayout,
72+
73+
components: {
74+
AppButton,
75+
},
76+
77+
props: {
78+
email: {
79+
type: String,
80+
default: "",
81+
},
82+
password: {
83+
type: String,
84+
default: "",
85+
},
86+
remember: {
87+
type: Boolean,
88+
default: false,
89+
},
90+
redirect: {
91+
type: String,
92+
default: "",
93+
},
94+
},
95+
96+
data() {
97+
return {
98+
title: "Login",
99+
form: useForm({
100+
email: this.email,
101+
password: this.password,
102+
remember: this.remember,
103+
redirect: this.redirect,
104+
}),
105+
};
106+
},
107+
108+
methods: {
109+
login() {
110+
let form = this.form;
111+
form.post(route("login.store"), {
112+
...form,
113+
...{
114+
onSuccess: () => {
115+
form.clearErrors();
116+
},
117+
},
118+
});
119+
},
120+
},
121+
};
122+
</script>

resources/js/app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
88

99
createInertiaApp({
1010
resolve: async name => {
11-
let page = resolvePageComponent(`./pages/${name}.vue`, import.meta.glob('./pages/**/*.vue'));
11+
let page = resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue'));
1212

1313
return page;
1414
},
@@ -30,7 +30,7 @@ createInertiaApp({
3030
.mount(el);
3131
},
3232

33-
title: title => title ? title + ' | App' : 'App',
33+
title: title => title ? `${title} | Template` : 'Template',
3434
});
3535

3636
InertiaProgress.init({

resources/js/pages/Index.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
</template>
66

77
<script>
8+
import AppLayout from "@js/layouts/App.vue";
9+
810
export default {
11+
layout: AppLayout,
12+
913
components: {
1014
//
1115
},

routes/web.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
<?php
22

33
use App\Http\Controllers\HomeController;
4+
use App\Http\Controllers\LoginController;
45
use Illuminate\Support\Facades\Route;
56

6-
Route::get('/', [HomeController::class, 'index'])->name('home');
7+
Route::middleware('guest')->group(function () {
8+
Route::controller(LoginController::class)->group(function () {
9+
Route::get('login', 'show')->name('login');
10+
Route::post('login', 'store')->name('login.store');
11+
});
12+
});
13+
14+
Route::middleware(['auth'])->group(function () {
15+
Route::post('logout', [LoginController::class, 'destroy'])->name('logout');
16+
});
17+
18+
Route::middleware('auth')->group(function () {
19+
Route::get('/', [HomeController::class, 'index'])->name('home');
20+
});
Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1-
2-
31
<?php
42

3+
use App\Models\User;
54
use Inertia\Testing\AssertableInertia as Assert;
65

6+
use function Pest\Laravel\actingAs;
77
use function Pest\Laravel\get;
88

9-
test("Users can access \"index\" route", function () {
10-
get(route('home'))
9+
test("Authenticated users can access \"index\" route", function () {
10+
/**
11+
* @var User
12+
*/
13+
$user = User::factory()->create();
14+
15+
actingAs($user)
16+
->get(route('home'))
1117
->assertStatus(200)
1218
->assertSessionHasNoErrors()
1319
->assertInertia(
1420
fn (Assert $page) => $page
1521
->component('Index')
1622
);
1723
});
24+
25+
test("Guests can't access \"index\" route", function () {
26+
get(route('home'))
27+
->assertRedirect(route('login'))
28+
->assertSessionHasNoErrors();
29+
});

0 commit comments

Comments
 (0)