mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-29 14:08:12 +00:00
feat: add support for global personal document requirements in applicant vaults
This commit is contained in:
@@ -6,6 +6,7 @@ export * from './lib/features/location';
|
||||
export * from './lib/features/seafarer';
|
||||
export * from './lib/features/seafarer-registration';
|
||||
export * from './lib/features/seafarer-document';
|
||||
export * from './lib/features/personal-document';
|
||||
export * from './lib/features/vessel';
|
||||
export { baseQueryWithReauth, configureTokenRefresh } from './lib/base-api/base-query-with-reauth';
|
||||
export { openAuthedDocument, downloadAuthedFile } from './lib/base-api/download';
|
||||
|
||||
@@ -236,7 +236,9 @@ export const licensingApi = baseApi
|
||||
|
||||
createDocumentRequirement: builder.mutation<
|
||||
DocumentRequirement,
|
||||
Partial<DocumentRequirement> & { licenseTypeId: string; key: string; name: DocumentRequirement['name'] }
|
||||
// No `licenseTypeId` means a personal document, required for every
|
||||
// licence and served from the applicant's own vault.
|
||||
Partial<DocumentRequirement> & { key: string; name: DocumentRequirement['name'] }
|
||||
>({
|
||||
query: (body) => ({ url: '/document-requirements', method: 'POST', body }),
|
||||
invalidatesTags: (_r, error) => (error ? [] : [listTag('DocumentRequirement')]),
|
||||
|
||||
@@ -252,7 +252,12 @@ export interface StcwCapacityRow {
|
||||
|
||||
export interface DocumentRequirement {
|
||||
id: string;
|
||||
licenseTypeId: string;
|
||||
/**
|
||||
* Null for a personal document — one every applicant keeps in their own
|
||||
* vault regardless of what they apply for, rather than a slot on one
|
||||
* licence's application form.
|
||||
*/
|
||||
licenseTypeId: string | null;
|
||||
key: string;
|
||||
name: Bilingual;
|
||||
description?: Bilingual;
|
||||
@@ -263,6 +268,8 @@ export interface DocumentRequirement {
|
||||
maxSizeMb: number;
|
||||
requiresValidityDates: boolean;
|
||||
allowMultiple: boolean;
|
||||
/** Files the slot accepts: 1, 2 for a front and back, null for unlimited. */
|
||||
maxFiles: number | null;
|
||||
sortOrder: number;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
2
libs/api/src/lib/features/personal-document/index.ts
Normal file
2
libs/api/src/lib/features/personal-document/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './personal-document.types';
|
||||
export * from './personal-document-api';
|
||||
@@ -0,0 +1,69 @@
|
||||
import { baseApi } from '../../base-api';
|
||||
import type { PersonalDocumentSlot } from './personal-document.types';
|
||||
|
||||
const TAG = 'PersonalDocument' as const;
|
||||
const LIST = { type: TAG, id: 'LIST' } as const;
|
||||
|
||||
/**
|
||||
* The applicant's own documents — identity card, photograph, education —
|
||||
* kept against their profile rather than any one application, so they survive
|
||||
* having no seafarer registration yet.
|
||||
*
|
||||
* Bodies are `FormData`: `fetchBaseQuery` passes one through untouched and
|
||||
* never sets `Content-Type`, so the browser's multipart boundary survives.
|
||||
* Going through RTK rather than the raw `uploadDocument` helper is what buys
|
||||
* the automatic refetch after every write.
|
||||
*/
|
||||
export const personalDocumentApi = baseApi
|
||||
.enhanceEndpoints({ addTagTypes: [TAG] })
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getMyPersonalDocuments: builder.query<{ slots: PersonalDocumentSlot[] }, void>({
|
||||
query: () => ({ url: '/profiles/me/documents' }),
|
||||
providesTags: () => [LIST],
|
||||
}),
|
||||
|
||||
/** One file per call — front and back of an ID are two uploads. */
|
||||
uploadPersonalDocument: builder.mutation<
|
||||
PersonalDocumentSlot,
|
||||
{ documentKey: string; file: File }
|
||||
>({
|
||||
query: ({ documentKey, file }) => {
|
||||
const body = new FormData();
|
||||
body.append('documentKey', documentKey);
|
||||
body.append('file', file, file.name);
|
||||
return { url: '/profiles/me/documents', method: 'POST', body };
|
||||
},
|
||||
invalidatesTags: (_r, error) => (error ? [] : [LIST]),
|
||||
}),
|
||||
|
||||
/** Swaps one file in place; the slot comes from the file, not the caller. */
|
||||
replacePersonalDocumentFile: builder.mutation<
|
||||
PersonalDocumentSlot,
|
||||
{ fileId: string; file: File }
|
||||
>({
|
||||
query: ({ fileId, file }) => {
|
||||
const body = new FormData();
|
||||
body.append('file', file, file.name);
|
||||
return { url: `/profiles/me/documents/files/${fileId}`, method: 'PUT', body };
|
||||
},
|
||||
invalidatesTags: (_r, error) => (error ? [] : [LIST]),
|
||||
}),
|
||||
|
||||
deletePersonalDocumentFile: builder.mutation<{ deleted: boolean }, string>({
|
||||
query: (fileId) => ({
|
||||
url: `/profiles/me/documents/files/${fileId}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: (_r, error) => (error ? [] : [LIST]),
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetMyPersonalDocumentsQuery,
|
||||
useUploadPersonalDocumentMutation,
|
||||
useReplacePersonalDocumentFileMutation,
|
||||
useDeletePersonalDocumentFileMutation,
|
||||
} = personalDocumentApi;
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { AttachmentFile, Bilingual } from '../licensing/licensing.types';
|
||||
|
||||
/**
|
||||
* One slot in the applicant's personal document vault, with whatever they
|
||||
* have put in it.
|
||||
*
|
||||
* The slot itself is configuration: a document requirement that names no
|
||||
* licence type applies to every licence, so the backoffice adds and retires
|
||||
* these without a release. That is why the label, the accepted types and the
|
||||
* limits arrive from the API rather than living in the portal.
|
||||
*/
|
||||
export interface PersonalDocumentSlot {
|
||||
key: string;
|
||||
name: Bilingual;
|
||||
description: Bilingual | null;
|
||||
/** How many files the slot holds; null means as many as the holder has. */
|
||||
maxFiles: number | null;
|
||||
allowedMimeTypes: string[];
|
||||
maxSizeMb: number;
|
||||
sortOrder: number;
|
||||
files: AttachmentFile[];
|
||||
}
|
||||
Reference in New Issue
Block a user