ok
This commit is contained in:
@@ -38,13 +38,36 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
||||
}
|
||||
// Auto check saat user mengetik (debounce)
|
||||
useEffect(() => {
|
||||
if (product.unique_name == false) return;
|
||||
|
||||
const name = customName.trim();
|
||||
|
||||
if (!name) {
|
||||
setStatus('idle');
|
||||
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;
|
||||
setStatus('checking');
|
||||
|
||||
@@ -79,7 +102,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
||||
subscriptions.some(sub =>
|
||||
String(sub.product_id) === String(product.id) || String(sub.product_parent_id) === String(product.id)
|
||||
);
|
||||
|
||||
console.log(hasMatchingSubscription)
|
||||
// ✅ Check subscription first
|
||||
if (hasMatchingSubscription) {
|
||||
const matching = subscriptions.filter(sub =>
|
||||
@@ -96,7 +119,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
||||
return;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -108,7 +131,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
||||
|
||||
// Fallback: direct checkout
|
||||
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)
|
||||
@@ -124,7 +147,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
||||
const encodedName = encodeURIComponent(customName.trim() || product.name);
|
||||
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
|
||||
@@ -149,7 +172,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
||||
const itemsParam = JSON.stringify([product.id]);
|
||||
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 = () => {
|
||||
@@ -171,14 +194,26 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
||||
const productName = selectedSubscription?.product_name;
|
||||
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(() => {
|
||||
if (willDo === 'checkout') {
|
||||
if (!product.executeCheckout && willDo === 'checkout') {
|
||||
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('');
|
||||
}, []);
|
||||
|
||||
@@ -318,7 +353,7 @@ const ProductDetail = ({ willDo, setWillDo, subscriptions, product, requestLogin
|
||||
style={{ width: '100%', padding: '8px', marginBottom: '8px', borderRadius: '10px' }}
|
||||
/>
|
||||
|
||||
{product.unique_name && <StatusLine />}
|
||||
<StatusLine />
|
||||
|
||||
<div className={styles.buttonGroup} style={{ marginTop: 12 }}>
|
||||
<button className={styles.button} onClick={() => setShowNamingInput(false)}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
const Dashboard = ({
|
||||
@@ -27,6 +27,31 @@ const Dashboard = ({
|
||||
}
|
||||
});
|
||||
|
||||
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) => {
|
||||
if (nested) {
|
||||
setSettings((prev) => ({
|
||||
@@ -106,24 +131,31 @@ const Dashboard = ({
|
||||
.filter((group) => data.some((p) => p.id === group.product_id))
|
||||
.map((group) => {
|
||||
const productData = data.find((p) => p.id === group.product_id);
|
||||
let description = productData?.description || "";
|
||||
let description = "";
|
||||
console.log(productData)
|
||||
let realProductName = productData?.name || "";
|
||||
if (!description && productData?.sub_product_of) {
|
||||
let site_url = productData?.site_url || "";
|
||||
|
||||
if (productData?.sub_product_of) {
|
||||
const parent = data.find((p) => p.id === productData.sub_product_of);
|
||||
description = parent?.description || "";
|
||||
realProductName = parent.name;
|
||||
description = parent?.description || "";
|
||||
site_url = parent?.site_url || "";
|
||||
}
|
||||
|
||||
return {
|
||||
executeCheckout: group.product_name,
|
||||
id: group.product_id,
|
||||
name: group.product_name,
|
||||
realProductName,
|
||||
type: productData?.type || "product",
|
||||
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,
|
||||
@@ -137,7 +169,7 @@ const Dashboard = ({
|
||||
}, [subscriptions]);
|
||||
|
||||
return (
|
||||
<Container fluid className="mt-4">
|
||||
<Container fluid className="py-4 px-3 px-md-5">
|
||||
<Tabs activeKey={activeTab} onSelect={(k) => setActiveTab(k)} className="mb-3">
|
||||
<Tab eventKey="products" title="Produk Saya">
|
||||
<Row>
|
||||
@@ -154,7 +186,7 @@ const Dashboard = ({
|
||||
>
|
||||
<Card.Body>
|
||||
<Card.Title>
|
||||
📦 {product.name.split("%%%")[0] + ' - ' + product.realProductName}
|
||||
📦 {product.name.split("%%%")[0] + ' | ' + product.realProductName}
|
||||
</Card.Title>
|
||||
<Card.Text style={{ fontSize: "0.9rem", color: "#555" }}>
|
||||
{product.description}
|
||||
@@ -169,8 +201,7 @@ const Dashboard = ({
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClick={() => {
|
||||
setSelectedProduct(product);
|
||||
setShowedModal("product");
|
||||
setWillDo("checkout");
|
||||
@@ -196,7 +227,10 @@ const Dashboard = ({
|
||||
</Row>
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="settings" title="Profil Pengguna">
|
||||
<Tab eventKey="settings" title="Profil">
|
||||
<Card className="shadow-sm border-0">
|
||||
<Card.Body>
|
||||
<Card.Title className="mb-4">Pengaturan Profil</Card.Title>
|
||||
<Form>
|
||||
<Row>
|
||||
<Col md={6}>
|
||||
@@ -226,7 +260,7 @@ const Dashboard = ({
|
||||
<Row>
|
||||
<Col md={6}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Full Name</Form.Label>
|
||||
<Form.Label>Nama Lengkap</Form.Label>
|
||||
<Form.Control
|
||||
value={settings.profile_data.name}
|
||||
onChange={(e) =>
|
||||
@@ -237,7 +271,7 @@ const Dashboard = ({
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Phone</Form.Label>
|
||||
<Form.Label>No. HP</Form.Label>
|
||||
<Form.Control
|
||||
value={settings.profile_data.phone}
|
||||
onChange={(e) =>
|
||||
@@ -249,8 +283,10 @@ const Dashboard = ({
|
||||
</Row>
|
||||
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Address</Form.Label>
|
||||
<Form.Label>Alamat</Form.Label>
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
rows={2}
|
||||
value={settings.profile_data.address}
|
||||
onChange={(e) =>
|
||||
handleSettingsChange("address", e.target.value, true)
|
||||
@@ -261,7 +297,7 @@ const Dashboard = ({
|
||||
<Row>
|
||||
<Col md={6}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Company</Form.Label>
|
||||
<Form.Label>Perusahaan</Form.Label>
|
||||
<Form.Control
|
||||
value={settings.profile_data.company}
|
||||
onChange={(e) =>
|
||||
@@ -272,7 +308,7 @@ const Dashboard = ({
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Profile Image URL</Form.Label>
|
||||
<Form.Label>URL Gambar Profil</Form.Label>
|
||||
<Form.Control
|
||||
value={settings.profile_data.image}
|
||||
onChange={(e) =>
|
||||
@@ -286,7 +322,7 @@ const Dashboard = ({
|
||||
<Row>
|
||||
<Col md={6}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>New Password</Form.Label>
|
||||
<Form.Label>Password Baru</Form.Label>
|
||||
<Form.Control
|
||||
type="password"
|
||||
value={settings.password}
|
||||
@@ -298,22 +334,105 @@ const Dashboard = ({
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<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.Group>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Button variant="success" onClick={saveSettings}>
|
||||
Save Changes
|
||||
Simpan Perubahan
|
||||
</Button>
|
||||
</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 eventKey="orders" title="Pembelian">
|
||||
<h4>My Orders</h4>
|
||||
<p>Orders list will be displayed here.</p>
|
||||
</Tab>
|
||||
|
||||
|
||||
</Tabs>
|
||||
</Container>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user