373 lines
15 KiB
TypeScript
373 lines
15 KiB
TypeScript
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
|
import { Button } from "./ui/button";
|
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "./ui/dialog";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "./ui/card";
|
|
import { Input } from "./ui/input";
|
|
import { Label } from "./ui/label";
|
|
import { Separator } from "./ui/separator";
|
|
import { Textarea } from "./ui/textarea";
|
|
import React, { useState, useEffect } from 'react';
|
|
import {AvatarUpload} from './AvatarUpload';
|
|
import {localizeTime} from "../lib/localizeTime";
|
|
import Loader from "./ui/loader";
|
|
import { Eye, Pencil, Trash2, Download, ChevronUp, ChevronDown, Search } from "lucide-react";
|
|
import { PDFDownloadLink } from '@react-pdf/renderer';
|
|
import InvoicePDF from "../lib/InvoicePDF";
|
|
import { useIsLoggedIn } from '../lib/isLoggedIn';
|
|
import PasswordUpdateCard from './PasswordUpdateCard';
|
|
interface SessionData {
|
|
[key: string]: any;
|
|
}
|
|
|
|
interface UserData {
|
|
success: boolean;
|
|
session_data: SessionData;
|
|
user_avatar: string;
|
|
}
|
|
interface BillingItems {
|
|
[key: string]: any;
|
|
}
|
|
type ToastState = {
|
|
visible: boolean;
|
|
message: string;
|
|
};
|
|
|
|
type Invoice = {
|
|
billing_id: string;
|
|
};
|
|
|
|
type PaymentData = {
|
|
txn_id: string;
|
|
user_email: string;
|
|
};
|
|
|
|
export default function ProfilePage() {
|
|
const { isLoggedIn, loading, sessionData } = useIsLoggedIn();
|
|
const typedSessionData = sessionData as SessionData | null;
|
|
const [userData, setUserData] = useState<UserData | null>(null);
|
|
const [invoiceList, setInvoiceList] = useState<any[]>([]);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
const [selectedData, setSelectedData] = useState<BillingItems | null>(null);
|
|
const [toast, setToast] = useState<ToastState>({ visible: false, message: '' });
|
|
const [txnId, setTxnId] = useState<string>('');
|
|
const [userEmail, setUserEmail] = useState<string>('');
|
|
const USER_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/users/';
|
|
const INVOICE_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/invoice/';
|
|
|
|
useEffect(() => {
|
|
const fetchSessionData = async () => {
|
|
try {
|
|
const response = await fetch(`${USER_API_URL}?query=get-user`, {
|
|
credentials: 'include', // Crucial for cookies
|
|
headers: { 'Accept': 'application/json' }
|
|
}
|
|
);
|
|
|
|
const data = await response.json();
|
|
if (!response.ok || !data.success) {
|
|
throw new Error(data.error || 'Session fetch failed');
|
|
}
|
|
setUserData(data);
|
|
return data.session_data;
|
|
} catch (error) {
|
|
console.error('Fetch error:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
const getInvoiceListData = async () => {
|
|
try {
|
|
const response = await fetch(`${USER_API_URL}?query=invoice-info`, {
|
|
method: 'GET',
|
|
credentials: 'include', // Crucial for cookies
|
|
headers: { 'Accept': 'application/json' }
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.message || 'Session fetch failed');
|
|
}
|
|
|
|
setInvoiceList(data.data); // Fix: Use `data.data` instead of `data`
|
|
return data.data; // Fix: `session_data` does not exist in response
|
|
} catch (error) {
|
|
console.error('Fetch error:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
fetchSessionData();
|
|
getInvoiceListData();
|
|
}, []);
|
|
|
|
|
|
|
|
const showToast = (message: string) => {
|
|
setToast({ visible: true, message });
|
|
setTimeout(() => setToast({ visible: false, message: '' }), 3000);
|
|
};
|
|
|
|
const handlePanelBuyNow = (invoice: Invoice) => {
|
|
// setTxnId(data.txn_id);
|
|
// setUserEmail(data.user_email);
|
|
window.location.href = `/make-payment?query=get-initiated_payment&orderId=${invoice.billing_id}`;
|
|
showToast('Redirecting to payment page...');
|
|
};
|
|
|
|
|
|
|
|
const handleViewItems = (items: BillingItems) => {
|
|
setDialogOpen(true);
|
|
setSelectedData(items);
|
|
};
|
|
|
|
const sessionLogOut = () => {
|
|
fetch(`${USER_API_URL}?query=logout`, {
|
|
method: 'GET',
|
|
credentials: 'include'
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if(data.success === true){
|
|
window.location.href = '/';
|
|
}
|
|
console.log('Logout Console', data.success);
|
|
})
|
|
}
|
|
|
|
if (loading) {
|
|
return <Loader />;
|
|
}
|
|
|
|
// Then handle not logged in state
|
|
if (!isLoggedIn) {
|
|
return (
|
|
<p className="text-center my-8">
|
|
You are not logged in! Please login first to access this page.{" "}
|
|
<a className="text-[#6d9e37]" href="/login">Click Here</a> to login
|
|
</p>
|
|
);
|
|
}
|
|
|
|
// Then handle error state
|
|
if (error) {
|
|
return <div>Error: {error}</div>;
|
|
}
|
|
|
|
// Then handle case where user data hasn't loaded yet
|
|
if (!userData) {
|
|
return <Loader />;
|
|
}
|
|
return (
|
|
<div className="space-y-6 container mx-auto">
|
|
<Separator />
|
|
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
|
|
<div className="flex-1 lg:max-w-3xl">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Personal Information</CardTitle>
|
|
<CardDescription>
|
|
Update your personal information and avatar.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="flex items-center space-x-4">
|
|
<Avatar className="h-16 w-16">
|
|
<AvatarImage src={userData.session_data?.user_avatar} />
|
|
<AvatarFallback>JP</AvatarFallback>
|
|
</Avatar>
|
|
{typedSessionData?.id && <AvatarUpload userId={typedSessionData.id} />}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="firstName">Full Name</Label>
|
|
<Input id="firstName" defaultValue={userData.session_data?.user_name || 'Jhon'} />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="phone">Phone</Label>
|
|
<Input id="phone" defaultValue={userData.session_data?.user_vatar || ''} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="email">Email</Label>
|
|
<Input id="email" type="email" defaultValue={userData.session_data?.user_email || ''} />
|
|
</div>
|
|
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="mt-6">
|
|
<CardHeader>
|
|
<div className="flex flex-row justify-between">
|
|
<CardTitle>Billing Information</CardTitle>
|
|
<a href="/profile/billing-info" className="hover:bg-[#6d9e37] hover:text-white transtion duration-500 py-1 px-2 rounded">View All</a>
|
|
</div>
|
|
|
|
<CardDescription>
|
|
View your billing history.
|
|
</CardDescription>
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr>
|
|
<th className="text-left">Order ID</th>
|
|
<th className="text-center">Invoice Date</th>
|
|
<th className="text-left">Description</th>
|
|
<th className="text-center">Amount</th>
|
|
<th className="text-center">Status</th>
|
|
<th className="text-center">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{
|
|
invoiceList.slice(0, 5).map((invoice, index) => (
|
|
<tr key={index} className="">
|
|
<td>{invoice.billing_id}</td>
|
|
<td className="text-center">{invoice?.created_at.split(' ')[0]}</td>
|
|
<td className=""><p className="line-clamp-1">{invoice.service}</p></td>
|
|
<td className="text-center">{invoice.amount}</td>
|
|
<td className={`text-center text-sm rounded-full h-fit ${invoice.status === 'pending' ? 'text-yellow-500' : invoice.status === 'completed' ? 'text-green-500' : 'text-red-500'}`}>{invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}</td>
|
|
<td className="text-center flex justify-center items-center gap-2 p-2">
|
|
<button onClick={() => handleViewItems(invoice)}>
|
|
<Eye />
|
|
</button>
|
|
<PDFDownloadLink
|
|
title="Download PDF"
|
|
document={<InvoicePDF data={invoice} />}
|
|
fileName={`invoice_${invoice.billing_id}.pdf`}
|
|
className="text-[#6d9e37] hover:text-green-600"
|
|
>
|
|
{({ loading }) => (loading ? '...' : <Download className="w-5 h-5" />)}
|
|
</PDFDownloadLink>
|
|
{
|
|
invoice.status !== 'completed' ? (
|
|
<Button onClick={() => {handlePanelBuyNow(invoice)}} variant="outline" size="sm">
|
|
{
|
|
invoice.status === 'pending' ? 'Pay' : invoice.status === 'failed' ? 'Retry' : ''
|
|
}
|
|
</Button>
|
|
) : ''
|
|
}
|
|
|
|
</td>
|
|
</tr>
|
|
))
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</CardHeader>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="flex-1 space-y-6 lg:max-w-xl">
|
|
<Card className="">
|
|
<CardHeader>
|
|
<div className="flex flex-row justify-between">
|
|
<CardTitle>My Services</CardTitle>
|
|
{/* <a href="/profile/billing-info" className="hover:bg-[#6d9e37] hover:text-white transtion duration-500 py-1 px-2 rounded">View All</a> */}
|
|
</div>
|
|
|
|
<CardDescription>
|
|
View your all Services.
|
|
</CardDescription>
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr>
|
|
<th className="text-left">Order ID</th>
|
|
<th className="text-center">Date</th>
|
|
<th className="text-left">Description</th>
|
|
<th className="text-center">Status</th>
|
|
{/* <th className="text-center">Action</th> */}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{
|
|
invoiceList.slice(0, 5).map((invoice, index) => (
|
|
<tr key={index} className="">
|
|
<td>{invoice.billing_id}</td>
|
|
<td className="text-center">{invoice?.created_at.split(' ')[0]}</td>
|
|
<td className=""><p className="line-clamp-1">{invoice.service}</p></td>
|
|
<td className={`text-center text-sm rounded-full h-fit ${invoice.service_status === '0' ? 'text-yellow-500' : invoice.service_status === '1' ? 'text-green-500' : 'text-red-500'}`}>{invoice.service_status === '1' ? 'Active' : invoice.service_status === '0' ? 'Unactive' : ''}</td>
|
|
</tr>
|
|
))
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</CardHeader>
|
|
</Card>
|
|
<PasswordUpdateCard userId={userData?.session_data?.id} onLogout={sessionLogOut} />
|
|
<Card className="mt-6">
|
|
<CardHeader>
|
|
<CardTitle>Danger Zone</CardTitle>
|
|
<CardDescription>These actions are irreversible. Proceed with caution.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex flex-col space-y-4">
|
|
<Button onClick={sessionLogOut} className="bg-red-500 hover:bg-red-600">Logout</Button>
|
|
<Button className="bg-red-500 hover:bg-red-600">Delete account</Button>
|
|
<Button variant="outline">Export data</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
<DialogContent className="max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle className="">Billing Details</DialogTitle>
|
|
<DialogDescription className="">Review the details for Billing ID: <span className="font-bold">{selectedData?.billing_id}</span></DialogDescription>
|
|
</DialogHeader>
|
|
<div className="mt-4 space-y-4 text-sm text-gray-700">
|
|
<div className="flex justify-between">
|
|
<span className="font-bold">Service:</span>
|
|
<span>{selectedData?.service}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="font-bold">cycle:</span>
|
|
<span className="capitalize">{selectedData?.cycle}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="font-bold">Amount:</span>
|
|
<span>₹{selectedData?.amount}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="font-bold">User:</span>
|
|
<span>{selectedData?.user}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="font-bold">Status:</span>
|
|
<span className={`px-2 py-0.5 rounded text-white text-xs ${selectedData?.status === 'pending' ? 'bg-yellow-500' : selectedData?.status === 'completed' ? 'bg-green-500' : 'bg-red-500'}`}>
|
|
{selectedData?.status}
|
|
</span>
|
|
</div>
|
|
<hr className="my-2 border-gray-200" />
|
|
<div className="flex justify-between">
|
|
<span className="font-bold">Created At:</span>
|
|
<span>{localizeTime(selectedData?.created_at)}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="font-bold">Updated At:</span>
|
|
<span>{selectedData?.updated_at ? localizeTime(selectedData.updated_at) : '—'}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="font-bold">Silicon ID:</span>
|
|
<span>{selectedData?.siliconId}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter className="mt-6">
|
|
{/* Optional: Add action buttons here */}
|
|
{/* <Button onClick={() => setDialogOpen(false)}>Close</Button> */}
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|
|
|