s12
parent
9f2f235c09
commit
e8f62c18a6
|
@ -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 <Loader />;
|
||||
}
|
||||
|
||||
if (!orderData) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center flex-grow py-10 text-center px-4 text-gray-600 mt-20">
|
||||
<FileX className="mb-4 text-gray-500" size={100} />
|
||||
<p className="text-lg">No payment request found. <a className="text-[#6d9e37] font-medium hover:underline" href="/profile">Click here</a> to go to your profile.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
<strong>Error:</strong> {error}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const formattedBalance = new Intl.NumberFormat('en-IN', {
|
||||
style: 'currency',
|
||||
currency: orderData.currency, // fallback if undefined
|
||||
maximumFractionDigits: 2,
|
||||
}).format(orderData.payment_data?.amount || 0);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen p-4">
|
||||
<Card className="max-w-md w-full bg-white p-6 rounded-lg shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle>Add Balance to Your Account</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
{orderData && (
|
||||
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex justify-between mb-2">
|
||||
<span className="font-bold">Order ID:</span>
|
||||
<span>{orderData.txn_id}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="font-bold">Amount:</span>
|
||||
<span>{formattedBalance}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<span className="font-medium">Pay Using:</span>
|
||||
<hr className="border-b border-b-[#6d9e37] mb-2" />
|
||||
|
||||
<Button disabled={orderData.currency !== 'INR'} onClick={handleQRPaymentClick}>UPI QR Code</Button>
|
||||
{
|
||||
orderData.currency !== 'INR' && (
|
||||
<p className="text-center text-xs text-gray-500">UPI QR Code Payment support only for INR currency</p>
|
||||
)
|
||||
}
|
||||
<span className="text-center text-gray-400 text-xs">Banking Service by DWD Consultancy Services (0% Processing Fee)</span>
|
||||
|
||||
<div className="flex items-center my-2">
|
||||
<div className="flex-grow border-t border-gray-500"></div>
|
||||
<span className="mx-2 text-gray-500 font-semibold">OR</span>
|
||||
<div className="flex-grow border-t border-gray-500"></div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
disabled={orderData.currency !== 'INR'}
|
||||
className="text-[#414042] font-bold border-[2px] border-[#00ad7d] hover:bg-[#00ad7d]/80"
|
||||
onClick={redirectToPayU}
|
||||
variant="outline"
|
||||
>
|
||||
Pay with
|
||||
<img className="w-[50px]" src="/assets/payu-logo.svg" alt="PayU" />
|
||||
</Button>
|
||||
{
|
||||
orderData.currency !== 'INR' && (
|
||||
<p className="text-center text-xs text-gray-500">Pay with PayU support only for INR currency</p>
|
||||
)
|
||||
}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="mt-2 text-[#414042] font-bold border-[2px] border-b-[#003087] border-r-[#003087] border-t-[#009CDE] border-l-[#009CDE] hover:bg-gradient-to-r hover:from-[#009CDE] hover:to-[#003087]"
|
||||
onClick={handlePayPalPayment}
|
||||
>
|
||||
Pay with
|
||||
<img className="w-[50px]" src="/assets/paypal-logo-white.svg" alt="PayPal" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* QR Code Modal */}
|
||||
<Dialog open={showQRModal} onOpenChange={(open) => {setShowQRModal(open); if (!open) {setShowHelpMessage(false);}}}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Scan QR Code to Add Balance</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<div className="p-4 bg-white rounded-lg border border-gray-200">
|
||||
<QRCode value={upiPaymentLink} size={256} level="H" />
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-500 text-center">
|
||||
Scan this QR code with any UPI app to complete your payment
|
||||
</p>
|
||||
|
||||
<Button variant="outline" onClick={handleCopy}>
|
||||
{copied ? 'Copied!' : orderId}
|
||||
{!copied && <Copy size={15} className="ml-2" />}
|
||||
</Button>
|
||||
|
||||
{showHelpMessage && (
|
||||
<div className="w-full mt-4 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
|
||||
<p className="text-sm text-yellow-800 mb-3">
|
||||
If you haven't received payment confirmation, please share your transaction ID with us.
|
||||
</p>
|
||||
<Input
|
||||
type="text"
|
||||
value={transactionId}
|
||||
onChange={(e) => setTransactionId(e.target.value)}
|
||||
placeholder="Enter UPI transaction ID"
|
||||
className="w-full mb-2"
|
||||
/>
|
||||
<Button onClick={handleSavePayment} className="w-full">
|
||||
Verify Payment
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Payment Response Toast */}
|
||||
{paymentResponse.visible && (
|
||||
<div className="fixed bottom-4 left-0 right-0 flex justify-center">
|
||||
<div className={`p-4 rounded-lg shadow-lg max-w-md w-full mx-4 ${
|
||||
paymentResponse.isSuccess
|
||||
? 'bg-green-50 border border-green-200 text-green-800'
|
||||
: 'bg-red-50 border border-red-200 text-red-800'
|
||||
}`}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
{paymentResponse.isSuccess ? (
|
||||
<CheckCircle2 className="w-5 h-5 mr-2 text-green-500" />
|
||||
) : (
|
||||
<AlertCircle className="w-5 h-5 mr-2 text-red-500" />
|
||||
)}
|
||||
<span>{paymentResponse.message}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setPaymentResponse(prev => ({...prev, visible: false}))}
|
||||
className="p-1 rounded-full hover:bg-opacity-20 hover:bg-current"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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) {
|
||||
|
@ -209,7 +210,7 @@ export default function TopicDetail(props) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="markdown-content" className="table-scroll-wrapper font-light mb-8 text-justify prose max-w-none" dangerouslySetInnerHTML={{ __html: marked.parse(props.topic.content || '') }} ></div>
|
||||
<div id="markdown-content" className="table-scroll-wrapper font-light mb-8 text-justify prose max-w-none leading-8" dangerouslySetInnerHTML={{ __html: marked.parse(props.topic.content || '') }} ></div>
|
||||
{
|
||||
allGalleryImages && (
|
||||
<ImageSlider images={allGalleryImages} />
|
||||
|
|
|
@ -42,7 +42,7 @@ export default function TopicItems(props) {
|
|||
</div>
|
||||
{
|
||||
props.topics.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 max-w-5xl mx-auto">
|
||||
{
|
||||
props.topics.map((topic) => (
|
||||
<a href={`/topic/${topic.slug}`} key={topic.id} className="hover:scale-[1.02] transition-transform duration-200 ">
|
||||
|
@ -52,7 +52,7 @@ export default function TopicItems(props) {
|
|||
</div>
|
||||
<CardContent className="flex-1 p-6">
|
||||
<CardTitle className="mb-2 line-clamp-1">{topic.title}</CardTitle>
|
||||
<CardDescription className="line-clamp-4 mb-4 text-justify" dangerouslySetInnerHTML={{ __html: marked.parse(topic.content || '') }}></CardDescription>
|
||||
<CardDescription className="line-clamp-6 mb-4 text-justify" dangerouslySetInnerHTML={{ __html: marked.parse(topic.content || '') }}></CardDescription>
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="text-xs text-gray-500"><strong>Author: </strong>{topic.user_name}</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
|
@ -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<UserData | null>(null);
|
||||
const [invoiceList, setInvoiceList] = useState<any[]>([]);
|
||||
|
@ -52,10 +53,27 @@ export default function ProfilePage() {
|
|||
const [toast, setToast] = useState<ToastState>({ visible: false, message: '' });
|
||||
const [txnId, setTxnId] = useState<string>('');
|
||||
const [userEmail, setUserEmail] = useState<string>('');
|
||||
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 <Loader />;
|
||||
}
|
||||
const formattedBalance = new Intl.NumberFormat('en-IN', {
|
||||
style: 'currency',
|
||||
currency: 'INR',
|
||||
maximumFractionDigits: 2,
|
||||
}).format(balance);
|
||||
return (
|
||||
<div className="space-y-6 container mx-auto">
|
||||
<Separator />
|
||||
|
@ -171,7 +230,17 @@ export default function ProfilePage() {
|
|||
<div className="flex-1 lg:max-w-3xl">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between">
|
||||
<CardTitle>Personal Information</CardTitle>
|
||||
<div className="group relative overflow-hidden w-fit cursor-pointer" onClick={() => {setAddBalanceModal(true)}}>
|
||||
<div className="flex items-center space-x-2 border-y border-l border-[#6d9e37] rounded-full relative z-10 transition-all duration-300">
|
||||
<p className="text-lg font-bold text-center px-2 group-hover:text-white">Balance: {formattedBalance}</p>
|
||||
<Button title="Add Balance" className="text-lg rounded-full group-hover:bg-[#262626] z-10" variant="outline" size="sm">+</Button>
|
||||
</div>
|
||||
<div className="absolute inset-0 bg-[#6d9e37] translate-x-full group-hover:translate-x-0 transition-transform duration-300 ease-in-out z-0 rounded-full"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<CardDescription>
|
||||
Update your personal information and avatar.
|
||||
</CardDescription>
|
||||
|
@ -367,6 +436,76 @@ export default function ProfilePage() {
|
|||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Add balance modal */}
|
||||
<Dialog open={addBalanceModal} onOpenChange={setAddBalanceModal}>
|
||||
<DialogContent className="max-w-md sm:rounded-2xl p-6">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="">Add Balance</DialogTitle>
|
||||
<DialogDescription className="">Choose an amount or enter to add to your balance</DialogDescription>
|
||||
{formError && <p className="text-red-600 text-sm mt-2">{formError}</p>}
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex gap-3 flex-wrap mb-4">
|
||||
{["INR", "USD"].map((currency) => (
|
||||
<Button
|
||||
key={currency}
|
||||
variant={selectedCurrency === currency ? "default" : "outline"}
|
||||
onClick={() => {
|
||||
setSelectedCurrency(currency);
|
||||
setSelectedAmount('');
|
||||
setCustomAmount('');
|
||||
setFormError('');
|
||||
}}
|
||||
>
|
||||
{currency}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
{balanceData
|
||||
.find((item) => item.currency === selectedCurrency)
|
||||
?.balanceList.map((amount) => (
|
||||
<Button
|
||||
key={amount}
|
||||
variant={customAmount === amount ? "default" : "outline"}
|
||||
onClick={() => {
|
||||
setCustomAmount(amount);
|
||||
setSelectedAmount(amount);
|
||||
}}
|
||||
>
|
||||
{selectedCurrency === "INR" ? `₹${amount}` : `$${amount}`}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className={`flex items-center gap-2 border-b ${
|
||||
customAmount ? "border-[#6d9e37]" : "border-gray-700"
|
||||
} p-2 rounded-md text-[#6d9e37] font-bold outline-none mt-4 w-full`}
|
||||
>
|
||||
<span className="text-sm whitespace-nowrap">You’ve selected</span>
|
||||
<span className="text-lg">
|
||||
{selectedCurrency === "INR" ? "₹" : "$"}
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Amount"
|
||||
className="w-20 bg-transparent outline-none text-[#6d9e37] text-lg font-bold text-center"
|
||||
value={customAmount}
|
||||
onChange={(e) => {
|
||||
setCustomAmount(e.target.value);
|
||||
setSelectedAmount(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<span className="text-sm whitespace-nowrap">to top up your balance.</span>
|
||||
</div>
|
||||
<DialogFooter className="mt-6 flex justify-between w-full">
|
||||
<Button variant="outline" onClick={() => setAddBalanceModal(false)} className="w-full"> Cancel </Button>
|
||||
<Button disabled={!customAmount || balanceApiloading} onClick={handleNext} className="w-full">{balanceApiloading ? "Processing..." : "Next"}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 <div>Loading...</div>;
|
||||
if (error) return <div>Error checking login status</div>;
|
||||
if (error) return <div>Error checking login status: {error.message}</div>;
|
||||
|
||||
return isLoggedIn ? children : fallback;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import AddBalancePage from "../components/AddBalance";
|
||||
---
|
||||
<Layout title="">
|
||||
<AddBalancePage client:load />
|
||||
</Layout>
|
|
@ -1,68 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Image Upload Test</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.upload-container {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.file-input-wrapper {
|
||||
margin: 20px 0;
|
||||
}
|
||||
.file-label {
|
||||
display: inline-block;
|
||||
padding: 10px 15px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.upload-btn {
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.note {
|
||||
margin-top: 20px;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>Image Upload Test</h1>
|
||||
|
||||
<div class="upload-container">
|
||||
<!-- Pure HTML form with direct submission -->
|
||||
<form
|
||||
action="/v1/check/"
|
||||
method="POST"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
|
||||
<input type="submit" value="Upload Image" class="upload-btn">
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
import Layout from "../layouts/Layout.astro"
|
||||
import TestFileUpload from "../components/TestFileUpload"
|
||||
---
|
||||
<Layout title="">
|
||||
<TestFileUpload client:load />
|
||||
</Layout>
|
|
@ -1,9 +0,0 @@
|
|||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout title="">
|
||||
<div class="text-center text-2xl font-bold my-4">Test to Include PHP under Astro file</div>
|
||||
<div set:html="<?php echo 'Hello! I am from PHP'; ?>"></div>
|
||||
</Layout>
|
||||
|
|
@ -51,33 +51,3 @@ export async function getStaticPaths() {
|
|||
<Layout title={`${topic.title ? topic.title + '- Silicon Topic' : 'Silicon Topic'}`} ogImage={topic.img? topic.img : '/assets/images/thumb-place.jpg'} canonicalURL={`/topic/${topic.slug}`} >
|
||||
<TopicDetail client:load topic={topic} />
|
||||
</Layout>
|
||||
<style>
|
||||
#markdown-content > table, th, tbody, td{
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
#markdown-content > table > thead > tr > th{
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#markdown-content > table > thead > tr > th:first-child,
|
||||
#markdown-content > table > tbody > tr > td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#markdown-content > table > thead > tr > th:not(:first-child),
|
||||
#markdown-content > table > tbody > tr > td:not(:first-child) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-scroll-wrapper > table {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
table-layout: auto;
|
||||
}
|
||||
.table-scroll-wrapper > table {
|
||||
min-width: 600px; /* adjust as needed */
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
</style>
|
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue