last work on invoice functionality
parent
1ed908b12e
commit
c927fd6087
|
@ -26,6 +26,7 @@ interface AuthResponse {
|
||||||
record: UserRecord;
|
record: UserRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
const [email, setEmail] = useState('suvodip@siliconpin.com');
|
const [email, setEmail] = useState('suvodip@siliconpin.com');
|
||||||
const [password, setPassword] = useState('Simple2pass');
|
const [password, setPassword] = useState('Simple2pass');
|
||||||
|
@ -35,18 +36,30 @@ const LoginPage = () => {
|
||||||
|
|
||||||
const pb = new PocketBase("https://tst-pb.s38.siliconpin.com");
|
const pb = new PocketBase("https://tst-pb.s38.siliconpin.com");
|
||||||
|
|
||||||
|
interface AuthResponse {
|
||||||
|
token: string;
|
||||||
|
record: {
|
||||||
|
query: string;
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
avatar: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: FormEvent) => {
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setStatus({ message: '', isError: false });
|
setStatus({ message: '', isError: false });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authData = await pb.collection("users").authWithPassword(email, password);
|
const authData = await pb.collection("users").authWithPassword(email, password);
|
||||||
|
const avatarUrl = authData.record.avatar ? pb.files.getUrl(authData.record, authData.record.avatar) : '';
|
||||||
|
|
||||||
const authResponse: AuthResponse = {
|
const authResponse: AuthResponse = {
|
||||||
token: authData.token,
|
token: authData.token,
|
||||||
record: {
|
record: {
|
||||||
query: 'new',
|
query: 'new',
|
||||||
id: authData.record.id,
|
id: authData.record.id,
|
||||||
email: authData.record.email,
|
email: authData.record.email,
|
||||||
name: authData.record.name || '',
|
name: authData.record.name || '',
|
||||||
|
@ -54,8 +67,8 @@ const LoginPage = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await syncSessionWithBackend(authResponse);
|
await syncSessionWithBackend(authResponse, avatarUrl);
|
||||||
// window.location.href = '/profile';
|
window.location.href = '/profile';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Login failed:", error);
|
console.error("Login failed:", error);
|
||||||
setStatus({
|
setStatus({
|
||||||
|
@ -77,20 +90,21 @@ const LoginPage = () => {
|
||||||
if (!authData?.record) {
|
if (!authData?.record) {
|
||||||
throw new Error("No user record found");
|
throw new Error("No user record found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const avatarUrl = authData.record.avatar ? pb.files.getUrl(authData.record, authData.record.avatar) : '';
|
||||||
const authResponse: AuthResponse = {
|
const authResponse: AuthResponse = {
|
||||||
token: authData.token,
|
token: authData.token,
|
||||||
record: {
|
record: {
|
||||||
query: 'new',
|
query: 'new',
|
||||||
id: authData.record.id,
|
id: authData.record.id,
|
||||||
email: authData.record.email || '',
|
email: authData.record.email || '',
|
||||||
name: authData.record.name || '',
|
name: authData.record.name || '',
|
||||||
avatar: authData.record.avatar || ''
|
avatar: authData.record.avatar || ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await syncSessionWithBackend(authResponse);
|
await syncSessionWithBackend(authResponse, avatarUrl);
|
||||||
// window.location.href = '/profile';
|
window.location.href = '/profile';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`${provider} Login failed:`, error);
|
console.error(`${provider} Login failed:`, error);
|
||||||
setStatus({
|
setStatus({
|
||||||
|
@ -101,33 +115,35 @@ const LoginPage = () => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const syncSessionWithBackend = async (authData: AuthResponse) => {
|
const syncSessionWithBackend = async (authData: AuthResponse, avatarUrl: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:2058/host-api/v1/users/session/', {
|
const response = await fetch('http://localhost:2058/host-api/v1/users/session/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include', // Crucial for cookies
|
credentials: 'include', // Important for cookies
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
accessToken: authData.token,
|
query: 'new',
|
||||||
email: authData.record.email,
|
accessToken: authData.token,
|
||||||
name: authData.record.name,
|
email: authData.record.email,
|
||||||
avatar: authData.record.avatar
|
name: authData.record.name,
|
||||||
? pb.files.getUrl(authData.record, authData.record.avatar)
|
avatar: avatarUrl,
|
||||||
: '',
|
isAuthenticated: true,
|
||||||
isAuthenticated: true,
|
id: authData.record.id
|
||||||
id: authData.record.id
|
})
|
||||||
})
|
});
|
||||||
});
|
|
||||||
|
if (!response.ok) {
|
||||||
if (!response.ok) throw new Error('Failed to sync session');
|
throw new Error('Failed to sync session');
|
||||||
|
}
|
||||||
const data = await response.json();
|
|
||||||
console.log('Session synced:', data);
|
const data = await response.json();
|
||||||
} catch (error) {
|
console.log('Session synced with backend:', data);
|
||||||
console.error('Error syncing session:', error);
|
} catch (error) {
|
||||||
}
|
console.error('Error syncing session:', error);
|
||||||
};
|
throw error; // Re-throw the error if you want calling functions to handle it
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center p-4">
|
<div className="min-h-screen flex items-center justify-center p-4">
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { useState, useRef } from 'react';
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Input } from "./ui/input";
|
||||||
|
import { Label } from "./ui/label";
|
||||||
|
import { X } from "lucide-react"; // Import an icon for the remove button
|
||||||
|
|
||||||
|
export function AvatarUpload() {
|
||||||
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (e.target.files && e.target.files.length > 0) {
|
||||||
|
setSelectedFile(e.target.files[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleRemoveFile = () => {
|
||||||
|
setSelectedFile(null);
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = ''; // Reset file input
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{!selectedFile ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Label
|
||||||
|
htmlFor="avatar"
|
||||||
|
className="bg-primary hover:bg-primary/90 text-primary-foreground py-2 px-4 text-sm rounded-md cursor-pointer transition-colorsfocus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
|
||||||
|
Change Avatar
|
||||||
|
</Label>
|
||||||
|
<Input type="file" id="avatar" ref={fileInputRef} accept="image/jpeg,image/png,image/gif" className="hidden" onChange={handleFileChange}/>
|
||||||
|
<Button variant="outline" size="sm" onClick={() => fileInputRef.current?.click()}>Browse</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
JPG, GIF or PNG. 1MB max.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-between p-3 space-x-2">
|
||||||
|
<div className="truncate max-w-[200px]">
|
||||||
|
<p className="text-sm font-medium truncate">{selectedFile.name}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">{(selectedFile.size / 1024).toFixed(2)} KB</p>
|
||||||
|
</div>
|
||||||
|
<Button size="sm" className="text-xs p-1 h-fit">Update</Button>
|
||||||
|
<Button size="sm" onClick={handleRemoveFile} className="bg-red-500 hover:bg-red-600 text-xs p-1 h-fit">Remove</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default AvatarUpload;
|
|
@ -7,6 +7,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from ".
|
||||||
import { Separator } from "./ui/separator";
|
import { Separator } from "./ui/separator";
|
||||||
import { Textarea } from "./ui/textarea";
|
import { Textarea } from "./ui/textarea";
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import UpdateAvatar from './UpdateAvatar';
|
||||||
|
|
||||||
interface SessionData {
|
interface SessionData {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
@ -21,6 +22,7 @@ interface UserData {
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
const [userData, setUserData] = useState<UserData | null>(null);
|
const [userData, setUserData] = useState<UserData | null>(null);
|
||||||
|
const [invoiceList, setInvoiceList] = useState<any[]>([]);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -45,8 +47,36 @@ export default function ProfilePage() {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const getInvoiceListData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:2058/host-api/v1/invoice/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();
|
fetchSessionData();
|
||||||
|
getInvoiceListData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -58,12 +88,10 @@ export default function ProfilePage() {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 container mx-auto">
|
<div className="space-y-6 container mx-auto">
|
||||||
<div>
|
{/* <div>
|
||||||
<h3 className="text-lg font-medium">Profile</h3>
|
<h3 className="text-lg font-medium">Profile</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">Update your profile settings.</p>
|
||||||
Update your profile settings.
|
</div> */}
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
|
<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-2xl">
|
<div className="flex-1 lg:max-w-2xl">
|
||||||
|
@ -80,14 +108,8 @@ export default function ProfilePage() {
|
||||||
<AvatarImage src={userData.session_data?.user_avatar} />
|
<AvatarImage src={userData.session_data?.user_avatar} />
|
||||||
<AvatarFallback>JP</AvatarFallback>
|
<AvatarFallback>JP</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="space-y-1">
|
<UpdateAvatar />
|
||||||
<Button size="sm">
|
|
||||||
Change avatar
|
|
||||||
</Button>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
JPG, GIF or PNG. 1MB max.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
|
@ -106,7 +128,7 @@ export default function ProfilePage() {
|
||||||
<Input id="email" type="email" defaultValue={userData.session_data?.user_email || ''} />
|
<Input id="email" type="email" defaultValue={userData.session_data?.user_email || ''} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
{/* <div className="space-y-2">
|
||||||
<Label htmlFor="bio">Bio</Label>
|
<Label htmlFor="bio">Bio</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="bio"
|
id="bio"
|
||||||
|
@ -114,11 +136,58 @@ export default function ProfilePage() {
|
||||||
className="resize-none"
|
className="resize-none"
|
||||||
rows={5}
|
rows={5}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="mt-6">
|
<Card className="mt-6">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Billing Information</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
View your billing history.
|
||||||
|
</CardDescription>
|
||||||
|
{/* <CardContent> */}
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="text-left">Invoice ID</th>
|
||||||
|
<th className="text-left">Date</th>
|
||||||
|
<th className="text-left">Description</th>
|
||||||
|
<th className="text-right">Amount</th>
|
||||||
|
<th className="text-center">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{/* {
|
||||||
|
invoiceList.map((invoice) => (
|
||||||
|
<tr key={invoice.id}>
|
||||||
|
<td>{invoice.invoice_id}</td>
|
||||||
|
<td>{invoice.date}</td>
|
||||||
|
<td>{invoice.description}</td>
|
||||||
|
<td className="text-right">{invoice.amount}</td>
|
||||||
|
<td className="text-center"><a href="">Print</a></td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
} */}
|
||||||
|
<tr>
|
||||||
|
<td>dcdicdicib</td>
|
||||||
|
<td>2023-10-01</td>
|
||||||
|
<td>Subscription Fee</td>
|
||||||
|
<td className="text-right">$10.00</td>
|
||||||
|
<td className="text-center"><a href="">Print</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>dcibcdici</td>
|
||||||
|
<td>2023-09-01</td>
|
||||||
|
<td>Subscription Fee</td>
|
||||||
|
<td className="text-right">$10.00</td>
|
||||||
|
<td className="text-center"><a href="" >Print</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{/* </CardContent> */}
|
||||||
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
{/* <Card className="mt-6">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Preferences</CardTitle>
|
<CardTitle>Preferences</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
|
@ -156,7 +225,7 @@ export default function ProfilePage() {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 space-y-6 lg:max-w-md">
|
<div className="flex-1 space-y-6 lg:max-w-md">
|
||||||
|
@ -183,8 +252,7 @@ export default function ProfilePage() {
|
||||||
<Button className="mt-4">Update password</Button>
|
<Button className="mt-4">Update password</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card className="mt-6">
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Danger Zone</CardTitle>
|
<CardTitle>Danger Zone</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
|
@ -193,7 +261,7 @@ export default function ProfilePage() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
<Button >Delete account</Button>
|
<Button className="bg-red-500 hover:bg-red-600">Delete account</Button>
|
||||||
<Button variant="outline">Export data</Button>
|
<Button variant="outline">Export data</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
Loading…
Reference in New Issue