Initial commit - Lost and Found Revisi

This commit is contained in:
bambang-code1
2025-12-20 00:01:08 +07:00
commit f4c838471c
210 changed files with 30706 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
// assets/js/utils/api.js
const ApiUtils = {
call: async (endpoint, options = {}) => {
const token = AuthUtils.getToken();
const defaultOptions = {
headers: {
"Content-Type": "application/json",
...(token && { Authorization: `Bearer ${token}` }),
},
};
const finalOptions = { ...defaultOptions, ...options };
if (options.headers) {
finalOptions.headers = { ...defaultOptions.headers, ...options.headers };
}
try {
const response = await fetch(
`${CONFIG.API_URL}${endpoint}`,
finalOptions
);
// 1. Cek jika Unauthorized (401)
if (response.status === 401) {
AuthUtils.clearAuth();
window.location.href = "/login";
return;
}
// 2. Cek apakah response valid JSON
const contentType = response.headers.get("content-type");
if (!contentType || !contentType.includes("application/json")) {
// Jika bukan JSON (misal text "200 OK" atau HTML error page)
const text = await response.text();
console.error("API Error (Non-JSON Response):", text);
throw new Error("Server returned invalid response format");
}
// 3. Parse JSON dengan aman
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || data.message || "Request failed");
}
return data;
} catch (error) {
console.error("Fetch error:", error);
throw error;
}
},
get: async (endpoint) => {
return ApiUtils.call(endpoint, { method: "GET" });
},
post: async (endpoint, body) => {
return ApiUtils.call(endpoint, {
method: "POST",
body: JSON.stringify(body),
});
},
put: async (endpoint, body) => {
return ApiUtils.call(endpoint, {
method: "PUT",
body: JSON.stringify(body),
});
},
patch: async (endpoint, body) => {
return ApiUtils.call(endpoint, {
method: "PATCH",
body: JSON.stringify(body),
});
},
delete: async (endpoint) => {
return ApiUtils.call(endpoint, { method: "DELETE" });
},
uploadFile: async (endpoint, formData) => {
const token = AuthUtils.getToken();
const response = await fetch(`${CONFIG.API_URL}${endpoint}`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: formData,
});
const data = await response.json();
if (!response.ok) {
if (response.status === 401) {
AuthUtils.clearAuth();
window.location.href = "/login";
}
throw new Error(data.error || "Upload failed");
}
return data;
},
};
+65
View File
@@ -0,0 +1,65 @@
// assets/js/utils/auth.js
const AuthUtils = {
getToken: () => localStorage.getItem("token"),
setToken: (token) => localStorage.setItem("token", token),
removeToken: () => localStorage.removeItem("token"),
getCurrentUser: () => {
const user = localStorage.getItem("user");
return user ? JSON.parse(user) : null;
},
setCurrentUser: (user) => {
localStorage.setItem("user", JSON.stringify(user));
},
removeCurrentUser: () => {
localStorage.removeItem("user");
},
clearAuth: () => {
localStorage.clear();
},
isAuthenticated: () => {
return !!AuthUtils.getToken();
},
hasRole: (role) => {
const user = AuthUtils.getCurrentUser();
return user && user.role === role;
},
redirectBasedOnRole: (user) => {
if (!user || !user.role) {
window.location.href = "/login";
return;
}
const redirectMap = {
admin: "/admin",
manager: "/manager",
user: "/user",
};
window.location.href = redirectMap[user.role] || "/login";
},
checkAuthAndRedirect: (requiredRole = null) => {
const user = AuthUtils.getCurrentUser();
if (!AuthUtils.isAuthenticated() || !user) {
window.location.href = "/login";
return false;
}
if (requiredRole && user.role !== requiredRole) {
AuthUtils.redirectBasedOnRole(user);
return false;
}
return true;
},
};
+115
View File
@@ -0,0 +1,115 @@
// assets/js/utils/helpers.js
const Helpers = {
formatDate: (dateString) => {
return new Date(dateString).toLocaleDateString("id-ID", {
year: "numeric",
month: "long",
day: "numeric",
});
},
formatDateShort: (dateString) => {
return new Date(dateString).toLocaleDateString("id-ID", {
year: "numeric",
month: "short",
day: "numeric",
});
},
formatDateTime: (dateString) => {
return new Date(dateString).toLocaleString("id-ID", {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
},
validateEmail: (email) => {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
},
validatePhone: (phone) => {
const re = /^(08|62)[0-9]{9,12}$/;
return re.test(phone);
},
checkPasswordStrength: (password) => {
let strength = 0;
if (password.length >= 8) strength++;
if (password.match(/[a-z]+/)) strength++;
if (password.match(/[A-Z]+/)) strength++;
if (password.match(/[0-9]+/)) strength++;
if (password.match(/[$@#&!]+/)) strength++;
if (password.length < 8) {
return "";
} else if (strength <= 2) {
return "weak";
} else if (strength <= 4) {
return "medium";
} else {
return "strong";
}
},
debounce: (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
truncateText: (text, maxLength) => {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + "...";
},
getStatusBadgeClass: (status) => {
const statusClasses = {
unclaimed: "bg-blue-500/20 text-blue-400 border border-blue-500/50",
verified: "bg-green-500/20 text-green-400 border border-green-500/50",
pending_claim:
"bg-yellow-500/20 text-yellow-400 border border-yellow-500/50",
case_closed: "bg-slate-500/20 text-slate-400 border border-slate-500/50",
expired: "bg-red-500/20 text-red-400 border border-red-500/50",
active: "bg-green-500/20 text-green-400 border border-green-500/50",
blocked: "bg-red-500/20 text-red-400 border border-red-500/50",
pending: "bg-yellow-500/20 text-yellow-400 border border-yellow-500/50",
approved: "bg-green-500/20 text-green-400 border border-green-500/50",
rejected: "bg-red-500/20 text-red-400 border border-red-500/50",
};
return (
statusClasses[status] ||
"bg-slate-500/20 text-slate-400 border border-slate-500/50"
);
},
getRoleBadgeClass: (role) => {
const roleClasses = {
admin: "bg-red-500/20 text-red-400 border border-red-500/50",
manager: "bg-yellow-500/20 text-yellow-400 border border-yellow-500/50",
user: "bg-blue-500/20 text-blue-400 border border-blue-500/50",
};
return (
roleClasses[role] ||
"bg-slate-500/20 text-slate-400 border border-slate-500/50"
);
},
getInitials: (name) => {
if (!name) return "?";
return name.charAt(0).toUpperCase();
},
showConfirm: (message) => {
return confirm(message);
},
};