ok
This commit is contained in:
10
src/App.css
10
src/App.css
@@ -1,5 +1,15 @@
|
|||||||
.App {
|
.App {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #dfdfdf;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-logo {
|
.App-logo {
|
||||||
|
|||||||
327
src/Checkout.css
327
src/Checkout.css
@@ -1,327 +0,0 @@
|
|||||||
.checkout-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 0 20px rgba(0,0,0,0.2);
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 40px auto;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
|
|
||||||
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-panel {
|
|
||||||
background-color: #2e2a4a;
|
|
||||||
color: white;
|
|
||||||
flex: 1;
|
|
||||||
padding: 40px 30px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-arrow {
|
|
||||||
font-size: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-name {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-name {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-price {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 32px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-image-container {
|
|
||||||
background-color: #b7c4f9;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
max-width: 300px;
|
|
||||||
max-height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-image-container img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-panel {
|
|
||||||
background-color: white;
|
|
||||||
flex: 1;
|
|
||||||
padding: 40px 30px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
.apple-pay-button {
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 15px 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator .line {
|
|
||||||
flex: 1;
|
|
||||||
border: none;
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shipping-info h3,
|
|
||||||
.payment-methods h3 {
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shipping-info input,
|
|
||||||
.shipping-info select {
|
|
||||||
width: 100%;
|
|
||||||
margin: 8px 0;
|
|
||||||
padding: 10px 12px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.manual-address {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 4px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.payment-methods label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.payment-methods input[type="radio"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.payment-methods .icon {
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 14px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #eee;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.payment-methods .klarna {
|
|
||||||
background-color: #ff4f8b;
|
|
||||||
color: white;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 0 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.payment-methods .ideal {
|
|
||||||
background-color: #f9a825;
|
|
||||||
color: white;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 0 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.klarna-subtext {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
margin-left: 30px;
|
|
||||||
margin-top: -6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pay-button {
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 15px 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pay-button:hover {
|
|
||||||
background-color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: auto;
|
|
||||||
padding-top: 20px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
/* Responsive styles */
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.checkout-container {
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 100vw;
|
|
||||||
min-height: 100vh;
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
.left-panel, .right-panel {
|
|
||||||
padding: 32px 16px;
|
|
||||||
min-width: 0;
|
|
||||||
max-width: 100vw;
|
|
||||||
}
|
|
||||||
.left-panel {
|
|
||||||
border-radius: 0 0 12px 12px;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.right-panel {
|
|
||||||
border-radius: 12px 12px 0 0;
|
|
||||||
}
|
|
||||||
.product-image-container {
|
|
||||||
max-width: 220px;
|
|
||||||
max-height: 220px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.checkout-container {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.left-panel, .right-panel {
|
|
||||||
padding: 20px 6vw;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
.product-name {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.product-price {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
.apple-pay-button, .pay-button {
|
|
||||||
font-size: 15px;
|
|
||||||
padding: 12px 0;
|
|
||||||
}
|
|
||||||
.shipping-info input,
|
|
||||||
.shipping-info select {
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 8px 10px;
|
|
||||||
}
|
|
||||||
.payment-methods label {
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 8px 8px;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
font-size: 10px;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.checkmark-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
animation: fadeIn 0.5s ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkmark {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
stroke-width: 2;
|
|
||||||
stroke: #4CAF50;
|
|
||||||
stroke-miterlimit: 10;
|
|
||||||
animation: scaleIn 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkmark-circle {
|
|
||||||
stroke-dasharray: 166;
|
|
||||||
stroke-dashoffset: 166;
|
|
||||||
stroke-width: 2;
|
|
||||||
stroke: #4CAF50;
|
|
||||||
fill: none;
|
|
||||||
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkmark-check {
|
|
||||||
transform-origin: 50% 50%;
|
|
||||||
stroke-dasharray: 48;
|
|
||||||
stroke-dashoffset: 48;
|
|
||||||
stroke: #4CAF50;
|
|
||||||
stroke-linecap: round;
|
|
||||||
stroke-linejoin: round;
|
|
||||||
animation: stroke 0.3s 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes stroke {
|
|
||||||
to {
|
|
||||||
stroke-dashoffset: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes scaleIn {
|
|
||||||
0% {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
293
src/Checkout.js
293
src/Checkout.js
@@ -1,248 +1,73 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React from 'react';
|
||||||
import './Checkout.css';
|
import styles from './Checkout.module.css';
|
||||||
import { QRCodeCanvas } from 'qrcode.react';
|
|
||||||
|
|
||||||
const Checkout = ({ socketId, transactionSuccess }) => {
|
|
||||||
const [qrisData, setQrisData] = useState(null); // QRIS string
|
|
||||||
const [value, setValue] = useState(null); // QRIS value (optional)
|
|
||||||
const [products, setProducts] = useState([]); // Produk dari itemsId
|
|
||||||
const [loadingProducts, setLoadingProducts] = useState(false);
|
|
||||||
|
|
||||||
// Helper get cookie value
|
|
||||||
const getCookie = (name) => {
|
|
||||||
const value = `; ${document.cookie}`;
|
|
||||||
const parts = value.split(`; ${name}=`);
|
|
||||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// document.cookie = "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoia2VkaXJpdGVjaG5vcGFyayIsImVtYWlsIjoiIiwicHJvZmlsZV9kYXRhIjp7fSwic3Vic2NyaXB0aW9ucyI6e30sImlhdCI6MTc1MzU4MDQ2N30.9TjxL5bV5i3zTAU_Rl7_p5cd76Fn6-O0elRtJw570jY; path=/; max-age=${7 * 24 * 60 * 60}";
|
|
||||||
// document.cookie = "itemsId=" + JSON.stringify([1, 2]) + "; path=/; max-age=${7 * 24 * 60 * 60}";
|
|
||||||
|
|
||||||
|
|
||||||
const fetchUserData = async () => {
|
|
||||||
const token = getCookie('token');
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
try {
|
|
||||||
const userDataResponse = await fetch('https://bot.kediritechnopark.com/webhook/user-dev/data', {
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (userDataResponse.ok) {
|
|
||||||
const userData = await userDataResponse.json();
|
|
||||||
document.cookie = `token=${userData[0]?.token}; path=/; max-age=${7 * 24 * 60 * 60}`;
|
|
||||||
console.log('User data:', userData);
|
|
||||||
// Store user data in state or context if needed
|
|
||||||
} else {
|
|
||||||
console.error('Failed to fetch user data:', userDataResponse.status);
|
|
||||||
// Handle error fetching user data, e.g., logout
|
|
||||||
document.cookie = 'token=; path=/; max-age=0';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching user data:', error);
|
|
||||||
// Handle network or other errors, e.g., logout
|
|
||||||
document.cookie = 'token=; path=/; max-age=0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const fetchProducts = async () => {
|
|
||||||
const itemsIdRaw = getCookie('itemsId');
|
|
||||||
if (!itemsIdRaw) return;
|
|
||||||
|
|
||||||
let itemsId = [];
|
|
||||||
try {
|
|
||||||
itemsId = JSON.parse(itemsIdRaw);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Gagal parse itemsId dari cookie:', e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemsId.length === 0) return;
|
|
||||||
|
|
||||||
setLoadingProducts(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const token = getCookie('token');
|
|
||||||
if (!token) {
|
|
||||||
console.warn('Token tidak ditemukan');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
itemsId.forEach(id => params.append('itemsId', id));
|
|
||||||
|
|
||||||
const res = await fetch(`https://bot.kediritechnopark.com/webhook/store-dev/products`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: params.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
setProducts(data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching products:', error);
|
|
||||||
} finally {
|
|
||||||
setLoadingProducts(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchProducts();
|
|
||||||
fetchUserData();
|
|
||||||
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
const handlePay = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
let itemsIdRaw = getCookie('itemsId');
|
|
||||||
let token = getCookie('token');
|
|
||||||
|
|
||||||
if (!itemsIdRaw || !token) {
|
|
||||||
alert("Token atau itemsId tidak ditemukan di cookies.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let itemsId = [];
|
|
||||||
try {
|
|
||||||
itemsId = JSON.parse(itemsIdRaw);
|
|
||||||
} catch (e) {
|
|
||||||
alert("Gagal parsing itemsId.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
itemsId.forEach(id => params.append('itemsId', id));
|
|
||||||
params.append('socketId', socketId);
|
|
||||||
|
|
||||||
|
|
||||||
const response = await fetch('https://bot.kediritechnopark.com/webhook/store-dev/pay', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Authorization': `Bearer ${token}`
|
|
||||||
},
|
|
||||||
body: params.toString()
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (response.ok && result.qris_dynamic) {
|
|
||||||
setQrisData(result.qris_dynamic);
|
|
||||||
setValue(result.total_price);
|
|
||||||
} else {
|
|
||||||
alert(`Gagal mendapatkan QRIS: ${result?.error || 'Unknown error'}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Network error:', error);
|
|
||||||
alert("Terjadi kesalahan jaringan.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const Checkout = () => {
|
||||||
return (
|
return (
|
||||||
<div className="checkout-container">
|
<div style={{
|
||||||
<div className="left-panel">
|
minHeight: '100vh',
|
||||||
<div className="back-arrow">←</div>
|
display: 'flex',
|
||||||
<div className="brand-name">Powdur</div>
|
alignItems: 'center',
|
||||||
<div className="product-name">Pure kit</div>
|
justifyContent: 'center',
|
||||||
<div className="product-price">$65.00</div>
|
padding: '1rem',
|
||||||
<div className="product-image-container">
|
boxSizing: 'border-box',
|
||||||
<img src="path/to/image.jpg" alt="Powdur Product" />
|
}}>
|
||||||
|
<div className={styles.checkoutCard}>
|
||||||
|
{/* Product List */}
|
||||||
|
<div className={styles.cartSection}>
|
||||||
|
<h2 className={styles.cartTitle}>Your Cart</h2>
|
||||||
|
<ul className={styles.cartList}>
|
||||||
|
<li className={styles.cartItem}>
|
||||||
|
<div className={styles.itemDetails}>
|
||||||
|
<img
|
||||||
|
src="https://via.placeholder.com/60"
|
||||||
|
alt="Product 1"
|
||||||
|
className={styles.productImage}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<p className={styles.itemText}>Pure Kit</p>
|
||||||
|
<p className={styles.itemPrice}>$65.00</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="right-panel">
|
<button className={styles.removeBtn} aria-label="Remove Pure Kit">×</button>
|
||||||
{!qrisData ? (
|
</li>
|
||||||
<>
|
|
||||||
<h3>Cart Items</h3>
|
<li className={styles.cartItem}>
|
||||||
{loadingProducts ? (
|
<div className={styles.itemDetails}>
|
||||||
<p>Loading products...</p>
|
<img
|
||||||
) : products.length === 0 ? (
|
src="https://via.placeholder.com/60"
|
||||||
<p>No products found</p>
|
alt="Product 2"
|
||||||
) : (
|
className={styles.productImage}
|
||||||
<ul>
|
/>
|
||||||
{products.map((product) => (
|
<div>
|
||||||
<li key={product.id || product._id}>
|
<p className={styles.itemText}>Energy Drink</p>
|
||||||
{product.name} - ${product.price}
|
<p className={styles.itemPrice}>$25.00</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button className={styles.removeBtn} aria-label="Remove Energy Drink">×</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
|
||||||
|
|
||||||
<form className="shipping-info" onSubmit={handlePay}>
|
|
||||||
<h3>Shipping information</h3>
|
|
||||||
<input type="email" placeholder="Email" />
|
|
||||||
<input type="text" placeholder="Name" />
|
|
||||||
<select >
|
|
||||||
<option value="United States">United States</option>
|
|
||||||
</select>
|
|
||||||
<input type="text" placeholder="Address" />
|
|
||||||
<a href="#" className="manual-address">Enter address manually</a>
|
|
||||||
|
|
||||||
<div className="payment-methods">
|
|
||||||
<h3>Payment method</h3>
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="payment" value="card" />
|
|
||||||
<span className="icon">💳</span> Card
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="payment" value="bank" />
|
|
||||||
<span className="icon">🏦</span> Bank
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="payment" value="klarna" />
|
|
||||||
<span className="icon klarna">K</span> Klarna
|
|
||||||
<div className="klarna-subtext">Buy now pay later</div>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="radio" name="payment" value="ideal" />
|
|
||||||
<span className="icon ideal">iD</span> iDEAL
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" className="pay-button">Pay</button>
|
{/* Checkout form */}
|
||||||
</form>
|
<div className={styles.checkoutSection}>
|
||||||
</>
|
<div>
|
||||||
) : (
|
<h2 className={styles.checkoutTitle}>Note / Request</h2>
|
||||||
<>
|
<div style={{ marginBottom: '1.5rem' }}>
|
||||||
{transactionSuccess ? (
|
<input
|
||||||
<div className="success-section">
|
type="text"
|
||||||
<div className="checkmark-container">
|
placeholder="Optional notes for seller"
|
||||||
<svg className="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
className={styles.inputNote}
|
||||||
<circle className="checkmark-circle" cx="26" cy="26" r="25" fill="none" />
|
/>
|
||||||
<path className="checkmark-check" fill="none" d="M14 27l7 7 16-16" />
|
|
||||||
</svg>
|
|
||||||
<h2>Payment Successful!</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button className={styles.paymentBtn}>Complete Payment</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="qris-section">
|
|
||||||
<h3>Scan QRIS to Pay</h3>
|
|
||||||
<QRCodeCanvas value={qrisData} size={256} />
|
|
||||||
<p className="qris-string">{qrisData}</p>
|
|
||||||
</div>
|
|
||||||
<h1>{value}</h1>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</>
|
{/* Footer */}
|
||||||
)}
|
<div className={styles.footerText}>
|
||||||
|
Powered by <span className={styles.footerHighlight}>Stripe</span> •{' '}
|
||||||
<div className="footer">
|
<a href="#" className={styles.footerLink}>Terms</a> •{' '}
|
||||||
Powered by <strong>stripe</strong> | Terms | Privacy
|
<a href="#" className={styles.footerLink}>Privacy Policy</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
183
src/Checkout.module.css
Normal file
183
src/Checkout.module.css
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
.checkoutCard {
|
||||||
|
border-radius: 1rem; /* rounded-2xl */
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; /* default mobile: vertical stack */
|
||||||
|
max-width: 28rem; /* max-w-md */
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
flex-grow: 1; /* agar bisa melebar dalam container flex */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartSection, .checkoutSection {
|
||||||
|
flex: 1; /* agar keduanya bisa melebar seimbang */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartSection {
|
||||||
|
padding: 1.5rem 1.5rem; /* p-6 */
|
||||||
|
background-color: #ececec; /* gray-50 */
|
||||||
|
border-bottom: 1px solid #F3F4F6; /* border-gray-100 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartTitle {
|
||||||
|
font-size: 1.25rem; /* text-xl */
|
||||||
|
font-weight: 600; /* font-semibold */
|
||||||
|
color: #1F2937; /* gray-800 */
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartList {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem; /* space-y-4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartItem {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemDetails {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productImage {
|
||||||
|
width: 3.5rem; /* 14 */
|
||||||
|
height: 3.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemText {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1F2937;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemPrice {
|
||||||
|
font-size: 0.875rem; /* text-sm */
|
||||||
|
color: #6B7280; /* gray-500 */
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.removeBtn {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: #EF4444; /* red-500 */
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.removeBtn:hover {
|
||||||
|
color: #B91C1C; /* red-700 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkoutSection {
|
||||||
|
padding: 2rem; /* p-8 */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkoutTitle {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1F2937;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputNote {
|
||||||
|
width: 100%; /* tetap full width agar input memenuhi container */
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 1px solid #D1D5DB; /* gray-300 */
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: box-shadow 0.2s ease, border-color 0.2s ease;
|
||||||
|
box-sizing: border-box; /* pastikan padding masuk ke width */
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputNote:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #3B82F6; /* blue-500 */
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.paymentBtn {
|
||||||
|
width: 100%; /* tombol harus full lebar */
|
||||||
|
background-color: #2563EB; /* blue-600 */
|
||||||
|
color: white;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 1.125rem; /* text-lg */
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 4px 6px rgba(37, 99, 235, 0.5);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paymentBtn:hover {
|
||||||
|
background-color: #1D4ED8; /* blue-700 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerText {
|
||||||
|
font-size: 0.75rem; /* text-xs */
|
||||||
|
color: #6B7280; /* gray-500 */
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerLink {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerLink:hover {
|
||||||
|
background-color: #E5E7EB; /* gray-200 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerHighlight {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151; /* gray-700 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive layout for desktop */
|
||||||
|
@media (min-width: 641px) {
|
||||||
|
.checkoutCard {
|
||||||
|
flex-direction: row; /* dua kolom berdampingan */
|
||||||
|
max-width: 64rem; /* lebar container desktop */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartSection {
|
||||||
|
border-bottom: none; /* hilangkan border bawah */
|
||||||
|
border-right: 1px solid #F3F4F6; /* border kanan */
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkoutSection {
|
||||||
|
padding: 2rem 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive layout for small screens */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.checkoutCard {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user