Spaces:
Running
Running
Upload 7 files
Browse files- javascript/cart.js +187 -3
- javascript/component.js +298 -3
- javascript/data.js +204 -3
- javascript/main.js +505 -3
- javascript/payment.js +538 -3
- javascript/profile.js +243 -3
- javascript/search.js +241 -3
javascript/cart.js
CHANGED
@@ -1,3 +1,187 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
async function updateCartIcon() {
|
2 |
+
const cart = await getCarts();
|
3 |
+
const count = cart.length;
|
4 |
+
const cartIcon = document.getElementById("cart-icon");
|
5 |
+
if (cartIcon) {
|
6 |
+
cartIcon.innerHTML = `
|
7 |
+
<i class="bi bi-cart"></i>
|
8 |
+
<span class="m-1">
|
9 |
+
<span class="nd-720">Keranjang</span>
|
10 |
+
<span class="nd-512">(${count})</span>
|
11 |
+
</span>`;
|
12 |
+
}
|
13 |
+
}
|
14 |
+
|
15 |
+
document.addEventListener("DOMContentLoaded", async function () {
|
16 |
+
setInterval(await updateCartIcon, 100);
|
17 |
+
});
|
18 |
+
|
19 |
+
async function promptQuantityAndAdd(productId) {
|
20 |
+
checkUID("addToCart");
|
21 |
+
let [qtyStr, isCancel] = await prompt(
|
22 |
+
"Masukkan jumlah produk yang ingin ditambahkan ke keranjang:",
|
23 |
+
"number"
|
24 |
+
);
|
25 |
+
|
26 |
+
console.log(qtyStr, isCancel);
|
27 |
+
|
28 |
+
if (isCancel) {
|
29 |
+
return;
|
30 |
+
}
|
31 |
+
|
32 |
+
if (!qtyStr) {
|
33 |
+
alert("Jumlah tidak boleh kosong.", false);
|
34 |
+
return;
|
35 |
+
}
|
36 |
+
|
37 |
+
let qty = parseInt(qtyStr);
|
38 |
+
|
39 |
+
if (isNaN(qty) || qty <= 0) {
|
40 |
+
alert("Masukkan jumlah produk yang valid.", false);
|
41 |
+
return;
|
42 |
+
}
|
43 |
+
|
44 |
+
await addToCart(productId, qty);
|
45 |
+
}
|
46 |
+
|
47 |
+
async function addToCart(productId, quantity = 1) {
|
48 |
+
checkUID("addToCart");
|
49 |
+
|
50 |
+
console.log(productId, quantity);
|
51 |
+
|
52 |
+
const cart = await getCarts();
|
53 |
+
|
54 |
+
let userData = await getUserData();
|
55 |
+
|
56 |
+
for (let i = 0; i < quantity; i++) {
|
57 |
+
cart.push(productId);
|
58 |
+
}
|
59 |
+
|
60 |
+
userData.cart = cart;
|
61 |
+
localStorage.setItem("cart", JSON.stringify(cart));
|
62 |
+
|
63 |
+
await updateUserDataToGitHub(userData);
|
64 |
+
await updateCartIcon();
|
65 |
+
|
66 |
+
alert(
|
67 |
+
`Produk telah ditambahkan ke keranjang sebanyak ${quantity} item.`,
|
68 |
+
false
|
69 |
+
);
|
70 |
+
}
|
71 |
+
|
72 |
+
function addToCartFromProduct(productId) {
|
73 |
+
checkUID("addToCart");
|
74 |
+
|
75 |
+
const qtyInput = document.getElementById("quantity");
|
76 |
+
let quantity = parseInt(qtyInput.value);
|
77 |
+
if (isNaN(quantity) || quantity <= 0) {
|
78 |
+
alert("Masukkan jumlah produk yang valid.", false);
|
79 |
+
return;
|
80 |
+
}
|
81 |
+
|
82 |
+
addToCart(productId, quantity);
|
83 |
+
}
|
84 |
+
|
85 |
+
async function loadCart() {
|
86 |
+
do {
|
87 |
+
const cart = await getCarts();
|
88 |
+
const params = new URLSearchParams(window.location.search);
|
89 |
+
const message = params.get("message") || "";
|
90 |
+
|
91 |
+
let cartCount = {};
|
92 |
+
cart.forEach((id) => {
|
93 |
+
cartCount[id] = (cartCount[id] || 0) + 1;
|
94 |
+
});
|
95 |
+
|
96 |
+
const cartItemsContainer = document.getElementById("cart-items");
|
97 |
+
cartItemsContainer.innerHTML = "";
|
98 |
+
let total = 0;
|
99 |
+
|
100 |
+
if (Object.keys(cartCount).length === 0) {
|
101 |
+
cartItemsContainer.innerHTML = "<p>Keranjang kosong.</p>";
|
102 |
+
} else {
|
103 |
+
cartItemsContainer.innerHTML = `<span class="text-center text-muted">${message}</span>`;
|
104 |
+
let index = 1;
|
105 |
+
for (let id in cartCount) {
|
106 |
+
const product = products.find((p) => p.id === parseInt(id));
|
107 |
+
if (product) {
|
108 |
+
const qty = cartCount[id];
|
109 |
+
const subtotal = product.price * qty;
|
110 |
+
total += subtotal;
|
111 |
+
cartItemsContainer.innerHTML += `
|
112 |
+
<div class="d-flex justify-content-between align-items-center border-bottom pb-2 mb-2">
|
113 |
+
<div>
|
114 |
+
<h5>${product.name}</h5>
|
115 |
+
<p class="mb-0">
|
116 |
+
${formatRupiah(product.price)} x
|
117 |
+
<input class="form-control form-control-lg" type="number" value="${qty}" min="1" style="width:60px;"
|
118 |
+
onchange="updateQuantity(${product.id}, this.value)">
|
119 |
+
</p>
|
120 |
+
</div>
|
121 |
+
<button class="btn btn-danger btn-lg" onclick="removeFromCart(${
|
122 |
+
product.id
|
123 |
+
})">Hapus</button>
|
124 |
+
</div>`;
|
125 |
+
index++;
|
126 |
+
}
|
127 |
+
}
|
128 |
+
}
|
129 |
+
const cartSummary = document.getElementById("cart-summary");
|
130 |
+
cartSummary.innerHTML = `<h4>Total: ${formatRupiah(total)}</h4>`;
|
131 |
+
|
132 |
+
if (cartItemsContainer.innerHTML.trim() == "") {
|
133 |
+
cartItemsContainer.innerHTML = "";
|
134 |
+
alert("Jika daftar keranjang tidak muncul, silahkan muat ulang halaman ini", false)
|
135 |
+
}
|
136 |
+
|
137 |
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
138 |
+
} while (cartItemsContainer.innerHTML.trim() == "");
|
139 |
+
}
|
140 |
+
|
141 |
+
async function updateQuantity(productId, newQty) {
|
142 |
+
newQty = parseInt(newQty);
|
143 |
+
if (isNaN(newQty) || newQty <= 0) {
|
144 |
+
alert("Masukkan jumlah produk yang valid.", false);
|
145 |
+
return;
|
146 |
+
}
|
147 |
+
|
148 |
+
// Ambil keranjang yang ada
|
149 |
+
const cart = await getCarts();
|
150 |
+
// Hapus semua kemunculan produk dengan productId tersebut
|
151 |
+
const newCart = cart.filter((id) => id !== productId);
|
152 |
+
// Tambahkan produk sesuai newQty
|
153 |
+
for (let i = 0; i < newQty; i++) {
|
154 |
+
newCart.push(productId);
|
155 |
+
}
|
156 |
+
localStorage.setItem("cart", JSON.stringify(newCart));
|
157 |
+
|
158 |
+
let userData = await getUserData();
|
159 |
+
userData.cart = newCart;
|
160 |
+
await updateUserDataToGitHub(userData);
|
161 |
+
|
162 |
+
await loadCart();
|
163 |
+
await updateCartIcon();
|
164 |
+
}
|
165 |
+
|
166 |
+
async function removeFromCart(productId) {
|
167 |
+
const cart = await getCarts();
|
168 |
+
const index = cart.indexOf(productId);
|
169 |
+
if (index !== -1) {
|
170 |
+
cart.splice(index, 1);
|
171 |
+
localStorage.setItem("cart", JSON.stringify(cart));
|
172 |
+
|
173 |
+
let userData = await getUserData();
|
174 |
+
userData.cart = cart;
|
175 |
+
|
176 |
+
await updateUserDataToGitHub(userData);
|
177 |
+
await loadCart();
|
178 |
+
await updateCartIcon();
|
179 |
+
}
|
180 |
+
}
|
181 |
+
|
182 |
+
document.addEventListener("DOMContentLoaded", function () {
|
183 |
+
AOS.init();
|
184 |
+
if (document.getElementById("cart-items")) {
|
185 |
+
loadCart();
|
186 |
+
}
|
187 |
+
});
|
javascript/component.js
CHANGED
@@ -1,3 +1,298 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const urlNow = window.location.href;
|
2 |
+
const header = document.querySelector("header");
|
3 |
+
|
4 |
+
header.className = "shadow-sm py-3 fixed-top";
|
5 |
+
header.innerHTML = `
|
6 |
+
<div class="container d-flex flex-column align-items-center mt-2">
|
7 |
+
<form id="searchForm" class="d-flex align-items-center w-75" style="max-width: 600px;">
|
8 |
+
<input id="searchInput" class="form-control form-control-lg" type="search" placeholder="${
|
9 |
+
urlNow.includes("transaction")
|
10 |
+
? "Cari transaksi..."
|
11 |
+
: "Cari produk..."
|
12 |
+
}" aria-label="Search">
|
13 |
+
<label for="searchInput" class="m-1" id="labelSearchInput">
|
14 |
+
<i class="bi bi-search me-2 text-secondary btn btn-light btn-lg"></i>
|
15 |
+
</label>
|
16 |
+
</form>
|
17 |
+
</div>
|
18 |
+
`;
|
19 |
+
|
20 |
+
function searchForm() {
|
21 |
+
const searchInputElm = document.getElementById("searchInput");
|
22 |
+
const value = searchInputElm.value.trim();
|
23 |
+
let nextSearch = false;
|
24 |
+
value ? (nextSearch = true) : searchInputElm.focus();
|
25 |
+
|
26 |
+
if (nextSearch)
|
27 |
+
window.location.href =
|
28 |
+
"search.html?" +
|
29 |
+
(urlNow.includes("transaction") ? "transactions=" : "products=") +
|
30 |
+
value;
|
31 |
+
}
|
32 |
+
|
33 |
+
document
|
34 |
+
.getElementById("labelSearchInput")
|
35 |
+
.addEventListener("click", () => searchForm());
|
36 |
+
|
37 |
+
document.getElementById("searchForm").addEventListener("submit", (e) => {
|
38 |
+
e.preventDefault();
|
39 |
+
searchForm();
|
40 |
+
});
|
41 |
+
|
42 |
+
const footer = document.querySelector("footer");
|
43 |
+
|
44 |
+
footer.className = "py-3 fixed-bottom shadow-sm";
|
45 |
+
footer.innerHTML = `
|
46 |
+
<div class="container d-flex justify-content-around">
|
47 |
+
<a href="index.html" class="btn btn-warning btn-lg d-flex flex-column align-items-center">
|
48 |
+
<i class="bi bi-bag"></i>
|
49 |
+
<span class="m-1">
|
50 |
+
<span class="nd-720">Cari</span>
|
51 |
+
<span class="nd-512">Produk</span>
|
52 |
+
</span>
|
53 |
+
</a>
|
54 |
+
<a href="transactions.html" class="btn btn-warning btn-lg d-flex flex-column align-items-center">
|
55 |
+
<i class="bi bi-receipt"></i>
|
56 |
+
<span class="m-1">
|
57 |
+
<span class="nd-720">Cek</span>
|
58 |
+
<span class="nd-512">Transaksi</span>
|
59 |
+
</span>
|
60 |
+
</a>
|
61 |
+
<a id="cart-icon" href="cart.html" class="btn btn-primary btn-lg d-flex align-items-center">
|
62 |
+
<i class="bi bi-cart"></i>
|
63 |
+
<span class="m-1">
|
64 |
+
<span class="nd-720">Keranjang</span>
|
65 |
+
<span class="nd-512">(0)</span>
|
66 |
+
</span>
|
67 |
+
</a>
|
68 |
+
<a href="profile.html" class="btn btn-secondary btn-lg d-flex flex-column align-items-center">
|
69 |
+
<i class="bi bi-person-circle"></i>
|
70 |
+
<span class="nd-512">Profil</span>
|
71 |
+
</a>
|
72 |
+
</div>
|
73 |
+
`;
|
74 |
+
|
75 |
+
const backAreaButton = document.getElementById("backAreaButton");
|
76 |
+
if (backAreaButton) {
|
77 |
+
backAreaButton.innerHTML = `
|
78 |
+
<div class="btn btn-primary btn-lg mb-4" id="backButton">
|
79 |
+
<i class="bi bi-arrow-left"></i> <span class="nd-512">Kembali</span>
|
80 |
+
</div>
|
81 |
+
<br><br>
|
82 |
+
`;
|
83 |
+
}
|
84 |
+
|
85 |
+
async function alert(
|
86 |
+
message = "",
|
87 |
+
isPopUp = true,
|
88 |
+
isTime = false,
|
89 |
+
timeOut = 5,
|
90 |
+
messagesAfterTimeOut = [],
|
91 |
+
awaitResolve = 0
|
92 |
+
) {
|
93 |
+
return new Promise((resolve) => {
|
94 |
+
try {
|
95 |
+
let modal;
|
96 |
+
let messageElem;
|
97 |
+
let timeOutElm;
|
98 |
+
let okBtn;
|
99 |
+
let intervalId;
|
100 |
+
|
101 |
+
if (isPopUp) {
|
102 |
+
modal = document.createElement("div");
|
103 |
+
modal.className = "modal";
|
104 |
+
modal.style.position = "fixed";
|
105 |
+
modal.style.top = "0";
|
106 |
+
modal.style.left = "0";
|
107 |
+
modal.style.width = "100%";
|
108 |
+
modal.style.height = "100%";
|
109 |
+
modal.style.backgroundColor = "rgba(0,0,0,0.5)";
|
110 |
+
modal.style.display = "flex";
|
111 |
+
modal.style.alignItems = "center";
|
112 |
+
modal.style.justifyContent = "center";
|
113 |
+
} else {
|
114 |
+
modal = document.createElement("div");
|
115 |
+
modal.className = "notification";
|
116 |
+
modal.style.position = "fixed";
|
117 |
+
modal.style.top = "5%";
|
118 |
+
modal.style.right = "2.5%";
|
119 |
+
modal.style.padding = "10px 20px";
|
120 |
+
modal.style.borderRadius = "5px";
|
121 |
+
modal.style.zIndex = "1000000000";
|
122 |
+
modal.setAttribute("data-aos", "fade-left");
|
123 |
+
}
|
124 |
+
|
125 |
+
const modalContent = document.createElement("div");
|
126 |
+
modalContent.className =
|
127 |
+
"modal-content d-flex align-items-center bg-warning p-3";
|
128 |
+
modalContent.style.width = isPopUp ? "75%" : "100%";
|
129 |
+
modalContent.style.height = isPopUp ? "" : "100%";
|
130 |
+
modalContent.style.borderRadius = "5px";
|
131 |
+
modalContent.style.boxShadow = isPopUp
|
132 |
+
? "0 2px 10px rgba(0,0,0,0.1)"
|
133 |
+
: "";
|
134 |
+
|
135 |
+
messageElem = document.createElement("p");
|
136 |
+
messageElem.innerHTML =
|
137 |
+
(!isPopUp ? `<i class="bi bi-bell-fill m-2"></i>` : ``) +
|
138 |
+
message;
|
139 |
+
|
140 |
+
timeOutElm = document.createElement("p");
|
141 |
+
|
142 |
+
okBtn = document.createElement("button");
|
143 |
+
okBtn.className = "btn btn-primary btn-lg";
|
144 |
+
okBtn.textContent = "OK";
|
145 |
+
okBtn.style.minWidth = "80px";
|
146 |
+
okBtn.style.display = isTime ? "none" : "block";
|
147 |
+
okBtn.style.marginTop = "10px";
|
148 |
+
|
149 |
+
modalContent.appendChild(messageElem);
|
150 |
+
|
151 |
+
if (isTime) {
|
152 |
+
modalContent.appendChild(timeOutElm);
|
153 |
+
}
|
154 |
+
|
155 |
+
if (isPopUp) {
|
156 |
+
const btnContainer = document.createElement("div");
|
157 |
+
btnContainer.style.marginTop = "20px";
|
158 |
+
btnContainer.appendChild(okBtn);
|
159 |
+
modalContent.appendChild(btnContainer);
|
160 |
+
}
|
161 |
+
|
162 |
+
modal.appendChild(modalContent);
|
163 |
+
document.body.appendChild(modal);
|
164 |
+
|
165 |
+
intervalId = setInterval(async () => {
|
166 |
+
timeOut--;
|
167 |
+
console.log(timeOut);
|
168 |
+
timeOutElm.textContent = timeOut;
|
169 |
+
if (messagesAfterTimeOut.length > 0) {
|
170 |
+
messagesAfterTimeOut.forEach((mes) => {
|
171 |
+
if (mes.timeOut >= timeOut) {
|
172 |
+
modalContent.className =
|
173 |
+
"modal-content d-flex align-items-center bg-warning p-3";
|
174 |
+
messageElem.textContent = mes.message;
|
175 |
+
}
|
176 |
+
});
|
177 |
+
}
|
178 |
+
|
179 |
+
if (timeOut <= 0) {
|
180 |
+
await new Promise((res) => setTimeout(res, awaitResolve));
|
181 |
+
if (document.body.contains(modal)) {
|
182 |
+
document.body.removeChild(modal);
|
183 |
+
}
|
184 |
+
clearInterval(intervalId);
|
185 |
+
resolve();
|
186 |
+
}
|
187 |
+
}, 1000);
|
188 |
+
|
189 |
+
okBtn.addEventListener("click", function () {
|
190 |
+
if (intervalId) clearInterval(intervalId);
|
191 |
+
if (document.body.contains(modal)) {
|
192 |
+
document.body.removeChild(modal);
|
193 |
+
}
|
194 |
+
resolve();
|
195 |
+
});
|
196 |
+
|
197 |
+
if (!isPopUp) {
|
198 |
+
setTimeout(() => {
|
199 |
+
if (document.body.contains(modal)) {
|
200 |
+
document.body.removeChild(modal);
|
201 |
+
}
|
202 |
+
resolve();
|
203 |
+
}, timeOut * 1000);
|
204 |
+
}
|
205 |
+
} catch (e) {
|
206 |
+
console.error(e);
|
207 |
+
}
|
208 |
+
});
|
209 |
+
}
|
210 |
+
|
211 |
+
async function prompt(message = "", type = "text", placeholder = "") {
|
212 |
+
return new Promise((resolve) => {
|
213 |
+
const modal = document.createElement("div");
|
214 |
+
modal.className = "modal";
|
215 |
+
modal.style.position = "fixed";
|
216 |
+
modal.style.top = 0;
|
217 |
+
modal.style.left = 0;
|
218 |
+
modal.style.width = "100%";
|
219 |
+
modal.style.height = "100%";
|
220 |
+
modal.style.backgroundColor = "rgba(0,0,0,0.5)";
|
221 |
+
modal.style.display = "flex";
|
222 |
+
modal.style.alignItems = "center";
|
223 |
+
modal.style.justifyContent = "center";
|
224 |
+
|
225 |
+
const modalContent = document.createElement("div");
|
226 |
+
modalContent.className = "modal-content";
|
227 |
+
modalContent.style.width = "75%";
|
228 |
+
modalContent.style.background = "#fff";
|
229 |
+
modalContent.style.padding = "20px";
|
230 |
+
modalContent.style.borderRadius = "5px";
|
231 |
+
modalContent.style.boxShadow = "0 2px 10px rgba(0,0,0,0.1)";
|
232 |
+
|
233 |
+
const messageElem = document.createElement("p");
|
234 |
+
messageElem.textContent = message;
|
235 |
+
|
236 |
+
const input = document.createElement("input");
|
237 |
+
input.type = type;
|
238 |
+
input.required = "true";
|
239 |
+
input.style.width = "100%";
|
240 |
+
input.style.marginTop = "10px";
|
241 |
+
input.placeholder = placeholder;
|
242 |
+
input.className = "form-control form-control-lg";
|
243 |
+
|
244 |
+
const btnContainer = document.createElement("div");
|
245 |
+
btnContainer.style.marginTop = "20px";
|
246 |
+
btnContainer.style.textAlign = "right";
|
247 |
+
|
248 |
+
const okBtn = document.createElement("button");
|
249 |
+
okBtn.className = "btn btn-success btn-lg";
|
250 |
+
okBtn.textContent = "OK";
|
251 |
+
okBtn.style.marginRight = "10px";
|
252 |
+
|
253 |
+
const cancelBtn = document.createElement("button");
|
254 |
+
cancelBtn.className = "btn btn-danger btn-lg";
|
255 |
+
cancelBtn.textContent = "Batal";
|
256 |
+
|
257 |
+
btnContainer.appendChild(okBtn);
|
258 |
+
btnContainer.appendChild(cancelBtn);
|
259 |
+
|
260 |
+
modalContent.appendChild(messageElem);
|
261 |
+
modalContent.appendChild(input);
|
262 |
+
modalContent.appendChild(btnContainer);
|
263 |
+
modal.appendChild(modalContent);
|
264 |
+
document.body.appendChild(modal);
|
265 |
+
|
266 |
+
input.addEventListener("click", () => {
|
267 |
+
input.removeAttribute("focus");
|
268 |
+
input.removeAttribute("invalid");
|
269 |
+
input.removeAttribute("style");
|
270 |
+
});
|
271 |
+
|
272 |
+
okBtn.addEventListener("click", function () {
|
273 |
+
const value = input.value;
|
274 |
+
if (input.value.trim().length > 0) {
|
275 |
+
document.body.removeChild(modal);
|
276 |
+
resolve([value, false]);
|
277 |
+
} else {
|
278 |
+
input.setAttribute("focus", true);
|
279 |
+
input.setAttribute("invalid", true);
|
280 |
+
input.style.border = "1px solid red";
|
281 |
+
input.focus();
|
282 |
+
}
|
283 |
+
});
|
284 |
+
|
285 |
+
cancelBtn.addEventListener("click", function () {
|
286 |
+
document.body.removeChild(modal);
|
287 |
+
resolve([null, true]);
|
288 |
+
});
|
289 |
+
});
|
290 |
+
}
|
291 |
+
|
292 |
+
document.querySelectorAll("main").forEach((elm) => {
|
293 |
+
elm.setAttribute(
|
294 |
+
"class",
|
295 |
+
"hf row mt-4 d-flex flex-column align-items-center"
|
296 |
+
);
|
297 |
+
elm.setAttribute("data-aos", "fade-down");
|
298 |
+
});
|
javascript/data.js
CHANGED
@@ -1,3 +1,204 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
var products = [];
|
2 |
+
var fileData = "";
|
3 |
+
|
4 |
+
const GITHUB_TOKEN = atob(
|
5 |
+
"Z2l0aHViX3BhdF8xMUFYNEtVTlkwY05lbWNpZHVrWDYxXzczTzV2bTh2Wk5uMzlpQm0wb1BXRmd5dUx2a0VKSGluZ0dZeURPV0JFWTJXMk9YTFFINVRLT2xpU2ts"
|
6 |
+
);
|
7 |
+
|
8 |
+
console.log(
|
9 |
+
GITHUB_TOKEN ===
|
10 |
+
"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"
|
11 |
+
);
|
12 |
+
|
13 |
+
const GITHUB_OWNER = "Adityadn64";
|
14 |
+
const GITHUB_REPO = "FINAL-PROJECT---ACQ20RC";
|
15 |
+
const USERS_FILE_PATH = "database/users.json";
|
16 |
+
const PRODUCTS_FILE_PATH = "database/products.json";
|
17 |
+
|
18 |
+
async function getUsers() {
|
19 |
+
const res = await fetch(
|
20 |
+
`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${USERS_FILE_PATH}`,
|
21 |
+
{
|
22 |
+
headers: {
|
23 |
+
Accept: "application/vnd.github+json",
|
24 |
+
Authorization: `Bearer ${GITHUB_TOKEN}`,
|
25 |
+
},
|
26 |
+
}
|
27 |
+
);
|
28 |
+
|
29 |
+
fileData = await res.json();
|
30 |
+
let currentUsers = {};
|
31 |
+
try {
|
32 |
+
currentUsers = JSON.parse(atob(fileData.content));
|
33 |
+
} catch (e) {
|
34 |
+
console.error("Error parsing current users; using empty object", e);
|
35 |
+
}
|
36 |
+
return currentUsers;
|
37 |
+
}
|
38 |
+
|
39 |
+
async function getUserData() {
|
40 |
+
const storedDataStr = localStorage.getItem("userData");
|
41 |
+
if (!storedDataStr) return null;
|
42 |
+
const storedData = JSON.parse(storedDataStr);
|
43 |
+
|
44 |
+
const usersDB = await getUsers();
|
45 |
+
let userData =
|
46 |
+
storedData.uid && usersDB && usersDB[storedData.uid]
|
47 |
+
? usersDB[storedData.uid]
|
48 |
+
: storedData;
|
49 |
+
// Ensure uid is set
|
50 |
+
if (storedData.uid) {
|
51 |
+
userData.uid = storedData.uid;
|
52 |
+
}
|
53 |
+
return userData;
|
54 |
+
}
|
55 |
+
|
56 |
+
async function getCarts() {
|
57 |
+
const userData = await getUserData();
|
58 |
+
let localCart = [];
|
59 |
+
const cartStr = localStorage.getItem("cart");
|
60 |
+
if (cartStr) {
|
61 |
+
try {
|
62 |
+
localCart = JSON.parse(cartStr);
|
63 |
+
if (!Array.isArray(localCart)) {
|
64 |
+
localCart = [];
|
65 |
+
}
|
66 |
+
} catch (e) {
|
67 |
+
console.error(
|
68 |
+
"Error parsing local cart data; using empty array",
|
69 |
+
e
|
70 |
+
);
|
71 |
+
localCart = [];
|
72 |
+
}
|
73 |
+
}
|
74 |
+
|
75 |
+
let cartData;
|
76 |
+
if (userData && userData.cart && Array.isArray(userData.cart)) {
|
77 |
+
cartData = userData.cart;
|
78 |
+
} else {
|
79 |
+
cartData = localCart;
|
80 |
+
}
|
81 |
+
|
82 |
+
return cartData;
|
83 |
+
}
|
84 |
+
|
85 |
+
|
86 |
+
function convertImageToBase64(imageUrl) {
|
87 |
+
return new Promise((resolve, reject) => {
|
88 |
+
fetch(imageUrl)
|
89 |
+
.then(res => res.blob())
|
90 |
+
.then(blob => {
|
91 |
+
const reader = new FileReader();
|
92 |
+
reader.readAsDataURL(blob);
|
93 |
+
reader.onloadend = () => resolve(reader.result);
|
94 |
+
reader.onerror = error => reject(error);
|
95 |
+
})
|
96 |
+
.catch(err => reject(err));
|
97 |
+
});
|
98 |
+
}
|
99 |
+
|
100 |
+
async function uploadFileToGitHub(filePath, base64Data) {
|
101 |
+
try {
|
102 |
+
const fileRes = await fetch(
|
103 |
+
`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${filePath}`,
|
104 |
+
{
|
105 |
+
method: "PUT",
|
106 |
+
headers: {
|
107 |
+
Accept: "application/vnd.github+json",
|
108 |
+
Authorization: `Bearer ${GITHUB_TOKEN}`,
|
109 |
+
},
|
110 |
+
body: JSON.stringify({
|
111 |
+
message: "Upload user photo",
|
112 |
+
content: base64Data.split(",")[1], // Hanya ambil data Base64 tanpa prefix
|
113 |
+
}),
|
114 |
+
}
|
115 |
+
);
|
116 |
+
|
117 |
+
const result = await fileRes.json();
|
118 |
+
return result;
|
119 |
+
} catch (err) {
|
120 |
+
console.error("Error uploading file to GitHub:", err);
|
121 |
+
return null;
|
122 |
+
}
|
123 |
+
}
|
124 |
+
|
125 |
+
async function updateUserDataToGitHub(userData) {
|
126 |
+
try {
|
127 |
+
let currentUsers = await getUsers();
|
128 |
+
const isNotValidUID = !userData.uid || userData.uid === undefined;
|
129 |
+
|
130 |
+
if (isNotValidUID) {
|
131 |
+
localStorage.clear();
|
132 |
+
window.location.href =
|
133 |
+
`/profile.html?redirect=${urlNow}` +
|
134 |
+
(message ? `&message=${message}` : "");
|
135 |
+
}
|
136 |
+
|
137 |
+
if (!userData.photo_profile.includes("github")) {
|
138 |
+
const photoFilePath = `assets/users/photo_profile/${userData.uid}.jpg`;
|
139 |
+
let photoBase64 = userData.photo_profile;
|
140 |
+
|
141 |
+
if (!photoBase64.startsWith("data:image/")) {
|
142 |
+
photoBase64 = await convertImageToBase64(userData.photo_profile);
|
143 |
+
}
|
144 |
+
|
145 |
+
const photoUploadRes = await uploadFileToGitHub(photoFilePath, photoBase64);
|
146 |
+
if (photoUploadRes) {
|
147 |
+
userData.photo_profile = `https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/main/${photoFilePath}`;
|
148 |
+
}
|
149 |
+
}
|
150 |
+
|
151 |
+
currentUsers[userData.uid] = {
|
152 |
+
name: userData.name ? userData.name : "",
|
153 |
+
email: userData.email ? userData.email : "",
|
154 |
+
phone: userData.phone ? userData.phone : "",
|
155 |
+
address: userData.address ? userData.address : "",
|
156 |
+
photo_profile: userData.photo_profile ? userData.photo_profile : "",
|
157 |
+
cart: userData.cart ? userData.cart : [],
|
158 |
+
transactions: userData.transactions ? userData.transactions : {},
|
159 |
+
};
|
160 |
+
|
161 |
+
const newContent = btoa(JSON.stringify(currentUsers, null, 4));
|
162 |
+
const updateRes = await fetch(
|
163 |
+
`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${USERS_FILE_PATH}`,
|
164 |
+
{
|
165 |
+
method: "PUT",
|
166 |
+
headers: {
|
167 |
+
Accept: "application/vnd.github+json",
|
168 |
+
Authorization: `Bearer ${GITHUB_TOKEN}`,
|
169 |
+
},
|
170 |
+
body: JSON.stringify({
|
171 |
+
message: "Update user data via login Google",
|
172 |
+
content: newContent,
|
173 |
+
sha: fileData.sha,
|
174 |
+
}),
|
175 |
+
}
|
176 |
+
);
|
177 |
+
|
178 |
+
const result = await updateRes.json();
|
179 |
+
console.log("Update user data result:", result, currentUsers);
|
180 |
+
} catch (err) {
|
181 |
+
console.error("Error updating user data to GitHub:", err);
|
182 |
+
}
|
183 |
+
}
|
184 |
+
|
185 |
+
async function fetchProductsData() {
|
186 |
+
const response = await fetch(
|
187 |
+
`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${PRODUCTS_FILE_PATH}`,
|
188 |
+
{
|
189 |
+
headers: {
|
190 |
+
Accept: "application/vnd.github.v3+json",
|
191 |
+
Authorization: `Bearer ${GITHUB_TOKEN}`,
|
192 |
+
},
|
193 |
+
}
|
194 |
+
);
|
195 |
+
const data = await response.json();
|
196 |
+
const fileContent = atob(data.content);
|
197 |
+
products = JSON.parse(fileContent);
|
198 |
+
return products;
|
199 |
+
}
|
200 |
+
|
201 |
+
document.addEventListener("DOMContentLoaded", async function () {
|
202 |
+
AOS.init();
|
203 |
+
products = await fetchProductsData();
|
204 |
+
});
|
javascript/main.js
CHANGED
@@ -1,3 +1,505 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
function checkUID(m = "") {
|
2 |
+
const userData = JSON.parse(localStorage.getItem("userData")) || {};
|
3 |
+
const urlNow = window.location.href;
|
4 |
+
const isNotValidUID = userData.length > 0 && !userData.uid;
|
5 |
+
|
6 |
+
const message =
|
7 |
+
m === "addToCart"
|
8 |
+
? "Silakan masuk terlebih dahulu untuk menambahkan produk ke keranjang."
|
9 |
+
: isNotValidUID
|
10 |
+
? "Sesi Anda telah habis. Silakan masuk kembali untuk melanjutkan."
|
11 |
+
: m === "jelajah"
|
12 |
+
? "Anda telah menjelajah produk selama 5 detik. Silakan masuk terlebih dahulu untuk mendapatkan pengalaman yang lebih baik."
|
13 |
+
: null;
|
14 |
+
|
15 |
+
if (
|
16 |
+
(Object.keys(userData).length === 0 || isNotValidUID) &&
|
17 |
+
!urlNow.includes("profile")
|
18 |
+
) {
|
19 |
+
localStorage.clear();
|
20 |
+
window.location.href =
|
21 |
+
`profile.html?redirect=${urlNow}` +
|
22 |
+
(message ? `&message=${message}` : "");
|
23 |
+
}
|
24 |
+
}
|
25 |
+
|
26 |
+
setTimeout(() => {
|
27 |
+
checkUID("jelajah");
|
28 |
+
}, 5000);
|
29 |
+
|
30 |
+
function formatRupiah(number) {
|
31 |
+
return "Rp " + number.toLocaleString("id-ID");
|
32 |
+
}
|
33 |
+
|
34 |
+
async function loadProducts() {
|
35 |
+
const products = await fetchProductsData();
|
36 |
+
|
37 |
+
const productList = document.getElementById("product-list");
|
38 |
+
if (!productList) return;
|
39 |
+
productList.innerHTML = "";
|
40 |
+
products.forEach((product) => {
|
41 |
+
const col = document.createElement("div");
|
42 |
+
col.className = "col-lg-3 col-md-4 col-sm-6";
|
43 |
+
|
44 |
+
const card = document.createElement("div");
|
45 |
+
card.className = "card h-100";
|
46 |
+
|
47 |
+
let thumbnail = "assets/";
|
48 |
+
if (product.files && product.files.length > 0) {
|
49 |
+
thumbnail += product.files[0];
|
50 |
+
}
|
51 |
+
let thumbHTML = "";
|
52 |
+
if (thumbnail.match(/\.(jpg|jpeg|png|gif)$/i)) {
|
53 |
+
thumbHTML = `<img src="${thumbnail}" class="card-img-top" alt="${product.name}">`;
|
54 |
+
} else {
|
55 |
+
thumbHTML = `<img src="img/placeholder.jpg" class="card-img-top" alt="${product.name}">`;
|
56 |
+
}
|
57 |
+
|
58 |
+
const cardBody = document.createElement("div");
|
59 |
+
cardBody.className = "card-body d-flex flex-column";
|
60 |
+
|
61 |
+
const title = document.createElement("h5");
|
62 |
+
title.className = "card-title";
|
63 |
+
title.innerText = product.name;
|
64 |
+
|
65 |
+
const desc = document.createElement("p");
|
66 |
+
desc.className = "card-text";
|
67 |
+
desc.innerText = product.description;
|
68 |
+
|
69 |
+
const price = document.createElement("p");
|
70 |
+
price.className = "card-text fw-bold";
|
71 |
+
price.innerText = formatRupiah(product.price);
|
72 |
+
|
73 |
+
const detailLink = document.createElement("a");
|
74 |
+
detailLink.href = "product.html?id=" + product.id;
|
75 |
+
detailLink.className = "btn btn-primary btn-lg mt-auto";
|
76 |
+
detailLink.innerText = "Lihat Detail";
|
77 |
+
|
78 |
+
const cartBtn = document.createElement("button");
|
79 |
+
cartBtn.className = "btn btn-success btn-lg mt-2";
|
80 |
+
cartBtn.innerText = "Tambah Keranjang";
|
81 |
+
cartBtn.onclick = async function () {
|
82 |
+
await promptQuantityAndAdd(product.id);
|
83 |
+
};
|
84 |
+
|
85 |
+
card.innerHTML = thumbHTML;
|
86 |
+
cardBody.appendChild(title);
|
87 |
+
cardBody.appendChild(desc);
|
88 |
+
cardBody.appendChild(price);
|
89 |
+
cardBody.appendChild(detailLink);
|
90 |
+
cardBody.appendChild(cartBtn);
|
91 |
+
card.appendChild(cardBody);
|
92 |
+
col.appendChild(card);
|
93 |
+
productList.appendChild(col);
|
94 |
+
});
|
95 |
+
}
|
96 |
+
|
97 |
+
async function loadProductDetail() {
|
98 |
+
const params = new URLSearchParams(window.location.search);
|
99 |
+
const productId = parseInt(params.get("id"));
|
100 |
+
const container = document.getElementById("product-detail");
|
101 |
+
if (!container) return;
|
102 |
+
|
103 |
+
if (!productId) {
|
104 |
+
container.innerText = "Produk tidak ditemukan.";
|
105 |
+
return;
|
106 |
+
}
|
107 |
+
|
108 |
+
const products = await fetchProductsData();
|
109 |
+
const product = products.find((p) => p.id === productId);
|
110 |
+
if (!product) {
|
111 |
+
container.innerText = "Produk tidak ditemukan.";
|
112 |
+
return;
|
113 |
+
}
|
114 |
+
let carouselHTML = "";
|
115 |
+
if (product.files && product.files.length > 0) {
|
116 |
+
carouselHTML += `<div id="carouselProduct" class="carousel slide" data-bs-ride="carousel">`;
|
117 |
+
carouselHTML += `<div class="carousel-indicators">`;
|
118 |
+
product.files.forEach((file, index) => {
|
119 |
+
carouselHTML += `<button type="button" data-bs-target="#carouselProduct" data-bs-slide-to="${index}" ${
|
120 |
+
index === 0 ? 'class="active" aria-current="true"' : ""
|
121 |
+
} aria-label="Slide ${index + 1}"></button>`;
|
122 |
+
});
|
123 |
+
carouselHTML += `</div>`;
|
124 |
+
carouselHTML += `<div class="carousel-inner">`;
|
125 |
+
product.files.forEach((file, index) => {
|
126 |
+
file = "assets/" + file;
|
127 |
+
carouselHTML += `<div class="carousel-item ${
|
128 |
+
index === 0 ? "active" : ""
|
129 |
+
}">`;
|
130 |
+
if (file.match(/\.(jpg|jpeg|png|gif)$/i)) {
|
131 |
+
carouselHTML += `<img src="${file}" class="d-block w-100" alt="${product.name}">`;
|
132 |
+
} else if (file.match(/\.(mp4|webm)$/i)) {
|
133 |
+
carouselHTML += `<video class="d-block w-100" controls>
|
134 |
+
<source src="${file}" type="video/mp4">
|
135 |
+
Browser Anda tidak mendukung video.
|
136 |
+
</video>`;
|
137 |
+
}
|
138 |
+
carouselHTML += `</div>`;
|
139 |
+
});
|
140 |
+
carouselHTML += `</div>`;
|
141 |
+
carouselHTML += `<button class="carousel-control-prev" type="button" data-bs-target="#carouselProduct" data-bs-slide="prev">
|
142 |
+
<span class="carousel-control-prev-icon bg-primary rounded-circle p-4" aria-hidden="true"></span>
|
143 |
+
<span class="visually-hidden">Previous</span>
|
144 |
+
</button>
|
145 |
+
<button class="carousel-control-next" type="button" data-bs-target="#carouselProduct" data-bs-slide="next">
|
146 |
+
<span class="carousel-control-next-icon bg-primary rounded-circle p-4" aria-hidden="true"></span>
|
147 |
+
<span class="visually-hidden">Next</span>
|
148 |
+
</button>`;
|
149 |
+
carouselHTML += `</div>`;
|
150 |
+
}
|
151 |
+
container.innerHTML = `
|
152 |
+
<div class="row">
|
153 |
+
<div class="col-md-6">
|
154 |
+
${carouselHTML}
|
155 |
+
</div>
|
156 |
+
<div class="col-md-6">
|
157 |
+
<h2>${product.name}</h2>
|
158 |
+
<p>${product.description}</p>
|
159 |
+
<h4 class="fw-bold">${formatRupiah(product.price)}</h4>
|
160 |
+
<div class="mb-2">
|
161 |
+
<label for="quantity" class="form-label">Jumlah:</label>
|
162 |
+
<input type="number" id="quantity" class="form-control form-control-lg" value="1" min="1" style="max-width:150px;">
|
163 |
+
</div>
|
164 |
+
<button class="btn btn-success btn-lg" onclick="addToCartFromProduct(${
|
165 |
+
product.id
|
166 |
+
})">
|
167 |
+
Tambahkan ke Keranjang
|
168 |
+
</button>
|
169 |
+
</div>
|
170 |
+
</div>
|
171 |
+
`;
|
172 |
+
AOS.refresh();
|
173 |
+
}
|
174 |
+
|
175 |
+
function formatTransactionDate(key) {
|
176 |
+
const parts = key.split("_");
|
177 |
+
if (parts.length !== 7) return key;
|
178 |
+
|
179 |
+
const [year, month, day, hour, minute, second, ms] = parts;
|
180 |
+
|
181 |
+
const dateObj = new Date(
|
182 |
+
year,
|
183 |
+
parseInt(month) - 1,
|
184 |
+
day,
|
185 |
+
hour,
|
186 |
+
minute,
|
187 |
+
second,
|
188 |
+
ms
|
189 |
+
);
|
190 |
+
|
191 |
+
const formattedDate = dateObj.toLocaleDateString("id-ID", {
|
192 |
+
day: "2-digit",
|
193 |
+
month: "long",
|
194 |
+
year: "numeric",
|
195 |
+
});
|
196 |
+
|
197 |
+
const formattedTime = dateObj.toLocaleTimeString("id-ID", {
|
198 |
+
hour: "2-digit",
|
199 |
+
minute: "2-digit",
|
200 |
+
hour12: false,
|
201 |
+
});
|
202 |
+
|
203 |
+
return [formattedDate, formattedTime];
|
204 |
+
}
|
205 |
+
|
206 |
+
async function loadTransactions() {
|
207 |
+
const userData = await getUserData();
|
208 |
+
const transactions = userData.transactions ? userData.transactions : {};
|
209 |
+
|
210 |
+
if (!transactions || Object.keys(transactions).length === 0) {
|
211 |
+
document.getElementById("transaction-details").innerHTML =
|
212 |
+
"<p>Anda belum melakukan transaksi apapun. Silahkan beli produk terlebih dahulu.</p>";
|
213 |
+
alert("Anda belum melakukan transaksi apapun. Silahkan beli produk terlebih dahulu.", false)
|
214 |
+
return;
|
215 |
+
}
|
216 |
+
|
217 |
+
const transactionList = document.getElementById("transaction-list");
|
218 |
+
if (!transactionList) return;
|
219 |
+
transactionList.innerHTML = "";
|
220 |
+
|
221 |
+
Object.entries(transactions)
|
222 |
+
.sort((a, b) => {
|
223 |
+
const parseDateFromKey = (key) => {
|
224 |
+
const [year, month, day, hour, minute, second, ms] =
|
225 |
+
key.split("_");
|
226 |
+
return new Date(year, month - 1, day, hour, minute, second, ms);
|
227 |
+
};
|
228 |
+
|
229 |
+
return parseDateFromKey(b[0]) - parseDateFromKey(a[0]);
|
230 |
+
})
|
231 |
+
.forEach(([transactionKey, transaction]) => {
|
232 |
+
let total = 0;
|
233 |
+
|
234 |
+
transaction.products.forEach((product) => {
|
235 |
+
total += product.price * product.total;
|
236 |
+
});
|
237 |
+
|
238 |
+
let paymentIcon = "";
|
239 |
+
switch (transaction.payment_method) {
|
240 |
+
case "bank":
|
241 |
+
paymentIcon = "bi-building";
|
242 |
+
break;
|
243 |
+
case "qr":
|
244 |
+
paymentIcon = "bi-qr-code";
|
245 |
+
break;
|
246 |
+
case "card":
|
247 |
+
paymentIcon = "bi-credit-card";
|
248 |
+
break;
|
249 |
+
default:
|
250 |
+
paymentIcon = "bi-currency-dollar";
|
251 |
+
}
|
252 |
+
|
253 |
+
const col = document.createElement("div");
|
254 |
+
col.className =
|
255 |
+
"transactions-body col-lg-4 col-md-6 col-sm-12 mt-4";
|
256 |
+
|
257 |
+
const card = document.createElement("div");
|
258 |
+
card.className = "card h-100";
|
259 |
+
|
260 |
+
const cardBody = document.createElement("div");
|
261 |
+
cardBody.className = "card-body d-flex flex-column";
|
262 |
+
|
263 |
+
cardBody.style.position = "relative";
|
264 |
+
|
265 |
+
const td = formatTransactionDate(transactionKey);
|
266 |
+
|
267 |
+
cardBody.innerHTML = `
|
268 |
+
<div class="payment-icon-bg">
|
269 |
+
<i class="bi ${paymentIcon}" style="font-size: 2.5rem;"></i>
|
270 |
+
<span class="m-2">Metode: ${
|
271 |
+
transaction.payment_method ? transaction.payment_method : "N/A"
|
272 |
+
}</span>
|
273 |
+
</div>
|
274 |
+
<div class="content">
|
275 |
+
<div class="d-flex justify-content-between align-items-center">
|
276 |
+
<div class="transaction-total fw-bold">
|
277 |
+
Total: ${formatRupiah(total)}
|
278 |
+
</div>
|
279 |
+
</div>
|
280 |
+
<div class="d-flex justify-content-between align-items-center mt-auto">
|
281 |
+
<div class="transaction-date">
|
282 |
+
Tanggal: ${td[0]} <br> Waktu: ${td[1]}
|
283 |
+
</div>
|
284 |
+
</div>
|
285 |
+
</div>
|
286 |
+
`;
|
287 |
+
|
288 |
+
card.appendChild(cardBody);
|
289 |
+
col.appendChild(card);
|
290 |
+
transactionList.appendChild(col);
|
291 |
+
|
292 |
+
col.addEventListener(
|
293 |
+
"click",
|
294 |
+
() =>
|
295 |
+
(window.location.href = `transaction.html?date=${transactionKey}`)
|
296 |
+
);
|
297 |
+
});
|
298 |
+
}
|
299 |
+
|
300 |
+
let transactionKey;
|
301 |
+
|
302 |
+
async function updateActionButtonsPosition() {
|
303 |
+
const wrapper = document.querySelector(".receipt-wrapper");
|
304 |
+
const actionButtons = document.querySelector(".action-buttons");
|
305 |
+
if (!wrapper || !actionButtons) {
|
306 |
+
console.error("Element tidak ditemukan!");
|
307 |
+
return;
|
308 |
+
}
|
309 |
+
|
310 |
+
const receiptHeight = wrapper.getBoundingClientRect().height + 128;
|
311 |
+
actionButtons.setAttribute("style", `top: ${receiptHeight}px !important`);
|
312 |
+
}
|
313 |
+
|
314 |
+
async function loadTransactionDetail() {
|
315 |
+
const params = new URLSearchParams(window.location.search);
|
316 |
+
const transactionKey = params.get("date");
|
317 |
+
const detailsContainer = document.getElementById("transaction-details");
|
318 |
+
const mainElement = document.querySelector("main");
|
319 |
+
|
320 |
+
if (!transactionKey) {
|
321 |
+
detailsContainer.innerHTML = "<p>Transaksi tidak ditemukan.</p>";
|
322 |
+
return;
|
323 |
+
}
|
324 |
+
|
325 |
+
const userData = await getUserData();
|
326 |
+
|
327 |
+
if (!userData.transactions || !userData.transactions[transactionKey]) {
|
328 |
+
mainElement.innerHTML = "<p>Transaksi tidak ditemukan.</p>";
|
329 |
+
return;
|
330 |
+
}
|
331 |
+
|
332 |
+
const transaction = userData.transactions[transactionKey];
|
333 |
+
|
334 |
+
const td = formatTransactionDate(transactionKey);
|
335 |
+
|
336 |
+
let total = 0;
|
337 |
+
transaction.products.forEach((prod) => {
|
338 |
+
total += prod.price * prod.total;
|
339 |
+
});
|
340 |
+
|
341 |
+
// Buat HTML struk
|
342 |
+
let html = `
|
343 |
+
<div class="receipt-header">
|
344 |
+
<h2>TonS E-Commerce</h2>
|
345 |
+
<hr>
|
346 |
+
<p>Sidoarjo</p>
|
347 |
+
<p>Telp: 0896-6804-1554</p>
|
348 |
+
<p>Tanggal: ${td[0]} ${td[1]}</p>
|
349 |
+
</div>
|
350 |
+
<table class="receipt-table">
|
351 |
+
<thead>
|
352 |
+
<tr>
|
353 |
+
<th>No</th>
|
354 |
+
<th>Produk</th>
|
355 |
+
<th>Harga</th>
|
356 |
+
<th>Qty</th>
|
357 |
+
<th>Subtotal</th>
|
358 |
+
</tr>
|
359 |
+
</thead>
|
360 |
+
<tbody>
|
361 |
+
`;
|
362 |
+
|
363 |
+
transaction.products.forEach((prod, index) => {
|
364 |
+
const qty = prod.total;
|
365 |
+
const subtotal = prod.price * qty;
|
366 |
+
html += `
|
367 |
+
<tr>
|
368 |
+
<td>${index + 1}</td>
|
369 |
+
<td>${prod.name}</td>
|
370 |
+
<td>${formatRupiah(prod.price)}</td>
|
371 |
+
<td>${qty}</td>
|
372 |
+
<td>${formatRupiah(subtotal)}</td>
|
373 |
+
</tr>
|
374 |
+
`;
|
375 |
+
});
|
376 |
+
|
377 |
+
html += `
|
378 |
+
<tr class="total-row">
|
379 |
+
<td colspan="4" class="text-end"><strong>Total</strong></td>
|
380 |
+
<td><strong>${formatRupiah(total)}</strong></td>
|
381 |
+
</tr>
|
382 |
+
</tbody>
|
383 |
+
</table>
|
384 |
+
<div class="receipt-footer">
|
385 |
+
<p>Terima kasih telah berbelanja di TonS</p>
|
386 |
+
<p>Harap simpan struk ini sebagai bukti pembelian</p>
|
387 |
+
</div>
|
388 |
+
`;
|
389 |
+
|
390 |
+
detailsContainer.innerHTML = html;
|
391 |
+
|
392 |
+
const wrapper = document.createElement("div");
|
393 |
+
wrapper.className = "receipt-wrapper";
|
394 |
+
wrapper.innerHTML = detailsContainer.innerHTML;
|
395 |
+
detailsContainer.innerHTML = "";
|
396 |
+
detailsContainer.appendChild(wrapper);
|
397 |
+
|
398 |
+
await updateActionButtonsPosition();
|
399 |
+
|
400 |
+
window.addEventListener("resize", async () => {
|
401 |
+
requestAnimationFrame(updateActionButtonsPosition);
|
402 |
+
});
|
403 |
+
}
|
404 |
+
|
405 |
+
document.addEventListener("DOMContentLoaded", async function () {
|
406 |
+
AOS.init();
|
407 |
+
|
408 |
+
if (document.getElementById("product-list")) {
|
409 |
+
loadProducts();
|
410 |
+
}
|
411 |
+
|
412 |
+
if (document.getElementById("product-detail")) {
|
413 |
+
loadProductDetail();
|
414 |
+
}
|
415 |
+
|
416 |
+
if (document.getElementById("transaction-details")) {
|
417 |
+
await loadTransactionDetail();
|
418 |
+
|
419 |
+
await updateActionButtonsPosition();
|
420 |
+
|
421 |
+
window.addEventListener("resize", async () => {
|
422 |
+
requestAnimationFrame(updateActionButtonsPosition);
|
423 |
+
});
|
424 |
+
|
425 |
+
document
|
426 |
+
.getElementById("download-pdf")
|
427 |
+
.addEventListener("click", async () => {
|
428 |
+
const mainElement = document.getElementById(
|
429 |
+
"transaction-details"
|
430 |
+
);
|
431 |
+
const receiptWrapperElement =
|
432 |
+
document.querySelector(".receipt-wrapper");
|
433 |
+
|
434 |
+
const widthElement =
|
435 |
+
receiptWrapperElement.offsetWidth -
|
436 |
+
receiptWrapperElement.offsetLeft;
|
437 |
+
const heightElement =
|
438 |
+
receiptWrapperElement.offsetHeight -
|
439 |
+
receiptWrapperElement.offsetTop;
|
440 |
+
|
441 |
+
const params = new URLSearchParams(window.location.search);
|
442 |
+
const transactionKey = params.get("date");
|
443 |
+
|
444 |
+
try {
|
445 |
+
await html2pdf()
|
446 |
+
.from(mainElement)
|
447 |
+
.set({
|
448 |
+
margin: 10,
|
449 |
+
filename: `receipt_${transactionKey}.pdf`,
|
450 |
+
image: { type: "png", quality: 3 },
|
451 |
+
html2canvas: { scale: 3, useCORS: true },
|
452 |
+
jsPDF: {
|
453 |
+
unit: "px",
|
454 |
+
format: [widthElement, heightElement],
|
455 |
+
orientation: "portrait",
|
456 |
+
},
|
457 |
+
})
|
458 |
+
.toPdf()
|
459 |
+
.save();
|
460 |
+
} catch (error) {
|
461 |
+
console.error("Error generating PDF:", error);
|
462 |
+
}
|
463 |
+
});
|
464 |
+
|
465 |
+
document
|
466 |
+
.getElementById("print-receipt")
|
467 |
+
.addEventListener("click", async () => {
|
468 |
+
const mainElement = document.getElementById(
|
469 |
+
"transaction-details"
|
470 |
+
);
|
471 |
+
|
472 |
+
const lastMainElement = mainElement.innerHTML;
|
473 |
+
|
474 |
+
mainElement.innerHTML += `
|
475 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
|
476 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
|
477 |
+
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/aos.css" />
|
478 |
+
<script src="https://printjs-4de6.kxcdn.com/print.min.css"></script>
|
479 |
+
<link rel="stylesheet" href="stylesheet/styles.css">
|
480 |
+
|
481 |
+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" defer></script>
|
482 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.2/html2pdf.bundle.min.js"></script>
|
483 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.13/html-to-image.js"></script>
|
484 |
+
<script src="https://unpkg.com/[email protected]/dist/aos.js" defer></script>
|
485 |
+
<script src="https://printjs-4de6.kxcdn.com/print.min.js"></script>
|
486 |
+
`;
|
487 |
+
|
488 |
+
await printJS("transaction-details", "html");
|
489 |
+
|
490 |
+
mainElement.innerHTML = lastMainElement;
|
491 |
+
});
|
492 |
+
}
|
493 |
+
|
494 |
+
if (document.getElementById("transaction-list")) {
|
495 |
+
loadTransactions();
|
496 |
+
}
|
497 |
+
|
498 |
+
let backButton = document.getElementById("backButton");
|
499 |
+
if (backButton) {
|
500 |
+
backButton.addEventListener("click", (e) => {
|
501 |
+
e.preventDefault();
|
502 |
+
window.history.back();
|
503 |
+
});
|
504 |
+
}
|
505 |
+
});
|
javascript/payment.js
CHANGED
@@ -1,3 +1,538 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const steps = [
|
2 |
+
{ step: 1, title: "Periksa Data Belanja" },
|
3 |
+
{ step: 2, title: "Periksa Informasi Anda" },
|
4 |
+
{ step: 3, title: "Pilih Metode Pembayaran" },
|
5 |
+
{ step: 4, title: "Lakukan Pembayaran" },
|
6 |
+
];
|
7 |
+
|
8 |
+
function getStep() {
|
9 |
+
const params = new URLSearchParams(window.location.search);
|
10 |
+
let step = parseInt(params.get("step"));
|
11 |
+
if (!step || step < 1 || step > 4) step = 1;
|
12 |
+
return step;
|
13 |
+
}
|
14 |
+
|
15 |
+
function goToStep(step) {
|
16 |
+
if (
|
17 |
+
(localStorage.getItem("paymentMethod") || null) === null &&
|
18 |
+
step === 4
|
19 |
+
) {
|
20 |
+
alert("Pilih salah satu metode pembayaran!", false);
|
21 |
+
return;
|
22 |
+
}
|
23 |
+
window.location.href = "payment.html?step=" + step;
|
24 |
+
}
|
25 |
+
|
26 |
+
function renderStepNav(currentStep) {
|
27 |
+
let navHtml = `<div class="table-responsive">
|
28 |
+
<table class="table table-bordered text-center">
|
29 |
+
<thead class="table-light">
|
30 |
+
<tr>`;
|
31 |
+
steps.forEach((s) => {
|
32 |
+
navHtml += `<th class="${
|
33 |
+
s.step === currentStep ? "bg-info text-white" : "text-muted"
|
34 |
+
}">
|
35 |
+
<small>Langkah ${s.step}</small>
|
36 |
+
</th>`;
|
37 |
+
});
|
38 |
+
navHtml += `</tr></thead></table></div>`;
|
39 |
+
document.getElementById("step-nav").innerHTML = navHtml;
|
40 |
+
}
|
41 |
+
|
42 |
+
function renderStepButtons(currentStep) {
|
43 |
+
let btnHtml = `<div class="d-flex justify-content-end">`;
|
44 |
+
if (currentStep > 1) {
|
45 |
+
btnHtml += `<button class="btn btn-secondary btn-lg me-2" onclick="goToStep(${
|
46 |
+
currentStep - 1
|
47 |
+
})">Kembali</button>`;
|
48 |
+
}
|
49 |
+
if (currentStep === 2) {
|
50 |
+
btnHtml += `<button class="btn btn-primary btn-lg" onclick="saveInfoAndLanjut()">Lanjut</button>`;
|
51 |
+
} else if (currentStep < 4) {
|
52 |
+
btnHtml += `<button class="btn btn-primary btn-lg" onclick="goToStep(${
|
53 |
+
currentStep + 1
|
54 |
+
})">Lanjut</button>`;
|
55 |
+
} else {
|
56 |
+
btnHtml += `<button class="btn btn-success btn-lg" onclick="processFinalPayment()">Submit</button>`;
|
57 |
+
}
|
58 |
+
btnHtml += `</div>`;
|
59 |
+
document.getElementById("step-buttons").innerHTML = btnHtml;
|
60 |
+
}
|
61 |
+
|
62 |
+
async function displayStep1Content() {
|
63 |
+
document.getElementById("step-content").innerHTML = `
|
64 |
+
<h2 class="mb-3">Langkah ${steps[0].step}: ${steps[0].title}</h2>
|
65 |
+
<div id="cart-summary-step" class="table-responsive"></div>`;
|
66 |
+
await loadCartSummary();
|
67 |
+
}
|
68 |
+
|
69 |
+
var products = [];
|
70 |
+
|
71 |
+
var totalPrice = 0;
|
72 |
+
async function calculateTotalPrice() {
|
73 |
+
products = await fetchProductsData();
|
74 |
+
|
75 |
+
try {
|
76 |
+
const cart = await getCarts();
|
77 |
+
let cartCount = {};
|
78 |
+
cart.forEach((id) => {
|
79 |
+
cartCount[id] = (cartCount[id] || 0) + 1;
|
80 |
+
});
|
81 |
+
totalPrice = 0;
|
82 |
+
for (let id in cartCount) {
|
83 |
+
const product = products.find((p) => p.id === parseInt(id));
|
84 |
+
if (product) {
|
85 |
+
totalPrice += product.price * cartCount[id];
|
86 |
+
}
|
87 |
+
}
|
88 |
+
} catch (err) {
|
89 |
+
console.error("Error loading cart summary:", err);
|
90 |
+
}
|
91 |
+
return totalPrice;
|
92 |
+
}
|
93 |
+
|
94 |
+
async function getTotalPrice() {
|
95 |
+
totalPrice = await calculateTotalPrice();
|
96 |
+
console.log("Total Harga:", totalPrice);
|
97 |
+
|
98 |
+
return totalPrice;
|
99 |
+
}
|
100 |
+
|
101 |
+
async function loadCartSummary() {
|
102 |
+
products = await fetchProductsData();
|
103 |
+
totalPrice = await getTotalPrice();
|
104 |
+
const cart = await getCarts();
|
105 |
+
|
106 |
+
let cartCount = {};
|
107 |
+
cart.forEach((id) => {
|
108 |
+
cartCount[id] = (cartCount[id] || 0) + 1;
|
109 |
+
});
|
110 |
+
let tableHtml = `<div id="cart-summary-step-wrapper">
|
111 |
+
<table class="table table-striped">
|
112 |
+
<thead>
|
113 |
+
<tr>
|
114 |
+
<th>No</th>
|
115 |
+
<th>Nama Produk</th>
|
116 |
+
<th>Harga</th>
|
117 |
+
<th>Quantity</th>
|
118 |
+
<th>Subtotal</th>
|
119 |
+
</tr>
|
120 |
+
</thead>
|
121 |
+
<tbody>`;
|
122 |
+
let index = 1;
|
123 |
+
if (Object.keys(cartCount).length === 0) {
|
124 |
+
tableHtml += `<tr><td colspan="5">Keranjang kosong.</td></tr>`;
|
125 |
+
} else {
|
126 |
+
for (let id in cartCount) {
|
127 |
+
const product = products.find((p) => p.id === parseInt(id));
|
128 |
+
if (product) {
|
129 |
+
const qty = cartCount[id];
|
130 |
+
const subtotal = product.price * qty;
|
131 |
+
tableHtml += `<tr>
|
132 |
+
<td>${index++}</td>
|
133 |
+
<td>${product.name}</td>
|
134 |
+
<td>${formatRupiah(product.price)}</td>
|
135 |
+
<td>${qty}</td>
|
136 |
+
<td>${formatRupiah(subtotal)}</td>
|
137 |
+
</tr>`;
|
138 |
+
}
|
139 |
+
}
|
140 |
+
tableHtml += `<tr class="table-light">
|
141 |
+
<td colspan="4" class="text-end"><strong>Total</strong></td>
|
142 |
+
<td><strong>${formatRupiah(totalPrice)}</strong></td>
|
143 |
+
</tr>`;
|
144 |
+
}
|
145 |
+
tableHtml += `</tbody></table>`;
|
146 |
+
document.getElementById("cart-summary-step").innerHTML = tableHtml;
|
147 |
+
}
|
148 |
+
|
149 |
+
async function displayStep2Content() {
|
150 |
+
const userData = await getUserData();
|
151 |
+
|
152 |
+
document.getElementById("step-content").innerHTML = `
|
153 |
+
<h2 class="mb-3">Langkah ${steps[1].step}: ${steps[1].title}</h2>
|
154 |
+
<form id="info-form" class="fs-5">
|
155 |
+
<div class="mb-3">
|
156 |
+
<label class="form-label">Nama Lengkap</label>
|
157 |
+
<input type="text" class="form-control form-control-lg" id="infoName" value="${userData.name}" required>
|
158 |
+
</div>
|
159 |
+
<div class="mb-3">
|
160 |
+
<label class="form-label">Email</label>
|
161 |
+
<input type="email" class="form-control form-control-lg" id="infoEmail" value="${userData.email}" required disabled>
|
162 |
+
</div>
|
163 |
+
<div class="mb-3">
|
164 |
+
<label class="form-label">Telepon</label>
|
165 |
+
<input type="text" class="form-control form-control-lg" id="infoPhone" value="${userData.phone}" required>
|
166 |
+
</div>
|
167 |
+
<div class="mb-3">
|
168 |
+
<label class="form-label">Alamat</label>
|
169 |
+
<input type="text" class="form-control form-control-lg" id="infoAddress" value="${userData.address}" required>
|
170 |
+
</div>
|
171 |
+
</form>`;
|
172 |
+
}
|
173 |
+
|
174 |
+
async function saveInfoAndLanjut() {
|
175 |
+
const name = document.getElementById("infoName").value.trim();
|
176 |
+
const email = document.getElementById("infoEmail").value.trim();
|
177 |
+
const phone = document.getElementById("infoPhone").value.trim();
|
178 |
+
const address = document.getElementById("infoAddress").value.trim();
|
179 |
+
|
180 |
+
const userData = await getUserData();
|
181 |
+
|
182 |
+
const transactions = userData.transactions ? userData.transactions : {};
|
183 |
+
|
184 |
+
const updatedUser = {
|
185 |
+
uid: userData.uid,
|
186 |
+
name: name,
|
187 |
+
email: userData.email,
|
188 |
+
phone: phone,
|
189 |
+
address: address,
|
190 |
+
picture: userData.picture,
|
191 |
+
cart: userData.cart,
|
192 |
+
transactions: transactions,
|
193 |
+
};
|
194 |
+
|
195 |
+
if (!name || !email || !phone || !address) {
|
196 |
+
alert("Semua input wajib diisi!", false);
|
197 |
+
return;
|
198 |
+
}
|
199 |
+
|
200 |
+
localStorage.setItem("userData", JSON.stringify(updatedUser));
|
201 |
+
await updateUserDataToGitHub(updatedUser);
|
202 |
+
goToStep(3);
|
203 |
+
}
|
204 |
+
|
205 |
+
function displayStep3Content() {
|
206 |
+
const params = new URLSearchParams(window.location.search);
|
207 |
+
const message = params.get("message") || "";
|
208 |
+
|
209 |
+
if (message.trim()) alert(message, false);
|
210 |
+
|
211 |
+
document.getElementById("step-content").innerHTML = `
|
212 |
+
<h2 class="mb-3">Langkah ${steps[2].step}: ${steps[2].title}</h2>
|
213 |
+
<br>
|
214 |
+
<br>
|
215 |
+
<div class="text-center ${message ? "text-danger mb-4" : ""}">
|
216 |
+
${message}
|
217 |
+
</div>
|
218 |
+
<div id="payment-method-buttons" class="d-flex flex-wrap justify-content-center mb-3">
|
219 |
+
<button class="btn btn-outline-primary btn-lg m-2" data-method="bank">
|
220 |
+
<i class="bi bi-building me-1"></i> Bank Transfer
|
221 |
+
</button>
|
222 |
+
<button class="btn btn-outline-primary btn-lg m-2" data-method="qr">
|
223 |
+
<i class="bi bi-qr-code me-1"></i> Scan QR
|
224 |
+
</button>
|
225 |
+
<button class="btn btn-outline-primary btn-lg m-2" data-method="card">
|
226 |
+
<i class="bi bi-credit-card me-1"></i> Kartu Kredit/Debit
|
227 |
+
</button>
|
228 |
+
</div>
|
229 |
+
<p class="text-center" id="infoChoose"></p>
|
230 |
+
`;
|
231 |
+
|
232 |
+
const infoChoose = document.getElementById("infoChoose");
|
233 |
+
|
234 |
+
document
|
235 |
+
.querySelectorAll("#payment-method-buttons button")
|
236 |
+
.forEach((btn) => {
|
237 |
+
btn.addEventListener("click", function () {
|
238 |
+
const method = this.getAttribute("data-method");
|
239 |
+
|
240 |
+
localStorage.setItem("paymentMethod", method);
|
241 |
+
|
242 |
+
document
|
243 |
+
.querySelectorAll("#payment-method-buttons button")
|
244 |
+
.forEach((b) => {
|
245 |
+
b.classList.remove("active");
|
246 |
+
});
|
247 |
+
|
248 |
+
this.classList.add("active");
|
249 |
+
|
250 |
+
infoChoose.innerText =
|
251 |
+
"Anda memilih metode pembayaran: " + this.innerText;
|
252 |
+
});
|
253 |
+
});
|
254 |
+
|
255 |
+
const savedMethod = localStorage.getItem("paymentMethod");
|
256 |
+
infoChoose.innerText =
|
257 |
+
"Sebelum lanjut pilih metode pembayaran yang tersedia diatas";
|
258 |
+
|
259 |
+
if (savedMethod) {
|
260 |
+
const btn = document.querySelector(
|
261 |
+
`#payment-method-buttons button[data-method="${savedMethod}"]`
|
262 |
+
);
|
263 |
+
if (btn) {
|
264 |
+
btn.classList.add("active");
|
265 |
+
infoChoose.innerText =
|
266 |
+
"Anda memilih metode pembayaran: " + btn.innerText;
|
267 |
+
}
|
268 |
+
}
|
269 |
+
}
|
270 |
+
|
271 |
+
async function loadBankList() {
|
272 |
+
try {
|
273 |
+
const url =
|
274 |
+
"https://gist.githubusercontent.com/muhammadyana/6abf8480799637b4082359b509410018/raw/indonesia-bank.json";
|
275 |
+
const response = await fetch(url);
|
276 |
+
if (!response.ok) {
|
277 |
+
throw new Error("Gagal memuat data bank: " + response.status);
|
278 |
+
}
|
279 |
+
const banks = await response.json();
|
280 |
+
const bankSelect = document.getElementById("bankName");
|
281 |
+
|
282 |
+
bankSelect.innerHTML = '<option value="">Pilih Bank</option>';
|
283 |
+
banks.forEach((bank) => {
|
284 |
+
const option = document.createElement("option");
|
285 |
+
option.value = bank.code;
|
286 |
+
option.text = bank.name;
|
287 |
+
bankSelect.appendChild(option);
|
288 |
+
});
|
289 |
+
} catch (error) {
|
290 |
+
console.error("Error loadBankList:", error);
|
291 |
+
}
|
292 |
+
}
|
293 |
+
|
294 |
+
async function displayStep4Content() {
|
295 |
+
await getTotalPrice();
|
296 |
+
const method = localStorage.getItem("paymentMethod") || null;
|
297 |
+
|
298 |
+
if (method === null) {
|
299 |
+
window.location.href =
|
300 |
+
"payment.html?step=3&message=Sebelum lanjut mohon pilih metode pembayaran terlebih dahulu!";
|
301 |
+
}
|
302 |
+
|
303 |
+
let html = `<h2 class="mb-3">Langkah ${steps[3].step}: ${steps[3].title}</h2>`;
|
304 |
+
|
305 |
+
if (method === "qr") {
|
306 |
+
html += `<p class="fs-5">Silakan scan QR Code berikut dengan aplikasi pembayaran Anda:</p>
|
307 |
+
<div id="qr-code" class="my-3"></div>`;
|
308 |
+
document.getElementById("step-content").innerHTML = html;
|
309 |
+
generateQRCode();
|
310 |
+
} else if (method === "bank") {
|
311 |
+
html += `<form id="final-payment-form" class="fs-5">
|
312 |
+
<div class="mb-3">
|
313 |
+
<label class="form-label">Nama Bank</label>
|
314 |
+
<select class="form-control form-control-lg" id="bankName" required>
|
315 |
+
<option value="">Pilih Bank</option>
|
316 |
+
</select>
|
317 |
+
</div>
|
318 |
+
<div class="mb-3">
|
319 |
+
<label class="form-label">Nomor Rekening</label>
|
320 |
+
<input type="text" class="form-control form-control-lg" id="accountNumber" required>
|
321 |
+
</div>
|
322 |
+
</form>`;
|
323 |
+
document.getElementById("step-content").innerHTML = html;
|
324 |
+
|
325 |
+
await loadBankList();
|
326 |
+
} else {
|
327 |
+
html += `<form id="final-payment-form" class="fs-5">
|
328 |
+
<div class="mb-3">
|
329 |
+
<label class="form-label">Nomor Kartu Kredit/Debit</label>
|
330 |
+
<input type="text" class="form-control form-control-lg" id="cardNumber" required>
|
331 |
+
</div>
|
332 |
+
<div class="mb-3">
|
333 |
+
<label class="form-label">Tanggal Kedaluwarsa (MM/YY)</label>
|
334 |
+
<input type="text" class="form-control form-control-lg" id="expiry" placeholder="MM/YY" required>
|
335 |
+
</div>
|
336 |
+
<div class="mb-3">
|
337 |
+
<label class="form-label">CVV</label>
|
338 |
+
<input type="text" class="form-control form-control-lg" id="cvv" required>
|
339 |
+
</div>
|
340 |
+
</form>`;
|
341 |
+
document.getElementById("step-content").innerHTML = html;
|
342 |
+
}
|
343 |
+
}
|
344 |
+
|
345 |
+
async function processFinalPayment() {
|
346 |
+
const method = localStorage.getItem("paymentMethod") || "card";
|
347 |
+
let valid = true;
|
348 |
+
|
349 |
+
if (method === "bank") {
|
350 |
+
valid =
|
351 |
+
document.getElementById("bankName").value.trim() &&
|
352 |
+
document.getElementById("accountNumber").value.trim();
|
353 |
+
} else if (method === "card") {
|
354 |
+
valid =
|
355 |
+
document.getElementById("cardNumber").value.trim() &&
|
356 |
+
document.getElementById("expiry").value.trim() &&
|
357 |
+
document.getElementById("cvv").value.trim();
|
358 |
+
}
|
359 |
+
|
360 |
+
if (!valid) {
|
361 |
+
alert("Semua input wajib diisi!", false);
|
362 |
+
return;
|
363 |
+
}
|
364 |
+
|
365 |
+
await alert(
|
366 |
+
"Proses pembayaran...",
|
367 |
+
true,
|
368 |
+
true,
|
369 |
+
7,
|
370 |
+
[
|
371 |
+
{
|
372 |
+
timeOut: 3,
|
373 |
+
message: "Pembayaran Berhasil!",
|
374 |
+
},
|
375 |
+
],
|
376 |
+
3
|
377 |
+
);
|
378 |
+
|
379 |
+
await clearCart(method);
|
380 |
+
}
|
381 |
+
|
382 |
+
async function clearCart(payment_method) {
|
383 |
+
const userData = await getUserData();
|
384 |
+
const transactions = userData.transactions ? userData.transactions : {};
|
385 |
+
|
386 |
+
const updatedUser = {
|
387 |
+
uid: userData.uid,
|
388 |
+
name: userData.name,
|
389 |
+
email: userData.email,
|
390 |
+
phone: userData.phone,
|
391 |
+
address: userData.address,
|
392 |
+
photo_profile: userData.photo_profile,
|
393 |
+
cart: [],
|
394 |
+
transactions: transactions,
|
395 |
+
};
|
396 |
+
|
397 |
+
console.log("clearCart", updatedUser);
|
398 |
+
|
399 |
+
await saveTransaction(updatedUser, userData.cart, payment_method);
|
400 |
+
|
401 |
+
updateCartIcon();
|
402 |
+
}
|
403 |
+
|
404 |
+
async function saveTransaction(userData, productIDs, paymentMethod) {
|
405 |
+
let now = new Date();
|
406 |
+
let transactionID =
|
407 |
+
now.getFullYear() +
|
408 |
+
"_" +
|
409 |
+
String(now.getMonth() + 1).padStart(2, "0") +
|
410 |
+
"_" +
|
411 |
+
String(now.getDate()).padStart(2, "0") +
|
412 |
+
"_" +
|
413 |
+
String(now.getHours()).padStart(2, "0") +
|
414 |
+
"_" +
|
415 |
+
String(now.getMinutes()).padStart(2, "0") +
|
416 |
+
"_" +
|
417 |
+
String(now.getSeconds()).padStart(2, "0") +
|
418 |
+
"_" +
|
419 |
+
String(now.getMilliseconds()).padStart(3, "0");
|
420 |
+
|
421 |
+
const products = await fetchProductsData();
|
422 |
+
|
423 |
+
const transactions = userData.transactions;
|
424 |
+
|
425 |
+
let productMap = {};
|
426 |
+
|
427 |
+
productIDs.forEach((id) => {
|
428 |
+
if (products[id]) {
|
429 |
+
if (productMap[id]) {
|
430 |
+
productMap[id].total += 1;
|
431 |
+
} else {
|
432 |
+
productMap[id] = { ...products[id], total: 1 };
|
433 |
+
}
|
434 |
+
}
|
435 |
+
});
|
436 |
+
|
437 |
+
let theProducts = Object.values(productMap);
|
438 |
+
|
439 |
+
transactions[transactionID] = {
|
440 |
+
products: theProducts,
|
441 |
+
payment_method: paymentMethod,
|
442 |
+
user: {
|
443 |
+
name: userData.name,
|
444 |
+
email: userData.email,
|
445 |
+
phone: userData.phone,
|
446 |
+
address: userData.address,
|
447 |
+
photo_profile: userData.photo_profile,
|
448 |
+
},
|
449 |
+
};
|
450 |
+
|
451 |
+
userData.transactions = transactions;
|
452 |
+
|
453 |
+
await updateUserDataToGitHub(userData);
|
454 |
+
localStorage.setItem("userData", JSON.stringify(userData));
|
455 |
+
|
456 |
+
console.log("saveTransaction", userData);
|
457 |
+
|
458 |
+
console.log("Transaksi berhasil disimpan dengan ID:", transactionID);
|
459 |
+
|
460 |
+
window.location.href = `transaction.html?date=${transactionID}`;
|
461 |
+
}
|
462 |
+
|
463 |
+
function computeCRC(input) {
|
464 |
+
let crc = 0xffff;
|
465 |
+
for (let i = 0; i < input.length; i++) {
|
466 |
+
crc ^= input.charCodeAt(i) << 8;
|
467 |
+
for (let j = 0; j < 8; j++) {
|
468 |
+
if (crc & 0x8000) {
|
469 |
+
crc = ((crc << 1) ^ 0x1021) & 0xffff;
|
470 |
+
} else {
|
471 |
+
crc = (crc << 1) & 0xffff;
|
472 |
+
}
|
473 |
+
}
|
474 |
+
}
|
475 |
+
return crc.toString(16).toUpperCase().padStart(4, "0");
|
476 |
+
}
|
477 |
+
|
478 |
+
function generateQRCode() {
|
479 |
+
const qrContainer = document.getElementById("qr-code");
|
480 |
+
if (!qrContainer) return;
|
481 |
+
qrContainer.innerHTML = "";
|
482 |
+
|
483 |
+
const payloadWithoutCRC = `00 02 01
|
484 |
+
01 0F ID.CO.QRIS000001
|
485 |
+
02 07 QRIS V2
|
486 |
+
03 02 12
|
487 |
+
04 03 360
|
488 |
+
05 06 ${totalPrice}
|
489 |
+
06 02 ID
|
490 |
+
07 0B BRI Virtual
|
491 |
+
08 07 Sidoarjo
|
492 |
+
63 04`;
|
493 |
+
|
494 |
+
const payloadForCRC = payloadWithoutCRC + "0000";
|
495 |
+
const crcValue = computeCRC(payloadForCRC);
|
496 |
+
|
497 |
+
const finalPayload = payloadWithoutCRC + crcValue;
|
498 |
+
|
499 |
+
new QRCode(qrContainer, {
|
500 |
+
text: finalPayload,
|
501 |
+
width: 200,
|
502 |
+
height: 200,
|
503 |
+
});
|
504 |
+
}
|
505 |
+
|
506 |
+
async function initPaymentSteps() {
|
507 |
+
const step = getStep();
|
508 |
+
const cart = await getCarts();
|
509 |
+
|
510 |
+
if (cart.length === 0)
|
511 |
+
window.location.href =
|
512 |
+
"cart.html?message=Anda belum memesan produk apapun. Pergi ke halaman produk untuk berbelanja";
|
513 |
+
|
514 |
+
products = await fetchProductsData();
|
515 |
+
switch (step) {
|
516 |
+
case 1:
|
517 |
+
await displayStep1Content();
|
518 |
+
break;
|
519 |
+
case 2:
|
520 |
+
await displayStep2Content();
|
521 |
+
break;
|
522 |
+
case 3:
|
523 |
+
displayStep3Content();
|
524 |
+
break;
|
525 |
+
case 4:
|
526 |
+
await displayStep4Content();
|
527 |
+
break;
|
528 |
+
default:
|
529 |
+
displayStep1Content();
|
530 |
+
}
|
531 |
+
renderStepNav(step);
|
532 |
+
renderStepButtons(step);
|
533 |
+
}
|
534 |
+
|
535 |
+
document.addEventListener("DOMContentLoaded", function () {
|
536 |
+
AOS.init();
|
537 |
+
initPaymentSteps();
|
538 |
+
});
|
javascript/profile.js
CHANGED
@@ -1,3 +1,243 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const CLIENT_ID =
|
2 |
+
"819389601271-fl7jsirpdkepkcou78erki5hmi30rrbm.apps.googleusercontent.com";
|
3 |
+
let tokenClient;
|
4 |
+
let accessToken;
|
5 |
+
|
6 |
+
async function handleCredentialResponse(response) {
|
7 |
+
const idToken = response.credential;
|
8 |
+
const payload = JSON.parse(atob(idToken.split(".")[1]));
|
9 |
+
const uid = payload.sub;
|
10 |
+
|
11 |
+
localStorage.setItem("userData", JSON.stringify({
|
12 |
+
uid: uid,
|
13 |
+
name: payload.name,
|
14 |
+
photo_profile: payload.picture,
|
15 |
+
email: payload.email,
|
16 |
+
}));
|
17 |
+
|
18 |
+
let userData = await getUserData();
|
19 |
+
if (!userData || Object.keys(userData).length === 0) {
|
20 |
+
userData = {
|
21 |
+
uid: uid,
|
22 |
+
name: payload.name,
|
23 |
+
photo_profile: payload.picture,
|
24 |
+
email: payload.email,
|
25 |
+
};
|
26 |
+
}
|
27 |
+
|
28 |
+
await updateUserDataToGitHub(userData);
|
29 |
+
|
30 |
+
localStorage.setItem("cart", JSON.stringify(userData.cart || []));
|
31 |
+
|
32 |
+
window.location.reload();
|
33 |
+
}
|
34 |
+
|
35 |
+
function handleAccessTokenResponse(response) {
|
36 |
+
accessToken = response.access_token;
|
37 |
+
console.log("Access Token:", accessToken);
|
38 |
+
fetchUserAdditionalInfo();
|
39 |
+
}
|
40 |
+
|
41 |
+
async function fetchUserAdditionalInfo() {
|
42 |
+
let userData = JSON.parse(localStorage.getItem("userData"));
|
43 |
+
if (userData && userData.uid && accessToken) {
|
44 |
+
try {
|
45 |
+
const res = await fetch(
|
46 |
+
"https://people.googleapis.com/v1/people/me?personFields=phoneNumbers,addresses",
|
47 |
+
{
|
48 |
+
headers: {
|
49 |
+
Authorization: `Bearer ${accessToken}`,
|
50 |
+
},
|
51 |
+
}
|
52 |
+
);
|
53 |
+
if (!res.ok)
|
54 |
+
throw new Error(
|
55 |
+
"Gagal mendapatkan data tambahan: " + res.status
|
56 |
+
);
|
57 |
+
const additionalData = await res.json();
|
58 |
+
console.log("Data tambahan:", additionalData);
|
59 |
+
|
60 |
+
if (
|
61 |
+
additionalData.phoneNumbers &&
|
62 |
+
additionalData.phoneNumbers.length > 0
|
63 |
+
) {
|
64 |
+
userData.phone = additionalData.phoneNumbers[0].value;
|
65 |
+
}
|
66 |
+
if (
|
67 |
+
additionalData.addresses &&
|
68 |
+
additionalData.addresses.length > 0
|
69 |
+
) {
|
70 |
+
userData.address = additionalData.addresses[0].formattedValue;
|
71 |
+
}
|
72 |
+
localStorage.setItem("userData", JSON.stringify(userData));
|
73 |
+
console.log("User Data Lengkap:", userData);
|
74 |
+
} catch (err) {
|
75 |
+
console.error("Error fetching additional info:", err);
|
76 |
+
}
|
77 |
+
} else {
|
78 |
+
console.warn("Data UID atau access token tidak tersedia.");
|
79 |
+
}
|
80 |
+
}
|
81 |
+
|
82 |
+
async function renderGoogleLoginButton() {
|
83 |
+
const container = document.getElementById("profile-container");
|
84 |
+
|
85 |
+
document
|
86 |
+
.querySelectorAll("#googleSignInButton")
|
87 |
+
.forEach((elm) => elm.remove());
|
88 |
+
|
89 |
+
const urlNow = window.location.href;
|
90 |
+
const urlParams = new URLSearchParams(window.location.search);
|
91 |
+
const redirectUrl = urlParams.get("redirect") || urlNow;
|
92 |
+
const message =
|
93 |
+
urlParams.get("message") ||
|
94 |
+
"Silakan masuk ke akun Anda terlebih dahulu untuk melanjutkan:";
|
95 |
+
|
96 |
+
container.innerHTML = `
|
97 |
+
<p class="text-center" style="text-decoration: underline; font-style: italic;">${message}</p>
|
98 |
+
<span class="text-center m-4">Klik tombol di bawah ini untuk masuk:</span>
|
99 |
+
<div id="googleSignInButton"></div>
|
100 |
+
`;
|
101 |
+
|
102 |
+
if (message.trim()) alert(message, false);
|
103 |
+
|
104 |
+
let isGoogle = false;
|
105 |
+
do {
|
106 |
+
try {
|
107 |
+
google.accounts.id.initialize({
|
108 |
+
client_id: CLIENT_ID,
|
109 |
+
callback: async (response) => {
|
110 |
+
await handleCredentialResponse(response);
|
111 |
+
window.location.href = redirectUrl;
|
112 |
+
},
|
113 |
+
auto_select: false,
|
114 |
+
button_auto_select: false,
|
115 |
+
scope: "email profile https://www.googleapis.com/auth/user.phonenumbers.read https://www.googleapis.com/auth/user.addresses.read",
|
116 |
+
});
|
117 |
+
|
118 |
+
google.accounts.id.prompt();
|
119 |
+
|
120 |
+
google.accounts.id.renderButton(
|
121 |
+
document.getElementById("googleSignInButton"),
|
122 |
+
{ theme: "outline", size: "large" }
|
123 |
+
);
|
124 |
+
|
125 |
+
isGoogle = true;
|
126 |
+
} catch (e) {
|
127 |
+
console.error("Error initializing Google login:", e);
|
128 |
+
alert("Jika tombol masuk tidak tampil, silahkan muat ulang halaman ini", false)
|
129 |
+
|
130 |
+
container.innerHTML += `
|
131 |
+
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
132 |
+
<script src="https://accounts.google.com/gsi/oauth2" async defer></script>
|
133 |
+
`;
|
134 |
+
}
|
135 |
+
|
136 |
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
137 |
+
} while (!isGoogle);
|
138 |
+
}
|
139 |
+
|
140 |
+
async function initProfile() {
|
141 |
+
const userData = await getUserData();
|
142 |
+
if (userData && Object.keys(userData).length > 0) {
|
143 |
+
renderProfileForm(userData);
|
144 |
+
} else {
|
145 |
+
await renderGoogleLoginButton();
|
146 |
+
}
|
147 |
+
}
|
148 |
+
|
149 |
+
function renderProfileForm(userData) {
|
150 |
+
const container = document.getElementById("profile-container");
|
151 |
+
container.innerHTML = `
|
152 |
+
<form id="profileForm" class="fs-5">
|
153 |
+
<div class="mb-2 d-flex flex-row align-items-center justify-content-center">
|
154 |
+
<img id="photo_profile" class="img-fluid img-thumbnail rounded-circle" src=${userData.photo_profile}>
|
155 |
+
<input class="d-none" type="file" id="fileInput" accept="image/*">
|
156 |
+
</div>
|
157 |
+
<div class="mb-2">
|
158 |
+
<input class="form-control form-control-lg" type="text" id="name" placeholder="Nama" value="${
|
159 |
+
userData.name || ""
|
160 |
+
}" required>
|
161 |
+
</div>
|
162 |
+
<div class="mb-2">
|
163 |
+
<input class="form-control form-control-lg" type="email" id="email" placeholder="Email" value="${
|
164 |
+
userData.email || ""
|
165 |
+
}" required disabled>
|
166 |
+
</div>
|
167 |
+
<div class="mb-2">
|
168 |
+
<input class="form-control form-control-lg" type="text" id="phone" placeholder="No. HP" value="${
|
169 |
+
userData.phone || ""
|
170 |
+
}" required>
|
171 |
+
</div>
|
172 |
+
<div class="mb-2">
|
173 |
+
<input class="form-control form-control-lg" type="text" id="address" placeholder="Alamat" value="${
|
174 |
+
userData.address || ""
|
175 |
+
}" required>
|
176 |
+
</div>
|
177 |
+
<br>
|
178 |
+
<button class="btn btn-primary btn-lg" type="button" id="saveProfile">Simpan</button>
|
179 |
+
<br><br>
|
180 |
+
<button class="btn btn-danger btn-lg" type="button" id="logout">Keluar Akun</button>
|
181 |
+
</form>`;
|
182 |
+
|
183 |
+
document.getElementById("photo_profile").addEventListener("click", function () {
|
184 |
+
document.getElementById("fileInput").click();
|
185 |
+
});
|
186 |
+
|
187 |
+
document.getElementById("fileInput").addEventListener("change", function (event) {
|
188 |
+
const file = event.target.files[0];
|
189 |
+
if (file) {
|
190 |
+
if (file.size > 1 * 1024 * 1024) {
|
191 |
+
alert("Ukuran file terlalu besar! Maksimal 1MB.", false);
|
192 |
+
return;
|
193 |
+
}
|
194 |
+
|
195 |
+
const reader = new FileReader();
|
196 |
+
reader.onload = function (e) {
|
197 |
+
document.getElementById("photo_profile").src = e.target.result;
|
198 |
+
};
|
199 |
+
reader.readAsDataURL(file);
|
200 |
+
}
|
201 |
+
});
|
202 |
+
|
203 |
+
document
|
204 |
+
.getElementById("saveProfile")
|
205 |
+
.addEventListener("click", async function () {
|
206 |
+
const updatedUser = {
|
207 |
+
uid: userData.uid,
|
208 |
+
name: document.getElementById("name").value.trim(),
|
209 |
+
email: userData.email,
|
210 |
+
phone: document.getElementById("phone").value.trim(),
|
211 |
+
address: document.getElementById("address").value.trim(),
|
212 |
+
photo_profile: document.getElementById("photo_profile").src,
|
213 |
+
cart: userData.cart ? userData.cart : [],
|
214 |
+
transactions: userData.transactions
|
215 |
+
? userData.transactions
|
216 |
+
: [],
|
217 |
+
};
|
218 |
+
|
219 |
+
if (
|
220 |
+
!updatedUser.name ||
|
221 |
+
!updatedUser.email ||
|
222 |
+
!updatedUser.phone ||
|
223 |
+
!updatedUser.address
|
224 |
+
) {
|
225 |
+
alert("Semua input wajib diisi!", false);
|
226 |
+
return;
|
227 |
+
}
|
228 |
+
|
229 |
+
localStorage.setItem("userData", JSON.stringify(updatedUser));
|
230 |
+
await updateUserDataToGitHub(updatedUser);
|
231 |
+
alert("Profil berhasil disimpan!", false);
|
232 |
+
});
|
233 |
+
|
234 |
+
document.getElementById("logout").addEventListener("click", function () {
|
235 |
+
localStorage.removeItem("userData");
|
236 |
+
window.location.reload();
|
237 |
+
});
|
238 |
+
}
|
239 |
+
|
240 |
+
document.addEventListener("DOMContentLoaded", function () {
|
241 |
+
AOS.init();
|
242 |
+
initProfile();
|
243 |
+
});
|
javascript/search.js
CHANGED
@@ -1,3 +1,241 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
document.addEventListener("DOMContentLoaded", async function () {
|
2 |
+
const resultList = document.getElementById("result-list");
|
3 |
+
const urlParams = new URLSearchParams(window.location.search);
|
4 |
+
const productQuery = urlParams.get("products");
|
5 |
+
const transactionQuery = urlParams.get("transactions");
|
6 |
+
|
7 |
+
const createHeader = (text) => {
|
8 |
+
const header = document.createElement("h3");
|
9 |
+
header.textContent = text;
|
10 |
+
header.className = "col-12 mt-4";
|
11 |
+
resultList.appendChild(header);
|
12 |
+
};
|
13 |
+
|
14 |
+
if (!productQuery && !transactionQuery) {
|
15 |
+
const messageElem = document.createElement("p");
|
16 |
+
messageElem.textContent =
|
17 |
+
"Tidak menemukan hasil pencarian yang sesuai.";
|
18 |
+
messageElem.className = "text-center";
|
19 |
+
resultList.appendChild(messageElem);
|
20 |
+
return;
|
21 |
+
}
|
22 |
+
|
23 |
+
document.getElementById("searchInput").value =
|
24 |
+
productQuery && transactionQuery
|
25 |
+
? productQuery + " " + transactionQuery
|
26 |
+
: productQuery
|
27 |
+
? productQuery
|
28 |
+
: transactionQuery;
|
29 |
+
|
30 |
+
if (productQuery) {
|
31 |
+
const products = await fetchProductsData();
|
32 |
+
|
33 |
+
const filteredProducts = products.filter((product) => {
|
34 |
+
const lowerQuery = productQuery.toLowerCase();
|
35 |
+
return (
|
36 |
+
product.name.toLowerCase().includes(lowerQuery) ||
|
37 |
+
(product.description &&
|
38 |
+
product.description.toLowerCase().includes(lowerQuery))
|
39 |
+
);
|
40 |
+
});
|
41 |
+
|
42 |
+
if (filteredProducts.length > 0) {
|
43 |
+
createHeader("Produk");
|
44 |
+
filteredProducts.forEach((product) => {
|
45 |
+
const col = document.createElement("div");
|
46 |
+
col.className = "col-lg-3 col-md-4 col-sm-6";
|
47 |
+
|
48 |
+
const card = document.createElement("div");
|
49 |
+
card.className = "card h-100";
|
50 |
+
|
51 |
+
let thumbnail = "assets/";
|
52 |
+
if (product.files && product.files.length > 0) {
|
53 |
+
thumbnail += product.files[0];
|
54 |
+
}
|
55 |
+
let thumbHTML = "";
|
56 |
+
if (thumbnail.match(/\.(jpg|jpeg|png|gif)$/i)) {
|
57 |
+
thumbHTML = `<img src="${thumbnail}" class="card-img-top" alt="${product.name}">`;
|
58 |
+
} else {
|
59 |
+
thumbHTML = `<img src="img/placeholder.jpg" class="card-img-top" alt="${product.name}">`;
|
60 |
+
}
|
61 |
+
|
62 |
+
const cardBody = document.createElement("div");
|
63 |
+
cardBody.className = "card-body d-flex flex-column";
|
64 |
+
|
65 |
+
const title = document.createElement("h5");
|
66 |
+
title.className = "card-title";
|
67 |
+
title.innerText = product.name;
|
68 |
+
|
69 |
+
const desc = document.createElement("p");
|
70 |
+
desc.className = "card-text";
|
71 |
+
desc.innerText = product.description;
|
72 |
+
|
73 |
+
const price = document.createElement("p");
|
74 |
+
price.className = "card-text fw-bold";
|
75 |
+
price.innerText = formatRupiah(product.price);
|
76 |
+
|
77 |
+
const detailLink = document.createElement("a");
|
78 |
+
detailLink.href = "product.html?id=" + product.id;
|
79 |
+
detailLink.className = "btn btn-primary btn-lg mt-auto";
|
80 |
+
detailLink.innerText = "Lihat Detail";
|
81 |
+
|
82 |
+
const cartBtn = document.createElement("button");
|
83 |
+
cartBtn.className = "btn btn-success btn-lg mt-2";
|
84 |
+
cartBtn.innerText = "Tambah Keranjang";
|
85 |
+
cartBtn.onclick = async function () {
|
86 |
+
await promptQuantityAndAdd(product.id);
|
87 |
+
};
|
88 |
+
|
89 |
+
card.innerHTML = thumbHTML;
|
90 |
+
cardBody.appendChild(title);
|
91 |
+
cardBody.appendChild(desc);
|
92 |
+
cardBody.appendChild(price);
|
93 |
+
cardBody.appendChild(detailLink);
|
94 |
+
cardBody.appendChild(cartBtn);
|
95 |
+
card.appendChild(cardBody);
|
96 |
+
col.appendChild(card);
|
97 |
+
resultList.appendChild(col);
|
98 |
+
});
|
99 |
+
} else {
|
100 |
+
const messageElem = document.createElement("p");
|
101 |
+
messageElem.textContent = "Produk tidak ditemukan.";
|
102 |
+
messageElem.className = "text-center";
|
103 |
+
resultList.appendChild(messageElem);
|
104 |
+
}
|
105 |
+
}
|
106 |
+
|
107 |
+
if (productQuery && transactionQuery) {
|
108 |
+
const hrElement = document.createElement("hr");
|
109 |
+
resultList.appendChild(hrElement);
|
110 |
+
}
|
111 |
+
|
112 |
+
if (transactionQuery) {
|
113 |
+
const userData = await getUserData();
|
114 |
+
const transactions = userData.transactions ? userData.transactions : {};
|
115 |
+
|
116 |
+
if (transactions && Object.keys(transactions).length !== 0) {
|
117 |
+
const entries = Object.entries(transactions).sort(
|
118 |
+
(a, b) => {
|
119 |
+
const parseDateFromKey = (key) => {
|
120 |
+
const [year, month, day, hour, minute, second, ms] =
|
121 |
+
key.split("_");
|
122 |
+
return new Date(
|
123 |
+
year,
|
124 |
+
month - 1,
|
125 |
+
day,
|
126 |
+
hour,
|
127 |
+
minute,
|
128 |
+
second,
|
129 |
+
ms
|
130 |
+
);
|
131 |
+
};
|
132 |
+
return parseDateFromKey(b[0]) - parseDateFromKey(a[0]);
|
133 |
+
}
|
134 |
+
);
|
135 |
+
|
136 |
+
const filteredTransactions = entries.filter(
|
137 |
+
([key, transaction]) => {
|
138 |
+
const td = formatTransactionDate(key);
|
139 |
+
const pm = transaction.payment_method;
|
140 |
+
const total = transaction.total.toString();
|
141 |
+
|
142 |
+
return (
|
143 |
+
key.includes(transactionQuery) ||
|
144 |
+
td[0].includes(transactionQuery) ||
|
145 |
+
td[1].includes(transactionQuery) ||
|
146 |
+
pm.includes(transactionQuery) ||
|
147 |
+
total.includes(transactionQuery) ||
|
148 |
+
Object.values(productCount)
|
149 |
+
.join(",")
|
150 |
+
.includes(transactionQuery)
|
151 |
+
);
|
152 |
+
}
|
153 |
+
);
|
154 |
+
|
155 |
+
if (filteredTransactions.length > 0) {
|
156 |
+
createHeader("Transaksi");
|
157 |
+
|
158 |
+
filteredTransactions.forEach(
|
159 |
+
([transactionKey, transaction]) => {
|
160 |
+
const col = document.createElement("div");
|
161 |
+
col.className =
|
162 |
+
"transactions-body col-lg-4 col-md-6 col-sm-12 mt-4";
|
163 |
+
|
164 |
+
const card = document.createElement("div");
|
165 |
+
card.className = "card h-100";
|
166 |
+
|
167 |
+
const cardBody = document.createElement("div");
|
168 |
+
cardBody.className = "card-body d-flex flex-column";
|
169 |
+
cardBody.style.position = "relative";
|
170 |
+
|
171 |
+
let total = 0;
|
172 |
+
|
173 |
+
transaction.products.forEach((product) => {
|
174 |
+
total += product.price * product.total;
|
175 |
+
});
|
176 |
+
|
177 |
+
const td = formatTransactionDate(transactionKey);
|
178 |
+
|
179 |
+
let paymentIcon = "";
|
180 |
+
switch (transaction.payment_method) {
|
181 |
+
case "bank":
|
182 |
+
paymentIcon = "bi-building";
|
183 |
+
break;
|
184 |
+
case "qr":
|
185 |
+
paymentIcon = "bi-qr-code";
|
186 |
+
break;
|
187 |
+
case "card":
|
188 |
+
paymentIcon = "bi-credit-card";
|
189 |
+
break;
|
190 |
+
default:
|
191 |
+
paymentIcon = "bi-currency-dollar";
|
192 |
+
}
|
193 |
+
|
194 |
+
cardBody.innerHTML = `
|
195 |
+
<div class="payment-icon-bg">
|
196 |
+
<i class="bi ${paymentIcon}" style="font-size: 2.5rem;"></i>
|
197 |
+
<span class="m-2">Metode: ${
|
198 |
+
transaction.payment_method
|
199 |
+
? transaction.payment_method
|
200 |
+
: "N/A"
|
201 |
+
}</span>
|
202 |
+
</div>
|
203 |
+
<div class="content">
|
204 |
+
<div class="d-flex justify-content-between align-items-center">
|
205 |
+
<div class="transaction-total fw-bold">
|
206 |
+
Total: ${formatRupiah(total)}
|
207 |
+
</div>
|
208 |
+
</div>
|
209 |
+
<div class="d-flex justify-content-between align-items-center mt-auto">
|
210 |
+
<div class="transaction-date">
|
211 |
+
Tanggal: ${td[0]} <br> Waktu: ${td[1]}
|
212 |
+
</div>
|
213 |
+
</div>
|
214 |
+
</div>
|
215 |
+
`;
|
216 |
+
|
217 |
+
card.appendChild(cardBody);
|
218 |
+
col.appendChild(card);
|
219 |
+
resultList.appendChild(col);
|
220 |
+
|
221 |
+
col.addEventListener("click", () => {
|
222 |
+
window.location.href = `transaction.html?date=${transactionKey}`;
|
223 |
+
});
|
224 |
+
}
|
225 |
+
);
|
226 |
+
} else {
|
227 |
+
const messageElem = document.createElement("p");
|
228 |
+
messageElem.textContent = "Transaksi tidak ditemukan.";
|
229 |
+
messageElem.className = "text-center";
|
230 |
+
alert(messageElem.textContent, false);
|
231 |
+
resultList.appendChild(messageElem);
|
232 |
+
}
|
233 |
+
} else {
|
234 |
+
const messageElem = document.createElement("p");
|
235 |
+
messageElem.textContent = "Anda belum melakukan transaksi apapun. Silahkan beli produk terlebih dahulu.";
|
236 |
+
messageElem.className = "text-center";
|
237 |
+
alert(messageElem.textContent, false);
|
238 |
+
resultList.appendChild(messageElem);
|
239 |
+
}
|
240 |
+
}
|
241 |
+
});
|