ok
This commit is contained in:
147
src/App.js
147
src/App.js
@@ -1,10 +1,10 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
// import './assets/css/templatemo-chain-app-dev.css'; // Assuming you copy your original CSS here
|
||||
// import './assets/css/animated.css';
|
||||
// import './assets/css/owl.css';
|
||||
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 your converted React components
|
||||
import Header from './components/Header';
|
||||
import HeroSection from './components/HeroSection';
|
||||
import ServicesSection from './components/ServicesSection';
|
||||
@@ -15,14 +15,70 @@ import KnowledgeBaseSection from './components/KnowledgeBaseSection';
|
||||
import ClientsSection from './components/ClientsSection';
|
||||
import Footer from './components/Footer';
|
||||
|
||||
import ProductDetailPage from './components/ProductDetailPage';
|
||||
|
||||
|
||||
function HomePage({
|
||||
hoveredCard,
|
||||
setHoveredCard,
|
||||
selectedProduct,
|
||||
setSelectedProduct,
|
||||
showedModal,
|
||||
setShowedModal,
|
||||
productSectionRef,
|
||||
courseSectionRef
|
||||
}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeroSection />
|
||||
<ServicesSection />
|
||||
<ProductSection
|
||||
productSectionRef={productSectionRef}
|
||||
hoveredCard={hoveredCard}
|
||||
setHoveredCard={setHoveredCard}
|
||||
setSelectedProduct={setSelectedProduct}
|
||||
setShowedModal={setShowedModal}
|
||||
/>
|
||||
<AcademySection
|
||||
courseSectionRef={courseSectionRef}
|
||||
hoveredCard={hoveredCard}
|
||||
setHoveredCard={setHoveredCard}
|
||||
setSelectedProduct={setSelectedProduct}
|
||||
setShowedModal={setShowedModal} />
|
||||
<AboutUsSection />
|
||||
<KnowledgeBaseSection />
|
||||
<ClientsSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// State yang diperlukan untuk HomePage
|
||||
const [hoveredCard, setHoveredCard] = useState(null);
|
||||
const [selectedProduct, setSelectedProduct] = useState({});
|
||||
const [showedModal, setShowedModal] = useState(null); // 'product' | 'login' | null
|
||||
const [postLoginAction, setPostLoginAction] = useState(null);
|
||||
|
||||
const [username, setUsername] = useState(null);
|
||||
|
||||
|
||||
const productSectionRef = useRef(null);
|
||||
const courseSectionRef = useRef(null);
|
||||
|
||||
const scrollToProduct = () => {
|
||||
productSectionRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
};
|
||||
const scrollToCourse = () => {
|
||||
courseSectionRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Simulate preloader and remove it after some time
|
||||
const timer = setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1000); // Adjust time as needed
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
@@ -42,29 +98,60 @@ function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<Header />
|
||||
<HeroSection />
|
||||
{/* FULL WIDTH IMAGE SECTION */}
|
||||
{/* This can be a separate component or integrated into HeroSection */}
|
||||
<div className="custom-image-section wow fadeInRight">
|
||||
<a href="https://registration.kediritechnopark.com/" target="_blank" className="custom-image-link" rel="noopener noreferrer">
|
||||
<div className="custom-image-wrapper">
|
||||
<img src="/assets/images/FREE!.png" className="custom-image" />
|
||||
<div className="light-glare"></div>
|
||||
</div>
|
||||
</a>
|
||||
<Router>
|
||||
<div className="App">
|
||||
<Header username={username} scrollToProduct={scrollToProduct} scrollToCourse={scrollToCourse} setShowedModal={setShowedModal} />
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<HomePage
|
||||
hoveredCard={hoveredCard}
|
||||
setHoveredCard={setHoveredCard}
|
||||
selectedProduct={selectedProduct}
|
||||
setSelectedProduct={setSelectedProduct}
|
||||
showedModal={showedModal}
|
||||
setShowedModal={setShowedModal}
|
||||
productSectionRef={productSectionRef}
|
||||
courseSectionRef={courseSectionRef}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
<Footer />
|
||||
{/* Unified Modal */}
|
||||
{showedModal && (
|
||||
<div
|
||||
className={styles.modal}
|
||||
onClick={() => {
|
||||
setShowedModal(null);
|
||||
setSelectedProduct({});
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={styles.modalBody}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{showedModal === 'product' && (
|
||||
<ProductDetailPage
|
||||
setPostLoginAction={setPostLoginAction}
|
||||
setShowedModal={setShowedModal}
|
||||
product={selectedProduct}
|
||||
onClose={() => {
|
||||
setShowedModal(null);
|
||||
setSelectedProduct({});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showedModal === 'login' && (
|
||||
<Login postLoginAction={postLoginAction} setPostLoginAction={setPostLoginAction} onClose={() => setShowedModal(null)} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ServicesSection />
|
||||
<ProductSection />
|
||||
<AcademySection />
|
||||
<AboutUsSection />
|
||||
<KnowledgeBaseSection />
|
||||
<ClientsSection />
|
||||
<Footer />
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Container, Row, Col, Card, Button } from 'react-bootstrap';
|
||||
import { Container } from 'react-bootstrap';
|
||||
import styles from './Styles.module.css';
|
||||
|
||||
const AcademySection = () => {
|
||||
const AcademySection = ({hoveredCard, setHoveredCard, setSelectedProduct, setShowedModal, courseSectionRef}) => {
|
||||
const [products, setProducts] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -18,43 +19,53 @@ const AcademySection = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section id="academy" className="academy-tables py-5">
|
||||
|
||||
<section id="services" className="services py-5">
|
||||
<Container>
|
||||
<div className="section-heading text-center 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>
|
||||
<Row>
|
||||
{products.map((product, idx) => (
|
||||
<Col lg={3} md={4} sm={6} xs={12} key={idx} className="mb-4">
|
||||
<Card className="academy-item-regular h-100">
|
||||
<Card.Body>
|
||||
<Card.Title>{product.name}</Card.Title>
|
||||
<div className="icon mb-3">
|
||||
<img src={product.image || '/assets/images/pricing-table-01.png'} alt={product.name} className="img-fluid" />
|
||||
<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 className={styles.courseImage} style={{ backgroundImage: `url(${product.image})` }}>
|
||||
{product.price === 0 && (
|
||||
<span className={styles.courseLabel}>Free</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.courseContent}>
|
||||
<h3 className={styles.courseTitle}>{product.name}</h3>
|
||||
<p className={styles.courseDesc}>{product.description}</p>
|
||||
<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>
|
||||
<ul>
|
||||
{product.duration && (
|
||||
<li>
|
||||
Durasi:{" "}
|
||||
{product.duration.hours
|
||||
? `${product.duration.hours} jam`
|
||||
: product.duration.days
|
||||
? `${product.duration.days} hari`
|
||||
: "-"}
|
||||
</li>
|
||||
)}
|
||||
<li>Harga: {product.currency} {product.price.toLocaleString()}</li>
|
||||
</ul>
|
||||
<div className="border-button mt-3">
|
||||
<Button variant="outline-primary" href="#program">Lihat Detail</Button>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,24 +1,72 @@
|
||||
import React from 'react';
|
||||
import { Navbar, Nav, Container } from 'react-bootstrap';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Navbar, Nav, Container } from 'react-bootstrap';
|
||||
import styles from './Styles.module.css';
|
||||
|
||||
const Header = ({ username, scrollToProduct, scrollToCourse, setShowedModal }) => {
|
||||
const navigate = useNavigate();
|
||||
const [hoveredNav, setHoveredNav] = useState(null);
|
||||
|
||||
|
||||
const Header = () => {
|
||||
return (
|
||||
<Navbar bg="light" expand="lg" sticky="top">
|
||||
<Container>
|
||||
<Navbar.Brand href="#">Kediri Technopark</Navbar.Brand>
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||
<Navbar.Collapse id="basic-navbar-nav">
|
||||
<Nav className="me-auto">
|
||||
<Nav.Link href="#top">Home</Nav.Link>
|
||||
<Nav.Link href="#services">Services</Nav.Link>
|
||||
<Nav.Link href="#produk">Product</Nav.Link>
|
||||
<Nav.Link href="#academy">Academy</Nav.Link>
|
||||
<Nav.Link href="#about">About</Nav.Link>
|
||||
<Nav.Link href="#knowledge">Knowledge</Nav.Link>
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</Container>
|
||||
</Navbar>
|
||||
|
||||
<header className={styles.header}>
|
||||
<img src="./kediri-technopark-logo.png" className={styles.logo} alt="Logo" />
|
||||
|
||||
<nav className={styles.nav}>
|
||||
|
||||
<a
|
||||
className={`${styles.navLink} ${hoveredNav === 2 ? styles.navLinkHover : ''}`}
|
||||
onMouseEnter={() => setHoveredNav(2)}
|
||||
onMouseLeave={() => setHoveredNav(null)}
|
||||
onClick={() => {
|
||||
if (username == null) {
|
||||
scrollToProduct();
|
||||
}
|
||||
else {
|
||||
navigate('/products');
|
||||
}
|
||||
}}
|
||||
>
|
||||
PRODUCTS
|
||||
</a>
|
||||
<a
|
||||
className={`${styles.navLink} ${hoveredNav === 3 ? styles.navLinkHover : ''}`}
|
||||
onMouseEnter={() => setHoveredNav(3)}
|
||||
onMouseLeave={() => setHoveredNav(null)}
|
||||
onClick={() => {
|
||||
if (username == null) {
|
||||
scrollToCourse();
|
||||
}
|
||||
else {
|
||||
navigate('/courses');
|
||||
}
|
||||
}}
|
||||
>
|
||||
COURSES
|
||||
</a>
|
||||
<a
|
||||
className={`${styles.navLink} ${hoveredNav === 4 ? styles.navLinkHover : ''}`}
|
||||
onMouseEnter={() => setHoveredNav(4)}
|
||||
onMouseLeave={() => setHoveredNav(null)}
|
||||
>
|
||||
USER
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div className={styles.authButtons}>
|
||||
{username ? (
|
||||
<span style={{ color: '#2563eb', fontWeight: '600' }}>
|
||||
Halo, {username}
|
||||
</span>
|
||||
) : (
|
||||
<button className={styles.loginButton} onClick={() => setShowedModal('login')}>
|
||||
LOGIN
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
240
src/components/Login.js
Normal file
240
src/components/Login.js
Normal file
@@ -0,0 +1,240 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const LoginRegister = ({postLoginAction, setPostLoginAction}) => {
|
||||
const [tab, setTab] = useState('login'); // 'login' or 'register'
|
||||
const [email, setEmail] = useState('');
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '1rem',
|
||||
padding: '2rem',
|
||||
maxWidth: '400px',
|
||||
margin: '0 auto',
|
||||
boxShadow: '0 8px 25px rgba(0, 0, 0, 0.15)',
|
||||
fontFamily: 'Inter, system-ui, sans-serif',
|
||||
},
|
||||
title: {
|
||||
fontSize: '1.3rem',
|
||||
fontWeight: 'bold',
|
||||
color: '#1e293b',
|
||||
marginBottom: '1.5rem',
|
||||
textAlign: 'center',
|
||||
},
|
||||
tabContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginBottom: '1.5rem',
|
||||
},
|
||||
tabButton: (active) => ({
|
||||
cursor: 'pointer',
|
||||
padding: '0.5rem 1rem',
|
||||
borderBottom: active ? '2px solid #2563eb' : '2px solid transparent',
|
||||
fontWeight: active ? 'bold' : 'normal',
|
||||
color: active ? '#2563eb' : '#64748b',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
outline: 'none',
|
||||
fontSize: '1rem',
|
||||
margin: '0 0.5rem',
|
||||
}),
|
||||
input: {
|
||||
display: 'block',
|
||||
padding: '0.75rem 1rem',
|
||||
marginBottom: '1rem',
|
||||
borderRadius: '0.5rem',
|
||||
border: '1px solid #cbd5e1',
|
||||
fontSize: '0.9rem',
|
||||
outline: 'none',
|
||||
boxSizing: 'border-box',
|
||||
width: '100%',
|
||||
},
|
||||
inputWrapper: {
|
||||
width: '100%',
|
||||
},
|
||||
button: {
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
padding: '0.75rem 1.5rem',
|
||||
borderRadius: '0.5rem',
|
||||
border: 'none',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: '600',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease',
|
||||
textTransform: 'uppercase',
|
||||
backgroundColor: '#2563eb',
|
||||
color: 'white',
|
||||
},
|
||||
};
|
||||
|
||||
const handleLogin = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!username || !password) {
|
||||
alert('Username dan password wajib diisi');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('https://bot.kediritechnopark.com/webhook/user-dev/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.text();
|
||||
alert(`Login gagal: ${err}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const token = data[0].token;
|
||||
if (token) {
|
||||
document.cookie = `token=${token}; path=/; max-age=${7 * 24 * 60 * 60}`;
|
||||
|
||||
if (postLoginAction) {
|
||||
postLoginAction(); // resume action (e.g., checkout)
|
||||
setPostLoginAction(null);
|
||||
}
|
||||
} else {
|
||||
alert('Token tidak ditemukan pada respons login');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Terjadi kesalahan saat login');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRegister = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!email || !username || !password) {
|
||||
alert('Email, username, dan password wajib diisi');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('https://bot.kediritechnopark.com/webhook/user-dev/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, username, password }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.text();
|
||||
alert(`Registrasi gagal: ${err}`);
|
||||
return;
|
||||
}
|
||||
|
||||
alert('Registrasi berhasil! Silakan login.');
|
||||
setTab('login');
|
||||
setEmail('');
|
||||
setUsername('');
|
||||
setPassword('');
|
||||
} catch (error) {
|
||||
alert('Terjadi kesalahan saat registrasi');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<h2 style={styles.title}>{tab === 'login' ? 'Login' : 'Register'}</h2>
|
||||
|
||||
<div style={styles.tabContainer}>
|
||||
<button
|
||||
style={styles.tabButton(tab === 'login')}
|
||||
onClick={() => setTab('login')}
|
||||
type="button"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
<button
|
||||
style={styles.tabButton(tab === 'register')}
|
||||
onClick={() => setTab('register')}
|
||||
type="button"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{tab === 'login' ? (
|
||||
<form onSubmit={handleLogin}>
|
||||
<div style={styles.inputWrapper}>
|
||||
<input
|
||||
style={styles.input}
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
<div style={styles.inputWrapper}>
|
||||
<input
|
||||
style={styles.input}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
style={styles.button}
|
||||
onMouseOver={(e) => (e.target.style.backgroundColor = '#1d4ed8')}
|
||||
onMouseOut={(e) => (e.target.style.backgroundColor = '#2563eb')}
|
||||
>
|
||||
Masuk
|
||||
</button>
|
||||
</form>
|
||||
) : (
|
||||
<form onSubmit={handleRegister}>
|
||||
<div style={styles.inputWrapper}>
|
||||
<input
|
||||
style={styles.input}
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
autoComplete="email"
|
||||
/>
|
||||
</div>
|
||||
<div style={styles.inputWrapper}>
|
||||
<input
|
||||
style={styles.input}
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
<div style={styles.inputWrapper}>
|
||||
<input
|
||||
style={styles.input}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
style={styles.button}
|
||||
onMouseOver={(e) => (e.target.style.backgroundColor = '#1d4ed8')}
|
||||
onMouseOut={(e) => (e.target.style.backgroundColor = '#2563eb')}
|
||||
>
|
||||
Daftar
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginRegister;
|
||||
95
src/components/ProductDetail.module.css
Normal file
95
src/components/ProductDetail.module.css
Normal file
@@ -0,0 +1,95 @@
|
||||
.container {
|
||||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 260px;
|
||||
background-color: #e2e8f0;
|
||||
border-radius: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #64748b;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.headerRow {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
color: #2563eb; /* default color, bisa override di inline style */
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1rem;
|
||||
color: #64748b;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.buttonGroup {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 0.75rem 0.4rem;
|
||||
border-radius: 0.5rem;
|
||||
border: none;
|
||||
font-size: 0.55rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.addToCartButton {
|
||||
background-color: #fbbf24;
|
||||
color: #1e293b;
|
||||
margin-right: 0.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.checkoutButton {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Responsive untuk mobile */
|
||||
@media (max-width: 600px) {
|
||||
.container {
|
||||
text-align: left;
|
||||
}
|
||||
.headerRow {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.buttonGroup {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
133
src/components/ProductDetailPage.js
Normal file
133
src/components/ProductDetailPage.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styles from './ProductDetail.module.css';
|
||||
|
||||
const ProductDetail = ({ product, setPostLoginAction, setShowedModal }) => {
|
||||
const [inCart, setInCart] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const existingCookie = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('itemsId='));
|
||||
let items = [];
|
||||
if (existingCookie) {
|
||||
try {
|
||||
const value = decodeURIComponent(existingCookie.split('=')[1]);
|
||||
items = JSON.parse(value);
|
||||
if (!Array.isArray(items)) items = [];
|
||||
} catch (e) {
|
||||
items = [];
|
||||
}
|
||||
}
|
||||
setInCart(items.includes(product.id));
|
||||
}, [product.id]);
|
||||
|
||||
const onSetCart = () => {
|
||||
const existingCookie = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('itemsId='));
|
||||
|
||||
let items = [];
|
||||
|
||||
if (existingCookie) {
|
||||
try {
|
||||
const value = decodeURIComponent(existingCookie.split('=')[1]);
|
||||
items = JSON.parse(value);
|
||||
if (!Array.isArray(items)) items = [];
|
||||
} catch (e) {
|
||||
items = [];
|
||||
}
|
||||
}
|
||||
|
||||
let updatedItems;
|
||||
if (items.includes(product.id)) {
|
||||
updatedItems = items.filter(id => id !== product.id); // remove
|
||||
setInCart(false);
|
||||
} else {
|
||||
updatedItems = [...items, product.id]; // add
|
||||
setInCart(true);
|
||||
}
|
||||
|
||||
document.cookie = `itemsId=${JSON.stringify(updatedItems)}; path=/; max-age=${7 * 24 * 60 * 60
|
||||
}`;
|
||||
};
|
||||
|
||||
const onCheckout = () => {
|
||||
// Ambil token dari cookie
|
||||
const tokenCookie = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('token='));
|
||||
const token = tokenCookie ? tokenCookie.split('=')[1] : '';
|
||||
|
||||
// Ambil itemsId dari cookie
|
||||
const itemsCookie = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('itemsId='));
|
||||
|
||||
let items = [];
|
||||
if (itemsCookie) {
|
||||
try {
|
||||
items = JSON.parse(itemsCookie.split('=')[1]);
|
||||
if (!Array.isArray(items)) items = [];
|
||||
} catch (e) {
|
||||
items = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!items.includes(product.id)) {
|
||||
items.push(product.id);
|
||||
}
|
||||
// Encode items ke string untuk query param
|
||||
const itemsParam = JSON.stringify(items);
|
||||
|
||||
if (!tokenCookie) {
|
||||
setPostLoginAction(() => () => onCheckout()); // remember intent
|
||||
setShowedModal('login');
|
||||
return;
|
||||
}
|
||||
|
||||
// Redirect dengan token dan itemsId di query
|
||||
window.location.href = `http://localhost:3001/?token=${token}&itemsId=${itemsParam}&redirect_uri=http://localhost:3000/courses&redirect_failed=http://localhost:3000`;
|
||||
};
|
||||
|
||||
|
||||
// Override harga warna jika free
|
||||
const priceColor = product.price === 0 ? '#059669' : '#2563eb';
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.image}>📦</div>
|
||||
|
||||
<div className={styles.headerRow}>
|
||||
<h2 className={styles.title}>{product.name}</h2>
|
||||
<div className={styles.price} style={{ color: priceColor }}>
|
||||
{product.price === 0
|
||||
? 'Free'
|
||||
: `Rp ${parseInt(product.price).toLocaleString('id-ID')}`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className={styles.description}>{product.description}</p>
|
||||
|
||||
<div className={styles.buttonGroup}>
|
||||
<button
|
||||
className={`${styles.button} ${styles.addToCartButton}`}
|
||||
onClick={onSetCart}
|
||||
onMouseOver={e => (e.target.style.backgroundColor = '#facc15')}
|
||||
onMouseOut={e => (e.target.style.backgroundColor = '#fbbf24')}
|
||||
>
|
||||
{inCart ? 'Hapus dari Keranjang' : '+ Keranjang'}
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.button} ${styles.checkoutButton}`}
|
||||
onClick={onCheckout}
|
||||
onMouseOver={e => (e.target.style.backgroundColor = '#1d4ed8')}
|
||||
onMouseOut={e => (e.target.style.backgroundColor = '#2563eb')}
|
||||
>
|
||||
Checkout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductDetail;
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Container, Row, Col, Card } from 'react-bootstrap';
|
||||
import { Container } from 'react-bootstrap';
|
||||
import styles from './Styles.module.css';
|
||||
|
||||
const ProductSection = () => {
|
||||
|
||||
const ProductSection = ({ hoveredCard, setHoveredCard, setSelectedProduct, setShowedModal, productSectionRef }) => {
|
||||
const [products, setProducts] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -10,7 +12,7 @@ const ProductSection = () => {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ type: 'product', onlyParents:true }),
|
||||
body: JSON.stringify({ type: 'product', onlyParents: true }),
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => setProducts(data))
|
||||
@@ -18,27 +20,53 @@ const ProductSection = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section id="produk" className="product-section py-5 bg-light">
|
||||
|
||||
<section id="services" className="services py-5">
|
||||
<Container>
|
||||
<div className="section-heading text-center mb-4">
|
||||
<h4>OUR <em>PRODUCT</em></h4>
|
||||
<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>
|
||||
</div>
|
||||
<Row className="justify-content-center">
|
||||
<Col lg={12}>
|
||||
<div className="d-flex overflow-auto">
|
||||
{products.map((product, idx) => (
|
||||
<Card key={idx} className="text-center me-3" style={{ minWidth: '200px' }}>
|
||||
<Card.Img variant="top" src={product.image || '/assets/images/placeholder.png'} alt={product.name} />
|
||||
<Card.Body>
|
||||
<Card.Title>{product.name}</Card.Title>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<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 className={styles.courseImage} style={{ backgroundImage: `url(${product.image})` }}>
|
||||
{product.price === 0 && (
|
||||
<span className={styles.courseLabel}>Free</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.courseContent}>
|
||||
<h3 className={styles.courseTitle}>{product.name}</h3>
|
||||
<p className={styles.courseDesc}>{product.description}</p>
|
||||
<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>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
|
||||
550
src/components/Styles.module.css
Normal file
550
src/components/Styles.module.css
Normal file
@@ -0,0 +1,550 @@
|
||||
/* TechnoAcademyWebsite.module.css */
|
||||
|
||||
/* Header */
|
||||
.modal {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.527);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.modalBody {
|
||||
color: white;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: white;
|
||||
padding: 14px 6rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navLink {
|
||||
text-decoration: none;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
transition: color 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.navLinkHover {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.authButtons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.searchButton,
|
||||
.userButton {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.loginButton {
|
||||
background-color: transparent;
|
||||
color: #64748b;
|
||||
border: none;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
background-image: url('https://academy.kediritechnopark.com/wp-content/uploads/2025/07/pexels-fauxels-3184360-scaled.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
padding: 0 2rem;
|
||||
color: white;
|
||||
position: relative;
|
||||
border-radius: 0 0 0 60px;
|
||||
text-align: left;
|
||||
height: calc(100vh - 61.61px);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.heroContainer {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.heroContent {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.heroTitle {
|
||||
font-size: 3.5rem;
|
||||
font-weight: bold;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.heroDescription {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 2rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.joinButton {
|
||||
background: linear-gradient(135deg, rgb(59, 130, 246) 0%, rgb(30, 64, 175) 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.heroImage {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.heroImagePlaceholder {
|
||||
width: 500px;
|
||||
height: 350px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.2rem;
|
||||
color: white;
|
||||
border: 2px dashed rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.Section {
|
||||
padding: 2rem 6rem;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
.coursesContainer {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.coursesTitle {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.coursesGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
.courseCard {
|
||||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
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;
|
||||
}
|
||||
|
||||
.courseCard:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
.courseCardHover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.courseImage {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background-color: #e2e8f0;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #64748b;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.courseLabel {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background-color: #ef4444;
|
||||
color: white;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.courseContent {
|
||||
padding: 1.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.courseCategory {
|
||||
font-size: 0.8rem;
|
||||
color: #64748b;
|
||||
margin-bottom: 0.5rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.courseTitle {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.courseStats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.9rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.courseStat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.coursePrice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.originalPrice {
|
||||
text-decoration: line-through;
|
||||
color: #94a3b8;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.currentPrice {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.freePrice {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.featuresContainer {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.featuresTitle {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
margin-bottom: 2rem;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.featuresDescription {
|
||||
font-size: 1.1rem;
|
||||
color: #64748b;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 3rem;
|
||||
text-align: left;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.featuresList {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.featureItem {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
background-color: #f8fafc;
|
||||
border-radius: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.featureIcon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.featureContent {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.featureTitle {
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 0.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.featureDescription {
|
||||
color: #64748b;
|
||||
line-height: 1.6;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ctaContainer {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4rem;
|
||||
}
|
||||
|
||||
.ctaCard {
|
||||
background-color: white;
|
||||
padding: 3rem;
|
||||
border-radius: 1rem;
|
||||
text-align: center;
|
||||
box-shadow: 10px 10px 10px rgb(0 0 0 / 15%);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ctaIcon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 2rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.ctaTitle {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ctaDescription {
|
||||
color: #64748b;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.ctaButton {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background-color: #1e293b;
|
||||
color: white;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footerContent {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footerText {
|
||||
font-size: 0.9rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.socialLinks {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.socialLink {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #374151;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.socialLink:hover {
|
||||
background-color: #4b5563;
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media (max-width: 800px) {
|
||||
.modalBody {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 14px 2rem;
|
||||
}
|
||||
|
||||
.heroContainer {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ctaTitle,
|
||||
.courseTitle {
|
||||
font-size: 15px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ctaDescription,
|
||||
.courseContent p {
|
||||
font-size: 13px;
|
||||
margin: 6px 0px;
|
||||
}
|
||||
|
||||
.ctaCard,
|
||||
.Section {
|
||||
padding: 2rem 0.8rem;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
.courseContent {
|
||||
text-align: left;
|
||||
padding: 0.8rem;
|
||||
}
|
||||
|
||||
.ctaContainer,
|
||||
.coursesGrid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(138px, 1fr));
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.featureTitle {
|
||||
margin-top: 6px;
|
||||
margin-bottom: 0.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.featureDescription {
|
||||
text-align: left;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.featuresList {
|
||||
gap: 0rem;
|
||||
}
|
||||
|
||||
|
||||
.ctaButton {
|
||||
font-size: 12px;
|
||||
padding: 1rem 1rem;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user