diff --git a/package-lock.json b/package-lock.json index 89c5767..3ec0673 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,8 @@ "simplemde": "^1.11.2", "tailwind-merge": "^3.0.2", "tailwindcss": "^3.4.17", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "xlsx": "^0.18.5" } }, "node_modules/@alloc/quick-lru": { @@ -3977,6 +3978,15 @@ "node": ">=0.4.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -4685,6 +4695,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", @@ -4862,6 +4885,15 @@ "typo-js": "*" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -4950,6 +4982,18 @@ "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", "license": "MIT" }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5673,6 +5717,15 @@ "node": ">=12.20.0" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -14730,6 +14783,18 @@ "node": ">=6" } }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/stdin-discarder": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", @@ -16017,6 +16082,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/wrap-ansi": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", @@ -16108,6 +16191,27 @@ "node": ">=8" } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/xml2js": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", diff --git a/package.json b/package.json index 7326088..cd460b1 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "simplemde": "^1.11.2", "tailwind-merge": "^3.0.2", "tailwindcss": "^3.4.17", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "xlsx": "^0.18.5" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/public/assets/paypal-logo-white.svg b/public/assets/paypal-logo-white.svg new file mode 100644 index 0000000..9dbd47d --- /dev/null +++ b/public/assets/paypal-logo-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/payu-logo.svg b/public/assets/payu-logo.svg new file mode 100644 index 0000000..3e6e3af --- /dev/null +++ b/public/assets/payu-logo.svg @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/AvatarUpload.tsx b/src/components/AvatarUpload.tsx new file mode 100644 index 0000000..da996fa --- /dev/null +++ b/src/components/AvatarUpload.tsx @@ -0,0 +1,127 @@ +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 PocketBase from 'pocketbase'; + +const pb = new PocketBase('https://tst-pb.s38.siliconpin.com'); +const INVOICE_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/users/'; + +export function AvatarUpload({ userId }: { userId: string }) { + const [selectedFile, setSelectedFile] = useState(null); + const [isUploading, setIsUploading] = useState(false); + const fileInputRef = useRef(null); + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files.length > 0) { + setSelectedFile(e.target.files[0]); + } + }; + + const handleRemoveFile = () => { + setSelectedFile(null); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + + const handleUpload = async () => { + if (!selectedFile || !userId) return; + + setIsUploading(true); + + try { + // 1. Upload to PocketBase + const formData = new FormData(); + formData.append('avatar', selectedFile); + + // Update PocketBase user record + const pbRecord = await pb.collection('users').update(userId, formData); + + // Get the avatar URL from PocketBase + const avatarUrl = pb.getFileUrl(pbRecord, pbRecord.avatar, { + thumb: '100x100' + }); + + // 2. Update PHP backend session + const response = await fetch(`${INVOICE_API_URL}?query=login`, { + method: 'POST', + credentials: 'include', // Important for sessions to work + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + avatar: avatarUrl, + query: 'avatar_update' + }) + }); + + if (!response.ok) { + throw new Error('Failed to update session'); + } + + // Success - you might want to refresh the user data or show a success message + window.location.reload(); + + } catch (error) { + console.error('Error uploading avatar:', error); + alert('Failed to update avatar'); + } finally { + setIsUploading(false); + } + }; + + return ( +
+ {!selectedFile ? ( +
+
+ + + +
+

+ JPG, GIF or PNG. 1MB max. +

+
+ ) : ( +
+
+

{selectedFile.name}

+

{(selectedFile.size / 1024).toFixed(2)} KB

+
+ + +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/BillingInfo.jsx b/src/components/BillingInfo.jsx new file mode 100644 index 0000000..c4c5262 --- /dev/null +++ b/src/components/BillingInfo.jsx @@ -0,0 +1,591 @@ +import React, { useEffect, useState, useMemo } from "react"; +import { useIsLoggedIn } from '../lib/isLoggedIn'; +import Loader from "./ui/loader"; +import { PDFDownloadLink } from '@react-pdf/renderer'; +import InvoicePDF from "../lib/InvoicePDF"; +import { Eye, Download, ChevronUp, ChevronDown, Search } from "lucide-react"; +import { Button } from "./ui/button"; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "./ui/dialog"; + +export default function UserBillingList() { + const { isLoggedIn, loading, error, sessionData } = useIsLoggedIn(); + const [billingData, setBillingData] = useState([]); + const [dataLoading, setDataLoading] = useState(true); + const [apiError, setApiError] = useState(null); + const [selectedItem, setSelectedItem] = useState(null); + const [dialogOpen, setDialogOpen] = useState(false); + + // Sorting state + const [sortConfig, setSortConfig] = useState({ key: 'created_at', direction: 'desc' }); + + // Filtering state + const [filters, setFilters] = useState({ + status: '', + cycle: '', + service: '' + }); + + // Search state + const [searchTerm, setSearchTerm] = useState(''); + + // Pagination state + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage] = useState(10); + + const INVOICE_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/users/'; + + useEffect(() => { + if (isLoggedIn) { + fetchBillingData(); + } + }, [isLoggedIn]); + + const fetchBillingData = async () => { + try { + const res = await fetch(`${INVOICE_API_URL}?query=invoice-info`, { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json' + } + }); + + if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); + const data = await res.json(); + + // For regular users, filter to only show their own billing data + const filteredData = sessionData?.user_type === 'admin' + ? data.data || [] + : (data.data || []).filter(item => item.user === sessionData?.email); + + setBillingData(filteredData); + } catch (err) { + setApiError(err.message); + } finally { + setDataLoading(false); + } + }; + + const handleViewItem = (item) => { + setSelectedItem(item); + setDialogOpen(true); + }; + + const closeModal = () => { + setDialogOpen(false); + setSelectedItem(null); + }; + + // Sorting functionality + const requestSort = (key) => { + let direction = 'asc'; + if (sortConfig.key === key && sortConfig.direction === 'asc') { + direction = 'desc'; + } + setSortConfig({ key, direction }); + setCurrentPage(1); + }; + + // Filter and sort data + const filteredAndSortedData = useMemo(() => { + let filteredData = [...billingData]; + + // Apply search + if (searchTerm) { + filteredData = filteredData.filter(item => + Object.values(item).some( + val => val && val.toString().toLowerCase().includes(searchTerm.toLowerCase()) + ) + ); + } + + // Apply filters + if (filters.status) { + filteredData = filteredData.filter(item => item.status === filters.status); + } + if (filters.cycle) { + filteredData = filteredData.filter(item => item.cycle === filters.cycle); + } + if (filters.service) { + filteredData = filteredData.filter(item => item.service === filters.service); + } + + // Apply sorting + if (sortConfig.key) { + filteredData.sort((a, b) => { + if (a[sortConfig.key] < b[sortConfig.key]) { + return sortConfig.direction === 'asc' ? -1 : 1; + } + if (a[sortConfig.key] > b[sortConfig.key]) { + return sortConfig.direction === 'asc' ? 1 : -1; + } + return 0; + }); + } + + return filteredData; + }, [billingData, searchTerm, filters, sortConfig]); + + const currentItems = useMemo(() => { + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + return filteredAndSortedData.slice(indexOfFirstItem, indexOfLastItem); + }, [currentPage, itemsPerPage, filteredAndSortedData]); + + const totalPages = Math.ceil(filteredAndSortedData.length / itemsPerPage); + + const paginate = (pageNumber) => { + if (pageNumber > 0 && pageNumber <= totalPages) { + setCurrentPage(pageNumber); + } + }; + + const getPaginationRange = () => { + const range = []; + const maxVisiblePages = 5; + range.push(1); + if (currentPage > 3) { + range.push('...'); + } + let start = Math.max(2, currentPage - 1); + let end = Math.min(totalPages - 1, currentPage + 1); + if (currentPage <= 3) { + end = Math.min(4, totalPages - 1); + } else if (currentPage >= totalPages - 2) { + start = Math.max(totalPages - 3, 2); + } + for (let i = start; i <= end; i++) { + if (i > 1 && i < totalPages) { + range.push(i); + } + } + if (currentPage < totalPages - 2) { + range.push('...'); + } + if (totalPages > 1) { + range.push(totalPages); + } + return range; + }; + + // Get unique values for filter dropdowns + const uniqueServices = [...new Set(billingData.map(item => item.service))]; + const uniqueStatuses = ['completed', 'pending', 'failed']; + const uniquecycles = ['monthly', 'yearly', 'one-time']; + + // Reset filters + const resetFilters = () => { + setFilters({ + status: '', + cycle: '', + service: '' + }); + setSearchTerm(''); + setCurrentPage(1); + }; + + // Handle filter change + const handleFilterChange = (e) => { + const { name, value } = e.target; + setFilters(prev => ({ + ...prev, + [name]: value + })); + setCurrentPage(1); + }; + + // Handle search + const handleSearch = (e) => { + setSearchTerm(e.target.value); + setCurrentPage(1); + }; + + const getStatusColor = (status) => { + switch (status) { + case 'completed': return 'bg-green-100 text-green-800'; + case 'pending': return 'bg-yellow-100 text-yellow-800'; + case 'failed': return 'bg-red-100 text-red-800'; + default: return 'bg-gray-100 text-gray-800'; + } + }; + + const formatDate = (dateString) => { + if (!dateString) return 'N/A'; + const options = { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }; + return new Date(dateString).toLocaleDateString(undefined, options); + }; + + const formatCurrency = (amount) => { + if (!amount) return '$0.00'; + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(parseFloat(amount)); + }; + + if (loading || dataLoading) return ; + if (error || apiError) return

Error: {error?.message || apiError}

; + if (!isLoggedIn) { + return

You need to be logged in to view this page. Click Here to go to the homepage.

; + } + + return ( +
+
+

My Billing History

+
+ + {/* Results Count */} +
+ Showing {currentItems.length} of {filteredAndSortedData.length} results +
+
+
+ {/* Search */} +
+
+ +
+ +
+ + {/* Status Filter */} + + + {/* cycle Filter */} + + + {/* Service Filter */} + +
+ + {/* Reset Filters Button */} + {(filters.status || filters.cycle || filters.service || searchTerm) && ( +
+ +
+ )} +
+
+
+ + + + + + + + + + + + + + {currentItems.length > 0 ? ( + currentItems.map((item) => ( + + + + + + + + + + )) + ) : ( + + + + )} + +
requestSort('billing_id')} + > +
+ Billing ID + {sortConfig.key === 'billing_id' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('service')} + > +
+ Service + {sortConfig.key === 'service' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('name')} + > +
+ Name + {sortConfig.key === 'name' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('amount')} + > +
+ Amount + {sortConfig.key === 'amount' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('status')} + > +
+ Status + {sortConfig.key === 'status' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('created_at')} + > +
+ Created At + {sortConfig.key === 'created_at' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
+ Actions +
+ {item.billing_id} + + {item.service} + + {item.name??item.name} + + {formatCurrency(item.amount)} + + + {item.status.charAt(0).toUpperCase() + item.status.slice(1)} + + + {formatDate(item.created_at)} + + + } + fileName={`invoice_${item.billing_id}.pdf`} + className="text-[#6d9e37] hover:text-green-600" + > + {({ loading }) => (loading ? '...' : )} + +
+ No records found +
+
+
+ + {/* Pagination */} + {filteredAndSortedData.length > itemsPerPage && ( +
+
+ Page {currentPage} of {totalPages} +
+
+ + + + {getPaginationRange().map((item, index) => { + if (item === '...') { + return ( + + ... + + ); + } + return ( + + ); + })} + + + +
+
+ )} + + {/* Dialog for View */} + + + + + Billing Details + + + Detailed information about your billing record + + + +
+
+

Billing ID

+

{selectedItem?.billing_id}

+
+
+

Service

+

{selectedItem?.service}

+
+
+

User

+

{selectedItem?.user}

+
+
+

cycle

+

{selectedItem?.cycle}

+
+
+

Amount

+

{formatCurrency(selectedItem?.amount)}

+
+
+

Status

+ + {selectedItem?.status?.charAt(0).toUpperCase() + selectedItem?.status?.slice(1)} + +
+
+

Created At

+

{formatDate(selectedItem?.created_at)}

+
+
+

Updated At

+

{formatDate(selectedItem?.updated_at)}

+
+
+ + + } + fileName={`invoice_${selectedItem?.billing_id}.pdf`} + className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700" + > + {({ loading }) => (loading ? 'Preparing PDF...' : 'Download PDF')} + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/DomainSetupForm.jsx b/src/components/DomainSetupForm.jsx index a59ea28..ac189bb 100644 --- a/src/components/DomainSetupForm.jsx +++ b/src/components/DomainSetupForm.jsx @@ -34,22 +34,23 @@ export const DomainSetupForm = ({ defaultSubdomain }) => { const [userEmail, setUserEmail] = useState(''); const [panelType, setPanelType] = useState(''); + const [panelServicesId, setPanelServicesId] = useState(''); const API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/users/'; const SERVICES_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/services/'; // const BILLING_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/users/index.php'; - const [selectedTenure, setSelectedTenure] = useState(''); + const [selectedcycle, setSelectedcycle] = useState(''); const [selectedPrice, setSelectedPrice] = useState(0); - const handleCheckboxChange = (tenure, price) => { - if (selectedTenure === tenure) { - setSelectedTenure(''); + const handleCheckboxChange = (cycle, price) => { + if (selectedcycle === cycle) { + setSelectedcycle(''); setSelectedPrice(0); } else { - setSelectedTenure(tenure); + setSelectedcycle(cycle); setSelectedPrice(price); } - // console.log(selectedTenure, ' ', selectedPrice); + // console.log(selectedcycle, ' ', selectedPrice); }; const handlePanelBuyNow = () => { // Disable button during processing @@ -59,9 +60,9 @@ export const DomainSetupForm = ({ defaultSubdomain }) => { showToast('Loading...'); const formData = new FormData(); formData.append('service', panelType); - formData.append('tenure', selectedTenure); + formData.append('serviceId', panelServicesId); + formData.append('cycle', selectedcycle); formData.append('amount', 1); //selectedPrice - fetch(`${API_URL}?query=initiate_payment`, { method: 'POST', body: formData, @@ -551,13 +552,16 @@ export const DomainSetupForm = ({ defaultSubdomain }) => { ) @@ -575,21 +579,21 @@ export const DomainSetupForm = ({ defaultSubdomain }) => { ))}
-
- {selectedTenure && ( -

You selected {selectedTenure} plan at ₹{selectedPrice}

+ {selectedcycle && ( +

You selected {selectedcycle} plan at ₹{selectedPrice}

)}
diff --git a/src/components/HireDeveloper.tsx b/src/components/HireDeveloper.tsx index 4ba5ef2..1853930 100644 --- a/src/components/HireDeveloper.tsx +++ b/src/components/HireDeveloper.tsx @@ -186,12 +186,7 @@ export default function HireHumanDeveloper() {

Specialized Skills:

{selectedType.skills.map((skill, index) => ( - - {skill} - + {skill} ))}
diff --git a/src/components/MakePayment.jsx b/src/components/MakePayment.jsx index 7e05965..2411fc1 100644 --- a/src/components/MakePayment.jsx +++ b/src/components/MakePayment.jsx @@ -1,17 +1,25 @@ 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 { Toast } from './Toast'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "./ui/card"; - -const API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/users/index.php'; +import { FileX, Copy, CheckCircle2, AlertCircle, X } from "lucide-react"; +import Loader from "./ui/loader"; +const API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/users/'; export default function MakePayment(){ const [initialOrderData, setInitialOrderData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [showQRModal, setShowQRModal] = useState(false); + const [showHelpMessage, setShowHelpMessage] = useState(false); + const [transactionId, setTransactionId] = useState(''); + const [orderIdInput, setOrderIdInput] = useState(''); const [upiPaymentLink, setUpiPaymentLink] = useState(""); + const [copied, setCopied] = useState(false); + const [getOrderId, setGetOrderId] = useState(); + const [saveUpiResponse, setSaveUpiResponse] = useState({ message: '', visible: false, isSuccess: false }); useEffect(() => { const urlParams = new URLSearchParams(window.location.search); @@ -22,7 +30,7 @@ export default function MakePayment(){ setIsLoading(false); return; } - + setGetOrderId(orderId); const getInitialOrderData = () => { setIsLoading(true); const formData = new FormData(); @@ -67,7 +75,7 @@ export default function MakePayment(){ function generateUPILink(amount, transactionId) { // Replace these with your actual merchant details - const merchantUPI = "siliconpin@ybl"; + const merchantUPI = "7001601485@okbizaxis"; const merchantName = "SiliconPin"; const currency = "INR"; @@ -78,8 +86,8 @@ export default function MakePayment(){ // Construct UPI payment link return `upi://pay?pa=${merchantUPI}&pn=${encodedMerchantName}&am=${amount}&cu=${currency}&tn=${transactionNote}`; } -// waiting payment update -// Payment update not recieved if + // waiting payment update + // Payment update not recieved if function redirectToPayU() { if (!initialOrderData?.payment_data || !initialOrderData.payment_url) { console.error('Payment data not loaded yet'); @@ -106,6 +114,17 @@ export default function MakePayment(){ form.submit(); } + useEffect(() => { + let timer; + if (showQRModal) { + setShowHelpMessage(false); + timer = setTimeout(() => { + setShowHelpMessage(true); + }, 2000); + } + return () => clearTimeout(timer); // Cleanup on unmount or when modal closes + }, [showQRModal]); + function handleQRPaymentClick() { if (!upiPaymentLink) { alert('Payment information is not ready. Please wait.'); @@ -114,10 +133,71 @@ export default function MakePayment(){ setShowQRModal(true); } + const handleCopy = () => { + navigator.clipboard.writeText(getOrderId) + .then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 1000); // Reset after 1 second + }) + .catch(err => { + console.error('Failed to copy text: ', err); + }); + }; + const handlePayPalPayment = () => { + const rawInrAmount = initialOrderData.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 handleSaveUPIPayment = async () => { + try { + const response = await fetch(`${API_URL}?query=save-upi-payment`, { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + orderId: orderIdInput, + transactionId: transactionId + }) + }); + + const data = await response.json(); + + if (data.success === true) { + setSaveUpiResponse({ + message: `Transaction ID ${transactionId} saved. We'll contact you shortly.`, + visible: true, + isSuccess: true + }); + setShowHelpMessage(false); + } + } catch (error) { + console.error('Error:', error); + setSaveUpiResponse({ + message: 'Failed to save payment details. Please try again.', + visible: true, + isSuccess: false + }); + } + }; + + if (isLoading) { - return
-
-
; + return ; + } + + if(!initialOrderData){ + return ( +
+ +

No bill was found. Click here to go to your profile.

+
+ ); } if (error) { @@ -148,33 +228,95 @@ export default function MakePayment(){
Pay Using: - - OR - - Applicable 2% Transaction Charge if using Payment Gateway +
+ + + Banking Service by DWD Consultancy Services (0% Processing Fee) + +
+
+ OR +
+
+ + + + {/* 2% Payment Gateway Charge */} + + {/* Add this divider and PayPal button */} + {/*
+
+ OR +
+
*/} + + + {/* 3.5% Payment Processing Fee */}
{/* QR Code Modal */} - + {setShowQRModal(open); if (!open) {setShowHelpMessage(false);}}}> Scan QR Code to Pay
- +

Scan this QR code with any UPI app to complete your payment -

-
- {upiPaymentLink} -
- +

+ + + {showHelpMessage && ( +
+

+ If you haven't received payment confirmation, please share your Order Id & transaction ID with us. +

+ setOrderIdInput(e.target.value)} placeholder="Enter your Order ID" className="w-full p-2 border border-gray-300 rounded-md text-sm mb-2" /> + setTransactionId(e.target.value)} placeholder="Enter your transaction ID" className="w-full p-2 border border-gray-300 rounded-md text-sm mb-2" /> + +
+ )} + {saveUpiResponse.visible && ( +
+
+ + +
+
+ {saveUpiResponse.isSuccess ? ( + + ) : ( + + )} +

{saveUpiResponse.message}

+
+ +
+
+
+ )}
); -} \ No newline at end of file +} +// Transaction ID ${transactionId} saved. We'll contact you shortly. \ No newline at end of file diff --git a/src/components/Manager/AllSellingList.jsx b/src/components/Manager/AllSellingList.jsx index 4f3c72f..3ffde5b 100644 --- a/src/components/Manager/AllSellingList.jsx +++ b/src/components/Manager/AllSellingList.jsx @@ -1,34 +1,63 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useMemo } from "react"; import { useIsLoggedIn } from '../../lib/isLoggedIn'; import Loader from "../../components/ui/loader"; -import { PDFDownloadLink, PDFViewer } from '@react-pdf/renderer'; -import InvoicePDF from "../../lib/InvoicePDF"; // We'll create this component next +import { PDFDownloadLink } from '@react-pdf/renderer'; +import InvoicePDF from "../../lib/InvoicePDF"; +import { Eye, Pencil, Trash2, Download, ChevronUp, ChevronDown, Search, ArrowLeft, FileText } from "lucide-react"; +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../ui/dialog"; +import * as XLSX from 'xlsx'; export default function AllSellingList() { const { isLoggedIn, loading, error, sessionData } = useIsLoggedIn(); const [billingData, setBillingData] = useState([]); + const [usersData, setUsersData] = useState([]); const [dataLoading, setDataLoading] = useState(true); const [apiError, setApiError] = useState(null); const [selectedItem, setSelectedItem] = useState(null); - const [showModal, setShowModal] = useState(false); + const [dialogOpen, setDialogOpen] = useState(false); const [editMode, setEditMode] = useState(false); - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState({ service: '', - tenure: 'monthly', - amount: '', - user: '', - status: 'pending' + serviceId: 'service-1', + cycle: 'monthly', + amount: '', + user: '', + siliconId: '', + name: '', + status: 'pending', + remarks: '' }); + const [currentStep, setCurrentStep] = useState(1); + const [selectedCustomer, setSelectedCustomer] = useState(null); + const [customerSearchTerm, setCustomerSearchTerm] = useState(''); + + // Sorting state + const [sortConfig, setSortConfig] = useState({ key: 'created_at', direction: 'desc' }); + + // Filtering state + const [filters, setFilters] = useState({ status: '', cycle: '', service: '' }); + + // Search state + const [searchTerm, setSearchTerm] = useState(''); + + // Pagination state + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage] = useState(10); + const INVOICE_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/users/'; useEffect(() => { if (isLoggedIn && sessionData?.user_type === 'admin') { fetchBillingData(); + fetchUsersData(); } }, [isLoggedIn, sessionData]); const fetchBillingData = async () => { try { + setDataLoading(true); const res = await fetch(`${INVOICE_API_URL}?query=all-selling-list`, { method: 'GET', credentials: 'include', @@ -47,23 +76,55 @@ export default function AllSellingList() { } }; + const fetchUsersData = async () => { + try { + const res = await fetch(`${INVOICE_API_URL}?query=get-all-users`, { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json' + } + }); + + if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); + const data = await res.json(); + setUsersData(data.data || []); + } catch (err) { + setApiError(err.message); + } + }; + + // Filter customers based on search term + const filteredCustomers = useMemo(() => { + return usersData.filter(user => + user.name.toLowerCase().includes(customerSearchTerm.toLowerCase()) || + user.email.toLowerCase().includes(customerSearchTerm.toLowerCase()) || + user.siliconId.toLowerCase().includes(customerSearchTerm.toLowerCase()) + ); + }, [usersData, customerSearchTerm]); + const handleViewItem = (item) => { setSelectedItem(item); - setShowModal(true); setEditMode(false); + setDialogOpen(true); }; const handleEditItem = (item) => { setSelectedItem(item); setFormData({ service: item.service, - tenure: item.tenure, + serviceId: item.serviceId, + cycle: item.cycle, amount: item.amount, user: item.user, - status: item.status + siliconId: item.siliconId, + name: item.name, + status: item.status, + remarks: item.remarks, + billing_date: item.billing_date }); setEditMode(true); - setShowModal(true); + setDialogOpen(true); }; const handleDeleteItem = async (id) => { @@ -87,15 +148,12 @@ export default function AllSellingList() { const handleCreateNew = () => { setSelectedItem(null); - setFormData({ - service: '', - tenure: 'monthly', - amount: '', - user: '', - status: 'pending' - }); + setFormData({ service: '', serviceId: 'service-2', cycle: 'monthly', amount: '', user: '', siliconId: '', name: '', status: 'pending', remarks: '', billing_date: new Date().toISOString().split('T')[0] }); setEditMode(true); - setShowModal(true); + setCurrentStep(1); + setSelectedCustomer(null); + setCustomerSearchTerm(''); + setDialogOpen(true); }; const handleSubmit = async (e) => { @@ -118,7 +176,8 @@ export default function AllSellingList() { if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); fetchBillingData(); - setShowModal(false); + setDialogOpen(false); + setCurrentStep(1); } catch (err) { setApiError(err.message); } @@ -132,10 +191,157 @@ export default function AllSellingList() { })); }; - const closeModal = () => { - setShowModal(false); - setSelectedItem(null); - setEditMode(false); + const handleCustomerSelect = (customer) => { + setSelectedCustomer(customer); + setFormData(prev => ({ + ...prev, + user: customer.email, + siliconId: customer.siliconId, + name: customer.name + })); + setCurrentStep(2); + }; + + const handleBackToCustomerSelect = () => { + setCurrentStep(1); + setSelectedCustomer(null); + }; + + // Export to Excel function + const exportToExcel = () => { + const worksheet = XLSX.utils.json_to_sheet(filteredAndSortedData.map(item => ({ + 'Billing ID': item.billing_id, + 'Service': item.service, + 'Customer Name': item.name, + 'Customer Email': item.user, + 'Silicon ID': item.siliconId, + 'Amount': item.amount, + 'cycle': item.cycle, + 'Status': item.status, + 'Created At': formatDate(item.created_at), + 'Remarks': item.remarks + }))); + + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, "Billing Data"); + XLSX.writeFile(workbook, `billing_data_${new Date().toISOString().slice(0, 10)}.xlsx`); + }; + + // Sorting functionality + const requestSort = (key) => { + let direction = 'asc'; + if (sortConfig.key === key && sortConfig.direction === 'asc') { + direction = 'desc'; + } + setSortConfig({ key, direction }); + setCurrentPage(1); + }; + + // Filter and sort data + const filteredAndSortedData = useMemo(() => { + let filteredData = [...billingData]; + + if (searchTerm) { + filteredData = filteredData.filter(item => + Object.values(item).some( + val => val && val.toString().toLowerCase().includes(searchTerm.toLowerCase()) + ) + ); + } + + if (filters.status) { + filteredData = filteredData.filter(item => item.status === filters.status); + } + if (filters.cycle) { + filteredData = filteredData.filter(item => item.cycle === filters.cycle); + } + if (filters.service) { + filteredData = filteredData.filter(item => item.service === filters.service); + } + + if (sortConfig.key) { + filteredData.sort((a, b) => { + if (a[sortConfig.key] < b[sortConfig.key]) { + return sortConfig.direction === 'asc' ? -1 : 1; + } + if (a[sortConfig.key] > b[sortConfig.key]) { + return sortConfig.direction === 'asc' ? 1 : -1; + } + return 0; + }); + } + + return filteredData; + }, [billingData, searchTerm, filters, sortConfig]); + + const currentItems = useMemo(() => { + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + return filteredAndSortedData.slice(indexOfFirstItem, indexOfLastItem); + }, [currentPage, itemsPerPage, filteredAndSortedData]); + + const totalPages = Math.ceil(filteredAndSortedData.length / itemsPerPage); + + const paginate = (pageNumber) => { + if (pageNumber > 0 && pageNumber <= totalPages) { + setCurrentPage(pageNumber); + } + }; + + const getPaginationRange = () => { + const range = []; + const maxVisiblePages = 5; + range.push(1); + if (currentPage > 3) { + range.push('...'); + } + let start = Math.max(2, currentPage - 1); + let end = Math.min(totalPages - 1, currentPage + 1); + if (currentPage <= 3) { + end = Math.min(4, totalPages - 1); + } else if (currentPage >= totalPages - 2) { + start = Math.max(totalPages - 3, 2); + } + for (let i = start; i <= end; i++) { + if (i > 1 && i < totalPages) { + range.push(i); + } + } + if (currentPage < totalPages - 2) { + range.push('...'); + } + if (totalPages > 1) { + range.push(totalPages); + } + return range; + }; + + const uniqueServices = [...new Set(billingData.map(item => item.service))]; + const uniqueStatuses = ['completed', 'pending', 'failed']; + const uniquecycles = ['monthly', 'yearly', 'one-time']; + + const resetFilters = () => { + setFilters({ + status: '', + cycle: '', + service: '' + }); + setSearchTerm(''); + setCurrentPage(1); + }; + + const handleFilterChange = (e) => { + const { name, value } = e.target; + setFilters(prev => ({ + ...prev, + [name]: value + })); + setCurrentPage(1); + }; + + const handleSearch = (e) => { + setSearchTerm(e.target.value); + setCurrentPage(1); }; const getStatusColor = (status) => { @@ -171,252 +377,573 @@ export default function AllSellingList() {

Billing Management

- +
+ + +
-
+ {/* Search and Filter Section */} +
+ Showing {currentItems.length} of {filteredAndSortedData.length} results +
+
+
+
+
+ +
+ +
+ + + + + + +
+ + {(filters.status || filters.cycle || filters.service || searchTerm) && ( +
+ +
+ )} +
+
- - - - - - - + + + + + + + + - {billingData.map((item) => ( - - - - - - - - + + + + + + + + + + )) + ) : ( + + - ))} + )}
Billing IDServiceUserAmountStatusCreated AtActions requestSort('billing_id')} + > +
+ Billing ID + {sortConfig.key === 'billing_id' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('service')} + > +
+ Service + {sortConfig.key === 'service' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('name')} + > +
+ Name + {sortConfig.key === 'name' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('user')} + > +
+ User + {sortConfig.key === 'user' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('amount')} + > +
+ Amount + {sortConfig.key === 'amount' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('status')} + > +
+ Status + {sortConfig.key === 'status' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
requestSort('created_at')} + > +
+ Created At + {sortConfig.key === 'created_at' && ( + sortConfig.direction === 'asc' ? + : + + )} +
+
+ Actions +
- {item.billing_id} - - {item.service} - - {item.user} - - {formatCurrency(item.amount)} - - - {item.status.charAt(0).toUpperCase() + item.status.slice(1)} - - - {formatDate(item.created_at)} - - - - - } - fileName={`invoice_${item.billing_id}.pdf`} - className="text-green-600 hover:text-green-900" - > - {({ loading }) => (loading ? 'Preparing...' : 'PDF')} - + {currentItems.length > 0 ? ( + currentItems.map((item) => ( +
+ {item.billing_id} + + {item.service} + + {item.name ?? item.name} + + {item.user} + + {formatCurrency(item.amount)} + + + {item.status.charAt(0).toUpperCase() + item.status.slice(1)} + + + {formatDate(item.created_at)} + + + + + } + fileName={`invoice_${item.billing_id}.pdf`} + className="text-[#6d9e37] hover:text-green-600" + > + {({ loading }) => (loading ? '...' : )} + +
+ No records found
- {/* Modal for View/Edit */} - {showModal && ( -
-
-
-
-

- {editMode ? (selectedItem ? 'Edit Billing' : 'Create New Billing') : 'Billing Details'} -

- + + + {getPaginationRange().map((item, index) => { + if (item === '...') { + return ( + + ... + + ); + } + return ( + -
- - {editMode ? ( -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
+ {item} + + ); + })} -
- - -
-
- ) : ( - <> -
-
-

Billing ID

-

{selectedItem.billing_id}

-
-
-

Service

-

{selectedItem.service}

-
-
-

User

-

{selectedItem.user}

-
-
-

Tenure

-

{selectedItem.tenure}

-
-
-

Amount

-

{formatCurrency(selectedItem.amount)}

-
-
-

Status

- - {selectedItem.status.charAt(0).toUpperCase() + selectedItem.status.slice(1)} - -
-
-

Created At

-

{formatDate(selectedItem.created_at)}

-
-
-

Updated At

-

{formatDate(selectedItem.updated_at)}

-
-
- -
- } - fileName={`invoice_${selectedItem.billing_id}.pdf`} - className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700" - > - {({ loading }) => (loading ? 'Preparing PDF...' : 'Download PDF')} - - - -
- - )} -
+ +
)} + + {/* Dialog for View/Edit/Create */} + { + if (!open) { + setCurrentStep(1); + setSelectedCustomer(null); + } + setDialogOpen(open); + }}> + + + + {editMode ? (selectedItem ? 'Edit Billing' : 'Create New Billing') : 'Billing Details'} + + + + {editMode && !selectedItem && currentStep === 1 ? ( +
+
+
+ +
+ setCustomerSearchTerm(e.target.value)} + className="pl-10 w-full px-3 py-2 border border-gray-300 rounded-md outline-none" + /> +
+ +

Select Customer

+ {filteredCustomers.length > 0 ? ( +
+ {filteredCustomers.map(user => ( +
handleCustomerSelect(user)} + className="p-3 border rounded-md cursor-pointer hover:bg-gray-50" + > +
+
+

{user.name}

+

{user.email}

+
+
+ ID: {user.siliconId} +
+
+
+ ))} +
+ ) : ( +

No customers found

+ )} +
+ ) : editMode ? ( +
+ {!selectedItem && selectedCustomer && ( +
+
+
+

Customer Information

+

Name: {selectedCustomer.name}

+

Email: {selectedCustomer.email}

+

Silicon ID: {selectedCustomer.siliconId}

+
+ {!selectedItem && ( + + )} +
+
+ )} + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + +
+ ) : ( + <> +
+
+

Billing ID

+

{selectedItem?.billing_id}

+
+
+

Service

+

{selectedItem?.service}

+
+
+

User

+

{selectedItem?.user}

+
+
+

Silicon ID

+

{selectedItem?.siliconId}

+
+
+

cycle

+

{selectedItem?.cycle}

+
+
+

Amount

+

{formatCurrency(selectedItem?.amount)}

+
+
+

Status

+ + {selectedItem?.status.charAt(0).toUpperCase() + selectedItem?.status.slice(1)} + +
+
+

Created At

+

{formatDate(selectedItem?.created_at)}

+
+
+

Updated At

+

{formatDate(selectedItem?.updated_at)}

+
+
+

Remarks

+

{selectedItem?.remarks}

+
+
+ + + } + fileName={`invoice_${selectedItem?.billing_id}.pdf`} + className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700" + > + {({ loading }) => (loading ? 'Preparing PDF...' : 'Download PDF')} + + + + + + )} +
+
); } \ No newline at end of file diff --git a/src/components/UpdateAvatar.tsx b/src/components/UpdateAvatar.tsx deleted file mode 100644 index 86d54f3..0000000 --- a/src/components/UpdateAvatar.tsx +++ /dev/null @@ -1,52 +0,0 @@ -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(null); - const fileInputRef = useRef(null); - - const handleFileChange = (e: React.ChangeEvent) => { - 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 ( -
- {!selectedFile ? ( -
-
- - - -
-

- JPG, GIF or PNG. 1MB max. -

-
- ) : ( -
-
-

{selectedFile.name}

-

{(selectedFile.size / 1024).toFixed(2)} KB

-
- - -
- )} -
- ); -} -export default AvatarUpload; \ No newline at end of file diff --git a/src/components/UserProfile.tsx b/src/components/UserProfile.tsx index c2cdf02..e484ae2 100644 --- a/src/components/UserProfile.tsx +++ b/src/components/UserProfile.tsx @@ -1,5 +1,6 @@ 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"; @@ -7,9 +8,14 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from ". import { Separator } from "./ui/separator"; import { Textarea } from "./ui/textarea"; import React, { useState, useEffect } from 'react'; -import UpdateAvatar from './UpdateAvatar'; +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'; + interface SessionData { [key: string]: any; } @@ -19,12 +25,35 @@ interface UserData { 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(null); const [invoiceList, setInvoiceList] = useState([]); const [error, setError] = useState(null); + const [dialogOpen, setDialogOpen] = useState(false); + const [selectedData, setSelectedData] = useState(null); + const [toast, setToast] = useState({ visible: false, message: '' }); + const [txnId, setTxnId] = useState(''); + const [userEmail, setUserEmail] = useState(''); +// console.log('isLoggedIn', sessionData.id) 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(() => { @@ -77,6 +106,27 @@ export default function ProfilePage() { 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', @@ -97,6 +147,9 @@ export default function ProfilePage() { if (!userData) { return (); } + { + // console.log('selectedData', selectedData) + } return (
@@ -115,7 +168,7 @@ export default function ProfilePage() { JP - + {typedSessionData?.id && }
@@ -140,7 +193,7 @@ export default function ProfilePage() {
Billing Information - View All + View All
@@ -149,29 +202,49 @@ export default function ProfilePage() { - - + + - - + + - { - invoiceList.map((invoice, index) => ( - - - - - - - - - )) - } + { + invoiceList.slice(-5).map((invoice, index) => ( + + + + + + + + + )) + }
InvoiceInvoice DateOrder IDInvoice Date DescriptionAmountStatusAmountStatus Action
{invoice.invoice_number}{invoice.invoice_date}{invoice.notes ? invoice.notes : ''}{invoice.total_amount}{invoice.status} - View -
{invoice.billing_id}{invoice?.created_at.split(' ')[0]}{invoice.service}{invoice.amount}{invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)} + + } + fileName={`invoice_${invoice.billing_id}.pdf`} + className="text-[#6d9e37] hover:text-green-600" + > + {({ loading }) => (loading ? '...' : )} + + { + invoice.status !== 'completed' ? ( + + ) : '' + } + +
@@ -205,9 +278,7 @@ export default function ProfilePage() { Danger Zone - - These actions are irreversible. Proceed with caution. - + These actions are irreversible. Proceed with caution.
@@ -219,6 +290,56 @@ export default function ProfilePage() {
+ + + + Billing Details + Review the details for Billing ID: {selectedData?.billing_id} + +
+
+ Service: + {selectedData?.service} +
+
+ cycle: + {selectedData?.cycle} +
+
+ Amount: + ${selectedData?.amount} +
+
+ User: + {selectedData?.user} +
+
+ Status: + + {selectedData?.status} + +
+
+
+ Created At: + {localizeTime(selectedData?.created_at)} +
+
+ Updated At: + {selectedData?.updated_at ? localizeTime(selectedData.updated_at) : '—'} +
+
+ Silicon ID: + {selectedData?.siliconId} +
+
+ + + {/* Optional: Add action buttons here */} + {/* */} + +
+
); -} \ No newline at end of file +} diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index b63192e..fb929e1 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -290,8 +290,21 @@ document.addEventListener('DOMContentLoaded', function() { } @media (max-width: 640px) { - body { - font-size: 15px; + body { + font-size: 15px; + } + } + @keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); } } + .animate-fade-in-up { + animation: fadeInUp 0.3s ease-out forwards; + } diff --git a/src/lib/InvoicePDF.jsx b/src/lib/InvoicePDF.jsx index 29b2386..7f4bc04 100644 --- a/src/lib/InvoicePDF.jsx +++ b/src/lib/InvoicePDF.jsx @@ -1,12 +1,24 @@ import React from 'react'; -import { Page, Text, View, Document, StyleSheet } from '@react-pdf/renderer'; - +import { Page, Text, View, Document, StyleSheet, Image, Font } from '@react-pdf/renderer'; +import {localizeTime} from "../lib/localizeTime"; +import { IndianRupee } from "lucide-react"; +import NotoSans from "../lib/fonts/static/NotoSans_Condensed-Bold.ttf"; +Font.register({ + family: "NotoSans", + src: NotoSans, +}); +// console.log('Fonts', Font) // Create styles -const styles = StyleSheet.create({ +const styles = StyleSheet.create({ page: { + display: "flex", flexDirection: 'column', backgroundColor: '#FFFFFF', - padding: 40 + padding: 40, + }, + currencyValue: { + fontSize: 12, + fontWeight: 'bold' }, header: { flexDirection: 'row', @@ -14,7 +26,18 @@ const styles = StyleSheet.create({ marginBottom: 20, borderBottomWidth: 2, borderBottomColor: '#6d9e37', - paddingBottom: 10 + paddingBottom: 10, + alignItems: 'center' + }, + logoContainer: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + marginBottom: 5 + }, + logoImage: { + width: 30, + height: 30 }, title: { fontSize: 24, @@ -54,15 +77,18 @@ const styles = StyleSheet.create({ borderBottomColor: '#EEEEEE' }, col1: { - width: '40%' + width: '40%', + fontSize: '12px' }, col2: { width: '30%', - textAlign: 'right' + textAlign: 'right', + fontSize: '12px' }, col3: { width: '30%', - textAlign: 'right' + textAlign: 'right', + fontSize: '12px' }, total: { flexDirection: 'row', @@ -90,35 +116,41 @@ const styles = StyleSheet.create({ } }); + +// Base64 encoded SVG logo (replace with your actual logo) +const SILICONPIN_LOGO = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABgElEQVR4nO3bsW0UURRA0TfYLThEDreatUSGC7BdBi4D0QAiWiQ3g0OTQgvsONiICv4J7pF+PE//jmaiN/vMDpzHWWyfedhnzqvv4sPqi8j/CoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmE1Ylvn0eT7+PMzNyhmOv+bv6cf8vlo5xMxsi59/8WW+zjZPi6f4Ns/LZ+iTpSkIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYbWZ/WD3E3N/dzuFl6cLOvB7/zPfT28zqlZ3Zz8BZvsV1eTH3f6vv4nqULSrDNovvo38IpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmDeAd5vHuV+dwDGAAAAAElFTkSuQmCC'; + const InvoicePDF = ({ data }) => { const statusColor = data.status === 'completed' ? '#10B981' : '#EF4444'; - + return ( {/* Header */} - INVOICE Invoice #: {data.billing_id} - Date: {new Date(data.created_at).toLocaleDateString()} - - - Your Company - 123 Business Street - City, Country - contact@yourcompany.com - + Date: {localizeTime(data.created_at)} {/* Bill To */} - + BILL TO: - {data.user} + {data.name} - + { + data.status === 'completed' && ( + + PAID ON: + + {data.payment_date} + + + ) + } + STATUS: {data.status.toUpperCase()} @@ -133,16 +165,16 @@ const InvoicePDF = ({ data }) => { DESCRIPTION - TENURE + CYCLE AMOUNT {data.service} - {data.tenure} - - {new Intl.NumberFormat('en-US', { + {data.cycle} + + {new Intl.NumberFormat('en-IN', { style: 'currency', - currency: 'USD' + currency: 'INR', }).format(parseFloat(data.amount))} @@ -155,30 +187,43 @@ const InvoicePDF = ({ data }) => { SUBTOTAL: - - {new Intl.NumberFormat('en-US', { + + {new Intl.NumberFormat('en-IN', { style: 'currency', - currency: 'USD' + currency: 'INR', }).format(parseFloat(data.amount))} TOTAL: - - {new Intl.NumberFormat('en-US', { + + {new Intl.NumberFormat('en-IN', { style: 'currency', - currency: 'USD' + currency: 'INR', }).format(parseFloat(data.amount))} + + + + + SiliconPin, Habra, W.B. 743271, India, contact@siliconpin.com + + {/* + + */} + {/* Footer */} - + {/* Thank you for your business! - Please make payments payable to Your Company - + Please make payments payable to SiliconPin + */} ); diff --git a/src/lib/InvoicePDF_V1.jsx b/src/lib/InvoicePDF_V1.jsx new file mode 100644 index 0000000..ef47c85 --- /dev/null +++ b/src/lib/InvoicePDF_V1.jsx @@ -0,0 +1,219 @@ +import React from 'react'; +import { Page, Text, View, Document, StyleSheet, Image, Font } from '@react-pdf/renderer'; +import {localizeTime} from "../lib/localizeTime"; +import { IndianRupee } from "lucide-react"; +import NotoSans from "../lib/fonts/static/NotoSans_Condensed-Bold.ttf"; +Font.register({ + family: "NotoSans", + src: NotoSans, +}); +// console.log('Fonts', Font) +// Create styles +const styles = StyleSheet.create({ + page: { + display: "flex", + flexDirection: 'column', + backgroundColor: '#FFFFFF', + padding: 40, + }, + currencyValue: { + fontSize: 12, + fontWeight: 'bold' + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 20, + borderBottomWidth: 2, + borderBottomColor: '#6d9e37', + paddingBottom: 10 + }, + logoContainer: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + marginBottom: 5 + }, + logoImage: { + width: 30, + height: 30 + }, + title: { + fontSize: 24, + fontWeight: 'bold', + color: '#6d9e37' + }, + section: { + marginBottom: 20 + }, + row: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 5 + }, + label: { + fontSize: 12, + fontWeight: 'bold' + }, + value: { + fontSize: 12 + }, + divider: { + borderBottomWidth: 1, + borderBottomColor: '#EEEEEE', + marginVertical: 10 + }, + tableHeader: { + flexDirection: 'row', + backgroundColor: '#F5F5F5', + padding: 5, + marginBottom: 5 + }, + tableRow: { + flexDirection: 'row', + padding: 5, + borderBottomWidth: 1, + borderBottomColor: '#EEEEEE' + }, + col1: { + width: '40%' + }, + col2: { + width: '30%', + textAlign: 'right' + }, + col3: { + width: '30%', + textAlign: 'right' + }, + total: { + flexDirection: 'row', + justifyContent: 'flex-end', + marginTop: 10 + }, + totalText: { + fontSize: 14, + fontWeight: 'bold' + }, + footer: { + position: 'absolute', + bottom: 30, + left: 40, + right: 40, + textAlign: 'center', + fontSize: 10, + color: '#999999' + }, + status: { + padding: 3, + borderRadius: 3, + fontSize: 10, + fontWeight: 'bold' + } +}); + + +// Base64 encoded SVG logo (replace with your actual logo) +const SILICONPIN_LOGO = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABgElEQVR4nO3bsW0UURRA0TfYLThEDreatUSGC7BdBi4D0QAiWiQ3g0OTQgvsONiICv4J7pF+PE//jmaiN/vMDpzHWWyfedhnzqvv4sPqi8j/CoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmIJgCoIpCKYgmE1Ylvn0eT7+PMzNyhmOv+bv6cf8vlo5xMxsi59/8WW+zjZPi6f4Ns/LZ+iTpSkIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYbWZ/WD3E3N/dzuFl6cLOvB7/zPfT28zqlZ3Zz8BZvsV1eTH3f6vv4nqULSrDNovvo38IpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmAKgikIpiCYgmDeAd5vHuV+dwDGAAAAAElFTkSuQmCC'; + +const InvoicePDF = ({ data }) => { + const statusColor = data.status === 'completed' ? '#10B981' : '#EF4444'; + + return ( + + + {/* Header */} + + + INVOICE + Invoice #: {data.billing_id} + Date: {localizeTime(data.created_at)} + + + + + SiliconPin + + 121 Lalbari, GourBongo Road + Habra, W.B. 743271, India + contact@siliconpin.com + + + + {/* Bill To */} + + + + BILL TO: + {data.name} + + + STATUS: + + {data.status.toUpperCase()} + + + + + + + + {/* Items Table */} + + + DESCRIPTION + cycle + AMOUNT + + + {data.service} + {data.cycle} + + {new Intl.NumberFormat('en-IN', { + style: 'currency', + currency: 'INR', + }).format(parseFloat(data.amount))} + + + + + + + {/* Total */} + + + + SUBTOTAL: + + {new Intl.NumberFormat('en-IN', { + style: 'currency', + currency: 'INR', + }).format(parseFloat(data.amount))} + + + + TOTAL: + + {new Intl.NumberFormat('en-IN', { + style: 'currency', + currency: 'INR', + }).format(parseFloat(data.amount))} + + + + + + {/* Footer */} + + Thank you for your business! + Please make payments payable to SiliconPin + + + + ); +}; + +export default InvoicePDF; \ No newline at end of file diff --git a/src/lib/fonts/NotoSans-Italic-VariableFont_wdth,wght.ttf b/src/lib/fonts/NotoSans-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..6245ba0 Binary files /dev/null and b/src/lib/fonts/NotoSans-Italic-VariableFont_wdth,wght.ttf differ diff --git a/src/lib/fonts/NotoSans-VariableFont_wdth,wght.ttf b/src/lib/fonts/NotoSans-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..9530d84 Binary files /dev/null and b/src/lib/fonts/NotoSans-VariableFont_wdth,wght.ttf differ diff --git a/src/lib/fonts/OFL.txt b/src/lib/fonts/OFL.txt new file mode 100644 index 0000000..09f020b --- /dev/null +++ b/src/lib/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/lib/fonts/README.txt b/src/lib/fonts/README.txt new file mode 100644 index 0000000..4958bee --- /dev/null +++ b/src/lib/fonts/README.txt @@ -0,0 +1,136 @@ +Noto Sans Variable Font +======================= + +This download contains Noto Sans as both variable fonts and static fonts. + +Noto Sans is a variable font with these axes: + wdth + wght + +This means all the styles are contained in these files: + NotoSans-VariableFont_wdth,wght.ttf + NotoSans-Italic-VariableFont_wdth,wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Noto Sans: + static/NotoSans_ExtraCondensed-Thin.ttf + static/NotoSans_ExtraCondensed-ExtraLight.ttf + static/NotoSans_ExtraCondensed-Light.ttf + static/NotoSans_ExtraCondensed-Regular.ttf + static/NotoSans_ExtraCondensed-Medium.ttf + static/NotoSans_ExtraCondensed-SemiBold.ttf + static/NotoSans_ExtraCondensed-Bold.ttf + static/NotoSans_ExtraCondensed-ExtraBold.ttf + static/NotoSans_ExtraCondensed-Black.ttf + static/NotoSans_Condensed-Thin.ttf + static/NotoSans_Condensed-ExtraLight.ttf + static/NotoSans_Condensed-Light.ttf + static/NotoSans_Condensed-Regular.ttf + static/NotoSans_Condensed-Medium.ttf + static/NotoSans_Condensed-SemiBold.ttf + static/NotoSans_Condensed-Bold.ttf + static/NotoSans_Condensed-ExtraBold.ttf + static/NotoSans_Condensed-Black.ttf + static/NotoSans_SemiCondensed-Thin.ttf + static/NotoSans_SemiCondensed-ExtraLight.ttf + static/NotoSans_SemiCondensed-Light.ttf + static/NotoSans_SemiCondensed-Regular.ttf + static/NotoSans_SemiCondensed-Medium.ttf + static/NotoSans_SemiCondensed-SemiBold.ttf + static/NotoSans_SemiCondensed-Bold.ttf + static/NotoSans_SemiCondensed-ExtraBold.ttf + static/NotoSans_SemiCondensed-Black.ttf + static/NotoSans-Thin.ttf + static/NotoSans-ExtraLight.ttf + static/NotoSans-Light.ttf + static/NotoSans-Regular.ttf + static/NotoSans-Medium.ttf + static/NotoSans-SemiBold.ttf + static/NotoSans-Bold.ttf + static/NotoSans-ExtraBold.ttf + static/NotoSans-Black.ttf + static/NotoSans_ExtraCondensed-ThinItalic.ttf + static/NotoSans_ExtraCondensed-ExtraLightItalic.ttf + static/NotoSans_ExtraCondensed-LightItalic.ttf + static/NotoSans_ExtraCondensed-Italic.ttf + static/NotoSans_ExtraCondensed-MediumItalic.ttf + static/NotoSans_ExtraCondensed-SemiBoldItalic.ttf + static/NotoSans_ExtraCondensed-BoldItalic.ttf + static/NotoSans_ExtraCondensed-ExtraBoldItalic.ttf + static/NotoSans_ExtraCondensed-BlackItalic.ttf + static/NotoSans_Condensed-ThinItalic.ttf + static/NotoSans_Condensed-ExtraLightItalic.ttf + static/NotoSans_Condensed-LightItalic.ttf + static/NotoSans_Condensed-Italic.ttf + static/NotoSans_Condensed-MediumItalic.ttf + static/NotoSans_Condensed-SemiBoldItalic.ttf + static/NotoSans_Condensed-BoldItalic.ttf + static/NotoSans_Condensed-ExtraBoldItalic.ttf + static/NotoSans_Condensed-BlackItalic.ttf + static/NotoSans_SemiCondensed-ThinItalic.ttf + static/NotoSans_SemiCondensed-ExtraLightItalic.ttf + static/NotoSans_SemiCondensed-LightItalic.ttf + static/NotoSans_SemiCondensed-Italic.ttf + static/NotoSans_SemiCondensed-MediumItalic.ttf + static/NotoSans_SemiCondensed-SemiBoldItalic.ttf + static/NotoSans_SemiCondensed-BoldItalic.ttf + static/NotoSans_SemiCondensed-ExtraBoldItalic.ttf + static/NotoSans_SemiCondensed-BlackItalic.ttf + static/NotoSans-ThinItalic.ttf + static/NotoSans-ExtraLightItalic.ttf + static/NotoSans-LightItalic.ttf + static/NotoSans-Italic.ttf + static/NotoSans-MediumItalic.ttf + static/NotoSans-SemiBoldItalic.ttf + static/NotoSans-BoldItalic.ttf + static/NotoSans-ExtraBoldItalic.ttf + static/NotoSans-BlackItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/src/lib/fonts/static/NotoSans-Black.ttf b/src/lib/fonts/static/NotoSans-Black.ttf new file mode 100644 index 0000000..d5a6e0d Binary files /dev/null and b/src/lib/fonts/static/NotoSans-Black.ttf differ diff --git a/src/lib/fonts/static/NotoSans-BlackItalic.ttf b/src/lib/fonts/static/NotoSans-BlackItalic.ttf new file mode 100644 index 0000000..dfc640c Binary files /dev/null and b/src/lib/fonts/static/NotoSans-BlackItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans-Bold.ttf b/src/lib/fonts/static/NotoSans-Bold.ttf new file mode 100644 index 0000000..506f7d8 Binary files /dev/null and b/src/lib/fonts/static/NotoSans-Bold.ttf differ diff --git a/src/lib/fonts/static/NotoSans-BoldItalic.ttf b/src/lib/fonts/static/NotoSans-BoldItalic.ttf new file mode 100644 index 0000000..0e8fa4b Binary files /dev/null and b/src/lib/fonts/static/NotoSans-BoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans-ExtraBold.ttf b/src/lib/fonts/static/NotoSans-ExtraBold.ttf new file mode 100644 index 0000000..5868446 Binary files /dev/null and b/src/lib/fonts/static/NotoSans-ExtraBold.ttf differ diff --git a/src/lib/fonts/static/NotoSans-ExtraBoldItalic.ttf b/src/lib/fonts/static/NotoSans-ExtraBoldItalic.ttf new file mode 100644 index 0000000..68abd4c Binary files /dev/null and b/src/lib/fonts/static/NotoSans-ExtraBoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans-ExtraLight.ttf b/src/lib/fonts/static/NotoSans-ExtraLight.ttf new file mode 100644 index 0000000..078f8dc Binary files /dev/null and b/src/lib/fonts/static/NotoSans-ExtraLight.ttf differ diff --git a/src/lib/fonts/static/NotoSans-ExtraLightItalic.ttf b/src/lib/fonts/static/NotoSans-ExtraLightItalic.ttf new file mode 100644 index 0000000..acaa466 Binary files /dev/null and b/src/lib/fonts/static/NotoSans-ExtraLightItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans-Italic.ttf b/src/lib/fonts/static/NotoSans-Italic.ttf new file mode 100644 index 0000000..d9b9e14 Binary files /dev/null and b/src/lib/fonts/static/NotoSans-Italic.ttf differ diff --git a/src/lib/fonts/static/NotoSans-Light.ttf b/src/lib/fonts/static/NotoSans-Light.ttf new file mode 100644 index 0000000..8d8a678 Binary files /dev/null and b/src/lib/fonts/static/NotoSans-Light.ttf differ diff --git a/src/lib/fonts/static/NotoSans-LightItalic.ttf b/src/lib/fonts/static/NotoSans-LightItalic.ttf new file mode 100644 index 0000000..0ab65c0 Binary files /dev/null and b/src/lib/fonts/static/NotoSans-LightItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans-Medium.ttf b/src/lib/fonts/static/NotoSans-Medium.ttf new file mode 100644 index 0000000..a44124b Binary files /dev/null and b/src/lib/fonts/static/NotoSans-Medium.ttf differ diff --git a/src/lib/fonts/static/NotoSans-MediumItalic.ttf b/src/lib/fonts/static/NotoSans-MediumItalic.ttf new file mode 100644 index 0000000..467af1b Binary files /dev/null and b/src/lib/fonts/static/NotoSans-MediumItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans-Regular.ttf b/src/lib/fonts/static/NotoSans-Regular.ttf new file mode 100644 index 0000000..4bac02f Binary files /dev/null and b/src/lib/fonts/static/NotoSans-Regular.ttf differ diff --git a/src/lib/fonts/static/NotoSans-SemiBold.ttf b/src/lib/fonts/static/NotoSans-SemiBold.ttf new file mode 100644 index 0000000..e846749 Binary files /dev/null and b/src/lib/fonts/static/NotoSans-SemiBold.ttf differ diff --git a/src/lib/fonts/static/NotoSans-SemiBoldItalic.ttf b/src/lib/fonts/static/NotoSans-SemiBoldItalic.ttf new file mode 100644 index 0000000..cacc7ec Binary files /dev/null and b/src/lib/fonts/static/NotoSans-SemiBoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans-Thin.ttf b/src/lib/fonts/static/NotoSans-Thin.ttf new file mode 100644 index 0000000..04335a5 Binary files /dev/null and b/src/lib/fonts/static/NotoSans-Thin.ttf differ diff --git a/src/lib/fonts/static/NotoSans-ThinItalic.ttf b/src/lib/fonts/static/NotoSans-ThinItalic.ttf new file mode 100644 index 0000000..910dfc7 Binary files /dev/null and b/src/lib/fonts/static/NotoSans-ThinItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-Black.ttf b/src/lib/fonts/static/NotoSans_Condensed-Black.ttf new file mode 100644 index 0000000..3186699 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-Black.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-BlackItalic.ttf b/src/lib/fonts/static/NotoSans_Condensed-BlackItalic.ttf new file mode 100644 index 0000000..d4b19bc Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-BlackItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-Bold.ttf b/src/lib/fonts/static/NotoSans_Condensed-Bold.ttf new file mode 100644 index 0000000..1ce44bc Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-Bold.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-BoldItalic.ttf b/src/lib/fonts/static/NotoSans_Condensed-BoldItalic.ttf new file mode 100644 index 0000000..1960e68 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-BoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-ExtraBold.ttf b/src/lib/fonts/static/NotoSans_Condensed-ExtraBold.ttf new file mode 100644 index 0000000..cb36919 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-ExtraBold.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-ExtraBoldItalic.ttf b/src/lib/fonts/static/NotoSans_Condensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..7bbea17 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-ExtraBoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-ExtraLight.ttf b/src/lib/fonts/static/NotoSans_Condensed-ExtraLight.ttf new file mode 100644 index 0000000..29a7751 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-ExtraLight.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-ExtraLightItalic.ttf b/src/lib/fonts/static/NotoSans_Condensed-ExtraLightItalic.ttf new file mode 100644 index 0000000..983b81a Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-ExtraLightItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-Italic.ttf b/src/lib/fonts/static/NotoSans_Condensed-Italic.ttf new file mode 100644 index 0000000..8e2d1f8 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-Italic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-Light.ttf b/src/lib/fonts/static/NotoSans_Condensed-Light.ttf new file mode 100644 index 0000000..32c58a5 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-Light.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-LightItalic.ttf b/src/lib/fonts/static/NotoSans_Condensed-LightItalic.ttf new file mode 100644 index 0000000..c5d1b45 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-LightItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-Medium.ttf b/src/lib/fonts/static/NotoSans_Condensed-Medium.ttf new file mode 100644 index 0000000..45f8ea4 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-Medium.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-MediumItalic.ttf b/src/lib/fonts/static/NotoSans_Condensed-MediumItalic.ttf new file mode 100644 index 0000000..92cd88a Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-MediumItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-Regular.ttf b/src/lib/fonts/static/NotoSans_Condensed-Regular.ttf new file mode 100644 index 0000000..3ad9a1b Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-Regular.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-SemiBold.ttf b/src/lib/fonts/static/NotoSans_Condensed-SemiBold.ttf new file mode 100644 index 0000000..2f20a21 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-SemiBold.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-SemiBoldItalic.ttf b/src/lib/fonts/static/NotoSans_Condensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..b28147d Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-SemiBoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-Thin.ttf b/src/lib/fonts/static/NotoSans_Condensed-Thin.ttf new file mode 100644 index 0000000..d5b50b5 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-Thin.ttf differ diff --git a/src/lib/fonts/static/NotoSans_Condensed-ThinItalic.ttf b/src/lib/fonts/static/NotoSans_Condensed-ThinItalic.ttf new file mode 100644 index 0000000..00d9315 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_Condensed-ThinItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-Black.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-Black.ttf new file mode 100644 index 0000000..619c4f8 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-Black.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-BlackItalic.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-BlackItalic.ttf new file mode 100644 index 0000000..f124627 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-BlackItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-Bold.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-Bold.ttf new file mode 100644 index 0000000..11e4699 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-Bold.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-BoldItalic.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-BoldItalic.ttf new file mode 100644 index 0000000..81ec21f Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-BoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraBold.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraBold.ttf new file mode 100644 index 0000000..2ce3cb3 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraBold.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraBoldItalic.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..9892967 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraBoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraLight.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraLight.ttf new file mode 100644 index 0000000..ce67cb1 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraLight.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraLightItalic.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraLightItalic.ttf new file mode 100644 index 0000000..45726c3 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-ExtraLightItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-Italic.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-Italic.ttf new file mode 100644 index 0000000..e6b1a73 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-Italic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-Light.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-Light.ttf new file mode 100644 index 0000000..5e9fef8 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-Light.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-LightItalic.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-LightItalic.ttf new file mode 100644 index 0000000..500c919 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-LightItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-Medium.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-Medium.ttf new file mode 100644 index 0000000..c78465e Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-Medium.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-MediumItalic.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-MediumItalic.ttf new file mode 100644 index 0000000..527291a Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-MediumItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-Regular.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-Regular.ttf new file mode 100644 index 0000000..8921daa Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-Regular.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-SemiBold.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-SemiBold.ttf new file mode 100644 index 0000000..83b98b2 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-SemiBold.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-SemiBoldItalic.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..9dedf3e Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-SemiBoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-Thin.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-Thin.ttf new file mode 100644 index 0000000..81e2bf9 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-Thin.ttf differ diff --git a/src/lib/fonts/static/NotoSans_ExtraCondensed-ThinItalic.ttf b/src/lib/fonts/static/NotoSans_ExtraCondensed-ThinItalic.ttf new file mode 100644 index 0000000..17b43b1 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_ExtraCondensed-ThinItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-Black.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-Black.ttf new file mode 100644 index 0000000..5a141da Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-Black.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-BlackItalic.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-BlackItalic.ttf new file mode 100644 index 0000000..538888d Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-BlackItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-Bold.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-Bold.ttf new file mode 100644 index 0000000..9cff468 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-Bold.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-BoldItalic.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-BoldItalic.ttf new file mode 100644 index 0000000..314024c Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-BoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraBold.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraBold.ttf new file mode 100644 index 0000000..c50c081 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraBold.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraBoldItalic.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..b8b053e Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraBoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraLight.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraLight.ttf new file mode 100644 index 0000000..6450a04 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraLight.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraLightItalic.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraLightItalic.ttf new file mode 100644 index 0000000..a655d1e Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-ExtraLightItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-Italic.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-Italic.ttf new file mode 100644 index 0000000..67c7a2f Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-Italic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-Light.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-Light.ttf new file mode 100644 index 0000000..f9221c3 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-Light.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-LightItalic.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-LightItalic.ttf new file mode 100644 index 0000000..9a72200 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-LightItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-Medium.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-Medium.ttf new file mode 100644 index 0000000..e2c825c Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-Medium.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-MediumItalic.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-MediumItalic.ttf new file mode 100644 index 0000000..6be577a Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-MediumItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-Regular.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-Regular.ttf new file mode 100644 index 0000000..06a2982 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-Regular.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-SemiBold.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-SemiBold.ttf new file mode 100644 index 0000000..8c8f313 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-SemiBold.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-SemiBoldItalic.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..59093a9 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-SemiBoldItalic.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-Thin.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-Thin.ttf new file mode 100644 index 0000000..7d7ef33 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-Thin.ttf differ diff --git a/src/lib/fonts/static/NotoSans_SemiCondensed-ThinItalic.ttf b/src/lib/fonts/static/NotoSans_SemiCondensed-ThinItalic.ttf new file mode 100644 index 0000000..44084d9 Binary files /dev/null and b/src/lib/fonts/static/NotoSans_SemiCondensed-ThinItalic.ttf differ diff --git a/src/lib/localizeTime.tsx b/src/lib/localizeTime.tsx index 50235f7..0b33f6b 100644 --- a/src/lib/localizeTime.tsx +++ b/src/lib/localizeTime.tsx @@ -1,11 +1,11 @@ -export function localizeTime(timeValue: string, targetTimeZone?: string): string { - // 1. Auto-detect user's timezone if none provided +export function localizeTime(timeValue?: string, targetTimeZone?: string): string { + if (!timeValue) return 'Invalid date'; + if (!targetTimeZone) { targetTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; } - // 2. Format the date in the target timezone - const date = new Date(timeValue.replace(' ', 'T') + 'Z'); // Ensure UTC + const date = new Date(timeValue.replace(' ', 'T') + 'Z'); const formatter = new Intl.DateTimeFormat('en-US', { timeZone: targetTimeZone, year: 'numeric', @@ -28,10 +28,3 @@ export function localizeTime(timeValue: string, targetTimeZone?: string): string return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } - -// Usage: -// const localTime = localizeTime(ticket.created_at); // Auto-detects timezone -// console.log(localTime); // "2025-04-03 20:14:50" (in user's local time) - -// const timeInTokyo = localizeTime(ticket.created_at, "Asia/Tokyo"); -// console.log(timeInTokyo); // "2025-04-04 05:14:50" (Tokyo time) \ No newline at end of file diff --git a/src/pages/profile.astro b/src/pages/profile.astro deleted file mode 100644 index 3b1d031..0000000 --- a/src/pages/profile.astro +++ /dev/null @@ -1,21 +0,0 @@ ---- -import Layout from "../layouts/Layout.astro"; -// const phpHello = `$_SESSION['userName']`; -import UserProfile from "../components/UserProfile"; ---- - - - - - diff --git a/src/pages/profile/billing-info.astro b/src/pages/profile/billing-info.astro new file mode 100644 index 0000000..b90da2a --- /dev/null +++ b/src/pages/profile/billing-info.astro @@ -0,0 +1,7 @@ +--- +import Layout from "../../layouts/Layout.astro"; +import BillingInformation from "../../components/BillingInfo"; +--- + + + \ No newline at end of file diff --git a/src/pages/profile/index.astro b/src/pages/profile/index.astro new file mode 100644 index 0000000..b73fe85 --- /dev/null +++ b/src/pages/profile/index.astro @@ -0,0 +1,9 @@ +--- +import Layout from "../../layouts/Layout.astro"; +// const phpHello = `$_SESSION['userName']`; +import UserProfile from "../../components/UserProfile"; +--- + + + + diff --git a/yarn.lock b/yarn.lock index 35afb45..eb869a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1106,6 +1106,11 @@ acorn@^8.14.1: resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz" integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== +adler-32@~1.3.0: + version "1.3.1" + resolved "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz" + integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A== + ansi-align@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz" @@ -1449,6 +1454,14 @@ ccount@^2.0.0: resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== +cfb@~1.2.1: + version "1.2.2" + resolved "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz" + integrity sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA== + dependencies: + adler-32 "~1.3.0" + crc-32 "~1.2.0" + chalk@^5.0.0, chalk@5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz" @@ -1557,6 +1570,11 @@ codemirror@*, codemirror@^5.65.15: resolved "https://registry.npmjs.org/codemirror/-/codemirror-5.65.19.tgz" integrity sha512-+aFkvqhaAVr1gferNMuN8vkTSrWIFvzlMV9I2KBLCWS2WpZ2+UAkZjlMZmEuT+gcXTi6RrGQCkWq1/bDtGqhIA== +codepage@~1.15.0: + version "1.15.0" + resolved "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz" + integrity sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" @@ -1625,6 +1643,11 @@ cookie@^1.0.1: resolved "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz" integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== +crc-32@~1.2.0, crc-32@~1.2.1: + version "1.2.2" + resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" @@ -2047,6 +2070,11 @@ formdata-polyfill@^4.0.10: dependencies: fetch-blob "^3.1.2" +frac@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz" + integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA== + fraction.js@^4.3.7: version "4.3.7" resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz" @@ -5028,6 +5056,13 @@ split-on-first@^1.0.0: resolved "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== +ssf@~0.11.2: + version "0.11.2" + resolved "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz" + integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g== + dependencies: + frac "~1.1.2" + stdin-discarder@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz" @@ -5707,6 +5742,16 @@ widest-line@^5.0.0: dependencies: string-width "^7.0.0" +wmf@~1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz" + integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw== + +word@~0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/word/-/word-0.3.0.tgz" + integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" @@ -5734,6 +5779,19 @@ wrap-ansi@^9.0.0: string-width "^7.0.0" strip-ansi "^7.1.0" +xlsx@^0.18.5: + version "0.18.5" + resolved "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz" + integrity sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ== + dependencies: + adler-32 "~1.3.0" + cfb "~1.2.1" + codepage "~1.15.0" + crc-32 "~1.2.1" + ssf "~0.11.2" + wmf "~1.0.1" + word "~0.3.0" + "xml2js@^0.5.0 || ^0.6.2": version "0.6.2" resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz"