Authentication dan Layouting Admin
This commit is contained in:
parent
177ed11260
commit
fcaf956cab
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Auth\LoginRequest;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use App\Http\Controllers\Auth\AuthenticatedSessionController;
|
||||||
|
|
||||||
|
Route::get('/', function () {
|
||||||
|
return view('welcome');
|
||||||
|
});
|
||||||
|
|
||||||
|
class AuthenticatedSessionController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the login view.
|
||||||
|
*/
|
||||||
|
public function create(): View
|
||||||
|
{
|
||||||
|
return view('auth.login');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming authentication request.
|
||||||
|
*/
|
||||||
|
public function store(LoginRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
// Coba login manual agar bisa tangani error
|
||||||
|
if (!Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'email' => ['Email atau password salah.'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->session()->regenerate();
|
||||||
|
|
||||||
|
return redirect()->intended('/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy an authenticated session.
|
||||||
|
*/
|
||||||
|
public function destroy(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
Auth::guard('web')->logout();
|
||||||
|
|
||||||
|
$request->session()->invalidate();
|
||||||
|
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class ConfirmablePasswordController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Show the confirm password view.
|
||||||
|
*/
|
||||||
|
public function show(): View
|
||||||
|
{
|
||||||
|
return view('auth.confirm-password');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm the user's password.
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
if (! Auth::guard('web')->validate([
|
||||||
|
'email' => $request->user()->email,
|
||||||
|
'password' => $request->password,
|
||||||
|
])) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'password' => __('auth.password'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->session()->put('auth.password_confirmed_at', time());
|
||||||
|
|
||||||
|
return redirect()->intended(route('dashboard', absolute: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class EmailVerificationNotificationController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Send a new email verification notification.
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
if ($request->user()->hasVerifiedEmail()) {
|
||||||
|
return redirect()->intended(route('dashboard', absolute: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->user()->sendEmailVerificationNotification();
|
||||||
|
|
||||||
|
return back()->with('status', 'verification-link-sent');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class EmailVerificationPromptController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the email verification prompt.
|
||||||
|
*/
|
||||||
|
public function __invoke(Request $request): RedirectResponse|View
|
||||||
|
{
|
||||||
|
return $request->user()->hasVerifiedEmail()
|
||||||
|
? redirect()->intended(route('dashboard', absolute: false))
|
||||||
|
: view('auth.verify-email');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Events\PasswordReset;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Validation\Rules;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class NewPasswordController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the password reset view.
|
||||||
|
*/
|
||||||
|
public function create(Request $request): View
|
||||||
|
{
|
||||||
|
return view('auth.reset-password', ['request' => $request]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming new password request.
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'token' => ['required'],
|
||||||
|
'email' => ['required', 'email'],
|
||||||
|
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Here we will attempt to reset the user's password. If it is successful we
|
||||||
|
// will update the password on an actual user model and persist it to the
|
||||||
|
// database. Otherwise we will parse the error and return the response.
|
||||||
|
$status = Password::reset(
|
||||||
|
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||||
|
function (User $user) use ($request) {
|
||||||
|
$user->forceFill([
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
'remember_token' => Str::random(60),
|
||||||
|
])->save();
|
||||||
|
|
||||||
|
event(new PasswordReset($user));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the password was successfully reset, we will redirect the user back to
|
||||||
|
// the application's home authenticated view. If there is an error we can
|
||||||
|
// redirect them back to where they came from with their error message.
|
||||||
|
return $status == Password::PASSWORD_RESET
|
||||||
|
? redirect()->route('login')->with('status', __($status))
|
||||||
|
: back()->withInput($request->only('email'))
|
||||||
|
->withErrors(['email' => __($status)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
class PasswordController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Update the user's password.
|
||||||
|
*/
|
||||||
|
public function update(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$validated = $request->validateWithBag('updatePassword', [
|
||||||
|
'current_password' => ['required', 'current_password'],
|
||||||
|
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request->user()->update([
|
||||||
|
'password' => Hash::make($validated['password']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return back()->with('status', 'password-updated');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Password;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class PasswordResetLinkController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the password reset link request view.
|
||||||
|
*/
|
||||||
|
public function create(): View
|
||||||
|
{
|
||||||
|
return view('auth.forgot-password');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming password reset link request.
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'email' => ['required', 'email'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// We will send the password reset link to this user. Once we have attempted
|
||||||
|
// to send the link, we will examine the response then see the message we
|
||||||
|
// need to show to the user. Finally, we'll send out a proper response.
|
||||||
|
$status = Password::sendResetLink(
|
||||||
|
$request->only('email')
|
||||||
|
);
|
||||||
|
|
||||||
|
return $status == Password::RESET_LINK_SENT
|
||||||
|
? back()->with('status', __($status))
|
||||||
|
: back()->withInput($request->only('email'))
|
||||||
|
->withErrors(['email' => __($status)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Events\Registered;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Validation\Rules;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class RegisteredUserController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the registration view.
|
||||||
|
*/
|
||||||
|
public function create(): View
|
||||||
|
{
|
||||||
|
return view('auth.register');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming registration request.
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||||
|
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
]);
|
||||||
|
|
||||||
|
event(new Registered($user));
|
||||||
|
|
||||||
|
Auth::login($user);
|
||||||
|
|
||||||
|
return redirect(route('dashboard', absolute: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Auth\Events\Verified;
|
||||||
|
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
|
||||||
|
class VerifyEmailController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Mark the authenticated user's email address as verified.
|
||||||
|
*/
|
||||||
|
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
if ($request->user()->hasVerifiedEmail()) {
|
||||||
|
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->user()->markEmailAsVerified()) {
|
||||||
|
event(new Verified($request->user()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\ProfileUpdateRequest;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Redirect;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class ProfileController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the user's profile form.
|
||||||
|
*/
|
||||||
|
public function edit(Request $request): View
|
||||||
|
{
|
||||||
|
return view('profile.edit', [
|
||||||
|
'user' => $request->user(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the user's profile information.
|
||||||
|
*/
|
||||||
|
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$request->user()->fill($request->validated());
|
||||||
|
|
||||||
|
if ($request->user()->isDirty('email')) {
|
||||||
|
$request->user()->email_verified_at = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->user()->save();
|
||||||
|
|
||||||
|
return Redirect::route('profile.edit')->with('status', 'profile-updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the user's account.
|
||||||
|
*/
|
||||||
|
public function destroy(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$request->validateWithBag('userDeletion', [
|
||||||
|
'password' => ['required', 'current_password'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
Auth::logout();
|
||||||
|
|
||||||
|
$user->delete();
|
||||||
|
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
|
return Redirect::to('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,7 +42,6 @@ class SiswaController extends Controller
|
||||||
return view('siswa.edit', compact('siswa', 'kelases'));
|
return view('siswa.edit', compact('siswa', 'kelases'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
|
|
@ -58,6 +57,17 @@ class SiswaController extends Controller
|
||||||
return redirect()->route('siswa.index')->with('success', 'Data siswa berhasil diupdate!');
|
return redirect()->route('siswa.index')->with('success', 'Data siswa berhasil diupdate!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
$siswa = Siswa::with('kelas')->find($id);
|
||||||
|
|
||||||
|
if (!$siswa) {
|
||||||
|
return view('siswa.notfound', ['id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('siswa.detail', compact('siswa'));
|
||||||
|
}
|
||||||
|
|
||||||
public function destroy($id)
|
public function destroy($id)
|
||||||
{
|
{
|
||||||
$siswa = Siswa::findOrFail($id);
|
$siswa = Siswa::findOrFail($id);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Auth;
|
||||||
|
|
||||||
|
use Illuminate\Auth\Events\Lockout;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class LoginRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email' => ['required', 'string', 'email'],
|
||||||
|
'password' => ['required', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to authenticate the request's credentials.
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public function authenticate(): void
|
||||||
|
{
|
||||||
|
$this->ensureIsNotRateLimited();
|
||||||
|
|
||||||
|
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
|
||||||
|
RateLimiter::hit($this->throttleKey());
|
||||||
|
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'email' => trans('auth.failed'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimiter::clear($this->throttleKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the login request is not rate limited.
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Validation\ValidationException
|
||||||
|
*/
|
||||||
|
public function ensureIsNotRateLimited(): void
|
||||||
|
{
|
||||||
|
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event(new Lockout($this));
|
||||||
|
|
||||||
|
$seconds = RateLimiter::availableIn($this->throttleKey());
|
||||||
|
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'email' => trans('auth.throttle', [
|
||||||
|
'seconds' => $seconds,
|
||||||
|
'minutes' => ceil($seconds / 60),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the rate limiting throttle key for the request.
|
||||||
|
*/
|
||||||
|
public function throttleKey(): string
|
||||||
|
{
|
||||||
|
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ProfileUpdateRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'email' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'lowercase',
|
||||||
|
'email',
|
||||||
|
'max:255',
|
||||||
|
Rule::unique(User::class)->ignore($this->user()->id),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class AppLayout extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('layouts.app');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class GuestLayout extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represents the component.
|
||||||
|
*/
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('layouts.guest');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
|
|
||||||
return [
|
return [
|
||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
|
App\Providers\RouteServiceProvider::class,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,14 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
|
"laravel/breeze": "^2.3",
|
||||||
"laravel/pail": "^1.2.2",
|
"laravel/pail": "^1.2.2",
|
||||||
"laravel/pint": "^1.13",
|
"laravel/pint": "^1.13",
|
||||||
"laravel/sail": "^1.41",
|
"laravel/sail": "^1.41",
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"nunomaduro/collision": "^8.6",
|
"nunomaduro/collision": "^8.6",
|
||||||
"phpunit/phpunit": "^11.5.3"
|
"pestphp/pest": "^3.8",
|
||||||
|
"pestphp/pest-plugin-laravel": "^3.2"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,8 +3,8 @@
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
{
|
{
|
||||||
|
|
@ -13,7 +13,11 @@ class DatabaseSeeder extends Seeder
|
||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// User::factory(10)->create();
|
// Buat akun user manual
|
||||||
|
User::create([
|
||||||
|
'name' => 'Syifa',
|
||||||
|
'email' => 'syifa@gmail.com',
|
||||||
|
'password' => Hash::make('syifapkl123'), // password HARUS di-hash
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -7,11 +7,15 @@
|
||||||
"dev": "vite"
|
"dev": "vite"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
|
"alpinejs": "^3.4.2",
|
||||||
|
"autoprefixer": "^10.4.2",
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"laravel-vite-plugin": "^1.2.0",
|
"laravel-vite-plugin": "^1.2.0",
|
||||||
"tailwindcss": "^4.0.0",
|
"postcss": "^8.4.31",
|
||||||
|
"tailwindcss": "^3.1.0",
|
||||||
"vite": "^6.2.4"
|
"vite": "^6.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
|
|
@ -1,11 +1,3 @@
|
||||||
@import 'tailwindcss';
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
@tailwind utilities;
|
||||||
@source '../../storage/framework/views/*.php';
|
|
||||||
@source '../**/*.blade.php';
|
|
||||||
@source '../**/*.js';
|
|
||||||
|
|
||||||
@theme {
|
|
||||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
|
||||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,7 @@
|
||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
|
|
||||||
|
import Alpine from 'alpinejs';
|
||||||
|
|
||||||
|
window.Alpine = Alpine;
|
||||||
|
|
||||||
|
Alpine.start();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<footer class="main-footer text-sm text-center">
|
||||||
|
<strong>© {{ date('Y') }} Admin Panel</strong> All rights reserved.
|
||||||
|
</footer>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
|
||||||
|
<!-- Tombol Toggle Sidebar -->
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-widget="pushmenu" href="#" role="button">
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- User Dropdown di Kanan -->
|
||||||
|
<ul class="navbar-nav ml-auto">
|
||||||
|
<!-- User Dropdown -->
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link" data-toggle="dropdown" href="#">
|
||||||
|
{{ Auth::user()->name }} <i class="fas fa-caret-down ml-1"></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a href="{{ route('profile.edit') }}" class="dropdown-item">Profile</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<form method="POST" action="{{ route('logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button class="dropdown-item text-danger" onclick="event.preventDefault(); this.closest('form').submit();">
|
||||||
|
Log Out
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
<aside class="main-sidebar elevation-4" style="background-color: #253244ff; color: #fff;">
|
||||||
|
|
||||||
|
<!-- Brand -->
|
||||||
|
<a href="{{ route('dashboard') }}" class="brand-link">
|
||||||
|
<i class="fas fa-laravel mx-2"></i>
|
||||||
|
<span class="brand-text font-weight-light">Halaman Admin</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div class="sidebar">
|
||||||
|
<nav>
|
||||||
|
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
|
||||||
|
|
||||||
|
<!-- Sidebar User Panel -->
|
||||||
|
<div class="user-panel d-flex flex-column align-items-center pt-3 pb-2">
|
||||||
|
<div class="image">
|
||||||
|
<img src="{{ asset('images/default.jpg') }}"
|
||||||
|
alt="User Image"
|
||||||
|
class="img-circle elevation-2"
|
||||||
|
style="width: 60px; height: 60px; object-fit: cover; border: 2px solid #fff; margin-left: -13px;">
|
||||||
|
</div>
|
||||||
|
<div class="info mt-3">
|
||||||
|
<span class="text-white fw-bold" style="font-size: 14px;">
|
||||||
|
{{ Auth::user()->name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dashboard -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="{{ route('dashboard') }}" class="nav-link {{ request()->routeIs('dashboard') ? 'active' : '' }}">
|
||||||
|
<i class="nav-icon fas fa-tachometer-alt"></i>
|
||||||
|
<p>Dashboard</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Master Data Dropdown -->
|
||||||
|
<li class="nav-item has-treeview {{ request()->is('siswa*') || request()->is('guru*') || request()->is('kelas*') ? 'menu-open' : '' }}">
|
||||||
|
<a href="#" class="nav-link">
|
||||||
|
<i class="nav-icon fas fa-database"></i>
|
||||||
|
<p>
|
||||||
|
Master Data
|
||||||
|
<i class="right fas fa-angle-left"></i>
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
<ul class="nav nav-treeview">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="{{ route('siswa.index') }}" class="nav-link {{ request()->is('siswa*') ? 'active' : '' }}">
|
||||||
|
<i class="fas fa-user-graduate nav-icon"></i>
|
||||||
|
<p>Data Siswa</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{-- Tambah menu lain seperti Guru/Kelas di sini --}}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Logout (Di luar Master Data) -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="{{ route('logout') }}" class="nav-link"
|
||||||
|
onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
|
||||||
|
<i class="nav-icon fas fa-sign-out-alt"></i>
|
||||||
|
<p>Logout</p>
|
||||||
|
</a>
|
||||||
|
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
|
||||||
|
@csrf
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<x-guest-layout>
|
||||||
|
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('password.confirm') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<div>
|
||||||
|
<x-input-label for="password" :value="__('Password')" />
|
||||||
|
|
||||||
|
<x-text-input id="password" class="block mt-1 w-full"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
required autocomplete="current-password" />
|
||||||
|
|
||||||
|
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end mt-4">
|
||||||
|
<x-primary-button>
|
||||||
|
{{ __('Confirm') }}
|
||||||
|
</x-primary-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</x-guest-layout>
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<x-guest-layout>
|
||||||
|
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Session Status -->
|
||||||
|
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('password.email') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<!-- Email Address -->
|
||||||
|
<div>
|
||||||
|
<x-input-label for="email" :value="__('Email')" />
|
||||||
|
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus />
|
||||||
|
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end mt-4">
|
||||||
|
<x-primary-button>
|
||||||
|
{{ __('Email Password Reset Link') }}
|
||||||
|
</x-primary-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</x-guest-layout>
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
<x-guest-layout>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: linear-gradient(135deg, #c2e9fb, #a1c4fd); /* gradasi biru kalem */
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth {
|
||||||
|
background-color: white;
|
||||||
|
padding: 2.5rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth h1 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #60a5fa;
|
||||||
|
box-shadow: 0 0 0 2px #bfdbfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-auth {
|
||||||
|
background-color: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.75rem;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-auth:hover {
|
||||||
|
background-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth a {
|
||||||
|
color: #2563eb;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth small {
|
||||||
|
display: block;
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="card-auth">
|
||||||
|
<h1 class="font-bold">Masuk ke Halaman Data Siswa</h1>
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
<div style="color: red; margin-bottom: 1rem;">
|
||||||
|
@foreach ($errors->all() as $error)
|
||||||
|
<div>{{ $error }}</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('login') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<input type="text" name="name" placeholder="Nama Lengkap" required>
|
||||||
|
<input type="email" name="email" placeholder="Email" required>
|
||||||
|
<input type="password" name="password" placeholder="Password" required>
|
||||||
|
|
||||||
|
<button class="btn-auth" type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<small>
|
||||||
|
Belum punya akun? <a href="{{ route('register') }}">Daftar di sini</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</x-guest-layout>
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
<x-guest-layout>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: linear-gradient(135deg, #c2e9fb, #a1c4fd); /* gradasi biru pastel */
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth {
|
||||||
|
background-color: white;
|
||||||
|
padding: 2.5rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth h1 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #60a5fa;
|
||||||
|
box-shadow: 0 0 0 2px #bfdbfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-auth {
|
||||||
|
background-color: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.75rem;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-auth:hover {
|
||||||
|
background-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth a {
|
||||||
|
color: #2563eb;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-auth small {
|
||||||
|
display: block;
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="card-auth">
|
||||||
|
<h1 class="font-bold">Daftar Akun Data Siswa</h1>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('register') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<input type="text" name="name" placeholder="Nama Lengkap" required autofocus>
|
||||||
|
<input type="email" name="email" placeholder="Email" required>
|
||||||
|
<input type="password" name="password" placeholder="Password" required>
|
||||||
|
<input type="password" name="password_confirmation" placeholder="Konfirmasi Password" required>
|
||||||
|
|
||||||
|
<button class="btn-auth" type="submit">Daftar</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<small>
|
||||||
|
Sudah punya akun? <a href="{{ route('login') }}">Login di sini</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</x-guest-layout>
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<x-guest-layout>
|
||||||
|
<form method="POST" action="{{ route('password.store') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<!-- Password Reset Token -->
|
||||||
|
<input type="hidden" name="token" value="{{ $request->route('token') }}">
|
||||||
|
|
||||||
|
<!-- Email Address -->
|
||||||
|
<div>
|
||||||
|
<x-input-label for="email" :value="__('Email')" />
|
||||||
|
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email', $request->email)" required autofocus autocomplete="username" />
|
||||||
|
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<x-input-label for="password" :value="__('Password')" />
|
||||||
|
<x-text-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
|
||||||
|
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirm Password -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||||
|
|
||||||
|
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||||
|
type="password"
|
||||||
|
name="password_confirmation" required autocomplete="new-password" />
|
||||||
|
|
||||||
|
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end mt-4">
|
||||||
|
<x-primary-button>
|
||||||
|
{{ __('Reset Password') }}
|
||||||
|
</x-primary-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</x-guest-layout>
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<x-guest-layout>
|
||||||
|
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (session('status') == 'verification-link-sent')
|
||||||
|
<div class="mb-4 font-medium text-sm text-green-600 dark:text-green-400">
|
||||||
|
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="mt-4 flex items-center justify-between">
|
||||||
|
<form method="POST" action="{{ route('verification.send') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-primary-button>
|
||||||
|
{{ __('Resend Verification Email') }}
|
||||||
|
</x-primary-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('logout') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<button type="submit" class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
|
||||||
|
{{ __('Log Out') }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</x-guest-layout>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
|
||||||
|
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
|
|
@ -0,0 +1,7 @@
|
||||||
|
@props(['status'])
|
||||||
|
|
||||||
|
@if ($status)
|
||||||
|
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600 dark:text-green-400']) }}>
|
||||||
|
{{ $status }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150']) }}>
|
||||||
|
{{ $slot }}
|
||||||
|
</button>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white dark:bg-gray-700'])
|
||||||
|
|
||||||
|
@php
|
||||||
|
$alignmentClasses = match ($align) {
|
||||||
|
'left' => 'ltr:origin-top-left rtl:origin-top-right start-0',
|
||||||
|
'top' => 'origin-top',
|
||||||
|
default => 'ltr:origin-top-right rtl:origin-top-left end-0',
|
||||||
|
};
|
||||||
|
|
||||||
|
$width = match ($width) {
|
||||||
|
'48' => 'w-48',
|
||||||
|
default => $width,
|
||||||
|
};
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
||||||
|
<div @click="open = ! open">
|
||||||
|
{{ $trigger }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="open"
|
||||||
|
x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-75"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
|
class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
|
||||||
|
style="display: none;"
|
||||||
|
@click="open = false">
|
||||||
|
<div class="rounded-md ring-1 ring-black ring-opacity-5 {{ $contentClasses }}">
|
||||||
|
{{ $content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
@props(['messages'])
|
||||||
|
|
||||||
|
@if ($messages)
|
||||||
|
<ul {{ $attributes->merge(['class' => 'text-sm text-red-600 dark:text-red-400 space-y-1']) }}>
|
||||||
|
@foreach ((array) $messages as $message)
|
||||||
|
<li>{{ $message }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
@endif
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
@props(['value'])
|
||||||
|
|
||||||
|
<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-gray-700 dark:text-gray-300']) }}>
|
||||||
|
{{ $value ?? $slot }}
|
||||||
|
</label>
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
@props([
|
||||||
|
'name',
|
||||||
|
'show' => false,
|
||||||
|
'maxWidth' => '2xl'
|
||||||
|
])
|
||||||
|
|
||||||
|
@php
|
||||||
|
$maxWidth = [
|
||||||
|
'sm' => 'sm:max-w-sm',
|
||||||
|
'md' => 'sm:max-w-md',
|
||||||
|
'lg' => 'sm:max-w-lg',
|
||||||
|
'xl' => 'sm:max-w-xl',
|
||||||
|
'2xl' => 'sm:max-w-2xl',
|
||||||
|
][$maxWidth];
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div
|
||||||
|
x-data="{
|
||||||
|
show: @js($show),
|
||||||
|
focusables() {
|
||||||
|
// All focusable element types...
|
||||||
|
let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
|
||||||
|
return [...$el.querySelectorAll(selector)]
|
||||||
|
// All non-disabled elements...
|
||||||
|
.filter(el => ! el.hasAttribute('disabled'))
|
||||||
|
},
|
||||||
|
firstFocusable() { return this.focusables()[0] },
|
||||||
|
lastFocusable() { return this.focusables().slice(-1)[0] },
|
||||||
|
nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
|
||||||
|
prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
|
||||||
|
nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
|
||||||
|
prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
|
||||||
|
}"
|
||||||
|
x-init="$watch('show', value => {
|
||||||
|
if (value) {
|
||||||
|
document.body.classList.add('overflow-y-hidden');
|
||||||
|
{{ $attributes->has('focusable') ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' }}
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('overflow-y-hidden');
|
||||||
|
}
|
||||||
|
})"
|
||||||
|
x-on:open-modal.window="$event.detail == '{{ $name }}' ? show = true : null"
|
||||||
|
x-on:close-modal.window="$event.detail == '{{ $name }}' ? show = false : null"
|
||||||
|
x-on:close.stop="show = false"
|
||||||
|
x-on:keydown.escape.window="show = false"
|
||||||
|
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
|
||||||
|
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
|
||||||
|
x-show="show"
|
||||||
|
class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50"
|
||||||
|
style="display: {{ $show ? 'block' : 'none' }};"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
x-show="show"
|
||||||
|
class="fixed inset-0 transform transition-all"
|
||||||
|
x-on:click="show = false"
|
||||||
|
x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100"
|
||||||
|
x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0"
|
||||||
|
>
|
||||||
|
<div class="absolute inset-0 bg-gray-500 dark:bg-gray-900 opacity-75"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
x-show="show"
|
||||||
|
class="mb-6 bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
|
||||||
|
x-transition:enter="ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave="ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
>
|
||||||
|
{{ $slot }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
@props(['active'])
|
||||||
|
|
||||||
|
@php
|
||||||
|
$classes = ($active ?? false)
|
||||||
|
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 dark:border-indigo-600 text-sm font-medium leading-5 text-gray-900 dark:text-gray-100 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||||
|
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-700 focus:outline-none focus:text-gray-700 dark:focus:text-gray-300 focus:border-gray-300 dark:focus:border-gray-700 transition duration-150 ease-in-out';
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||||
|
{{ $slot }}
|
||||||
|
</a>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150']) }}>
|
||||||
|
{{ $slot }}
|
||||||
|
</button>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
@props(['active'])
|
||||||
|
|
||||||
|
@php
|
||||||
|
$classes = ($active ?? false)
|
||||||
|
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 dark:border-indigo-600 text-start text-base font-medium text-indigo-700 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/50 focus:outline-none focus:text-indigo-800 dark:focus:text-indigo-200 focus:bg-indigo-100 dark:focus:bg-indigo-900 focus:border-indigo-700 dark:focus:border-indigo-300 transition duration-150 ease-in-out'
|
||||||
|
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600 focus:outline-none focus:text-gray-800 dark:focus:text-gray-200 focus:bg-gray-50 dark:focus:bg-gray-700 focus:border-gray-300 dark:focus:border-gray-600 transition duration-150 ease-in-out';
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||||
|
{{ $slot }}
|
||||||
|
</a>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
||||||
|
{{ $slot }}
|
||||||
|
</button>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
@props(['disabled' => false])
|
||||||
|
|
||||||
|
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm']) }}>
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
@extends('layouts.admin')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="p-4" style="min-height: 80vh; background: linear-gradient(to right, #6eb7e7ff, #cedff3ff);">
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="card shadow-lg" style="background: rgba(255, 255, 255, 0.15); backdrop-filter: blur(10px); border-radius: 16px; color: white;">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title h4 font-weight-bold mb-2">👋 Selamat datang, {{ Auth::user()->name }}!</h1>
|
||||||
|
<p class="card-text">Kamu berhasil login ke aplikasi Data Siswa 🎉</p>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<!-- Kotak Data Siswa -->
|
||||||
|
<div class="col-md-4 col-sm-12 mb-3">
|
||||||
|
<a href="{{ route('siswa.index') }}" class="text-decoration-none">
|
||||||
|
<div class="card shadow-sm h-100" style="background-color: #3a8cf7ff; color: #ffffffff;">
|
||||||
|
<div class="card-body text-center d-flex flex-column justify-content-center align-items-center">
|
||||||
|
<i class="fas fa-user-graduate fa-4x mb-3" style="color: #ffffffff;"></i>
|
||||||
|
<h5 class="card-title">2 Siswa</h5>
|
||||||
|
<p class="m-0">View Details</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Grafik Statistik Pengguna -->
|
||||||
|
<div class="col-md-8 col-sm-12 mb-3">
|
||||||
|
<div class="card bg-white text-dark shadow-sm h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="mb-3">Statistik Pengguna</h5>
|
||||||
|
<canvas id="userChart" height="100"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card:hover {
|
||||||
|
transform: scale(1.03);
|
||||||
|
transition: 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Chart.js CDN -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const ctx = document.getElementById('userChart').getContext('2d');
|
||||||
|
const userChart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: ['Siswa'],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Jumlah Data',
|
||||||
|
data: [2, 1, 6],
|
||||||
|
backgroundColor: ['#007bff', '#28a745', '#dc3545'],
|
||||||
|
borderRadius: 5
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false }
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: { beginAtZero: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Admin Panel</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<!-- AdminLTE CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body class="hold-transition sidebar-mini sidebar-collapse">
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
|
||||||
|
{{-- Navbar --}}
|
||||||
|
@include('admin.partials.navbar')
|
||||||
|
|
||||||
|
{{-- Sidebar --}}
|
||||||
|
@include('admin.partials.sidebar')
|
||||||
|
|
||||||
|
{{-- Main Content --}}
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<section class="content pt-3">
|
||||||
|
<div class="container-fluid">
|
||||||
|
@yield('content')
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Footer --}}
|
||||||
|
@include('admin.partials.footer')
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- AdminLTE JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/js/adminlte.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}" />
|
||||||
|
|
||||||
|
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||||
|
|
||||||
|
<!-- Fonts -->
|
||||||
|
<link rel="preconnect" href="https://fonts.bunny.net" />
|
||||||
|
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||||
|
|
||||||
|
<!-- Vite -->
|
||||||
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
|
|
||||||
|
<!-- Optional: Tambahan style background -->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(to right, #7ee8fa, #80ffea);
|
||||||
|
font-family: 'Figtree', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 1.25rem;
|
||||||
|
box-shadow: 0 0 20px rgba(0,0,0,0.1);
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 3rem auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="font-sans antialiased">
|
||||||
|
<div class="min-h-screen">
|
||||||
|
@include('layouts.navigation')
|
||||||
|
|
||||||
|
<!-- Page Heading -->
|
||||||
|
@isset($header)
|
||||||
|
<header class="bg-white shadow">
|
||||||
|
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||||
|
{{ $header }}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
@endisset
|
||||||
|
|
||||||
|
<!-- Page Content -->
|
||||||
|
<main>
|
||||||
|
{{ $slot }}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Login / Register</title>
|
||||||
|
@vite(['resources/css/app.css', 'resources/js/app.js']) <!-- INI PENTING -->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ $slot }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
<nav x-data="{ open: false }" class="bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700">
|
||||||
|
<!-- Primary Navigation Menu -->
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between h-16">
|
||||||
|
<div class="flex">
|
||||||
|
<!-- Logo -->
|
||||||
|
<div class="shrink-0 flex items-center">
|
||||||
|
<a href="{{ route('dashboard') }}">
|
||||||
|
<x-application-logo class="block h-9 w-auto fill-current text-gray-800 dark:text-gray-200" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Links -->
|
||||||
|
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
|
||||||
|
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
|
||||||
|
{{ __('Dashboard') }}
|
||||||
|
</x-nav-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Settings Dropdown -->
|
||||||
|
<div class="hidden sm:flex sm:items-center sm:ms-6">
|
||||||
|
<x-dropdown align="right" width="48">
|
||||||
|
<x-slot name="trigger">
|
||||||
|
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
|
||||||
|
<div>{{ Auth::user()->name }}</div>
|
||||||
|
|
||||||
|
<div class="ms-1">
|
||||||
|
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<x-slot name="content">
|
||||||
|
<x-dropdown-link :href="route('profile.edit')">
|
||||||
|
{{ __('Profile') }}
|
||||||
|
</x-dropdown-link>
|
||||||
|
|
||||||
|
<!-- Authentication -->
|
||||||
|
<form method="POST" action="{{ route('logout') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<x-dropdown-link :href="route('logout')"
|
||||||
|
onclick="event.preventDefault();
|
||||||
|
this.closest('form').submit();">
|
||||||
|
{{ __('Log Out') }}
|
||||||
|
</x-dropdown-link>
|
||||||
|
</form>
|
||||||
|
</x-slot>
|
||||||
|
</x-dropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hamburger -->
|
||||||
|
<div class="-me-2 flex items-center sm:hidden">
|
||||||
|
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-900 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-500 dark:focus:text-gray-400 transition duration-150 ease-in-out">
|
||||||
|
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||||
|
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Responsive Navigation Menu -->
|
||||||
|
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
|
||||||
|
<div class="pt-2 pb-3 space-y-1">
|
||||||
|
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
|
||||||
|
{{ __('Dashboard') }}
|
||||||
|
</x-responsive-nav-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Responsive Settings Options -->
|
||||||
|
<div class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-600">
|
||||||
|
<div class="px-4">
|
||||||
|
<div class="font-medium text-base text-gray-800 dark:text-gray-200">{{ Auth::user()->name }}</div>
|
||||||
|
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 space-y-1">
|
||||||
|
<x-responsive-nav-link :href="route('profile.edit')">
|
||||||
|
{{ __('Profile') }}
|
||||||
|
</x-responsive-nav-link>
|
||||||
|
|
||||||
|
<!-- Authentication -->
|
||||||
|
<form method="POST" action="{{ route('logout') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<x-responsive-nav-link :href="route('logout')"
|
||||||
|
onclick="event.preventDefault();
|
||||||
|
this.closest('form').submit();">
|
||||||
|
{{ __('Log Out') }}
|
||||||
|
</x-responsive-nav-link>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<x-app-layout>
|
||||||
|
<x-slot name="header">
|
||||||
|
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||||
|
{{ __('Profile') }}
|
||||||
|
</h2>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<div class="py-12">
|
||||||
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
|
<div class="max-w-xl">
|
||||||
|
@include('profile.partials.update-profile-information-form')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
|
<div class="max-w-xl">
|
||||||
|
@include('profile.partials.update-password-form')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||||
|
<div class="max-w-xl">
|
||||||
|
@include('profile.partials.delete-user-form')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-app-layout>
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
<section class="space-y-6">
|
||||||
|
<header>
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ __('Delete Account') }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __('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.') }}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<x-danger-button
|
||||||
|
x-data=""
|
||||||
|
x-on:click.prevent="$dispatch('open-modal', 'confirm-user-deletion')"
|
||||||
|
>{{ __('Delete Account') }}</x-danger-button>
|
||||||
|
|
||||||
|
<x-modal name="confirm-user-deletion" :show="$errors->userDeletion->isNotEmpty()" focusable>
|
||||||
|
<form method="post" action="{{ route('profile.destroy') }}" class="p-6">
|
||||||
|
@csrf
|
||||||
|
@method('delete')
|
||||||
|
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ __('Are you sure you want to delete your account?') }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __('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.') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="password" value="{{ __('Password') }}" class="sr-only" />
|
||||||
|
|
||||||
|
<x-text-input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
class="mt-1 block w-3/4"
|
||||||
|
placeholder="{{ __('Password') }}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<x-input-error :messages="$errors->userDeletion->get('password')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 flex justify-end">
|
||||||
|
<x-secondary-button x-on:click="$dispatch('close')">
|
||||||
|
{{ __('Cancel') }}
|
||||||
|
</x-secondary-button>
|
||||||
|
|
||||||
|
<x-danger-button class="ms-3">
|
||||||
|
{{ __('Delete Account') }}
|
||||||
|
</x-danger-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</x-modal>
|
||||||
|
</section>
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
<section>
|
||||||
|
<header>
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ __('Update Password') }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __('Ensure your account is using a long, random password to stay secure.') }}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<form method="post" action="{{ route('password.update') }}" class="mt-6 space-y-6">
|
||||||
|
@csrf
|
||||||
|
@method('put')
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="update_password_current_password" :value="__('Current Password')" />
|
||||||
|
<x-text-input id="update_password_current_password" name="current_password" type="password" class="mt-1 block w-full" autocomplete="current-password" />
|
||||||
|
<x-input-error :messages="$errors->updatePassword->get('current_password')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="update_password_password" :value="__('New Password')" />
|
||||||
|
<x-text-input id="update_password_password" name="password" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
||||||
|
<x-input-error :messages="$errors->updatePassword->get('password')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="update_password_password_confirmation" :value="__('Confirm Password')" />
|
||||||
|
<x-text-input id="update_password_password_confirmation" name="password_confirmation" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
||||||
|
<x-input-error :messages="$errors->updatePassword->get('password_confirmation')" class="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<x-primary-button>{{ __('Save') }}</x-primary-button>
|
||||||
|
|
||||||
|
@if (session('status') === 'password-updated')
|
||||||
|
<p
|
||||||
|
x-data="{ show: true }"
|
||||||
|
x-show="show"
|
||||||
|
x-transition
|
||||||
|
x-init="setTimeout(() => show = false, 2000)"
|
||||||
|
class="text-sm text-gray-600 dark:text-gray-400"
|
||||||
|
>{{ __('Saved.') }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
<section>
|
||||||
|
<header>
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{{ __('Profile Information') }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ __("Update your account's profile information and email address.") }}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<form id="send-verification" method="post" action="{{ route('verification.send') }}">
|
||||||
|
@csrf
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="{{ route('profile.update') }}" class="mt-6 space-y-6">
|
||||||
|
@csrf
|
||||||
|
@method('patch')
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="name" :value="__('Name')" />
|
||||||
|
<x-text-input id="name" name="name" type="text" class="mt-1 block w-full" :value="old('name', $user->name)" required autofocus autocomplete="name" />
|
||||||
|
<x-input-error class="mt-2" :messages="$errors->get('name')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="email" :value="__('Email')" />
|
||||||
|
<x-text-input id="email" name="email" type="email" class="mt-1 block w-full" :value="old('email', $user->email)" required autocomplete="username" />
|
||||||
|
<x-input-error class="mt-2" :messages="$errors->get('email')" />
|
||||||
|
|
||||||
|
@if ($user instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! $user->hasVerifiedEmail())
|
||||||
|
<div>
|
||||||
|
<p class="text-sm mt-2 text-gray-800 dark:text-gray-200">
|
||||||
|
{{ __('Your email address is unverified.') }}
|
||||||
|
|
||||||
|
<button form="send-verification" class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
|
||||||
|
{{ __('Click here to re-send the verification email.') }}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@if (session('status') === 'verification-link-sent')
|
||||||
|
<p class="mt-2 font-medium text-sm text-green-600 dark:text-green-400">
|
||||||
|
{{ __('A new verification link has been sent to your email address.') }}
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<x-primary-button>{{ __('Save') }}</x-primary-button>
|
||||||
|
|
||||||
|
@if (session('status') === 'profile-updated')
|
||||||
|
<p
|
||||||
|
x-data="{ show: true }"
|
||||||
|
x-show="show"
|
||||||
|
x-transition
|
||||||
|
x-init="setTimeout(() => show = false, 2000)"
|
||||||
|
class="text-sm text-gray-600 dark:text-gray-400"
|
||||||
|
>{{ __('Saved.') }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
<!-- resources/views/siswa/show.blade.php -->
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -85,7 +87,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $siswa->id }}</td>
|
<td>{{ $siswa->id }}</td>
|
||||||
<td>{{ $siswa->nama }}</td>
|
<td>{{ $siswa->nama }}</td>
|
||||||
<td>{{ $siswa->kelas }}</td>
|
<td>{{ $siswa->kelas->nama }}</td>
|
||||||
<td>{{ $siswa->umur }}</td>
|
<td>{{ $siswa->umur }}</td>
|
||||||
<td>{{ $siswa->absen }}</td>
|
<td>{{ $siswa->absen }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -1,102 +1,119 @@
|
||||||
<!DOCTYPE html>
|
@extends('layouts.admin')
|
||||||
<html lang="id">
|
|
||||||
<head>
|
@section('title', 'Data Siswa')
|
||||||
<meta charset="UTF-8">
|
@section('content')
|
||||||
<title>Data Siswa</title>
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
body {
|
||||||
margin: 0;
|
background: #e5f1ff;
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: 'Poppins', sans-serif;
|
font-family: 'Poppins', sans-serif;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg, #74ebd5, #ACB6E5);
|
.card {
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 90%;
|
|
||||||
max-width: 900px;
|
|
||||||
background: #ffffff;
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
|
||||||
}
|
background-color: white;
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
.button, .button-edit {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 12px 25px;
|
|
||||||
margin-top: 20px;
|
|
||||||
color: #ffffff;
|
|
||||||
background: #3498db;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 50px;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: background 0.3s ease;
|
|
||||||
}
|
|
||||||
.button:hover, .button-edit:hover {
|
|
||||||
background: #2980b9;
|
|
||||||
}
|
|
||||||
.alert {
|
|
||||||
padding: 15px;
|
|
||||||
background-color: #d1f2eb;
|
|
||||||
color: #117864;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-left: 6px solid #1abc9c;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin-top: 20px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 15px;
|
|
||||||
}
|
}
|
||||||
thead {
|
|
||||||
background-color: #3498db;
|
.card-header.bg-primary {
|
||||||
|
background: linear-gradient(to right, #3b82f6, #60a5fa);
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th,
|
||||||
|
.table td {
|
||||||
|
vertical-align: middle !important;
|
||||||
|
font-size: 15px;
|
||||||
|
font-family: 'Poppins', sans-serif;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background-color: #93c5fd;
|
||||||
|
color: #1e3a8a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 6px 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: 'Poppins', sans-serif;
|
||||||
|
transition: 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #3b82f6;
|
||||||
|
border-color: #3b82f6;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
th, td {
|
|
||||||
padding: 15px;
|
.btn-primary:hover {
|
||||||
text-align: center;
|
background: #2563eb;
|
||||||
font-size: 16px;
|
|
||||||
border-bottom: 1px solid #e1e1e1;
|
|
||||||
}
|
}
|
||||||
tbody tr:nth-child(even) {
|
|
||||||
background-color: #f4f9fd;
|
.btn-info {
|
||||||
|
background: #60a5fa;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
tbody tr:hover {
|
|
||||||
background-color: #eaf6ff;
|
.btn-info:hover {
|
||||||
|
background: #3b82f6;
|
||||||
}
|
}
|
||||||
p {
|
|
||||||
text-align: center;
|
.btn-danger {
|
||||||
|
background: #ef4444;
|
||||||
|
border-color: #dc2626;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
font-size: 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: #6b7280 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-right {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-size: 18px;
|
|
||||||
color: #555;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
|
||||||
<body>
|
<div class="card">
|
||||||
<div class="container">
|
<div class="card-header bg-primary">
|
||||||
<h1>Data Siswa</h1>
|
<h3 class="card-title">📋 Data Siswa</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
<div class="alert">{{ session('success') }}</div>
|
<div class="alert alert-success">{{ session('success') }}</div>
|
||||||
@endif
|
@endif
|
||||||
@if(session('error'))
|
@if(session('error'))
|
||||||
<div class="alert" style="background: #ffe6e6; color: #c0392b;">{{ session('error') }}</div>
|
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if(count($siswas) > 0)
|
@if(count($siswas) > 0)
|
||||||
<table>
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered text-center">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>No</th>
|
<th>No</th>
|
||||||
|
|
@ -116,22 +133,28 @@
|
||||||
<td>{{ $siswa->umur }}</td>
|
<td>{{ $siswa->umur }}</td>
|
||||||
<td>{{ $siswa->absen }}</td>
|
<td>{{ $siswa->absen }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ route('siswa.edit', $siswa->id) }}" class="button-edit">Edit</a>
|
<a href="{{ route('siswa.edit', $siswa->id) }}" class="btn btn-info btn-sm">Edit</a>
|
||||||
<form action="{{ route('siswa.destroy', $siswa->id) }}" method="POST" style="display:inline;">
|
<form action="{{ route('siswa.destroy', $siswa->id) }}" method="POST" class="d-inline">
|
||||||
@csrf
|
@csrf
|
||||||
@method('DELETE')
|
@method('DELETE')
|
||||||
<button type="submit" class="button-edit" style="background:#e74c3c; margin-top:0; margin-left:10px;">Hapus</button>
|
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Yakin mau hapus?')">Hapus</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
@else
|
@else
|
||||||
|
<div class="text-center text-muted my-4">
|
||||||
<p>Belum ada data siswa, silahkan tambahkan data.</p>
|
<p>Belum ada data siswa, silahkan tambahkan data.</p>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<a href="{{ route('siswa.create') }}" class="button">+ Tambah Data Siswa</a>
|
<div class="text-right">
|
||||||
|
<a href="{{ route('siswa.create') }}" class="btn btn-primary">+ Tambah Data Siswa</a>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
|
||||||
</html>
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Auth\AuthenticatedSessionController;
|
||||||
|
use App\Http\Controllers\Auth\ConfirmablePasswordController;
|
||||||
|
use App\Http\Controllers\Auth\EmailVerificationNotificationController;
|
||||||
|
use App\Http\Controllers\Auth\EmailVerificationPromptController;
|
||||||
|
use App\Http\Controllers\Auth\NewPasswordController;
|
||||||
|
use App\Http\Controllers\Auth\PasswordController;
|
||||||
|
use App\Http\Controllers\Auth\PasswordResetLinkController;
|
||||||
|
use App\Http\Controllers\Auth\RegisteredUserController;
|
||||||
|
use App\Http\Controllers\Auth\VerifyEmailController;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::middleware('guest')->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');
|
||||||
|
});
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\ProfileController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Http\Controllers\SiswaController;
|
use App\Http\Controllers\SiswaController;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
return view('welcome');
|
return view('welcome');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/about', function () {
|
Route::get('/dashboard', function () {
|
||||||
return view('about');
|
return view('dashboard');
|
||||||
|
})->middleware(['auth', 'verified'])->name('dashboard');
|
||||||
|
|
||||||
|
Route::middleware('auth')->group(function () {
|
||||||
|
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||||
|
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||||
|
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Halaman Utama Data Siswa
|
// Halaman Utama Data Siswa
|
||||||
|
|
@ -28,3 +34,5 @@ Route::get('/datasiswa/{id}', [SiswaController::class, 'show'])->name('siswa.sho
|
||||||
|
|
||||||
// Hapus Data Siswa
|
// Hapus Data Siswa
|
||||||
Route::delete('/datasiswa/{id}', [SiswaController::class, 'destroy'])->name('siswa.destroy');
|
Route::delete('/datasiswa/{id}', [SiswaController::class, 'destroy'])->name('siswa.destroy');
|
||||||
|
|
||||||
|
require __DIR__.'/auth.php';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import defaultTheme from 'tailwindcss/defaultTheme';
|
||||||
|
import forms from '@tailwindcss/forms';
|
||||||
|
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
|
||||||
|
'./storage/framework/views/*.php',
|
||||||
|
'./resources/views/**/*.blade.php',
|
||||||
|
],
|
||||||
|
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Figtree', ...defaultTheme.fontFamily.sans],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [forms],
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
test('login screen can be rendered', function () {
|
||||||
|
$response = $this->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('/');
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Events\Verified;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
|
||||||
|
test('email verification screen can be rendered', function () {
|
||||||
|
$user = User::factory()->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();
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
test('confirm password screen can be rendered', function () {
|
||||||
|
$user = User::factory()->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();
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Notifications\ResetPassword;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
|
|
||||||
|
test('reset password link screen can be rendered', function () {
|
||||||
|
$response = $this->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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
test('password can be updated', function () {
|
||||||
|
$user = User::factory()->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');
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
test('registration screen can be rendered', function () {
|
||||||
|
$response = $this->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));
|
||||||
|
});
|
||||||
|
|
@ -1,19 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature;
|
it('returns a successful response', function () {
|
||||||
|
|
||||||
// use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class ExampleTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* A basic test example.
|
|
||||||
*/
|
|
||||||
public function test_the_application_returns_a_successful_response(): void
|
|
||||||
{
|
|
||||||
$response = $this->get('/');
|
$response = $this->get('/');
|
||||||
|
|
||||||
$response->assertStatus(200);
|
$response->assertStatus(200);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
test('profile page is displayed', function () {
|
||||||
|
$user = User::factory()->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());
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Test Case
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
|
||||||
|
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
|
||||||
|
| need to change it using the "pest()" function to bind a different classes or traits.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
pest()->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()
|
||||||
|
{
|
||||||
|
// ..
|
||||||
|
}
|
||||||
|
|
@ -1,16 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Unit;
|
test('that true is true', function () {
|
||||||
|
expect(true)->toBeTrue();
|
||||||
use PHPUnit\Framework\TestCase;
|
});
|
||||||
|
|
||||||
class ExampleTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* A basic test example.
|
|
||||||
*/
|
|
||||||
public function test_that_true_is_true(): void
|
|
||||||
{
|
|
||||||
$this->assertTrue(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import laravel from 'laravel-vite-plugin';
|
import laravel from 'laravel-vite-plugin';
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
@ -8,6 +7,5 @@ export default defineConfig({
|
||||||
input: ['resources/css/app.css', 'resources/js/app.js'],
|
input: ['resources/css/app.css', 'resources/js/app.js'],
|
||||||
refresh: true,
|
refresh: true,
|
||||||
}),
|
}),
|
||||||
tailwindcss(),
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue