This commit is contained in:
Vassshhh
2025-08-25 03:47:47 +07:00
parent 4ec28f7089
commit bfbb750c4d
2 changed files with 277 additions and 123 deletions

View File

@@ -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)}>

View File

@@ -1,5 +1,5 @@
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";
const Dashboard = ({ 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) => { const handleSettingsChange = (field, value, nested = false) => {
if (nested) { if (nested) {
setSettings((prev) => ({ setSettings((prev) => ({
@@ -106,24 +131,31 @@ const Dashboard = ({
.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 description = productData?.description || ""; let description = "";
console.log(productData) console.log(productData)
let realProductName = productData?.name || ""; 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); const parent = data.find((p) => p.id === productData.sub_product_of);
description = parent?.description || "";
realProductName = parent.name; realProductName = parent.name;
description = parent?.description || "";
site_url = parent?.site_url || "";
} }
return { return {
executeCheckout: group.product_name, executeCheckout: group.product_name,
id: group.product_id,
name: group.product_name, name: group.product_name,
realProductName, realProductName,
type: productData?.type || "product", type: productData?.type || "product",
description, description,
site_url,
price: productData?.price || 0, price: productData?.price || 0,
currency: productData?.currency || "IDR", currency: productData?.currency || "IDR",
duration: productData?.duration || {}, 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, unit_type: productData?.unit_type || group.unit_type,
quantity: group.quantity, quantity: group.quantity,
end_date: group.end_date, end_date: group.end_date,
@@ -137,7 +169,7 @@ const Dashboard = ({
}, [subscriptions]); }, [subscriptions]);
return ( 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"> <Tabs activeKey={activeTab} onSelect={(k) => setActiveTab(k)} className="mb-3">
<Tab eventKey="products" title="Produk Saya"> <Tab eventKey="products" title="Produk Saya">
<Row> <Row>
@@ -154,7 +186,7 @@ const Dashboard = ({
> >
<Card.Body> <Card.Body>
<Card.Title> <Card.Title>
📦 {product.name.split("%%%")[0] + ' - ' + product.realProductName} 📦 &nbsp; {product.name.split("%%%")[0] + ' | ' + product.realProductName}
</Card.Title> </Card.Title>
<Card.Text style={{ fontSize: "0.9rem", color: "#555" }}> <Card.Text style={{ fontSize: "0.9rem", color: "#555" }}>
{product.description} {product.description}
@@ -169,8 +201,7 @@ const Dashboard = ({
<Button <Button
variant="outline-primary" variant="outline-primary"
size="sm" size="sm"
onClick={(e) => { onClick={() => {
e.stopPropagation();
setSelectedProduct(product); setSelectedProduct(product);
setShowedModal("product"); setShowedModal("product");
setWillDo("checkout"); setWillDo("checkout");
@@ -196,7 +227,10 @@ const Dashboard = ({
</Row> </Row>
</Tab> </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> <Form>
<Row> <Row>
<Col md={6}> <Col md={6}>
@@ -226,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) =>
@@ -237,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) =>
@@ -249,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)
@@ -261,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) =>
@@ -272,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) =>
@@ -286,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}
@@ -298,22 +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>
<Tab eventKey="orders" title="Pembelian">
<h4>My Orders</h4>
<p>Orders list will be displayed here.</p>
</Tab>
</Tabs> </Tabs>
</Container> </Container>
); );