Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@capacitor/clipboard": "^7.0.0",
"@capacitor/core": "^7.0.0",
"@capacitor/device": "^7.0.0",
"@capacitor/filesystem": "^7.1.4",
"@capacitor/geolocation": "^7.0.0",
"@capacitor/haptics": "^7.0.0",
"@capacitor/ios": "^7.0.0",
Expand All @@ -59,6 +60,7 @@
"@capacitor/splash-screen": "^7.0.0",
"@capacitor/status-bar": "^7.0.0",
"@capacitor/text-zoom": "^7.0.0",
"@capawesome/capacitor-file-picker": "^7.2.0",
"@googlemaps/js-api-loader": "^1.16.1",
"@ionic/angular": "^8.4.3",
"@ionic/core": "^8.4.3",
Expand Down
177 changes: 88 additions & 89 deletions src/app/fyle/add-edit-expense/add-edit-expense.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
/* eslint-disable complexity */
import { TitleCasePipe } from '@angular/common';
import { Component, ElementRef, EventEmitter, HostListener, OnInit, ViewChild } from '@angular/core';
import { FilePicker } from '@capawesome/capacitor-file-picker';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { Filesystem } from '@capacitor/filesystem';
import {
AbstractControl,
UntypedFormArray,
Expand Down Expand Up @@ -199,8 +202,6 @@ type FormValue = {
export class AddEditExpensePage implements OnInit {
@ViewChild('formContainer') formContainer: ElementRef<HTMLFormElement>;

@ViewChild('fileUpload', { static: false }) fileUpload: ElementRef<HTMLInputElement>;

@ViewChild('projectDependentFieldsRef') projectDependentFieldsRef: DependentFieldsComponent;

@ViewChild('costCenterDependentFieldsRef') costCenterDependentFieldsRef: DependentFieldsComponent;
Expand Down Expand Up @@ -4786,112 +4787,110 @@ export class AddEditExpensePage implements OnInit {
}
}

async uploadFileCallback(file: File): Promise<void> {
let fileData: { type: string; dataUrl: string | ArrayBuffer; actionSource: string };
if (file) {
if (file.size < MAX_FILE_SIZE) {
const fileRead$ = from(this.fileService.readFile(file));
const delayedLoader$ = timer(300).pipe(
switchMap(() => from(this.loaderService.showLoader('Please wait...', 5000))),
switchMap(() => fileRead$), // switch to fileRead$ after showing loader
);
// Use race to show loader only if fileRead$ takes more than 300ms.
fileRead$
.pipe(
raceWith(delayedLoader$),
map((dataUrl) => {
fileData = {
type: file.type,
dataUrl,
actionSource: 'gallery_upload',
};
this.attachReceipts(fileData);
this.trackingService.addAttachment({ type: file.type });
}),
finalize(() => this.loaderService.hideLoader()),
)
.subscribe();
async addAttachments(event: Event): Promise<void> {
event.stopPropagation();
const popup = await this.popoverController.create({
component: CameraOptionsPopupComponent,
cssClass: 'camera-options-popover',
});

await popup.present();

const { data } = (await popup.onWillDismiss()) as {
data: {
option?: string;
type: string;
dataUrl: string;
actionSource?: string;
};
};

if (data && data.option) {
if (data.option === 'camera') {
this.openCamera();
} else {
this.showSizeLimitExceededPopover(MAX_FILE_SIZE);
this.openGallery();
}
}
}
Comment on lines +4790 to 4815
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Mind it! This popup flow is smooth as my stunts, but needs one small touch!

The addAttachments method is well-structured. However, when the popup is dismissed without selecting an option, the data could be undefined. Consider adding a null check.

-    if (data && data.option) {
+    if (data?.option) {
       if (data.option === 'camera') {
         this.openCamera();
       } else {
         this.openGallery();
       }
     }
🤖 Prompt for AI Agents
In src/app/fyle/add-edit-expense/add-edit-expense.page.ts around lines 4790 to
4815, the addAttachments method assumes data is always defined after the popup
dismissal, but data can be undefined if the popup is dismissed without
selection. Add a null or undefined check for data before accessing its
properties to prevent runtime errors. Adjust the condition to safely handle
cases when data is missing.


async onChangeCallback(nativeElement: HTMLInputElement): Promise<void> {
const file = nativeElement.files[0];
this.uploadFileCallback(file);
}
async openCamera(): Promise<void> {
try {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.DataUrl,
source: CameraSource.Camera,
});

async addAttachments(event: Event): Promise<void> {
event.stopPropagation();
if (image && image.dataUrl) {
const receiptDetails = {
type: this.fileService.getImageTypeFromDataUrl(image.dataUrl),
dataUrl: image.dataUrl,
actionSource: 'camera',
};
this.attachReceipts(receiptDetails);
this.showAddedToExpenseToast();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
this.showSnackBarToast({ message: 'Error opening camera' }, 'failure', ['msb-error-with-camera-icon']);
}
}

Comment on lines +4817 to 4840
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Kabali da! Camera handling is super, but error tracking needs more power!

The camera error handling catches the error but doesn't log it. In production, you'll want to know what went wrong.

     } catch (error) {
+      console.error('Camera error:', error);
+      this.trackingService.captureException(error);
       this.showSnackBarToast({ message: 'Error opening camera' }, 'failure', ['msb-error-with-camera-icon']);
     }
🤖 Prompt for AI Agents
In src/app/fyle/add-edit-expense/add-edit-expense.page.ts around lines 4817 to
4840, the catch block for the openCamera method handles errors by showing a
toast but does not log the actual error details. To fix this, add a logging
statement inside the catch block that records the caught error, so you can track
and debug camera-related issues effectively in production.

if (this.platform.is('ios')) {
const nativeElement = this.fileUpload.nativeElement;
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
nativeElement.onchange = async () => {
this.onChangeCallback(nativeElement);
};
nativeElement.click();
} else {
const popup = await this.popoverController.create({
component: CameraOptionsPopupComponent,
cssClass: 'camera-options-popover',
componentProps: {
mode: this.mode,
},
async openGallery(): Promise<void> {
try {
const result = await FilePicker.pickFiles({
types: ['image/jpeg', 'image/png', 'application/pdf', 'image/heic'],
readData: true,
});

await popup.present();

let { data: receiptDetails } = (await popup.onWillDismiss()) as {
data: {
option?: string;
type: string;
dataUrl: string;
actionSource?: string;
};
};
if (result.files.length > 0) {
const file = result.files[0];

if (receiptDetails && receiptDetails.option === 'camera') {
const captureReceiptModal = await this.modalController.create({
component: CaptureReceiptComponent,
componentProps: {
isModal: true,
allowGalleryUploads: false,
allowBulkFyle: false,
},
cssClass: 'hide-modal',
});
if (file.size > MAX_FILE_SIZE) {
this.showSizeLimitExceededPopover(MAX_FILE_SIZE);
return;
}

await captureReceiptModal.present();
this.isCameraPreviewStarted = true;
from(this.loaderService.showLoader('Please wait...', 5000)).subscribe(async () => {
let dataUrl = `data:${file.mimeType};base64,${file.data}`;

const { data } = (await captureReceiptModal.onWillDismiss()) as {
data: {
dataUrl: string;
};
};
this.isCameraPreviewStarted = false;
if (file.mimeType === 'image/heic') {
const conversionResult = await FilePicker.convertHeicToJpeg({
path: file.path,
});
const jpegFile = await Filesystem.readFile({
path: conversionResult.path,
});
dataUrl = `data:image/jpeg;base64,${jpegFile.data}`;
}

if (data && data.dataUrl) {
receiptDetails = {
type: this.fileService.getImageTypeFromDataUrl(data.dataUrl),
dataUrl: data.dataUrl,
actionSource: 'camera',
const receiptDetails = {
type: file.mimeType,
dataUrl: dataUrl,
actionSource: 'gallery_upload',
};
}
}
if (receiptDetails && receiptDetails.dataUrl) {
this.attachReceipts(receiptDetails as { type: string; dataUrl: string });
const message = 'Receipt added to Expense successfully';
this.showSnackBarToast({ message }, 'success', ['msb-success-with-camera-icon']);
this.showReceiptMandatoryError = false;

this.trackingService.showToastMessage({ ToastContent: message });
this.attachReceipts(receiptDetails);
this.trackingService.addAttachment({ type: file.mimeType });
this.showAddedToExpenseToast();
this.loaderService.hideLoader();
});
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
this.showSnackBarToast({ message: 'Error opening gallery' }, 'failure', ['msb-error-with-camera-icon']);
Comment on lines +4882 to +4883
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Gallery error also needs the same punch as camera error, machi!

Similar to the camera error, the gallery error should also be logged for debugging.

     } catch (error) {
+      console.error('Gallery error:', error);
+      this.trackingService.captureException(error);
       this.showSnackBarToast({ message: 'Error opening gallery' }, 'failure', ['msb-error-with-camera-icon']);
     }
🤖 Prompt for AI Agents
In src/app/fyle/add-edit-expense/add-edit-expense.page.ts around lines 4882 to
4883, the catch block for gallery errors only shows a toast message but does not
log the error. Add a logging statement to capture and log the error details for
debugging, similar to how camera errors are logged, before showing the toast
notification.

}
}

showAddedToExpenseToast(): void {
const message = 'Receipt added to Expense successfully';
this.showSnackBarToast({ message }, 'success', ['msb-success-with-camera-icon']);
this.showReceiptMandatoryError = false;
this.trackingService.showToastMessage({ ToastContent: message });
}

getReceiptExtension(name: string): string | null {
let res: string = null;

Expand Down
Loading
Loading