Compare commits
2 Commits
b1ae4c5d82
...
bfbb750c4d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfbb750c4d | ||
|
|
4ec28f7089 |
42
src/App.js
42
src/App.js
@@ -38,7 +38,7 @@ function HomePage({
|
|||||||
|
|
||||||
if (tab === 'products') scrollToProduct();
|
if (tab === 'products') scrollToProduct();
|
||||||
if (tab === 'academy') scrollToCourse();
|
if (tab === 'academy') scrollToCourse();
|
||||||
}, [productSectionRef, courseSectionRef]);
|
}, [productSectionRef.current, courseSectionRef.current]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -194,31 +194,42 @@ function App() {
|
|||||||
if (!productModalRequest || !subscriptions) return;
|
if (!productModalRequest || !subscriptions) return;
|
||||||
|
|
||||||
const { productId, authorizedUri, unauthorizedUri } = productModalRequest;
|
const { productId, authorizedUri, unauthorizedUri } = productModalRequest;
|
||||||
console.log(subscriptions)
|
|
||||||
const hasAccess = subscriptions && subscriptions.some(
|
const hasAccess = subscriptions && subscriptions.some(
|
||||||
sub => sub.product_id === productId || sub.product_parent_id === productId
|
sub => sub.product_id === productId || sub.product_parent_id === productId
|
||||||
);
|
);
|
||||||
console.log(hasAccess)
|
console.log("hasAccess:", hasAccess);
|
||||||
|
|
||||||
if (hasAccess) {
|
if (hasAccess) {
|
||||||
if (authorizedUri) {
|
if (authorizedUri) {
|
||||||
let finalUri = decodeURIComponent(authorizedUri);
|
let finalUri = decodeURIComponent(authorizedUri);
|
||||||
const token = document.cookie.match(/(^| )token=([^;]+)/)?.[2];
|
const token = document.cookie.match(/(^| )token=([^;]+)/)?.[2];
|
||||||
|
|
||||||
if (finalUri.includes('token=null') || finalUri.includes('token=')) {
|
// --- ambil product_name distinct berdasarkan productId/parent ---
|
||||||
|
const relatedSubs = subscriptions.filter(
|
||||||
|
sub => sub.product_id === productId || sub.product_parent_id === productId
|
||||||
|
);
|
||||||
|
const distinctNames = [...new Set(relatedSubs.map(sub => sub.product_name))];
|
||||||
|
|
||||||
|
if (distinctNames.length > 1) {
|
||||||
|
// lebih dari 1 → pakai dashboard=true
|
||||||
const url = new URL(finalUri);
|
const url = new URL(finalUri);
|
||||||
url.searchParams.set('token', token || '');
|
url.searchParams.set("token", token || "");
|
||||||
|
finalUri = url.toString();
|
||||||
|
} else if (distinctNames.length === 1) {
|
||||||
|
// hanya 1 → tambahkan productName=<nama> sebelum query lain
|
||||||
|
const url = new URL(finalUri);
|
||||||
|
url.searchParams.set("token", token || "");
|
||||||
|
url.searchParams.set("productName", distinctNames[0]);
|
||||||
finalUri = url.toString();
|
finalUri = url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.href = finalUri;
|
window.location.href = finalUri;
|
||||||
}
|
} else {
|
||||||
else {// Assuming you already imported processProducts from './processProducts'
|
// fallback ambil detail produk via fetch
|
||||||
|
|
||||||
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
|
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
itemsId: [productId],
|
itemsId: [productId],
|
||||||
withChildren: true,
|
withChildren: true,
|
||||||
@@ -227,9 +238,7 @@ function App() {
|
|||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
// Process the raw data to group children under their parent
|
|
||||||
const processed = processProducts(data);
|
const processed = processProducts(data);
|
||||||
// Set the first product (which should be the parent with children nested)
|
|
||||||
setSelectedProduct(processed[0]);
|
setSelectedProduct(processed[0]);
|
||||||
setShowedModal('product');
|
setShowedModal('product');
|
||||||
}
|
}
|
||||||
@@ -240,12 +249,9 @@ function App() {
|
|||||||
if (unauthorizedUri) {
|
if (unauthorizedUri) {
|
||||||
window.location.href = decodeURIComponent(unauthorizedUri);
|
window.location.href = decodeURIComponent(unauthorizedUri);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
|
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
itemsId: [productId],
|
itemsId: [productId],
|
||||||
withChildren: true,
|
withChildren: true,
|
||||||
@@ -254,9 +260,7 @@ function App() {
|
|||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
// Process the raw data to group children under their parent
|
|
||||||
const processed = processProducts(data);
|
const processed = processProducts(data);
|
||||||
// Set the first product (which should be the parent with children nested)
|
|
||||||
setSelectedProduct(processed[0]);
|
setSelectedProduct(processed[0]);
|
||||||
setShowedModal('product');
|
setShowedModal('product');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,13 +38,36 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
|||||||
}
|
}
|
||||||
// Auto check saat user mengetik (debounce)
|
// Auto check saat user mengetik (debounce)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (product.unique_name == false) return;
|
|
||||||
|
|
||||||
const name = customName.trim();
|
const name = customName.trim();
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
setStatus('idle');
|
setStatus('idle');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isNameTaken = subscriptions?.some(sub => {
|
||||||
|
const isSameProduct = sub.product_id === product.id || sub.product_parent_id === product.id;
|
||||||
|
if (!isSameProduct) return false;
|
||||||
|
|
||||||
|
const existingName = sub.product_name?.split('%%%')[0]?.trim().toLowerCase();
|
||||||
|
return existingName === name.toLowerCase();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (subscriptions && isNameTaken) {
|
||||||
|
setStatus('unavailable');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(!product.unique_name){
|
||||||
|
setStatus('available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!product.unique_name) {
|
||||||
|
console.log(subscriptions);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
setStatus('checking');
|
setStatus('checking');
|
||||||
|
|
||||||
@@ -79,7 +102,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
|||||||
subscriptions.some(sub =>
|
subscriptions.some(sub =>
|
||||||
String(sub.product_id) === String(product.id) || String(sub.product_parent_id) === String(product.id)
|
String(sub.product_id) === String(product.id) || String(sub.product_parent_id) === String(product.id)
|
||||||
);
|
);
|
||||||
|
console.log(hasMatchingSubscription)
|
||||||
// ✅ Check subscription first
|
// ✅ Check subscription first
|
||||||
if (hasMatchingSubscription) {
|
if (hasMatchingSubscription) {
|
||||||
const matching = subscriptions.filter(sub =>
|
const matching = subscriptions.filter(sub =>
|
||||||
@@ -96,7 +119,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const itemsParam = JSON.stringify([product.id]);
|
const itemsParam = JSON.stringify([product.id]);
|
||||||
window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&set_name=${product.name}&redirect_uri=https://kediritechnopark.com/products&redirect_failed=https://kediritechnopark.com`;
|
window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&set_name=${product.name}&redirect_uri=https://kediritechnopark.com/dashboard&redirect_failed=https://kediritechnopark.com`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +131,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
|||||||
|
|
||||||
// Fallback: direct checkout
|
// Fallback: direct checkout
|
||||||
const itemsParam = JSON.stringify([product.id]);
|
const itemsParam = 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}&redirect_uri=https://kediritechnopark.com/dashboard&redirect_failed=https://kediritechnopark.com`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ Confirm child selection (final step after naming)
|
// ✅ Confirm child selection (final step after naming)
|
||||||
@@ -124,7 +147,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
|||||||
const encodedName = encodeURIComponent(customName.trim() || product.name);
|
const encodedName = encodeURIComponent(customName.trim() || product.name);
|
||||||
const itemsParam = JSON.stringify(selectedChildIds);
|
const itemsParam = JSON.stringify(selectedChildIds);
|
||||||
|
|
||||||
window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&new_name=${encodedName}&redirect_uri=https://kediritechnopark.com/products&redirect_failed=https://kediritechnopark.com`;
|
window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&new_name=${encodedName}&redirect_uri=https://kediritechnopark.com/dashboard&redirect_failed=https://kediritechnopark.com`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ User sets name first → then if product has children, show child selector
|
// ✅ User sets name first → then if product has children, show child selector
|
||||||
@@ -149,7 +172,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
|||||||
const itemsParam = JSON.stringify([product.id]);
|
const itemsParam = JSON.stringify([product.id]);
|
||||||
const encodedName = encodeURIComponent(customName.trim());
|
const encodedName = encodeURIComponent(customName.trim());
|
||||||
|
|
||||||
window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&new_name=${encodedName}&redirect_uri=https://kediritechnopark.com/products&redirect_failed=https://kediritechnopark.com`;
|
window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&new_name=${encodedName}&redirect_uri=https://kediritechnopark.com/dashboard&redirect_failed=https://kediritechnopark.com`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onConfirmSelector = () => {
|
const onConfirmSelector = () => {
|
||||||
@@ -171,14 +194,26 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
|||||||
const productName = selectedSubscription?.product_name;
|
const productName = selectedSubscription?.product_name;
|
||||||
const encodedName = encodeURIComponent(productName);
|
const encodedName = encodeURIComponent(productName);
|
||||||
|
|
||||||
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`;
|
window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&set_name=${encodedName}&redirect_uri=https://kediritechnopark.com/dashboard&redirect_failed=https://kediritechnopark.com`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (willDo === 'checkout') {
|
if (!product.executeCheckout && willDo === 'checkout') {
|
||||||
onCheckout();
|
onCheckout();
|
||||||
}
|
}
|
||||||
|
else if (product.children && product.children.length > 0) {
|
||||||
|
setShowChildSelector(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
const tokenCookie = document.cookie.split('; ').find(row => row.startsWith('token='));
|
||||||
|
const token = tokenCookie ? tokenCookie.split('=')[1] : '';
|
||||||
|
const encodedName = encodeURIComponent(product.name);
|
||||||
|
const itemsParam = JSON.stringify([product.id]);
|
||||||
|
|
||||||
|
window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&set_name=${encodedName}&redirect_uri=https://kediritechnopark.com/dashboard&redirect_failed=https://kediritechnopark.com`;
|
||||||
|
}
|
||||||
if (setWillDo) setWillDo('');
|
if (setWillDo) setWillDo('');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -318,7 +353,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
|||||||
style={{ width: '100%', padding: '8px', marginBottom: '8px', borderRadius: '10px' }}
|
style={{ width: '100%', padding: '8px', marginBottom: '8px', borderRadius: '10px' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{product.unique_name && <StatusLine />}
|
<StatusLine />
|
||||||
|
|
||||||
<div className={styles.buttonGroup} style={{ marginTop: 12 }}>
|
<div className={styles.buttonGroup} style={{ marginTop: 12 }}>
|
||||||
<button className={styles.button} onClick={() => setShowNamingInput(false)}>
|
<button className={styles.button} onClick={() => setShowNamingInput(false)}>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Container, Row, Col, Card, Button, Tabs, Tab, Form } from "react-bootstrap";
|
import { Container, Row, Col, Card, Button, Tabs, Tab, Form, ListGroup, Badge, Accordion } from "react-bootstrap";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import processProducts from '../../helper/processProducts';
|
|
||||||
|
|
||||||
const Dashboard = ({
|
const Dashboard = ({
|
||||||
subscriptions,
|
subscriptions,
|
||||||
@@ -28,7 +27,31 @@ const Dashboard = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 🔹 Handle input settings
|
const [purchaseHistory, setPurchaseHistory] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
|
||||||
|
const token = match ? match[2] : null;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
console.error("Token not found in cookies");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch("https://bot.kediritechnopark.com/webhook/store-production/history", {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (!res.ok) throw new Error("Failed to fetch purchase history");
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then((data) => setPurchaseHistory(data))
|
||||||
|
.catch((err) => console.error("Error fetching purchase history:", err));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleSettingsChange = (field, value, nested = false) => {
|
const handleSettingsChange = (field, value, nested = false) => {
|
||||||
if (nested) {
|
if (nested) {
|
||||||
setSettings((prev) => ({
|
setSettings((prev) => ({
|
||||||
@@ -40,7 +63,6 @@ const Dashboard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 🔹 Save profile
|
|
||||||
const saveSettings = () => {
|
const saveSettings = () => {
|
||||||
fetch("https://bot.kediritechnopark.com/webhook-test/user-production/data", {
|
fetch("https://bot.kediritechnopark.com/webhook-test/user-production/data", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -52,7 +74,6 @@ const Dashboard = ({
|
|||||||
.catch((err) => alert("Error updating settings: " + err));
|
.catch((err) => alert("Error updating settings: " + err));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 🔹 Group subscriptions
|
|
||||||
const groupSubscriptionsByProductName = (subs) => {
|
const groupSubscriptionsByProductName = (subs) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
subs.forEach((sub) => {
|
subs.forEach((sub) => {
|
||||||
@@ -82,7 +103,6 @@ const Dashboard = ({
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 🔹 Fetch produk berdasarkan subscription
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!subscriptions) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
@@ -96,40 +116,39 @@ const Dashboard = ({
|
|||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
const dataMap = {};
|
const dataMap = {};
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
dataMap[item.id] = { ...item, children: [] };
|
dataMap[item.id] = { ...item, children: [] };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Masukkan anak-anak ke parent-nya
|
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
if (item.sub_product_of && dataMap[item.sub_product_of]) {
|
if (item.sub_product_of && dataMap[item.sub_product_of]) {
|
||||||
dataMap[item.sub_product_of].children.push(dataMap[item.id]);
|
dataMap[item.sub_product_of].children.push(dataMap[item.id]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(dataMap)
|
|
||||||
const enrichedData = Object.values(groupedSubs)
|
const enrichedData = Object.values(groupedSubs)
|
||||||
.filter((group) => data.some((p) => p.id === group.product_id))
|
.filter((group) => data.some((p) => p.id === group.product_id))
|
||||||
.map((group) => {
|
.map((group) => {
|
||||||
const productData = data.find((p) => p.id === group.product_id);
|
const productData = data.find((p) => p.id === group.product_id);
|
||||||
let image = productData?.image || "";
|
let description = "";
|
||||||
let description = productData?.description || "";
|
console.log(productData)
|
||||||
|
let realProductName = productData?.name || "";
|
||||||
let site_url = productData?.site_url || "";
|
let site_url = productData?.site_url || "";
|
||||||
if (!image && productData?.sub_product_of) {
|
|
||||||
|
if (productData?.sub_product_of) {
|
||||||
const parent = data.find((p) => p.id === productData.sub_product_of);
|
const parent = data.find((p) => p.id === productData.sub_product_of);
|
||||||
image = parent?.image || "";
|
realProductName = parent.name;
|
||||||
description = parent?.description || "";
|
description = parent?.description || "";
|
||||||
site_url = parent?.site_url || "";
|
site_url = parent?.site_url || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
executeCheckout: group.product_name,
|
executeCheckout: group.product_name,
|
||||||
id: group.product_id,
|
id: group.product_id,
|
||||||
name: group.product_name,
|
name: group.product_name,
|
||||||
|
realProductName,
|
||||||
type: productData?.type || "product",
|
type: productData?.type || "product",
|
||||||
image,
|
|
||||||
description,
|
description,
|
||||||
site_url,
|
site_url,
|
||||||
price: productData?.price || 0,
|
price: productData?.price || 0,
|
||||||
@@ -143,26 +162,21 @@ const Dashboard = ({
|
|||||||
children: dataMap[productData?.sub_product_of]?.children || []
|
children: dataMap[productData?.sub_product_of]?.children || []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
console.log(enrichedData)
|
|
||||||
setProducts(enrichedData);
|
setProducts(enrichedData);
|
||||||
})
|
})
|
||||||
.catch((err) => console.error("Fetch error:", err));
|
.catch((err) => console.error("Fetch error:", err));
|
||||||
}, [subscriptions]);
|
}, [subscriptions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container fluid className="mt-4">
|
<Container fluid className="py-4 px-3 px-md-5">
|
||||||
<Tabs
|
<Tabs activeKey={activeTab} onSelect={(k) => setActiveTab(k)} className="mb-3">
|
||||||
activeKey={activeTab}
|
|
||||||
onSelect={(k) => setActiveTab(k)}
|
|
||||||
className="mb-3"
|
|
||||||
>
|
|
||||||
{/* Produk Saya */}
|
|
||||||
<Tab eventKey="products" title="Produk Saya">
|
<Tab eventKey="products" title="Produk Saya">
|
||||||
<Row>
|
<Row>
|
||||||
{products.map((product) => (
|
{products.map((product, i) => (
|
||||||
<Col md={4} key={product.id} className="mb-4">
|
<Col md={4} key={i} className="mb-4">
|
||||||
<Card
|
<Card
|
||||||
className={`h-100 shadow-sm ${hoveredCard === product.name ? "border-primary" : ""}`}
|
className={`h-100 shadow-sm p-2 ${hoveredCard === product.name ? "border-primary" : ""}`}
|
||||||
onMouseEnter={() => setHoveredCard(product.name)}
|
onMouseEnter={() => setHoveredCard(product.name)}
|
||||||
onMouseLeave={() => setHoveredCard(null)}
|
onMouseLeave={() => setHoveredCard(null)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -170,26 +184,23 @@ const Dashboard = ({
|
|||||||
setShowedModal("product");
|
setShowedModal("product");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{product.image && (
|
|
||||||
<Card.Img variant="top" src={product.image} />
|
|
||||||
)}
|
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title>{product.name.split("%%%")[0]}</Card.Title>
|
<Card.Title>
|
||||||
<Card.Text>{product.description}</Card.Text>
|
📦 {product.name.split("%%%")[0] + ' | ' + product.realProductName}
|
||||||
|
</Card.Title>
|
||||||
|
<Card.Text style={{ fontSize: "0.9rem", color: "#555" }}>
|
||||||
|
{product.description}
|
||||||
|
</Card.Text>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
<Card.Footer>
|
<Card.Footer className="d-flex justify-content-between align-items-center">
|
||||||
<small className="text-muted">
|
<small className="text-muted">
|
||||||
{product.unit_type === "duration"
|
{product.unit_type === "duration"
|
||||||
? `Valid until: ${product.end_date
|
? `Valid until: ${product.end_date ? new Date(product.end_date).toLocaleDateString() : "N/A"}`
|
||||||
? new Date(product.end_date).toLocaleDateString()
|
: `SISA TOKEN: ${product.quantity || 0}`}
|
||||||
: "N/A"
|
|
||||||
}`
|
|
||||||
: `SISA TOKEN ${product.quantity || 0}`}
|
|
||||||
</small>
|
</small>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="outline-primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="float-end"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedProduct(product);
|
setSelectedProduct(product);
|
||||||
setShowedModal("product");
|
setShowedModal("product");
|
||||||
@@ -203,22 +214,23 @@ const Dashboard = ({
|
|||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Tambah produk baru */}
|
|
||||||
<Col md={4} className="mb-4">
|
<Col md={4} className="mb-4">
|
||||||
<Card
|
<Card
|
||||||
className={`h-100 shadow-sm text-center align-items-center justify-content-center`}
|
className="h-100 shadow-sm d-flex justify-content-center align-items-center text-center"
|
||||||
onClick={() => navigate("/?tab=products")}
|
onClick={() => navigate("/?tab=products")}
|
||||||
>
|
>
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<h5>+ Tambah produk baru</h5>
|
<h5 style={{ color: "#007bff" }}>➕ Tambah Produk Baru</h5>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
{/* Profil Pengguna */}
|
<Tab eventKey="settings" title="Profil">
|
||||||
<Tab eventKey="settings" title="Profil Pengguna">
|
<Card className="shadow-sm border-0">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title className="mb-4">Pengaturan Profil</Card.Title>
|
||||||
<Form>
|
<Form>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={6}>
|
<Col md={6}>
|
||||||
@@ -232,7 +244,6 @@ const Dashboard = ({
|
|||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col md={6}>
|
<Col md={6}>
|
||||||
<Form.Group className="mb-3">
|
<Form.Group className="mb-3">
|
||||||
<Form.Label>Email</Form.Label>
|
<Form.Label>Email</Form.Label>
|
||||||
@@ -249,7 +260,7 @@ const Dashboard = ({
|
|||||||
<Row>
|
<Row>
|
||||||
<Col md={6}>
|
<Col md={6}>
|
||||||
<Form.Group className="mb-3">
|
<Form.Group className="mb-3">
|
||||||
<Form.Label>Full Name</Form.Label>
|
<Form.Label>Nama Lengkap</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
value={settings.profile_data.name}
|
value={settings.profile_data.name}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -260,7 +271,7 @@ const Dashboard = ({
|
|||||||
</Col>
|
</Col>
|
||||||
<Col md={6}>
|
<Col md={6}>
|
||||||
<Form.Group className="mb-3">
|
<Form.Group className="mb-3">
|
||||||
<Form.Label>Phone</Form.Label>
|
<Form.Label>No. HP</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
value={settings.profile_data.phone}
|
value={settings.profile_data.phone}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -272,8 +283,10 @@ const Dashboard = ({
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Form.Group className="mb-3">
|
<Form.Group className="mb-3">
|
||||||
<Form.Label>Address</Form.Label>
|
<Form.Label>Alamat</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
|
as="textarea"
|
||||||
|
rows={2}
|
||||||
value={settings.profile_data.address}
|
value={settings.profile_data.address}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleSettingsChange("address", e.target.value, true)
|
handleSettingsChange("address", e.target.value, true)
|
||||||
@@ -284,7 +297,7 @@ const Dashboard = ({
|
|||||||
<Row>
|
<Row>
|
||||||
<Col md={6}>
|
<Col md={6}>
|
||||||
<Form.Group className="mb-3">
|
<Form.Group className="mb-3">
|
||||||
<Form.Label>Company</Form.Label>
|
<Form.Label>Perusahaan</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
value={settings.profile_data.company}
|
value={settings.profile_data.company}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -295,7 +308,7 @@ const Dashboard = ({
|
|||||||
</Col>
|
</Col>
|
||||||
<Col md={6}>
|
<Col md={6}>
|
||||||
<Form.Group className="mb-3">
|
<Form.Group className="mb-3">
|
||||||
<Form.Label>Profile Image URL</Form.Label>
|
<Form.Label>URL Gambar Profil</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
value={settings.profile_data.image}
|
value={settings.profile_data.image}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -309,7 +322,7 @@ const Dashboard = ({
|
|||||||
<Row>
|
<Row>
|
||||||
<Col md={6}>
|
<Col md={6}>
|
||||||
<Form.Group className="mb-3">
|
<Form.Group className="mb-3">
|
||||||
<Form.Label>New Password</Form.Label>
|
<Form.Label>Password Baru</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="password"
|
type="password"
|
||||||
value={settings.password}
|
value={settings.password}
|
||||||
@@ -321,23 +334,105 @@ const Dashboard = ({
|
|||||||
</Col>
|
</Col>
|
||||||
<Col md={6}>
|
<Col md={6}>
|
||||||
<Form.Group className="mb-3">
|
<Form.Group className="mb-3">
|
||||||
<Form.Label>Re-type New Password</Form.Label>
|
<Form.Label>Ketik Ulang Password</Form.Label>
|
||||||
<Form.Control type="password" />
|
<Form.Control type="password" />
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Button variant="success" onClick={saveSettings}>
|
<Button variant="success" onClick={saveSettings}>
|
||||||
Save Changes
|
Simpan Perubahan
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Tab>
|
||||||
|
<Tab eventKey="orders" title="Pembelian">
|
||||||
|
<Card className="shadow-sm border-0">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title className="mb-4">Riwayat Pembelian</Card.Title>
|
||||||
|
|
||||||
|
{purchaseHistory.length === 0 ? (
|
||||||
|
<p className="text-muted">Tidak ada riwayat pembelian.</p>
|
||||||
|
) : (
|
||||||
|
<Accordion defaultActiveKey={null} alwaysOpen>
|
||||||
|
{purchaseHistory
|
||||||
|
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))
|
||||||
|
.map((order, idx) => {
|
||||||
|
const createdAt = new Date(order.created_at);
|
||||||
|
|
||||||
|
// Konversi status ke label yang lebih ramah pengguna
|
||||||
|
const statusLabel = {
|
||||||
|
failed: "Transaksi Dibatalkan",
|
||||||
|
pending: "Menunggu Pembayaran",
|
||||||
|
completed: "Sukses",
|
||||||
|
}[order.status] || order.status;
|
||||||
|
|
||||||
|
// Tentukan warna/status visual
|
||||||
|
const statusVariant = {
|
||||||
|
failed: "danger",
|
||||||
|
pending: "warning",
|
||||||
|
completed: "success",
|
||||||
|
}[order.status] || "secondary";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion.Item eventKey={String(idx)} key={order.transaction_id + "-" + order.detailed_transactions_id}>
|
||||||
|
<Accordion.Header>
|
||||||
|
<div className="d-flex flex-column flex-md-row justify-content-between w-100">
|
||||||
|
<div>
|
||||||
|
<strong>{order.product_name.replace(/\t/g, " ")}</strong>
|
||||||
|
<div className="text-muted small">
|
||||||
|
ID: {order.transaction_id} • {createdAt.toLocaleString("id-ID")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-md-end mt-2 mt-md-0">
|
||||||
|
<Badge bg={statusVariant} className="text-capitalize">
|
||||||
|
{statusLabel}
|
||||||
|
</Badge>
|
||||||
|
<div>
|
||||||
|
<strong className="ms-2">Rp {order.amount.toLocaleString("id-ID")}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Accordion.Header>
|
||||||
|
<Accordion.Body>
|
||||||
|
<Row>
|
||||||
|
<Col md={6}>
|
||||||
|
<p className="mb-1"><strong>Metode Pembayaran:</strong> {order.payment_method || "N/A"}</p>
|
||||||
|
<p className="mb-1"><strong>Status:</strong> {statusLabel}</p>
|
||||||
|
<p className="mb-1"><strong>Tanggal Transaksi:</strong> {createdAt.toLocaleString("id-ID")}</p>
|
||||||
|
</Col>
|
||||||
|
<Col md={6} className="text-md-end">
|
||||||
|
{order.status == 'failed' && (
|
||||||
|
<div>
|
||||||
|
<p className="text-danger small">
|
||||||
|
Jika Anda sudah melakukan pembayaran, silakan konfirmasi manual.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="outline-danger"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
alert("Fungsi konfirmasi manual belum diimplementasikan.");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Konfirmasi Manual
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Accordion.Body>
|
||||||
|
</Accordion.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Accordion>
|
||||||
|
)}
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
{/* Orders */}
|
|
||||||
<Tab eventKey="orders" title="Pembelian">
|
|
||||||
<h4>My Orders</h4>
|
|
||||||
<p>Orders list will be displayed here.</p>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user