This commit is contained in:
Vassshhh
2025-08-11 16:38:35 +07:00
parent 7d3655236e
commit 82518c96aa
18 changed files with 636 additions and 417 deletions

View File

@@ -14,6 +14,7 @@ import ClientsSection from './components/ClientsSection';
import Footer from './components/Footer'; import Footer from './components/Footer';
import ProductDetailPage from './components/ProductDetailPage'; import ProductDetailPage from './components/ProductDetailPage';
import Dashboard from './components/Dashboard'; import Dashboard from './components/Dashboard';
import CreateProductPage from './components/CreateProductPage';
import ProductsPage from './components/pages/ProductsPage'; import ProductsPage from './components/pages/ProductsPage';
import processProducts from './helper/processProducts'; import processProducts from './helper/processProducts';
@@ -31,6 +32,8 @@ function HomePage({
return ( return (
<> <>
<HeroSection /> <HeroSection />
<AboutUsSection />
<ServicesSection /> <ServicesSection />
<ProductSection <ProductSection
productSectionRef={productSectionRef} productSectionRef={productSectionRef}
@@ -46,7 +49,6 @@ function HomePage({
setSelectedProduct={setSelectedProduct} setSelectedProduct={setSelectedProduct}
setShowedModal={setShowedModal} setShowedModal={setShowedModal}
/> />
<AboutUsSection />
<KnowledgeBaseSection /> <KnowledgeBaseSection />
<ClientsSection /> <ClientsSection />
</> </>
@@ -75,6 +77,7 @@ function App() {
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);
const [subProductOf, setSubProductOf] = useState(null);
const [username, setUsername] = useState(null); const [username, setUsername] = useState(null);
const productSectionRef = useRef(null); const productSectionRef = useRef(null);
@@ -103,7 +106,7 @@ function App() {
const match = document.cookie.match(new RegExp('(^| )token=([^;]+)')); const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
if (match) { if (match) {
const token = match[2]; const token = match[2];
fetch('https://bot.kediritechnopark.com/webhook/user-dev/data', { fetch('https://bot.kediritechnopark.com/webhook/user-production/data', {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -121,7 +124,11 @@ function App() {
} }
} }
}) })
.catch(err => console.error('Fetch error:', err)); .catch(err => {
document.cookie = 'token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC';
setUsername(null);
window.location.reload();
});
} }
}, []); }, []);
@@ -193,7 +200,7 @@ function App() {
} }
else {// Assuming you already imported processProducts from './processProducts' else {// Assuming you already imported processProducts from './processProducts'
fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', { fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -220,7 +227,7 @@ function App() {
window.location.href = decodeURIComponent(unauthorizedUri); window.location.href = decodeURIComponent(unauthorizedUri);
} else { } else {
fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', { fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -304,7 +311,17 @@ function App() {
} }
/> />
<Route path="/products" element={<ProductsPage subscriptions={subscriptions} />} /> <Route path="/products" element={<ProductsPage subscriptions={subscriptions} />} />
<Route path="/dashboard" element={<Dashboard />} /> <Route
path="/dashboard"
element={
<Dashboard
setShowedModal={(e, productId) => {
setShowedModal(e);
setSubProductOf(productId);
}}
/>
}
/>
</Routes> </Routes>
<Footer /> <Footer />
@@ -333,6 +350,15 @@ function App() {
setShowedModal={setShowedModal} setShowedModal={setShowedModal}
/> />
)} )}
{showedModal === 'create-item' && (
<CreateProductPage
parentId={subProductOf}
subscriptions={subscriptions}
requestLogin={requestLogin}
product={selectedProduct}
setShowedModal={setShowedModal}
/>
)}
{showedModal === 'login' && ( {showedModal === 'login' && (
<Login setShowedModal={setShowedModal} /> <Login setShowedModal={setShowedModal} />
)} )}

View File

@@ -17,8 +17,8 @@ const AboutUsSection = () => {
Dengan misi memberdayakan talenta lokal, menjembatani teknologi dan industri, serta mempercepat transformasi digital, Kediri Technopark berkomitmen menjadi penggerak kemajuan ekonomi dan teknologi, baik di tingkat lokal maupun nasional. Dengan misi memberdayakan talenta lokal, menjembatani teknologi dan industri, serta mempercepat transformasi digital, Kediri Technopark berkomitmen menjadi penggerak kemajuan ekonomi dan teknologi, baik di tingkat lokal maupun nasional.
</p> </p>
<div className="mt-4 d-flex gap-3"> <div className="mt-4 d-flex gap-3">
<Button href="konsultasi.html" className="px-4 py-2 rounded-pill text-white" style={{ background: 'linear-gradient(to right, #6a59ff, #8261ee)', border: 'none' }}> <Button href="https://instagram.com/kediri.technopark" className="px-4 py-2 rounded-pill text-white" style={{ background: 'linear-gradient(to right, #6a59ff, #8261ee)', border: 'none' }}>
Konsultasi Instagram
</Button> </Button>
<Button href="https://wa.me/6281318894994" target="_blank" variant="outline-success" className="px-4 py-2 rounded-pill"> <Button href="https://wa.me/6281318894994" target="_blank" variant="outline-success" className="px-4 py-2 rounded-pill">
<i className="fab fa-whatsapp"></i> WhatsApp <i className="fab fa-whatsapp"></i> WhatsApp

View File

@@ -6,7 +6,7 @@ const AcademySection = ({hoveredCard, setHoveredCard, setSelectedProduct, setSho
const [products, setProducts] = useState([]); const [products, setProducts] = useState([]);
useEffect(() => { useEffect(() => {
fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', { fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -20,17 +20,19 @@ const AcademySection = ({hoveredCard, setHoveredCard, setSelectedProduct, setSho
return ( return (
<section id="services" className="services pt-5"> <section id="services" className="services pt-5" ref={courseSectionRef}>
<Container> <Container>
<div className="section-heading text-center mb-4"> <div className="section-heading mb-4">
<h4>OUR <em>ACADEMY PROGRAM</em></h4> <h4>OUR <em>ACADEMY PROGRAM</em></h4>
<img src="/assets/images/heading-line-dec.png" alt="" /> <img src="/assets/images/heading-line-dec.png" alt="" />
<p>Academy Program adalah wadah belajar digital untuk anak-anak dan remaja. Didesain interaktif, kreatif, dan gratis setiap modul membekali peserta dengan keterampilan masa depan, dari teknologi dasar hingga coding dan proyek nyata.</p> <p>Academy Program adalah wadah belajar digital untuk anak-anak dan remaja. Didesain interaktif, kreatif, dan gratis setiap modul membekali peserta dengan keterampilan masa depan, dari teknologi dasar hingga coding dan proyek nyata.</p>
</div> </div>
<div className={styles.coursesGrid}> <div className={styles.coursesGrid}>
{products && {products &&
products[0]?.name && products[0]?.name &&
products.map(product => ( products
.map(product => (
<div <div
key={product.id} key={product.id}
className={`${styles.courseCard} ${hoveredCard === product.id ? styles.courseCardHover : ''}`} className={`${styles.courseCard} ${hoveredCard === product.id ? styles.courseCardHover : ''}`}
@@ -51,8 +53,8 @@ const AcademySection = ({hoveredCard, setHoveredCard, setSelectedProduct, setSho
<h3 className={styles.courseTitle}>{product.name}</h3> <h3 className={styles.courseTitle}>{product.name}</h3>
<p className={styles.courseDesc}>{product.description}</p> <p className={styles.courseDesc}>{product.description}</p>
</div> </div>
</div>
<div className={styles.courseContentBottom}> <div className={styles.courseContentBottom}>
<div className={styles.coursePrice}> <div className={styles.coursePrice}>
<span <span
className={ className={
@@ -68,7 +70,6 @@ const AcademySection = ({hoveredCard, setHoveredCard, setSelectedProduct, setSho
</div> </div>
</div> </div>
</div> </div>
</div>
))} ))}
</div> </div>
</Container> </Container>

View File

@@ -1,23 +1,35 @@
import React from 'react'; import React from 'react';
import { Container, Row, Col, Image } from 'react-bootstrap'; import { Container, Row, Col, Image } from 'react-bootstrap';
import styles from './Styles.module.css';
const ClientsSection = () => { const ClientsSection = () => {
const logos = [
'dermalounge.jpg',
'suar.avif',
'kloowear.png',
'psi.png',
];
return ( return (
<section id="clients" className="the-clients section pt-5"> <section id="clients" className="the-clients section py-5">
<Container> <Container>
<Row> <Row>
<Col lg={{ span: 8, offset: 2 }}> <Col>
<div className="section-heading text-center mb-4"> <div className="section-heading mb-4">
<h4>TRUSTED BY <em>OUR CLIENTS</em></h4> <h4>TRUSTED BY <em>OUR CLIENTS</em></h4>
<img src="/assets/images/heading-line-dec.png" alt="" className="mb-3" />
<p>We are proud to work with these amazing brands and organizations.</p> <p>We are proud to work with these amazing brands and organizations.</p>
</div> </div>
<div id="clients-carousel" className="d-flex justify-content-center flex-wrap"> <div id="clients-carousel" className="d-flex justify-content-left flex-wrap">
{[1, 2, 3, 4, 5].map((num) => ( {logos.map((logo, index) => (
<div key={num} className="client-logo-wrapper m-2"> <div className={`${styles.clientLogoWrapper} m-2`} key={index}>
<Image src={`/assets/images/client-logo${num}.png`} alt={`Client ${num}`} fluid /> <Image
src={`https://kediritechnopark.com/assets/${logo}`}
fluid
className={styles.clientLogo}
/>
</div>
))}
</div> </div>
))}</div>
</Col> </Col>
</Row> </Row>
</Container> </Container>

View File

@@ -0,0 +1,239 @@
import React, { useEffect, useState } from 'react';
import styles from './Dashboard.module.css';
const CreateProductPage = ({ parentId = null, onSuccess, onCancel }) => {
const [availableTypes, setAvailableTypes] = useState([]);
const [availableGroups, setAvailableGroups] = useState([]);
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [image, setImage] = useState('');
const [price, setPrice] = useState('');
const [unitType, setUnitType] = useState('duration');
const [durationValue, setDurationValue] = useState('');
const [durationUnit, setDurationUnit] = useState('days');
const [quantity, setQuantity] = useState('');
const [selectedType, setSelectedType] = useState('');
const [selectedGroup, setSelectedGroup] = useState('');
const [siteUrl, setSiteUrl] = useState('');
const [createUpdateUrl, setCreateUpdateUrl] = useState('');
const [isVisible, setIsVisible] = useState(true);
const [step, setStep] = useState(0);
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);
} catch (err) {
console.error('Gagal ambil produk:', err);
}
};
fetchDistinctOptions();
}, []);
const sendDataToN8N = async () => {
const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
if (!match) {
alert('Token tidak ditemukan. Silakan login kembali.');
return;
}
const token = match[2];
const isToken = unitType === 'token';
const payload = {
name,
type: selectedType,
image,
description,
price: price === '' ? null : parseInt(price, 10),
currency: 'IDR',
duration: isToken ? null : `${parseInt(durationValue || '0', 10)} ${durationUnit}`,
quantity: isToken ? parseInt(quantity || '0', 10) : null,
unit_type: unitType,
sub_product_of: parentId,
is_visible: isVisible,
group: selectedGroup || null,
site_url: siteUrl || null,
create_update_url: createUpdateUrl || null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
try {
const response = await fetch(
'https://bot.kediritechnopark.com/webhook/store-production/add-product',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(payload),
}
);
if (response.ok) {
alert('Produk berhasil ditambahkan!');
if (onSuccess) onSuccess();
} else {
const errorText = await response.text();
console.error('Response Error:', errorText);
alert('Gagal mengirim data: ' + response.status);
}
} catch (error) {
console.error('Error sending data to n8n:', error);
alert('Terjadi kesalahan saat mengirim data.');
}
};
return (
<div className={styles.chartCard}>
<h3 className={styles.transactionsTitle}>
{parentId ? 'Tambah Sub-Produk' : 'Tambah Produk Baru'}
</h3>
{step === 0 && (
<section className={styles.formSection}>
<div className={styles.formGroup}>
<label>Nama Produk</label>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
</div>
<div className={styles.formGroup}>
<label>Deskripsi</label>
<textarea rows={3} value={description} onChange={(e) => setDescription(e.target.value)} />
</div>
<div className={styles.formGroup}>
<label>URL Gambar</label>
<input type="text" value={image} onChange={(e) => setImage(e.target.value)} />
</div>
</section>
)}
{step === 1 && (
<section className={styles.formSection}>
<div className={styles.formGroup}>
<label>Harga</label>
<input type="number" value={price} onChange={(e) => setPrice(e.target.value)} />
</div>
<div className={styles.formGroup}>
<label>Jenis Unit</label>
<select value={unitType} onChange={(e) => setUnitType(e.target.value)}>
<option value="duration">Durasi</option>
<option value="token">Token</option>
</select>
</div>
{unitType === 'token' ? (
<div className={styles.formGroup}>
<label>Jumlah Token</label>
<input type="number" value={quantity} onChange={(e) => setQuantity(e.target.value)} />
</div>
) : (
<div className={styles.formGroup}>
<label>Durasi</label>
<div style={{ display: 'flex', gap: '0.5rem', width: '100%' }}>
<input type="number" style={{ width: '100%' }} value={durationValue} onChange={(e) => setDurationValue(e.target.value)} />
<select value={durationUnit} onChange={(e) => setDurationUnit(e.target.value)}>
<option value="days">Hari</option>
<option value="weeks">Minggu</option>
<option value="months">Bulan</option>
</select>
</div>
</div>
)}
<div className={styles.formGroup} style={{ display: 'flex', flexDirection: 'row', gap: '0.5rem' }}>
<input id="visible" type="checkbox" checked={isVisible} onChange={(e) => setIsVisible(e.target.checked)} />
<label htmlFor="visible" style={{ margin: 0 }}>Tampilkan produk</label>
</div>
</section>
)}
{step === 2 && (
<section className={styles.formSection}>
<div className={styles.formGroup}>
<label>Tipe Produk</label>
<input type="text" value={selectedType} onChange={(e) => setSelectedType(e.target.value)} />
<div className={styles.suggestionContainer}>
{availableTypes.map((type) => (
<button
key={type}
type="button"
className={styles.suggestionButton}
onClick={() => setSelectedType(type)}
>
{type}
</button>
))}
</div>
</div>
<div className={styles.formGroup}>
<label>Group</label>
<input type="text" value={selectedGroup} onChange={(e) => setSelectedGroup(e.target.value)} />
<div className={styles.suggestionContainer}>
{availableGroups.map((group) => (
<button
key={group}
type="button"
className={styles.suggestionButton}
onClick={() => setSelectedGroup(group)}
>
{group}
</button>
))}
</div>
</div>
<div className={styles.formGroup}>
<label>Site URL</label>
<input type="text" value={siteUrl} onChange={(e) => setSiteUrl(e.target.value)} />
</div>
<div className={styles.formGroup}>
<label>CREATE UPDATE URL</label>
<input type="text" value={createUpdateUrl} onChange={(e) => setCreateUpdateUrl(e.target.value)} />
</div>
</section>
)}
<div className={styles.formActions}>
<button type="button" className={styles.submitButton} style={{visibility: step < 1 ? 'hidden': 'visible' }} onClick={() => setStep((s) => s - 1)}>
Back
</button>
{step < 2 ? (
<button type="button" className={styles.submitButton} onClick={() => setStep((s) => s + 1)}>
Next
</button>
) : (
<button type="button" className={styles.submitButton} onClick={sendDataToN8N}>
{parentId ? 'Buat Sub-Produk' : 'Buat Produk'}
</button>
)}
</div>
</div>
);
};
export default CreateProductPage;

View File

@@ -1,12 +1,15 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { TrendingUp, TrendingDown, DollarSign, ShoppingCart, Users } from 'lucide-react'; import { TrendingUp, TrendingDown, DollarSign, ShoppingCart, Users, Plus, GitBranchPlus } from 'lucide-react';
import styles from './Dashboard.module.css'; import styles from './Dashboard.module.css';
import processProducts from '../helper/processProducts'; import processProducts from '../helper/processProducts';
/**
const Dashboard = () => { * Props:
const [unitType, setUnitType] = useState('duration'); * - setShowedModal: (modalName: string, productId?: string|number) => void
const [durationUnit, setDurationUnit] = useState('day'); */
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 [availableTypes, setAvailableTypes] = useState([]);
const [availableGroups, setAvailableGroups] = useState([]); const [availableGroups, setAvailableGroups] = useState([]);
const [selectedType, setSelectedType] = useState(null); const [selectedType, setSelectedType] = useState(null);
@@ -14,24 +17,10 @@ const Dashboard = () => {
const [isVisible, setIsVisible] = useState(true); const [isVisible, setIsVisible] = useState(true);
const [products, setProducts] = useState([]); const [products, setProducts] = useState([]);
const [dashboardData, setDashboardData] = useState({ const [dashboardData, setDashboardData] = useState({
totalRevenue: { totalRevenue: { amount: 10215845, currency: 'IDR', change: 33.87, period: '22 - 29 May 2025' },
amount: 10215845, totalItemsSold: { amount: 128980, change: -33.87, period: '22 - 29 May 2025' },
currency: 'IDR', totalVisitors: { amount: 2905897, change: 33.87, period: '22 - 29 May 2025' },
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: [ chartData: [
{ date: '22/06', items: 200, revenue: 800 }, { date: '22/06', items: 200, revenue: 800 },
{ date: '23/06', items: 750, revenue: 450 }, { date: '23/06', items: 750, revenue: 450 },
@@ -40,48 +29,7 @@ const Dashboard = () => {
{ date: '26/06', items: 900, revenue: 450 }, { date: '26/06', items: 900, revenue: 450 },
{ date: '27/06', items: 550, revenue: 200 }, { date: '27/06', items: 550, revenue: 200 },
], ],
latestTransactions: [ latestTransactions: []
{
id: 1,
name: 'Samantha William',
amount: 250875,
date: 'May 22, 2025',
status: 'confirmed',
avatar: 'SW'
},
{
id: 2,
name: 'Kevin Anderson',
amount: 350620,
date: 'May 22, 2025',
status: 'waiting payment',
avatar: 'KA'
},
{
id: 3,
name: 'Angela Samantha',
amount: 870563,
date: 'May 22, 2025',
status: 'confirmed',
avatar: 'AS'
},
{
id: 4,
name: 'Michael Smith',
amount: 653975,
date: 'May 22, 2025',
status: 'payment expired',
avatar: 'MS'
},
{
id: 5,
name: 'Jonathan Sebastian',
amount: 950000,
date: 'May 22, 2025',
status: 'confirmed',
avatar: 'JS'
}
]
}); });
useEffect(() => { useEffect(() => {
@@ -91,7 +39,7 @@ const Dashboard = () => {
const token = match[2]; const token = match[2];
try { try {
const res = await fetch('https://bot.kediritechnopark.com/webhook/store-dev/get-products', { const res = await fetch('https://bot.kediritechnopark.com/webhook/store-production/get-products', {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -99,16 +47,15 @@ const Dashboard = () => {
}, },
}); });
const result = await res.json(); // hasil berupa array produk const result = await res.json();
const products = result || []; const productsArr = result || [];
// Ambil distinct `type` dan `group` manual const types = [...new Set(productsArr.map(p => p.type).filter(Boolean))];
const types = [...new Set(products.map(p => p.type).filter(Boolean))]; const groups = [...new Set(productsArr.map(p => p.group).filter(Boolean))];
const groups = [...new Set(products.map(p => p.group).filter(Boolean))];
setAvailableTypes(types); setAvailableTypes(types);
setAvailableGroups(groups); setAvailableGroups(groups);
setProducts(processProducts(products)); setProducts(processProducts(productsArr));
} catch (err) { } catch (err) {
console.error('Gagal ambil produk:', err); console.error('Gagal ambil produk:', err);
} }
@@ -117,47 +64,6 @@ const Dashboard = () => {
fetchDistinctOptions(); fetchDistinctOptions();
}, []); }, []);
const sendDataToN8N = async (webhookUrl, data) => {
const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
if (match) {
const token = match[2];
const payload = {
...data,
duration: data.unit_type === 'token' ? null : data.duration,
quantity: data.unit_type === 'duration' ? null : data.quantity,
};
if (!token) {
alert('Token tidak ditemukan. Silakan login kembali.');
return;
}
try {
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(payload),
});
if (response.ok) {
alert('Dorm berhasil ditambahkan!');
} else {
const errorText = await response.text();
console.error('Response Error:', errorText);
alert('Gagal mengirim data: ' + response.status);
}
} catch (error) {
console.error('Error sending data to n8n:', error);
alert('Terjadi kesalahan saat mengirim data.');
}
}
};
const formatCurrency = (amount) => new Intl.NumberFormat('id-ID').format(amount); const formatCurrency = (amount) => new Intl.NumberFormat('id-ID').format(amount);
const getStatusClass = (status) => { const getStatusClass = (status) => {
@@ -221,41 +127,24 @@ const Dashboard = () => {
</div> </div>
<div className={styles.chartsGrid}> <div className={styles.chartsGrid}>
{/* Chart and Transactions UI as before */} {/* Tempatkan <BarChart data={dashboardData.chartData} /> jika mau ditampilkan */}
</div> </div>
{/* <div className={styles.chartCard}> {/* Products List */}
<div className={styles.transactionsHeader}>
<h3 className={styles.transactionsTitle}>Latest Transactions</h3>
<a href="#" className={styles.seeAllLink}>see all</a>
</div>
<div className={styles.transactionsList}>
{products.map((transaction) => (
<div key={transaction.id} className={styles.transactionItem}>
<div className={styles.transactionLeft}>
<div className={styles.transactionInfo}>
<h4>{transaction.name}</h4>
<p>on {transaction.date}</p>
</div>
</div>
<div className={styles.transactionRight}>
<span className={styles.transactionAmount}>
IDR {formatCurrency(transaction.amount)}
</span>
<div className={`${styles.statusIndicator} ${getStatusClass(transaction.status)}`}></div>
<span className={styles.transactionStatus}>
{transaction.status}
</span>
</div>
</div>
))}
</div>
</div> */}
<div className={styles.chartCard}> <div className={styles.chartCard}>
<div className={styles.transactionsHeader}> <div className={styles.transactionsHeader}>
<h3 className={styles.transactionsTitle}>Products</h3> <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>
<div className={styles.transactionsList}> <div className={styles.transactionsList}>
@@ -265,158 +154,36 @@ const Dashboard = () => {
<div className={styles.transactionInfo}> <div className={styles.transactionInfo}>
<h4>{product.name}</h4> <h4>{product.name}</h4>
{product.children && product.children.map((child) => ( {product.children && product.children.map((child) => (
<p key={child.id}>- {child.name}</p>
<p>- {child.name}</p>
))} ))}
</div> </div>
</div> </div>
<div className={styles.transactionRight}> <div className={styles.transactionRight}>
<span className={styles.transactionAmount}> <span className={styles.transactionAmount}>
IDR {formatCurrency(product.amount)} IDR {formatCurrency(product.price)}
</span> </span>
<div className={`${styles.statusIndicator} ${getStatusClass(product.status)}`}></div> <div className={`${styles.statusIndicator} ${getStatusClass(product.status)}`}></div>
<span className={styles.transactionStatus}> <span className={styles.transactionStatus}>{product.status}</span>
{product.status}
</span>
</div>
</div>
))}
</div>
</div>
<div className={styles.chartCard} style={{ marginTop: '2rem' }}>
<h3 className={styles.transactionsTitle}>Tambah Produk Baru</h3>
<form
onSubmit={(e) => {
e.preventDefault();
const form = e.target;
const isToken = unitType === 'token';
const durationValue = form.duration_value?.value;
const quantityValue = form.duration_quantity?.value;
const dormData = { {/* Tombol "Add Child" → buka modal create dengan parent product_id */}
name: form.name.value,
type: selectedType,
image: form.image.value,
description: form.description.value,
price: parseInt(form.price.value, 10),
currency: 'IDR',
duration: isToken ? null : { [durationUnit]: parseInt(durationValue, 10) },
quantity: isToken ? parseInt(quantityValue, 10) : null,
unit_type: unitType,
sub_product_of: null,
is_visible: isVisible,
group: selectedGroup,
site_url: form.site_url.value || null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
sendDataToN8N('https://bot.kediritechnopark.com/webhook/store-dev/add-product', dormData);
}}
className={styles.form}
>
<div className={styles.formGroup}>
<label>Nama Produk</label>
<input type="text" name="name" required />
</div>
<div className={styles.formGroup}>
<label>Deskripsi</label>
<textarea name="description" rows={3} required />
</div>
<div className={styles.formGroup}>
<label>Harga</label>
<input type="number" name="price" required />
</div>
<div className={styles.formGroup}>
<label>Jenis Unit</label>
<select
name="unit_type"
value={unitType}
onChange={(e) => setUnitType(e.target.value)}
required
>
<option value="duration">Durasi</option>
<option value="token">Token</option>
</select>
</div>
{unitType === 'token' ? (
<div className={styles.formGroup}>
<label>Jumlah Token</label>
<input type="number" name="duration_quantity" required min="1" />
</div>
) : (
<div className={styles.formGroup}>
<label>Durasi</label>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<input type="number" name="duration_value" min="1" required />
<select name="duration_unit" value={durationUnit} onChange={(e) => setDurationUnit(e.target.value)} required>
<option value="day">Hari</option>
<option value="week">Minggu</option>
<option value="month">Bulan</option>
</select>
</div>
</div>
)}
<div className={styles.formGroup}>
<label>URL Gambar</label>
<input type="text" name="image" />
</div>
<div className={styles.formGroup}>
<label>Site URL (opsional)</label>
<input type="text" name="site_url" />
</div>
<div className={styles.formGroup}>
<label>Tipe Produk</label>
<input
type="text"
name="type"
value={selectedType || ''}
onChange={(e) => setSelectedType(e.target.value)}
required
/>
<div className={styles.suggestionContainer}>
{availableTypes.map((type) => (
<button <button
key={type}
type="button" type="button"
className={styles.suggestionButton} className={styles.secondaryButton}
onClick={() => setSelectedType(type)} onClick={() => setShowedModal('create-item', product.id)}
title="Tambah sub-produk"
style={{ marginLeft: '0.75rem' }}
> >
{type} <GitBranchPlus size={16} style={{ marginRight: 6 }} />
Add Child
</button> </button>
</div>
</div>
))} ))}
</div> </div>
</div> </div>
<div className={styles.formGroup}> {/* Bagian form create yang lama sudah DIPINDAH ke halaman/komponen baru */}
<label>Group</label>
<input
type="text"
name="group"
value={selectedGroup || ''}
onChange={(e) => setSelectedGroup(e.target.value)}
/>
<div className={styles.suggestionContainer}>
{availableGroups.map((group) => (
<button
key={group}
type="button"
className={styles.suggestionButton}
onClick={() => setSelectedGroup(group)}
>
{group}
</button>
))}
</div>
</div>
<button type="submit" className={styles.submitButton}>Buat Produk</button>
</form>
</div>
</div> </div>
); );
}; };

View File

@@ -117,6 +117,9 @@
} }
.chartCard { .chartCard {
width: 100%;
color: black;
text-align: left;
background: white; background: white;
border-radius: 12px; border-radius: 12px;
padding: 24px; padding: 24px;
@@ -383,3 +386,10 @@
.suggestionButton:hover { .suggestionButton:hover {
background-color: #ccc; background-color: #ccc;
} }
.formActions {
margin-top: 10px;
display: flex;
justify-content: space-between;
}

View File

@@ -1,43 +1,33 @@
import React from 'react'; import React from 'react';
import { Container, Row, Col } from 'react-bootstrap'; import { Container, Row, Col } from 'react-bootstrap';
import styles from './Styles.module.css';
const Footer = () => { const Footer = () => {
return ( return (
<footer id="contact" className="bg-dark text-white py-4"> <footer id="contact" className={`bg-dark text-white py-4 ${styles.footer}`}>
<Container> <Container>
<Row className="justify-content-center"> <Row className="justify-content-center text-start">
<Col lg={6} className="text-center mb-3"> <Col lg={6} className="mb-3">
<h4>Contact Us</h4> <h4>Contact Us</h4>
<p>Sunan Giri GG. I No. 11, Rejomulyo, Kediri, Jawa Timur 64129</p> <p>Sunan Giri GG. I No. 11, Rejomulyo, Kediri, Jawa Timur 64129</p>
<p><a href="tel:+6281318894994" className="text-white">0813 1889 4994</a></p> <p><a href="tel:+6281318894994">0813 1889 4994</a></p>
<p><a href="mailto:marketing@kediritechnopark.com" className="text-white">marketing@kediritechnopark.com</a></p> <p><a href="mailto:marketing@kediritechnopark.com">marketing@kediritechnopark.com</a></p>
<p><a href="https://instagram.com/kediri.technopark" target="_blank" rel="noopener noreferrer" className="text-white">@kediri.technopark</a></p> <p><a href="https://instagram.com/kediri.technopark" target="_blank" rel="noopener noreferrer">@kediri.technopark</a></p>
<p><a href="https://kediritechnopark.com" target="_blank" rel="noopener noreferrer" className="text-white">www.KEDIRITECHNOPARK.com</a></p> <p><a href="https://kediritechnopark.com" target="_blank" rel="noopener noreferrer">www.KEDIRITECHNOPARK.com</a></p>
<div className="mt-3">
<a href="https://wa.me/6281318894994" target="_blank" rel="noopener noreferrer" className="me-3 text-white fs-4">
<i className="fab fa-whatsapp"></i>
</a>
<a href="https://instagram.com/kediri.technopark" target="_blank" rel="noopener noreferrer" className="text-white fs-4">
<i className="fab fa-instagram"></i>
</a>
</div>
</Col> </Col>
<Col lg={6} className="text-center"> <Col lg={6}>
<div className="footer-widget"> <div className="footer-widget">
<h4>About Our Company</h4> <h4>About Our Company</h4>
<div className="logo mb-3"> <div className={styles.logo}>
<img src="/assets/images/logo-white.png" alt="Logo" className="img-fluid" /> <img src="https://kediritechnopark.com/kediri-technopark-logo-white.png" alt="Logo" />
</div> </div>
<p>Kediri Technopark adalah pusat pengembangan inovasi digital dan aplikasi untuk masyarakat dan pelaku usaha.</p> <p>Kediri Technopark adalah pusat pengembangan inovasi digital dan aplikasi untuk masyarakat dan pelaku usaha.</p>
</div> </div>
</Col> </Col>
<Col lg={12} className="text-center mt-3"> <Col lg={12} className="mt-3">
<div className="copyright-text"> <div className="copyright-text">
<p> <p>&copy; 2025 Kediri Technopark. All Rights Reserved.</p>
&copy; 2025 Kediri Technopark. All Rights Reserved.<br />
Design by <a href="https://templatemo.com/" target="_blank" rel="noopener noreferrer" className="text-white">TemplateMo</a><br />
Distributed by <a href="https://themewagon.com/" target="_blank" rel="noopener noreferrer" className="text-white">ThemeWagon</a>
</p>
</div> </div>
</Col> </Col>
</Row> </Row>

View File

@@ -26,12 +26,23 @@ const Header = ({ username, scrollToProduct, scrollToCourse, setShowedModal, han
onMouseEnter={() => setHoveredNav(3)} onMouseEnter={() => setHoveredNav(3)}
onMouseLeave={() => setHoveredNav(null)} onMouseLeave={() => setHoveredNav(null)}
onClick={() => { onClick={() => {
if (!username) scrollToCourse(); if (!username) scrollToProduct();
else navigate('/products'); else navigate('/products');
}} }}
> >
{username ? 'MY PRODUCTS' : 'PRODUCTS'} {username ? 'MY PRODUCTS' : 'PRODUCTS'}
</a> </a>
<a
className={`${styles.navLink} ${hoveredNav === 3 ? styles.navLinkHover : ''}`}
onMouseEnter={() => setHoveredNav(3)}
onMouseLeave={() => setHoveredNav(null)}
onClick={() => {
if (!username) scrollToCourse();
else window.location.href = 'https://academy.kediritechnopark.com'
}}
>
{username ? 'MY ACADEMY' : 'ACADEMY'}
</a>
</nav> </nav>
{/* Burger Menu Button */} {/* Burger Menu Button */}

View File

@@ -1,21 +1,36 @@
// HeroSection.jsx — 2025 refresh using React-Bootstrap + CSS Module
import React from 'react'; import React from 'react';
import { Container, Row, Col, Button } from 'react-bootstrap'; import { Container, Row, Col, Button } from 'react-bootstrap';
import styles from './HeroSection.module.css';
const HeroSection = () => { const HeroSection = () => {
return ( return (
<section className="hero-section pt-5 bg-light"> <section className={`${styles.hero} pt-5`}
aria-label="Kediri Technopark hero section">
<Container> <Container>
<Row className="align-items-center"> <Row className="align-items-center gy-5">
<Col lg={6}> {/* Image on top for mobile, text first on lg+ */}
<h1>KATALIS KARIR DAN BISNIS DIGITAL</h1> <Col xs={{ order: 1 }} lg={{ span: 6, order: 1 }}>
<p>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.copyWrap}>
<div className="d-flex gap-3"> <h1 className={styles.title}>
<Button variant="outline-primary" href="https://instagram.com/kediri.technopark" target="_blank">Instagram</Button> KATALIS KARIR DAN BISNIS DIGITAL
<Button variant="outline-success" href="tel:+6281318894994">WhatsApp</Button> </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.
</p>
</div> </div>
</Col> </Col>
<Col lg={6}> <Col xs={{ order: 0 }} lg={{ span: 6, order: 2 }}>
<img src="https://kediritechnopark.com/assets/images/gambar1.png" alt="Hero Image" className="img-fluid" /> <div className={styles.imageWrap}>
<img
src="https://kediritechnopark.com/assets/hero.png"
alt="Ekosistem digital Kediri Technopark"
className={`img-fluid ${styles.heroImage}`}
loading="lazy"
decoding="async"
/>
<div className={styles.glow} aria-hidden="true" />
</div>
</Col> </Col>
</Row> </Row>
</Container> </Container>

View File

@@ -0,0 +1,109 @@
.hero {
position: relative;
background: radial-gradient(1200px 600px at 10% -10%, rgba(37, 99, 235, 0.15), transparent 60%),
radial-gradient(1000px 500px at 110% 10%, rgba(34, 197, 94, 0.15), transparent 60%),
var(--surface);
overflow: clip;
}
.copyWrap {
max-width: var(--hero-maxw);
}
.title {
font-weight: 800;
line-height: 1.08;
letter-spacing: -0.02em;
/* Fluid type: min 28px → max 56px */
font-size: clamp(1.75rem, 2.5vw + 1rem, 3.5rem);
background: linear-gradient(92deg, var(--text), #3b82f6 40%, #22c55e 90%);
-webkit-background-clip: text;
background-clip: text;
color: black;
}
.lead {
margin-top: 1rem;
color: var(--muted);
font-size: clamp(1rem, 0.6vw + 0.95rem, 1.2rem);
}
.ctaGroup {
margin-top: 1.25rem;
}
.cta {
--ring: 0 0 0 0 rgba(37,99,235,0);
border-radius: var(--radius-2xl) !important;
padding: 0.625rem 1rem !important;
font-weight: 600 !important;
backdrop-filter: saturate(140%);
transition: transform .2s ease, box-shadow .2s ease, background-color .2s ease;
box-shadow: var(--shadow-soft);
}
.ctaPrimary:hover,
.ctaPrimary:focus-visible {
transform: translateY(-1px);
box-shadow: 0 12px 32px rgba(37, 99, 235, 0.25);
}
.ctaSecondary:hover,
.ctaSecondary:focus-visible {
transform: translateY(-1px);
box-shadow: 0 12px 32px rgba(34, 197, 94, 0.25);
}
.imageWrap {
position: relative;
display: grid;
place-items: center;
isolation: isolate;
}
.imageWrap::before,
.imageWrap::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
width: 80px; /* lebar gradasi di sisi */
pointer-events: none; /* biar nggak ganggu klik */
z-index: 2;
}
.imageWrap::before {
left: 0;
background: linear-gradient(to right, rgba(255,255,255,1), rgba(255,255,255,0));
}
.imageWrap::after {
right: 0;
background: linear-gradient(to left, rgba(255,255,255,1), rgba(255,255,255,0));
}
.heroImage {
border-radius: var(--radius-2xl);
box-shadow: var(--shadow-soft);
}
.glow {
position: absolute;
inset: auto 10% -10% 10%;
height: 40%;
filter: blur(40px);
z-index: -1;
background: radial-gradient(60% 60% at 50% 0%, rgba(59,130,246,.35), transparent 60%),
radial-gradient(50% 50% at 30% 60%, rgba(34,197,94,.25), transparent 60%);
}
/* Fine-tuned responsive spacing */
@media (min-width: 992px) {
.copyWrap { padding-right: 1rem; }
}
@media (max-width: 575.98px) {
.hero { padding-top: 2rem; }
}

View File

@@ -6,10 +6,10 @@ const KnowledgeBaseSection = () => {
<section id="knowledge" className="knowledge section pt-5"> <section id="knowledge" className="knowledge section pt-5">
<Container> <Container>
<Row> <Row>
<Col lg={{ span: 8, offset: 2 }}> <Col >
<div className="section-heading text-center mb-4"> <div className="section-heading mb-4">
<h4>KNOWLEDGE <em>BASE</em></h4> <h4>KNOWLEDGE <em>BASE</em></h4>
<img src="/assets/images/heading-line-dec.png" alt="" className="mb-3" /> {/* <img src="/assets/images/heading-line-dec.png" alt="" className="mb-3" /> */}
<p>Berbagai artikel dan panduan untuk membantu Anda memahami teknologi dan inovasi digital.</p> <p>Berbagai artikel dan panduan untuk membantu Anda memahami teknologi dan inovasi digital.</p>
</div> </div>
<div className="knowledge-content"> <div className="knowledge-content">

View File

@@ -77,7 +77,7 @@ const LoginRegister = ({setShowedModal}) => {
} }
try { try {
const res = await fetch('https://bot.kediritechnopark.com/webhook/user-dev/login', { const res = await fetch('https://bot.kediritechnopark.com/webhook/user-production/login', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }), body: JSON.stringify({ username, password }),
@@ -122,7 +122,7 @@ const LoginRegister = ({setShowedModal}) => {
} }
try { try {
const res = await fetch('https://bot.kediritechnopark.com/webhook/user-dev/register', { const res = await fetch('https://bot.kediritechnopark.com/webhook/user-production/register', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, username, password }), body: JSON.stringify({ email, username, password }),

View File

@@ -85,6 +85,8 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
} }
} }
setShowNamingInput(true);
return;
} }
// No children, no matching subscription // No children, no matching subscription
@@ -156,20 +158,20 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
<> <>
<div className={styles.image} style={{ backgroundImage: `url(${product.image})` }}></div> <div className={styles.image} style={{ backgroundImage: `url(${product.image})` }}></div>
<div className={styles.headerRow}> <div className={styles.headerRow}>
<h2 className={styles.title}>{product.name}</h2> <h2 className={styles.title}>{product.name.split('%%%')[0]}</h2>
<div className={styles.price} style={{ color: priceColor }}> <div className={styles.price} style={{ color: priceColor }}>
{product.price == null ? 'Pay-As-You-Go' : `Rp ${parseInt(product.price).toLocaleString('id-ID')}`} {product.price == null ? 'Pay-As-You-Go' : `Rp ${parseInt(product.price).toLocaleString('id-ID')}`}
</div> </div>
</div> </div>
<p className={styles.description}>{product.description}</p> <p className={styles.description}>{product.description}</p>
<div className={styles.buttonGroup}> <div className={styles.buttonGroup}>
{product.end_date && product.site_url && ( {product.site_url && (
<button <button
className={`${styles.button} ${styles.checkoutButton}`} className={`${styles.button} ${styles.checkoutButton}`}
onClick={() => { onClick={() => {
const token = (document.cookie.split('; ').find(row => row.startsWith('token=')) || '').split('=')[1] || ''; const token = (document.cookie.split('; ').find(row => row.startsWith('token=')) || '').split('=')[1] || '';
const url = `${product.site_url}/dashboard/${product.name.toLowerCase().replace(/\s+/g, '_')}?token=${token}`; const url = `https://${product.site_url}/dashboard/${product.name.split('%%%')[0]}?token=${token}`;
window.open(url, '_blank'); window.location.href = url;
}} }}
> >
KUNJUNGI KUNJUNGI
@@ -225,7 +227,7 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
{showSubscriptionSelector && !showNamingInput && ( {showSubscriptionSelector && !showNamingInput && (
<div className={styles.childSelector}> <div className={styles.childSelector}>
<h5>Perpanjang {product.name}</h5> <h5>Perpanjang {product.name.split('%%%')[0]} </h5>
{matchingSubscriptions.map(sub => ( {matchingSubscriptions.map(sub => (
<label key={sub.id} className={styles.childProduct}> <label key={sub.id} className={styles.childProduct}>
<input <input
@@ -235,7 +237,7 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
checked={selectedSubscriptionId == sub.id} checked={selectedSubscriptionId == sub.id}
onChange={() => { setSelectedSubscriptionId(sub.id); setCustomName(sub.product_name) }} onChange={() => { setSelectedSubscriptionId(sub.id); setCustomName(sub.product_name) }}
/> />
&nbsp;{sub.product_name} &nbsp;{sub.product_name.split('%%%')[0]}
</label> </label>
))} ))}
<h6>Atau buat baru</h6> <h6>Atau buat baru</h6>
@@ -246,7 +248,7 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
checked={selectedSubscriptionId === product.id} checked={selectedSubscriptionId === product.id}
onChange={() => setSelectedSubscriptionId(product.id)} onChange={() => setSelectedSubscriptionId(product.id)}
/> />
&nbsp;Buat {product.name} baru &nbsp;Buat {product.name.split('%%%')[0]} baru
</label> </label>
<div className={styles.buttonGroup}> <div className={styles.buttonGroup}>
<button className={styles.button} onClick={() => setShowSubscriptionSelector(false)}> <button className={styles.button} onClick={() => setShowSubscriptionSelector(false)}>
@@ -261,7 +263,7 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
{showNamingInput && ( {showNamingInput && (
<div className={styles.childSelector}> <div className={styles.childSelector}>
<h5>Buat {product.name} Baru</h5> <h5>Buat {product.name.split('%%%')[0]} Baru</h5>
<input <input
type="text" type="text"
placeholder="Nama produk..." placeholder="Nama produk..."
@@ -286,7 +288,12 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
className={styles.button} className={styles.button}
onClick={() => { onClick={() => {
setShowNamingInput(false); setShowNamingInput(false);
setShowSubscriptionSelector(true);
const hasMatchingSubscription = Array.isArray(subscriptions) &&
subscriptions.some(sub =>
String(sub.product_id) === String(product.id) || String(sub.product_parent_id) === String(product.id)
);
if(hasMatchingSubscription) setShowSubscriptionSelector(true);
}} }}
> >
Kembali Kembali

View File

@@ -11,7 +11,7 @@ const ProductSection = ({ hoveredCard, setHoveredCard, setSelectedProduct, setSh
// Inside your component // Inside your component
useEffect(() => { useEffect(() => {
fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', { fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -28,9 +28,9 @@ useEffect(() => {
return ( return (
<section id="services" className="services pt-5"> <section id="services" className="services pt-5" ref={productSectionRef}>
<Container> <Container>
<div className="section-heading text-center mb-4"> <div className="section-heading mb-4">
<h4>OUR <em>PRODUCTS</em></h4> <h4>OUR <em>PRODUCTS</em></h4>
<img src="/assets/images/heading-line-dec.png" alt="" /> <img src="/assets/images/heading-line-dec.png" alt="" />
<p>Kami menyediakan berbagai solusi teknologi untuk mendukung transformasi digital bisnis dan masyarakat.</p> <p>Kami menyediakan berbagai solusi teknologi untuk mendukung transformasi digital bisnis dan masyarakat.</p>

View File

@@ -5,7 +5,7 @@ const ServicesSection = () => {
return ( return (
<section id="services" className="services py-5"> <section id="services" className="services py-5">
<Container> <Container>
<div className="section-heading text-center mb-4"> <div className="section-heading mb-4">
<h4>OUR <em>SERVICES</em></h4> <h4>OUR <em>SERVICES</em></h4>
<img src="/assets/images/heading-line-dec.png" alt="" /> <img src="/assets/images/heading-line-dec.png" alt="" />
<p>Kami menyediakan berbagai solusi teknologi untuk mendukung transformasi digital bisnis dan masyarakat.</p> <p>Kami menyediakan berbagai solusi teknologi untuk mendukung transformasi digital bisnis dan masyarakat.</p>

View File

@@ -29,7 +29,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: sticky; /* position: sticky; */
top: 0; top: 0;
z-index: 1000; z-index: 1000;
} }
@@ -217,7 +217,7 @@
.courseImage { .courseImage {
width: 100%; width: 100%;
height: 200px; height: 200px;
background-color: #e2e8f0; background-color: white;
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -225,7 +225,7 @@
color: #64748b; color: #64748b;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: cover; background-size: contain;
background-position: center; background-position: center;
} }
@@ -636,3 +636,35 @@
background-color: #2563eb; background-color: #2563eb;
color: white; color: white;
} }
.clientLogoWrapper {
max-width: 150px; /* batas lebar */
max-height: 80px; /* batas tinggi */
display: flex;
align-items: center;
justify-content: center;
}
.clientLogo {
max-height: 80px;
width: auto;
object-fit: contain;
}
.footer{
padding: 0;
}
.footer a {
color: white;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline; /* opsional kalau mau hover effect */
}
.logo img {
max-width: 150px; /* biar logo tidak terlalu besar */
height: auto;
}

View File

@@ -65,7 +65,7 @@ const CoursePage = ({ subscriptions }) => {
const groupedSubs = groupSubscriptionsByProductName(subscriptions); const groupedSubs = groupSubscriptionsByProductName(subscriptions);
const productIds = [...new Set(subscriptions.map(s => s.product_id))]; const productIds = [...new Set(subscriptions.map(s => s.product_id))];
fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', { fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -135,7 +135,7 @@ const CoursePage = ({ subscriptions }) => {
<div> <div>
<div className={styles.courseImage} style={{ backgroundImage: `url(${product.image})` }} /> <div className={styles.courseImage} style={{ backgroundImage: `url(${product.image})` }} />
<div className={styles.courseContentTop}> <div className={styles.courseContentTop}>
<h3 className={styles.courseTitle}>{product.name}</h3> <h3 className={styles.courseTitle}>{product.name.split('%%%')[0]}</h3>
<p className={styles.courseDesc}>{product.description}</p> <p className={styles.courseDesc}>{product.description}</p>
</div> </div>
</div> </div>