diff --git a/src/App.js b/src/App.js index 1438463..452f007 100644 --- a/src/App.js +++ b/src/App.js @@ -2,9 +2,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import styles from './components/Styles.module.css'; -// Import components import Login from './components/Login'; - import Header from './components/Header'; import HeroSection from './components/HeroSection'; import ServicesSection from './components/ServicesSection'; @@ -14,13 +12,11 @@ import AboutUsSection from './components/AboutUsSection'; import KnowledgeBaseSection from './components/KnowledgeBaseSection'; import ClientsSection from './components/ClientsSection'; import Footer from './components/Footer'; - import ProductDetailPage from './components/ProductDetailPage'; - import Dashboard from './components/Dashboard'; import ProductsPage from './components/pages/ProductsPage'; - +import processProducts from './helper/processProducts'; function HomePage({ hoveredCard, @@ -32,7 +28,6 @@ function HomePage({ productSectionRef, courseSectionRef }) { - return ( <> @@ -49,13 +44,15 @@ function HomePage({ hoveredCard={hoveredCard} setHoveredCard={setHoveredCard} setSelectedProduct={setSelectedProduct} - setShowedModal={setShowedModal} /> + setShowedModal={setShowedModal} + /> > ); } + function parseJwt(token) { try { const base64Url = token.split('.')[1]; @@ -74,19 +71,26 @@ function parseJwt(token) { function App() { const [loading, setLoading] = useState(true); - - // State yang diperlukan untuk HomePage const [hoveredCard, setHoveredCard] = useState(null); - const [subscriptions, setSubscriptions] = useState(null); const [selectedProduct, setSelectedProduct] = useState({}); - const [showedModal, setShowedModal] = useState(null); // 'product' | 'login' | null - + const [showedModal, setShowedModal] = useState(null); const [username, setUsername] = useState(null); const productSectionRef = useRef(null); const courseSectionRef = useRef(null); + const [productModalRequest, setProductModalRequest] = useState(null); + + + const scrollToProduct = () => { + productSectionRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + const scrollToCourse = () => { + courseSectionRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + const requestLogin = (nextAction) => { const url = new URL(window.location); url.searchParams.set('next', nextAction); @@ -94,53 +98,151 @@ function App() { setShowedModal('login'); }; + // Ambil token dan user info dari cookie useEffect(() => { - // Ambil token dari cookies const match = document.cookie.match(new RegExp('(^| )token=([^;]+)')); if (match) { const token = match[2]; - fetch('https://bot.kediritechnopark.com/webhook/user-dev/data', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token - }, }) .then(res => res.json()) .then(data => { - if (data && data.token) { - // Update token with data[0].token document.cookie = `token=${data.token}; path=/`; - console.log(data) - setSubscriptions(data.subscriptions) + setSubscriptions(data.subscriptions); const payload = parseJwt(data.token); if (payload && payload.username) { setUsername(payload.username); } - } else { - console.warn('Token tidak ditemukan dalam data.'); } }) .catch(err => console.error('Fetch error:', err)); + } + }, []); + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const modalType = params.get('modal'); + const productId = params.get('product_id'); + const authorizedUri = params.get('authorized_uri'); + const unauthorizedUri = params.get('unauthorized_uri'); + if (modalType === 'product' && productId) { + const token = document.cookie.match(/(^| )token=([^;]+)/)?.[2]; + + // Simpan semua param penting ke localStorage + localStorage.setItem('product_id', productId); + if (authorizedUri) localStorage.setItem('authorized_uri', authorizedUri); + if (unauthorizedUri) localStorage.setItem('unauthorized_uri', unauthorizedUri); + + // Jika belum login, tampilkan modal login + if (!token) { + setShowedModal('login'); + } + // Jika sudah login, tidak langsung fetch di sini — akan diproses saat subscriptions tersedia + } + }, []); + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const modalType = params.get('modal'); + const productId = parseInt(params.get('product_id')); + const authorizedUri = params.get('authorized_uri'); + const unauthorizedUri = params.get('unauthorized_uri'); + + const token = document.cookie.match(/(^| )token=([^;]+)/)?.[2]; + + if (modalType === 'product' && productId) { + if (!token) { + setShowedModal('login'); // belum login → tampilkan login modal + } else { + // sudah login → lanjutkan proses otorisasi saat subscriptions tersedia + setProductModalRequest({ productId, authorizedUri, unauthorizedUri }); + console.log('modal') + } } }, []); useEffect(() => { - const params = new URLSearchParams(window.location.search); - if (params.has('next')) { - setShowedModal('login'); + if (!productModalRequest) return; + + const { productId, authorizedUri, unauthorizedUri } = productModalRequest; + + const hasAccess = subscriptions && subscriptions.some(sub => sub.product_id === productId); + + if (hasAccess) { + if (authorizedUri) { + let finalUri = decodeURIComponent(authorizedUri); + const token = document.cookie.match(/(^| )token=([^;]+)/)?.[2]; + + if (finalUri.includes('token=null') || finalUri.includes('token=')) { + const url = new URL(finalUri); + url.searchParams.set('token', token || ''); + finalUri = url.toString(); + } + + window.location.href = finalUri; + } + else {// Assuming you already imported processProducts from './processProducts' + + fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + itemsId: [productId], + withChildren: true, + }), + }) + .then(res => res.json()) + .then(data => { + if (Array.isArray(data) && data.length > 0) { + // Process the raw data to group children under their parent + const processed = processProducts(data); + // Set the first product (which should be the parent with children nested) + setSelectedProduct(processed[0]); + setShowedModal('product'); + } + }) + .catch(err => console.error('Fetch product error:', err)); + } + } else { + if (unauthorizedUri) { + window.location.href = decodeURIComponent(unauthorizedUri); + } else { + + fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + itemsId: [productId], + withChildren: true, + }), + }) + .then(res => res.json()) + .then(data => { + if (Array.isArray(data) && data.length > 0) { + // Process the raw data to group children under their parent + const processed = processProducts(data); + // Set the first product (which should be the parent with children nested) + setSelectedProduct(processed[0]); + setShowedModal('product'); + } + }) + .catch(err => console.error('Fetch product error:', err)); + + console.log('modal') + } } - }, []); - const scrollToProduct = () => { - productSectionRef.current?.scrollIntoView({ behavior: "smooth" }); - }; - const scrollToCourse = () => { - courseSectionRef.current?.scrollIntoView({ behavior: "smooth" }); - }; + + setProductModalRequest(null); // reset + }, [subscriptions, productModalRequest]); useEffect(() => { const timer = setTimeout(() => { @@ -150,13 +252,8 @@ function App() { }, []); const handleLogout = () => { - // Hapus cookie token dengan mengatur tanggal kadaluarsa ke masa lalu document.cookie = 'token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC'; - - // Jika kamu menggunakan state seperti `setUsername`, bersihkan di sini juga - setUsername(null); // jika applicable - - // Redirect ke homepage atau reload halaman + setUsername(null); window.location.reload(); }; @@ -178,7 +275,13 @@ function App() { return ( - + } /> - - } - /> - - } - /> + } /> + } /> - {/* Unified Modal */} + + {/* Modal */} {showedModal && ( { const url = new URL(window.location); - if (url.searchParams.has('next')) { - url.searchParams.delete('next'); - window.history.pushState({}, '', url); - } + url.searchParams.delete('modal'); + url.searchParams.delete('product_id'); + url.searchParams.delete('authorized_uri'); + url.searchParams.delete('unauthorized_uri'); + url.searchParams.delete('next'); + window.history.pushState({}, '', url); setShowedModal(null); setSelectedProduct({}); }} > - e.stopPropagation()} - > + e.stopPropagation()}> {showedModal === 'product' && ( setHoveredCard(product.id)} onMouseLeave={() => setHoveredCard(null)} > + {product.price === 0 && ( Free )} - + {product.name} {product.description} + + + + ))} diff --git a/src/components/ProductDetail.module.css b/src/components/ProductDetail.module.css index 947a59a..ca13e44 100644 --- a/src/components/ProductDetail.module.css +++ b/src/components/ProductDetail.module.css @@ -28,11 +28,11 @@ .headerRow { display: flex; - justify-content: center; + flex-wrap: wrap; + justify-content: space-between; align-items: center; - gap: 1rem; + gap: 0.5rem; /* optional: supaya nggak terlalu rapat saat wrap */ margin-bottom: 1rem; - text-align: center; } .title { @@ -53,6 +53,7 @@ color: #64748b; margin-bottom: 1.5rem; line-height: 1.6; + text-align: left; } .buttonGroup { @@ -90,9 +91,6 @@ .container { text-align: left; } - .headerRow { - justify-content: flex-start; - } .buttonGroup { gap: 0.5rem; } diff --git a/src/components/ProductDetailPage.js b/src/components/ProductDetailPage.js index 3b021f4..f046c73 100644 --- a/src/components/ProductDetailPage.js +++ b/src/components/ProductDetailPage.js @@ -84,7 +84,7 @@ if (hasMatchingSubscription) { } // No children, no matching subscription const itemsParam = JSON.stringify([product.id]); - window.location.href = `http://localhost:3002/?token=${token}&itemsId=${itemsParam}&redirect_uri=http://localhost:3000/products&redirect_failed=http://localhost:3000`; + window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&redirect_uri=https://kediritechnopark.com/products&redirect_failed=https://kediritechnopark.com`; }; const onConfirmChildren = () => { @@ -103,7 +103,7 @@ if (hasMatchingSubscription) { } const itemsParam = selectedChildIds.length > 0 ? JSON.stringify(selectedChildIds) : JSON.stringify([product.id]); - window.location.href = `http://localhost:3002/?token=${token}&itemsId=${itemsParam}&redirect_uri=http://localhost:3000/products&redirect_failed=http://localhost:3000`; + window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&redirect_uri=https://kediritechnopark.com/products&redirect_failed=https://kediritechnopark.com`; }; const onFinalCheckoutNewProduct = () => { @@ -117,7 +117,7 @@ if (hasMatchingSubscription) { const itemsParam = selectedChildIds.length > 0 ? JSON.stringify(selectedChildIds) : JSON.stringify([product.id]); const encodedName = encodeURIComponent(customName.trim()); - window.location.href = `http://localhost:3002/?token=${token}&itemsId=${itemsParam}&new_name=${encodedName}&redirect_uri=http://localhost:3000/products&redirect_failed=http://localhost:3000`; + window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&new_name=${encodedName}&redirect_uri=https://kediritechnopark.com/products&redirect_failed=https://kediritechnopark.com`; }; const onConfirmSelector = () => { @@ -139,7 +139,7 @@ if (hasMatchingSubscription) { const productName = selectedSubscription?.product_name; const encodedName = encodeURIComponent(productName); - window.location.href = `http://localhost:3002/?token=${token}&itemsId=${itemsParam}&set_name=${encodedName}&redirect_uri=http://localhost:3000/products&redirect_failed=http://localhost:3000`; + window.location.href = `https://checkout.kediritechnopark.com/?token=${token}&itemsId=${itemsParam}&set_name=${encodedName}&redirect_uri=https://kediritechnopark.com/products&redirect_failed=https://kediritechnopark.com`; } }; diff --git a/src/components/ProductSection.js b/src/components/ProductSection.js index 85135ae..b1c05df 100644 --- a/src/components/ProductSection.js +++ b/src/components/ProductSection.js @@ -1,11 +1,15 @@ import React, { useEffect, useState } from 'react'; import { Container } from 'react-bootstrap'; import styles from './Styles.module.css'; +import processProducts from '../helper/processProducts'; const ProductSection = ({ hoveredCard, setHoveredCard, setSelectedProduct, setShowedModal, productSectionRef }) => { const [products, setProducts] = useState([]); +// Define this function outside useEffect so it can be called anywhere + +// Inside your component useEffect(() => { fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', { method: 'POST', @@ -16,34 +20,7 @@ useEffect(() => { }) .then(res => res.json()) .then(data => { - const parentMap = {}; - const childrenMap = {}; - - // Pisahkan parent dan child - data.forEach(product => { - if (product.sub_product_of) { - const parentId = product.sub_product_of; - if (!childrenMap[parentId]) childrenMap[parentId] = []; - childrenMap[parentId].push(product); - } else { - parentMap[product.id] = { - ...product, - children: [] - }; - } - }); - - // Pasang children ke parent - Object.keys(childrenMap).forEach(parentId => { - const parent = parentMap[parentId]; - if (parent) { - parent.children = childrenMap[parentId]; - } - }); - - // Ambil parent saja - const enrichedData = Object.values(parentMap); - + const enrichedData = processProducts(data); setProducts(enrichedData); }) .catch(err => console.error('Fetch error:', err)); @@ -62,41 +39,45 @@ useEffect(() => { {products && products[0]?.name && products - .map(product => ( - { - setSelectedProduct(product); - setShowedModal('product'); - }} - onMouseEnter={() => setHoveredCard(product.id)} - onMouseLeave={() => setHoveredCard(null)} - > - - {product.price === 0 && ( - Free - )} - - - {product.name} - {product.description} - - - {product.price == null - ? 'Pay-As-You-Go' - : `Rp ${product.price.toLocaleString('id-ID')}`} - + .map(product => ( + { + setSelectedProduct(product); + setShowedModal('product'); + }} + onMouseEnter={() => setHoveredCard(product.id)} + onMouseLeave={() => setHoveredCard(null)} + > + + + {product.price === 0 && ( + Free + )} + + + {product.name} + {product.description} + + + + + + {product.price == null + ? 'Pay-As-You-Go' + : `Rp ${product.price.toLocaleString('id-ID')}`} + + - - ))} + ))} diff --git a/src/components/Styles.module.css b/src/components/Styles.module.css index c3c6302..918b872 100644 --- a/src/components/Styles.module.css +++ b/src/components/Styles.module.css @@ -194,14 +194,14 @@ box-shadow: 2px 4px 6px rgba(0, 0, 0, 0.3); transition: all 0.3s ease; cursor: pointer; - - /* Tambahan untuk menghilangkan highlight biru */ -webkit-tap-highlight-color: transparent; -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; user-select: none; outline: none; + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; } .courseCard:focus { @@ -241,8 +241,20 @@ font-weight: 600; } -.courseContent { - padding: 1.5rem; +.courseContentTop { + padding: 1rem 1rem 0rem; + text-align: left; +} + +.courseContentTop p { + margin-top: 0.5rem; + margin-bottom: 1rem; +} + + +.courseContentBottom { + padding: 1rem; + padding-top: 0; text-align: left; } @@ -490,13 +502,11 @@ .ctaTitle, .courseTitle { - font-size: 15px; margin: 0; } .ctaDescription, - .courseContent p { - font-size: 13px; + .courseContentTop, .courseContentBottom p { margin: 6px 0px; } @@ -506,9 +516,8 @@ background-color: #f8fafc; } - .courseContent { + .courseContentTop, .courseContentBottom { text-align: left; - padding: 0.8rem; } .ctaContainer, diff --git a/src/components/pages/ProductsPage.js b/src/components/pages/ProductsPage.js index dad2a27..b213790 100644 --- a/src/components/pages/ProductsPage.js +++ b/src/components/pages/ProductsPage.js @@ -150,14 +150,18 @@ const CoursePage = ({ subscriptions }) => { onMouseEnter={() => setHoveredCard(product.name)} onMouseLeave={() => setHoveredCard(null)} > + {/* {product.price == 0 && ( Free )} */} - + {product.name} {product.description} + + + { + if (product.sub_product_of) { + const parentId = product.sub_product_of; + if (!childrenMap[parentId]) childrenMap[parentId] = []; + childrenMap[parentId].push(product); + } else { + parentMap[product.id] = { + ...product, + children: [] + }; + } + }); + + // Pasang children ke parent + Object.keys(childrenMap).forEach(parentId => { + const parent = parentMap[parentId]; + if (parent) { + parent.children = childrenMap[parentId]; + } + }); + + // Ambil parent saja + return Object.values(parentMap); +}
{product.description}