feat(portal): real diploma file upload on the self-service page (refs #103)
Replaces the S-10a stub button with a labelled file input (accept application/pdf); the component base64-encodes the chosen file client-side and posts it (with its name and type) keyed by the reference, confirming on success and surfacing a retryable failure. The upload button stays disabled until a file is chosen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,11 +19,20 @@
|
||||
Het aanleveren van uw documenten is niet gelukt. Probeer het opnieuw.
|
||||
</p>
|
||||
}
|
||||
<p utrecht-paragraph>Lever uw diploma aan (PDF).</p>
|
||||
<label utrecht-form-label for="diploma">Diploma</label>
|
||||
<input
|
||||
id="diploma"
|
||||
type="file"
|
||||
accept="application/pdf"
|
||||
[disabled]="providingDocuments()"
|
||||
(change)="onFileSelected($event)"
|
||||
/>
|
||||
<button
|
||||
utrecht-button
|
||||
appearance="primary-action-button"
|
||||
type="button"
|
||||
[disabled]="providingDocuments()"
|
||||
[disabled]="providingDocuments() || !selectedFile()"
|
||||
(click)="provideDocuments()"
|
||||
>
|
||||
Documenten aanleveren
|
||||
|
||||
@@ -29,6 +29,7 @@ export class RegistrationPage {
|
||||
protected readonly providingDocuments = signal(false);
|
||||
protected readonly documentsProvided = signal(false);
|
||||
protected readonly provideDocumentsFailed = signal(false);
|
||||
protected readonly selectedFile = signal<File | undefined>(undefined);
|
||||
|
||||
submit(): void {
|
||||
this.submitting.set(true);
|
||||
@@ -47,24 +48,44 @@ export class RegistrationPage {
|
||||
});
|
||||
}
|
||||
|
||||
provideDocuments(): void {
|
||||
onFileSelected(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
this.selectedFile.set(input.files?.[0] ?? undefined);
|
||||
}
|
||||
|
||||
async provideDocuments(): Promise<void> {
|
||||
const reference = this.reference();
|
||||
if (!reference) {
|
||||
const file = this.selectedFile();
|
||||
if (!reference || !file) {
|
||||
return;
|
||||
}
|
||||
this.providingDocuments.set(true);
|
||||
this.provideDocumentsFailed.set(false);
|
||||
this.bff.postSelfServiceRegistrationsIdDocuments(reference).subscribe({
|
||||
next: () => {
|
||||
this.documentsProvided.set(true);
|
||||
this.providingDocuments.set(false);
|
||||
},
|
||||
// Surface the failure instead of swallowing it: keep the action so the user can retry.
|
||||
error: () => {
|
||||
this.provideDocumentsFailed.set(true);
|
||||
this.providingDocuments.set(false);
|
||||
},
|
||||
});
|
||||
let contentBase64: string;
|
||||
try {
|
||||
contentBase64 = await readAsBase64(file);
|
||||
} catch {
|
||||
this.provideDocumentsFailed.set(true);
|
||||
this.providingDocuments.set(false);
|
||||
return;
|
||||
}
|
||||
this.bff
|
||||
.postSelfServiceRegistrationsIdDocuments(reference, {
|
||||
contentBase64,
|
||||
fileName: file.name,
|
||||
contentType: file.type || 'application/pdf',
|
||||
})
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.documentsProvided.set(true);
|
||||
this.providingDocuments.set(false);
|
||||
},
|
||||
// Surface the failure instead of swallowing it: keep the action so the user can retry.
|
||||
error: () => {
|
||||
this.provideDocumentsFailed.set(true);
|
||||
this.providingDocuments.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
withdraw(): void {
|
||||
@@ -87,3 +108,13 @@ export class RegistrationPage {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Read a file's bytes as a base64 string (without the `data:...;base64,` prefix). */
|
||||
function readAsBase64(file: File): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(((reader.result as string) ?? '').split(',', 2)[1] ?? '');
|
||||
reader.onerror = () => reject(reader.error ?? new Error('Could not read the file.'));
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user