Add utility bills proof of payment file upload functionality

Changes:
- Updated BillingLocation interface:
  - Added utilBillsProofOfPaymentAttachment field (BillAttachment type)
- Added server action uploadUtilBillsProofOfPayment:
  - Validates PDF file type
  - Serializes file attachment to base64
  - Stores attachment in BillingLocation document
  - Returns success/error status
- Updated ViewLocationCard component:
  - Added file upload input with PDF-only accept
  - Implemented handleFileChange with immediate upload
  - Added upload state management (isUploading, uploadError, attachment)
  - Shows spinner while uploading
  - Input disabled during upload
  - Conditionally renders file input or download link
  - Link displayed after successful upload
- Created route handler for serving proof of payment PDFs:
  - GET /share/proof-of-payment/[id]/route.tsx
  - Fetches attachment from database
  - Converts base64 to binary
  - Returns PDF with proper headers
- Added not-found page for proof of payment route
- Updated middleware to include proof-of-payment in public pages
- Added translations:
  - en: "Upload proof of payment (PDF only)"
  - hr: "Priložite potvrdu o uplati:"

File uploads immediately on selection without page reload.
Only PDF files accepted with client and server-side validation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Knee Cola
2025-11-22 23:47:08 +01:00
parent 82d29b39c3
commit 6df9557921
6 changed files with 195 additions and 14 deletions

View File

@@ -580,4 +580,77 @@ export const setSeenByTenant = async (locationID: string): Promise<void> => {
{ _id: locationID },
{ $set: { seenByTenant: true } }
);
}
/**
* Serializes a file attachment to be stored in the database
* @param file - The file to serialize
* @returns BillAttachment object or null if file is invalid
*/
const serializeAttachment = async (file: File | null) => {
if (!file) {
return null;
}
const {
name: fileName,
size: fileSize,
type: fileType,
lastModified: fileLastModified,
} = file;
if(!fileName || fileName === 'undefined' || fileSize === 0) {
return null;
}
// Convert file contents to base64 for database storage
const fileContents = await file.arrayBuffer();
const fileContentsBase64 = Buffer.from(fileContents).toString('base64');
return {
fileName,
fileSize,
fileType,
fileLastModified,
fileContentsBase64,
};
}
/**
* Uploads utility bills proof of payment attachment for a location
* @param locationID - The ID of the location
* @param formData - FormData containing the file
* @returns Promise with success status
*/
export const uploadUtilBillsProofOfPayment = async (locationID: string, formData: FormData): Promise<{ success: boolean; error?: string }> => {
noStore();
try {
const file = formData.get('utilBillsProofOfPaymentAttachment') as File;
// Validate file type
if (file && file.type !== 'application/pdf') {
return { success: false, error: 'Only PDF files are accepted' };
}
const attachment = await serializeAttachment(file);
if (!attachment) {
return { success: false, error: 'Invalid file' };
}
const dbClient = await getDbClient();
// Update the location with the attachment
await dbClient.collection<BillingLocation>("lokacije")
.updateOne(
{ _id: locationID },
{ $set: { utilBillsProofOfPaymentAttachment: attachment } }
);
return { success: true };
} catch (error: any) {
console.error('Error uploading util bills proof of payment:', error);
return { success: false, error: error.message || 'Upload failed' };
}
}