Adityadn commited on
Commit
7a21841
·
verified ·
1 Parent(s): ca08ea8

Upload 51 files

Browse files
.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:6b89f15f0d9069982e9d156072572188a88601143e1877aa1cc630e2fc84139f
3
- size 5529
 
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
- const urlNow = window.location.href;
2
- const header = document.querySelector("header");
3
-
4
- header.className = "shadow-sm py-3 fixed-top";
5
- header.innerHTML = `
6
- <div class="container d-flex flex-column align-items-center mt-2">
7
- <form id="searchForm" class="d-flex align-items-center w-75" style="max-width: 600px;">
8
- <input id="searchInput" class="form-control form-control-lg" type="search" placeholder="${
9
- urlNow.includes("transaction")
10
- ? "Cari transaksi..."
11
- : "Cari produk..."
12
- }" aria-label="Search">
13
- <label for="searchInput" class="m-1" id="labelSearchInput">
14
- <i class="bi bi-search me-2 text-secondary btn btn-light btn-lg"></i>
15
- </label>
16
- </form>
17
- </div>
18
- `;
19
-
20
- function searchForm() {
21
- const searchInputElm = document.getElementById("searchInput");
22
- const value = searchInputElm.value.trim();
23
- let nextSearch = false;
24
- value ? (nextSearch = true) : searchInputElm.focus();
25
-
26
- if (nextSearch)
27
- window.location.href =
28
- "search.html?" +
29
- (urlNow.includes("transaction") ? "transactions=" : "products=") +
30
- value;
31
- }
32
-
33
- document
34
- .getElementById("labelSearchInput")
35
- .addEventListener("click", () => searchForm());
36
-
37
- document.getElementById("searchForm").addEventListener("submit", (e) => {
38
- e.preventDefault();
39
- searchForm();
40
- });
41
-
42
- const footer = document.querySelector("footer");
43
-
44
- footer.className = "py-3 fixed-bottom shadow-sm";
45
- footer.innerHTML = `
46
- <div class="container d-flex justify-content-around">
47
- <a href="index.html" class="btn btn-warning btn-lg d-flex flex-column align-items-center">
48
- <i class="bi bi-bag"></i>
49
- <span class="m-1">
50
- <span class="nd-720">Cari</span>
51
- <span class="nd-512">Produk</span>
52
- </span>
53
- </a>
54
- <a href="transactions.html" class="btn btn-warning btn-lg d-flex flex-column align-items-center">
55
- <i class="bi bi-receipt"></i>
56
- <span class="m-1">
57
- <span class="nd-720">Cek</span>
58
- <span class="nd-512">Transaksi</span>
59
- </span>
60
- </a>
61
- <a id="cart-icon" href="cart.html" class="btn btn-primary btn-lg d-flex align-items-center">
62
- <i class="bi bi-cart"></i>
63
- <span class="m-1">
64
- <span class="nd-720">Keranjang</span>
65
- <span class="nd-512">(0)</span>
66
- </span>
67
- </a>
68
- <a href="profile.html" class="btn btn-secondary btn-lg d-flex flex-column align-items-center">
69
- <i class="bi bi-person-circle"></i>
70
- <span class="nd-512">Profil</span>
71
- </a>
72
- </div>
73
- `;
74
-
75
- const backAreaButton = document.getElementById("backAreaButton");
76
- if (backAreaButton) {
77
- backAreaButton.innerHTML = `
78
- <div class="btn btn-primary btn-lg mb-4" id="backButton">
79
- <i class="bi bi-arrow-left"></i> <span class="nd-512">Kembali</span>
80
- </div>
81
- <br><br>
82
- `;
83
- }
84
-
85
- async function alert(
86
- message = "",
87
- isPopUp = true,
88
- isTime = false,
89
- timeOut = 5,
90
- messagesAfterTimeOut = [],
91
- awaitResolve = 0
92
- ) {
93
- return new Promise((resolve) => {
94
- try {
95
- let modal;
96
- let messageElem;
97
- let timeOutElm;
98
- let okBtn;
99
- let intervalId;
100
-
101
- if (isPopUp) {
102
- modal = document.createElement("div");
103
- modal.className = "modal";
104
- modal.style.position = "fixed";
105
- modal.style.top = "0";
106
- modal.style.left = "0";
107
- modal.style.width = "100%";
108
- modal.style.height = "100%";
109
- modal.style.backgroundColor = "rgba(0,0,0,0.5)";
110
- modal.style.display = "flex";
111
- modal.style.alignItems = "center";
112
- modal.style.justifyContent = "center";
113
- } else {
114
- modal = document.createElement("div");
115
- modal.className = "notification";
116
- modal.style.position = "fixed";
117
- modal.style.top = "5%";
118
- modal.style.right = "2.5%";
119
- modal.style.padding = "10px 20px";
120
- modal.style.borderRadius = "5px";
121
- modal.style.zIndex = "1000000000";
122
- modal.setAttribute("data-aos", "fade-left");
123
- }
124
-
125
- const modalContent = document.createElement("div");
126
- modalContent.className =
127
- "modal-content d-flex align-items-center bg-warning p-3";
128
- modalContent.style.width = isPopUp ? "75%" : "100%";
129
- modalContent.style.height = isPopUp ? "" : "100%";
130
- modalContent.style.borderRadius = "5px";
131
- modalContent.style.boxShadow = isPopUp
132
- ? "0 2px 10px rgba(0,0,0,0.1)"
133
- : "";
134
-
135
- messageElem = document.createElement("p");
136
- messageElem.innerHTML =
137
- (!isPopUp ? `<i class="bi bi-bell-fill m-2"></i>` : ``) +
138
- message;
139
-
140
- timeOutElm = document.createElement("p");
141
-
142
- okBtn = document.createElement("button");
143
- okBtn.className = "btn btn-primary btn-lg";
144
- okBtn.textContent = "OK";
145
- okBtn.style.minWidth = "80px";
146
- okBtn.style.display = isTime ? "none" : "block";
147
- okBtn.style.marginTop = "10px";
148
-
149
- modalContent.appendChild(messageElem);
150
-
151
- if (isTime) {
152
- modalContent.appendChild(timeOutElm);
153
- }
154
-
155
- if (isPopUp) {
156
- const btnContainer = document.createElement("div");
157
- btnContainer.style.marginTop = "20px";
158
- btnContainer.appendChild(okBtn);
159
- modalContent.appendChild(btnContainer);
160
- }
161
-
162
- modal.appendChild(modalContent);
163
- document.body.appendChild(modal);
164
-
165
- intervalId = setInterval(async () => {
166
- timeOut--;
167
- console.log(timeOut);
168
- timeOutElm.textContent = timeOut;
169
- if (messagesAfterTimeOut.length > 0) {
170
- messagesAfterTimeOut.forEach((mes) => {
171
- if (mes.timeOut >= timeOut) {
172
- modalContent.className =
173
- "modal-content d-flex align-items-center bg-warning p-3";
174
- messageElem.textContent = mes.message;
175
- }
176
- });
177
- }
178
-
179
- if (timeOut <= 0) {
180
- await new Promise((res) => setTimeout(res, awaitResolve));
181
- if (document.body.contains(modal)) {
182
- document.body.removeChild(modal);
183
- }
184
- clearInterval(intervalId);
185
- resolve();
186
- }
187
- }, 1000);
188
-
189
- okBtn.addEventListener("click", function () {
190
- if (intervalId) clearInterval(intervalId);
191
- if (document.body.contains(modal)) {
192
- document.body.removeChild(modal);
193
- }
194
- resolve();
195
- });
196
-
197
- if (!isPopUp) {
198
- setTimeout(() => {
199
- if (document.body.contains(modal)) {
200
- document.body.removeChild(modal);
201
- }
202
- resolve();
203
- }, timeOut * 1000);
204
- }
205
- } catch (e) {
206
- console.error(e);
207
- }
208
- });
209
- }
210
-
211
- async function prompt(message = "", type = "text", placeholder = "") {
212
- return new Promise((resolve) => {
213
- const modal = document.createElement("div");
214
- modal.className = "modal";
215
- modal.style.position = "fixed";
216
- modal.style.top = 0;
217
- modal.style.left = 0;
218
- modal.style.width = "100%";
219
- modal.style.height = "100%";
220
- modal.style.backgroundColor = "rgba(0,0,0,0.5)";
221
- modal.style.display = "flex";
222
- modal.style.alignItems = "center";
223
- modal.style.justifyContent = "center";
224
-
225
- const modalContent = document.createElement("div");
226
- modalContent.className = "modal-content";
227
- modalContent.style.width = "75%";
228
- modalContent.style.background = "#fff";
229
- modalContent.style.padding = "20px";
230
- modalContent.style.borderRadius = "5px";
231
- modalContent.style.boxShadow = "0 2px 10px rgba(0,0,0,0.1)";
232
-
233
- const messageElem = document.createElement("p");
234
- messageElem.textContent = message;
235
-
236
- const input = document.createElement("input");
237
- input.type = type;
238
- input.required = "true";
239
- input.style.width = "100%";
240
- input.style.marginTop = "10px";
241
- input.placeholder = placeholder;
242
- input.className = "form-control form-control-lg";
243
-
244
- const btnContainer = document.createElement("div");
245
- btnContainer.style.marginTop = "20px";
246
- btnContainer.style.textAlign = "right";
247
-
248
- const okBtn = document.createElement("button");
249
- okBtn.className = "btn btn-success btn-lg";
250
- okBtn.textContent = "OK";
251
- okBtn.style.marginRight = "10px";
252
-
253
- const cancelBtn = document.createElement("button");
254
- cancelBtn.className = "btn btn-danger btn-lg";
255
- cancelBtn.textContent = "Batal";
256
-
257
- btnContainer.appendChild(okBtn);
258
- btnContainer.appendChild(cancelBtn);
259
-
260
- modalContent.appendChild(messageElem);
261
- modalContent.appendChild(input);
262
- modalContent.appendChild(btnContainer);
263
- modal.appendChild(modalContent);
264
- document.body.appendChild(modal);
265
-
266
- input.addEventListener("click", () => {
267
- input.removeAttribute("focus");
268
- input.removeAttribute("invalid");
269
- input.removeAttribute("style");
270
- });
271
-
272
- okBtn.addEventListener("click", function () {
273
- const value = input.value;
274
- if (input.value.trim().length > 0) {
275
- document.body.removeChild(modal);
276
- resolve([value, false]);
277
- } else {
278
- input.setAttribute("focus", true);
279
- input.setAttribute("invalid", true);
280
- input.style.border = "1px solid red";
281
- input.focus();
282
- }
283
- });
284
-
285
- cancelBtn.addEventListener("click", function () {
286
- document.body.removeChild(modal);
287
- resolve([null, true]);
288
- });
289
- });
290
- }
291
-
292
- document.querySelectorAll("main").forEach((elm) => {
293
- elm.setAttribute(
294
- "class",
295
- "hf row mt-4 d-flex flex-column align-items-center"
296
- );
297
- elm.setAttribute("data-aos", "fade-down");
298
- });
 
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
- var products = [];
2
- var fileData = "";
3
-
4
- const GITHUB_TOKEN = atob(
5
- "Z2l0aHViX3BhdF8xMUFYNEtVTlkwY05lbWNpZHVrWDYxXzczTzV2bTh2Wk5uMzlpQm0wb1BXRmd5dUx2a0VKSGluZ0dZeURPV0JFWTJXMk9YTFFINVRLT2xpU2ts"
6
- );
7
-
8
- console.log(
9
- GITHUB_TOKEN ===
10
- "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"
11
- );
12
-
13
- const GITHUB_OWNER = "Adityadn64";
14
- const GITHUB_REPO = "FINAL-PROJECT---ACQ20RC";
15
- const USERS_FILE_PATH = "database/users.json";
16
- const PRODUCTS_FILE_PATH = "database/products.json";
17
-
18
- async function getUsers() {
19
- const res = await fetch(
20
- `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${USERS_FILE_PATH}`,
21
- {
22
- headers: {
23
- Accept: "application/vnd.github+json",
24
- Authorization: `Bearer ${GITHUB_TOKEN}`,
25
- },
26
- }
27
- );
28
-
29
- fileData = await res.json();
30
- let currentUsers = {};
31
- try {
32
- currentUsers = JSON.parse(atob(fileData.content));
33
- } catch (e) {
34
- console.error("Error parsing current users; using empty object", e);
35
- }
36
- return currentUsers;
37
- }
38
-
39
- async function getUserData() {
40
- const storedDataStr = localStorage.getItem("userData");
41
- if (!storedDataStr) return null;
42
- const storedData = JSON.parse(storedDataStr);
43
-
44
- const usersDB = await getUsers();
45
- let userData =
46
- storedData.uid && usersDB && usersDB[storedData.uid]
47
- ? usersDB[storedData.uid]
48
- : storedData;
49
- // Ensure uid is set
50
- if (storedData.uid) {
51
- userData.uid = storedData.uid;
52
- }
53
- return userData;
54
- }
55
-
56
- async function getCarts() {
57
- const userData = await getUserData();
58
- let localCart = [];
59
- const cartStr = localStorage.getItem("cart");
60
- if (cartStr) {
61
- try {
62
- localCart = JSON.parse(cartStr);
63
- if (!Array.isArray(localCart)) {
64
- localCart = [];
65
- }
66
- } catch (e) {
67
- console.error(
68
- "Error parsing local cart data; using empty array",
69
- e
70
- );
71
- localCart = [];
72
- }
73
- }
74
-
75
- let cartData;
76
- if (userData && userData.cart && Array.isArray(userData.cart)) {
77
- cartData = userData.cart;
78
- } else {
79
- cartData = localCart;
80
- }
81
-
82
- return cartData;
83
- }
84
-
85
-
86
- function convertImageToBase64(imageUrl) {
87
- return new Promise((resolve, reject) => {
88
- fetch(imageUrl)
89
- .then(res => res.blob())
90
- .then(blob => {
91
- const reader = new FileReader();
92
- reader.readAsDataURL(blob);
93
- reader.onloadend = () => resolve(reader.result);
94
- reader.onerror = error => reject(error);
95
- })
96
- .catch(err => reject(err));
97
- });
98
- }
99
-
100
- async function uploadFileToGitHub(filePath, base64Data) {
101
- try {
102
- const fileRes = await fetch(
103
- `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${filePath}`,
104
- {
105
- method: "PUT",
106
- headers: {
107
- Accept: "application/vnd.github+json",
108
- Authorization: `Bearer ${GITHUB_TOKEN}`,
109
- },
110
- body: JSON.stringify({
111
- message: "Upload user photo",
112
- content: base64Data.split(",")[1], // Hanya ambil data Base64 tanpa prefix
113
- }),
114
- }
115
- );
116
-
117
- const result = await fileRes.json();
118
- return result;
119
- } catch (err) {
120
- console.error("Error uploading file to GitHub:", err);
121
- return null;
122
- }
123
- }
124
-
125
- async function updateUserDataToGitHub(userData) {
126
- try {
127
- let currentUsers = await getUsers();
128
- const isNotValidUID = !userData.uid || userData.uid === undefined;
129
-
130
- if (isNotValidUID) {
131
- localStorage.clear();
132
- window.location.href =
133
- `/profile.html?redirect=${urlNow}` +
134
- (message ? `&message=${message}` : "");
135
- }
136
-
137
- if (!userData.photo_profile.includes("github")) {
138
- const photoFilePath = `assets/users/photo_profile/${userData.uid}.jpg`;
139
- let photoBase64 = userData.photo_profile;
140
-
141
- if (!photoBase64.startsWith("data:image/")) {
142
- photoBase64 = await convertImageToBase64(userData.photo_profile);
143
- }
144
-
145
- const photoUploadRes = await uploadFileToGitHub(photoFilePath, photoBase64);
146
- if (photoUploadRes) {
147
- userData.photo_profile = `https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/main/${photoFilePath}`;
148
- }
149
- }
150
-
151
- currentUsers[userData.uid] = {
152
- name: userData.name ? userData.name : "",
153
- email: userData.email ? userData.email : "",
154
- phone: userData.phone ? userData.phone : "",
155
- address: userData.address ? userData.address : "",
156
- photo_profile: userData.photo_profile ? userData.photo_profile : "",
157
- cart: userData.cart ? userData.cart : [],
158
- transactions: userData.transactions ? userData.transactions : {},
159
- };
160
-
161
- const newContent = btoa(JSON.stringify(currentUsers, null, 4));
162
- const updateRes = await fetch(
163
- `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${USERS_FILE_PATH}`,
164
- {
165
- method: "PUT",
166
- headers: {
167
- Accept: "application/vnd.github+json",
168
- Authorization: `Bearer ${GITHUB_TOKEN}`,
169
- },
170
- body: JSON.stringify({
171
- message: "Update user data via login Google",
172
- content: newContent,
173
- sha: fileData.sha,
174
- }),
175
- }
176
- );
177
-
178
- const result = await updateRes.json();
179
- console.log("Update user data result:", result, currentUsers);
180
- } catch (err) {
181
- console.error("Error updating user data to GitHub:", err);
182
- }
183
- }
184
-
185
- async function fetchProductsData() {
186
- const response = await fetch(
187
- `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${PRODUCTS_FILE_PATH}`,
188
- {
189
- headers: {
190
- Accept: "application/vnd.github.v3+json",
191
- Authorization: `Bearer ${GITHUB_TOKEN}`,
192
- },
193
- }
194
- );
195
- const data = await response.json();
196
- const fileContent = atob(data.content);
197
- products = JSON.parse(fileContent);
198
- return products;
199
- }
200
-
201
- document.addEventListener("DOMContentLoaded", async function () {
202
- AOS.init();
203
- products = await fetchProductsData();
204
- });
 
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
- function checkUID(m = "") {
2
- const userData = JSON.parse(localStorage.getItem("userData")) || {};
3
- const urlNow = window.location.href;
4
- const isNotValidUID = userData.length > 0 && !userData.uid;
5
-
6
- const message =
7
- m === "addToCart"
8
- ? "Silakan masuk terlebih dahulu untuk menambahkan produk ke keranjang."
9
- : isNotValidUID
10
- ? "Sesi Anda telah habis. Silakan masuk kembali untuk melanjutkan."
11
- : m === "jelajah"
12
- ? "Anda telah menjelajah 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
- const steps = [
2
- { step: 1, title: "Periksa Data Belanja" },
3
- { step: 2, title: "Periksa Informasi Anda" },
4
- { step: 3, title: "Pilih Metode Pembayaran" },
5
- { step: 4, title: "Lakukan Pembayaran" },
6
- ];
7
-
8
- function getStep() {
9
- const params = new URLSearchParams(window.location.search);
10
- let step = parseInt(params.get("step"));
11
- if (!step || step < 1 || step > 4) step = 1;
12
- return step;
13
- }
14
-
15
- function goToStep(step) {
16
- if (
17
- (localStorage.getItem("paymentMethod") || null) === null &&
18
- step === 4
19
- ) {
20
- alert("Pilih salah satu metode pembayaran!", false);
21
- return;
22
- }
23
- window.location.href = "payment.html?step=" + step;
24
- }
25
-
26
- function renderStepNav(currentStep) {
27
- let navHtml = `<div class="table-responsive">
28
- <table class="table table-bordered text-center">
29
- <thead class="table-light">
30
- <tr>`;
31
- steps.forEach((s) => {
32
- navHtml += `<th class="${
33
- s.step === currentStep ? "bg-info text-white" : "text-muted"
34
- }">
35
- <small>Langkah ${s.step}</small>
36
- </th>`;
37
- });
38
- navHtml += `</tr></thead></table></div>`;
39
- document.getElementById("step-nav").innerHTML = navHtml;
40
- }
41
-
42
- function renderStepButtons(currentStep) {
43
- let btnHtml = `<div class="d-flex justify-content-end">`;
44
- if (currentStep > 1) {
45
- btnHtml += `<button class="btn btn-secondary btn-lg me-2" onclick="goToStep(${
46
- currentStep - 1
47
- })">Kembali</button>`;
48
- }
49
- if (currentStep === 2) {
50
- btnHtml += `<button class="btn btn-primary btn-lg" onclick="saveInfoAndLanjut()">Lanjut</button>`;
51
- } else if (currentStep < 4) {
52
- btnHtml += `<button class="btn btn-primary btn-lg" onclick="goToStep(${
53
- currentStep + 1
54
- })">Lanjut</button>`;
55
- } else {
56
- btnHtml += `<button class="btn btn-success btn-lg" onclick="processFinalPayment()">Submit</button>`;
57
- }
58
- btnHtml += `</div>`;
59
- document.getElementById("step-buttons").innerHTML = btnHtml;
60
- }
61
-
62
- async function displayStep1Content() {
63
- document.getElementById("step-content").innerHTML = `
64
- <h2 class="mb-3">Langkah ${steps[0].step}: ${steps[0].title}</h2>
65
- <div id="cart-summary-step" class="table-responsive"></div>`;
66
- await loadCartSummary();
67
- }
68
-
69
- var products = [];
70
-
71
- var totalPrice = 0;
72
- async function calculateTotalPrice() {
73
- products = await fetchProductsData();
74
-
75
- try {
76
- const cart = await getCarts();
77
- let cartCount = {};
78
- cart.forEach((id) => {
79
- cartCount[id] = (cartCount[id] || 0) + 1;
80
- });
81
- totalPrice = 0;
82
- for (let id in cartCount) {
83
- const product = products.find((p) => p.id === parseInt(id));
84
- if (product) {
85
- totalPrice += product.price * cartCount[id];
86
- }
87
- }
88
- } catch (err) {
89
- console.error("Error loading cart summary:", err);
90
- }
91
- return totalPrice;
92
- }
93
-
94
- async function getTotalPrice() {
95
- totalPrice = await calculateTotalPrice();
96
- console.log("Total Harga:", totalPrice);
97
-
98
- return totalPrice;
99
- }
100
-
101
- async function loadCartSummary() {
102
- products = await fetchProductsData();
103
- totalPrice = await getTotalPrice();
104
- const cart = await getCarts();
105
-
106
- let cartCount = {};
107
- cart.forEach((id) => {
108
- cartCount[id] = (cartCount[id] || 0) + 1;
109
- });
110
- let tableHtml = `<div id="cart-summary-step-wrapper">
111
- <table class="table table-striped">
112
- <thead>
113
- <tr>
114
- <th>No</th>
115
- <th>Nama Produk</th>
116
- <th>Harga</th>
117
- <th>Quantity</th>
118
- <th>Subtotal</th>
119
- </tr>
120
- </thead>
121
- <tbody>`;
122
- let index = 1;
123
- if (Object.keys(cartCount).length === 0) {
124
- tableHtml += `<tr><td colspan="5">Keranjang kosong.</td></tr>`;
125
- } else {
126
- for (let id in cartCount) {
127
- const product = products.find((p) => p.id === parseInt(id));
128
- if (product) {
129
- const qty = cartCount[id];
130
- const subtotal = product.price * qty;
131
- tableHtml += `<tr>
132
- <td>${index++}</td>
133
- <td>${product.name}</td>
134
- <td>${formatRupiah(product.price)}</td>
135
- <td>${qty}</td>
136
- <td>${formatRupiah(subtotal)}</td>
137
- </tr>`;
138
- }
139
- }
140
- tableHtml += `<tr class="table-light">
141
- <td colspan="4" class="text-end"><strong>Total</strong></td>
142
- <td><strong>${formatRupiah(totalPrice)}</strong></td>
143
- </tr>`;
144
- }
145
- tableHtml += `</tbody></table>`;
146
- document.getElementById("cart-summary-step").innerHTML = tableHtml;
147
- }
148
-
149
- async function displayStep2Content() {
150
- const userData = await getUserData();
151
-
152
- document.getElementById("step-content").innerHTML = `
153
- <h2 class="mb-3">Langkah ${steps[1].step}: ${steps[1].title}</h2>
154
- <form id="info-form" class="fs-5">
155
- <div class="mb-3">
156
- <label class="form-label">Nama Lengkap</label>
157
- <input type="text" class="form-control form-control-lg" id="infoName" value="${userData.name}" required>
158
- </div>
159
- <div class="mb-3">
160
- <label class="form-label">Email</label>
161
- <input type="email" class="form-control form-control-lg" id="infoEmail" value="${userData.email}" required disabled>
162
- </div>
163
- <div class="mb-3">
164
- <label class="form-label">Telepon</label>
165
- <input type="text" class="form-control form-control-lg" id="infoPhone" value="${userData.phone}" required>
166
- </div>
167
- <div class="mb-3">
168
- <label class="form-label">Alamat</label>
169
- <input type="text" class="form-control form-control-lg" id="infoAddress" value="${userData.address}" required>
170
- </div>
171
- </form>`;
172
- }
173
-
174
- async function saveInfoAndLanjut() {
175
- const name = document.getElementById("infoName").value.trim();
176
- const email = document.getElementById("infoEmail").value.trim();
177
- const phone = document.getElementById("infoPhone").value.trim();
178
- const address = document.getElementById("infoAddress").value.trim();
179
-
180
- const userData = await getUserData();
181
-
182
- const transactions = userData.transactions ? userData.transactions : {};
183
-
184
- const updatedUser = {
185
- uid: userData.uid,
186
- name: name,
187
- email: userData.email,
188
- phone: phone,
189
- address: address,
190
- 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
- const CLIENT_ID =
2
- "819389601271-fl7jsirpdkepkcou78erki5hmi30rrbm.apps.googleusercontent.com";
3
- let tokenClient;
4
- let accessToken;
5
-
6
- async function handleCredentialResponse(response) {
7
- const idToken = response.credential;
8
- const payload = JSON.parse(atob(idToken.split(".")[1]));
9
- const uid = payload.sub;
10
-
11
- localStorage.setItem("userData", JSON.stringify({
12
- uid: uid,
13
- name: payload.name,
14
- photo_profile: payload.picture,
15
- email: payload.email,
16
- }));
17
-
18
- let userData = await getUserData();
19
- if (!userData || Object.keys(userData).length === 0) {
20
- userData = {
21
- uid: uid,
22
- name: payload.name,
23
- photo_profile: payload.picture,
24
- email: payload.email,
25
- };
26
- }
27
-
28
- await updateUserDataToGitHub(userData);
29
-
30
- localStorage.setItem("cart", JSON.stringify(userData.cart || []));
31
-
32
- window.location.reload();
33
- }
34
-
35
- function handleAccessTokenResponse(response) {
36
- accessToken = response.access_token;
37
- console.log("Access Token:", accessToken);
38
- fetchUserAdditionalInfo();
39
- }
40
-
41
- async function fetchUserAdditionalInfo() {
42
- let userData = JSON.parse(localStorage.getItem("userData"));
43
- if (userData && userData.uid && accessToken) {
44
- try {
45
- const res = await fetch(
46
- "https://people.googleapis.com/v1/people/me?personFields=phoneNumbers,addresses",
47
- {
48
- headers: {
49
- Authorization: `Bearer ${accessToken}`,
50
- },
51
- }
52
- );
53
- if (!res.ok)
54
- throw new Error(
55
- "Gagal mendapatkan data tambahan: " + res.status
56
- );
57
- const additionalData = await res.json();
58
- console.log("Data tambahan:", additionalData);
59
-
60
- if (
61
- additionalData.phoneNumbers &&
62
- additionalData.phoneNumbers.length > 0
63
- ) {
64
- userData.phone = additionalData.phoneNumbers[0].value;
65
- }
66
- if (
67
- additionalData.addresses &&
68
- additionalData.addresses.length > 0
69
- ) {
70
- userData.address = additionalData.addresses[0].formattedValue;
71
- }
72
- localStorage.setItem("userData", JSON.stringify(userData));
73
- console.log("User Data Lengkap:", userData);
74
- } catch (err) {
75
- console.error("Error fetching additional info:", err);
76
- }
77
- } else {
78
- console.warn("Data UID atau access token tidak tersedia.");
79
- }
80
- }
81
-
82
- async function renderGoogleLoginButton() {
83
- const container = document.getElementById("profile-container");
84
-
85
- document.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
- document.addEventListener("DOMContentLoaded", async function () {
2
- const resultList = document.getElementById("result-list");
3
- const urlParams = new URLSearchParams(window.location.search);
4
- const productQuery = urlParams.get("products");
5
- const transactionQuery = urlParams.get("transactions");
6
-
7
- const createHeader = (text) => {
8
- const header = document.createElement("h3");
9
- header.textContent = text;
10
- header.className = "col-12 mt-4";
11
- resultList.appendChild(header);
12
- };
13
-
14
- if (!productQuery && !transactionQuery) {
15
- const messageElem = document.createElement("p");
16
- messageElem.textContent = "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