Adityadn commited on
Commit
4a97c42
·
verified ·
1 Parent(s): 3ae39c8

Upload 7 files

Browse files
javascript/cart.js CHANGED
@@ -1,3 +1,187 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:73f4e3e747a78d0f4a19cffde6e5a27121c0778a73e785eff744319b0af8c7e3
3
- size 5704
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:2e47cba0f834c8034e1cc207b8781134d345e27589d7443fb15f99b4c4ac16e7
3
- size 10707
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:fb444f036fd2376ca0f99f059b67ca1a923bf00f075e5d13ee72cdcec09a70fe
3
- size 6758
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:df00f6f01ceb1885cb0b19ecb4451f766362d384ceab3d943fa77a072f5b1194
3
- size 17656
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c55fb2fec92c75eed44d2527781b7bba1fa4e658c572ca67bc2430a7ddc19481
3
- size 16798
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:d6a1748ed1c7934d221c432c6ad7e670be7134d5c8c389a3270ef36b7f15f9f5
3
- size 8818
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:5c718b23f3951dcd4c23c5f5bb4cdc0acbbf00124e2219813dcd50ba74d87853
3
- size 9948
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ });