fix(clients): minor-age threshold to 18, senior/PWD doc reload, in-app document preview dialog
- Raise isMinor threshold from age <10 to <18 (legal definition), cascading correctly to guardian requirement, KYC document filtering, and Education Level/Degree Qualification visibility (now shown for all ages, not hidden for minors). - Load and display previously-uploaded Senior Citizen proof / PWD certificate documents on edit (view/replace/remove), instead of always showing an empty upload prompt. - Fix seniorPwdDocsComplete to accept an existing certificate, not just a freshly-picked file. - Add DocumentPreviewDialogComponent: a reusable in-app modal (image/PDF/download) used in place of window.open() for saved KYC/address/senior-citizen/PWD documents — avoids popup blockers (fired only after the async download resolved) and Chrome/Edge's failure to paint large base64 data: URLs on first open. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<h1 mat-dialog-title>{{ data.title || data.fileName || 'Document Preview' }}</h1>
|
||||
|
||||
<div mat-dialog-content class="doc-preview-content">
|
||||
@if (isImage) {
|
||||
<img [src]="previewUrl" [alt]="data.fileName || 'Document'" class="doc-preview-image" />
|
||||
} @else if (isPdf) {
|
||||
<iframe [src]="safeUrl" class="doc-preview-pdf" title="Document preview"></iframe>
|
||||
} @else {
|
||||
<p>Preview isn't available for this file type — use Download to view it.</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-raised-button (click)="download()">Download</button>
|
||||
<button mat-raised-button mat-dialog-close color="primary">Close</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.doc-preview-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-width: 320px;
|
||||
max-width: 80vw;
|
||||
}
|
||||
|
||||
.doc-preview-image {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.doc-preview-pdf {
|
||||
width: 70vw;
|
||||
height: 70vh;
|
||||
border: none;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnDestroy, inject } from '@angular/core';
|
||||
import {
|
||||
MatDialogRef,
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
} from '@angular/material/dialog';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
export interface DocumentPreviewDialogData {
|
||||
blob: Blob;
|
||||
title?: string;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic in-app document preview dialog — shows an image inline or a PDF in an embedded
|
||||
* viewer, with a Download fallback for anything else. Used in place of window.open() for
|
||||
* saved KYC/address/senior-citizen/PWD documents: opening a new browser tab is subject to
|
||||
* popup blockers (fires only after the async download resolves) and, for images, Chrome/Edge
|
||||
* frequently fail to paint a huge base64 data: URL until the tab is manually refreshed.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-document-preview-dialog',
|
||||
templateUrl: './document-preview-dialog.component.html',
|
||||
styleUrls: ['./document-preview-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
CdkScrollable,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
]
|
||||
})
|
||||
export class DocumentPreviewDialogComponent implements OnDestroy {
|
||||
dialogRef = inject<MatDialogRef<DocumentPreviewDialogComponent>>(MatDialogRef);
|
||||
private sanitizer = inject(DomSanitizer);
|
||||
data = inject<DocumentPreviewDialogData>(MAT_DIALOG_DATA);
|
||||
|
||||
private readonly objectUrl: string;
|
||||
readonly safeUrl: SafeResourceUrl;
|
||||
readonly isImage: boolean;
|
||||
readonly isPdf: boolean;
|
||||
|
||||
constructor() {
|
||||
this.objectUrl = URL.createObjectURL(this.data.blob);
|
||||
this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.objectUrl);
|
||||
const type = this.data.blob.type || '';
|
||||
this.isImage = type.startsWith('image/');
|
||||
this.isPdf = type === 'application/pdf';
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
URL.revokeObjectURL(this.objectUrl);
|
||||
}
|
||||
|
||||
get previewUrl(): string {
|
||||
return this.objectUrl;
|
||||
}
|
||||
|
||||
download(): void {
|
||||
const a = document.createElement('a');
|
||||
a.href = this.objectUrl;
|
||||
a.download = this.data.fileName || 'document';
|
||||
a.click();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user