ok
This commit is contained in:
@@ -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.
|
||||
</p>
|
||||
<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' }}>
|
||||
Konsultasi
|
||||
<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' }}>
|
||||
Instagram
|
||||
</Button>
|
||||
<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
|
||||
|
||||
@@ -6,7 +6,7 @@ const AcademySection = ({hoveredCard, setHoveredCard, setSelectedProduct, setSho
|
||||
const [products, setProducts] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', {
|
||||
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -20,56 +20,57 @@ const AcademySection = ({hoveredCard, setHoveredCard, setSelectedProduct, setSho
|
||||
|
||||
return (
|
||||
|
||||
<section id="services" className="services pt-5">
|
||||
<section id="services" className="services pt-5" ref={courseSectionRef}>
|
||||
<Container>
|
||||
<div className="section-heading text-center mb-4">
|
||||
<div className="section-heading mb-4">
|
||||
<h4>OUR <em>ACADEMY PROGRAM</em></h4>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className={styles.coursesGrid}>
|
||||
{products &&
|
||||
products[0]?.name &&
|
||||
products.map(product => (
|
||||
<div
|
||||
key={product.id}
|
||||
className={`${styles.courseCard} ${hoveredCard === product.id ? styles.courseCardHover : ''}`}
|
||||
onClick={() => {
|
||||
setSelectedProduct(product);
|
||||
setShowedModal('product');
|
||||
}}
|
||||
onMouseEnter={() => setHoveredCard(product.id)}
|
||||
onMouseLeave={() => setHoveredCard(null)}
|
||||
>
|
||||
<div>
|
||||
<div className={styles.courseImage} style={{ backgroundImage: `url(${product.image})` }}>
|
||||
{product.price === 0 && (
|
||||
<span className={styles.courseLabel}>Free</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.courseContentTop}>
|
||||
<h3 className={styles.courseTitle}>{product.name}</h3>
|
||||
<p className={styles.courseDesc}>{product.description}</p>
|
||||
products
|
||||
.map(product => (
|
||||
<div
|
||||
key={product.id}
|
||||
className={`${styles.courseCard} ${hoveredCard === product.id ? styles.courseCardHover : ''}`}
|
||||
onClick={() => {
|
||||
setSelectedProduct(product);
|
||||
setShowedModal('product');
|
||||
}}
|
||||
onMouseEnter={() => setHoveredCard(product.id)}
|
||||
onMouseLeave={() => setHoveredCard(null)}
|
||||
>
|
||||
<div>
|
||||
<div className={styles.courseImage} style={{ backgroundImage: `url(${product.image})` }}>
|
||||
{product.price === 0 && (
|
||||
<span className={styles.courseLabel}>Free</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.courseContentTop}>
|
||||
<h3 className={styles.courseTitle}>{product.name}</h3>
|
||||
<p className={styles.courseDesc}>{product.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.courseContentBottom}>
|
||||
|
||||
<div className={styles.coursePrice}>
|
||||
<span
|
||||
className={
|
||||
product.price === 0
|
||||
? styles.freePrice
|
||||
: styles.currentPrice
|
||||
}
|
||||
>
|
||||
{product.price == null
|
||||
? 'Pay-As-You-Go'
|
||||
: `Rp ${product.price.toLocaleString('id-ID')}`}
|
||||
</span>
|
||||
<div className={styles.courseContentBottom}>
|
||||
<div className={styles.coursePrice}>
|
||||
<span
|
||||
className={
|
||||
product.price === 0
|
||||
? styles.freePrice
|
||||
: styles.currentPrice
|
||||
}
|
||||
>
|
||||
{product.price == null
|
||||
? 'Pay-As-You-Go'
|
||||
: `Rp ${product.price.toLocaleString('id-ID')}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Container, Row, Col, Image } from 'react-bootstrap';
|
||||
import styles from './Styles.module.css';
|
||||
|
||||
const ClientsSection = () => {
|
||||
const logos = [
|
||||
'dermalounge.jpg',
|
||||
'suar.avif',
|
||||
'kloowear.png',
|
||||
'psi.png',
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="clients" className="the-clients section pt-5">
|
||||
<section id="clients" className="the-clients section py-5">
|
||||
<Container>
|
||||
<Row>
|
||||
<Col lg={{ span: 8, offset: 2 }}>
|
||||
<div className="section-heading text-center mb-4">
|
||||
<Col>
|
||||
<div className="section-heading mb-4">
|
||||
<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>
|
||||
</div>
|
||||
<div id="clients-carousel" className="d-flex justify-content-center flex-wrap">
|
||||
{[1, 2, 3, 4, 5].map((num) => (
|
||||
<div key={num} className="client-logo-wrapper m-2">
|
||||
<Image src={`/assets/images/client-logo${num}.png`} alt={`Client ${num}`} fluid />
|
||||
<div id="clients-carousel" className="d-flex justify-content-left flex-wrap">
|
||||
{logos.map((logo, index) => (
|
||||
<div className={`${styles.clientLogoWrapper} m-2`} key={index}>
|
||||
<Image
|
||||
src={`https://kediritechnopark.com/assets/${logo}`}
|
||||
fluid
|
||||
className={styles.clientLogo}
|
||||
/>
|
||||
</div>
|
||||
))}</div>
|
||||
))}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
@@ -25,4 +37,4 @@ const ClientsSection = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ClientsSection;
|
||||
export default ClientsSection;
|
||||
|
||||
239
src/components/CreateProductPage.js
Normal file
239
src/components/CreateProductPage.js
Normal 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;
|
||||
@@ -1,12 +1,15 @@
|
||||
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 processProducts from '../helper/processProducts';
|
||||
|
||||
|
||||
const Dashboard = () => {
|
||||
const [unitType, setUnitType] = useState('duration');
|
||||
const [durationUnit, setDurationUnit] = useState('day');
|
||||
/**
|
||||
* 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);
|
||||
@@ -14,24 +17,10 @@ const Dashboard = () => {
|
||||
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'
|
||||
},
|
||||
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 },
|
||||
@@ -40,48 +29,7 @@ const Dashboard = () => {
|
||||
{ date: '26/06', items: 900, revenue: 450 },
|
||||
{ date: '27/06', items: 550, revenue: 200 },
|
||||
],
|
||||
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'
|
||||
}
|
||||
]
|
||||
latestTransactions: []
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -91,7 +39,7 @@ const Dashboard = () => {
|
||||
const token = match[2];
|
||||
|
||||
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',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -99,16 +47,15 @@ const Dashboard = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const result = await res.json(); // hasil berupa array produk
|
||||
const products = result || [];
|
||||
const result = await res.json();
|
||||
const productsArr = result || [];
|
||||
|
||||
// Ambil distinct `type` dan `group` manual
|
||||
const types = [...new Set(products.map(p => p.type).filter(Boolean))];
|
||||
const groups = [...new Set(products.map(p => p.group).filter(Boolean))];
|
||||
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(products));
|
||||
setProducts(processProducts(productsArr));
|
||||
} catch (err) {
|
||||
console.error('Gagal ambil produk:', err);
|
||||
}
|
||||
@@ -117,47 +64,6 @@ const Dashboard = () => {
|
||||
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 getStatusClass = (status) => {
|
||||
@@ -221,202 +127,63 @@ const Dashboard = () => {
|
||||
</div>
|
||||
|
||||
<div className={styles.chartsGrid}>
|
||||
{/* Chart and Transactions UI as before */}
|
||||
{/* Tempatkan <BarChart data={dashboardData.chartData} /> jika mau ditampilkan */}
|
||||
</div>
|
||||
|
||||
{/* <div className={styles.chartCard}>
|
||||
<div className={styles.transactionsHeader}>
|
||||
<h3 className={styles.transactionsTitle}>Latest Transactions</h3>
|
||||
<a href="#" className={styles.seeAllLink}>see all</a>
|
||||
</div>
|
||||
{/* Products List */}
|
||||
<div className={styles.chartCard}>
|
||||
<div className={styles.transactionsHeader}>
|
||||
<h3 className={styles.transactionsTitle}>Products</h3>
|
||||
|
||||
<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.transactionsHeader}>
|
||||
<h3 className={styles.transactionsTitle}>Products</h3>
|
||||
</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>- {child.name}</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.transactionRight}>
|
||||
<span className={styles.transactionAmount}>
|
||||
IDR {formatCurrency(product.amount)}
|
||||
</span>
|
||||
<div className={`${styles.statusIndicator} ${getStatusClass(product.status)}`}></div>
|
||||
<span className={styles.transactionStatus}>
|
||||
{product.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* 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.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 = {
|
||||
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()
|
||||
};
|
||||
<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>
|
||||
|
||||
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>
|
||||
<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>
|
||||
|
||||
{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>
|
||||
{/* 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 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
|
||||
key={type}
|
||||
type="button"
|
||||
className={styles.suggestionButton}
|
||||
onClick={() => setSelectedType(type)}
|
||||
>
|
||||
{type}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.formGroup}>
|
||||
<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>
|
||||
|
||||
{/* Bagian form create yang lama sudah DIPINDAH ke halaman/komponen baru */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -117,6 +117,9 @@
|
||||
}
|
||||
|
||||
.chartCard {
|
||||
width: 100%;
|
||||
color: black;
|
||||
text-align: left;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
@@ -383,3 +386,10 @@
|
||||
.suggestionButton:hover {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
|
||||
.formActions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@@ -1,43 +1,33 @@
|
||||
import React from 'react';
|
||||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import styles from './Styles.module.css';
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<footer id="contact" className="bg-dark text-white py-4">
|
||||
<footer id="contact" className={`bg-dark text-white py-4 ${styles.footer}`}>
|
||||
<Container>
|
||||
<Row className="justify-content-center">
|
||||
<Col lg={6} className="text-center mb-3">
|
||||
<Row className="justify-content-center text-start">
|
||||
<Col lg={6} className="mb-3">
|
||||
<h4>Contact Us</h4>
|
||||
<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="mailto:marketing@kediritechnopark.com" className="text-white">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://kediritechnopark.com" target="_blank" rel="noopener noreferrer" className="text-white">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>
|
||||
<p><a href="tel:+6281318894994">0813 1889 4994</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">@kediri.technopark</a></p>
|
||||
<p><a href="https://kediritechnopark.com" target="_blank" rel="noopener noreferrer">www.KEDIRITECHNOPARK.com</a></p>
|
||||
|
||||
</Col>
|
||||
<Col lg={6} className="text-center">
|
||||
<Col lg={6}>
|
||||
<div className="footer-widget">
|
||||
<h4>About Our Company</h4>
|
||||
<div className="logo mb-3">
|
||||
<img src="/assets/images/logo-white.png" alt="Logo" className="img-fluid" />
|
||||
<div className={styles.logo}>
|
||||
<img src="https://kediritechnopark.com/kediri-technopark-logo-white.png" alt="Logo" />
|
||||
</div>
|
||||
<p>Kediri Technopark adalah pusat pengembangan inovasi digital dan aplikasi untuk masyarakat dan pelaku usaha.</p>
|
||||
</div>
|
||||
</Col>
|
||||
<Col lg={12} className="text-center mt-3">
|
||||
<Col lg={12} className="mt-3">
|
||||
<div className="copyright-text">
|
||||
<p>
|
||||
© 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>
|
||||
<p>© 2025 Kediri Technopark. All Rights Reserved.</p>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -46,4 +36,4 @@ const Footer = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
export default Footer;
|
||||
|
||||
@@ -26,12 +26,23 @@ const Header = ({ username, scrollToProduct, scrollToCourse, setShowedModal, han
|
||||
onMouseEnter={() => setHoveredNav(3)}
|
||||
onMouseLeave={() => setHoveredNav(null)}
|
||||
onClick={() => {
|
||||
if (!username) scrollToCourse();
|
||||
if (!username) scrollToProduct();
|
||||
else navigate('/products');
|
||||
}}
|
||||
>
|
||||
{username ? 'MY PRODUCTS' : 'PRODUCTS'}
|
||||
</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>
|
||||
|
||||
{/* Burger Menu Button */}
|
||||
|
||||
@@ -1,21 +1,36 @@
|
||||
// HeroSection.jsx — 2025 refresh using React-Bootstrap + CSS Module
|
||||
import React from 'react';
|
||||
import { Container, Row, Col, Button } from 'react-bootstrap';
|
||||
import styles from './HeroSection.module.css';
|
||||
|
||||
const HeroSection = () => {
|
||||
return (
|
||||
<section className="hero-section pt-5 bg-light">
|
||||
<section className={`${styles.hero} pt-5`}
|
||||
aria-label="Kediri Technopark hero section">
|
||||
<Container>
|
||||
<Row className="align-items-center">
|
||||
<Col lg={6}>
|
||||
<h1>KATALIS KARIR DAN BISNIS DIGITAL</h1>
|
||||
<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="d-flex gap-3">
|
||||
<Button variant="outline-primary" href="https://instagram.com/kediri.technopark" target="_blank">Instagram</Button>
|
||||
<Button variant="outline-success" href="tel:+6281318894994">WhatsApp</Button>
|
||||
<Row className="align-items-center gy-5">
|
||||
{/* Image on top for mobile, text first on lg+ */}
|
||||
<Col xs={{ order: 1 }} lg={{ span: 6, order: 1 }}>
|
||||
<div className={styles.copyWrap}>
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
</Col>
|
||||
<Col lg={6}>
|
||||
<img src="https://kediritechnopark.com/assets/images/gambar1.png" alt="Hero Image" className="img-fluid" />
|
||||
<Col xs={{ order: 0 }} lg={{ span: 6, order: 2 }}>
|
||||
<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>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
109
src/components/HeroSection.module.css
Normal file
109
src/components/HeroSection.module.css
Normal 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; }
|
||||
}
|
||||
@@ -6,10 +6,10 @@ const KnowledgeBaseSection = () => {
|
||||
<section id="knowledge" className="knowledge section pt-5">
|
||||
<Container>
|
||||
<Row>
|
||||
<Col lg={{ span: 8, offset: 2 }}>
|
||||
<div className="section-heading text-center mb-4">
|
||||
<Col >
|
||||
<div className="section-heading mb-4">
|
||||
<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>
|
||||
</div>
|
||||
<div className="knowledge-content">
|
||||
|
||||
@@ -77,7 +77,7 @@ const LoginRegister = ({setShowedModal}) => {
|
||||
}
|
||||
|
||||
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',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password }),
|
||||
@@ -122,7 +122,7 @@ const LoginRegister = ({setShowedModal}) => {
|
||||
}
|
||||
|
||||
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',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, username, password }),
|
||||
|
||||
@@ -85,6 +85,8 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
|
||||
}
|
||||
}
|
||||
|
||||
setShowNamingInput(true);
|
||||
return;
|
||||
|
||||
}
|
||||
// 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.headerRow}>
|
||||
<h2 className={styles.title}>{product.name}</h2>
|
||||
<h2 className={styles.title}>{product.name.split('%%%')[0]}</h2>
|
||||
<div className={styles.price} style={{ color: priceColor }}>
|
||||
{product.price == null ? 'Pay-As-You-Go' : `Rp ${parseInt(product.price).toLocaleString('id-ID')}`}
|
||||
</div>
|
||||
</div>
|
||||
<p className={styles.description}>{product.description}</p>
|
||||
<div className={styles.buttonGroup}>
|
||||
{product.end_date && product.site_url && (
|
||||
{product.site_url && (
|
||||
<button
|
||||
className={`${styles.button} ${styles.checkoutButton}`}
|
||||
onClick={() => {
|
||||
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}`;
|
||||
window.open(url, '_blank');
|
||||
const url = `https://${product.site_url}/dashboard/${product.name.split('%%%')[0]}?token=${token}`;
|
||||
window.location.href = url;
|
||||
}}
|
||||
>
|
||||
KUNJUNGI
|
||||
@@ -225,7 +227,7 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
|
||||
|
||||
{showSubscriptionSelector && !showNamingInput && (
|
||||
<div className={styles.childSelector}>
|
||||
<h5>Perpanjang {product.name}</h5>
|
||||
<h5>Perpanjang {product.name.split('%%%')[0]} </h5>
|
||||
{matchingSubscriptions.map(sub => (
|
||||
<label key={sub.id} className={styles.childProduct}>
|
||||
<input
|
||||
@@ -235,7 +237,7 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
|
||||
checked={selectedSubscriptionId == sub.id}
|
||||
onChange={() => { setSelectedSubscriptionId(sub.id); setCustomName(sub.product_name) }}
|
||||
/>
|
||||
{sub.product_name}
|
||||
{sub.product_name.split('%%%')[0]}
|
||||
</label>
|
||||
))}
|
||||
<h6>Atau buat baru</h6>
|
||||
@@ -246,7 +248,7 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
|
||||
checked={selectedSubscriptionId === product.id}
|
||||
onChange={() => setSelectedSubscriptionId(product.id)}
|
||||
/>
|
||||
Buat {product.name} baru
|
||||
Buat {product.name.split('%%%')[0]} baru
|
||||
</label>
|
||||
<div className={styles.buttonGroup}>
|
||||
<button className={styles.button} onClick={() => setShowSubscriptionSelector(false)}>
|
||||
@@ -261,7 +263,7 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
|
||||
|
||||
{showNamingInput && (
|
||||
<div className={styles.childSelector}>
|
||||
<h5>Buat {product.name} Baru</h5>
|
||||
<h5>Buat {product.name.split('%%%')[0]} Baru</h5>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nama produk..."
|
||||
@@ -286,7 +288,12 @@ const ProductDetail = ({ subscriptions, product, requestLogin, setShowedModal })
|
||||
className={styles.button}
|
||||
onClick={() => {
|
||||
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
|
||||
|
||||
@@ -11,7 +11,7 @@ const ProductSection = ({ hoveredCard, setHoveredCard, setSelectedProduct, setSh
|
||||
|
||||
// Inside your component
|
||||
useEffect(() => {
|
||||
fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', {
|
||||
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -28,9 +28,9 @@ useEffect(() => {
|
||||
|
||||
return (
|
||||
|
||||
<section id="services" className="services pt-5">
|
||||
<section id="services" className="services pt-5" ref={productSectionRef}>
|
||||
<Container>
|
||||
<div className="section-heading text-center mb-4">
|
||||
<div className="section-heading mb-4">
|
||||
<h4>OUR <em>PRODUCTS</em></h4>
|
||||
<img src="/assets/images/heading-line-dec.png" alt="" />
|
||||
<p>Kami menyediakan berbagai solusi teknologi untuk mendukung transformasi digital bisnis dan masyarakat.</p>
|
||||
|
||||
@@ -5,7 +5,7 @@ const ServicesSection = () => {
|
||||
return (
|
||||
<section id="services" className="services py-5">
|
||||
<Container>
|
||||
<div className="section-heading text-center mb-4">
|
||||
<div className="section-heading mb-4">
|
||||
<h4>OUR <em>SERVICES</em></h4>
|
||||
<img src="/assets/images/heading-line-dec.png" alt="" />
|
||||
<p>Kami menyediakan berbagai solusi teknologi untuk mendukung transformasi digital bisnis dan masyarakat.</p>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
position: sticky;
|
||||
/* position: sticky; */
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
@@ -217,7 +217,7 @@
|
||||
.courseImage {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background-color: #e2e8f0;
|
||||
background-color: white;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -225,7 +225,7 @@
|
||||
color: #64748b;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
@@ -636,3 +636,35 @@
|
||||
background-color: #2563eb;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ const CoursePage = ({ subscriptions }) => {
|
||||
const groupedSubs = groupSubscriptionsByProductName(subscriptions);
|
||||
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',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -135,7 +135,7 @@ const CoursePage = ({ subscriptions }) => {
|
||||
<div>
|
||||
<div className={styles.courseImage} style={{ backgroundImage: `url(${product.image})` }} />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user