Spaces:
Running
Running
Upload 51 files
Browse files- .gitattributes +5 -1
- database/products.json +44 -0
- database/users.json +1 -0
- javascript/cart.js +2 -2
- javascript/component.js +3 -298
- javascript/data.js +3 -204
- javascript/main.js +3 -504
- javascript/payment.js +3 -537
- javascript/profile.js +3 -260
- javascript/search.js +3 -247
- python/file.bat +3 -0
- python/project2flow.py +153 -0
- results/js_flowcharts/cart_full_flowchart +3 -0
- results/js_flowcharts/cart_full_flowchart.pdf +3 -0
- results/js_flowcharts/component_full_flowchart +3 -0
- results/js_flowcharts/component_full_flowchart.pdf +3 -0
- results/js_flowcharts/data_full_flowchart +3 -0
- results/js_flowcharts/data_full_flowchart.pdf +3 -0
- results/js_flowcharts/main_full_flowchart +3 -0
- results/js_flowcharts/main_full_flowchart.pdf +3 -0
- results/js_flowcharts/payment_full_flowchart +3 -0
- results/js_flowcharts/payment_full_flowchart.pdf +3 -0
- results/js_flowcharts/profile_full_flowchart +3 -0
- results/js_flowcharts/profile_full_flowchart.pdf +3 -0
- results/js_flowcharts/search_full_flowchart +3 -0
- results/js_flowcharts/search_full_flowchart.pdf +3 -0
- results/project_file_dependencies +3 -0
- results/project_file_dependencies.pdf +3 -0
.gitattributes
CHANGED
@@ -36,4 +36,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
36 |
javascript/* filter=lfs diff=lfs merge=lfs -text
|
37 |
.prettierrc filter=lfs diff=lfs merge=lfs -text
|
38 |
results/* filter=lfs diff=lfs merge=lfs -text
|
39 |
-
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
36 |
javascript/* filter=lfs diff=lfs merge=lfs -text
|
37 |
.prettierrc filter=lfs diff=lfs merge=lfs -text
|
38 |
results/* filter=lfs diff=lfs merge=lfs -text
|
39 |
+
*.mp4 filter=lfs diff=lfs merge=lfs -textassets/img/product1b.jpg filter=lfs diff=lfs merge=lfs -text
|
40 |
+
assets/img/product2a.jpg filter=lfs diff=lfs merge=lfs -text
|
41 |
+
assets/img/product2b.jpg filter=lfs diff=lfs merge=lfs -text
|
42 |
+
assets/img/product4b.jpg filter=lfs diff=lfs merge=lfs -text
|
43 |
+
assets/img/product6a.jpg filter=lfs diff=lfs merge=lfs -text
|
database/products.json
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
{
|
3 |
+
"id": 1,
|
4 |
+
"name": "Kopi Arabika Premium",
|
5 |
+
"description": "Kopi Arabika pilihan dengan aroma khas yang lembut dan rasa yang nikmat.",
|
6 |
+
"price": 75000,
|
7 |
+
"files": ["img/product1a.jpg", "img/product1b.jpg", "vd/product1.mp4"]
|
8 |
+
},
|
9 |
+
{
|
10 |
+
"id": 2,
|
11 |
+
"name": "Teh Hijau Organik",
|
12 |
+
"description": "Teh hijau organik yang menyegarkan, cocok untuk kesehatan harian.",
|
13 |
+
"price": 50000,
|
14 |
+
"files": ["img/product2a.jpg", "img/product2b.jpg"]
|
15 |
+
},
|
16 |
+
{
|
17 |
+
"id": 3,
|
18 |
+
"name": "Biskuit Gandum Sehat",
|
19 |
+
"description": "Biskuit dengan bahan gandum alami, cocok untuk cemilan ringan.",
|
20 |
+
"price": 35000,
|
21 |
+
"files": ["img/product3a.jpg"]
|
22 |
+
},
|
23 |
+
{
|
24 |
+
"id": 4,
|
25 |
+
"name": "Minyak Zaitun Extra Virgin",
|
26 |
+
"description": "Minyak zaitun berkualitas tinggi, ideal untuk masakan dan salad.",
|
27 |
+
"price": 120000,
|
28 |
+
"files": ["img/product4a.jpg", "img/product4b.jpg"]
|
29 |
+
},
|
30 |
+
{
|
31 |
+
"id": 5,
|
32 |
+
"name": "Madu Hutan Murni",
|
33 |
+
"description": "Madu asli dari hutan tropis, kaya akan nutrisi dan antioksidan.",
|
34 |
+
"price": 90000,
|
35 |
+
"files": ["img/product5a.jpg"]
|
36 |
+
},
|
37 |
+
{
|
38 |
+
"id": 6,
|
39 |
+
"name": "Susu Almond tanpa Gula",
|
40 |
+
"description": "Susu almond alami, pilihan tepat untuk diet rendah gula.",
|
41 |
+
"price": 80000,
|
42 |
+
"files": ["img/product6a.jpg"]
|
43 |
+
}
|
44 |
+
]
|
database/users.json
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
{}
|
javascript/cart.js
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
-
size
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:554a4b012b01e288a7557b7b392a0ef3eff9bb422f196f413b0b3c8f449eb5e8
|
3 |
+
size 5615
|
javascript/component.js
CHANGED
@@ -1,298 +1,3 @@
|
|
1 |
-
|
2 |
-
|
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 |
-
});
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:ccafb2ce3c450f594c046457ee1febd3bca2a20fc07b22f120c60c314f1cfa2d
|
3 |
+
size 10808
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
javascript/data.js
CHANGED
@@ -1,204 +1,3 @@
|
|
1 |
-
|
2 |
-
|
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 |
-
});
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:fb444f036fd2376ca0f99f059b67ca1a923bf00f075e5d13ee72cdcec09a70fe
|
3 |
+
size 6758
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
javascript/main.js
CHANGED
@@ -1,504 +1,3 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
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 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() || {transactions: {}};
|
208 |
-
const transactions = userData.transactions || {};
|
209 |
-
|
210 |
-
if (Object.keys(transactions).length === 0) {
|
211 |
-
document.getElementById("transaction-list").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 |
-
let html = `
|
342 |
-
<div class="receipt-header">
|
343 |
-
<h2>TonS E-Commerce</h2>
|
344 |
-
<hr>
|
345 |
-
<p>Sidoarjo</p>
|
346 |
-
<p>Telp: 0896-6804-1554</p>
|
347 |
-
<p>Tanggal: ${td[0]} ${td[1]}</p>
|
348 |
-
</div>
|
349 |
-
<table class="receipt-table">
|
350 |
-
<thead>
|
351 |
-
<tr>
|
352 |
-
<th>No</th>
|
353 |
-
<th>Produk</th>
|
354 |
-
<th>Harga</th>
|
355 |
-
<th>Qty</th>
|
356 |
-
<th>Subtotal</th>
|
357 |
-
</tr>
|
358 |
-
</thead>
|
359 |
-
<tbody>
|
360 |
-
`;
|
361 |
-
|
362 |
-
transaction.products.forEach((prod, index) => {
|
363 |
-
const qty = prod.total;
|
364 |
-
const subtotal = prod.price * qty;
|
365 |
-
html += `
|
366 |
-
<tr>
|
367 |
-
<td>${index + 1}</td>
|
368 |
-
<td>${prod.name}</td>
|
369 |
-
<td>${formatRupiah(prod.price)}</td>
|
370 |
-
<td>${qty}</td>
|
371 |
-
<td>${formatRupiah(subtotal)}</td>
|
372 |
-
</tr>
|
373 |
-
`;
|
374 |
-
});
|
375 |
-
|
376 |
-
html += `
|
377 |
-
<tr class="total-row">
|
378 |
-
<td colspan="4" class="text-end"><strong>Total</strong></td>
|
379 |
-
<td><strong>${formatRupiah(total)}</strong></td>
|
380 |
-
</tr>
|
381 |
-
</tbody>
|
382 |
-
</table>
|
383 |
-
<div class="receipt-footer">
|
384 |
-
<p>Terima kasih telah berbelanja di TonS</p>
|
385 |
-
<p>Harap simpan struk ini sebagai bukti pembelian</p>
|
386 |
-
</div>
|
387 |
-
`;
|
388 |
-
|
389 |
-
detailsContainer.innerHTML = html;
|
390 |
-
|
391 |
-
const wrapper = document.createElement("div");
|
392 |
-
wrapper.className = "receipt-wrapper";
|
393 |
-
wrapper.innerHTML = detailsContainer.innerHTML;
|
394 |
-
detailsContainer.innerHTML = "";
|
395 |
-
detailsContainer.appendChild(wrapper);
|
396 |
-
|
397 |
-
await updateActionButtonsPosition();
|
398 |
-
|
399 |
-
window.addEventListener("resize", async () => {
|
400 |
-
requestAnimationFrame(updateActionButtonsPosition);
|
401 |
-
});
|
402 |
-
}
|
403 |
-
|
404 |
-
document.addEventListener("DOMContentLoaded", async function () {
|
405 |
-
AOS.init();
|
406 |
-
|
407 |
-
if (document.getElementById("product-list")) {
|
408 |
-
loadProducts();
|
409 |
-
}
|
410 |
-
|
411 |
-
if (document.getElementById("product-detail")) {
|
412 |
-
loadProductDetail();
|
413 |
-
}
|
414 |
-
|
415 |
-
if (document.getElementById("transaction-details")) {
|
416 |
-
await loadTransactionDetail();
|
417 |
-
|
418 |
-
await updateActionButtonsPosition();
|
419 |
-
|
420 |
-
window.addEventListener("resize", async () => {
|
421 |
-
requestAnimationFrame(updateActionButtonsPosition);
|
422 |
-
});
|
423 |
-
|
424 |
-
document
|
425 |
-
.getElementById("download-pdf")
|
426 |
-
.addEventListener("click", async () => {
|
427 |
-
const mainElement = document.getElementById(
|
428 |
-
"transaction-details"
|
429 |
-
);
|
430 |
-
const receiptWrapperElement =
|
431 |
-
document.querySelector(".receipt-wrapper");
|
432 |
-
|
433 |
-
const widthElement =
|
434 |
-
receiptWrapperElement.offsetWidth -
|
435 |
-
receiptWrapperElement.offsetLeft;
|
436 |
-
const heightElement =
|
437 |
-
receiptWrapperElement.offsetHeight -
|
438 |
-
receiptWrapperElement.offsetTop;
|
439 |
-
|
440 |
-
const params = new URLSearchParams(window.location.search);
|
441 |
-
const transactionKey = params.get("date");
|
442 |
-
|
443 |
-
try {
|
444 |
-
await html2pdf()
|
445 |
-
.from(mainElement)
|
446 |
-
.set({
|
447 |
-
margin: 10,
|
448 |
-
filename: `receipt_${transactionKey}.pdf`,
|
449 |
-
image: { type: "png", quality: 3 },
|
450 |
-
html2canvas: { scale: 3, useCORS: true },
|
451 |
-
jsPDF: {
|
452 |
-
unit: "px",
|
453 |
-
format: [widthElement, heightElement],
|
454 |
-
orientation: "portrait",
|
455 |
-
},
|
456 |
-
})
|
457 |
-
.toPdf()
|
458 |
-
.save();
|
459 |
-
} catch (error) {
|
460 |
-
console.error("Error generating PDF:", error);
|
461 |
-
}
|
462 |
-
});
|
463 |
-
|
464 |
-
document
|
465 |
-
.getElementById("print-receipt")
|
466 |
-
.addEventListener("click", async () => {
|
467 |
-
const mainElement = document.getElementById(
|
468 |
-
"transaction-details"
|
469 |
-
);
|
470 |
-
|
471 |
-
const lastMainElement = mainElement.innerHTML;
|
472 |
-
|
473 |
-
mainElement.innerHTML += `
|
474 |
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
|
475 |
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
|
476 |
-
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/aos.css" />
|
477 |
-
<script src="https://printjs-4de6.kxcdn.com/print.min.css"></script>
|
478 |
-
<link rel="stylesheet" href="stylesheet/styles.css">
|
479 |
-
|
480 |
-
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" defer></script>
|
481 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.2/html2pdf.bundle.min.js"></script>
|
482 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.13/html-to-image.js"></script>
|
483 |
-
<script src="https://unpkg.com/[email protected]/dist/aos.js" defer></script>
|
484 |
-
<script src="https://printjs-4de6.kxcdn.com/print.min.js"></script>
|
485 |
-
`;
|
486 |
-
|
487 |
-
await printJS("transaction-details", "html");
|
488 |
-
|
489 |
-
mainElement.innerHTML = lastMainElement;
|
490 |
-
});
|
491 |
-
}
|
492 |
-
|
493 |
-
if (document.getElementById("transaction-list")) {
|
494 |
-
loadTransactions();
|
495 |
-
}
|
496 |
-
|
497 |
-
let backButton = document.getElementById("backButton");
|
498 |
-
if (backButton) {
|
499 |
-
backButton.addEventListener("click", (e) => {
|
500 |
-
e.preventDefault();
|
501 |
-
window.history.back();
|
502 |
-
});
|
503 |
-
}
|
504 |
-
});
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:c49cc503278559ba38e16c88c98acda017015b9d52b827be8c27f4aa433d5b44
|
3 |
+
size 17180
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
javascript/payment.js
CHANGED
@@ -1,537 +1,3 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
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 |
-
photo_profile: userData.photo_profile,
|
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 = `cart.html?message=${encodeURIComponent("Anda belum memesan produk apapun. Pergi ke halaman produk untuk berbelanja")}`;
|
512 |
-
|
513 |
-
products = await fetchProductsData();
|
514 |
-
switch (step) {
|
515 |
-
case 1:
|
516 |
-
await displayStep1Content();
|
517 |
-
break;
|
518 |
-
case 2:
|
519 |
-
await displayStep2Content();
|
520 |
-
break;
|
521 |
-
case 3:
|
522 |
-
displayStep3Content();
|
523 |
-
break;
|
524 |
-
case 4:
|
525 |
-
await displayStep4Content();
|
526 |
-
break;
|
527 |
-
default:
|
528 |
-
displayStep1Content();
|
529 |
-
}
|
530 |
-
renderStepNav(step);
|
531 |
-
renderStepButtons(step);
|
532 |
-
}
|
533 |
-
|
534 |
-
document.addEventListener("DOMContentLoaded", function () {
|
535 |
-
AOS.init();
|
536 |
-
initPaymentSteps();
|
537 |
-
});
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:af939652b2ad35ead18d205c0ee0d69d5ce86bfe89b46d53f3f6f8c75713c993
|
3 |
+
size 16823
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
javascript/profile.js
CHANGED
@@ -1,260 +1,3 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
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.querySelectorAll("#googleSignInButton").forEach((elm) => elm.remove());
|
86 |
-
|
87 |
-
const urlNow = window.location.href;
|
88 |
-
const urlParams = new URLSearchParams(window.location.search);
|
89 |
-
const redirectUrl = urlParams.get("redirect") || urlNow;
|
90 |
-
const message =
|
91 |
-
urlParams.get("message") ||
|
92 |
-
"Silakan masuk ke akun Anda terlebih dahulu untuk melanjutkan:";
|
93 |
-
|
94 |
-
container.innerHTML = `
|
95 |
-
<p class="text-center" style="text-decoration: underline; font-style: italic;">${message}</p>
|
96 |
-
<span class="text-center m-4">Klik tombol di bawah ini untuk masuk:</span>
|
97 |
-
<div id="googleSignInButton"></div>
|
98 |
-
`;
|
99 |
-
|
100 |
-
if (message.trim()) alert(message, false);
|
101 |
-
|
102 |
-
async function loadGoogleAPI(callback) {
|
103 |
-
if (typeof google !== "undefined") {
|
104 |
-
callback();
|
105 |
-
return;
|
106 |
-
}
|
107 |
-
|
108 |
-
console.log("Memuat Google API...");
|
109 |
-
const script = document.createElement("script");
|
110 |
-
script.src = "https://accounts.google.com/gsi/client";
|
111 |
-
script.async = true;
|
112 |
-
script.defer = true;
|
113 |
-
|
114 |
-
script.onload = function () {
|
115 |
-
console.log("Google API berhasil dimuat.");
|
116 |
-
if (typeof google !== "undefined") {
|
117 |
-
callback();
|
118 |
-
} else {
|
119 |
-
console.error("Google API gagal dimuat.");
|
120 |
-
alert("Jika tombol masuk tidak tampil, silakan muat ulang halaman ini", false);
|
121 |
-
}
|
122 |
-
};
|
123 |
-
|
124 |
-
script.onerror = function () {
|
125 |
-
console.error("Gagal memuat Google API.");
|
126 |
-
alert("Gagal memuat Google Login. Coba gunakan browser lain atau muat ulang halaman ini.", false);
|
127 |
-
};
|
128 |
-
|
129 |
-
document.head.appendChild(script);
|
130 |
-
}
|
131 |
-
|
132 |
-
function initializeGoogleLogin() {
|
133 |
-
google.accounts.id.initialize({
|
134 |
-
client_id: CLIENT_ID,
|
135 |
-
callback: async (response) => {
|
136 |
-
await handleCredentialResponse(response);
|
137 |
-
window.location.href = redirectUrl;
|
138 |
-
},
|
139 |
-
auto_select: false,
|
140 |
-
button_auto_select: false,
|
141 |
-
scope: "email profile https://www.googleapis.com/auth/user.phonenumbers.read https://www.googleapis.com/auth/user.addresses.read",
|
142 |
-
});
|
143 |
-
|
144 |
-
google.accounts.id.prompt();
|
145 |
-
|
146 |
-
google.accounts.id.renderButton(
|
147 |
-
document.getElementById("googleSignInButton"),
|
148 |
-
{ theme: "outline", size: "large" }
|
149 |
-
);
|
150 |
-
}
|
151 |
-
|
152 |
-
loadGoogleAPI(() => {
|
153 |
-
initializeGoogleLogin();
|
154 |
-
});
|
155 |
-
}
|
156 |
-
|
157 |
-
async function initProfile() {
|
158 |
-
const userData = await getUserData();
|
159 |
-
if (userData && Object.keys(userData).length > 0) {
|
160 |
-
renderProfileForm(userData);
|
161 |
-
} else {
|
162 |
-
await renderGoogleLoginButton();
|
163 |
-
}
|
164 |
-
}
|
165 |
-
|
166 |
-
function renderProfileForm(userData) {
|
167 |
-
const container = document.getElementById("profile-container");
|
168 |
-
container.innerHTML = `
|
169 |
-
<form id="profileForm" class="fs-5">
|
170 |
-
<div class="mb-2 d-flex flex-row align-items-center justify-content-center">
|
171 |
-
<img id="photo_profile" class="img-fluid img-thumbnail rounded-circle" src=${userData.photo_profile}>
|
172 |
-
<input class="d-none" type="file" id="fileInput" accept="image/*">
|
173 |
-
</div>
|
174 |
-
<div class="mb-2">
|
175 |
-
<input class="form-control form-control-lg" type="text" id="name" placeholder="Nama" value="${
|
176 |
-
userData.name || ""
|
177 |
-
}" required>
|
178 |
-
</div>
|
179 |
-
<div class="mb-2">
|
180 |
-
<input class="form-control form-control-lg" type="email" id="email" placeholder="Email" value="${
|
181 |
-
userData.email || ""
|
182 |
-
}" required disabled>
|
183 |
-
</div>
|
184 |
-
<div class="mb-2">
|
185 |
-
<input class="form-control form-control-lg" type="text" id="phone" placeholder="No. HP" value="${
|
186 |
-
userData.phone || ""
|
187 |
-
}" required>
|
188 |
-
</div>
|
189 |
-
<div class="mb-2">
|
190 |
-
<input class="form-control form-control-lg" type="text" id="address" placeholder="Alamat" value="${
|
191 |
-
userData.address || ""
|
192 |
-
}" required>
|
193 |
-
</div>
|
194 |
-
<br>
|
195 |
-
<button class="btn btn-primary btn-lg" type="button" id="saveProfile">Simpan</button>
|
196 |
-
<br><br>
|
197 |
-
<button class="btn btn-danger btn-lg" type="button" id="logout">Keluar Akun</button>
|
198 |
-
</form>`;
|
199 |
-
|
200 |
-
document.getElementById("photo_profile").addEventListener("click", function () {
|
201 |
-
document.getElementById("fileInput").click();
|
202 |
-
});
|
203 |
-
|
204 |
-
document.getElementById("fileInput").addEventListener("change", function (event) {
|
205 |
-
const file = event.target.files[0];
|
206 |
-
if (file) {
|
207 |
-
if (file.size > 1 * 1024 * 1024) {
|
208 |
-
alert("Ukuran file terlalu besar! Maksimal 1MB.", false);
|
209 |
-
return;
|
210 |
-
}
|
211 |
-
|
212 |
-
const reader = new FileReader();
|
213 |
-
reader.onload = function (e) {
|
214 |
-
document.getElementById("photo_profile").src = e.target.result;
|
215 |
-
};
|
216 |
-
reader.readAsDataURL(file);
|
217 |
-
}
|
218 |
-
});
|
219 |
-
|
220 |
-
document
|
221 |
-
.getElementById("saveProfile")
|
222 |
-
.addEventListener("click", async function () {
|
223 |
-
const updatedUser = {
|
224 |
-
uid: userData.uid,
|
225 |
-
name: document.getElementById("name").value.trim(),
|
226 |
-
email: userData.email,
|
227 |
-
phone: document.getElementById("phone").value.trim(),
|
228 |
-
address: document.getElementById("address").value.trim(),
|
229 |
-
photo_profile: document.getElementById("photo_profile").src,
|
230 |
-
cart: userData.cart ? userData.cart : [],
|
231 |
-
transactions: userData.transactions
|
232 |
-
? userData.transactions
|
233 |
-
: [],
|
234 |
-
};
|
235 |
-
|
236 |
-
if (
|
237 |
-
!updatedUser.name ||
|
238 |
-
!updatedUser.email ||
|
239 |
-
!updatedUser.phone ||
|
240 |
-
!updatedUser.address
|
241 |
-
) {
|
242 |
-
alert("Semua input wajib diisi!", false);
|
243 |
-
return;
|
244 |
-
}
|
245 |
-
|
246 |
-
localStorage.setItem("userData", JSON.stringify(updatedUser));
|
247 |
-
await updateUserDataToGitHub(updatedUser);
|
248 |
-
alert("Profil berhasil disimpan!", false);
|
249 |
-
});
|
250 |
-
|
251 |
-
document.getElementById("logout").addEventListener("click", function () {
|
252 |
-
localStorage.removeItem("userData");
|
253 |
-
window.location.reload();
|
254 |
-
});
|
255 |
-
}
|
256 |
-
|
257 |
-
document.addEventListener("DOMContentLoaded", function () {
|
258 |
-
AOS.init();
|
259 |
-
initProfile();
|
260 |
-
});
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:748d9acdda8605df3dc2a5729582c24ea33535659bbd79c7ec29cc25a462ace0
|
3 |
+
size 9334
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
javascript/search.js
CHANGED
@@ -1,247 +1,3 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
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 = "Tidak menemukan hasil pencarian yang sesuai.";
|
17 |
-
messageElem.className = "text-center";
|
18 |
-
resultList.appendChild(messageElem);
|
19 |
-
return;
|
20 |
-
}
|
21 |
-
|
22 |
-
const searchInput = document.getElementById("searchInput")
|
23 |
-
if (searchInput) searchInput.value = (productQuery ? productQuery : "") + " " + (transactionQuery ? transactionQuery : "");
|
24 |
-
|
25 |
-
if (productQuery) {
|
26 |
-
const products = await fetchProductsData();
|
27 |
-
|
28 |
-
const filteredProducts = products.filter((product) => {
|
29 |
-
const lowerQuery = productQuery.toLowerCase();
|
30 |
-
return (
|
31 |
-
product.name.toLowerCase().includes(lowerQuery) ||
|
32 |
-
(product.description &&
|
33 |
-
product.description.toLowerCase().includes(lowerQuery))
|
34 |
-
);
|
35 |
-
});
|
36 |
-
|
37 |
-
if (filteredProducts.length > 0) {
|
38 |
-
createHeader("Produk");
|
39 |
-
filteredProducts.forEach((product) => {
|
40 |
-
const col = document.createElement("div");
|
41 |
-
col.className = "col-lg-3 col-md-4 col-sm-6";
|
42 |
-
|
43 |
-
const card = document.createElement("div");
|
44 |
-
card.className = "card h-100";
|
45 |
-
|
46 |
-
let thumbnail = "assets/";
|
47 |
-
if (product.files && product.files.length > 0) {
|
48 |
-
thumbnail += product.files[0];
|
49 |
-
}
|
50 |
-
let thumbHTML = "";
|
51 |
-
if (thumbnail.match(/\.(jpg|jpeg|png|gif)$/i)) {
|
52 |
-
thumbHTML = `<img src="${thumbnail}" class="card-img-top" alt="${product.name}">`;
|
53 |
-
} else {
|
54 |
-
thumbHTML = `<img src="img/placeholder.jpg" class="card-img-top" alt="${product.name}">`;
|
55 |
-
}
|
56 |
-
|
57 |
-
const cardBody = document.createElement("div");
|
58 |
-
cardBody.className = "card-body d-flex flex-column";
|
59 |
-
|
60 |
-
const title = document.createElement("h5");
|
61 |
-
title.className = "card-title";
|
62 |
-
title.innerText = product.name;
|
63 |
-
|
64 |
-
const desc = document.createElement("p");
|
65 |
-
desc.className = "card-text";
|
66 |
-
desc.innerText = product.description;
|
67 |
-
|
68 |
-
const price = document.createElement("p");
|
69 |
-
price.className = "card-text fw-bold";
|
70 |
-
price.innerText = formatRupiah(product.price);
|
71 |
-
|
72 |
-
const detailLink = document.createElement("a");
|
73 |
-
detailLink.href = "product.html?id=" + product.id;
|
74 |
-
detailLink.className = "btn btn-primary btn-lg mt-auto";
|
75 |
-
detailLink.innerText = "Lihat Detail";
|
76 |
-
|
77 |
-
const cartBtn = document.createElement("button");
|
78 |
-
cartBtn.className = "btn btn-success btn-lg mt-2";
|
79 |
-
cartBtn.innerText = "Tambah Keranjang";
|
80 |
-
cartBtn.onclick = async function () {
|
81 |
-
await promptQuantityAndAdd(product.id);
|
82 |
-
};
|
83 |
-
|
84 |
-
card.innerHTML = thumbHTML;
|
85 |
-
cardBody.appendChild(title);
|
86 |
-
cardBody.appendChild(desc);
|
87 |
-
cardBody.appendChild(price);
|
88 |
-
cardBody.appendChild(detailLink);
|
89 |
-
cardBody.appendChild(cartBtn);
|
90 |
-
card.appendChild(cardBody);
|
91 |
-
col.appendChild(card);
|
92 |
-
resultList.appendChild(col);
|
93 |
-
});
|
94 |
-
} else {
|
95 |
-
const messageElem = document.createElement("p");
|
96 |
-
messageElem.textContent = "Produk tidak ditemukan.";
|
97 |
-
messageElem.className = "text-center";
|
98 |
-
resultList.appendChild(messageElem);
|
99 |
-
}
|
100 |
-
}
|
101 |
-
|
102 |
-
if (productQuery && transactionQuery) {
|
103 |
-
const hrElement = document.createElement("hr");
|
104 |
-
resultList.appendChild(hrElement);
|
105 |
-
}
|
106 |
-
|
107 |
-
if (transactionQuery) {
|
108 |
-
const userData = await getUserData() || {transactions: {}};
|
109 |
-
const transactions = userData.transactions || {};
|
110 |
-
|
111 |
-
if (Object.keys(transactions).length > 0) {
|
112 |
-
const entries = Object.entries(transactions).sort((a, b) => {
|
113 |
-
const parseDateFromKey = (key) => {
|
114 |
-
const [year, month, day, hour, minute, second, ms] = key.split("_");
|
115 |
-
return new Date(year, month - 1, day, hour, minute, second, ms);
|
116 |
-
};
|
117 |
-
return parseDateFromKey(b[0]) - parseDateFromKey(a[0]);
|
118 |
-
});
|
119 |
-
|
120 |
-
const filteredTransactions = entries.filter(([key, transaction]) => {
|
121 |
-
const cartItems = transaction.products;
|
122 |
-
let productCount = {};
|
123 |
-
|
124 |
-
cartItems.forEach((product) => {
|
125 |
-
const id = product.id;
|
126 |
-
productCount[id] = (productCount[id] || 0) + 1;
|
127 |
-
});
|
128 |
-
|
129 |
-
const td = formatTransactionDate(key);
|
130 |
-
const pm = transaction.payment_method;
|
131 |
-
|
132 |
-
let total = 0;
|
133 |
-
for (let id in productCount) {
|
134 |
-
const prod = products.find((p) => p.id == id);
|
135 |
-
if (prod) {
|
136 |
-
const qty = productCount[id];
|
137 |
-
const subtotal = prod.price * qty;
|
138 |
-
total += subtotal;
|
139 |
-
}
|
140 |
-
}
|
141 |
-
|
142 |
-
const lowerQuery = transactionQuery.toLowerCase();
|
143 |
-
|
144 |
-
return (
|
145 |
-
key.toLowerCase().includes(lowerQuery) ||
|
146 |
-
td[0].toLowerCase().includes(lowerQuery) ||
|
147 |
-
td[1].toLowerCase.includes(lowerQuery) ||
|
148 |
-
pm.toLowerCase.includes(lowerQuery) ||
|
149 |
-
total.toString().includes(transactionQuery) ||
|
150 |
-
Object.values(productCount).join(",").includes(transactionQuery)
|
151 |
-
);
|
152 |
-
});
|
153 |
-
|
154 |
-
if (filteredTransactions.length > 0) {
|
155 |
-
createHeader("Transaksi");
|
156 |
-
|
157 |
-
filteredTransactions.forEach(([transactionKey, transaction]) => {
|
158 |
-
const col = document.createElement("div");
|
159 |
-
col.className = "transactions-body col-lg-4 col-md-6 col-sm-12 mt-4";
|
160 |
-
|
161 |
-
const card = document.createElement("div");
|
162 |
-
card.className = "card h-100";
|
163 |
-
|
164 |
-
const cardBody = document.createElement("div");
|
165 |
-
cardBody.className = "card-body d-flex flex-column";
|
166 |
-
cardBody.style.position = "relative";
|
167 |
-
|
168 |
-
const td = formatTransactionDate(transactionKey);
|
169 |
-
|
170 |
-
const cartItems = transaction.products;
|
171 |
-
let productCount = {};
|
172 |
-
|
173 |
-
cartItems.forEach((product) => {
|
174 |
-
const id = product.id;
|
175 |
-
productCount[id] = (productCount[id] || 0) + 1;
|
176 |
-
});
|
177 |
-
|
178 |
-
let total = 0;
|
179 |
-
for (let id in productCount) {
|
180 |
-
const prod = products.find((p) => p.id == id);
|
181 |
-
if (prod) {
|
182 |
-
const qty = productCount[id];
|
183 |
-
const subtotal = prod.price * qty;
|
184 |
-
total += subtotal;
|
185 |
-
}
|
186 |
-
}
|
187 |
-
|
188 |
-
let paymentIcon = "";
|
189 |
-
switch (transaction.payment_method) {
|
190 |
-
case "bank":
|
191 |
-
paymentIcon = "bi-building";
|
192 |
-
break;
|
193 |
-
case "qr":
|
194 |
-
paymentIcon = "bi-qr-code";
|
195 |
-
break;
|
196 |
-
case "card":
|
197 |
-
paymentIcon = "bi-credit-card";
|
198 |
-
break;
|
199 |
-
default:
|
200 |
-
paymentIcon = "bi-currency-dollar";
|
201 |
-
}
|
202 |
-
|
203 |
-
cardBody.innerHTML = `
|
204 |
-
<div class="payment-icon-bg">
|
205 |
-
<i class="bi ${paymentIcon}" style="font-size: 2.5rem;"></i>
|
206 |
-
<span class="m-2">Metode: ${
|
207 |
-
transaction.payment_method ? transaction.payment_method : "N/A"
|
208 |
-
}</span>
|
209 |
-
</div>
|
210 |
-
<div class="content">
|
211 |
-
<div class="d-flex justify-content-between align-items-center">
|
212 |
-
<div class="transaction-total fw-bold">
|
213 |
-
Total: ${formatRupiah(total)}
|
214 |
-
</div>
|
215 |
-
</div>
|
216 |
-
<div class="d-flex justify-content-between align-items-center mt-auto">
|
217 |
-
<div class="transaction-date">
|
218 |
-
Tanggal: ${td[0]} <br> Waktu: ${td[1]}
|
219 |
-
</div>
|
220 |
-
</div>
|
221 |
-
</div>
|
222 |
-
`;
|
223 |
-
|
224 |
-
card.appendChild(cardBody);
|
225 |
-
col.appendChild(card);
|
226 |
-
resultList.appendChild(col);
|
227 |
-
|
228 |
-
col.addEventListener("click", () => {
|
229 |
-
window.location.href = `transaction.html?date=${transactionKey}`;
|
230 |
-
});
|
231 |
-
});
|
232 |
-
} else {
|
233 |
-
const messageElem = document.createElement("p");
|
234 |
-
messageElem.textContent = "Transaksi tidak ditemukan.";
|
235 |
-
messageElem.className = "text-center";
|
236 |
-
resultList.appendChild(messageElem);
|
237 |
-
alert(messageElem.textContent, false);
|
238 |
-
}
|
239 |
-
} else {
|
240 |
-
const messageElem = document.createElement("p");
|
241 |
-
messageElem.textContent = "Anda belum melakukan transaksi apapun. Silahkan beli produk terlebih dahulu.";
|
242 |
-
messageElem.className = "text-center";
|
243 |
-
resultList.appendChild(messageElem);
|
244 |
-
alert(messageElem.textContent, false);
|
245 |
-
}
|
246 |
-
}
|
247 |
-
});
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:1499e9fd42d573c7095c3266aedbe8971270ec9b26cd8a08037611403c4fb068
|
3 |
+
size 11077
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
python/file.bat
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
@echo off
|
2 |
+
python nama_file.py
|
3 |
+
pause
|
python/project2flow.py
ADDED
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import re
|
3 |
+
import sys
|
4 |
+
from graphviz import Digraph
|
5 |
+
import esprima
|
6 |
+
|
7 |
+
def detect_file_dependencies(root_dir):
|
8 |
+
"""
|
9 |
+
Menelusuri semua file dalam root_dir dan mendeteksi dependency sederhana.
|
10 |
+
Jika sebuah file (HTML) menautkan file lain (JS/CSS) melalui tag <script> atau <link>,
|
11 |
+
maka dependency tersebut dicatat.
|
12 |
+
"""
|
13 |
+
dep_graph = {}
|
14 |
+
for subdir, _, files in os.walk(root_dir):
|
15 |
+
for file in files:
|
16 |
+
ext = os.path.splitext(file)[1].lower()
|
17 |
+
if ext in ['.html', '.js', '.css']:
|
18 |
+
filepath = os.path.join(subdir, file)
|
19 |
+
try:
|
20 |
+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
21 |
+
content = f.read()
|
22 |
+
except Exception as e:
|
23 |
+
print(f"Error reading {filepath}: {e}")
|
24 |
+
continue
|
25 |
+
|
26 |
+
dependencies = []
|
27 |
+
if ext == '.html':
|
28 |
+
scripts = re.findall(r'<script\s+[^>]*src=["\'](.*?)["\']', content, re.IGNORECASE)
|
29 |
+
links = re.findall(r'<link\s+[^>]*href=["\'](.*?)["\']', content, re.IGNORECASE)
|
30 |
+
dependencies.extend(scripts)
|
31 |
+
dependencies.extend(links)
|
32 |
+
dep_graph[filepath] = dependencies
|
33 |
+
return dep_graph
|
34 |
+
|
35 |
+
def build_file_dependency_flowchart(dep_graph, output_file='results/project_file_dependencies'):
|
36 |
+
"""
|
37 |
+
Membuat diagram flowchart dependency antar file menggunakan Graphviz.
|
38 |
+
Jika string dependency (misalnya, 'script.js') ditemukan dalam nama file lain, dibuat edge.
|
39 |
+
"""
|
40 |
+
dot = Digraph(comment='Project File Dependencies')
|
41 |
+
for file in dep_graph:
|
42 |
+
dot.node(file, os.path.basename(file))
|
43 |
+
for file, deps in dep_graph.items():
|
44 |
+
for dep in deps:
|
45 |
+
for candidate in dep_graph:
|
46 |
+
if dep in os.path.basename(candidate):
|
47 |
+
dot.edge(file, candidate)
|
48 |
+
dot.render(output_file, view=True)
|
49 |
+
print(f"Diagram dependency file disimpan sebagai {output_file}.pdf")
|
50 |
+
|
51 |
+
def generate_full_js_flowchart(js_file):
|
52 |
+
"""
|
53 |
+
Menghasilkan flowchart yang mencoba mengidentifikasi seluruh _syntax_ yang mempengaruhi
|
54 |
+
alur kontrol pada file JavaScript dengan mengurai AST menggunakan esprima.
|
55 |
+
Node-node yang dihasilkan mencakup FunctionDeclaration, IfStatement, loop, SwitchStatement,
|
56 |
+
ReturnStatement, CallExpression, dan jenis node lainnya.
|
57 |
+
"""
|
58 |
+
try:
|
59 |
+
with open(js_file, 'r', encoding='utf-8') as f:
|
60 |
+
code = f.read()
|
61 |
+
except Exception as e:
|
62 |
+
print(f"Error reading {js_file}: {e}")
|
63 |
+
return None
|
64 |
+
|
65 |
+
try:
|
66 |
+
ast = esprima.parseScript(code, tolerant=True)
|
67 |
+
except Exception as e:
|
68 |
+
print(f"Error parsing {js_file}: {e}")
|
69 |
+
return None
|
70 |
+
|
71 |
+
flowchart = Digraph(comment=f'Full JS Flowchart: {os.path.basename(js_file)}')
|
72 |
+
node_counter = 0
|
73 |
+
|
74 |
+
def new_node(label):
|
75 |
+
nonlocal node_counter
|
76 |
+
node_id = f"node{node_counter}"
|
77 |
+
node_counter += 1
|
78 |
+
flowchart.node(node_id, label)
|
79 |
+
return node_id
|
80 |
+
|
81 |
+
def traverse_full(node, parent_node=None):
|
82 |
+
if isinstance(node, dict):
|
83 |
+
node_type = node.get('type')
|
84 |
+
current_node = None
|
85 |
+
|
86 |
+
if node_type == 'FunctionDeclaration':
|
87 |
+
func_name = node.get('id', {}).get('name', 'anonymous')
|
88 |
+
current_node = new_node(f'Function: {func_name}')
|
89 |
+
elif node_type == 'IfStatement':
|
90 |
+
current_node = new_node('If Statement')
|
91 |
+
elif node_type in ['ForStatement', 'WhileStatement', 'DoWhileStatement']:
|
92 |
+
current_node = new_node(node_type)
|
93 |
+
elif node_type == 'SwitchStatement':
|
94 |
+
current_node = new_node('Switch Statement')
|
95 |
+
elif node_type == 'ReturnStatement':
|
96 |
+
current_node = new_node('Return')
|
97 |
+
elif node_type == 'CallExpression':
|
98 |
+
callee = node.get('callee')
|
99 |
+
if callee and callee.get('type') == 'Identifier':
|
100 |
+
current_node = new_node(f'Call: {callee.get("name")}')
|
101 |
+
|
102 |
+
else:
|
103 |
+
|
104 |
+
current_node = new_node(node_type)
|
105 |
+
|
106 |
+
if parent_node and current_node:
|
107 |
+
flowchart.edge(parent_node, current_node)
|
108 |
+
new_parent = current_node if current_node else parent_node
|
109 |
+
for key, value in node.items():
|
110 |
+
|
111 |
+
if key in ['loc', 'range', 'raw']:
|
112 |
+
continue
|
113 |
+
traverse_full(value, parent_node=new_parent)
|
114 |
+
elif isinstance(node, list):
|
115 |
+
for item in node:
|
116 |
+
traverse_full(item, parent_node=parent_node)
|
117 |
+
|
118 |
+
traverse_full(ast.toDict(), parent_node=None)
|
119 |
+
return flowchart
|
120 |
+
|
121 |
+
def build_all_full_js_flowcharts(root_dir, output_folder='results/full_js_flowcharts'):
|
122 |
+
"""
|
123 |
+
Menelusuri seluruh file dalam root_dir, dan untuk setiap file JS menghasilkan flowchart
|
124 |
+
lengkap dari seluruh syntax yang mempengaruhi alur. Flowchart disimpan di output_folder.
|
125 |
+
"""
|
126 |
+
if not os.path.exists(output_folder):
|
127 |
+
os.makedirs(output_folder)
|
128 |
+
for subdir, _, files in os.walk(root_dir):
|
129 |
+
for file in files:
|
130 |
+
ext = os.path.splitext(file)[1].lower()
|
131 |
+
if ext == '.js':
|
132 |
+
js_file = os.path.join(subdir, file)
|
133 |
+
print(f"Memproses {js_file} untuk flowchart lengkap...")
|
134 |
+
flowchart = generate_full_js_flowchart(js_file)
|
135 |
+
if flowchart:
|
136 |
+
base_name = os.path.splitext(os.path.basename(js_file))[0]
|
137 |
+
output_name = os.path.join(output_folder, base_name + '_full_flowchart')
|
138 |
+
flowchart.render(output_name, view=False)
|
139 |
+
print(f"Flowchart lengkap untuk {js_file} disimpan sebagai {output_name}.pdf")
|
140 |
+
|
141 |
+
if __name__ == '__main__':
|
142 |
+
root_directory = '/content/drive/MyDrive/Developer/Project/Competition/Alhazen Coding Quest 20 Days Ramadhan Challenge/FINAL PROJECT'
|
143 |
+
|
144 |
+
dependency_graph = detect_file_dependencies(root_directory)
|
145 |
+
print("Dependency Graph Antar File:")
|
146 |
+
for file, deps in dependency_graph.items():
|
147 |
+
print(f"{file}: {deps}")
|
148 |
+
build_file_dependency_flowchart(dependency_graph)
|
149 |
+
|
150 |
+
build_all_full_js_flowcharts(root_directory)
|
151 |
+
|
152 |
+
sys.stdout.flush()
|
153 |
+
input("Tekan Enter untuk keluar...")
|
results/js_flowcharts/cart_full_flowchart
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:20f13d2a001221c75cb5fa270970c241672cf012726ae1f40c2899a0b13c363c
|
3 |
+
size 24372
|
results/js_flowcharts/cart_full_flowchart.pdf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:086916f09bd13ce18580b1c10a2622eff78ec0c5e8e03721eb6aeb2ccb0fc8ef
|
3 |
+
size 80446
|
results/js_flowcharts/component_full_flowchart
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a40f95034ef13d50827c4a4ae76b6456b6e5b8520e08697814e9483ef8a2a4ca
|
3 |
+
size 50818
|
results/js_flowcharts/component_full_flowchart.pdf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:fab6a77e0cfdd260526ba879a8fb6681c6a37b9a758f8c49ad79c662900df0cd
|
3 |
+
size 141264
|
results/js_flowcharts/data_full_flowchart
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:be0c1a32bd3b687636cbae03b6c1b40c4079928a6395aae6f5a194e7f8b97409
|
3 |
+
size 25828
|
results/js_flowcharts/data_full_flowchart.pdf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a29b0425ea5a9fa4f230eb71bfdaf21107098acbda4510f4407593ff5d03ac45
|
3 |
+
size 84382
|
results/js_flowcharts/main_full_flowchart
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:f938cfb8f5e7c0da3e5b1a96ac83ee089a9301f0e2175458b184772e7968fcfc
|
3 |
+
size 88049
|
results/js_flowcharts/main_full_flowchart.pdf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:3567a06143f5dba0930e389b7e81c52989a23ee3628068c4627971fcdae1dd66
|
3 |
+
size 236883
|
results/js_flowcharts/payment_full_flowchart
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:b3d7798262eabcfc262bb65554ab253b4a413794eb5c0ac1e15de511ae7622a4
|
3 |
+
size 85162
|
results/js_flowcharts/payment_full_flowchart.pdf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:11a00c87ef0467e74e162312fc268e087d7da301114f6235cee961462c3f4253
|
3 |
+
size 231942
|
results/js_flowcharts/profile_full_flowchart
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a97b06c534fb12e215e59ecde952040ea2755c4e49b01c91eb710347b6d0d3c5
|
3 |
+
size 34133
|
results/js_flowcharts/profile_full_flowchart.pdf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:875fbd29734d8008429fe11214dd40022f8ebd18c9f7af4a91c9323b2f7ed667
|
3 |
+
size 106216
|
results/js_flowcharts/search_full_flowchart
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:d6f44e0130ff01d6d7846ab231334e2aa3cbded22fc378932c81c1c6c46e7f56
|
3 |
+
size 51019
|
results/js_flowcharts/search_full_flowchart.pdf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:5d8c3c06364d34083dee8a4f160dccd19bb5d2bb66dca5a00333f10d1bf11448
|
3 |
+
size 147654
|
results/project_file_dependencies
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:acca815340a7ffa0806d1a086b213b9fd80d3815472f227c53b84ab4919e189d
|
3 |
+
size 2551
|
results/project_file_dependencies.pdf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:91e4970b1526c193934391af3bad3b8c2743bf70980c72cdec2a6a8c2b159f8d
|
3 |
+
size 11324
|