187 lines
4.1 KiB
JavaScript
187 lines
4.1 KiB
JavaScript
/**
|
|
* CLQMS Frontend - Global Alpine.js Components & Utilities
|
|
*/
|
|
|
|
// Wait for Alpine to be ready
|
|
document.addEventListener('alpine:init', () => {
|
|
|
|
/**
|
|
* Global Auth Store
|
|
* Manages authentication state across the app
|
|
*/
|
|
Alpine.store('auth', {
|
|
user: null,
|
|
isAuthenticated: false,
|
|
|
|
setUser(userData) {
|
|
this.user = userData;
|
|
this.isAuthenticated = !!userData;
|
|
},
|
|
|
|
clearUser() {
|
|
this.user = null;
|
|
this.isAuthenticated = false;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Toast Notification Store
|
|
*/
|
|
Alpine.store('toast', {
|
|
messages: [],
|
|
|
|
show(message, type = 'info', duration = 4000) {
|
|
const id = Date.now();
|
|
this.messages.push({ id, message, type });
|
|
|
|
setTimeout(() => {
|
|
this.dismiss(id);
|
|
}, duration);
|
|
},
|
|
|
|
dismiss(id) {
|
|
this.messages = this.messages.filter(m => m.id !== id);
|
|
},
|
|
|
|
success(message) { this.show(message, 'success'); },
|
|
error(message) { this.show(message, 'error', 6000); },
|
|
info(message) { this.show(message, 'info'); }
|
|
});
|
|
|
|
/**
|
|
* Login Component
|
|
*/
|
|
Alpine.data('loginForm', () => ({
|
|
username: '',
|
|
password: '',
|
|
rememberMe: false,
|
|
showPassword: false,
|
|
isLoading: false,
|
|
error: null,
|
|
|
|
async submitLogin() {
|
|
// Reset error
|
|
this.error = null;
|
|
|
|
// Validation
|
|
if (!this.username.trim()) {
|
|
this.error = 'Please enter your username';
|
|
this.shakeForm();
|
|
return;
|
|
}
|
|
|
|
if (!this.password) {
|
|
this.error = 'Please enter your password';
|
|
this.shakeForm();
|
|
return;
|
|
}
|
|
|
|
// Start loading
|
|
this.isLoading = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
credentials: 'include', // Important for cookies
|
|
body: JSON.stringify({
|
|
username: this.username.trim(),
|
|
password: this.password
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.status === 'success') {
|
|
// Store user data
|
|
Alpine.store('auth').setUser(data.data);
|
|
|
|
// Show success feedback
|
|
Alpine.store('toast').success('Login successful! Redirecting...');
|
|
|
|
// Redirect to dashboard
|
|
setTimeout(() => {
|
|
window.location.href = '/dashboard';
|
|
}, 500);
|
|
} else {
|
|
// Handle error
|
|
this.error = data.message || 'Invalid username or password';
|
|
this.shakeForm();
|
|
}
|
|
} catch (err) {
|
|
console.error('Login error:', err);
|
|
this.error = 'Connection error. Please try again.';
|
|
this.shakeForm();
|
|
} finally {
|
|
this.isLoading = false;
|
|
}
|
|
},
|
|
|
|
shakeForm() {
|
|
const form = this.$refs.loginCard;
|
|
if (form) {
|
|
form.classList.add('shake');
|
|
setTimeout(() => form.classList.remove('shake'), 500);
|
|
}
|
|
},
|
|
|
|
togglePassword() {
|
|
this.showPassword = !this.showPassword;
|
|
}
|
|
}));
|
|
|
|
});
|
|
|
|
/**
|
|
* Utility Functions
|
|
*/
|
|
const Utils = {
|
|
// Format date to locale string
|
|
formatDate(dateString) {
|
|
return new Date(dateString).toLocaleDateString('id-ID', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
});
|
|
},
|
|
|
|
// Debounce function
|
|
debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
},
|
|
|
|
// API helper with credentials
|
|
async api(endpoint, options = {}) {
|
|
const defaultOptions = {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json'
|
|
},
|
|
credentials: 'include'
|
|
};
|
|
|
|
const response = await fetch(endpoint, { ...defaultOptions, ...options });
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.message || 'API request failed');
|
|
}
|
|
|
|
return data;
|
|
}
|
|
};
|
|
|
|
// Expose Utils globally
|
|
window.Utils = Utils;
|