This commit is contained in:
Vassshhh
2025-08-14 17:32:48 +07:00
parent 4dd03969b3
commit c9d2e8946b
9 changed files with 271 additions and 299 deletions

View File

@@ -20,11 +20,7 @@ import ProductsPage from './components/pages/ProductsPage';
import processProducts from './helper/processProducts'; import processProducts from './helper/processProducts';
function HomePage({ function HomePage({
hoveredCard,
setHoveredCard,
selectedProduct,
setSelectedProduct, setSelectedProduct,
showedModal,
setShowedModal, setShowedModal,
productSectionRef, productSectionRef,
courseSectionRef, courseSectionRef,
@@ -38,16 +34,12 @@ function HomePage({
<ServicesSection /> <ServicesSection />
<ProductSection <ProductSection
productSectionRef={productSectionRef} productSectionRef={productSectionRef}
hoveredCard={hoveredCard}
setHoveredCard={setHoveredCard}
setSelectedProduct={setSelectedProduct} setSelectedProduct={setSelectedProduct}
setShowedModal={setShowedModal} setShowedModal={setShowedModal}
setWillDo={setWillDo} setWillDo={setWillDo}
/> />
<AcademySection <AcademySection
courseSectionRef={courseSectionRef} courseSectionRef={courseSectionRef}
hoveredCard={hoveredCard}
setHoveredCard={setHoveredCard}
setSelectedProduct={setSelectedProduct} setSelectedProduct={setSelectedProduct}
setShowedModal={setShowedModal} setShowedModal={setShowedModal}
setWillDo={setWillDo} setWillDo={setWillDo}
@@ -76,7 +68,6 @@ function parseJwt(token) {
function App() { function App() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [hoveredCard, setHoveredCard] = useState(null);
const [subscriptions, setSubscriptions] = useState(null); const [subscriptions, setSubscriptions] = useState(null);
const [selectedProduct, setSelectedProduct] = useState({}); const [selectedProduct, setSelectedProduct] = useState({});
const [showedModal, setShowedModal] = useState(null); const [showedModal, setShowedModal] = useState(null);
@@ -303,11 +294,7 @@ function App() {
path="/" path="/"
element={ element={
<HomePage <HomePage
hoveredCard={hoveredCard}
setHoveredCard={setHoveredCard}
selectedProduct={selectedProduct}
setSelectedProduct={setSelectedProduct} setSelectedProduct={setSelectedProduct}
showedModal={showedModal}
setShowedModal={setShowedModal} setShowedModal={setShowedModal}
productSectionRef={productSectionRef} productSectionRef={productSectionRef}
courseSectionRef={courseSectionRef} courseSectionRef={courseSectionRef}
@@ -315,9 +302,11 @@ function App() {
/> />
} }
/> />
<Route path="/products" element={<ProductsPage subscriptions={subscriptions} />} /> <Route path="/dashboard" element={<ProductsPage
setShowedModal={setShowedModal}
setSelectedProduct={setSelectedProduct} subscriptions={subscriptions} />} />
<Route <Route
path="/dashboard" path="/admin"
element={ element={
<Dashboard <Dashboard
setShowedModal={(e, productId) => { setShowedModal={(e, productId) => {

View File

@@ -2,8 +2,9 @@ import React, { useEffect, useState } from 'react';
import { Container } from 'react-bootstrap'; import { Container } from 'react-bootstrap';
import styles from './Styles.module.css'; import styles from './Styles.module.css';
const AcademySection = ({hoveredCard, setHoveredCard, setSelectedProduct, setShowedModal, courseSectionRef, setWillDo}) => { const AcademySection = ({setSelectedProduct, setShowedModal, courseSectionRef, setWillDo}) => {
const [products, setProducts] = useState([]); const [products, setProducts] = useState([]);
const [hoveredCard, setHoveredCard] = useState(null);
useEffect(() => { useEffect(() => {
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', { fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {

View File

@@ -0,0 +1,191 @@
import React, { useState, useEffect } from 'react';
import { TrendingUp, TrendingDown, DollarSign, ShoppingCart, Users, Plus, GitBranchPlus } from 'lucide-react';
import styles from './Dashboard.module.css';
import processProducts from '../helper/processProducts';
/**
* Props:
* - setShowedModal: (modalName: string, productId?: string|number) => void
*/
const Dashboard = ({ setShowedModal }) => {
const [unitType, setUnitType] = useState('duration'); // kept for potential reuse
const [durationUnit, setDurationUnit] = useState('days'); // kept for potential reuse
const [availableTypes, setAvailableTypes] = useState([]);
const [availableGroups, setAvailableGroups] = useState([]);
const [selectedType, setSelectedType] = useState(null);
const [selectedGroup, setSelectedGroup] = useState(null);
const [isVisible, setIsVisible] = useState(true);
const [products, setProducts] = useState([]);
const [dashboardData, setDashboardData] = useState({
totalRevenue: { amount: 10215845, currency: 'IDR', change: 33.87, period: '22 - 29 May 2025' },
totalItemsSold: { amount: 128980, change: -33.87, period: '22 - 29 May 2025' },
totalVisitors: { amount: 2905897, change: 33.87, period: '22 - 29 May 2025' },
chartData: [
{ date: '22/06', items: 200, revenue: 800 },
{ date: '23/06', items: 750, revenue: 450 },
{ date: '24/06', items: 550, revenue: 200 },
{ date: '25/06', items: 300, revenue: 350 },
{ date: '26/06', items: 900, revenue: 450 },
{ date: '27/06', items: 550, revenue: 200 },
],
latestTransactions: []
});
useEffect(() => {
const fetchDistinctOptions = async () => {
const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
if (!match) return;
const token = match[2];
try {
const res = await fetch('https://bot.kediritechnopark.com/webhook/store-production/get-products', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});
const result = await res.json();
const productsArr = result || [];
const types = [...new Set(productsArr.map(p => p.type).filter(Boolean))];
const groups = [...new Set(productsArr.map(p => p.group).filter(Boolean))];
setAvailableTypes(types);
setAvailableGroups(groups);
setProducts(processProducts(productsArr));
} catch (err) {
console.error('Gagal ambil produk:', err);
}
};
fetchDistinctOptions();
}, []);
const formatCurrency = (amount) => new Intl.NumberFormat('id-ID').format(amount);
const getStatusClass = (status) => {
switch (status) {
case 'confirmed': return styles.statusConfirmed;
case 'waiting payment': return styles.statusWaiting;
case 'payment expired': return styles.statusExpired;
default: return styles.statusConfirmed;
}
};
const StatCard = ({ title, value, currency, change, period, icon: Icon, isNegative }) => (
<div className={styles.statCard}>
<div className={styles.statCardHeader}>
<h3 className={styles.statCardTitle}>{title}</h3>
<Icon className={styles.statCardIcon} />
</div>
<div className={styles.statCardValue}>
{currency && `${currency} `}{formatCurrency(value)}
</div>
<div className={styles.statCardFooter}>
<div className={styles.statCardChange}>
{isNegative ? (
<TrendingDown className={`${styles.trendIcon} ${styles.trendDown}`} />
) : (
<TrendingUp className={`${styles.trendIcon} ${styles.trendUp}`} />
)}
<span className={`${styles.changeText} ${isNegative ? styles.changeTextNegative : styles.changeTextPositive}`}>
{Math.abs(change)}%
</span>
<span className={styles.fromLastWeek}>from last week</span>
</div>
</div>
<div className={styles.statCardPeriod}>{period}</div>
</div>
);
const BarChart = ({ data }) => {
const maxValue = Math.max(...data.map(item => Math.max(item.items, item.revenue)));
return (
<div className={styles.barChart}>
{data.map((item, index) => (
<div key={index} className={styles.barGroup}>
<div className={styles.barContainer}>
<div className={`${styles.bar} ${styles.barItems}`} style={{ height: `${(item.items / maxValue) * 200}px` }} />
<div className={`${styles.bar} ${styles.barRevenue}`} style={{ height: `${(item.revenue / maxValue) * 200}px` }} />
</div>
<span className={styles.barLabel}>{item.date}</span>
</div>
))}
</div>
);
};
return (
<div className={styles.container}>
<div className={styles.statsGrid}>
<StatCard title="Total Revenue" value={dashboardData.totalRevenue.amount} currency="IDR" change={dashboardData.totalRevenue.change} period={dashboardData.totalRevenue.period} icon={DollarSign} isNegative={false} />
<StatCard title="Total Items Sold" value={dashboardData.totalItemsSold.amount} change={dashboardData.totalItemsSold.change} period={dashboardData.totalItemsSold.period} icon={ShoppingCart} isNegative={true} />
<StatCard title="Total Visitor" value={dashboardData.totalVisitors.amount} change={dashboardData.totalVisitors.change} period={dashboardData.totalVisitors.period} icon={Users} isNegative={false} />
</div>
<div className={styles.chartsGrid}>
{/* Tempatkan <BarChart data={dashboardData.chartData} /> jika mau ditampilkan */}
</div>
{/* Products List */}
<div className={styles.chartCard}>
<div className={styles.transactionsHeader}>
<h3 className={styles.transactionsTitle}>Products</h3>
{/* Tombol "Buat Item" → buka modal create */}
<button
type="button"
className={styles.primaryButton}
onClick={() => setShowedModal && setShowedModal('create-item')}
title="Buat produk baru"
>
<Plus size={16} style={{ marginRight: 6 }} />
Buat Item
</button>
</div>
<div className={styles.transactionsList}>
{products.map((product) => (
<div key={product.id} className={styles.transactionItem}>
<div className={styles.transactionLeft}>
<div className={styles.transactionInfo}>
<h4>{product.name}</h4>
{product.children && product.children.map((child) => (
<p key={child.id}>- {child.name}</p>
))}
</div>
</div>
<div className={styles.transactionRight}>
<span className={styles.transactionAmount}>
IDR {formatCurrency(product.price)}
</span>
<div className={`${styles.statusIndicator} ${getStatusClass(product.status)}`}></div>
<span className={styles.transactionStatus}>{product.status}</span>
{/* Tombol "Add Child" → buka modal create dengan parent product_id */}
<button
type="button"
className={styles.secondaryButton}
onClick={() => setShowedModal('create-item', product.id)}
title="Tambah sub-produk"
style={{ marginLeft: '0.75rem' }}
>
<GitBranchPlus size={16} style={{ marginRight: 6 }} />
Add Child
</button>
</div>
</div>
))}
</div>
</div>
{/* Bagian form create yang lama sudah DIPINDAH ke halaman/komponen baru */}
</div>
);
};
export default Dashboard;

View File

@@ -1,191 +0,0 @@
import React, { useState, useEffect } from 'react';
import { TrendingUp, TrendingDown, DollarSign, ShoppingCart, Users, Plus, GitBranchPlus } from 'lucide-react';
import styles from './Dashboard.module.css';
import processProducts from '../helper/processProducts';
/**
* Props:
* - setShowedModal: (modalName: string, productId?: string|number) => void
*/
const Dashboard = ({ setShowedModal }) => {
const [unitType, setUnitType] = useState('duration'); // kept for potential reuse
const [durationUnit, setDurationUnit] = useState('days'); // kept for potential reuse
const [availableTypes, setAvailableTypes] = useState([]);
const [availableGroups, setAvailableGroups] = useState([]);
const [selectedType, setSelectedType] = useState(null);
const [selectedGroup, setSelectedGroup] = useState(null);
const [isVisible, setIsVisible] = useState(true);
const [products, setProducts] = useState([]);
const [dashboardData, setDashboardData] = useState({
totalRevenue: { amount: 10215845, currency: 'IDR', change: 33.87, period: '22 - 29 May 2025' },
totalItemsSold: { amount: 128980, change: -33.87, period: '22 - 29 May 2025' },
totalVisitors: { amount: 2905897, change: 33.87, period: '22 - 29 May 2025' },
chartData: [
{ date: '22/06', items: 200, revenue: 800 },
{ date: '23/06', items: 750, revenue: 450 },
{ date: '24/06', items: 550, revenue: 200 },
{ date: '25/06', items: 300, revenue: 350 },
{ date: '26/06', items: 900, revenue: 450 },
{ date: '27/06', items: 550, revenue: 200 },
],
latestTransactions: []
});
useEffect(() => {
const fetchDistinctOptions = async () => {
const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
if (!match) return;
const token = match[2];
try {
const res = await fetch('https://bot.kediritechnopark.com/webhook/store-production/get-products', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});
const result = await res.json();
const productsArr = result || [];
const types = [...new Set(productsArr.map(p => p.type).filter(Boolean))];
const groups = [...new Set(productsArr.map(p => p.group).filter(Boolean))];
setAvailableTypes(types);
setAvailableGroups(groups);
setProducts(processProducts(productsArr));
} catch (err) {
console.error('Gagal ambil produk:', err);
}
};
fetchDistinctOptions();
}, []);
const formatCurrency = (amount) => new Intl.NumberFormat('id-ID').format(amount);
const getStatusClass = (status) => {
switch (status) {
case 'confirmed': return styles.statusConfirmed;
case 'waiting payment': return styles.statusWaiting;
case 'payment expired': return styles.statusExpired;
default: return styles.statusConfirmed;
}
};
const StatCard = ({ title, value, currency, change, period, icon: Icon, isNegative }) => (
<div className={styles.statCard}>
<div className={styles.statCardHeader}>
<h3 className={styles.statCardTitle}>{title}</h3>
<Icon className={styles.statCardIcon} />
</div>
<div className={styles.statCardValue}>
{currency && `${currency} `}{formatCurrency(value)}
</div>
<div className={styles.statCardFooter}>
<div className={styles.statCardChange}>
{isNegative ? (
<TrendingDown className={`${styles.trendIcon} ${styles.trendDown}`} />
) : (
<TrendingUp className={`${styles.trendIcon} ${styles.trendUp}`} />
)}
<span className={`${styles.changeText} ${isNegative ? styles.changeTextNegative : styles.changeTextPositive}`}>
{Math.abs(change)}%
</span>
<span className={styles.fromLastWeek}>from last week</span>
</div>
</div>
<div className={styles.statCardPeriod}>{period}</div>
</div>
);
const BarChart = ({ data }) => {
const maxValue = Math.max(...data.map(item => Math.max(item.items, item.revenue)));
return (
<div className={styles.barChart}>
{data.map((item, index) => (
<div key={index} className={styles.barGroup}>
<div className={styles.barContainer}>
<div className={`${styles.bar} ${styles.barItems}`} style={{ height: `${(item.items / maxValue) * 200}px` }} />
<div className={`${styles.bar} ${styles.barRevenue}`} style={{ height: `${(item.revenue / maxValue) * 200}px` }} />
</div>
<span className={styles.barLabel}>{item.date}</span>
</div>
))}
</div>
);
};
return (
<div className={styles.container}>
<div className={styles.statsGrid}>
<StatCard title="Total Revenue" value={dashboardData.totalRevenue.amount} currency="IDR" change={dashboardData.totalRevenue.change} period={dashboardData.totalRevenue.period} icon={DollarSign} isNegative={false} />
<StatCard title="Total Items Sold" value={dashboardData.totalItemsSold.amount} change={dashboardData.totalItemsSold.change} period={dashboardData.totalItemsSold.period} icon={ShoppingCart} isNegative={true} />
<StatCard title="Total Visitor" value={dashboardData.totalVisitors.amount} change={dashboardData.totalVisitors.change} period={dashboardData.totalVisitors.period} icon={Users} isNegative={false} />
</div>
<div className={styles.chartsGrid}>
{/* Tempatkan <BarChart data={dashboardData.chartData} /> jika mau ditampilkan */}
</div>
{/* Products List */}
<div className={styles.chartCard}>
<div className={styles.transactionsHeader}>
<h3 className={styles.transactionsTitle}>Products</h3>
{/* Tombol "Buat Item" → buka modal create */}
<button
type="button"
className={styles.primaryButton}
onClick={() => setShowedModal && setShowedModal('create-item')}
title="Buat produk baru"
>
<Plus size={16} style={{ marginRight: 6 }} />
Buat Item
</button>
</div>
<div className={styles.transactionsList}>
{products.map((product) => (
<div key={product.id} className={styles.transactionItem}>
<div className={styles.transactionLeft}>
<div className={styles.transactionInfo}>
<h4>{product.name}</h4>
{product.children && product.children.map((child) => (
<p key={child.id}>- {child.name}</p>
))}
</div>
</div>
<div className={styles.transactionRight}>
<span className={styles.transactionAmount}>
IDR {formatCurrency(product.price)}
</span>
<div className={`${styles.statusIndicator} ${getStatusClass(product.status)}`}></div>
<span className={styles.transactionStatus}>{product.status}</span>
{/* Tombol "Add Child" → buka modal create dengan parent product_id */}
<button
type="button"
className={styles.secondaryButton}
onClick={() => setShowedModal('create-item', product.id)}
title="Tambah sub-produk"
style={{ marginLeft: '0.75rem' }}
>
<GitBranchPlus size={16} style={{ marginRight: 6 }} />
Add Child
</button>
</div>
</div>
))}
</div>
</div>
{/* Bagian form create yang lama sudah DIPINDAH ke halaman/komponen baru */}
</div>
);
};
export default Dashboard;

View File

@@ -21,28 +21,41 @@ const Header = ({ username, scrollToProduct, scrollToCourse, setShowedModal, han
> >
HOME HOME
</a> </a>
<a {username &&
className={`${styles.navLink} ${hoveredNav === 3 ? styles.navLinkHover : ''}`} <a
onMouseEnter={() => setHoveredNav(3)} className={`${styles.navLink} ${hoveredNav === 3 ? styles.navLinkHover : ''}`}
onMouseLeave={() => setHoveredNav(null)} onMouseEnter={() => setHoveredNav(3)}
onClick={() => { onMouseLeave={() => setHoveredNav(null)}
if (!username) scrollToProduct(); onClick={() => {
else navigate('/products'); navigate('/dashboard');
}} }}>
> DASHBOARD
{username ? 'MY PRODUCTS' : 'PRODUCTS'} </a>
</a> }
<a {!username &&
className={`${styles.navLink} ${hoveredNav === 4 ? styles.navLinkHover : ''}`} <>
onMouseEnter={() => setHoveredNav(4)} <a
onMouseLeave={() => setHoveredNav(null)} className={`${styles.navLink} ${hoveredNav === 3 ? styles.navLinkHover : ''}`}
onClick={() => { onMouseEnter={() => setHoveredNav(3)}
if (!username) scrollToCourse(); onMouseLeave={() => setHoveredNav(null)}
else window.location.href = 'https://academy.kediritechnopark.com' onClick={() => {
}} navigate('/products');
> }}
{username ? 'MY ACADEMY' : 'ACADEMY'} >
</a> PRODUCTS
</a>
<a
className={`${styles.navLink} ${hoveredNav === 4 ? styles.navLinkHover : ''}`}
onMouseEnter={() => setHoveredNav(4)}
onMouseLeave={() => setHoveredNav(null)}
onClick={() => {
scrollToCourse();
}}
>
ACADEMY
</a>
</>
}
</nav> </nav>
{/* Burger Menu Button */} {/* Burger Menu Button */}
@@ -58,9 +71,9 @@ const Header = ({ username, scrollToProduct, scrollToCourse, setShowedModal, han
<div className={styles.username}>{username}</div> <div className={styles.username}>{username}</div>
<button className={styles.logoutButton} onClick={() => { <button className={styles.logoutButton} onClick={() => {
navigate('/products'); navigate('/dashboard');
}}> }}>
MY PRODUCTS DASHBOARD
</button> </button>
<button className={styles.logoutButton} onClick={() => { <button className={styles.logoutButton} onClick={() => {

View File

@@ -1,6 +1,8 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
const LoginRegister = ({setShowedModal}) => { const LoginRegister = ({setShowedModal}) => {
const navigate = useNavigate();
const [tab, setTab] = useState('login'); // 'login' or 'register' const [tab, setTab] = useState('login'); // 'login' or 'register'
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
@@ -103,6 +105,7 @@ const LoginRegister = ({setShowedModal}) => {
window.history.replaceState({}, '', newUrl); window.history.replaceState({}, '', newUrl);
setShowedModal('product'); setShowedModal('product');
} else { } else {
navigate('/dashboard');
window.location.reload(); window.location.reload();
} }
} else { } else {

View File

@@ -4,27 +4,28 @@ import styles from './Styles.module.css';
import processProducts from '../helper/processProducts'; import processProducts from '../helper/processProducts';
const ProductSection = ({ hoveredCard, setHoveredCard, setSelectedProduct, setShowedModal, productSectionRef, setWillDo }) => { const ProductSection = ({ setSelectedProduct, setShowedModal, productSectionRef, setWillDo }) => {
const [products, setProducts] = useState([]); const [products, setProducts] = useState([]);
// Define this function outside useEffect so it can be called anywhere const [hoveredCard, setHoveredCard] = useState(null);
// Define this function outside useEffect so it can be called anywhere
// Inside your component // Inside your component
useEffect(() => { useEffect(() => {
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({ type: 'product' }), body: JSON.stringify({ type: 'product' }),
})
.then(res => res.json())
.then(data => {
const enrichedData = processProducts(data);
setProducts(enrichedData);
}) })
.catch(err => console.error('Fetch error:', err)); .then(res => res.json())
}, []); .then(data => {
const enrichedData = processProducts(data);
setProducts(enrichedData);
})
.catch(err => console.error('Fetch error:', err));
}, []);
return ( return (
@@ -75,12 +76,12 @@ useEffect(() => {
: `Rp ${product.price.toLocaleString('id-ID')}`} : `Rp ${product.price.toLocaleString('id-ID')}`}
</span> </span>
</div> </div>
<button className="px-4 py-2 rounded-pill text-white" style={{ background: 'linear-gradient(to right, #6a59ff, #8261ee)', border: 'none' }} <button className="px-4 py-2 rounded-pill text-white" style={{ fontSize: '0.8rem', background: 'linear-gradient(to right, #6a59ff, #8261ee)', border: 'none' }}
onClick={() => { onClick={() => {
setSelectedProduct(product); setSelectedProduct(product);
setShowedModal('product'); setShowedModal('product');
setWillDo('checkout'); setWillDo('checkout');
}}>Beli</button> }}>Beli</button>
</div> </div>
</div> </div>
))} ))}

View File

@@ -304,7 +304,7 @@
} }
.currentPrice { .currentPrice {
font-size: 1.2rem; font-size: 0.9rem;
font-weight: bold; font-weight: bold;
color: #2563eb; color: #2563eb;
} }

View File

@@ -3,11 +3,8 @@ import ProductDetailPage from '../ProductDetailPage';
import Login from '../Login'; import Login from '../Login';
import styles from '../Styles.module.css'; import styles from '../Styles.module.css';
const CoursePage = ({ subscriptions }) => { const CoursePage = ({ subscriptions, setSelectedProduct, setShowedModal}) => {
const [postLoginAction, setPostLoginAction] = useState(null);
const [selectedProduct, setSelectedProduct] = useState({});
const [hoveredCard, setHoveredCard] = useState(null); const [hoveredCard, setHoveredCard] = useState(null);
const [showedModal, setShowedModal] = useState(null);
const [products, setProducts] = useState([]); const [products, setProducts] = useState([]);
// Buka modal otomatis berdasarkan query // Buka modal otomatis berdasarkan query
@@ -107,7 +104,7 @@ const CoursePage = ({ subscriptions }) => {
children: [], children: [],
}; };
}); });
console.log(enrichedData) console.log(enrichedData)
setProducts(enrichedData); setProducts(enrichedData);
}) })
.catch(err => console.error('Fetch error:', err)); .catch(err => console.error('Fetch error:', err));
@@ -156,50 +153,18 @@ const CoursePage = ({ subscriptions }) => {
: `SISA TOKEN ${product.quantity || 0}`} : `SISA TOKEN ${product.quantity || 0}`}
</span> </span>
</div> </div>
<button className="px-4 py-2 rounded-pill text-white" style={{ background: 'linear-gradient(to right, #6a59ff, #8261ee)', border: 'none' }}
onClick={() => {
setSelectedProduct(product);
setShowedModal('product');
}}>Perpanjang</button>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
</section> </section>
{/* Features Section */}
{/* ... tidak berubah ... */}
{/* Footer */}
{/* ... tidak berubah ... */}
{/* Unified Modal */}
{showedModal && (
<div
className={styles.modal}
onClick={() => {
setShowedModal(null);
setSelectedProduct({});
}}
>
<div className={styles.modalBody} onClick={(e) => e.stopPropagation()}>
{showedModal === 'product' && (
<div>
<ProductDetailPage
setPostLoginAction={setPostLoginAction}
setShowedModal={setShowedModal}
product={selectedProduct}
subscriptions={subscriptions}
onClose={() => {
setShowedModal(null);
setSelectedProduct({});
}}
/>
</div>
)}
{showedModal === 'login' && (
<Login postLoginAction={postLoginAction} setPostLoginAction={setPostLoginAction} onClose={() => setShowedModal(null)} />
)}
</div>
</div>
)
}
</div > </div >
); );
}; };