From e8f62c18a61eaa027f2427223f89f4822154b271 Mon Sep 17 00:00:00 2001 From: suvodip ghosh Date: Sat, 12 Jul 2025 13:22:40 +0000 Subject: [PATCH] s12 --- src/components/AddBalance.jsx | 360 +++++++++++++++++++++++++++++++++ src/components/TopicDetail.jsx | 5 +- src/components/TopicItem.jsx | 4 +- src/components/Topics.jsx | 2 +- src/components/UserProfile.tsx | 145 ++++++++++++- src/lib/isLoggedIn.jsx | 30 ++- src/pages/add-balance.astro | 7 + src/pages/success.astro | 2 +- src/pages/test-file.html | 68 ------- src/pages/test-up.astro | 7 - src/pages/test.astro | 9 - src/pages/topic/[id].astro | 32 +-- src/styles/markdown.css | 28 +++ 13 files changed, 569 insertions(+), 130 deletions(-) create mode 100644 src/components/AddBalance.jsx create mode 100644 src/pages/add-balance.astro delete mode 100644 src/pages/test-file.html delete mode 100644 src/pages/test-up.astro delete mode 100644 src/pages/test.astro create mode 100644 src/styles/markdown.css diff --git a/src/components/AddBalance.jsx b/src/components/AddBalance.jsx new file mode 100644 index 0000000..32cdb25 --- /dev/null +++ b/src/components/AddBalance.jsx @@ -0,0 +1,360 @@ +import React, {useState, useEffect} from "react"; +import { Button } from "./ui/button"; +import { Input } from "./ui/input"; +import QRCode from "react-qr-code"; +import { Dialog, DialogContent, DialogHeader, DialogTitle} from "./ui/dialog"; +import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; +import { FileX, Copy, CheckCircle2, AlertCircle, X } from "lucide-react"; +import Loader from "./ui/loader"; + +const PUBLIC_USER_API_URL = 'https://siliconpin.com/v1/balance/'; +// const PUBLIC_USER_API_URL = import.meta.env.PUBLIC_USER_API_URL; + +export default function AddBalance() { + const [orderData, setOrderData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [showQRModal, setShowQRModal] = useState(false); + const [showHelpMessage, setShowHelpMessage] = useState(false); + const [upiPaymentLink, setUpiPaymentLink] = useState(""); + const [copied, setCopied] = useState(false); + const [orderId, setOrderId] = useState(''); + const [transactionId, setTransactionId] = useState(''); + const [paymentResponse, setPaymentResponse] = useState({ + message: '', + visible: false, + isSuccess: false + }); + + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const orderId = urlParams.get('orderId'); + + if (!orderId) { + setError('Order ID is missing from URL'); + setIsLoading(false); + return; + } + + setOrderId(orderId); + fetchOrderData(orderId); + }, []); + + const fetchOrderData = async (orderId) => { + setIsLoading(true); + try { + const formData = new FormData(); + formData.append('order_id', orderId); + + const response = await fetch(`${PUBLIC_USER_API_URL}?query=get-initiated-add-balance`, { + method: 'POST', + headers: { + 'Content-Type' : 'application/json' + }, + body: JSON.stringify({order_id:orderId}), + credentials: 'include' + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (!data.success) { + throw new Error(data.message || 'Failed to fetch payment details'); + } + + setOrderData(data); + setError(null); + + // Generate UPI payment link + if (data.payment_data?.amount) { + const upiLink = generateUPILink(data.payment_data.amount, data.txn_id); + setUpiPaymentLink(upiLink); + } + } catch (error) { + setError(error.message || 'Failed to load payment details. Please try again.'); + console.error('Error:', error); + } finally { + setIsLoading(false); + } + }; + + function generateUPILink(amount, transactionId) { + const merchantUPI = "7001601485@okbizaxis"; + const merchantName = "SiliconPin"; + const currency = "INR"; + const transactionNote = encodeURIComponent(`Balance top-up #${transactionId}`); + + return `upi://pay?pa=${merchantUPI}&pn=${encodeURIComponent(merchantName)}&am=${amount}&cu=${currency}&tn=${transactionNote}`; + } + + const redirectToPayU = () => { + if (!orderData?.payment_data || !orderData.payment_url) { + console.error('Payment data not loaded yet'); + alert('Payment information is not ready. Please wait.'); + return; + } + + // Create a form dynamically + const form = document.createElement('form'); + form.method = 'POST'; + form.action = orderData.payment_url; + form.style.display = 'none'; + + Object.entries(orderData.payment_data).forEach(([key, value]) => { + const input = document.createElement('input'); + input.type = 'text'; + input.name = key; + input.value = value; + form.appendChild(input); + }); + + document.body.appendChild(form); + form.submit(); + }; + + const handlePayPalPayment = () => { + const rawInrAmount = orderData.payment_data?.amount || 0; + const exchangeRate = 80; + const convertedUSD = rawInrAmount / exchangeRate; + const usdAmount = Math.max(convertedUSD, 1).toFixed(2); + const paypalUrl = `https://www.paypal.com/paypalme/dwdconsultancy/${usdAmount}`; + window.open(paypalUrl, '_blank'); + }; + + const handleQRPaymentClick = () => { + if(orderData.currency !== 'INR'){ + return + } + if (!upiPaymentLink) { + alert('Payment information is not ready. Please wait.'); + return; + } + setShowQRModal(true); + }; + + const handleCopy = () => { + navigator.clipboard.writeText(orderId) + .then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 1000); + }) + .catch(err => { + console.error('Failed to copy text: ', err); + }); + }; + + const handleSavePayment = async () => { + try { + const response = await fetch(`${PUBLIC_USER_API_URL}?query=save-balance-payment`, { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + orderId: orderId, + transactionId: transactionId + }) + }); + + const data = await response.json(); + + if (data.success) { + setPaymentResponse({ + message: `Payment of ₹${orderData?.payment_data?.amount} recorded. Your balance will be updated shortly.`, + visible: true, + isSuccess: true + }); + } else { + throw new Error(data.message || 'Payment verification failed'); + } + } catch (error) { + console.error('Error:', error); + setPaymentResponse({ + message: 'Failed to verify payment. Please contact support.', + visible: true, + isSuccess: false + }); + } + }; + + useEffect(() => { + let timer; + if (showQRModal) { + setShowHelpMessage(false); + timer = setTimeout(() => { + setShowHelpMessage(true); + }, 2000); + } + return () => clearTimeout(timer); + }, [showQRModal]); + + if (isLoading) { + return ; + } + + if (!orderData) { + return ( +
+ +

No payment request found. Click here to go to your profile.

+
+ ); + } + + if (error) { + return ( +
+
+ Error: {error} +
+
+ ); + } + + const formattedBalance = new Intl.NumberFormat('en-IN', { + style: 'currency', + currency: orderData.currency, // fallback if undefined + maximumFractionDigits: 2, + }).format(orderData.payment_data?.amount || 0); + + return ( +
+ + + Add Balance to Your Account + + + + {orderData && ( +
+
+ Order ID: + {orderData.txn_id} +
+
+ Amount: + {formattedBalance} +
+
+ )} + +
+ Pay Using: +
+ + + { + orderData.currency !== 'INR' && ( +

UPI QR Code Payment support only for INR currency

+ ) + } + Banking Service by DWD Consultancy Services (0% Processing Fee) + +
+
+ OR +
+
+ + + { + orderData.currency !== 'INR' && ( +

Pay with PayU support only for INR currency

+ ) + } + + +
+
+
+ + {/* QR Code Modal */} + {setShowQRModal(open); if (!open) {setShowHelpMessage(false);}}}> + + + Scan QR Code to Add Balance + +
+
+ +
+ +

+ Scan this QR code with any UPI app to complete your payment +

+ + + + {showHelpMessage && ( +
+

+ If you haven't received payment confirmation, please share your transaction ID with us. +

+ setTransactionId(e.target.value)} + placeholder="Enter UPI transaction ID" + className="w-full mb-2" + /> + +
+ )} +
+
+
+ + {/* Payment Response Toast */} + {paymentResponse.visible && ( +
+
+
+
+ {paymentResponse.isSuccess ? ( + + ) : ( + + )} + {paymentResponse.message} +
+ +
+
+
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/TopicDetail.jsx b/src/components/TopicDetail.jsx index b0b7e0f..e80c80c 100644 --- a/src/components/TopicDetail.jsx +++ b/src/components/TopicDetail.jsx @@ -8,6 +8,7 @@ import { Button } from "./ui/button"; import { useIsLoggedIn } from '../lib/isLoggedIn'; import { token, user_name, pb_id } from '../lib/CookieValues'; import CommentSystem from './CommentSystem/CommentSystem'; +import '../styles/markdown.css' const COMMENTS_API_URL = 'https://host-api-sxashuasysagibx.siliconpin.com/v1/comments/'; export default function TopicDetail(props) { @@ -123,7 +124,7 @@ export default function TopicDetail(props) { return (
- {props.topic.title} + {props.topic.title}

{props.topic.title}

{/* Enhanced Social Share Buttons */} @@ -209,7 +210,7 @@ export default function TopicDetail(props) {
-
+
{ allGalleryImages && ( diff --git a/src/components/TopicItem.jsx b/src/components/TopicItem.jsx index ebc483e..de43321 100644 --- a/src/components/TopicItem.jsx +++ b/src/components/TopicItem.jsx @@ -42,7 +42,7 @@ export default function TopicItems(props) { { props.topics.length > 0 ? ( -
+
{ props.topics.map((topic) => ( @@ -52,7 +52,7 @@ export default function TopicItems(props) {
{topic.title} - +

Author: {topic.user_name}

diff --git a/src/components/Topics.jsx b/src/components/Topics.jsx index 16d5af2..d578f85 100644 --- a/src/components/Topics.jsx +++ b/src/components/Topics.jsx @@ -15,7 +15,7 @@ export default function TopicCreation() { const [pagination, setPagination] = useState({ current_page: 1, last_page: 1, - per_page: 10, + per_page: 12, total: 0 }); diff --git a/src/components/UserProfile.tsx b/src/components/UserProfile.tsx index b775fbe..5df5bae 100644 --- a/src/components/UserProfile.tsx +++ b/src/components/UserProfile.tsx @@ -15,6 +15,7 @@ import { PDFDownloadLink } from '@react-pdf/renderer'; import InvoicePDF from "../lib/InvoicePDF"; import { useIsLoggedIn } from '../lib/isLoggedIn'; import PasswordUpdateCard from './PasswordUpdateCard'; +import Index from "../pages/topic/index.astro"; interface SessionData { [key: string]: any; } @@ -42,7 +43,7 @@ type PaymentData = { }; export default function ProfilePage() { - const { isLoggedIn, loading, sessionData } = useIsLoggedIn(); + const { isLoggedIn, loading, sessionData, balance } = useIsLoggedIn(); const typedSessionData = sessionData as SessionData | null; const [userData, setUserData] = useState(null); const [invoiceList, setInvoiceList] = useState([]); @@ -52,10 +53,27 @@ export default function ProfilePage() { const [toast, setToast] = useState({ visible: false, message: '' }); const [txnId, setTxnId] = useState(''); const [userEmail, setUserEmail] = useState(''); + const [addBalanceModal, setAddBalanceModal] = useState(false); + const [balanceData, setBalanceData] = useState([ + { + currency: "INR", + balanceList: ["100", "500", "1000", "1500"] + }, + { + currency: "USD", + balanceList: ["10", "50", "100", "150"] + } + ]); + + const [selectedAmount, setSelectedAmount] = useState(''); + const [customAmount, setCustomAmount] = useState(''); + const [selectedCurrency, setSelectedCurrency] = useState("INR"); + const [balanceApiloading, setBalanceApiloading] = useState(false); + const [formError, setFormError] = useState(''); const PUBLIC_USER_API_URL = import.meta.env.PUBLIC_USER_API_URL; const PUBLIC_INVOICE_API_URL = import.meta.env.PUBLIC_INVOICE_API_URL; - + const currentBalanceList = balanceData.find(item => item.currency === selectedCurrency)?.balanceList || []; useEffect(() => { const fetchSessionData = async () => { try { @@ -106,6 +124,42 @@ export default function ProfilePage() { getInvoiceListData(); }, []); + const handleNext = async () => { + const amountToSend = selectedAmount === "others" ? customAmount : selectedAmount; + if (!amountToSend || isNaN(Number(amountToSend))) { + setFormError("Please enter a valid amount."); + return; + } + + const formData = new FormData(); + formData.append('amount', amountToSend); + formData.append('currency', selectedCurrency); // ✅ Include currency + + try { + setBalanceApiloading(true); + + const response = await fetch(`https://siliconpin.com/v1/balance/?query=initiate-add-balance`, { + method: "POST", + credentials: 'include', + body: formData, + }); + + const data = await response.json(); + + if (data.success === true) { + window.location.href = `/add-balance?orderId=${data.transaction_id}`; + } else { + alert("Something went wrong. Please try again."); + } + } catch (error) { + console.error("API Error:", error); + alert("An error occurred while processing your request."); + } finally { + setBalanceApiloading(false); + } + }; + + const showToast = (message: string) => { @@ -164,6 +218,11 @@ export default function ProfilePage() { if (!userData) { return ; } + const formattedBalance = new Intl.NumberFormat('en-IN', { + style: 'currency', + currency: 'INR', + maximumFractionDigits: 2, + }).format(balance); return (

@@ -171,7 +230,17 @@ export default function ProfilePage() {
- Personal Information +
+ Personal Information +
{setAddBalanceModal(true)}}> +
+

Balance: {formattedBalance}

+ +
+
+
+ +
Update your personal information and avatar. @@ -367,6 +436,76 @@ export default function ProfilePage() { + + {/* Add balance modal */} + + + + Add Balance + Choose an amount or enter to add to your balance + {formError &&

{formError}

} +
+ +
+ {["INR", "USD"].map((currency) => ( + + ))} +
+ +
+ {balanceData + .find((item) => item.currency === selectedCurrency) + ?.balanceList.map((amount) => ( + + ))} +
+
+ You’ve selected + + {selectedCurrency === "INR" ? "₹" : "$"} + + { + setCustomAmount(e.target.value); + setSelectedAmount(e.target.value); + }} + /> + to top up your balance. +
+ + + + +
+
); } diff --git a/src/lib/isLoggedIn.jsx b/src/lib/isLoggedIn.jsx index a3153aa..b64f1cf 100644 --- a/src/lib/isLoggedIn.jsx +++ b/src/lib/isLoggedIn.jsx @@ -1,11 +1,13 @@ import { useState, useEffect } from 'react'; const PUBLIC_USER_API_URL = import.meta.env.PUBLIC_USER_API_URL; + export const useIsLoggedIn = () => { - const [isLoggedIn, setIsLoggedIn] = useState(null); // null means "unknown" + const [isLoggedIn, setIsLoggedIn] = useState(null); const [sessionData, setSessionData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [balance, setBalance] = useState(0); useEffect(() => { const checkLoginStatus = async () => { @@ -13,14 +15,30 @@ export const useIsLoggedIn = () => { const response = await fetch(`${PUBLIC_USER_API_URL}?query=isLoggedIn`, { credentials: 'include' }); + + if (!response.ok) { + throw new Error(`Network response was not ok (status: ${response.status})`); + } + const data = await response.json(); - setIsLoggedIn(!!data?.isLoggedIn); - setSessionData(data?.session_data || null); + + if (!data.success) { + throw new Error('API request was not successful'); + } + + setIsLoggedIn(!!data.isLoggedIn); + setSessionData(data.session_data || null); + + + const userData = data.userDBData?.[0]; // Get first item in array + setBalance(Number(data?.userDBData?.balance || 0)); + setLoading(false); } catch (err) { setError(err); setIsLoggedIn(false); setSessionData(null); + setBalance(0); setLoading(false); } }; @@ -28,14 +46,14 @@ export const useIsLoggedIn = () => { checkLoginStatus(); }, []); - return { isLoggedIn, sessionData, loading, error }; + return { isLoggedIn, sessionData, loading, error, balance }; }; const IsLoggedIn = ({ children, fallback = null }) => { - const { isLoggedIn, loading, error } = useIsLoggedIn(); + const { isLoggedIn, loading, error, balance } = useIsLoggedIn(); if (loading) return
Loading...
; - if (error) return
Error checking login status
; + if (error) return
Error checking login status: {error.message}
; return isLoggedIn ? children : fallback; }; diff --git a/src/pages/add-balance.astro b/src/pages/add-balance.astro new file mode 100644 index 0000000..cd1fa8d --- /dev/null +++ b/src/pages/add-balance.astro @@ -0,0 +1,7 @@ +--- +import Layout from "../layouts/Layout.astro"; +import AddBalancePage from "../components/AddBalance"; +--- + + + \ No newline at end of file diff --git a/src/pages/success.astro b/src/pages/success.astro index a3203e8..fe6c639 100644 --- a/src/pages/success.astro +++ b/src/pages/success.astro @@ -4,5 +4,5 @@ import PaymentSucces from "../components/PaymentSuccessfully"; --- - + \ No newline at end of file diff --git a/src/pages/test-file.html b/src/pages/test-file.html deleted file mode 100644 index 3488147..0000000 --- a/src/pages/test-file.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - Image Upload Test - - - -
-

Image Upload Test

- -
- -
- - - - -
-
-
- - - - - diff --git a/src/pages/test-up.astro b/src/pages/test-up.astro deleted file mode 100644 index 95cf04b..0000000 --- a/src/pages/test-up.astro +++ /dev/null @@ -1,7 +0,0 @@ ---- -import Layout from "../layouts/Layout.astro" -import TestFileUpload from "../components/TestFileUpload" ---- - - - \ No newline at end of file diff --git a/src/pages/test.astro b/src/pages/test.astro deleted file mode 100644 index d52f551..0000000 --- a/src/pages/test.astro +++ /dev/null @@ -1,9 +0,0 @@ ---- -import Layout from "../layouts/Layout.astro"; ---- - - -
Test to Include PHP under Astro file
-
-
- diff --git a/src/pages/topic/[id].astro b/src/pages/topic/[id].astro index ae864dd..faefb4b 100644 --- a/src/pages/topic/[id].astro +++ b/src/pages/topic/[id].astro @@ -50,34 +50,4 @@ export async function getStaticPaths() { - - \ No newline at end of file + \ No newline at end of file diff --git a/src/styles/markdown.css b/src/styles/markdown.css new file mode 100644 index 0000000..7cda3b6 --- /dev/null +++ b/src/styles/markdown.css @@ -0,0 +1,28 @@ +/* markdown.css */ + +/* Wrapper div scroll korbe */ +.table-scroll-wrapper { + @apply overflow-x-auto; + } + + /* Markdown-er table scrollable + bordered hobe */ + .table-scroll-wrapper table { + @apply w-max min-w-full border border-gray-300 block; + } + + /* Table cell border, padding */ + .table-scroll-wrapper th, + .table-scroll-wrapper td { + @apply border border-gray-300 px-4 py-2 text-left; + } + + + /* markdown.css */ +table { + border-collapse: collapse; + } + th, td { + border: 1px solid #d1d5db; /* Tailwind's gray-300 */ + padding: 0.5rem 1rem; + } + \ No newline at end of file