diff --git a/src/App.js b/src/App.js
index 74cf227..921475b 100644
--- a/src/App.js
+++ b/src/App.js
@@ -24,8 +24,19 @@ function HomePage({
setShowedModal,
productSectionRef,
courseSectionRef,
+ scrollToProduct,
+ scrollToCourse,
setWillDo
}) {
+
+ useEffect(() => {
+ const params = new URLSearchParams(window.location.search);
+ const tab = params.get('tab');
+
+ if(tab === 'products') scrollToProduct();
+ if(tab === 'academy') scrollToCourse();
+ }, [productSectionRef, courseSectionRef]);
+
return (
<>
@@ -46,6 +57,7 @@ function HomePage({
/>
+
>
);
}
@@ -130,6 +142,7 @@ function App() {
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const modalType = params.get('modal');
+ const tab = params.get('tab');
const productId = params.get('product_id');
const authorizedUri = params.get('authorized_uri');
const unauthorizedUri = params.get('unauthorized_uri');
@@ -148,6 +161,9 @@ function App() {
}
// Jika sudah login, tidak langsung fetch di sini — akan diproses saat subscriptions tersedia
}
+
+ if(tab === 'products') scrollToProduct();
+ if(tab === 'academy') scrollToCourse();
}, []);
useEffect(() => {
@@ -299,12 +315,14 @@ function App() {
productSectionRef={productSectionRef}
courseSectionRef={courseSectionRef}
setWillDo={setWillDo}
+ scrollToProduct={scrollToProduct}
+ scrollToCourse={scrollToCourse}
/>
}
/>
} />
+ setSelectedProduct={setSelectedProduct} subscriptions={subscriptions} setWillDo={setWillDo} />} />
-
{/* Modal */}
{showedModal && (
diff --git a/src/components/ProductDetailPage.js b/src/components/ProductDetailPage.js
index f024a08..d6f225a 100644
--- a/src/components/ProductDetailPage.js
+++ b/src/components/ProductDetailPage.js
@@ -79,15 +79,14 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
};
const onConfirmChildren = () => {
- if (matchingSubscriptions.length > 0) {
+ if (matchingSubscriptions.length > 0 && !product.executeCheckout) {
setShowChildSelector(false);
setShowSubscriptionSelector(true);
return;
}
- else {
+ else if (!product.executeCheckout){
setShowChildSelector(false);
setShowNamingInput(true);
- return;
}
const tokenCookie = document.cookie.split('; ').find(row => row.startsWith('token='));
@@ -97,9 +96,9 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
alert('Pilih minimal satu produk');
return;
}
-
+ const encodedName = encodeURIComponent(product.executeCheckout);
const itemsParam = selectedChildIds.length > 0 ? JSON.stringify(selectedChildIds) : JSON.stringify([product.id]);
- window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&redirect_uri=https://kediritechnopark.com/products&redirect_failed=https://kediritechnopark.com`;
+ window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&set_name=${encodedName}&redirect_uri=https://kediritechnopark.com/products&redirect_failed=https://kediritechnopark.com`;
};
const onFinalCheckoutNewProduct = () => {
diff --git a/src/components/Styles.module.css b/src/components/Styles.module.css
index dada98a..8b7bd6a 100644
--- a/src/components/Styles.module.css
+++ b/src/components/Styles.module.css
@@ -304,7 +304,7 @@
}
.currentPrice {
- font-size: 0.9rem;
+ font-size: 1.2rem;
font-weight: bold;
color: #2563eb;
}
@@ -670,3 +670,139 @@
max-width: 150px; /* biar logo tidak terlalu besar */
height: auto;
}
+
+/* Navigation Tabs */
+.navTabs {
+ display: flex;
+ align-items: center;
+ background-color: #2a4fd6; /* blue bar like screenshot */
+ padding: 0;
+ border-radius: 6px 6px 0 0;
+ overflow: hidden;
+}
+
+.floatMenuItem {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ padding: 12px 16px;
+ font-size: 14px;
+ font-weight: 500;
+ color: #ffffffcc; /* light white text */
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ transition: background 0.2s ease, color 0.2s ease;
+}
+
+.floatMenuItem:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+ color: #fff;
+}
+
+.floatMenuItemActive {
+ background-color: #ffffff;
+ color: #2a4fd6; /* active text color */
+ font-weight: 600;
+}
+
+.floatMenuTitle {
+ display: inline-block;
+}
+
+.floatMenuIcon {
+ stroke-width: 2;
+}
+
+/* Sections */
+.Section {
+ padding: 24px;
+ background: #fff;
+ border-radius: 0 0 6px 6px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+/* Settings Page Layout */
+.profileSection {
+ background: #fff;
+ border-radius: 8px;
+ padding: 24px;
+}
+
+.profileHeading {
+ font-size: 20px;
+ font-weight: 600;
+ color: #1f2937;
+ margin-bottom: 8px;
+}
+
+.sectionDivider {
+ height: 2px;
+ background-color: #2a4fd6;
+ width: 40px;
+ margin: 8px 0 20px 0;
+}
+
+.formGrid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+ background-color: #f6f9fc;
+ padding: 20px;
+ border-radius: 8px;
+}
+
+.fullWidth {
+ grid-column: span 2;
+}
+
+.label {
+ font-size: 14px;
+ font-weight: 500;
+ color: #374151;
+ margin-bottom: 6px;
+ display: block;
+}
+
+.inputField,
+.selectField {
+ width: 100%;
+ padding: 10px 12px;
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+ background-color: #fff;
+ font-size: 14px;
+}
+
+.selectField {
+ appearance: none;
+}
+
+.saveButton {
+ background-color: #2a4fd6;
+ color: white;
+ font-size: 14px;
+ font-weight: 500;
+ padding: 10px 24px;
+ border-radius: 50px;
+ border: none;
+ cursor: pointer;
+ margin-top: 16px;
+ transition: background 0.2s ease;
+}
+
+.saveButton:hover {
+ background-color: #223fa9;
+}
+
+/* Change Password Section */
+.changePasswordSection {
+ margin-top: 32px;
+}
+
+.buttonRow {
+ display: flex;
+ gap: 8px;
+}
diff --git a/src/components/pages/ProductsPage.js b/src/components/pages/ProductsPage.js
index f50f4cb..f0e4189 100644
--- a/src/components/pages/ProductsPage.js
+++ b/src/components/pages/ProductsPage.js
@@ -1,172 +1,425 @@
-import React, { useState, useEffect } from 'react';
-import ProductDetailPage from '../ProductDetailPage';
-import Login from '../Login';
-import styles from '../Styles.module.css';
+import React, { useState, useEffect } from "react";
+import styles from "../Styles.module.css";
+import { Box, Settings, ShoppingCart } from "lucide-react";
+import { useNavigate } from 'react-router-dom';
-const CoursePage = ({ subscriptions, setSelectedProduct, setShowedModal}) => {
- const [hoveredCard, setHoveredCard] = useState(null);
- const [products, setProducts] = useState([]);
- // Buka modal otomatis berdasarkan query
- useEffect(() => {
- const urlParams = new URLSearchParams(window.location.search);
- const modal = urlParams.get('modal');
- const productId = urlParams.get('product_id');
+const Dashboard = ({
+ subscriptions,
+ setSelectedProduct,
+ setShowedModal,
+ userData,
+ setWillDo
+}) => {
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = useState("products");
+ const [hoveredCard, setHoveredCard] = useState(null);
+ const [products, setProducts] = useState([]);
- if (modal === 'product' && productId && products.length > 0) {
- const product = products.find(p => String(p.id) === productId);
- if (product) {
- setSelectedProduct(product);
- setShowedModal('product');
+ // User Settings form state
+ const [settings, setSettings] = useState({
+ username: "",
+ email: "",
+ password: "",
+ profile_data: {
+ name: "",
+ image: "",
+ phone: "",
+ address: "",
+ company: ""
+ }
+ });
+
+ useEffect(() => {
+ if (userData) {
+ setSettings(userData);
+ }
+ }, [userData]);
+
+ useEffect(() => {
+ if (!subscriptions) return;
+
+ function groupSubscriptionsByProductName(subs) {
+ const result = {};
+ subs.forEach((sub) => {
+ const name = sub.product_name;
+ const productId = sub.product_id;
+ if (!result[name]) {
+ result[name] = {
+ product_id: productId,
+ product_name: name,
+ unit_type: sub.unit_type,
+ end_date: sub.end_date,
+ quantity: 0,
+ subscriptions: []
+ };
+ }
+
+ const currentEnd = new Date(result[name].end_date);
+ const thisEnd = new Date(sub.end_date);
+ if (thisEnd > currentEnd) {
+ result[name].end_date = sub.end_date;
+ }
+
+ if (sub.unit_type === "token") {
+ result[name].quantity += sub.quantity ?? 0;
+ } else {
+ result[name].quantity += 1;
+ }
+
+ result[name].subscriptions.push(sub);
+ });
+
+ return result;
+ }
+
+ const groupedSubs = groupSubscriptionsByProductName(subscriptions);
+ const productIds = [...new Set(subscriptions.map((s) => s.product_id))];
+
+ fetch(
+ "https://bot.kediritechnopark.com/webhook/store-production/products",
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({ itemsId: productIds})
+ }
+ )
+ .then((res) => res.json())
+ .then((data) => {
+ // Step 1: Enrich base products (without children yet)
+ const enrichedData = Object.values(groupedSubs)
+ .filter((group) => data.some((p) => p.id === group.product_id))
+ .map((group) => {
+ const productData = data.find((p) => p.id == group.product_id);
+ let image = productData?.image || "";
+ let description = productData?.description || "";
+ let site_url = productData?.site_url || "";
+ if (!image && productData?.sub_product_of) {
+ const parent = data.find(
+ (p) => p.id === productData.sub_product_of
+ );
+ image = parent?.image || "";
+ description = parent?.description || "";
+ site_url = parent?.site_url || "";
}
- }
- }, [products]);
+ return {
+ executeCheckout: group.product_name,
+ id: group.product_id,
+ name: group.product_name,
+ type: productData?.type || "product",
+ image,
+ description,
+ site_url,
+ price: productData?.price || 0,
+ currency: productData?.currency || "IDR",
+ duration: productData?.duration || {},
+ sub_product_of: productData?.sub_product_of || null,
+ is_visible: productData?.is_visible ?? true,
+ unit_type: productData?.unit_type || group.unit_type,
+ quantity: group.quantity,
+ end_date: group.end_date,
+ children: []
+ };
+ });
- useEffect(() => {
- if (!subscriptions) return;
+ // Step 2: Create a quick lookup table for enrichedData
+ const productMap = {};
+ enrichedData.forEach((p) => {
+ console.log(p)
+ productMap[p.name.split("%%%")[1]] = p;
+ });
- function groupSubscriptionsByProductName(subs) {
- const result = {};
- subs.forEach(sub => {
- const name = sub.product_name;
- const productId = sub.product_id;
- if (!result[name]) {
- result[name] = {
- product_id: productId,
- product_name: name,
- unit_type: sub.unit_type,
- end_date: sub.end_date,
- quantity: 0,
- subscriptions: []
- };
- }
-
- const currentEnd = new Date(result[name].end_date);
- const thisEnd = new Date(sub.end_date);
- if (thisEnd > currentEnd) {
- result[name].end_date = sub.end_date;
- }
-
- if (sub.unit_type == 'token') {
- result[name].quantity += sub.quantity ?? 0;
- } else {
- result[name].quantity += 1;
- }
-
- result[name].subscriptions.push(sub);
+ // Step 3: Find children in API `data` and attach to parents
+ data
+ .filter((p) => p.sub_product_of) // only those with a parent
+ .forEach((child) => {
+ // ✅ Current logic — attach to the real parent
+ const parent = productMap[child.sub_product_of];
+ if (parent) {
+ parent.children.push(child);
+ }
+ // ➕ New logic — attach to other products with the same sub_product_of
+ Object.values(productMap).forEach((possibleParent) => {
+ if (
+ possibleParent.id !== child.id && // not itself
+ possibleParent.sub_product_of === child.sub_product_of // same parent reference
+ ) {
+ possibleParent.children.push(child);
+ }
});
+ });
+ console.log(enrichedData)
+ setProducts(enrichedData);
+ })
+ .catch((err) => console.error("Fetch error:", err));
- return result;
- }
+ }, [subscriptions]);
- const groupedSubs = groupSubscriptionsByProductName(subscriptions);
- const productIds = [...new Set(subscriptions.map(s => s.product_id))];
+ const handleSettingsChange = (field, value, nested = false) => {
+ if (nested) {
+ setSettings((prev) => ({
+ ...prev,
+ profile_data: { ...prev.profile_data, [field]: value }
+ }));
+ } else {
+ setSettings((prev) => ({ ...prev, [field]: value }));
+ }
+ };
- fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ itemsId: productIds }),
- })
- .then(res => res.json())
- .then(data => {
- const enrichedData = Object.values(groupedSubs)
- .filter(group => data.some(p => p.id === group.product_id))
- .map(group => {
- const productData = data.find(p => p.id == group.product_id);
- let image = productData?.image || '';
- let description = productData?.description || '';
- let site_url = productData?.site_url || '';
- if (!image && productData?.sub_product_of) {
- const parent = data.find(p => p.id === productData.sub_product_of);
- console.log(parent)
- image = parent?.image || '';
- description = parent?.description || '';
- site_url = parent?.site_url || '';
- }
- console.log(site_url)
- return {
- id: group.product_id,
- name: group.product_name,
- type: productData?.type || 'product',
- image: image,
- description: description,
- site_url: site_url,
- price: productData?.price || 0,
- currency: productData?.currency || 'IDR',
- duration: productData?.duration || {},
- sub_product_of: productData?.sub_product_of || null,
- is_visible: productData?.is_visible ?? true,
- unit_type: productData?.unit_type || group.unit_type,
- quantity: group.quantity,
- end_date: group.end_date,
- children: [],
- };
- });
- console.log(enrichedData)
- setProducts(enrichedData);
- })
- .catch(err => console.error('Fetch error:', err));
- }, [subscriptions]);
+ const saveSettings = () => {
+ fetch(
+ "https://bot.kediritechnopark.com/webhook-test/user-production/data",
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify(settings)
+ }
+ )
+ .then((res) => res.json())
+ .then(() => {
+ alert("Settings updated successfully!");
+ })
+ .catch((err) => alert("Error updating settings: " + err));
+ };
- const features = [/* ... (tidak diubah) ... */];
+ return (
+
+ {/* Tabs Navigation */}
+
+
- return (
-
- {/* Courses Section */}
-
-
-
MY PRODUCTS
-
- {products &&
- products[0]?.name &&
- products.map(product => (
-
{
- setSelectedProduct(product);
- setShowedModal('product');
- }}
- onMouseEnter={() => setHoveredCard(product.name)}
- onMouseLeave={() => setHoveredCard(null)}
- >
-
-
-
-
{product.name.split('%%%')[0]}
-
{product.description}
-
-
-
-
-
- {product.unit_type === 'duration'
- ? `Valid until: ${product.end_date ? new Date(product.end_date).toLocaleDateString() : 'N/A'}`
- : `SISA TOKEN ${product.quantity || 0}`}
-
-
+
-
-
-
- ))}
+
+
+
+ {/* Tab Content */}
+ {activeTab === "products" && (
+
+
+
+ {products.map((product) => (
+
{
+ setSelectedProduct(product);
+ setShowedModal("product");
+ }}
+ onMouseEnter={() => setHoveredCard(product.name)}
+ onMouseLeave={() => setHoveredCard(null)}
+ >
+
+
+
+
+ {product.name.split("%%%")[0]}
+
+
+ {product.description}
+
+
+
+
+
+ {product.unit_type === "duration"
+ ? `Valid until: ${product.end_date
+ ? new Date(
+ product.end_date
+ ).toLocaleDateString()
+ : "N/A"
+ }`
+ : `SISA TOKEN ${product.quantity || 0}`}
+
+
+
+
+
-
-
- );
+ ))}
+
setHoveredCard(0)}
+ onMouseLeave={() => setHoveredCard(null)}
+ onClick={() => navigate('/?tab=products')
+ }
+ >
+
+ + Tambah produk baru
+
+
+
+
+ )}
+
+ {activeTab === "settings" && (
+
+ Profil
+
+
+
+
+
+
+ handleSettingsChange("username", e.target.value)
+ }
+ />
+
+
+
+
+ handleSettingsChange("email", e.target.value)
+ }
+ />
+
+
+
+
+ handleSettingsChange("name", e.target.value, true)
+ }
+ />
+
+
+
+
+ handleSettingsChange("phone", e.target.value, true)
+ }
+ />
+
+
+
+
+ handleSettingsChange("address", e.target.value, true)
+ }
+ />
+
+
+
+
+ handleSettingsChange("company", e.target.value, true)
+ }
+ />
+
+
+
+
+ handleSettingsChange("image", e.target.value, true)
+ }
+ />
+
+
+
+ Ganti password
+
+
+
+
+
+ )}
+
+ {activeTab === "orders" && (
+
+ My Orders
+ Orders list will be displayed here.
+
+ )}
+
+ );
};
-export default CoursePage;
+export default Dashboard;