diff --git a/public/cart-cross-svgrepo-com.svg b/public/cart-cross-svgrepo-com.svg
new file mode 100644
index 0000000..09b79d1
--- /dev/null
+++ b/public/cart-cross-svgrepo-com.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/public/cart-plus-svgrepo-com.svg b/public/cart-plus-svgrepo-com.svg
new file mode 100644
index 0000000..9fd8f7d
--- /dev/null
+++ b/public/cart-plus-svgrepo-com.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/public/cart-shopping-svgrepo-com.svg b/public/cart-shopping-svgrepo-com.svg
new file mode 100644
index 0000000..c722911
--- /dev/null
+++ b/public/cart-shopping-svgrepo-com.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/App.js b/src/App.js
index 0c1cf07..216245c 100644
--- a/src/App.js
+++ b/src/App.js
@@ -105,16 +105,17 @@ function App() {
if (data && data[0] && data[0].token) {
// Update token with data[0].token
document.cookie = `token=${data[0].token}; path=/`;
+
+ const payload = parseJwt(token);
+ if (payload && payload.username) {
+ setUsername(payload.username);
+ }
} else {
console.warn('Token tidak ditemukan dalam data.');
}
})
.catch(err => console.error('Fetch error:', err));
- const payload = parseJwt(token);
- if (payload && payload.username) {
- setUsername(payload.username);
- }
}
}, []);
const scrollToProduct = () => {
diff --git a/src/components/ProductDetail.module.css b/src/components/ProductDetail.module.css
index c48ab22..947a59a 100644
--- a/src/components/ProductDetail.module.css
+++ b/src/components/ProductDetail.module.css
@@ -10,7 +10,7 @@
}
.image {
- width: 100%;
+ width: 40vw;
height: 260px;
background-color: #e2e8f0;
border-radius: 0.75rem;
@@ -20,6 +20,10 @@
color: #64748b;
font-size: 1rem;
margin-bottom: 1.5rem;
+
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-position: center;
}
.headerRow {
@@ -32,14 +36,14 @@
}
.title {
- font-size: 1.1rem;
+ font-size: 0.9rem;
font-weight: bold;
color: #1e293b;
margin: 0;
}
.price {
- font-size: 1.1rem;
+ font-size: 0.9rem;
font-weight: bold;
color: #2563eb; /* default color, bisa override di inline style */
}
@@ -92,4 +96,41 @@
.buttonGroup {
gap: 0.5rem;
}
+
+.image {
+ width: 63vw;
+}
+}
+
+.childSelector {
+ background: white;
+ color: black;
+ text-align: left;
+}
+
+.childProduct {
+ display: block;
+ margin-bottom: 8px;
+ border: 1px solid black;
+ padding: 10px;
+ border-radius: 15px;
+}
+
+.confirmButton {
+ background-color: #2563eb;
+ color: white;
+ padding: 8px 16px;
+ margin-right: 10px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.cancelButton {
+ background-color: #f87171;
+ color: white;
+ padding: 8px 16px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
}
diff --git a/src/components/ProductDetailPage.js b/src/components/ProductDetailPage.js
index f43e00f..9520b30 100644
--- a/src/components/ProductDetailPage.js
+++ b/src/components/ProductDetailPage.js
@@ -3,6 +3,8 @@ import styles from './ProductDetail.module.css';
const ProductDetail = ({ product, setPostLoginAction, setShowedModal }) => {
const [inCart, setInCart] = useState(false);
+ const [showChildSelector, setShowChildSelector] = useState(false);
+ const [selectedChildIds, setSelectedChildIds] = useState([]);
useEffect(() => {
const existingCookie = document.cookie
@@ -47,85 +49,193 @@ const ProductDetail = ({ product, setPostLoginAction, setShowedModal }) => {
setInCart(true);
}
- document.cookie = `itemsId=${JSON.stringify(updatedItems)}; path=/; max-age=${7 * 24 * 60 * 60
- }`;
+ document.cookie = `itemsId=${JSON.stringify(updatedItems)}; path=/; max-age=${7 * 24 * 60 * 60}`;
};
- const onCheckout = () => {
- // Ambil token dari cookie
- const tokenCookie = document.cookie
- .split('; ')
- .find(row => row.startsWith('token='));
- const token = tokenCookie ? tokenCookie.split('=')[1] : '';
+const onCheckout = () => {
+ const tokenCookie = document.cookie
+ .split('; ')
+ .find(row => row.startsWith('token='));
+ const token = tokenCookie ? tokenCookie.split('=')[1] : '';
- // Ambil itemsId dari cookie
- const itemsCookie = document.cookie
- .split('; ')
- .find(row => row.startsWith('itemsId='));
+ if (!tokenCookie) {
+ setPostLoginAction(() => () => onCheckout());
+ setShowedModal('login');
+ return;
+ }
- let items = [];
- if (itemsCookie) {
- try {
- items = JSON.parse(itemsCookie.split('=')[1]);
- if (!Array.isArray(items)) items = [];
- } catch (e) {
- items = [];
- }
+ // Jika punya children, tampilkan pilihan
+ if (product.children && product.children.length > 0) {
+ setShowChildSelector(true);
+ return;
+ }
+
+ // Ambil itemsId dari cookie
+ const itemsCookie = document.cookie
+ .split('; ')
+ .find(row => row.startsWith('itemsId='));
+
+ let items = [];
+ if (itemsCookie) {
+ try {
+ items = JSON.parse(itemsCookie.split('=')[1]);
+ if (!Array.isArray(items)) items = [];
+ } catch (e) {
+ items = [];
}
+ }
- if (!items.includes(product.id)) {
- items.push(product.id);
+ // Tambahkan product.id jika belum ada
+ if (!items.includes(product.id)) {
+ items.push(product.id);
+ }
+
+ const itemsParam = JSON.stringify(items);
+
+ window.location.href = `http://localhost:3002/?token=${token}&itemsId=${itemsParam}&redirect_uri=http://localhost:3000/products&redirect_failed=http://localhost:3000`;
+};
+
+const onConfirmChildren = () => {
+ const tokenCookie = document.cookie
+ .split('; ')
+ .find(row => row.startsWith('token='));
+ const token = tokenCookie ? tokenCookie.split('=')[1] : '';
+
+ if (selectedChildIds.length === 0) {
+ alert('Pilih minimal satu produk');
+ return;
+ }
+
+ // Ambil itemsId dari cookie
+ const itemsCookie = document.cookie
+ .split('; ')
+ .find(row => row.startsWith('itemsId='));
+
+ let items = [];
+ if (itemsCookie) {
+ try {
+ items = JSON.parse(itemsCookie.split('=')[1]);
+ if (!Array.isArray(items)) items = [];
+ } catch (e) {
+ items = [];
}
- // Encode items ke string untuk query param
- const itemsParam = JSON.stringify(items);
+ }
- if (!tokenCookie) {
- setPostLoginAction(() => () => onCheckout()); // remember intent
- setShowedModal('login');
- return;
- }
+ // Gabungkan items dari cookie dengan selectedChildIds
+ const mergedItems = Array.from(new Set([...items, ...selectedChildIds]));
- // Redirect dengan token dan itemsId di query route ke checkout.kediritechnopark.com
- window.location.href = `http://localhost:3002/?token=${token}&itemsId=${itemsParam}&redirect_uri=http://localhost:3000/products&redirect_failed=http://localhost:3000`;
- };
+ const itemsParam = JSON.stringify(mergedItems);
+
+ window.location.href = `http://localhost:3002/?token=${token}&itemsId=${itemsParam}&redirect_uri=http://localhost:3000/products&redirect_failed=http://localhost:3000`;
+};
- // Override harga warna jika free
const priceColor = product.price === 0 ? '#059669' : '#2563eb';
return (
-
📦
-
-
{product.name}
-
- {product.price == null
- ? 'Pay-As-You-Go'
- : `Rp ${parseInt(product.price).toLocaleString('id-ID')}`}
+ {/* ✅ Tampilan utama disembunyikan jika sedang memilih child */}
+ {!showChildSelector && (
+ <>
+
+
+
+
{product.name}
+
+ {product.price == null
+ ? 'Pay-As-You-Go'
+ : `Rp ${parseInt(product.price).toLocaleString('id-ID')}`}
+
+
+
+
{product.description}
+
+
+
+
+
+
+ >
+ )}
+
+ {/* ✅ UI pemilihan child */}
+ {showChildSelector && (
+
+
Pilih Paket
+ {product.children.map(child => (
+
+ ))}
+
+
+ Total Harga:{' '}
+ Rp {selectedChildIds
+ .map(id => {
+ const found = product.children.find(child => child.id === id);
+ return found ? found.price || 0 : 0;
+ })
+ .reduce((a, b) => a + b, 0)
+ .toLocaleString('id-ID')}
+
+
+
+
+
+
+
-
-
-
{product.description}
-
-
-
-
-
+ )}
);
};
diff --git a/src/components/ProductSection.js b/src/components/ProductSection.js
index 1d862d7..85135ae 100644
--- a/src/components/ProductSection.js
+++ b/src/components/ProductSection.js
@@ -6,18 +6,48 @@ import styles from './Styles.module.css';
const ProductSection = ({ hoveredCard, setHoveredCard, setSelectedProduct, setShowedModal, productSectionRef }) => {
const [products, setProducts] = useState([]);
- useEffect(() => {
- fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ type: 'product', onlyParents: true }),
+useEffect(() => {
+ fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ type: 'product' }),
+ })
+ .then(res => res.json())
+ .then(data => {
+ const parentMap = {};
+ const childrenMap = {};
+
+ // Pisahkan parent dan child
+ data.forEach(product => {
+ if (product.sub_product_of) {
+ const parentId = product.sub_product_of;
+ if (!childrenMap[parentId]) childrenMap[parentId] = [];
+ childrenMap[parentId].push(product);
+ } else {
+ parentMap[product.id] = {
+ ...product,
+ children: []
+ };
+ }
+ });
+
+ // Pasang children ke parent
+ Object.keys(childrenMap).forEach(parentId => {
+ const parent = parentMap[parentId];
+ if (parent) {
+ parent.children = childrenMap[parentId];
+ }
+ });
+
+ // Ambil parent saja
+ const enrichedData = Object.values(parentMap);
+
+ setProducts(enrichedData);
})
- .then(res => res.json())
- .then(data => setProducts(data))
- .catch(err => console.error('Fetch error:', err));
- }, []);
+ .catch(err => console.error('Fetch error:', err));
+}, []);
return (
@@ -31,7 +61,8 @@ const ProductSection = ({ hoveredCard, setHoveredCard, setSelectedProduct, setSh
{products &&
products[0]?.name &&
- products.map(product => (
+ products
+ .map(product => (
p.product_id);
+ return [...new Set(productIds)];
+}
+
+function getLatestEndDatesFromJwt(token) {
+ const payload = parseJwt(token);
+ if (!payload || !payload.subscriptions || !payload.subscriptions) return {};
+
+ const result = {};
+ payload.subscriptions.forEach(p => {
+ if (!p.end_date) return;
+ const id = p.product_id;
+ const endDate = new Date(p.end_date);
+ if (!result[id] || endDate > new Date(result[id])) {
+ result[id] = p.end_date;
+ }
+ });
+
+ return result;
+}
+function getTotalTokenFromJwt(token) {
+ const payload = parseJwt(token);
+ if (!payload || !payload.subscriptions || !payload.subscriptions) return {};
+
+ const tokenQuantities = {};
+ payload.subscriptions.forEach(p => {
+ // Pastikan ada quantity dan unit_type token
+ if (p.quantity && p.product_id) {
+ tokenQuantities[p.product_id] = (tokenQuantities[p.product_id] || 0) + p.quantity;
+ }
+ });
+
+ return tokenQuantities;
+}
+
+
const CoursePage = () => {
const [postLoginAction, setPostLoginAction] = useState(null);
const [selectedProduct, setSelectedProduct] = useState({});
const [hoveredCard, setHoveredCard] = useState(null);
- const [showedModal, setShowedModal] = useState(null); // 'product' | 'login' | null
+ const [showedModal, setShowedModal] = useState(null);
const [products, setProducts] = useState([]);
+useEffect(() => {
+ const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
+ if (match) {
+ const token = match[2];
- useEffect(() => {
- // Ambil token dari cookies
- const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
- if (match) {
- const token = match[2];
+ const productIds = getDistinctProductIdsFromJwt(token);
+ const endDates = getLatestEndDatesFromJwt(token);
+ const tokenQuantitiesFromJwt = getTotalTokenFromJwt(token);
- fetch('https://bot.kediritechnopark.com/webhook/users-dev/my-products', {
+ fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- 'Authorization': 'Bearer ' + token
},
- body: JSON.stringify({ type: 'product' }),
+ body: JSON.stringify({ itemsId: productIds, type: 'product' }),
})
.then(res => res.json())
- .then(data => setProducts(data))
- .catch(err => console.error('Fetch error:', err));
+ .then(data => {
+ const parentMap = {};
+ const childrenMap = {};
+
+ data.forEach(product => {
+ if (product.sub_product_of) {
+ const parentId = product.sub_product_of;
+ if (!childrenMap[parentId]) childrenMap[parentId] = [];
+ childrenMap[parentId].push(product);
+ } else {
+ parentMap[product.id] = {
+ ...product,
+ quantity: product.quantity || 0,
+ end_date: endDates[product.id] || null,
+ children: []
+ };
+ }
+ });
+// ...
+
+Object.keys(childrenMap).forEach(parentId => {
+ const parent = parentMap[parentId];
+ const children = childrenMap[parentId];
+
+ if (parent) {
+ parent.children = children;
+
+ // Pakai quantity dari JWT langsung (tokenQuantitiesFromJwt)
+ parent.quantity = children.reduce((total, child) => {
+ return total + (tokenQuantitiesFromJwt[child.id] || 0);
+ }, 0);
+ }
+});
+
+// ...
+
+// Update quantity untuk produk yang bukan parent dan bukan anak
+Object.values(parentMap).forEach(product => {
+ if (!product.children.length) {
+ if (product.unit_type === 'token') {
+ product.quantity = tokenQuantitiesFromJwt[product.id] || 0;
}
- }, []);
+ }
+});
+
+ const enrichedData = Object.values(parentMap);
+ setProducts(enrichedData);
+ console.log(enrichedData);
+ })
+ .catch(err => console.error('Fetch error:', err));
+ }
+}, []);
+
const features = [
{
@@ -70,49 +160,52 @@ const CoursePage = () => {
return (
-
+
{/* Courses Section */}
-
OUR COURSES
+
MY PRODUCTS
{products &&
products[0]?.name &&
- products.map(product => (
-
{
- setSelectedProduct(product);
- setShowedModal('product');
- }}
- onMouseEnter={() => setHoveredCard(product.id)}
- onMouseLeave={() => setHoveredCard(null)}
- >
-
- {product.price == 0 && (
- Free
- )}
-
-
-
{product.name}
-
{product.description}
-
-
- {product.price == 0
- ? 'Free'
- : `Rp ${product.price.toLocaleString('id-ID')}`}
-
+ products
+ .map(product => (
+
{
+ setSelectedProduct(product);
+ setShowedModal('product');
+ }}
+ onMouseEnter={() => setHoveredCard(product.id)}
+ onMouseLeave={() => setHoveredCard(null)}
+ >
+
+ {product.price == 0 && (
+ Free
+ )}
+
+
+
{product.name}
+
{product.description}
+
+
+ {product.unit_type === 'duration'
+ ? `Valid until: ${product.end_date ? new Date(product.end_date).toLocaleDateString() : 'N/A'}`
+ : `SISA TOKEN ${product.quantity || 0}`
+ }
+
+
+
-
- ))}
+ ))}