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.
|
Het aanleveren van uw documenten is niet gelukt. Probeer het opnieuw.
|
||||||
</p>
|
</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
|
<button
|
||||||
utrecht-button
|
utrecht-button
|
||||||
appearance="primary-action-button"
|
appearance="primary-action-button"
|
||||||
type="button"
|
type="button"
|
||||||
[disabled]="providingDocuments()"
|
[disabled]="providingDocuments() || !selectedFile()"
|
||||||
(click)="provideDocuments()"
|
(click)="provideDocuments()"
|
||||||
>
|
>
|
||||||
Documenten aanleveren
|
Documenten aanleveren
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export class RegistrationPage {
|
|||||||
protected readonly providingDocuments = signal(false);
|
protected readonly providingDocuments = signal(false);
|
||||||
protected readonly documentsProvided = signal(false);
|
protected readonly documentsProvided = signal(false);
|
||||||
protected readonly provideDocumentsFailed = signal(false);
|
protected readonly provideDocumentsFailed = signal(false);
|
||||||
|
protected readonly selectedFile = signal<File | undefined>(undefined);
|
||||||
|
|
||||||
submit(): void {
|
submit(): void {
|
||||||
this.submitting.set(true);
|
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();
|
const reference = this.reference();
|
||||||
if (!reference) {
|
const file = this.selectedFile();
|
||||||
|
if (!reference || !file) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.providingDocuments.set(true);
|
this.providingDocuments.set(true);
|
||||||
this.provideDocumentsFailed.set(false);
|
this.provideDocumentsFailed.set(false);
|
||||||
this.bff.postSelfServiceRegistrationsIdDocuments(reference).subscribe({
|
let contentBase64: string;
|
||||||
next: () => {
|
try {
|
||||||
this.documentsProvided.set(true);
|
contentBase64 = await readAsBase64(file);
|
||||||
this.providingDocuments.set(false);
|
} catch {
|
||||||
},
|
this.provideDocumentsFailed.set(true);
|
||||||
// Surface the failure instead of swallowing it: keep the action so the user can retry.
|
this.providingDocuments.set(false);
|
||||||
error: () => {
|
return;
|
||||||
this.provideDocumentsFailed.set(true);
|
}
|
||||||
this.providingDocuments.set(false);
|
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 {
|
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