sp/src/components/UserProfile.tsx

339 lines
13 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "./ui/select";
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">
<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>&#8377;{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>
);
}