update: pindah folder
|
@ -5,4 +5,5 @@ wwwroot/lib/
|
|||
appsettings.Development.json
|
||||
*.user
|
||||
*.db
|
||||
node_modules/
|
||||
node_modules/
|
||||
*.vs
|
|
@ -1,23 +0,0 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
||||
namespace eSPJ.Controllers
|
||||
{
|
||||
[Route("login")]
|
||||
public class LoginController : Controller
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public LoginController(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
ViewBag.SSOLoginUrl = _configuration["SSO:LoginUrl"];
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace eSPJ.Controllers
|
||||
namespace eSPJ.Controllers.SpjDriverController
|
||||
{
|
||||
[Route("detail-penjemputan")]
|
||||
public class DetailPenjemputanController : Controller
|
||||
|
@ -9,13 +9,13 @@ namespace eSPJ.Controllers
|
|||
[HttpGet("")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
return View("~/Views/Admin/Transport/SpjDriver/DetailPenjemputan/Index.cshtml");
|
||||
}
|
||||
|
||||
[HttpGet("batal")]
|
||||
public IActionResult Batal()
|
||||
{
|
||||
return View("~/Views/DetailPenjemputan/Batal.cshtml");
|
||||
return View("~/Views/Admin/Transport/SpjDriver/DetailPenjemputan/Batal.cshtml");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace eSPJ.Controllers
|
||||
namespace eSPJ.Controllers.SpjDriverController
|
||||
{
|
||||
[Route("history")]
|
||||
public class HistoryController : Controller
|
||||
|
@ -9,14 +9,14 @@ namespace eSPJ.Controllers
|
|||
[HttpGet("")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
return View("~/Views/Admin/Transport/SpjDriver/History/Index.cshtml");
|
||||
}
|
||||
|
||||
[HttpGet("details/{id}")]
|
||||
public IActionResult Details(int id)
|
||||
{
|
||||
ViewData["Id"] = id;
|
||||
return View();
|
||||
return View("~/Views/Admin/Transport/SpjDriver/History/Details.cshtml");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ using System.Diagnostics;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using eSPJ.Models;
|
||||
|
||||
namespace eSPJ.Controllers;
|
||||
namespace eSPJ.Controllers.SpjDriverController;
|
||||
|
||||
public class HomeController : Controller
|
||||
{
|
||||
|
@ -16,17 +16,12 @@ public class HomeController : Controller
|
|||
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Batal()
|
||||
{
|
||||
return View();
|
||||
return View("~/Views/Admin/Transport/SpjDriver/Home/Index.cshtml");
|
||||
}
|
||||
|
||||
public IActionResult Privacy()
|
||||
{
|
||||
return View();
|
||||
return View("~/Views/Admin/Transport/SpjDriver/Home/Privacy.cshtml");
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
|
@ -0,0 +1,23 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
||||
namespace eSPJ.Controllers.SpjDriverController
|
||||
{
|
||||
[Route("login")]
|
||||
public class LoginController : Controller
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public LoginController(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
ViewBag.SSOLoginUrl = _configuration["SSO:LoginUrl"];
|
||||
return View("~/Views/Admin/Transport/SpjDriver/Login/Index.cshtml");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace eSPJ.Controllers
|
||||
namespace eSPJ.Controllers.SpjDriverController
|
||||
{
|
||||
[Route("profil")]
|
||||
public class ProfilController : Controller
|
||||
|
@ -9,7 +9,7 @@ namespace eSPJ.Controllers
|
|||
[HttpGet("")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
return View("~/Views/Admin/Transport/SpjDriver/Profil/Index.cshtml");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace eSPJ.Controllers
|
||||
namespace eSPJ.Controllers.SpjDriverController
|
||||
{
|
||||
[Route("submit")]
|
||||
public class SubmitController : Controller
|
||||
|
@ -9,14 +9,14 @@ namespace eSPJ.Controllers
|
|||
[HttpGet("")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
return View("~/Views/Admin/Transport/SpjDriver/Submit/Index.cshtml");
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("struk")]
|
||||
public IActionResult Struk()
|
||||
{
|
||||
return View();
|
||||
return View("~/Views/Admin/Transport/SpjDriver/Submit/Struk.cshtml");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
@{
|
||||
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Detail Batal Penjemputan";
|
||||
}
|
||||
|
||||
|
@ -69,8 +70,9 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Shared/Components/_Navigation.cshtml" />
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||
</div>
|
||||
|
||||
<register-block dynamic-section="scripts" key="jsDetailBatal">
|
|
@ -1,4 +1,5 @@
|
|||
@{
|
||||
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Detail Penjemputan";
|
||||
}
|
||||
|
||||
|
@ -49,7 +50,7 @@
|
|||
<h3 class="font-bold text-gray-800 text-center text-lg tracking-wide">Masukkan Odometer</h3>
|
||||
<div class="flex justify-center">
|
||||
<div class="relative flex items-center justify-center">
|
||||
<img src="@Url.Content("~/odo_simple.svg")" class="overflow-visible drop-shadow-lg scale-110" alt="">
|
||||
<img src="@Url.Content("~/driver/odo_simple.svg")" class="overflow-visible drop-shadow-lg scale-110" alt="">
|
||||
<!-- Digital display -->
|
||||
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2">
|
||||
<input type="text" maxlength="7" class="bg-gray-900 text-green-400 px-4 py-2 rounded-lg text-lg font-mono tracking-widest text-center w-36 outline-none border-2 border-green-400 focus:ring-2 focus:ring-orange-400 transition" placeholder="000000" />
|
||||
|
@ -66,8 +67,9 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Shared/Components/_Navigation.cshtml" />
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||
</div>
|
||||
|
||||
<register-block dynamic-section="scripts" key="jsDetailPenjemputan">
|
|
@ -1,4 +1,5 @@
|
|||
@{
|
||||
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Detail Perjalanan - DLH";
|
||||
}
|
||||
|
||||
|
@ -157,6 +158,7 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Shared/Components/_Navigation.cshtml" />
|
||||
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||
</div>
|
|
@ -1,4 +1,5 @@
|
|||
@{
|
||||
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "History - DLH";
|
||||
}
|
||||
|
||||
|
@ -137,8 +138,9 @@
|
|||
}
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Shared/Components/_Navigation.cshtml" />
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||
|
||||
<!-- Kalau butuh tampilan kosong (jika tidak ada data) -->
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
@{
|
||||
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Home Page";
|
||||
}
|
||||
|
||||
|
@ -28,7 +29,7 @@
|
|||
<!-- SPJ Data Card -->
|
||||
<div class="bg-green-500 text-white mx-4 px-6 py-6 mt-40 rounded-2xl relative overflow-hidden shadow-lg z-21">
|
||||
<div class="absolute right-0 bottom-0 opacity-30 pointer-events-none select-none" style="width: 160px; height: 160px;">
|
||||
<img src="@Url.Content("~/tree.svg")" alt="tree" class="w-full h-full object-contain">
|
||||
<img src="@Url.Content("~/driver/tree.svg")" alt="tree" class="w-full h-full object-contain">
|
||||
</div>
|
||||
<div class="relative z-10">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
|
@ -48,7 +49,7 @@
|
|||
</div>
|
||||
<!-- DLH logo -->
|
||||
<div class="absolute bottom-2 right-4 opacity-80" style="width: 48px; height: 48px;">
|
||||
<img src="@Url.Content("~/dlh_type.svg")" alt="DLH Logo" class="w-full h-full object-contain">
|
||||
<img src="@Url.Content("~/driver/dlh_type.svg")" alt="DLH Logo" class="w-full h-full object-contain">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -131,7 +132,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Shared/Components/_Navigation.cshtml" />
|
||||
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,880 @@
|
|||
@{
|
||||
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Login eSPJ";
|
||||
}
|
||||
|
||||
<div class="bg-gradient-to-br from-indigo-50 via-white to-purple-50">
|
||||
<div class="max-w-sm mx-auto bg-white min-h-screen shadow-xl relative overflow-hidden">
|
||||
|
||||
<!-- Splash Screens Container -->
|
||||
<div id="splashContainer" class="splash-container">
|
||||
|
||||
<!-- Welcome Screen -->
|
||||
<div class="slide">
|
||||
<div class="slide-content flex flex-col items-center">
|
||||
@* <div class="icon-circle">
|
||||
<svg width="32" height="32" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z" color="white"></path>
|
||||
</svg>
|
||||
</div> *@
|
||||
<img class="w-20 h-20" src="@Url.Content("~/driver/logo.svg")" alt="">
|
||||
<h2>Selamat Datang di eSPJ</h2>
|
||||
<p>Aplikasi modern untuk pengelolaan Surat Perintah Jalan Driver yang efisien dan terintegrasi.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Monitoring Screen -->
|
||||
<div class="slide">
|
||||
<div class="slide-content">
|
||||
<div class="icon-circle">
|
||||
<i class="w-10 h-10 text-white" data-lucide="home"></i>
|
||||
</div>
|
||||
<h2>Monitoring Real-Time</h2>
|
||||
<p>Pantau status SPJ driver, kondisi kendaraan, dan muatan di setiap lokasi secara langsung.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Integrasi Lengkap Screen -->
|
||||
<div class="slide">
|
||||
<div class="slide-content">
|
||||
<div class="icon-circle">
|
||||
<svg width="32" height="32" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 4v16m8-8H4" color="white"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2>Integrasi Lengkap</h2>
|
||||
<p>Sistem terhubung antara admin, driver, dan manajemen untuk pengelolaan SPJ yang efisien.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Login Screen -->
|
||||
<div class="slide">
|
||||
<div style="width: 100%; display: flex; align-items: center; justify-content: center; min-height: 100vh;">
|
||||
<div class="login-form">
|
||||
<div class="login-icon">
|
||||
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" color="white"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-bottom: 2rem;">
|
||||
<h2 style="font-size: 1.5rem; font-weight: 700; color: #1f2937; margin-bottom: 0.5rem;">Masuk ke eSPJ</h2>
|
||||
<p style="color: #6b7280; font-size: 0.9rem;">Gunakan Single Sign-On untuk akses yang aman</p>
|
||||
</div>
|
||||
|
||||
<button class="sso-btn" onclick="showSSOModal()">
|
||||
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
||||
</svg>
|
||||
Masuk dengan SSO
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Controls -->
|
||||
<div id="navigationControls" class="navigation">
|
||||
<!-- Dot Navigation -->
|
||||
<div class="dots">
|
||||
<div class="dot active" data-slide="0"></div>
|
||||
<div class="dot" data-slide="1"></div>
|
||||
<div class="dot" data-slide="2"></div>
|
||||
<div class="dot" data-slide="3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Buttons -->
|
||||
<div class="nav-buttons">
|
||||
<button id="skipBtn" class="btn btn-skip">Lewati</button>
|
||||
<button id="nextBtn" class="btn btn-primary">Selanjutnya</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSO Webview Modal -->
|
||||
<div id="ssoModal" class="webview-modal">
|
||||
<div class="webview-container">
|
||||
<div class="webview-header">
|
||||
<div class="webview-title">Single Sign-On</div>
|
||||
<button class="close-btn" onclick="closeSSOModal()">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div style="position: relative; height: calc(100% - 60px);">
|
||||
<div id="loadingSpinner" class="loading-spinner"></div>
|
||||
<iframe id="ssoIframe" class="webview-iframe" src="" style="display: none;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Update Available Notification -->
|
||||
<div id="updateNotification" class="update-notification" style="display: none;">
|
||||
<div class="update-content">
|
||||
<div class="update-icon">
|
||||
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="update-text">
|
||||
<p>Update tersedia</p>
|
||||
<small>Versi baru aplikasi sudah tersedia</small>
|
||||
</div>
|
||||
<button onclick="applyUpdate()" class="update-btn">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* Display any server-side validation errors *@
|
||||
@if (ViewData.ModelState.ErrorCount > 0)
|
||||
{
|
||||
<div id="errorNotification" class="error-notification">
|
||||
<div class="error-content">
|
||||
@foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors))
|
||||
{
|
||||
<p>@error.ErrorMessage</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@section Styles {
|
||||
<style>
|
||||
|
||||
.splash-container {
|
||||
width: 400%;
|
||||
display: flex;
|
||||
transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
}
|
||||
|
||||
.slide {
|
||||
width: 25%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.slide-content {
|
||||
text-align: center;
|
||||
animation: slideIn 0.8s ease-out;
|
||||
}
|
||||
|
||||
@@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-circle {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 2rem;
|
||||
box-shadow: 0 10px 30px rgba(251, 146, 60, 0.3);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
|
||||
.slide h2 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.slide p {
|
||||
color: #6b7280;
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
position: absolute;
|
||||
bottom: 2rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.dots {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #e5e7eb;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dot.active {
|
||||
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-skip {
|
||||
background: transparent;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.btn-skip:hover {
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(251, 146, 60, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
box-shadow: 0 8px 25px rgba(251, 146, 60, 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.login-form {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 24px;
|
||||
padding: 2rem;
|
||||
margin: 2rem;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||
animation: fadeInUp 0.8s ease-out;
|
||||
}
|
||||
|
||||
@@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.login-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 1.5rem;
|
||||
box-shadow: 0 8px 20px rgba(251, 146, 60, 0.3);
|
||||
}
|
||||
|
||||
.sso-btn, .w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sso-btn {
|
||||
padding: 1rem;
|
||||
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(251, 146, 60, 0.3);
|
||||
}
|
||||
|
||||
.sso-btn:hover {
|
||||
box-shadow: 0 8px 25px rgba(251, 146, 60, 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.webview-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.webview-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 90vw;
|
||||
max-width: 400px;
|
||||
height: 80vh;
|
||||
max-height: 600px;
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
|
||||
animation: modalSlideIn 0.4s ease-out;
|
||||
}
|
||||
|
||||
@@keyframes modalSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -60%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.webview-header {
|
||||
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.webview-title {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
color: white;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.webview-iframe {
|
||||
width: 100%;
|
||||
height: calc(100% - 60px);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid #f3f4f6;
|
||||
border-top: 3px solid #fb923c;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
.update-notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
padding: 1rem;
|
||||
z-index: 1001;
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
@@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.update-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.update-icon {
|
||||
color: #fb923c;
|
||||
}
|
||||
|
||||
.update-btn {
|
||||
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.error-notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #fee2e2;
|
||||
border: 1px solid #fca5a5;
|
||||
color: #dc2626;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
z-index: 1001;
|
||||
animation: slideInRight 0.3s ease-out;
|
||||
}
|
||||
|
||||
@@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* PWA Status Bar Safe Area */
|
||||
@@supports (padding-top: env(safe-area-inset-top)) {
|
||||
.max-w-sm {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@@media (max-width: 480px) {
|
||||
.slide {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin: 1rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.webview-container {
|
||||
width: 95vw;
|
||||
height: 85vh;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
// PWA Service Worker Registration
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/driver/sw.js')
|
||||
.then((registration) => {
|
||||
console.log('SW registered: ', registration);
|
||||
|
||||
// Check for updates
|
||||
registration.addEventListener('updatefound', () => {
|
||||
const newWorker = registration.installing;
|
||||
newWorker.addEventListener('statechange', () => {
|
||||
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||||
showUpdateNotification();
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((registrationError) => {
|
||||
console.log('SW registration failed: ', registrationError);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Splash Screen Navigation
|
||||
let currentSlide = 0;
|
||||
const totalSlides = 4;
|
||||
const container = document.getElementById('splashContainer');
|
||||
const dots = document.querySelectorAll('.dot');
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
const skipBtn = document.getElementById('skipBtn');
|
||||
const navigationControls = document.getElementById('navigationControls');
|
||||
|
||||
function updateSlide(slideIndex) {
|
||||
// Ensure slideIndex is within bounds
|
||||
if (slideIndex < 0) slideIndex = 0;
|
||||
if (slideIndex >= totalSlides) slideIndex = totalSlides - 1;
|
||||
|
||||
currentSlide = slideIndex;
|
||||
container.style.transform = `translateX(-${slideIndex * 25}%)`;
|
||||
|
||||
// Update dots
|
||||
dots.forEach((dot, index) => {
|
||||
dot.classList.toggle('active', index === slideIndex);
|
||||
});
|
||||
|
||||
// Update navigation visibility and button text
|
||||
if (slideIndex === totalSlides - 1) {
|
||||
navigationControls.style.display = 'none';
|
||||
} else {
|
||||
navigationControls.style.display = 'block';
|
||||
nextBtn.textContent = slideIndex === totalSlides - 2 ? 'Masuk' : 'Selanjutnya';
|
||||
}
|
||||
}
|
||||
|
||||
function nextSlide() {
|
||||
if (currentSlide < totalSlides - 1) {
|
||||
currentSlide++;
|
||||
updateSlide(currentSlide);
|
||||
}
|
||||
}
|
||||
|
||||
function skipToLogin() {
|
||||
currentSlide = totalSlides - 1;
|
||||
updateSlide(currentSlide);
|
||||
}
|
||||
|
||||
// Event Listeners
|
||||
nextBtn.addEventListener('click', nextSlide);
|
||||
skipBtn.addEventListener('click', skipToLogin);
|
||||
|
||||
dots.forEach((dot, index) => {
|
||||
dot.addEventListener('click', () => {
|
||||
currentSlide = index;
|
||||
updateSlide(currentSlide);
|
||||
});
|
||||
});
|
||||
|
||||
// Touch/Swipe Navigation
|
||||
let startX = 0;
|
||||
let endX = 0;
|
||||
let isTouch = false;
|
||||
|
||||
container.addEventListener('touchstart', (e) => {
|
||||
startX = e.touches[0].clientX;
|
||||
isTouch = true;
|
||||
}, { passive: true });
|
||||
|
||||
container.addEventListener('touchmove', (e) => {
|
||||
if (!isTouch) return;
|
||||
// Prevent default scrolling behavior during swipe
|
||||
e.preventDefault();
|
||||
}, { passive: false });
|
||||
|
||||
container.addEventListener('touchend', (e) => {
|
||||
if (!isTouch) return;
|
||||
endX = e.changedTouches[0].clientX;
|
||||
handleSwipe();
|
||||
isTouch = false;
|
||||
}, { passive: true });
|
||||
|
||||
function handleSwipe() {
|
||||
const swipeThreshold = 50;
|
||||
const diff = startX - endX;
|
||||
|
||||
if (Math.abs(diff) > swipeThreshold) {
|
||||
if (diff > 0 && currentSlide < totalSlides - 1) {
|
||||
// Swipe left - next slide
|
||||
nextSlide();
|
||||
} else if (diff < 0 && currentSlide > 0) {
|
||||
// Swipe right - previous slide
|
||||
currentSlide--;
|
||||
updateSlide(currentSlide);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SSO Modal Functions
|
||||
function showSSOModal() {
|
||||
const modal = document.getElementById('ssoModal');
|
||||
const iframe = document.getElementById('ssoIframe');
|
||||
const spinner = document.getElementById('loadingSpinner');
|
||||
|
||||
// Show modal
|
||||
modal.style.display = 'block';
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// Show loading spinner
|
||||
spinner.style.display = 'block';
|
||||
iframe.style.display = 'none';
|
||||
|
||||
// Set SSO URL from server-side ViewBag
|
||||
const ssoUrl = '@Html.Raw(ViewBag.SSOLoginUrl ?? "")';
|
||||
if (ssoUrl) {
|
||||
iframe.src = ssoUrl;
|
||||
} else {
|
||||
console.error('SSO URL not provided');
|
||||
closeSSOModal();
|
||||
alert('SSO tidak tersedia saat ini. Silakan gunakan login manual.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle iframe load
|
||||
iframe.onload = function() {
|
||||
spinner.style.display = 'none';
|
||||
iframe.style.display = 'block';
|
||||
};
|
||||
|
||||
// Handle iframe error
|
||||
iframe.onerror = function() {
|
||||
spinner.style.display = 'none';
|
||||
alert('Gagal memuat halaman SSO. Silakan coba lagi.');
|
||||
closeSSOModal();
|
||||
};
|
||||
}
|
||||
|
||||
function closeSSOModal() {
|
||||
const modal = document.getElementById('ssoModal');
|
||||
const iframe = document.getElementById('ssoIframe');
|
||||
|
||||
modal.style.display = 'none';
|
||||
document.body.style.overflow = 'auto';
|
||||
iframe.src = '';
|
||||
}
|
||||
|
||||
// Listen for SSO success message from iframe
|
||||
window.addEventListener('message', function(event) {
|
||||
// Verify origin for security (uncomment and set your SSO domain)
|
||||
// const allowedOrigins = ['https://your-sso-domain.com'];
|
||||
// if (!allowedOrigins.includes(event.origin)) return;
|
||||
|
||||
if (event.data === 'sso-success' || (event.data && event.data.type === 'sso-success')) {
|
||||
closeSSOModal();
|
||||
|
||||
// Show success message
|
||||
showNotification('Login berhasil! Mengarahkan...', 'success');
|
||||
|
||||
// Store login state
|
||||
if (typeof(Storage) !== "undefined") {
|
||||
sessionStorage.setItem('isLoggedIn', 'true');
|
||||
sessionStorage.setItem('loginTime', new Date().toISOString());
|
||||
}
|
||||
|
||||
// Redirect after short delay
|
||||
setTimeout(() => {
|
||||
window.location.href = '@Url.Action("Index", "Home")' || '/';
|
||||
}, 1500);
|
||||
} else if (event.data === 'sso-error' || (event.data && event.data.type === 'sso-error')) {
|
||||
closeSSOModal();
|
||||
showNotification('Login gagal. Silakan coba lagi.', 'error');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle back button on Android PWA
|
||||
window.addEventListener('popstate', function(event) {
|
||||
const modal = document.getElementById('ssoModal');
|
||||
if (modal.style.display === 'block') {
|
||||
closeSSOModal();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// PWA Update Functions
|
||||
function showUpdateNotification() {
|
||||
const notification = document.getElementById('updateNotification');
|
||||
if (notification) {
|
||||
notification.style.display = 'block';
|
||||
|
||||
// Auto-hide after 10 seconds
|
||||
setTimeout(() => {
|
||||
notification.style.display = 'none';
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
function applyUpdate() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.getRegistration().then((registration) => {
|
||||
if (registration && registration.waiting) {
|
||||
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Notification System
|
||||
function showNotification(message, type = 'info') {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification notification-${type}`;
|
||||
notification.innerHTML = `
|
||||
<div class="notification-content">
|
||||
<span>${message}</span>
|
||||
<button onclick="this.parentElement.parentElement.remove()">×</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add notification styles
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: ${type === 'success' ? '#dcfce7' : type === 'error' ? '#fee2e2' : '#f3f4f6'};
|
||||
color: ${type === 'success' ? '#166534' : type === 'error' ? '#dc2626' : '#374151'};
|
||||
border: 1px solid ${type === 'success' ? '#bbf7d0' : type === 'error' ? '#fca5a5' : '#d1d5db'};
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
z-index: 1002;
|
||||
animation: slideInRight 0.3s ease-out;
|
||||
max-width: 300px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Handle connection status
|
||||
function updateConnectionStatus() {
|
||||
const isOnline = navigator.onLine;
|
||||
|
||||
if (!isOnline) {
|
||||
showNotification('Koneksi internet terputus. Beberapa fitur mungkin tidak tersedia.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('online', () => {
|
||||
showNotification('Koneksi internet kembali terhubung.', 'success');
|
||||
});
|
||||
|
||||
window.addEventListener('offline', updateConnectionStatus);
|
||||
|
||||
// Initialize everything when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('DOM Content Loaded');
|
||||
|
||||
// Initialize Lucide icons
|
||||
if (typeof lucide !== 'undefined') {
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
// Auto-hide error notifications after page load
|
||||
const errorNotification = document.getElementById('errorNotification');
|
||||
if (errorNotification) {
|
||||
setTimeout(() => {
|
||||
errorNotification.style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Initialize splash screen - ALWAYS start from first slide
|
||||
currentSlide = 0;
|
||||
updateSlide(0);
|
||||
|
||||
console.log('Splash screen initialized at slide:', currentSlide);
|
||||
|
||||
// Optional: Check if user is a returning visitor and show a different experience
|
||||
// But don't automatically skip the splash screen
|
||||
const isReturningUser = sessionStorage.getItem('hasSeenSplash') === 'true';
|
||||
if (isReturningUser) {
|
||||
// You can add a "Skip Intro" button or similar UX improvement
|
||||
console.log('Returning user detected');
|
||||
// But still show the splash screen by default
|
||||
} else {
|
||||
sessionStorage.setItem('hasSeenSplash', 'true');
|
||||
}
|
||||
});
|
||||
|
||||
// PWA Install Prompt
|
||||
let deferredPrompt;
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
// Prevent Chrome 67 and earlier from automatically showing the prompt
|
||||
e.preventDefault();
|
||||
// Stash the event so it can be triggered later
|
||||
deferredPrompt = e;
|
||||
|
||||
// Show install button/notification
|
||||
showInstallPrompt();
|
||||
});
|
||||
|
||||
function showInstallPrompt() {
|
||||
const installNotification = document.createElement('div');
|
||||
installNotification.innerHTML = `
|
||||
<div style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: white; padding: 1rem; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); z-index: 1001; text-align: center;">
|
||||
<p style="margin-bottom: 1rem; color: #374151;">Install eSPJ untuk pengalaman yang lebih baik</p>
|
||||
<button onclick="installPWA()" style="background: linear-gradient(135deg, #fb923c, #f59e42); color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; margin-right: 0.5rem; cursor: pointer;">Install</button>
|
||||
<button onclick="this.parentElement.parentElement.remove()" style="background: #6b7280; color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; cursor: pointer;">Nanti</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(installNotification);
|
||||
}
|
||||
|
||||
function installPWA() {
|
||||
if (deferredPrompt) {
|
||||
deferredPrompt.prompt();
|
||||
deferredPrompt.userChoice.then((result) => {
|
||||
if (result.outcome === 'accepted') {
|
||||
console.log('User accepted the install prompt');
|
||||
showNotification('Aplikasi berhasil diinstall!', 'success');
|
||||
}
|
||||
deferredPrompt = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
@{
|
||||
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Profil Page";
|
||||
}
|
||||
|
||||
|
@ -99,7 +100,8 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Shared/Components/_Navigation.cshtml" />
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||
|
||||
</div>
|
|
@ -34,9 +34,14 @@
|
|||
|
||||
<!-- Center Submit -->
|
||||
<div class="absolute -top-4 left-1/2 transform -translate-x-1/2">
|
||||
<a href="@Url.Action("Index", "Submit")" id="truckBtn" class="hover:cursor-pointer w-14 h-14 bg-gradient-to-br from-orange-500 via-orange-400 to-orange-600 rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
|
||||
<a href="@Url.Action("Index", "Submit")" id="odoBtn" class="hover:cursor-pointer w-14 h-14 bg-gradient-to-br from-orange-500 via-orange-400 to-orange-600 rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
|
||||
<i class="w-6 h-6 text-white" data-lucide="truck"></i>
|
||||
</a>
|
||||
|
||||
@* <!-- ini untuk yang struk -->
|
||||
<a href="@Url.Action("Struk", "Submit")" id="strukBtn" class="hover:cursor-pointer w-14 h-14 bg-gradient-to-br from-orange-500 via-orange-400 to-orange-600 rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
|
||||
<i class="w-6 h-6 text-white" data-lucide="file-text"></i>
|
||||
</a> *@
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -25,42 +25,31 @@
|
|||
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png">
|
||||
<meta name="msapplication-TileColor" content="#f97316">
|
||||
|
||||
<link rel="manifest" href="~/manifest.json" />
|
||||
<link rel="manifest" href="@Url.Content("~/driver/manifest.json")" />
|
||||
@* <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> *@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="~/css/watch.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/css/website.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/eSPJ.styles.css" asp-append-version="true" />
|
||||
@await RenderSectionAsync("Styles", required: false)
|
||||
<link rel="stylesheet" href="@Url.Content("~/driver/css/watch.css")" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="@Url.Content("~/driver/css/website.css")" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="@Url.Content("~/driver/eSPJ.styles.css")" asp-append-version="true" />
|
||||
@await RenderSectionAsync("Styles", required: false)
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-100">
|
||||
|
||||
@RenderBody()
|
||||
@RenderBody()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
<script src="@Url.Content("~/driver/lib/jquery/dist/jquery.min.js")"></script>
|
||||
<script src="@Url.Content("~/driver/lib/bootstrap/dist/js/bootstrap.bundle.min.js")"></script>
|
||||
<script src="@Url.Content("~/driver/js/site.js")">" asp-append-version="true"></script>
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
lucide.createIcons();
|
||||
});
|
||||
</script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
<dynamic-section name="scripts" />
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/service-worker.js')
|
||||
.then(() => console.log('Service Worker registered'));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<dynamic-section name="scripts" />
|
||||
</body>
|
||||
</html>
|
|
@ -1,4 +1,5 @@
|
|||
@{
|
||||
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Submit Foto Muatan";
|
||||
}
|
||||
|
||||
|
@ -64,8 +65,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Shared/Components/_Navigation.cshtml" />
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||
</div>
|
||||
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
@{
|
||||
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||
ViewData["Title"] = "Submit Struk";
|
||||
}
|
||||
|
||||
|
@ -58,8 +59,8 @@
|
|||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Shared/Components/_Navigation.cshtml" />
|
||||
<!-- Bottom Navigation -->
|
||||
<partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
|
||||
|
||||
</div>
|
||||
|
|
@ -1,277 +0,0 @@
|
|||
@{
|
||||
ViewData["Title"] = "Login eSPJ";
|
||||
}
|
||||
|
||||
<div class="bg-gradient-to-br from-indigo-50 via-white to-purple-50">
|
||||
<div class="max-w-sm mx-auto bg-white min-h-screen shadow-xl relative overflow-hidden">
|
||||
<!-- Background Pattern -->
|
||||
<div class="absolute inset-0 opacity-5">
|
||||
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="#667eea" stroke-width="1"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Splash Screens Container -->
|
||||
<div id="splashContainer" class="splash-container" style="width: 400%; display: flex;">
|
||||
|
||||
<!-- Splash Screen 1 -->
|
||||
<div class="w-1/4 flex flex-col items-center justify-center h-screen px-6 relative">
|
||||
<div class="fade-in flex flex-col items-center">
|
||||
<div class="icon-bg w-24 h-24 rounded-3xl flex items-center justify-center mb-6 shadow-lg">
|
||||
<svg class="w-12 h-12 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold mb-3 text-gray-800 text-center">Selamat Datang di eSPJ</h2>
|
||||
<p class="text-gray-600 text-center leading-relaxed">Aplikasi pengelolaan Surat Perjalanan Dinas yang modern dan efisien</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Splash Screen 2 -->
|
||||
<div class="w-1/4 flex flex-col items-center justify-center h-screen px-6 relative">
|
||||
<div class="fade-in flex flex-col items-center">
|
||||
<div class="icon-bg w-24 h-24 rounded-3xl flex items-center justify-center mb-6 shadow-lg">
|
||||
<svg class="w-12 h-12 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold mb-3 text-gray-800 text-center">Mudah & Cepat</h2>
|
||||
<p class="text-gray-600 text-center leading-relaxed">Proses pengajuan SPJ lebih efisien dan terintegrasi dengan sistem terbaru</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Splash Screen 3 -->
|
||||
<div class="w-1/4 flex flex-col items-center justify-center h-screen px-6 relative">
|
||||
<div class="fade-in flex flex-col items-center">
|
||||
<div class="icon-bg w-24 h-24 rounded-3xl flex items-center justify-center mb-6 shadow-lg">
|
||||
<svg class="w-12 h-12 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold mb-3 text-gray-800 text-center">Keamanan Data Terjamin</h2>
|
||||
<p class="text-gray-600 text-center leading-relaxed">Data perjalanan dinas Anda aman dengan enkripsi tingkat enterprise</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<div class="w-1/4 flex flex-col items-center justify-center h-screen px-6 relative">
|
||||
<div class="w-full max-w-sm slide-up">
|
||||
<div class="glass-effect rounded-3xl p-8 ">
|
||||
<div class="text-center mb-8">
|
||||
<div class="icon-bg w-16 h-16 rounded-2xl flex items-center justify-center mx-auto mb-4 shadow-lg">
|
||||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-2">Masuk ke eSPJ</h2>
|
||||
<p class="text-gray-600">Silakan masukkan kredensial Anda</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<button class="btn-gradient w-full py-3 px-4 rounded-xl text-white font-semibold flex items-center justify-center" type="button" onclick="showSSOModal()">
|
||||
<i class="w-5 h-5 mr-3" data-lucide="key"></i>
|
||||
Masuk dengan SSO
|
||||
</button>
|
||||
<div class="flex items-center justify-center">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="navigationControls" class="absolute bottom-8 left-0 right-0 px-6">
|
||||
<!-- Dot Navigation -->
|
||||
<div class="flex justify-center space-x-2 mb-6">
|
||||
<div class="dot w-3 h-3 rounded-full transition-all duration-300 dot-active" data-slide="0"></div>
|
||||
<div class="dot w-3 h-3 rounded-full transition-all duration-300 dot-inactive" data-slide="1"></div>
|
||||
<div class="dot w-3 h-3 rounded-full transition-all duration-300 dot-inactive" data-slide="2"></div>
|
||||
<div class="dot w-3 h-3 rounded-full transition-all duration-300 dot-inactive" data-slide="3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Next Button -->
|
||||
<div class="flex justify-between items-center">
|
||||
<button id="skipBtn" class="text-gray-500 hover:text-gray-700 transition-colors duration-300">Lewati</button>
|
||||
<button id="nextBtn" class="btn-gradient px-8 py-3 rounded-xl text-white font-semibold transition-all duration-300">
|
||||
Selanjutnya
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal SSO -->
|
||||
<div id="ssoModal" style="display:none; position:fixed; z-index:9999; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.5);">
|
||||
<div style="position:relative; width:100%; height:100%;">
|
||||
<iframe id="ssoIframe" src="" style="width:90vw; max-width:400px; height:70vh; max-height:600px; border-radius:16px; border:none; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); background:#fff;"></iframe>
|
||||
<button onclick="closeSSOModal()" style="position:absolute; top:20px; right:20px; z-index:10000;">Tutup</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Styles {
|
||||
<style>
|
||||
.splash-container {
|
||||
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.8s ease-out;
|
||||
}
|
||||
|
||||
.slide-up {
|
||||
animation: slideUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
@@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@@keyframes slideUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.dot-active {
|
||||
background: linear-gradient(135deg, #fb923c 0%, #f59e42 100%);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.dot-inactive {
|
||||
background: #fde68a;
|
||||
}
|
||||
|
||||
.btn-gradient {
|
||||
background: linear-gradient(135deg, #fb923c 0%, #f59e42 100%);
|
||||
box-shadow: 0 4px 15px rgba(251, 146, 60, 0.4);
|
||||
}
|
||||
|
||||
.btn-gradient:hover {
|
||||
box-shadow: 0 8px 25px rgba(251, 146, 60, 0.6);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.glass-effect {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.icon-bg {
|
||||
background: linear-gradient(135deg, #fb923c 0%, #f59e42 100%);
|
||||
}
|
||||
|
||||
.form-input {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(251, 146, 60, 0.15);
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
let currentSlide = 0;
|
||||
const totalSlides = 4;
|
||||
const container = document.getElementById('splashContainer');
|
||||
const dots = document.querySelectorAll('.dot');
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
const skipBtn = document.getElementById('skipBtn');
|
||||
const navigationControls = document.getElementById('navigationControls');
|
||||
|
||||
function updateSlide(slideIndex) {
|
||||
container.style.transform = `translateX(-${slideIndex * 25}%)`;
|
||||
|
||||
dots.forEach((dot, index) => {
|
||||
if (index === slideIndex) {
|
||||
dot.classList.remove('dot-inactive');
|
||||
dot.classList.add('dot-active');
|
||||
} else {
|
||||
dot.classList.remove('dot-active');
|
||||
dot.classList.add('dot-inactive');
|
||||
}
|
||||
});
|
||||
|
||||
if (slideIndex === totalSlides - 1) {
|
||||
navigationControls.style.display = 'none';
|
||||
} else {
|
||||
navigationControls.style.display = 'block';
|
||||
nextBtn.textContent = slideIndex === totalSlides - 2 ? 'Masuk' : 'Selanjutnya';
|
||||
}
|
||||
}
|
||||
|
||||
function nextSlide() {
|
||||
if (currentSlide < totalSlides - 1) {
|
||||
currentSlide++;
|
||||
updateSlide(currentSlide);
|
||||
}
|
||||
}
|
||||
|
||||
function skipToLogin() {
|
||||
currentSlide = totalSlides - 1;
|
||||
updateSlide(currentSlide);
|
||||
}
|
||||
|
||||
nextBtn.addEventListener('click', nextSlide);
|
||||
skipBtn.addEventListener('click', skipToLogin);
|
||||
|
||||
dots.forEach((dot, index) => {
|
||||
dot.addEventListener('click', () => {
|
||||
currentSlide = index;
|
||||
updateSlide(currentSlide);
|
||||
});
|
||||
});
|
||||
|
||||
let startX = 0;
|
||||
let endX = 0;
|
||||
|
||||
container.addEventListener('touchstart', (e) => {
|
||||
startX = e.touches[0].clientX;
|
||||
});
|
||||
|
||||
container.addEventListener('touchend', (e) => {
|
||||
endX = e.changedTouches[0].clientX;
|
||||
handleSwipe();
|
||||
});
|
||||
|
||||
function handleSwipe() {
|
||||
const swipeThreshold = 50;
|
||||
const diff = startX - endX;
|
||||
|
||||
if (Math.abs(diff) > swipeThreshold) {
|
||||
if (diff > 0 && currentSlide < totalSlides - 1) {
|
||||
nextSlide();
|
||||
} else if (diff < 0 && currentSlide > 0) {
|
||||
currentSlide--;
|
||||
updateSlide(currentSlide);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
function showSSOModal() {
|
||||
document.getElementById('ssoIframe').src = '@ViewBag.SSOLoginUrl';
|
||||
document.getElementById('ssoModal').style.display = 'block';
|
||||
}
|
||||
function closeSSOModal() {
|
||||
document.getElementById('ssoModal').style.display = 'none';
|
||||
document.getElementById('ssoIframe').src = '';
|
||||
}
|
||||
|
||||
// biar bisa nerima pesan dari iframe SSO ( endpoint callback harus mengirim window.parent.postMessage)
|
||||
window.addEventListener('message', function(event) {
|
||||
if (event.data === 'sso-success') {
|
||||
closeSSOModal();
|
||||
window.location.href = '/';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011-2021 Twitter, Inc.
|
||||
Copyright (c) 2011-2021 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,597 @@
|
|||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
|
@ -0,0 +1,594 @@
|
|||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
|
@ -0,0 +1,23 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) .NET Foundation and Contributors
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,435 @@
|
|||
/**
|
||||
* @license
|
||||
* Unobtrusive validation support library for jQuery and jQuery Validate
|
||||
* Copyright (c) .NET Foundation. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
* @version v4.0.0
|
||||
*/
|
||||
|
||||
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */
|
||||
/*global document: false, jQuery: false */
|
||||
|
||||
(function (factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define("jquery.validate.unobtrusive", ['jquery-validation'], factory);
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
// CommonJS-like environments that support module.exports
|
||||
module.exports = factory(require('jquery-validation'));
|
||||
} else {
|
||||
// Browser global
|
||||
jQuery.validator.unobtrusive = factory(jQuery);
|
||||
}
|
||||
}(function ($) {
|
||||
var $jQval = $.validator,
|
||||
adapters,
|
||||
data_validation = "unobtrusiveValidation";
|
||||
|
||||
function setValidationValues(options, ruleName, value) {
|
||||
options.rules[ruleName] = value;
|
||||
if (options.message) {
|
||||
options.messages[ruleName] = options.message;
|
||||
}
|
||||
}
|
||||
|
||||
function splitAndTrim(value) {
|
||||
return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g);
|
||||
}
|
||||
|
||||
function escapeAttributeValue(value) {
|
||||
// As mentioned on http://api.jquery.com/category/selectors/
|
||||
return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
|
||||
}
|
||||
|
||||
function getModelPrefix(fieldName) {
|
||||
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
function appendModelPrefix(value, prefix) {
|
||||
if (value.indexOf("*.") === 0) {
|
||||
value = value.replace("*.", prefix);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function onError(error, inputElement) { // 'this' is the form element
|
||||
var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"),
|
||||
replaceAttrValue = container.attr("data-valmsg-replace"),
|
||||
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null;
|
||||
|
||||
container.removeClass("field-validation-valid").addClass("field-validation-error");
|
||||
error.data("unobtrusiveContainer", container);
|
||||
|
||||
if (replace) {
|
||||
container.empty();
|
||||
error.removeClass("input-validation-error").appendTo(container);
|
||||
}
|
||||
else {
|
||||
error.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function onErrors(event, validator) { // 'this' is the form element
|
||||
var container = $(this).find("[data-valmsg-summary=true]"),
|
||||
list = container.find("ul");
|
||||
|
||||
if (list && list.length && validator.errorList.length) {
|
||||
list.empty();
|
||||
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
|
||||
|
||||
$.each(validator.errorList, function () {
|
||||
$("<li />").html(this.message).appendTo(list);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onSuccess(error) { // 'this' is the form element
|
||||
var container = error.data("unobtrusiveContainer");
|
||||
|
||||
if (container) {
|
||||
var replaceAttrValue = container.attr("data-valmsg-replace"),
|
||||
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null;
|
||||
|
||||
container.addClass("field-validation-valid").removeClass("field-validation-error");
|
||||
error.removeData("unobtrusiveContainer");
|
||||
|
||||
if (replace) {
|
||||
container.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onReset(event) { // 'this' is the form element
|
||||
var $form = $(this),
|
||||
key = '__jquery_unobtrusive_validation_form_reset';
|
||||
if ($form.data(key)) {
|
||||
return;
|
||||
}
|
||||
// Set a flag that indicates we're currently resetting the form.
|
||||
$form.data(key, true);
|
||||
try {
|
||||
$form.data("validator").resetForm();
|
||||
} finally {
|
||||
$form.removeData(key);
|
||||
}
|
||||
|
||||
$form.find(".validation-summary-errors")
|
||||
.addClass("validation-summary-valid")
|
||||
.removeClass("validation-summary-errors");
|
||||
$form.find(".field-validation-error")
|
||||
.addClass("field-validation-valid")
|
||||
.removeClass("field-validation-error")
|
||||
.removeData("unobtrusiveContainer")
|
||||
.find(">*") // If we were using valmsg-replace, get the underlying error
|
||||
.removeData("unobtrusiveContainer");
|
||||
}
|
||||
|
||||
function validationInfo(form) {
|
||||
var $form = $(form),
|
||||
result = $form.data(data_validation),
|
||||
onResetProxy = $.proxy(onReset, form),
|
||||
defaultOptions = $jQval.unobtrusive.options || {},
|
||||
execInContext = function (name, args) {
|
||||
var func = defaultOptions[name];
|
||||
func && $.isFunction(func) && func.apply(form, args);
|
||||
};
|
||||
|
||||
if (!result) {
|
||||
result = {
|
||||
options: { // options structure passed to jQuery Validate's validate() method
|
||||
errorClass: defaultOptions.errorClass || "input-validation-error",
|
||||
errorElement: defaultOptions.errorElement || "span",
|
||||
errorPlacement: function () {
|
||||
onError.apply(form, arguments);
|
||||
execInContext("errorPlacement", arguments);
|
||||
},
|
||||
invalidHandler: function () {
|
||||
onErrors.apply(form, arguments);
|
||||
execInContext("invalidHandler", arguments);
|
||||
},
|
||||
messages: {},
|
||||
rules: {},
|
||||
success: function () {
|
||||
onSuccess.apply(form, arguments);
|
||||
execInContext("success", arguments);
|
||||
}
|
||||
},
|
||||
attachValidation: function () {
|
||||
$form
|
||||
.off("reset." + data_validation, onResetProxy)
|
||||
.on("reset." + data_validation, onResetProxy)
|
||||
.validate(this.options);
|
||||
},
|
||||
validate: function () { // a validation function that is called by unobtrusive Ajax
|
||||
$form.validate();
|
||||
return $form.valid();
|
||||
}
|
||||
};
|
||||
$form.data(data_validation, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
$jQval.unobtrusive = {
|
||||
adapters: [],
|
||||
|
||||
parseElement: function (element, skipAttach) {
|
||||
/// <summary>
|
||||
/// Parses a single HTML element for unobtrusive validation attributes.
|
||||
/// </summary>
|
||||
/// <param name="element" domElement="true">The HTML element to be parsed.</param>
|
||||
/// <param name="skipAttach" type="Boolean">[Optional] true to skip attaching the
|
||||
/// validation to the form. If parsing just this single element, you should specify true.
|
||||
/// If parsing several elements, you should specify false, and manually attach the validation
|
||||
/// to the form when you are finished. The default is false.</param>
|
||||
var $element = $(element),
|
||||
form = $element.parents("form")[0],
|
||||
valInfo, rules, messages;
|
||||
|
||||
if (!form) { // Cannot do client-side validation without a form
|
||||
return;
|
||||
}
|
||||
|
||||
valInfo = validationInfo(form);
|
||||
valInfo.options.rules[element.name] = rules = {};
|
||||
valInfo.options.messages[element.name] = messages = {};
|
||||
|
||||
$.each(this.adapters, function () {
|
||||
var prefix = "data-val-" + this.name,
|
||||
message = $element.attr(prefix),
|
||||
paramValues = {};
|
||||
|
||||
if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy)
|
||||
prefix += "-";
|
||||
|
||||
$.each(this.params, function () {
|
||||
paramValues[this] = $element.attr(prefix + this);
|
||||
});
|
||||
|
||||
this.adapt({
|
||||
element: element,
|
||||
form: form,
|
||||
message: message,
|
||||
params: paramValues,
|
||||
rules: rules,
|
||||
messages: messages
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$.extend(rules, { "__dummy__": true });
|
||||
|
||||
if (!skipAttach) {
|
||||
valInfo.attachValidation();
|
||||
}
|
||||
},
|
||||
|
||||
parse: function (selector) {
|
||||
/// <summary>
|
||||
/// Parses all the HTML elements in the specified selector. It looks for input elements decorated
|
||||
/// with the [data-val=true] attribute value and enables validation according to the data-val-*
|
||||
/// attribute values.
|
||||
/// </summary>
|
||||
/// <param name="selector" type="String">Any valid jQuery selector.</param>
|
||||
|
||||
// $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one
|
||||
// element with data-val=true
|
||||
var $selector = $(selector),
|
||||
$forms = $selector.parents()
|
||||
.addBack()
|
||||
.filter("form")
|
||||
.add($selector.find("form"))
|
||||
.has("[data-val=true]");
|
||||
|
||||
$selector.find("[data-val=true]").each(function () {
|
||||
$jQval.unobtrusive.parseElement(this, true);
|
||||
});
|
||||
|
||||
$forms.each(function () {
|
||||
var info = validationInfo(this);
|
||||
if (info) {
|
||||
info.attachValidation();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
adapters = $jQval.unobtrusive.adapters;
|
||||
|
||||
adapters.add = function (adapterName, params, fn) {
|
||||
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation.</summary>
|
||||
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
|
||||
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
|
||||
/// <param name="params" type="Array" optional="true">[Optional] An array of parameter names (strings) that will
|
||||
/// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and
|
||||
/// mmmm is the parameter name).</param>
|
||||
/// <param name="fn" type="Function">The function to call, which adapts the values from the HTML
|
||||
/// attributes into jQuery Validate rules and/or messages.</param>
|
||||
/// <returns type="jQuery.validator.unobtrusive.adapters" />
|
||||
if (!fn) { // Called with no params, just a function
|
||||
fn = params;
|
||||
params = [];
|
||||
}
|
||||
this.push({ name: adapterName, params: params, adapt: fn });
|
||||
return this;
|
||||
};
|
||||
|
||||
adapters.addBool = function (adapterName, ruleName) {
|
||||
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
|
||||
/// the jQuery Validate validation rule has no parameter values.</summary>
|
||||
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
|
||||
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
|
||||
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
|
||||
/// of adapterName will be used instead.</param>
|
||||
/// <returns type="jQuery.validator.unobtrusive.adapters" />
|
||||
return this.add(adapterName, function (options) {
|
||||
setValidationValues(options, ruleName || adapterName, true);
|
||||
});
|
||||
};
|
||||
|
||||
adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) {
|
||||
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
|
||||
/// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and
|
||||
/// one for min-and-max). The HTML parameters are expected to be named -min and -max.</summary>
|
||||
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
|
||||
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
|
||||
/// <param name="minRuleName" type="String">The name of the jQuery Validate rule to be used when you only
|
||||
/// have a minimum value.</param>
|
||||
/// <param name="maxRuleName" type="String">The name of the jQuery Validate rule to be used when you only
|
||||
/// have a maximum value.</param>
|
||||
/// <param name="minMaxRuleName" type="String">The name of the jQuery Validate rule to be used when you
|
||||
/// have both a minimum and maximum value.</param>
|
||||
/// <param name="minAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
|
||||
/// contains the minimum value. The default is "min".</param>
|
||||
/// <param name="maxAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
|
||||
/// contains the maximum value. The default is "max".</param>
|
||||
/// <returns type="jQuery.validator.unobtrusive.adapters" />
|
||||
return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) {
|
||||
var min = options.params.min,
|
||||
max = options.params.max;
|
||||
|
||||
if (min && max) {
|
||||
setValidationValues(options, minMaxRuleName, [min, max]);
|
||||
}
|
||||
else if (min) {
|
||||
setValidationValues(options, minRuleName, min);
|
||||
}
|
||||
else if (max) {
|
||||
setValidationValues(options, maxRuleName, max);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
adapters.addSingleVal = function (adapterName, attribute, ruleName) {
|
||||
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
|
||||
/// the jQuery Validate validation rule has a single value.</summary>
|
||||
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
|
||||
/// in the data-val-nnnn HTML attribute(where nnnn is the adapter name).</param>
|
||||
/// <param name="attribute" type="String">[Optional] The name of the HTML attribute that contains the value.
|
||||
/// The default is "val".</param>
|
||||
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
|
||||
/// of adapterName will be used instead.</param>
|
||||
/// <returns type="jQuery.validator.unobtrusive.adapters" />
|
||||
return this.add(adapterName, [attribute || "val"], function (options) {
|
||||
setValidationValues(options, ruleName || adapterName, options.params[attribute]);
|
||||
});
|
||||
};
|
||||
|
||||
$jQval.addMethod("__dummy__", function (value, element, params) {
|
||||
return true;
|
||||
});
|
||||
|
||||
$jQval.addMethod("regex", function (value, element, params) {
|
||||
var match;
|
||||
if (this.optional(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
match = new RegExp(params).exec(value);
|
||||
return (match && (match.index === 0) && (match[0].length === value.length));
|
||||
});
|
||||
|
||||
$jQval.addMethod("nonalphamin", function (value, element, nonalphamin) {
|
||||
var match;
|
||||
if (nonalphamin) {
|
||||
match = value.match(/\W/g);
|
||||
match = match && match.length >= nonalphamin;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
if ($jQval.methods.extension) {
|
||||
adapters.addSingleVal("accept", "mimtype");
|
||||
adapters.addSingleVal("extension", "extension");
|
||||
} else {
|
||||
// for backward compatibility, when the 'extension' validation method does not exist, such as with versions
|
||||
// of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for
|
||||
// validating the extension, and ignore mime-type validations as they are not supported.
|
||||
adapters.addSingleVal("extension", "extension", "accept");
|
||||
}
|
||||
|
||||
adapters.addSingleVal("regex", "pattern");
|
||||
adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");
|
||||
adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range");
|
||||
adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength");
|
||||
adapters.add("equalto", ["other"], function (options) {
|
||||
var prefix = getModelPrefix(options.element.name),
|
||||
other = options.params.other,
|
||||
fullOtherName = appendModelPrefix(other, prefix),
|
||||
element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0];
|
||||
|
||||
setValidationValues(options, "equalTo", element);
|
||||
});
|
||||
adapters.add("required", function (options) {
|
||||
// jQuery Validate equates "required" with "mandatory" for checkbox elements
|
||||
if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") {
|
||||
setValidationValues(options, "required", true);
|
||||
}
|
||||
});
|
||||
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
|
||||
var value = {
|
||||
url: options.params.url,
|
||||
type: options.params.type || "GET",
|
||||
data: {}
|
||||
},
|
||||
prefix = getModelPrefix(options.element.name);
|
||||
|
||||
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
|
||||
var paramName = appendModelPrefix(fieldName, prefix);
|
||||
value.data[paramName] = function () {
|
||||
var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']");
|
||||
// For checkboxes and radio buttons, only pick up values from checked fields.
|
||||
if (field.is(":checkbox")) {
|
||||
return field.filter(":checked").val() || field.filter(":hidden").val() || '';
|
||||
}
|
||||
else if (field.is(":radio")) {
|
||||
return field.filter(":checked").val() || '';
|
||||
}
|
||||
return field.val();
|
||||
};
|
||||
});
|
||||
|
||||
setValidationValues(options, "remote", value);
|
||||
});
|
||||
adapters.add("password", ["min", "nonalphamin", "regex"], function (options) {
|
||||
if (options.params.min) {
|
||||
setValidationValues(options, "minlength", options.params.min);
|
||||
}
|
||||
if (options.params.nonalphamin) {
|
||||
setValidationValues(options, "nonalphamin", options.params.nonalphamin);
|
||||
}
|
||||
if (options.params.regex) {
|
||||
setValidationValues(options, "regex", options.params.regex);
|
||||
}
|
||||
});
|
||||
adapters.add("fileextensions", ["extensions"], function (options) {
|
||||
setValidationValues(options, "extension", options.params.extensions);
|
||||
});
|
||||
|
||||
$(function () {
|
||||
$jQval.unobtrusive.parse(document);
|
||||
});
|
||||
|
||||
return $jQval.unobtrusive;
|
||||
}));
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
=====================
|
||||
|
||||
Copyright Jörn Zaefferer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,9 @@
|
|||
<svg width="141" height="140" viewBox="0 0 141 140" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M30.2734 2.05371C74.7715 2.66045 79.8364 41.1082 80.7119 57.207V115.636H85.75V44.4717C85.7501 37.2786 91.5756 31.4543 98.7549 31.4541V88.252H100.156L100.372 86.1016C101.234 77.4926 108.649 71.897 117.573 72.0049C132.733 72.2208 138 82.441 138 95.0342L137.993 95.0273V140H133.319C128.659 140 124.88 136.218 124.88 131.553V96.8535C124.88 88.245 120.684 84.6919 114.239 84.584C104.992 84.2604 98.7549 90.2878 98.7549 100.939V115.636H105.039C110.184 115.636 114.36 119.816 114.36 124.966V128.229H67.7002V57.207H67.7256C67.7144 57.1018 67.7047 57.0069 67.7002 56.9238C67.0402 44.7688 64.1034 16.4004 34.5771 13.6836C33.1023 21.4501 32.2877 38.1421 50.0742 45.4297C51.7972 36.4559 42.4024 24.1019 42.457 23.9844C55.7154 31.9585 61.8678 43.1728 62.5732 57.3008H62.6016L62.6025 58.0049C62.6295 58.7335 62.6439 59.47 62.6426 60.2139L62.6084 60.207L62.71 100.083L62.6016 128.566V128.573H49.6104L49.502 110.081H48.1084L48 111.261C46.9291 122.546 37.6889 129.537 25.667 129.537C10.3113 129.537 4.86308e-05 119.216 0 100.73C0 82.2451 10.0957 71.169 25.667 71.1689C37.9112 71.1689 47.145 78.3686 48.2158 91.3799L48.3232 92.5596H49.6104V57.3945C11.007 45.7426 24.7593 6.0654 24.7979 5.94336L26.1113 2L30.2734 2.05371ZM28.1318 83.8564C19.1138 83.8566 13.0999 89.7688 12.9922 100.73C13.1 111.692 19.0061 116.856 28.1318 116.856C37.9042 116.856 44.1343 110.513 44.2422 99.9824C44.3499 90.2004 37.9043 83.8564 28.1318 83.8564Z" fill="#59C207"/>
|
||||
<path d="M18.7641 75C14.7641 77 6.4641 83.3 5.2641 92.5C4.2641 88.1667 5.5641 78.6 18.7641 75Z" fill="#C9FF9E"/>
|
||||
<path d="M96 34C93.9657 35.0286 89.7446 38.2686 89.1343 43C88.6257 40.7714 89.2869 35.8514 96 34Z" fill="#C9FF9E"/>
|
||||
<path d="M133.039 97.7773C133.366 93.3171 132.196 82.9628 124.903 77.227C129.139 78.5835 136.695 84.5928 133.039 97.7773Z" fill="#C9FF9E"/>
|
||||
<path d="M52.1485 12.9621C48.8059 9.9911 39.8738 4.62472 30.8862 6.92757C34.5382 4.38964 43.9033 2.04346 52.1485 12.9621Z" fill="#C9FF9E"/>
|
||||
<path d="M62.5 128.5H49.5C56.7 128.5 61.1667 120.167 62.5 116V128.5Z" fill="#C9FF9E" fill-opacity="0.3"/>
|
||||
<path d="M68 128.5H81C73.8 128.5 69.3333 120.167 68 116V128.5Z" fill="#C9FF9E" fill-opacity="0.3"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"name": "eSPJ - Surat Perjalanan Dinas",
|
||||
"short_name": "eSPJ",
|
||||
"description": "Aplikasi pengelolaan Surat Perjalanan Dinas yang modern dan efisien",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#fb923c",
|
||||
"orientation": "portrait-primary",
|
||||
"scope": "/",
|
||||
"lang": "id",
|
||||
"dir": "ltr",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "icon-96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "icon-128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "icon-144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "icon-152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "icon-384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "screenshot-wide.png",
|
||||
"sizes": "1280x720",
|
||||
"type": "image/png",
|
||||
"form_factor": "wide"
|
||||
},
|
||||
{
|
||||
"src": "screenshot-narrow.png",
|
||||
"sizes": "360x640",
|
||||
"type": "image/png",
|
||||
"form_factor": "narrow"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Login",
|
||||
"short_name": "Login",
|
||||
"description": "Masuk ke aplikasi eSPJ",
|
||||
"url": "/login",
|
||||
"icons": [{ "src": "icon-96.png", "sizes": "96x96" }]
|
||||
},
|
||||
{
|
||||
"name": "Dashboard",
|
||||
"short_name": "Dashboard",
|
||||
"description": "Buka dashboard utama",
|
||||
"url": "/dashboard",
|
||||
"icons": [{ "src": "icon-96.png", "sizes": "96x96" }]
|
||||
}
|
||||
]
|
||||
}
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
@ -0,0 +1,227 @@
|
|||
const CACHE_NAME = "espj-v1.0.0";
|
||||
const urlsToCache = [
|
||||
"/",
|
||||
"/login",
|
||||
"/driver/manifest.json",
|
||||
"/driver/icon-192.png",
|
||||
"/driver/icon-512.png",
|
||||
"https://unpkg.com/lucide@latest",
|
||||
"https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&display=swap",
|
||||
];
|
||||
|
||||
// Install Service Worker
|
||||
self.addEventListener("install", (event) => {
|
||||
console.log("[SW] Installing...");
|
||||
event.waitUntil(
|
||||
caches
|
||||
.open(CACHE_NAME)
|
||||
.then((cache) => {
|
||||
console.log("[SW] Caching app shell");
|
||||
return cache.addAll(urlsToCache);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("[SW] Skip waiting");
|
||||
return self.skipWaiting();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Activate Service Worker
|
||||
self.addEventListener("activate", (event) => {
|
||||
console.log("[SW] Activating...");
|
||||
event.waitUntil(
|
||||
caches
|
||||
.keys()
|
||||
.then((cacheNames) => {
|
||||
return Promise.all(
|
||||
cacheNames.map((cacheName) => {
|
||||
if (cacheName !== CACHE_NAME) {
|
||||
console.log("[SW] Deleting old cache:", cacheName);
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("[SW] Claiming clients");
|
||||
return self.clients.claim();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch Event - Cache Strategy: Network First with Cache Fallback
|
||||
self.addEventListener("fetch", (event) => {
|
||||
// Skip cross-origin requests
|
||||
if (!event.request.url.startsWith(self.location.origin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.respondWith(
|
||||
fetch(event.request)
|
||||
.then((response) => {
|
||||
// Check if we received a valid response
|
||||
if (!response || response.status !== 200 || response.type !== "basic") {
|
||||
return response;
|
||||
}
|
||||
|
||||
// Clone the response
|
||||
const responseToCache = response.clone();
|
||||
|
||||
caches.open(CACHE_NAME).then((cache) => {
|
||||
cache.put(event.request, responseToCache);
|
||||
});
|
||||
|
||||
return response;
|
||||
})
|
||||
.catch(() => {
|
||||
// Network failed, serve from cache
|
||||
return caches.match(event.request).then((response) => {
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
// If no cache match, return offline page for navigation requests
|
||||
if (event.request.mode === "navigate") {
|
||||
return caches.match("/offline.html");
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Background Sync for offline actions
|
||||
self.addEventListener("sync", (event) => {
|
||||
console.log("[SW] Background sync:", event.tag);
|
||||
|
||||
if (event.tag === "background-sync-login") {
|
||||
event.waitUntil(handleBackgroundLogin());
|
||||
}
|
||||
});
|
||||
|
||||
// Push Notifications
|
||||
self.addEventListener("push", (event) => {
|
||||
console.log("[SW] Push received:", event.data?.text());
|
||||
|
||||
const options = {
|
||||
body: event.data?.text() || "Notifikasi dari eSPJ",
|
||||
icon: "/icon-192.png",
|
||||
badge: "/icon-72.png",
|
||||
vibrate: [100, 50, 100],
|
||||
data: {
|
||||
dateOfArrival: Date.now(),
|
||||
primaryKey: "1",
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
action: "explore",
|
||||
title: "Buka Aplikasi",
|
||||
icon: "/icon-96.png",
|
||||
},
|
||||
{
|
||||
action: "close",
|
||||
title: "Tutup",
|
||||
icon: "/icon-96.png",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
event.waitUntil(self.registration.showNotification("eSPJ", options));
|
||||
});
|
||||
|
||||
// Notification Click Handler
|
||||
self.addEventListener("notificationclick", (event) => {
|
||||
console.log("[SW] Notification click received.");
|
||||
|
||||
event.notification.close();
|
||||
|
||||
if (event.action === "explore") {
|
||||
event.waitUntil(clients.openWindow("/"));
|
||||
} else if (event.action === "close") {
|
||||
// Just close the notification
|
||||
return;
|
||||
} else {
|
||||
// Default action - open the app
|
||||
event.waitUntil(
|
||||
clients.matchAll().then((clientList) => {
|
||||
for (const client of clientList) {
|
||||
if (client.url === "/" && "focus" in client) {
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow("/");
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle background login sync
|
||||
async function handleBackgroundLogin() {
|
||||
try {
|
||||
// Implement your background login logic here
|
||||
console.log("[SW] Handling background login sync");
|
||||
|
||||
// Example: retry failed login attempts
|
||||
const failedLogins = await getFailedLogins();
|
||||
|
||||
for (const login of failedLogins) {
|
||||
try {
|
||||
await retryLogin(login);
|
||||
await removeFailedLogin(login.id);
|
||||
} catch (error) {
|
||||
console.error("[SW] Failed to retry login:", error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[SW] Background sync failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for background sync
|
||||
async function getFailedLogins() {
|
||||
// Implement logic to get failed login attempts from IndexedDB
|
||||
return [];
|
||||
}
|
||||
|
||||
async function retryLogin(loginData) {
|
||||
// Implement retry login logic
|
||||
const response = await fetch("/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(loginData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Login retry failed");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function removeFailedLogin(loginId) {
|
||||
// Implement logic to remove successful login from IndexedDB
|
||||
console.log("[SW] Removing failed login:", loginId);
|
||||
}
|
||||
|
||||
// Message handling between SW and main thread
|
||||
self.addEventListener("message", (event) => {
|
||||
console.log("[SW] Message received:", event.data);
|
||||
|
||||
if (event.data && event.data.type === "SKIP_WAITING") {
|
||||
self.skipWaiting();
|
||||
}
|
||||
|
||||
if (event.data && event.data.type === "GET_VERSION") {
|
||||
event.ports[0].postMessage({ version: CACHE_NAME });
|
||||
}
|
||||
});
|
||||
|
||||
// Update available notification
|
||||
self.addEventListener("message", (event) => {
|
||||
if (event.data.action === "skipWaiting") {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
@ -1,48 +0,0 @@
|
|||
{
|
||||
"name": "eSPJ - DLH",
|
||||
"short_name": "eSPJ - DLH",
|
||||
"description": "Aplikasi pemantauan SPJ dan lokasi driver UPST",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#f97316",
|
||||
"orientation": "portrait",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/icon-72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
const CACHE_NAME = "spj-dlh-v1";
|
||||
const urlsToCache = [
|
||||
"/",
|
||||
"/css/site.css",
|
||||
"/js/site.js",
|
||||
"/icons/icon-192.png",
|
||||
"/icons/icon-512.png",
|
||||
];
|
||||
|
||||
// Install
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then((cache) => cache.addAll(urlsToCache))
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch
|
||||
self.addEventListener("fetch", (event) => {
|
||||
event.respondWith(
|
||||
caches
|
||||
.match(event.request)
|
||||
.then((response) => response || fetch(event.request))
|
||||
);
|
||||
});
|
||||
|
||||
// Activate
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
caches
|
||||
.keys()
|
||||
.then((cacheNames) =>
|
||||
Promise.all(
|
||||
cacheNames
|
||||
.filter((name) => name !== CACHE_NAME)
|
||||
.map((name) => caches.delete(name))
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|