880 lines
28 KiB
Plaintext
880 lines
28 KiB
Plaintext
@{
|
||
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>
|
||
} |