Compare commits

...

7 Commits

Author SHA1 Message Date
jaya
3776a9559a ok 2025-10-09 12:38:02 +07:00
jaya
17f5685840 ok 2025-10-08 21:42:50 +07:00
everythingonblack
c7ab5db1b5 ok 2025-10-08 17:28:11 +07:00
jaya
fc934c88d8 ok 2025-09-24 16:06:06 +07:00
Vassshhhh
1a84386cdc ok 2025-09-24 14:05:15 +07:00
Vassshhh
bfbb750c4d ok 2025-08-25 03:47:47 +07:00
Vassshhh
4ec28f7089 ok 2025-08-23 16:26:47 +07:00
17 changed files with 654 additions and 762 deletions

BIN
public/assets/cafe-hore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
public/assets/kloowear.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
public/assets/psi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
public/assets/suar.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
public/maya-idle.mp4 Normal file

Binary file not shown.

View File

@@ -38,11 +38,11 @@ function HomePage({
if (tab === 'products') scrollToProduct();
if (tab === 'academy') scrollToCourse();
}, [productSectionRef, courseSectionRef]);
}, [productSectionRef.current, courseSectionRef.current]);
return (
<>
<HeroSection />
<HeroSection scrollToProduct={scrollToProduct} scrollToCourse={scrollToCourse}/>
<AboutUsSection />
<ServicesSection />
@@ -159,7 +159,7 @@ function App() {
if (unauthorizedUri) localStorage.setItem('unauthorized_uri', unauthorizedUri);
// Jika belum login, tampilkan modal login
if (!token) {
if (!token && authorizedUri) {
setShowedModal('login');
}
// Jika sudah login, tidak langsung fetch di sini — akan diproses saat subscriptions tersedia
@@ -179,7 +179,7 @@ function App() {
const token = document.cookie.match(/(^| )token=([^;]+)/)?.[2];
if (modalType === 'product' && productId) {
if (!token) {
if (!token && authorizedUri) {
setShowedModal('login'); // belum login → tampilkan login modal
} else {
// sudah login → lanjutkan proses otorisasi saat subscriptions tersedia
@@ -194,31 +194,42 @@ function App() {
if (!productModalRequest || !subscriptions) return;
const { productId, authorizedUri, unauthorizedUri } = productModalRequest;
console.log(subscriptions)
const hasAccess = subscriptions && subscriptions.some(
sub => sub.product_id === productId || sub.product_parent_id === productId
);
console.log(hasAccess)
console.log("hasAccess:", hasAccess);
if (hasAccess) {
if (authorizedUri) {
let finalUri = decodeURIComponent(authorizedUri);
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);
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();
}
window.location.href = finalUri;
}
else {// Assuming you already imported processProducts from './processProducts'
} else {
// fallback ambil detail produk via fetch
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
itemsId: [productId],
withChildren: true,
@@ -227,9 +238,7 @@ function App() {
.then(res => res.json())
.then(data => {
if (Array.isArray(data) && data.length > 0) {
// Process the raw data to group children under their parent
const processed = processProducts(data);
// Set the first product (which should be the parent with children nested)
setSelectedProduct(processed[0]);
setShowedModal('product');
}
@@ -240,12 +249,9 @@ function App() {
if (unauthorizedUri) {
window.location.href = decodeURIComponent(unauthorizedUri);
} else {
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
itemsId: [productId],
withChildren: true,
@@ -254,9 +260,7 @@ function App() {
.then(res => res.json())
.then(data => {
if (Array.isArray(data) && data.length > 0) {
// Process the raw data to group children under their parent
const processed = processProducts(data);
// Set the first product (which should be the parent with children nested)
setSelectedProduct(processed[0]);
setShowedModal('product');
}

View File

@@ -65,10 +65,7 @@ const AcademySection = ({setSelectedProduct, setShowedModal, courseSectionRef, s
<div
key={product.id}
className={`${styles.courseCard} ${hoveredCard === product.id ? styles.courseCardHover : ''}`}
onClick={() => {
setSelectedProduct(product);
setShowedModal('product');
}}
onMouseEnter={() => setHoveredCard(product.id)}
onMouseLeave={() => setHoveredCard(null)}
>

View File

@@ -5,10 +5,11 @@ import useInView from '../hooks/useInView';
const ClientsSection = () => {
const logos = [
'dermalounge.jpg',
'logo_pemprov_jatim.png',
'suar.avif',
'kloowear.png',
'kloowear.webp',
'psi.png',
'cafe-hore.png'
];
const { ref, inView } = useInView();
@@ -25,7 +26,7 @@ const ClientsSection = () => {
{logos.map((logo, index) => (
<div className={`${styles.clientLogoWrapper} m-2`} key={index}>
<Image
src={`https://kediritechnopark.com/assets/${logo}`}
src={`/assets/${logo}`}
fluid
className={styles.clientLogo}
/>

View File

@@ -1,51 +1,79 @@
// HeroSection.jsx — 2025 refresh using React-Bootstrap + CSS Module
import React from 'react';
import { Container, Row, Col, Button } from 'react-bootstrap';
import { useNavigate } from 'react-router-dom';
import styles from './HeroSection.module.css';
const HeroSection = () => {
const navigate = useNavigate();
const goProducts = () => navigate('/products');
const goAcademy = () => navigate('/#services');
import { Container, Row, Col, Button } from "react-bootstrap";
import styles from "./HeroSection.module.css";
const HeroSection = ({ scrollToProduct, scrollToCourse }) => {
return (
<section className={`${styles.hero} pt-3 pb-3`}
aria-label="Kediri Technopark hero section">
<section
className={`${styles.hero} pt-3`}
aria-label="Kediri Technopark hero section"
>
<Container className={styles.heroContainer}>
<Row className="align-items-center gy-3">
{/* Text first for mobile and desktop for clarity */}
<Col xs={{ order: 0 }} lg={{ span: 8, order: 1 }} xl={{ span: 7, order: 1 }}>
<Col
xs={{ order: 0 }}
lg={{ span: 8, order: 1 }}
xl={{ span: 7, order: 1 }}
>
<div className={styles.copyWrap}>
<h1 className={styles.title}>
KATALIS KARIR DAN BISNIS DIGITAL
</h1>
<h1 className={styles.title}>KATALIS KARIR DAN BISNIS DIGITAL</h1>
<p className={styles.lead}>
Kami adalah ekosistem tempat mimpi digital tumbuh dan masa depan dibentuk. Di sinilah semangat belajar bertemu dengan inovasi, dan ide-ide muda diberi ruang untuk berkembang. Lebih dari sekadar tempat, kami adalah rumah bagi talenta, teknologi, dan transformasi. Mari jelajahi dunia digital, bangun karir, dan ciptakan solusi semua dimulai dari sini.
Kami adalah ekosistem tempat mimpi digital tumbuh dan masa depan
dibentuk. Di sinilah semangat belajar bertemu dengan inovasi,
dan ide-ide muda diberi ruang untuk berkembang. Lebih dari
sekadar tempat, kami adalah rumah bagi talenta, teknologi, dan
transformasi. Mari jelajahi dunia digital, bangun karir, dan
ciptakan solusi semua dimulai dari sini.
</p>
<div className={styles.ctaGroup}>
<Button className={styles.ctaPrimary} onClick={goProducts}>
<Button className={styles.ctaPrimary} onClick={scrollToProduct}>
Explore Products
</Button>
<Button variant="light" className={styles.ctaSecondary} onClick={goAcademy}>
<Button
variant="light"
className={styles.ctaSecondary}
onClick={scrollToCourse}
>
View Academy
</Button>
</div>
</div>
</Col>
<Col xs={{ order: 1 }} lg={{ span: 4, order: 2 }} xl={{ span: 5, order: 2 }}>
<Col
xs={{ order: 1 }}
lg={{ span: 4, order: 2 }}
xl={{ span: 5, order: 2 }}
>
<div className={styles.imageWrap}>
<div className={styles.imageFrame}>
<img
src="https://kediritechnopark.com/assets/hero.png"
alt="Ekosistem digital Kediri Technopark"
<video
className={`img-fluid ${styles.heroImage}`}
autoPlay
muted
loop
playsInline
loading="lazy"
decoding="async"
/>
>
<source src="/maya-idle.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
</div>
<div className={styles.header}>
<h1>Perkenalkan Maya</h1>
<p>Asisten digital kami</p>
<Button
variant="light"
className={styles.ctaSecondary}
onClick={() =>
(window.location.href =
"https://mayagen-cs.kediritechnopark.com")
}
>
Bincang dengan Maya
</Button>
</div>
</Col>
</Row>
</Container>

View File

@@ -1,8 +1,15 @@
.hero {
position: relative;
background:
radial-gradient(900px 400px at 0% -10%, color-mix(in srgb, var(--brand) 12%, transparent), transparent 60%),
radial-gradient(800px 350px at 110% 0%, rgba(0,0,0,0.05), transparent 60%);
background: radial-gradient(
900px 400px at 0% -10%,
color-mix(in srgb, var(--brand) 12%, transparent),
transparent 60%
),
radial-gradient(
800px 350px at 110% 0%,
rgba(0, 0, 0, 0.05),
transparent 60%
);
overflow: visible;
min-height: clamp(300px, 40svh, 450px);
display: grid;
@@ -25,7 +32,9 @@
pointer-events: auto;
}
.kickerRow { margin-bottom: .25rem; }
.kickerRow {
margin-bottom: 0.25rem;
}
.kickerBadge {
display: inline-flex;
align-items: center;
@@ -45,7 +54,11 @@
letter-spacing: -0.02em;
/* Fluid type: min 24px → max 40px, ensure 1-line on desktop */
font-size: clamp(1.8rem, 2vw + 0.8rem, 2.8rem);
background: linear-gradient(92deg, var(--text) 0%, color-mix(in srgb, var(--brand) 70%, #0f172a) 100%);
background: linear-gradient(
92deg,
var(--text) 0%,
color-mix(in srgb, var(--brand) 70%, #0f172a) 100%
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
@@ -76,7 +89,8 @@
}
.bulletIcon {
width: 18px; height: 18px;
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid var(--brand);
box-shadow: inset 0 0 0 2px #fff;
@@ -96,8 +110,8 @@
padding: 0.45rem 0.8rem !important;
font-weight: 600 !important;
letter-spacing: 0.02em;
transition: background-color .16s ease, border-color .16s ease;
margin-right: .5rem;
transition: background-color 0.16s ease, border-color 0.16s ease;
margin-right: 0.5rem;
box-shadow: var(--shadow-neutral-s);
position: relative;
z-index: 2;
@@ -117,7 +131,8 @@
padding: 0.45rem 0.8rem !important;
font-weight: 600 !important;
letter-spacing: 0.02em;
transition: color .16s ease, border-color .16s ease, background-color .16s ease;
transition: color 0.16s ease, border-color 0.16s ease,
background-color 0.16s ease;
box-shadow: var(--shadow-neutral-s);
position: relative;
z-index: 2;
@@ -150,14 +165,29 @@
pointer-events: none;
}
.imageWrap::before, .imageWrap::after { content: none; }
.imageWrap::before,
.imageWrap::after {
content: none;
}
.imageFrame {
position: relative;
border-radius: calc(var(--radius-2xl) + 6px);
overflow: hidden;
mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
mask-image: linear-gradient(
to right,
transparent 0%,
black 10%,
black 90%,
transparent 100%
);
-webkit-mask-image: linear-gradient(
to right,
transparent 0%,
black 10%,
black 90%,
transparent 100%
);
z-index: -1;
pointer-events: none;
}
@@ -166,9 +196,141 @@
display: block;
width: 100%;
height: auto;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: calc(var(--radius-2xl) - 4px);
object-fit: contain;
}
.header {
position: absolute;
top: 233px;
right: 150px;
z-index: 10;
max-width: 450px;
text-align: left;
}
.header h1 {
font-size: 3.02rem;
font-weight: 700;
margin-bottom: 0.75rem;
line-height: 1.1;
letter-spacing: -0.01em;
white-space: nowrap;
background: linear-gradient(
120deg,
#0f172a 0%,
#1e3a8a 25%,
#2563eb 50%,
#1e40af 75%,
#0f172a 100%
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
background-size: 200% auto;
animation: gradientFlow 5s ease-in-out infinite;
filter: drop-shadow(2px 2px 4px rgba(255, 255, 255, 0.8))
drop-shadow(-1px -1px 2px rgba(255, 255, 255, 0.6))
drop-shadow(0 3px 10px rgba(37, 99, 235, 0.2));
}
@keyframes gradientFlow {
0%,
100% {
background-position: 0% center;
}
50% {
background-position: 100% center;
}
}
.header p {
font-size: 1.125rem;
color: #475569;
margin-bottom: 1.5rem;
font-weight: 500;
text-shadow: 1px 1px 3px rgba(255, 255, 255, 0.9),
0 2px 6px rgba(0, 0, 0, 0.12);
}
.ctaSecondary {
background: rgba(255, 255, 255, 0.75);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 2.5px solid #1e40af;
color: #1e40af;
padding: 0.875rem 2rem;
border-radius: 28px;
font-weight: 600;
font-size: 0.975rem;
cursor: pointer;
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 3px 10px rgba(30, 64, 175, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
display: inline-block;
position: relative;
z-index: 1;
}
.ctaSecondary:hover {
background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #2563eb 100%);
color: white;
border-color: #1e3a8a;
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(30, 58, 138, 0.4),
0 2px 8px rgba(37, 99, 235, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
.ctaSecondary:active {
transform: translateY(0);
}
/* Responsive Design */
@media (max-width: 1400px) {
.header {
right: 120px;
}
}
@media (max-width: 1200px) {
.header {
right: 80px;
max-width: 400px;
}
.header h1 {
font-size: 2.75rem;
}
}
@media (max-width: 992px) {
.header {
position: static;
max-width: 100%;
text-align: center;
padding: 2rem;
margin: 0 auto;
}
.header h1 {
font-size: 2.5rem;
white-space: normal;
}
}
@media (max-width: 768px) {
.header h1 {
font-size: 2rem;
}
.header p {
font-size: 1rem;
}
.ctaSecondary {
padding: 0.75rem 1.75rem;
}
}
.glow {
@@ -177,7 +339,11 @@
height: 40%;
filter: blur(40px);
z-index: -1;
background: radial-gradient(60% 60% at 50% 0%, color-mix(in srgb, var(--brand) 30%, transparent), transparent 60%);
background: radial-gradient(
60% 60% at 50% 0%,
color-mix(in srgb, var(--brand) 30%, transparent),
transparent 60%
);
}
.stats {
@@ -185,54 +351,117 @@
display: flex;
align-items: center;
gap: 10px;
color: #64748b; /* slate-500 */
color: #64748b;
font-size: 0.9rem;
}
.statItem strong { color: #0f172a; font-weight: 700; margin-right: 4px; }
.statDot { width: 4px; height: 4px; border-radius: 2px; background: #cbd5e1; }
.statItem strong {
color: #0f172a;
font-weight: 700;
margin-right: 4px;
}
.statDot {
width: 4px;
height: 4px;
border-radius: 2px;
background: #cbd5e1;
}
/* Fine-tuned responsive spacing */
@media (min-width: 992px) {
.copyWrap { padding-right: 1rem; }
.copyWrap {
padding-right: 1rem;
}
}
@media (max-width: 575.98px) {
.hero { padding-top: 1rem; }
.ctaGroup { display: grid; gap: 8px; }
.ctaPrimary, .ctaSecondary { width: 100% !important; text-align: center; }
.copyWrap { max-width: 100%; padding: 0 10px; }
.title { font-size: clamp(1.3rem, 2.5vw + 1rem, 1.8rem); }
.lead { font-size: clamp(0.9rem, 0.5vw + 0.8rem, 1rem); }
.hero {
padding-top: 1rem;
}
.ctaGroup {
display: grid;
gap: 8px;
}
.ctaPrimary,
.ctaSecondary {
width: 100% !important;
text-align: center;
}
.copyWrap {
max-width: 100%;
padding: 0 10px;
}
.title {
font-size: clamp(1.3rem, 2.5vw + 1rem, 1.8rem);
}
.lead {
font-size: clamp(0.9rem, 0.5vw + 0.8rem, 1rem);
}
}
@media (max-width: 767.98px) {
.imageWrap::before,
.imageWrap::after { display: none; }
.title { font-size: clamp(1.4rem, 2vw + 1rem, 2.1rem); line-height: 1.12; }
.lead { font-size: clamp(0.93rem, 0.4vw + 0.84rem, 1.03rem); }
.bulletItem { font-size: 0.92rem; }
.mesh, .grid { display: none; }
.copyWrap { max-width: 100%; padding: 0 15px; }
.imageWrap { max-width: 100%; }
.imageFrame { border-radius: calc(var(--radius-2xl) + 2px); }
.heroImage { border-radius: calc(var(--radius-2xl) - 6px); }
.imageWrap::after {
display: none;
}
.title {
font-size: clamp(1.4rem, 2vw + 1rem, 2.1rem);
line-height: 1.12;
}
.lead {
font-size: clamp(0.93rem, 0.4vw + 0.84rem, 1.03rem);
}
.bulletItem {
font-size: 0.92rem;
}
.mesh,
.grid {
display: none;
}
.copyWrap {
max-width: 100%;
padding: 0 15px;
}
.imageWrap {
max-width: 100%;
}
.imageFrame {
border-radius: calc(var(--radius-2xl) + 2px);
}
.heroImage {
border-radius: calc(var(--radius-2xl) - 6px);
}
}
.imageFrame:hover { box-shadow: none; transform: none; }
.imageFrame:hover {
box-shadow: none;
transform: none;
}
@media (min-width: 1400px) {
.imageWrap { max-width: 720px; }
.imageWrap {
max-width: 720px;
}
}
@media (max-width: 400px) {
.hero { padding-top: 0.8rem; }
.title { font-size: clamp(1.2rem, 3vw + 0.9rem, 1.7rem); }
.lead { font-size: clamp(0.85rem, 0.5vw + 0.75rem, 0.95rem); }
.ctaGroup { gap: 6px; }
.ctaPrimary, .ctaSecondary {
.hero {
padding-top: 0.8rem;
}
.title {
font-size: clamp(1.2rem, 3vw + 0.9rem, 1.7rem);
}
.lead {
font-size: clamp(0.85rem, 0.5vw + 0.75rem, 0.95rem);
}
.ctaGroup {
gap: 6px;
}
.ctaPrimary,
.ctaSecondary {
padding: 0.4rem 0.7rem !important;
font-size: 0.9rem;
}
.copyWrap { padding: 0 5px; }
.copyWrap {
padding: 0 5px;
}
}

View File

@@ -37,12 +37,12 @@ const ProductCard = ({ product, onCardClick, isCenter, canHover, onCollapse }) =
>
Detail
</button>
<button
{/* <button
className={styles.buyButton}
onClick={(e) => { e.preventDefault(); e.stopPropagation(); onCardClick && onCardClick(product); }}
>
Beli
</button>
</button> */}
</div>
</div>
</div>

View File

@@ -18,6 +18,8 @@
height: 100%;
object-fit: cover;
transition: transform 0.45s ease, filter 0.45s ease;
background-color: #f1f1f1;
object-fit: contain;
}
.canHover:hover .cover {

View File

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

View File

@@ -42,9 +42,9 @@ const ProductSection = ({ setSelectedProduct, setShowedModal, productSectionRef,
// Handle product selection for detail view
const handleViewDetail = (product, detailed) => {
console.log(product, detailed)
if(detailed) {
setSelectedProduct(product);
setShowedModal('product');
window.location.href = product.site_landing_url;
return;
}
setSelectedProduct(product);

View File

@@ -1,499 +0,0 @@
import React from 'react';
const KedaiMasterLanding = () => {
const styles = {
container: {
fontFamily: 'Arial, sans-serif',
margin: 0,
padding: 0,
background: 'linear-gradient(135deg, #e8f5e8 0%, #f0f8ff 100%)',
minHeight: '100vh'
},
header: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '1rem 2rem',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)'
},
logo: {
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
fontSize: '1.2rem',
fontWeight: 'bold',
color: '#2c3e50'
},
nav: {
display: 'flex',
gap: '2rem',
listStyle: 'none',
margin: 0,
padding: 0
},
navLink: {
textDecoration: 'none',
color: '#2c3e50',
fontSize: '0.9rem',
transition: 'color 0.3s'
},
ctaButton: {
backgroundColor: '#4a90e2',
color: 'white',
padding: '0.5rem 1.5rem',
border: 'none',
borderRadius: '25px',
fontSize: '0.9rem',
cursor: 'pointer',
transition: 'transform 0.3s'
},
hero: {
display: 'flex',
alignItems: 'center',
padding: '4rem 2rem',
maxWidth: '1200px',
margin: '0 auto'
},
heroContent: {
flex: 1,
paddingRight: '2rem'
},
heroTitle: {
fontSize: '3rem',
fontWeight: 'bold',
color: '#2c3e50',
marginBottom: '1rem',
lineHeight: '1.2'
},
heroText: {
fontSize: '1.1rem',
color: '#666',
lineHeight: '1.6',
marginBottom: '2rem'
},
heroImage: {
flex: 1,
textAlign: 'center'
},
coffeeIcon: {
fontSize: '8rem',
background: 'linear-gradient(45deg, #ffa726, #ff9800)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
padding: '2rem',
borderRadius: '20px',
backgroundColor: 'rgba(255, 167, 38, 0.1)'
},
features: {
padding: '4rem 2rem',
maxWidth: '1200px',
margin: '0 auto'
},
featuresTitle: {
textAlign: 'center',
fontSize: '2.5rem',
color: '#2c3e50',
marginBottom: '3rem'
},
featuresGrid: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
gap: '2rem',
marginBottom: '4rem'
},
featureCard: {
backgroundColor: 'rgba(255, 255, 255, 0.3)',
padding: '2rem',
borderRadius: '15px',
textAlign: 'center',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255, 255, 255, 0.2)',
transition: 'transform 0.3s'
},
featureTitle: {
fontSize: '1.3rem',
fontWeight: 'bold',
color: '#2c3e50',
marginBottom: '1rem'
},
featureText: {
color: '#666',
lineHeight: '1.5'
},
appShowcase: {
display: 'flex',
alignItems: 'center',
gap: '3rem',
marginTop: '4rem'
},
appContent: {
flex: 1
},
appTitle: {
fontSize: '1.5rem',
fontWeight: 'bold',
color: '#2c3e50',
marginBottom: '1rem'
},
appText: {
color: '#666',
lineHeight: '1.6'
},
appImages: {
flex: 1,
position: 'relative',
height: '300px'
},
phoneScreen: {
width: '200px',
height: '350px',
backgroundColor: 'white',
borderRadius: '25px',
padding: '1rem',
boxShadow: '0 10px 30px rgba(0,0,0,0.2)',
position: 'absolute',
display: 'flex',
flexDirection: 'column',
gap: '0.5rem'
},
phoneScreen1: {
left: '0',
top: '0',
zIndex: 2
},
phoneScreen2: {
right: '0',
top: '50px',
zIndex: 1
},
screenHeader: {
height: '40px',
backgroundColor: '#f0f0f0',
borderRadius: '10px',
marginBottom: '0.5rem'
},
screenContent: {
flex: 1,
backgroundColor: '#f8f8f8',
borderRadius: '10px',
padding: '0.5rem'
},
cta: {
textAlign: 'center',
padding: '4rem 2rem',
backgroundColor: 'rgba(255, 255, 255, 0.2)',
backdropFilter: 'blur(10px)'
},
ctaTitle: {
fontSize: '2rem',
color: '#2c3e50',
marginBottom: '2rem'
},
ctaButtonLarge: {
backgroundColor: '#8b4513',
color: 'white',
padding: '1rem 2rem',
border: 'none',
borderRadius: '30px',
fontSize: '1.1rem',
cursor: 'pointer',
transition: 'transform 0.3s'
},
pricing: {
padding: '4rem 2rem',
background: 'linear-gradient(135deg, #ffa726 0%, #ff9800 100%)',
textAlign: 'center'
},
pricingTitle: {
fontSize: '2.5rem',
color: 'white',
marginBottom: '3rem'
},
pricingCards: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '2rem',
maxWidth: '1000px',
margin: '0 auto'
},
pricingCard: {
backgroundColor: 'rgba(255, 255, 255, 0.95)',
padding: '2rem',
borderRadius: '15px',
position: 'relative'
},
pricingBadge: {
position: 'absolute',
top: '-10px',
left: '50%',
transform: 'translateX(-50%)',
backgroundColor: '#4caf50',
color: 'white',
padding: '0.5rem 1rem',
borderRadius: '20px',
fontSize: '0.8rem'
},
pricingPlan: {
fontSize: '1.3rem',
fontWeight: 'bold',
color: '#2c3e50',
marginBottom: '1rem'
},
pricingPrice: {
fontSize: '2rem',
fontWeight: 'bold',
color: '#4caf50',
marginBottom: '1.5rem'
},
pricingFeatures: {
listStyle: 'none',
padding: 0,
marginBottom: '2rem'
},
pricingFeature: {
padding: '0.5rem 0',
color: '#666',
borderBottom: '1px solid #eee'
},
pricingButton: {
backgroundColor: '#4caf50',
color: 'white',
padding: '0.8rem 2rem',
border: 'none',
borderRadius: '25px',
cursor: 'pointer',
width: '100%',
fontSize: '1rem'
},
footer: {
background: 'linear-gradient(135deg, #2196f3 0%, #21cbf3 100%)',
color: 'white',
padding: '4rem 2rem 2rem',
position: 'relative',
overflow: 'hidden'
},
footerWave: {
position: 'absolute',
top: '-50px',
left: 0,
width: '100%',
height: '100px',
background: 'rgba(255, 255, 255, 0.1)',
borderRadius: '50% 50% 0 0'
},
footerContent: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '3rem',
maxWidth: '1200px',
margin: '0 auto',
position: 'relative',
zIndex: 1
},
footerSection: {
textAlign: 'left'
},
footerTitle: {
fontSize: '1.5rem',
marginBottom: '1rem'
},
footerText: {
lineHeight: '1.6',
opacity: 0.9
},
copyright: {
textAlign: 'center',
marginTop: '2rem',
paddingTop: '2rem',
borderTop: '1px solid rgba(255, 255, 255, 0.2)',
opacity: 0.7
}
};
return (
<div style={styles.container}>
{/* Header */}
<header style={styles.header}>
<div style={styles.logo}>
<span>🏪</span>
<span>TECHNORAMA</span>
</div>
<nav>
<ul style={styles.nav}>
<li><a href="#" style={styles.navLink}>Home</a></li>
<li><a href="#" style={styles.navLink}>Services</a></li>
<li><a href="#" style={styles.navLink}>Product</a></li>
<li><a href="#" style={styles.navLink}>Academy</a></li>
<li><a href="#" style={styles.navLink}>About</a></li>
<li><a href="#" style={styles.navLink}>Contact</a></li>
</ul>
</nav>
<button style={styles.ctaButton}>Sign Up Now</button>
</header>
{/* Hero Section */}
<section style={styles.hero}>
<div style={styles.heroContent}>
<h1 style={styles.heroTitle}>Kedai Master</h1>
<p style={styles.heroText}>
Platform Point of Sale terdepan yang dirancang khusus untuk meningkatkan
kepuasan operational kafe dan restoran milik KM. Dengan sistem yang fleksibel,
terpercaya, dan efisien.
</p>
</div>
<div style={styles.heroImage}>
<div style={styles.coffeeIcon}></div>
</div>
</section>
{/* Features Section */}
<section style={styles.features}>
<h2 style={styles.featuresTitle}>Fitur Unggulan</h2>
<div style={styles.featuresGrid}>
<div style={styles.featureCard}>
<h3 style={styles.featureTitle}>Manajemen Tenant & Kasir</h3>
<p style={styles.featureText}>
Sistem untuk mengatur dan mengoptimalkan kinerja seluruh tenant.
</p>
</div>
<div style={styles.featureCard}>
<h3 style={styles.featureTitle}>QR Pemesanan di Meja</h3>
<p style={styles.featureText}>
Tamu restoran langsung dan mengoptimalkan waktu pemesanan dan pelayanan.
</p>
</div>
<div style={styles.featureCard}>
<h3 style={styles.featureTitle}>Otomatisasi Pesanan & Keuangan</h3>
<p style={styles.featureText}>
Mengatur operasional anda dengan otomatisasi pesanan dan keuangan.
</p>
</div>
<div style={styles.featureCard}>
<h3 style={styles.featureTitle}>Request & Voting Lagu</h3>
<p style={styles.featureText}>
Tamu dapat meminta lagu untuk diputar di restoran dan memberikan suasana.
</p>
</div>
</div>
{/* App Showcase */}
<div style={styles.appShowcase}>
<div style={styles.appContent}>
<h3 style={styles.appTitle}>
Gak perlu repot anti jam kerja yang baik bozen lagi.
Tinggal scan QR yang ada di meja, langsung bisa udah langsung workflow
</h3>
<p style={styles.appText}>
© 2025 KEDAIMASTERPBM.COM
</p>
</div>
<div style={styles.appImages}>
<div style={{...styles.phoneScreen, ...styles.phoneScreen1}}>
<div style={styles.screenHeader}></div>
<div style={styles.screenContent}></div>
</div>
<div style={{...styles.phoneScreen, ...styles.phoneScreen2}}>
<div style={styles.screenHeader}></div>
<div style={styles.screenContent}></div>
</div>
</div>
</div>
<div style={{...styles.appShowcase, flexDirection: 'row-reverse', marginTop: '4rem'}}>
<div style={styles.appContent}>
<h3 style={styles.appTitle}>Desain Menu Modern</h3>
<p style={styles.appText}>
Tampilan menu yang familiar, menarik dan mudah dipahami sehingga customer bisa dengan mudah memahami visual yang menarik untuk pengalaman ordering yang maksimal untuk kafe dan restoran masa kini.
</p>
</div>
<div style={styles.appImages}>
<div style={{...styles.phoneScreen, ...styles.phoneScreen1}}>
<div style={styles.screenHeader}></div>
<div style={styles.screenContent}></div>
</div>
</div>
</div>
</section>
{/* CTA Section */}
<section style={styles.cta}>
<h2 style={styles.ctaTitle}>Siap Tingkatkan Bisnis Anda?</h2>
<button style={styles.ctaButtonLarge}>Coba Kedai Master Sekarang</button>
</section>
{/* Pricing Section */}
<section style={styles.pricing}>
<h2 style={styles.pricingTitle}>OUR PACK KEDAI MASTER</h2>
<div style={styles.pricingCards}>
<div style={styles.pricingCard}>
<div style={styles.pricingBadge}>PAKET BASIC</div>
<h3 style={styles.pricingPlan}>Starter Pack</h3>
<div style={styles.pricingPrice}>Rp 245.000</div>
<ul style={styles.pricingFeatures}>
<li style={styles.pricingFeature}>1 user untuk admin</li>
<li style={styles.pricingFeature}>Support via email</li>
</ul>
<button style={styles.pricingButton}>Pilih Paket</button>
</div>
<div style={styles.pricingCard}>
<div style={styles.pricingBadge}>PAKET SILVER</div>
<h3 style={styles.pricingPlan}>Business Pack</h3>
<div style={styles.pricingPrice}>Rp 499.000</div>
<ul style={styles.pricingFeatures}>
<li style={styles.pricingFeature}>Integrasi Meja & Jemput</li>
<li style={styles.pricingFeature}>All permission & control</li>
<li style={styles.pricingFeature}>Unlimited locations for pemasangan</li>
</ul>
<button style={styles.pricingButton}>Pilih Paket</button>
</div>
<div style={styles.pricingCard}>
<div style={styles.pricingBadge}>PAKET GOLD</div>
<h3 style={styles.pricingPlan}>Enterprise Pack</h3>
<div style={styles.pricingPrice}>Rp 849.000</div>
<ul style={styles.pricingFeatures}>
<li style={styles.pricingFeature}>All benefits unlimited fitures &</li>
<li style={styles.pricingFeature}>Multi outlet & multi users</li>
<li style={styles.pricingFeature}>Integrasi fitur locations</li>
</ul>
<button style={styles.pricingButton}>Pilih Paket</button>
</div>
</div>
</section>
{/* Footer */}
<footer style={styles.footer}>
<div style={styles.footerWave}></div>
<div style={styles.footerContent}>
<div style={styles.footerSection}>
<h3 style={styles.footerTitle}>Contact Us</h3>
<p style={styles.footerText}>
Jalan ABC No. 123, Kota Surabaya, Jawa Timur 60123<br/>
Phone: +62 123 456 7890<br/>
Email: info@kedaimaster.com<br/>
Website: www.kedaimaster.com
</p>
</div>
<div style={styles.footerSection}>
<h3 style={styles.footerTitle}>About Our Company</h3>
<div style={styles.logo}>
<span>🏪</span>
<span>TECHNORAMA</span>
</div>
<p style={styles.footerText}>
Kami adalah perusahaan yang berfokus pada solusi teknologi untuk industri F&B.
Dengan pengalaman bertahun-tahun, kami berkomitmen memberikan layanan terbaik.
</p>
</div>
</div>
<div style={styles.copyright}>
<p>© 2025 Kedai Master by Technorama. All rights reserved.</p>
</div>
</footer>
</div>
);
};
export default KedaiMasterLanding;

View File

@@ -1,7 +1,6 @@
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 processProducts from '../../helper/processProducts';
const Dashboard = ({
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) => {
if (nested) {
setSettings((prev) => ({
@@ -40,7 +63,6 @@ const Dashboard = ({
}
};
// 🔹 Save profile
const saveSettings = () => {
fetch("https://bot.kediritechnopark.com/webhook-test/user-production/data", {
method: "PUT",
@@ -52,7 +74,6 @@ const Dashboard = ({
.catch((err) => alert("Error updating settings: " + err));
};
// 🔹 Group subscriptions
const groupSubscriptionsByProductName = (subs) => {
const result = {};
subs.forEach((sub) => {
@@ -82,7 +103,6 @@ const Dashboard = ({
return result;
};
// 🔹 Fetch produk berdasarkan subscription
useEffect(() => {
if (!subscriptions) return;
@@ -96,40 +116,39 @@ const Dashboard = ({
})
.then((res) => res.json())
.then((data) => {
console.log(data)
const dataMap = {};
data.forEach((item) => {
dataMap[item.id] = { ...item, children: [] };
});
// Masukkan anak-anak ke parent-nya
data.forEach((item) => {
if (item.sub_product_of && dataMap[item.sub_product_of]) {
dataMap[item.sub_product_of].children.push(dataMap[item.id]);
}
});
console.log(dataMap)
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 description = "";
console.log(productData)
let realProductName = productData?.name || "";
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);
image = parent?.image || "";
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",
image,
description,
site_url,
price: productData?.price || 0,
@@ -143,26 +162,21 @@ const Dashboard = ({
children: dataMap[productData?.sub_product_of]?.children || []
};
});
console.log(enrichedData)
setProducts(enrichedData);
})
.catch((err) => console.error("Fetch error:", err));
}, [subscriptions]);
return (
<Container fluid className="mt-4">
<Tabs
activeKey={activeTab}
onSelect={(k) => setActiveTab(k)}
className="mb-3"
>
{/* Produk Saya */}
<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>
{products.map((product) => (
<Col md={4} key={product.id} className="mb-4">
{products.map((product, i) => (
<Col md={4} key={i} className="mb-4">
<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)}
onMouseLeave={() => setHoveredCard(null)}
onClick={() => {
@@ -170,26 +184,23 @@ const Dashboard = ({
setShowedModal("product");
}}
>
{product.image && (
<Card.Img variant="top" src={product.image} />
)}
<Card.Body>
<Card.Title>{product.name.split("%%%")[0]}</Card.Title>
<Card.Text>{product.description}</Card.Text>
<Card.Title>
📦 &nbsp; {product.name.split("%%%")[0] + ' | ' + product.realProductName}
</Card.Title>
<Card.Text style={{ fontSize: "0.9rem", color: "#555" }}>
{product.description}
</Card.Text>
</Card.Body>
<Card.Footer>
<Card.Footer className="d-flex justify-content-between align-items-center">
<small className="text-muted">
{product.unit_type === "duration"
? `Valid until: ${product.end_date
? new Date(product.end_date).toLocaleDateString()
: "N/A"
}`
: `SISA TOKEN ${product.quantity || 0}`}
? `Valid until: ${product.end_date ? new Date(product.end_date).toLocaleDateString() : "N/A"}`
: `SISA TOKEN: ${product.quantity || 0}`}
</small>
<Button
variant="primary"
variant="outline-primary"
size="sm"
className="float-end"
onClick={() => {
setSelectedProduct(product);
setShowedModal("product");
@@ -203,22 +214,23 @@ const Dashboard = ({
</Col>
))}
{/* Tambah produk baru */}
<Col md={4} className="mb-4">
<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")}
>
<Card.Body>
<h5>+ Tambah produk baru</h5>
<h5 style={{ color: "#007bff" }}> Tambah Produk Baru</h5>
</Card.Body>
</Card>
</Col>
</Row>
</Tab>
{/* Profil Pengguna */}
<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}>
@@ -232,7 +244,6 @@ const Dashboard = ({
/>
</Form.Group>
</Col>
<Col md={6}>
<Form.Group className="mb-3">
<Form.Label>Email</Form.Label>
@@ -249,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) =>
@@ -260,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) =>
@@ -272,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)
@@ -284,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) =>
@@ -295,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) =>
@@ -309,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}
@@ -321,23 +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>
{/* Orders */}
<Tab eventKey="orders" title="Pembelian">
<h4>My Orders</h4>
<p>Orders list will be displayed here.</p>
</Tab>
</Tabs>
</Container>
);