perbaikan tampilan

perbaikan tampilan
This commit is contained in:
Ryfandii 2026-05-19 18:28:30 +07:00
parent 9762288c38
commit 0f2b254f28
6 changed files with 1123 additions and 464 deletions

View File

@ -227,19 +227,37 @@ public function logout(Request $request)
}
// ================= VERIFY OTP (halaman terpisah jika ada) =================
public function showVerifyOtp()
{
// ================= VERIFY OTP (lupa password) =================
public function showVerifyOtp()
{
return view('auth.verify-otp');
}
public function verifyOtp(Request $request)
{
$user = User::find(session('otp_user'));
if (!$user) {
return back()->with('error', 'Sesi tidak valid, ulangi dari awal.');
}
public function verifyOtp(Request $request)
{
if ((string) $request->otp === (string) session('otp')) {
// Cek OTP dari kolom database, bukan session
if ((string)$request->otp !== (string)$user->otp) {
return back()->with('error', 'Kode OTP salah.');
}
if (!$user->otp_expired_at || now()->gt($user->otp_expired_at)) {
return back()->with('error', 'OTP sudah kadaluarsa, kirim ulang.');
}
// OTP valid — hapus OTP, redirect ke reset password
$user->update([
'otp' => null,
'otp_expired_at' => null,
]);
return redirect()->route('reset.password.form');
}
return back()->with('error', 'OTP salah');
}
}
// ================= RESET PASSWORD (via OTP lupa password) =================
public function showResetPassword()
@ -295,4 +313,30 @@ private function redirectByRole($user)
default => redirect('/login'),
};
}
// ================= KIRIM OTP LUPA PASSWORD =================
// ================= KIRIM OTP LUPA PASSWORD =================
public function sendForgotPasswordOtp(Request $request)
{
$request->validate([
'email' => 'required|email|exists:users,email',
], [
'email.exists' => 'Email tidak terdaftar dalam sistem.',
]);
$user = User::where('email', $request->email)->first();
// Kirim OTP via email menggunakan OtpService yang sudah ada
app(\App\Services\OtpService::class)->send($user, 'email');
// Simpan ke session untuk dipakai saat verifikasi & reset
session([
'otp_user' => $user->id,
'email' => $user->email,
]);
// Redirect ke halaman verifikasi OTP
return redirect()->route('verify.otp')
->with('success', 'OTP telah dikirim ke ' . $user->email);
}
}

View File

@ -3,9 +3,9 @@
@section('content')
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
:root {
:root {
--brand-primary: #1a56db;
--brand-primary-light: #e8f0fe;
--brand-success: #0d9488;
@ -29,7 +29,8 @@
--shadow-xs: 0 1px 2px rgba(0,0,0,0.04);
--shadow-sm: 0 1px 4px rgba(0,0,0,0.06), 0 2px 8px rgba(0,0,0,0.04);
--shadow-md: 0 4px 16px rgba(0,0,0,0.08), 0 1px 4px rgba(0,0,0,0.04);
}
}
*,
*::before,
*::after {
@ -39,42 +40,43 @@
body {
font-family: 'Plus Jakarta Sans', sans-serif;
}
body, .dashboard-wrap * {
}
body, .dashboard-wrap * {
.dashboard-wrap {
}
.dashboard-wrap {
background: var(--surface-page);
min-height: 100vh;
padding: 28px 32px 48px;
}
}
/* ─── HEADER ─── */
.db-header {
/* ─── HEADER ─── */
.db-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 28px;
}
}
.db-header-left .db-eyebrow {
.db-header-left .db-eyebrow {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--brand-primary);
margin-bottom: 4px;
}
}
.db-header-left h1 {
.db-header-left h1 {
font-size: 26px;
font-weight: 700;
color: var(--text-primary);
margin: 0;
letter-spacing: -0.5px;
}
}
.db-user-badge {
.db-user-badge {
display: flex;
align-items: center;
gap: 10px;
@ -83,9 +85,9 @@
border-radius: 40px;
padding: 6px 14px 6px 8px;
box-shadow: var(--shadow-xs);
}
}
.db-user-avatar {
.db-user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
@ -97,23 +99,23 @@
font-weight: 700;
color: white;
letter-spacing: 0;
}
}
.db-user-name {
.db-user-name {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
}
}
/* ─── STAT CARDS ─── */
.db-stats-grid {
/* ─── STAT CARDS ─── */
.db-stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 24px;
}
}
.stat-card {
.stat-card {
background: var(--surface-base);
border-radius: var(--radius-lg);
border: 1px solid var(--border-soft);
@ -125,41 +127,41 @@
position: relative;
overflow: hidden;
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
}
.stat-card:hover {
.stat-card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
}
.stat-card::before {
.stat-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 3px;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
}
.stat-card.primary::before { background: var(--brand-primary); }
.stat-card.success::before { background: var(--brand-success); }
.stat-card.warning::before { background: var(--brand-warning); }
.stat-card.danger::before { background: var(--brand-danger); }
.stat-card.primary::before { background: var(--brand-primary); }
.stat-card.success::before { background: var(--brand-success); }
.stat-card.warning::before { background: var(--brand-warning); }
.stat-card.danger::before { background: var(--brand-danger); }
.stat-card-top {
.stat-card-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
}
.stat-card-label {
.stat-card-label {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--text-secondary);
}
}
.stat-icon {
.stat-icon {
width: 38px;
height: 38px;
border-radius: var(--radius-sm);
@ -167,14 +169,14 @@
align-items: center;
justify-content: center;
font-size: 16px;
}
}
.stat-icon.primary { background: var(--brand-primary-light); color: var(--brand-primary); }
.stat-icon.success { background: var(--brand-success-light); color: var(--brand-success); }
.stat-icon.warning { background: var(--brand-warning-light); color: var(--brand-warning); }
.stat-icon.danger { background: var(--brand-danger-light); color: var(--brand-danger); }
.stat-icon.primary { background: var(--brand-primary-light); color: var(--brand-primary); }
.stat-icon.success { background: var(--brand-success-light); color: var(--brand-success); }
.stat-icon.warning { background: var(--brand-warning-light); color: var(--brand-warning); }
.stat-icon.danger { background: var(--brand-danger-light); color: var(--brand-danger); }
.stat-value {
.stat-value {
font-size: 36px;
font-weight: 700;
color: var(--text-primary);
@ -182,36 +184,36 @@
line-height: 1;
font-variant-numeric: tabular-nums;
font-family: 'DM Mono', monospace;
}
}
/* ─── SECTION LABEL ─── */
.section-label {
/* ─── SECTION LABEL ─── */
.section-label {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text-muted);
margin: 0 0 14px;
}
}
/* ─── CARD BASE ─── */
.db-card {
/* ─── CARD BASE ─── */
.db-card {
background: var(--surface-base);
border-radius: var(--radius-lg);
border: 1px solid var(--border-soft);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
}
.db-card-header {
.db-card-header {
padding: 16px 20px;
border-bottom: 1px solid var(--border-soft);
display: flex;
align-items: center;
justify-content: space-between;
}
}
.db-card-title {
.db-card-title {
font-size: 14px;
font-weight: 700;
color: var(--text-primary);
@ -219,18 +221,18 @@
display: flex;
align-items: center;
gap: 8px;
}
}
.db-card-title .title-dot {
.db-card-title .title-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
}
.db-card-title .title-dot.primary { background: var(--brand-primary); }
.db-card-title .title-dot.teal { background: var(--brand-success); }
.db-card-title .title-dot.primary { background: var(--brand-primary); }
.db-card-title .title-dot.teal { background: var(--brand-success); }
.db-card-badge {
.db-card-badge {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
@ -238,71 +240,71 @@
background: var(--surface-page);
color: var(--text-secondary);
border: 1px solid var(--border-soft);
}
}
.db-card-body {
.db-card-body {
padding: 20px;
}
}
/* ─── CHARTS GRID ─── */
.charts-grid {
/* ─── CHARTS GRID ─── */
.charts-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 24px;
}
}
/* ─── DATA LISTS ─── */
.data-lists-grid {
/* ─── DATA LISTS ─── */
.data-lists-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 24px;
}
}
.data-row {
.data-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 11px 20px;
border-bottom: 1px solid var(--border-soft);
transition: background 0.15s ease;
}
}
.data-row:last-child { border-bottom: none; }
.data-row:last-child { border-bottom: none; }
.data-row:hover { background: var(--surface-page); }
.data-row:hover { background: var(--surface-page); }
.data-row-left {
.data-row-left {
display: flex;
align-items: center;
gap: 10px;
}
}
.data-row-index {
.data-row-index {
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
font-family: 'DM Mono', monospace;
min-width: 18px;
}
}
.data-row-name {
.data-row-name {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
}
}
.data-row-sub {
.data-row-sub {
font-size: 12px;
color: var(--text-muted);
background: var(--surface-page);
border: 1px solid var(--border-soft);
border-radius: 6px;
padding: 2px 8px;
}
}
.score-badge {
.score-badge {
font-size: 13px;
font-weight: 700;
font-family: 'DM Mono', monospace;
@ -311,14 +313,14 @@
border-radius: var(--radius-sm);
padding: 3px 10px;
letter-spacing: 0.5px;
}
}
.score-badge.high { background: #0d9488; }
.score-badge.mid { background: #d97706; }
.score-badge.low { background: #dc2626; }
.score-badge.high { background: #0d9488; }
.score-badge.mid { background: #d97706; }
.score-badge.low { background: #dc2626; }
/* ─── QUICK ACTIONS ─── */
.quick-actions {
/* ─── QUICK ACTIONS ─── */
.quick-actions {
display: flex;
align-items: center;
justify-content: space-between;
@ -327,26 +329,26 @@
border: 1px solid var(--border-soft);
box-shadow: var(--shadow-sm);
padding: 18px 24px;
}
}
.qa-left h5 {
.qa-left h5 {
font-size: 15px;
font-weight: 700;
color: var(--text-primary);
margin: 0 0 2px;
}
}
.qa-left small {
.qa-left small {
font-size: 12px;
color: var(--text-muted);
}
}
.qa-buttons {
.qa-buttons {
display: flex;
gap: 10px;
}
}
.btn-qa {
.btn-qa {
display: inline-flex;
align-items: center;
gap: 6px;
@ -359,36 +361,192 @@
text-decoration: none;
transition: all 0.15s ease;
letter-spacing: 0.01em;
}
}
.btn-qa:hover { transform: translateY(-1px); filter: brightness(0.95); }
.btn-qa:hover { transform: translateY(-1px); filter: brightness(0.95); }
.btn-qa.primary {
.btn-qa.primary {
background: var(--brand-primary);
border-color: var(--brand-primary);
color: #fff;
}
}
.btn-qa.success {
.btn-qa.success {
background: var(--brand-success);
border-color: var(--brand-success);
color: #fff;
}
}
.btn-qa.outline {
.btn-qa.outline {
background: transparent;
border-color: var(--border-medium);
color: var(--text-primary);
}
}
/* ─── EMPTY STATE ─── */
.empty-state {
/* ─── EMPTY STATE ─── */
.empty-state {
padding: 28px 20px;
text-align: center;
color: var(--text-muted);
font-size: 13px;
}
/* ================================================================
RESPONSIVE MOBILE & TABLET
Hanya menambahkan media query tidak ada variabel/logika diubah
================================================================ */
/* Tablet (≤ 1024px) */
@media (max-width: 1024px) {
.dashboard-wrap {
padding: 24px 20px 40px;
}
.db-stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.charts-grid {
grid-template-columns: 1fr;
}
.data-lists-grid {
grid-template-columns: 1fr;
}
}
/* Mobile (≤ 768px) */
@media (max-width: 768px) {
.dashboard-wrap {
padding: 16px 14px 36px;
}
/* Header — stack vertikal */
.db-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
margin-bottom: 20px;
}
.db-header-left h1 {
font-size: 20px;
}
/* Stat cards — 2 kolom di mobile */
.db-stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.stat-card {
padding: 16px 16px;
gap: 10px;
}
.stat-value {
font-size: 28px;
letter-spacing: -1px;
}
.stat-icon {
width: 32px;
height: 32px;
font-size: 14px;
}
/* Charts — 1 kolom */
.charts-grid {
grid-template-columns: 1fr;
gap: 12px;
margin-bottom: 20px;
}
/* Data lists — 1 kolom */
.data-lists-grid {
grid-template-columns: 1fr;
gap: 12px;
margin-bottom: 20px;
}
/* Data row — nama bisa wrap */
.data-row-name {
font-size: 12px;
max-width: 120px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.data-row-sub {
font-size: 11px;
padding: 2px 6px;
max-width: 80px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Quick actions — stack vertikal */
.quick-actions {
flex-direction: column;
align-items: flex-start;
gap: 16px;
padding: 16px 18px;
}
.qa-buttons {
width: 100%;
flex-wrap: wrap;
gap: 8px;
}
.btn-qa {
flex: 1;
justify-content: center;
padding: 9px 12px;
font-size: 12px;
min-width: 0;
}
}
/* Extra small phones (≤ 380px) */
@media (max-width: 380px) {
.dashboard-wrap {
padding: 14px 12px 32px;
}
.db-stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.stat-card {
padding: 14px 12px;
}
.stat-value {
font-size: 24px;
}
.stat-card-label {
font-size: 10px;
}
.db-header-left h1 {
font-size: 18px;
}
/* Sembunyikan nama user di badge, cukup avatar */
.db-user-name {
display: none;
}
.db-user-badge {
padding: 6px 10px 6px 8px;
}
}
</style>
<div class="dashboard-wrap">

View File

@ -30,7 +30,6 @@
width: 280px;
max-width: 100%;
margin-bottom: 32px;
filter: drop-shadow(0 8px 24px rgba(79,70,229,.18));
transition: transform .3s;
}
@ -129,6 +128,95 @@
text-decoration: none; transition: color .2s;
}
.back-link:hover { color: #fff; }
/* ================================================================
RESPONSIVE MOBILE
Layout: logo + info sekolah di atas, form di bawah
================================================================ */
@media (max-width: 768px) {
.page {
flex-direction: column;
height: auto;
min-height: 100vh;
}
/* LEFT — di atas */
.left {
width: 100%;
padding: 32px 24px 28px;
order: 1;
}
/* Garis pembatas horizontal */
.left::after {
right: unset;
top: unset;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: linear-gradient(to right, transparent, #C7D2FE 30%, #C7D2FE 70%, transparent);
}
.logo {
width: 180px;
margin-bottom: 20px;
}
.left-title { font-size: 18px; }
.left-sub { font-size: 12px; }
.left-badge {
margin-top: 20px;
font-size: 11px;
}
/* RIGHT — form di bawah */
.right {
order: 2;
padding: 36px 24px 52px;
min-height: auto;
align-items: flex-start;
justify-content: center;
}
/* Sembunyikan dekorasi lingkaran di mobile */
.right::before,
.right::after {
display: none;
}
.form-box {
width: 100%;
max-width: 420px;
}
.form-title { font-size: 21px; }
.form-sub {
font-size: 12.5px;
margin-bottom: 22px;
}
.input-wrap input {
font-size: 16px; /* Cegah auto-zoom di iOS */
}
}
/* Extra small phones */
@media (max-width: 380px) {
.left {
padding: 24px 20px 22px;
}
.logo { width: 150px; }
.right {
padding: 28px 20px 44px;
}
.form-title { font-size: 19px; }
}
</style>
</head>
<body>

View File

@ -341,6 +341,105 @@
flex-shrink: 0;
color: #6EE7B7;
}
/* ================================================================
RESPONSIVE MOBILE
Layout: logo + info sekolah di atas, form login di bawah
================================================================ */
@media (max-width: 768px) {
.page {
flex-direction: column;
height: auto;
min-height: 100vh;
}
/* LEFT — pindah ke atas */
.left {
width: 100%;
padding: 32px 24px 28px;
order: 1;
}
/* Garis pembatas horizontal */
.left::after {
right: unset;
top: unset;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: linear-gradient(to right, transparent, #C7D2FE 30%, #C7D2FE 70%, transparent);
}
.logo {
width: 180px;
margin-bottom: 20px;
}
.left-title {
font-size: 18px;
}
.left-sub {
font-size: 12px;
}
.left-badge {
margin-top: 20px;
font-size: 11px;
}
/* RIGHT — form login di bawah */
.right {
order: 2;
padding: 36px 24px 52px;
min-height: auto;
align-items: flex-start;
justify-content: center;
}
/* Sembunyikan dekorasi lingkaran di mobile */
.right::before,
.right::after {
display: none;
}
.form-box {
width: 100%;
max-width: 420px;
}
.form-title {
font-size: 22px;
}
.form-sub {
margin-bottom: 20px;
}
.input-wrap input {
font-size: 16px; /* Cegah auto-zoom di iOS */
}
}
/* Extra small phones */
@media (max-width: 380px) {
.left {
padding: 24px 20px 22px;
}
.logo {
width: 150px;
}
.right {
padding: 28px 20px 44px;
}
.form-title {
font-size: 20px;
}
}
</style>
</head>

View File

@ -1,173 +1,444 @@
@extends('layouts.app')
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset Password - Sistem Akademik</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Plus Jakarta Sans', sans-serif; }
html, body { height: 100%; }
@section('content')
.page { display: flex; height: 100vh; width: 100%; }
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--warning: #D97706;
--warning-light: #FFFBEB;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--danger-light: #FEF2F2;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 14px;
/* ── LEFT ── */
.left {
width: 38%;
background: #F3F4F8;
display: flex; flex-direction: column;
justify-content: center; align-items: center;
text-align: center; padding: 48px 40px;
position: relative;
}
.left::after {
content: '';
position: absolute; right: 0; top: 0; bottom: 0; width: 1px;
background: linear-gradient(to bottom, transparent, #C7D2FE 30%, #C7D2FE 70%, transparent);
}
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.logo {
width: 280px;
max-width: 100%;
margin-bottom: 32px;
filter: drop-shadow(0 8px 24px rgba(79,70,229,.18));
transition: transform .3s;
}
.logo:hover { transform: scale(1.04); }
.sw-page {
min-height: 100vh; background: var(--bg);
.left-title { font-size: 22px; font-weight: 800; color: #1E3A6E; line-height: 1.3; margin-bottom: 10px; }
.left-sub { font-size: 13px; color: #6B7280; line-height: 1.7; }
.left-badge {
margin-top: 28px;
display: inline-flex; align-items: center; gap: 6px;
background: #EEF2FF; border: 1px solid #C7D2FE;
border-radius: 99px; padding: 6px 16px;
font-size: 11.5px; font-weight: 600; color: #4F46E5;
}
.left-badge span { width: 6px; height: 6px; background: #4F46E5; border-radius: 50%; display: inline-block; }
/* Step indicators */
.steps {
margin-top: 36px;
display: flex; flex-direction: column; gap: 12px;
width: 100%; max-width: 220px;
}
.step { display: flex; align-items: center; gap: 12px; text-align: left; }
.step-dot {
width: 30px; height: 30px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
padding: 32px 16px;
font-size: 12px; font-weight: 700; flex-shrink: 0;
}
.step-dot.done { background: #D1FAE5; color: #059669; }
.step-dot.active { background: #4F46E5; color: #fff; box-shadow: 0 0 0 4px #C7D2FE; }
.step-dot.pending { background: #F3F4F6; color: #9CA3AF; }
.step-text { font-size: 12.5px; font-weight: 600; }
.step-text.done { color: #059669; }
.step-text.active { color: #4F46E5; }
.step-text.pending { color: #9CA3AF; }
/* ── RIGHT ── */
.right {
flex: 1;
background: linear-gradient(145deg, #1E3A6E 0%, #2D5BB5 55%, #4F8DD6 100%);
display: flex; justify-content: center; align-items: center;
position: relative; overflow: hidden;
}
.right::before {
content: ''; position: absolute;
width: 420px; height: 420px; border-radius: 50%;
border: 1px solid rgba(255,255,255,.07);
top: -80px; right: -80px;
}
.right::after {
content: ''; position: absolute;
width: 300px; height: 300px; border-radius: 50%;
border: 1px solid rgba(255,255,255,.06);
bottom: -60px; left: -60px;
}
.sw-card {
width: 100%; max-width: 440px;
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 4px 24px rgba(0,0,0,.07);
overflow: hidden;
}
/* ── FORM BOX ── */
.form-box { width: 340px; position: relative; z-index: 2; }
.sw-card-header {
padding: 28px 32px 24px;
background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
border-bottom: 1px solid #FDE68A;
text-align: center;
}
.sw-card-header-icon {
width: 52px; height: 52px;
background: var(--warning);
border-radius: 14px;
/* Icon ring */
.form-icon {
width: 64px; height: 64px; border-radius: 50%;
background: rgba(255,255,255,.12);
border: 2px solid rgba(255,255,255,.25);
display: flex; align-items: center; justify-content: center;
color: #fff; margin: 0 auto 16px;
box-shadow: 0 4px 14px rgba(217,119,6,.3);
margin: 0 0 22px;
backdrop-filter: blur(8px);
}
.sw-card-header h4 {
font-size: 18px; font-weight: 700;
color: #92400E; margin: 0 0 4px;
letter-spacing: -.3px;
.form-title {
font-size: 24px; font-weight: 800; color: #fff;
letter-spacing: -.5px; margin-bottom: 6px;
}
.sw-card-header p { font-size: 12.5px; color: #A16207; margin: 0; }
.sw-card-body { padding: 28px 32px; }
/* ALERTS */
.sw-alert {
display: flex; align-items: center; gap: 9px;
border-radius: var(--radius-md);
padding: 12px 14px;
font-size: 12.5px; margin-bottom: 20px;
.form-sub {
font-size: 13px; color: rgba(255,255,255,.65);
line-height: 1.6; margin-bottom: 28px;
}
.sw-alert-danger { background: var(--danger-light); border: 1px solid #FECACA; color: var(--danger); }
.sw-alert-success { background: var(--success-light); border: 1px solid #A7F3D0; color: var(--success); }
/* FORM */
.sw-form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.sw-label { font-size: 12.5px; font-weight: 600; color: var(--text-mid); }
.sw-label span { color: var(--danger); margin-left: 2px; }
.sw-input-wrap { position: relative; }
.sw-input-wrap svg {
position: absolute; left: 14px; top: 50%;
transform: translateY(-50%); color: #9CA3AF; pointer-events: none;
/* ── INPUT ── */
.input-wrap { position: relative; margin-bottom: 14px; }
.input-wrap svg {
position: absolute; left: 16px; top: 50%;
transform: translateY(-50%); color: rgba(255,255,255,.5); pointer-events: none;
}
.sw-input {
.input-label {
font-size: 11px; font-weight: 700;
color: rgba(255,255,255,.5);
text-transform: uppercase; letter-spacing: 1px;
margin-bottom: 6px; display: block;
}
.input-wrap input {
width: 100%;
padding: 11px 14px 11px 42px;
border: 1.5px solid var(--border);
border-radius: var(--radius-md);
font-size: 14px; font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark); background: var(--neutral-light);
outline: none; transition: all .15s;
}
.sw-input:focus {
border-color: var(--warning);
background: #fff;
box-shadow: 0 0 0 3px rgba(217,119,6,.1);
}
.sw-input::placeholder { color: #C3C7D0; }
.sw-btn-submit {
width: 100%; padding: 13px; border-radius: var(--radius-md); border: none;
background: var(--warning); color: #fff;
font-size: 14px; font-weight: 700;
padding: 13px 16px 13px 44px;
border-radius: 12px;
border: 1.5px solid rgba(255,255,255,.15);
background: rgba(255,255,255,.12);
color: #fff; font-size: 14px;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .18s; margin-top: 8px;
box-shadow: 0 4px 14px rgba(217,119,6,.3);
outline: none; transition: all .2s;
backdrop-filter: blur(6px);
}
.input-wrap input::placeholder { color: rgba(255,255,255,.45); }
.input-wrap input:focus {
border-color: rgba(255,255,255,.5);
background: rgba(255,255,255,.18);
box-shadow: 0 0 0 3px rgba(255,255,255,.1);
}
/* Toggle password visibility */
.toggle-pw {
position: absolute; right: 14px; top: 50%;
transform: translateY(-50%);
background: none; border: none; cursor: pointer;
color: rgba(255,255,255,.4); padding: 0;
transition: color .2s;
display: flex; align-items: center;
}
.toggle-pw:hover { color: rgba(255,255,255,.8); }
/* Password strength bar */
.pw-strength {
margin-top: 8px; margin-bottom: 4px;
display: flex; gap: 4px; height: 3px;
}
.pw-strength-bar {
flex: 1; border-radius: 99px;
background: rgba(255,255,255,.15);
transition: background .3s;
}
.pw-strength-bar.weak { background: #EF4444; }
.pw-strength-bar.medium { background: #F59E0B; }
.pw-strength-bar.strong { background: #10B981; }
.pw-strength-text {
font-size: 11px; color: rgba(255,255,255,.45);
margin-bottom: 14px; min-height: 16px;
}
/* ── BUTTON ── */
.btn-submit {
width: 100%; padding: 13px; border-radius: 12px; border: none;
background: linear-gradient(135deg, #10B981, #059669);
color: #fff; font-size: 14px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .2s; margin-top: 4px;
box-shadow: 0 4px 16px rgba(5,150,105,.35);
display: flex; align-items: center; justify-content: center; gap: 8px;
}
.sw-btn-submit:hover { background: #B45309; transform: translateY(-1px); box-shadow: 0 6px 20px rgba(217,119,6,.35); }
</style>
.btn-submit:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(5,150,105,.4); }
<div class="sw-page">
<div class="sw-card">
/* ── ALERTS ── */
.sw-alert-error {
display: flex; align-items: center; gap: 8px;
background: rgba(239,68,68,.15); border: 1px solid rgba(239,68,68,.3);
border-radius: 10px; padding: 11px 14px;
font-size: 12.5px; color: #FCA5A5; margin-bottom: 18px;
}
.sw-alert-success {
display: flex; align-items: center; gap: 8px;
background: rgba(16,185,129,.15); border: 1px solid rgba(16,185,129,.3);
border-radius: 10px; padding: 11px 14px;
font-size: 12.5px; color: #6EE7B7; margin-bottom: 18px;
}
<div class="sw-card-header">
<div class="sw-card-header-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>
/* ── BACK LINK ── */
.back-link {
display: flex; align-items: center; justify-content: center; gap: 6px;
margin-top: 20px;
font-size: 12.5px; color: rgba(255,255,255,.6);
text-decoration: none; transition: color .2s;
}
.back-link:hover { color: #fff; }
/* ================================================================
RESPONSIVE MOBILE
Layout: logo + info sekolah di atas, form di bawah
================================================================ */
@media (max-width: 768px) {
.page {
flex-direction: column;
height: auto;
min-height: 100vh;
}
.left {
width: 100%;
padding: 32px 24px 28px;
order: 1;
}
.left::after {
right: unset; top: unset; left: 0; bottom: 0;
width: 100%; height: 1px;
background: linear-gradient(to right, transparent, #C7D2FE 30%, #C7D2FE 70%, transparent);
}
/* Sembunyikan steps di mobile agar tidak terlalu panjang */
.steps { display: none; }
.logo { width: 180px; margin-bottom: 20px; }
.left-title { font-size: 18px; }
.left-sub { font-size: 12px; }
.left-badge { margin-top: 20px; font-size: 11px; }
.right {
order: 2;
padding: 36px 24px 52px;
min-height: auto;
align-items: flex-start;
justify-content: center;
}
.right::before,
.right::after { display: none; }
.form-box { width: 100%; max-width: 420px; }
.form-title { font-size: 21px; }
.form-sub { font-size: 12.5px; margin-bottom: 22px; }
.input-wrap input { font-size: 16px; }
}
@media (max-width: 380px) {
.left { padding: 24px 20px 22px; }
.logo { width: 150px; }
.right { padding: 28px 20px 44px; }
.form-title { font-size: 19px; }
}
</style>
</head>
<body>
<div class="page">
<!-- LEFT -->
<div class="left">
<img src="{{ asset('sbadmin2/img/logosmasa.png') }}" class="logo" alt="Logo">
<div class="left-title">SMAN 1<br>BONDOWOSO</div>
<div class="left-sub">Buat password baru untuk<br>mengamankan akun Anda</div>
<div class="left-badge"><span></span> Sistem Akademik Sekolah</div>
<!-- Step indicators -->
<div class="steps">
<div class="step">
<div class="step-dot done">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>
</div>
<div class="step-text done">Masukkan Email</div>
</div>
<div class="step">
<div class="step-dot done">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>
</div>
<div class="step-text done">Verifikasi OTP</div>
</div>
<div class="step">
<div class="step-dot active">3</div>
<div class="step-text active">Reset Password</div>
</div>
</div>
<h4>Reset Password</h4>
<p>Buat password baru untuk akun Anda</p>
</div>
<div class="sw-card-body">
<!-- RIGHT -->
<div class="right">
<div class="form-box">
{{-- ERROR --}}
<div class="form-icon">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.9)" stroke-width="1.8">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
</div>
<div class="form-title">Reset Password</div>
<div class="form-sub">Buat password baru yang kuat<br>untuk mengamankan akun Anda.</div>
{{-- Alert Error --}}
@if(session('error'))
<div class="sw-alert sw-alert-danger">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<div class="sw-alert-error">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" style="flex-shrink:0">
<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
</svg>
{{ session('error') }}
</div>
@endif
{{-- SUCCESS --}}
{{-- Alert Success --}}
@if(session('success'))
<div class="sw-alert sw-alert-success">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
<div class="sw-alert-success">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" style="flex-shrink:0">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
</svg>
{{ session('success') }}
</div>
@endif
<form method="POST" action="{{ route('password.update') }}">
@csrf
<input type="hidden" name="email" value="{{ request('email') }}">
<input type="hidden" name="email" value="{{ session('email') ?? request('email') }}">
<div class="sw-form-group">
<label class="sw-label">Password Baru <span>*</span></label>
<div class="sw-input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<input type="password" name="password" class="sw-input" placeholder="Password baru" required>
</div>
</div>
<div class="sw-form-group">
<label class="sw-label">Konfirmasi Password <span>*</span></label>
<div class="sw-input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
<input type="password" name="password_confirmation" class="sw-input" placeholder="Konfirmasi password" required>
</div>
</div>
<button type="submit" class="sw-btn-submit">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Reset Password
{{-- Password Baru --}}
<label class="input-label">Password Baru</label>
<div class="input-wrap" style="margin-bottom:6px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
<input type="password" name="password" id="pw-new"
placeholder="Minimal 8 karakter" required
oninput="checkStrength(this.value)">
<button type="button" class="toggle-pw" onclick="togglePw('pw-new', this)" tabindex="-1">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" id="eye-new">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>
</svg>
</button>
</div>
{{-- Strength bar --}}
<div class="pw-strength">
<div class="pw-strength-bar" id="bar1"></div>
<div class="pw-strength-bar" id="bar2"></div>
<div class="pw-strength-bar" id="bar3"></div>
<div class="pw-strength-bar" id="bar4"></div>
</div>
<div class="pw-strength-text" id="strength-text"></div>
{{-- Konfirmasi Password --}}
<label class="input-label">Konfirmasi Password</label>
<div class="input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 11 12 14 22 4"/>
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
</svg>
<input type="password" name="password_confirmation" id="pw-confirm"
placeholder="Ulangi password baru" required>
<button type="button" class="toggle-pw" onclick="togglePw('pw-confirm', this)" tabindex="-1">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" id="eye-confirm">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>
</svg>
</button>
</div>
<button type="submit" class="btn-submit">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17 21 17 13 7 13 7 21"/>
<polyline points="7 3 7 8 15 8"/>
</svg>
Simpan Password Baru
</button>
</form>
<a href="{{ route('login') }}" class="back-link">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3">
<line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/>
</svg>
Kembali ke Login
</a>
</div>
</div>
</div>
@endsection
<script>
// ── Toggle show/hide password ──
function togglePw(id, btn) {
const inp = document.getElementById(id);
const isHidden = inp.type === 'password';
inp.type = isHidden ? 'text' : 'password';
btn.querySelector('svg').innerHTML = isHidden
? '<line x1="1" y1="1" x2="23" y2="23"/><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/>'
: '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>';
}
// ── Password strength checker ──
function checkStrength(val) {
const bars = [
document.getElementById('bar1'),
document.getElementById('bar2'),
document.getElementById('bar3'),
document.getElementById('bar4'),
];
const txt = document.getElementById('strength-text');
// Reset
bars.forEach(b => { b.className = 'pw-strength-bar'; });
if (!val) { txt.textContent = ''; return; }
let score = 0;
if (val.length >= 8) score++;
if (/[A-Z]/.test(val)) score++;
if (/[0-9]/.test(val)) score++;
if (/[^A-Za-z0-9]/.test(val)) score++;
const levels = [
{ cls: 'weak', label: 'Lemah', color: '#EF4444' },
{ cls: 'weak', label: 'Lemah', color: '#EF4444' },
{ cls: 'medium', label: 'Sedang', color: '#F59E0B' },
{ cls: 'strong', label: 'Kuat', color: '#10B981' },
{ cls: 'strong', label: 'Sangat Kuat', color: '#10B981' },
];
const lv = levels[score] || levels[0];
for (let i = 0; i < score; i++) bars[i].classList.add(lv.cls);
txt.textContent = lv.label;
txt.style.color = lv.color;
}
</script>
</body>
</html>

View File

@ -95,8 +95,7 @@
return view('auth.forgot-password');
})->name('password.request');
Route::post('/forgot-password', [AuthController::class, 'sendOtp'])
->name('password.email');
Route::post('forgot-password', [AuthController::class, 'sendForgotPasswordOtp'])->name('password.email');
Route::get('/verify-otp', [AuthController::class, 'showVerifyOtp'])
->name('verify.otp.form');