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 { useIsLoggedIn } from '../lib/isLoggedIn';
|
||||||
import { token, user_name, pb_id } from '../lib/CookieValues';
|
import { token, user_name, pb_id } from '../lib/CookieValues';
|
||||||
import CommentSystem from './CommentSystem/CommentSystem';
|
import CommentSystem from './CommentSystem/CommentSystem';
|
||||||
|
import '../styles/markdown.css'
|
||||||
|
|
||||||
const COMMENTS_API_URL = 'https://host-api-sxashuasysagibx.siliconpin.com/v1/comments/';
|
const COMMENTS_API_URL = 'https://host-api-sxashuasysagibx.siliconpin.com/v1/comments/';
|
||||||
export default function TopicDetail(props) {
|
export default function TopicDetail(props) {
|
||||||
|
@ -123,7 +124,7 @@ export default function TopicDetail(props) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-12">
|
<div className="container mx-auto px-4 py-12">
|
||||||
<article className="max-w-4xl mx-auto">
|
<article className="max-w-4xl mx-auto">
|
||||||
<img src={props.topic.img ? props.topic.img : '/assets/images/thumb-place.jpg'} alt={props.topic.title} className="w-full object-cover rounded-lg mb-8 shadow-md" />
|
<img src={props.topic.img ? props.topic.img : '/assets/images/thumb-place.jpg'} alt={props.topic.title} className="w-full object-cover rounded-lg mb-8 shadow-md" />
|
||||||
<h1 className="text-4xl font-bold text-[#6d9e37] mb-6">{props.topic.title}</h1>
|
<h1 className="text-4xl font-bold text-[#6d9e37] mb-6">{props.topic.title}</h1>
|
||||||
|
|
||||||
{/* Enhanced Social Share Buttons */}
|
{/* Enhanced Social Share Buttons */}
|
||||||
|
@ -209,7 +210,7 @@ export default function TopicDetail(props) {
|
||||||
</div>
|
</div>
|
||||||
</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 && (
|
allGalleryImages && (
|
||||||
<ImageSlider images={allGalleryImages} />
|
<ImageSlider images={allGalleryImages} />
|
||||||
|
|
|
@ -42,7 +42,7 @@ export default function TopicItems(props) {
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
props.topics.length > 0 ? (
|
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) => (
|
props.topics.map((topic) => (
|
||||||
<a href={`/topic/${topic.slug}`} key={topic.id} className="hover:scale-[1.02] transition-transform duration-200 ">
|
<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>
|
</div>
|
||||||
<CardContent className="flex-1 p-6">
|
<CardContent className="flex-1 p-6">
|
||||||
<CardTitle className="mb-2 line-clamp-1">{topic.title}</CardTitle>
|
<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">
|
<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"><strong>Author: </strong>{topic.user_name}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default function TopicCreation() {
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
current_page: 1,
|
current_page: 1,
|
||||||
last_page: 1,
|
last_page: 1,
|
||||||
per_page: 10,
|
per_page: 12,
|
||||||
total: 0
|
total: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { PDFDownloadLink } from '@react-pdf/renderer';
|
||||||
import InvoicePDF from "../lib/InvoicePDF";
|
import InvoicePDF from "../lib/InvoicePDF";
|
||||||
import { useIsLoggedIn } from '../lib/isLoggedIn';
|
import { useIsLoggedIn } from '../lib/isLoggedIn';
|
||||||
import PasswordUpdateCard from './PasswordUpdateCard';
|
import PasswordUpdateCard from './PasswordUpdateCard';
|
||||||
|
import Index from "../pages/topic/index.astro";
|
||||||
interface SessionData {
|
interface SessionData {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +43,7 @@ type PaymentData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
const { isLoggedIn, loading, sessionData } = useIsLoggedIn();
|
const { isLoggedIn, loading, sessionData, balance } = useIsLoggedIn();
|
||||||
const typedSessionData = sessionData as SessionData | null;
|
const typedSessionData = sessionData as SessionData | null;
|
||||||
const [userData, setUserData] = useState<UserData | null>(null);
|
const [userData, setUserData] = useState<UserData | null>(null);
|
||||||
const [invoiceList, setInvoiceList] = useState<any[]>([]);
|
const [invoiceList, setInvoiceList] = useState<any[]>([]);
|
||||||
|
@ -52,10 +53,27 @@ export default function ProfilePage() {
|
||||||
const [toast, setToast] = useState<ToastState>({ visible: false, message: '' });
|
const [toast, setToast] = useState<ToastState>({ visible: false, message: '' });
|
||||||
const [txnId, setTxnId] = useState<string>('');
|
const [txnId, setTxnId] = useState<string>('');
|
||||||
const [userEmail, setUserEmail] = 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_USER_API_URL = import.meta.env.PUBLIC_USER_API_URL;
|
||||||
const PUBLIC_INVOICE_API_URL = import.meta.env.PUBLIC_INVOICE_API_URL;
|
const PUBLIC_INVOICE_API_URL = import.meta.env.PUBLIC_INVOICE_API_URL;
|
||||||
|
const currentBalanceList = balanceData.find(item => item.currency === selectedCurrency)?.balanceList || [];
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSessionData = async () => {
|
const fetchSessionData = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -106,6 +124,42 @@ export default function ProfilePage() {
|
||||||
getInvoiceListData();
|
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) => {
|
const showToast = (message: string) => {
|
||||||
|
@ -164,6 +218,11 @@ export default function ProfilePage() {
|
||||||
if (!userData) {
|
if (!userData) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
const formattedBalance = new Intl.NumberFormat('en-IN', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'INR',
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
}).format(balance);
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 container mx-auto">
|
<div className="space-y-6 container mx-auto">
|
||||||
<Separator />
|
<Separator />
|
||||||
|
@ -171,7 +230,17 @@ export default function ProfilePage() {
|
||||||
<div className="flex-1 lg:max-w-3xl">
|
<div className="flex-1 lg:max-w-3xl">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Personal Information</CardTitle>
|
<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>
|
<CardDescription>
|
||||||
Update your personal information and avatar.
|
Update your personal information and avatar.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
@ -367,6 +436,76 @@ export default function ProfilePage() {
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
const PUBLIC_USER_API_URL = import.meta.env.PUBLIC_USER_API_URL;
|
const PUBLIC_USER_API_URL = import.meta.env.PUBLIC_USER_API_URL;
|
||||||
|
|
||||||
export const useIsLoggedIn = () => {
|
export const useIsLoggedIn = () => {
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(null); // null means "unknown"
|
const [isLoggedIn, setIsLoggedIn] = useState(null);
|
||||||
const [sessionData, setSessionData] = useState(null);
|
const [sessionData, setSessionData] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const [balance, setBalance] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkLoginStatus = async () => {
|
const checkLoginStatus = async () => {
|
||||||
|
@ -13,14 +15,30 @@ export const useIsLoggedIn = () => {
|
||||||
const response = await fetch(`${PUBLIC_USER_API_URL}?query=isLoggedIn`, {
|
const response = await fetch(`${PUBLIC_USER_API_URL}?query=isLoggedIn`, {
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Network response was not ok (status: ${response.status})`);
|
||||||
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
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);
|
setLoading(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err);
|
setError(err);
|
||||||
setIsLoggedIn(false);
|
setIsLoggedIn(false);
|
||||||
setSessionData(null);
|
setSessionData(null);
|
||||||
|
setBalance(0);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -28,14 +46,14 @@ export const useIsLoggedIn = () => {
|
||||||
checkLoginStatus();
|
checkLoginStatus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return { isLoggedIn, sessionData, loading, error };
|
return { isLoggedIn, sessionData, loading, error, balance };
|
||||||
};
|
};
|
||||||
|
|
||||||
const IsLoggedIn = ({ children, fallback = null }) => {
|
const IsLoggedIn = ({ children, fallback = null }) => {
|
||||||
const { isLoggedIn, loading, error } = useIsLoggedIn();
|
const { isLoggedIn, loading, error, balance } = useIsLoggedIn();
|
||||||
|
|
||||||
if (loading) return <div>Loading...</div>;
|
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;
|
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>
|
|
@ -4,5 +4,5 @@ import PaymentSucces from "../components/PaymentSuccessfully";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="">
|
<Layout title="">
|
||||||
<PaymentSucces client:load />
|
<PaymentSucces client:load />
|
||||||
</Layout>
|
</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>
|
|
||||||
|
|
|
@ -50,34 +50,4 @@ 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}`} >
|
<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} />
|
<TopicDetail client:load topic={topic} />
|
||||||
</Layout>
|
</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