ok
This commit is contained in:
279
src/Checkout.js
279
src/Checkout.js
@@ -18,6 +18,13 @@ function parseJwt(token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatTimeLeft(ms) {
|
||||||
|
const totalSeconds = Math.max(Math.floor(ms / 1000), 0);
|
||||||
|
const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0');
|
||||||
|
const seconds = String(totalSeconds % 60).padStart(2, '0');
|
||||||
|
return `${minutes}:${seconds}`;
|
||||||
|
}
|
||||||
|
|
||||||
const Checkout = ({ socketId, transactionSuccess }) => {
|
const Checkout = ({ socketId, transactionSuccess }) => {
|
||||||
const [products, setProducts] = useState([]);
|
const [products, setProducts] = useState([]);
|
||||||
const [itemIds, setItemIds] = useState(null);
|
const [itemIds, setItemIds] = useState(null);
|
||||||
@@ -30,80 +37,32 @@ const Checkout = ({ socketId, transactionSuccess }) => {
|
|||||||
|
|
||||||
const [redirect_uri, setRedirect_Uri] = useState('');
|
const [redirect_uri, setRedirect_Uri] = useState('');
|
||||||
const [redirect_failed, setRedirect_Failed] = useState('');
|
const [redirect_failed, setRedirect_Failed] = useState('');
|
||||||
|
|
||||||
const [paymentMethod, setPaymentMethod] = useState('QRIS');
|
const [paymentMethod, setPaymentMethod] = useState('QRIS');
|
||||||
|
|
||||||
|
const [payTimeout, setPayTimeout] = useState(null);
|
||||||
|
const [timeLeft, setTimeLeft] = useState(null);
|
||||||
|
|
||||||
|
const [activeAccordion, setActiveAccordion] = useState('QRIS');
|
||||||
|
|
||||||
|
let grandTotal = 0;
|
||||||
|
let tax = 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
if (!socketId) return;
|
||||||
const tokenParam = urlParams.get('token');
|
|
||||||
const itemsIdString = urlParams.get('itemsId');
|
|
||||||
setRedirect_Uri(urlParams.get('redirect_uri') || '');
|
|
||||||
setRedirect_Failed(urlParams.get('redirect_failed') || '');
|
|
||||||
setToken(tokenParam);
|
|
||||||
|
|
||||||
if (!itemsIdString) {
|
let urlParams = new URLSearchParams(window.location.search);
|
||||||
window.location.href = urlParams.get('redirect_failed') || '/';
|
let tokenParam = urlParams.get('token');
|
||||||
return;
|
let itemsIdString = urlParams.get('itemsId');
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsedIds = JSON.parse(itemsIdString);
|
|
||||||
if (!Array.isArray(parsedIds) || parsedIds.length === 0) {
|
|
||||||
window.location.href = urlParams.get('redirect_failed') || '/';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setItemIds(parsedIds);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Invalid itemsId format', e);
|
|
||||||
window.location.href = urlParams.get('redirect_failed') || '/';
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Fetch products
|
|
||||||
useEffect(() => {
|
|
||||||
if (itemIds && Array.isArray(itemIds) && itemIds.length > 0) {
|
|
||||||
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ itemsId: itemIds, noParents: true }),
|
|
||||||
})
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data) => setProducts(data))
|
|
||||||
.catch((err) => console.error('Error fetching products:', err));
|
|
||||||
}
|
|
||||||
}, [itemIds]);
|
|
||||||
|
|
||||||
const handleRemove = (id) => {
|
|
||||||
const updatedItemIds = itemIds.filter((itemId) => itemId !== id);
|
|
||||||
const updatedProducts = products.filter((product) => product.id !== id);
|
|
||||||
|
|
||||||
if (updatedItemIds.length === 0) {
|
|
||||||
window.location.href = redirect_failed;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setItemIds(updatedItemIds);
|
|
||||||
setProducts(updatedProducts);
|
|
||||||
|
|
||||||
const url = new URL(window.location);
|
|
||||||
url.searchParams.set('itemsId', JSON.stringify(updatedItemIds));
|
|
||||||
window.history.replaceState(null, '', url.toString());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePay = async () => {
|
const handlePay = async () => {
|
||||||
if (!itemIds || !token) {
|
|
||||||
alert('Token atau itemsId tidak ditemukan.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoadingPay(true);
|
setLoadingPay(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const newName = urlParams.get('new_name');
|
const newName = urlParams.get('new_name');
|
||||||
const setName = urlParams.get('set_name');
|
const setName = urlParams.get('set_name');
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
itemIds.forEach((id) => params.append('itemsId', id));
|
itemsIdString.forEach((id) => params.append('itemsId', id));
|
||||||
params.append('socketId', socketId);
|
params.append('socketId', socketId);
|
||||||
params.append('paymentMethod', paymentMethod);
|
params.append('paymentMethod', paymentMethod);
|
||||||
if (newName) params.append('newName', newName);
|
if (newName) params.append('newName', newName);
|
||||||
@@ -113,22 +72,27 @@ const Checkout = ({ socketId, transactionSuccess }) => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${tokenParam}`,
|
||||||
},
|
},
|
||||||
body: params.toString(),
|
body: params.toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (paymentMethod === 'QRIS' && result?.qris_dynamic) {
|
|
||||||
setQrisData(result.qris_dynamic);
|
|
||||||
setValue(result.total_price);
|
setValue(result.total_price);
|
||||||
} else if (paymentMethod === 'Bank Transfer' && result?.bank_account) {
|
if (result.pay_timeout && result.time_now) {
|
||||||
setTransferData(result);
|
const timeout = new Date(result.pay_timeout).getTime();
|
||||||
setValue(result.total_price);
|
const now = new Date(result.time_now).getTime();
|
||||||
} else {
|
setPayTimeout(timeout);
|
||||||
alert(`Gagal memproses pembayaran: ${result?.error || 'Unknown error'}`);
|
setTimeLeft(timeout - now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setQrisData(result.qris_dynamic || null);
|
||||||
|
setTransferData(result);
|
||||||
|
|
||||||
|
grandTotal = result.total_price;
|
||||||
|
tax = 0;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
alert(`Request gagal: ${result?.error || 'Unknown error'}`);
|
alert(`Request gagal: ${result?.error || 'Unknown error'}`);
|
||||||
}
|
}
|
||||||
@@ -140,6 +104,33 @@ const Checkout = ({ socketId, transactionSuccess }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
itemsIdString = JSON.parse(urlParams.get('itemsId'));
|
||||||
|
setRedirect_Uri(urlParams.get('redirect_uri') || '');
|
||||||
|
setRedirect_Failed(urlParams.get('redirect_failed') || '');
|
||||||
|
setToken(tokenParam);
|
||||||
|
|
||||||
|
if (!itemsIdString || !Array.isArray(itemsIdString) || itemsIdString.length === 0) {
|
||||||
|
window.location.href = urlParams.get('redirect_failed') || '/';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setItemIds(itemsIdString);
|
||||||
|
handlePay();
|
||||||
|
}, [socketId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (itemIds?.length > 0) {
|
||||||
|
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ itemsId: itemIds, noParents: true }),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => setProducts(data))
|
||||||
|
.catch((err) => console.error('Error fetching products:', err));
|
||||||
|
}
|
||||||
|
}, [itemIds]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (transactionSuccess) {
|
if (transactionSuccess) {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
@@ -149,17 +140,25 @@ const Checkout = ({ socketId, transactionSuccess }) => {
|
|||||||
}
|
}
|
||||||
}, [transactionSuccess, redirect_uri]);
|
}, [transactionSuccess, redirect_uri]);
|
||||||
|
|
||||||
const subtotal = products.reduce((acc, item) => acc + (item.price || 0), 0);
|
useEffect(() => {
|
||||||
const shipping = 0;
|
if (!payTimeout) return;
|
||||||
const tax = 0;
|
const interval = setInterval(() => {
|
||||||
const grandTotal = subtotal + shipping + tax;
|
const now = Date.now();
|
||||||
|
const remaining = payTimeout - now;
|
||||||
|
setTimeLeft(remaining);
|
||||||
|
if (remaining <= 0) {
|
||||||
|
clearInterval(interval);
|
||||||
|
alert('Waktu pembayaran habis.');
|
||||||
|
window.location.href = redirect_failed;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [payTimeout]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.page}>
|
<div className={styles.page}>
|
||||||
<div className={styles.checkoutCard}>
|
<div className={styles.checkoutCard}>
|
||||||
<div className={styles.cartSection}>
|
<div className={styles.cartSection}>
|
||||||
{!qrisData && !transferData ? (
|
|
||||||
<>
|
|
||||||
<div className={styles.invHeader}>
|
<div className={styles.invHeader}>
|
||||||
<div>
|
<div>
|
||||||
<h2 className={styles.brand}>
|
<h2 className={styles.brand}>
|
||||||
@@ -181,27 +180,16 @@ const Checkout = ({ socketId, transactionSuccess }) => {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Item</th>
|
<th>Item</th>
|
||||||
<th>Quantity</th>
|
<th className={styles.textRight}>{grandTotal}</th>
|
||||||
<th className={styles.textRight}>Subtotal</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{products.map((item) => (
|
{products.map((item) => (
|
||||||
<tr key={item.id}>
|
<tr key={item.id}>
|
||||||
<td>{item.name}</td>
|
<td>{item.name}</td>
|
||||||
<td>{item.qty ?? 1}</td>
|
|
||||||
<td className={styles.textRight}>
|
<td className={styles.textRight}>
|
||||||
Rp{(item.price || 0).toLocaleString('id-ID')}
|
Rp{(item.price || 0).toLocaleString('id-ID')}
|
||||||
</td>
|
</td>
|
||||||
<td className={styles.textRight}>
|
|
||||||
<button
|
|
||||||
className={styles.removeBtn}
|
|
||||||
onClick={() => handleRemove(item.id)}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -210,11 +198,7 @@ const Checkout = ({ socketId, transactionSuccess }) => {
|
|||||||
<div className={styles.summary}>
|
<div className={styles.summary}>
|
||||||
<div className={styles.summaryRow}>
|
<div className={styles.summaryRow}>
|
||||||
<span>SUBTOTAL</span>
|
<span>SUBTOTAL</span>
|
||||||
<span>Rp{subtotal.toLocaleString('id-ID')}</span>
|
<span>Rp{grandTotal.toLocaleString('id-ID')}</span>
|
||||||
</div>
|
|
||||||
<div className={styles.summaryRow}>
|
|
||||||
<span>SHIPPING</span>
|
|
||||||
<span>Rp{shipping.toLocaleString('id-ID')}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.summaryRow}>
|
<div className={styles.summaryRow}>
|
||||||
<span>TAX</span>
|
<span>TAX</span>
|
||||||
@@ -225,12 +209,32 @@ const Checkout = ({ socketId, transactionSuccess }) => {
|
|||||||
<span>Rp{grandTotal.toLocaleString('id-ID')}</span>
|
<span>Rp{grandTotal.toLocaleString('id-ID')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
) : paymentMethod === 'QRIS' ? (
|
|
||||||
|
<div className={styles.checkoutSection}>
|
||||||
|
{(qrisData || transferData) && (
|
||||||
|
<div className="mt-4">
|
||||||
|
{qrisData && (
|
||||||
|
<div className={styles.accordion}>
|
||||||
|
<div
|
||||||
|
className={styles.accordionHeader}
|
||||||
|
onClick={() =>
|
||||||
|
setActiveAccordion(activeAccordion === 'QRIS' ? '' : 'QRIS')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
QRIS Payment
|
||||||
|
</div>
|
||||||
|
{activeAccordion === 'QRIS' && (
|
||||||
|
<div className={styles.accordionBody}>
|
||||||
|
<QRCodeCanvas value={qrisData} size={200} />
|
||||||
|
{!transactionSuccess && (
|
||||||
<>
|
<>
|
||||||
<p className={styles.qrTitle}>Silakan scan QRIS ini</p>
|
<h5 className="mt-3">Rp{value?.toLocaleString('id-ID')}</h5>
|
||||||
<div className={styles.qrBox}>
|
{timeLeft !== null && (
|
||||||
<QRCodeCanvas value={qrisData} size={256} />
|
<p>Waktu tersisa: <strong>{formatTimeLeft(timeLeft)}</strong></p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{transactionSuccess && (
|
{transactionSuccess && (
|
||||||
<div className={styles.CheckmarkOverlay}>
|
<div className={styles.CheckmarkOverlay}>
|
||||||
<svg className={styles.Checkmark} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
<svg className={styles.Checkmark} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
||||||
@@ -239,67 +243,36 @@ const Checkout = ({ socketId, transactionSuccess }) => {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!transactionSuccess && <h2>Rp{value?.toLocaleString('id-ID')}</h2>}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className={styles.transferBox}>
|
|
||||||
<h3 className={styles.transferTitle}>Bank Transfer Information</h3>
|
|
||||||
<div className={styles.transferRow}><span>Bank</span><span>{transferData?.bank_name}</span></div>
|
|
||||||
<div className={styles.transferRow}><span>Account No</span><span>{transferData?.bank_account}</span></div>
|
|
||||||
<div className={styles.transferRow}><span>Account Name</span><span>{transferData?.account_name}</span></div>
|
|
||||||
<div className={styles.transferRow}><span>Total</span><span>Rp{value?.toLocaleString('id-ID')}</span></div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={styles.checkoutSection}>
|
{transferData && (
|
||||||
<div>
|
<div className={styles.accordion}>
|
||||||
<h2 className={styles.checkoutTitle}>
|
<div
|
||||||
Rp{(qrisData || transferData ? value : grandTotal).toLocaleString('id-ID')}
|
className={styles.accordionHeader}
|
||||||
</h2>
|
onClick={() =>
|
||||||
|
setActiveAccordion(activeAccordion === 'Bank' ? '' : 'Bank')
|
||||||
<div className={styles.paymentInfo}>
|
}
|
||||||
<p className={styles.paymentHeading}>PAYMENT INFORMATION</p>
|
|
||||||
<label className={styles.radioLabel}>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="payment"
|
|
||||||
value="Bank Transfer"
|
|
||||||
checked={paymentMethod === 'Bank Transfer'}
|
|
||||||
onChange={(e) => setPaymentMethod(e.target.value)}
|
|
||||||
/>
|
|
||||||
Bank Transfer
|
|
||||||
</label>
|
|
||||||
<label className={styles.radioLabel}>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="payment"
|
|
||||||
value="QRIS"
|
|
||||||
checked={paymentMethod === 'QRIS'}
|
|
||||||
onChange={(e) => setPaymentMethod(e.target.value)}
|
|
||||||
/>
|
|
||||||
QRIS
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.inputGroup}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder={parseJwt(token)?.username || 'User'}
|
|
||||||
className={styles.inputNote}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className={styles.paymentBtn}
|
|
||||||
onClick={handlePay}
|
|
||||||
disabled={loadingPay || qrisData !== null || transferData !== null}
|
|
||||||
>
|
>
|
||||||
{loadingPay ? 'Processing...' : (qrisData || transferData) ? 'Payment Created' : 'PAY'}
|
Bank Transfer
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
{activeAccordion === 'Bank' && (
|
||||||
|
<div className={styles.accordionBody}>
|
||||||
|
<div><strong>Bank:</strong> {transferData?.bank_name}</div>
|
||||||
|
<div><strong>Account No:</strong> {transferData?.bank_account}</div>
|
||||||
|
<div><strong>Account Name:</strong> {transferData?.account_name}</div>
|
||||||
|
<div><strong>Total:</strong> Rp{value?.toLocaleString('id-ID')}</div>
|
||||||
|
{timeLeft !== null && (
|
||||||
|
<div><strong>Waktu Tersisa:</strong> {formatTimeLeft(timeLeft)}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={styles.footerText}>
|
<div className={styles.footerText}>
|
||||||
Powered by <span className={styles.footerHighlight}>KEDIRITECHNOPARK</span>
|
Powered by <span className={styles.footerHighlight}>KEDIRITECHNOPARK</span>
|
||||||
|
|||||||
@@ -46,18 +46,20 @@
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
.brand {
|
.brand {
|
||||||
|
margin-top: 0;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #2563eb;
|
color: #2563eb;
|
||||||
}
|
}
|
||||||
.brandLight { font-weight: 500; }
|
.brandLight { font-weight: 500; }
|
||||||
.greeting { margin-top: .5rem; font-size: .875rem; color: #374151; }
|
.greeting { text-align: left; margin-top: .5rem; font-size: .875rem; color: #374151; }
|
||||||
.orderInfo { text-align: right; font-size: .875rem; color: #6b7280; }
|
.orderInfo { text-align: right; font-size: .875rem; color: #6b7280; }
|
||||||
.invoiceLabel { font-size: 1rem; color: #4b5563; }
|
.invoiceLabel { font-size: 1rem; color: #4b5563; }
|
||||||
.orderMeta { margin-top: .125rem; }
|
.orderMeta { margin-top: .125rem; }
|
||||||
|
|
||||||
/* Table */
|
/* Table */
|
||||||
.table {
|
.table {
|
||||||
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin-top: .5rem;
|
margin-top: .5rem;
|
||||||
@@ -67,7 +69,6 @@
|
|||||||
.table th, .table td { padding: .75rem 0; }
|
.table th, .table td { padding: .75rem 0; }
|
||||||
.table th {
|
.table th {
|
||||||
border-bottom: 1px solid #d1d5db;
|
border-bottom: 1px solid #d1d5db;
|
||||||
text-align: left;
|
|
||||||
color: #374151;
|
color: #374151;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -210,3 +211,87 @@
|
|||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.checkoutCard { flex-direction: column; }
|
.checkoutCard { flex-direction: column; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selectPayment {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectPaymentModern {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml;utf8,<svg fill='gray' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/></svg>");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 1rem center;
|
||||||
|
background-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.selectPaymentModern:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transferBox {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: #f9fafb;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.transferBox span {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.transferTitle {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
.transferRow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
border-bottom: 1px dashed #d1d5db;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
.transferRow:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordionHeader {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 12px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordionHeader:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordionBody {
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user