From bbae230ad5c00d4f2f0ff1494bb594b8833275f4 Mon Sep 17 00:00:00 2001 From: estifanos Date: Wed, 5 Aug 2026 08:50:38 +0000 Subject: [PATCH] feat: implement ModalFooter component for consistent modal action layout --- .../pages/CertificateDesignerPage.tsx | 6 +-- .../certification/pages/CertificationPage.tsx | 6 +-- .../configuration/pages/ConfigurationPage.tsx | 5 ++- .../features/exam/pages/ExamDetailPage.tsx | 10 ++--- .../src/app/features/exam/pages/ExamPage.tsx | 6 +-- .../components/DecisionConfirmModal.tsx | 6 +-- .../pages/LicenseReviewPage.tsx | 10 ++--- .../features/location/pages/LocationPage.tsx | 6 +-- .../pages/PaymentConfigPage.tsx | 6 +-- .../features/question/pages/QuestionPage.tsx | 6 +-- .../result/components/RecordResultModal.tsx | 6 +-- .../app/features/result/pages/ResultPage.tsx | 14 +++---- .../VesselRegistrationFormBuilderPage.tsx | 10 ++--- .../pages/LicenseApplicationPage.tsx | 25 ++++++------ .../components/OperationsFormContent.tsx | 6 +-- .../seafarer/pages/SeafarerProfilePage.tsx | 18 ++++----- apps/portal/src/app/theme/portalTheme.ts | 16 ++++++++ libs/shared/src/lib/theme/ema-theme.ts | 18 +++++++++ libs/ui/src/index.ts | 1 + libs/ui/src/lib/feedback/ConfirmModal.tsx | 7 ++-- libs/ui/src/lib/feedback/ModalFooter.tsx | 38 +++++++++++++++++++ 21 files changed, 152 insertions(+), 74 deletions(-) create mode 100644 libs/ui/src/lib/feedback/ModalFooter.tsx diff --git a/apps/backoffice/src/app/features/certificate-designer/pages/CertificateDesignerPage.tsx b/apps/backoffice/src/app/features/certificate-designer/pages/CertificateDesignerPage.tsx index 46b801648..d98ef0ed0 100644 --- a/apps/backoffice/src/app/features/certificate-designer/pages/CertificateDesignerPage.tsx +++ b/apps/backoffice/src/app/features/certificate-designer/pages/CertificateDesignerPage.tsx @@ -44,7 +44,7 @@ import { useUpdateLicenseTemplateMutation, type LicenseTemplate, } from '@ema-platform/api'; -import { EmptyState, ErrorState, PageHeader } from '@ema-platform/ui'; +import { EmptyState, ErrorState, ModalFooter, PageHeader } from '@ema-platform/ui'; import { authStorage, usePermissions } from '@ema-platform/auth'; import { PERMISSIONS } from '../../../layouts/nav-config'; @@ -529,7 +529,7 @@ export function CertificateDesignerPage() { 'Starts from the live design, or the built-in layout if this type has none.', )} - + @@ -550,7 +550,7 @@ export function CertificateDesignerPage() { > {t('designer.create', 'Create')} - + diff --git a/apps/backoffice/src/app/features/certification/pages/CertificationPage.tsx b/apps/backoffice/src/app/features/certification/pages/CertificationPage.tsx index a7ab26d0e..bd6163ae1 100644 --- a/apps/backoffice/src/app/features/certification/pages/CertificationPage.tsx +++ b/apps/backoffice/src/app/features/certification/pages/CertificationPage.tsx @@ -17,7 +17,7 @@ import { import { useDisclosure } from '@mantine/hooks'; import { useTranslation } from 'react-i18next'; import { IconEdit, IconTrash, IconPlus, IconInfoCircle, IconCertificate } from '@tabler/icons-react'; -import { notify, useErrorHandler, AdvancedTable, useServerTable, type AdvancedColumn } from '@ema-platform/ui'; +import { notify, useErrorHandler, AdvancedTable, useServerTable, ModalFooter, type AdvancedColumn } from '@ema-platform/ui'; import { useGetCertificationsQuery, useCreateCertificationMutation, @@ -199,10 +199,10 @@ export function CertificationPage() { {t('certification.deleteConfirmText', { name: deleteTarget?.name?.[locale] })} - + - + ); diff --git a/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx b/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx index 6f01c73a5..c8e624dd0 100644 --- a/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx +++ b/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx @@ -33,6 +33,7 @@ import { useErrorHandler, AdvancedTable, useServerTable, + ModalFooter, type AdvancedColumn, } from "@ema-platform/ui"; import { LocationPage } from "../../location/pages/LocationPage"; @@ -388,14 +389,14 @@ function ProfessionTab() { name: deleteTarget?.name?.[locale] ?? "", })} - + - + ); diff --git a/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx b/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx index ce77b3eef..2ad3478f2 100644 --- a/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx +++ b/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx @@ -40,7 +40,7 @@ import { IconX, IconDetails } from "@tabler/icons-react"; -import { notify, useErrorHandler } from "@ema-platform/ui"; +import { notify, useErrorHandler, ModalFooter } from "@ema-platform/ui"; import { useGetExamQuery, useUpdateExamMutation, @@ -494,14 +494,14 @@ export function ExamDetailPage() { mode="manual" actions={setWhatAction} /> - + - + ) : ( <> @@ -531,14 +531,14 @@ export function ExamDetailPage() { onChange={setDraftQuestions} mode="random" /> - + - + )} diff --git a/apps/backoffice/src/app/features/exam/pages/ExamPage.tsx b/apps/backoffice/src/app/features/exam/pages/ExamPage.tsx index 086ade3f7..f49686075 100644 --- a/apps/backoffice/src/app/features/exam/pages/ExamPage.tsx +++ b/apps/backoffice/src/app/features/exam/pages/ExamPage.tsx @@ -31,7 +31,7 @@ import { IconClipboardList, IconDetails, } from "@tabler/icons-react"; -import { notify, useErrorHandler, AdvancedTable, useServerTable, type AdvancedColumn } from "@ema-platform/ui"; +import { notify, useErrorHandler, AdvancedTable, useServerTable, ModalFooter, type AdvancedColumn } from "@ema-platform/ui"; import { useGetCertificationsQuery } from "../../certification/api/certification-api"; import { useGetExamsQuery, @@ -600,14 +600,14 @@ export function ExamPage() { name: deleteTarget?.title?.[locale] ?? "", })} - + - + ); diff --git a/apps/backoffice/src/app/features/license-review/components/DecisionConfirmModal.tsx b/apps/backoffice/src/app/features/license-review/components/DecisionConfirmModal.tsx index d9df085d5..2c34fc9cd 100644 --- a/apps/backoffice/src/app/features/license-review/components/DecisionConfirmModal.tsx +++ b/apps/backoffice/src/app/features/license-review/components/DecisionConfirmModal.tsx @@ -3,7 +3,6 @@ import { Alert, Button, Checkbox, - Group, Modal, Select, Stack, @@ -13,6 +12,7 @@ import { } from '@mantine/core'; import { IconAlertTriangle } from '@tabler/icons-react'; import { useTranslation } from 'react-i18next'; +import { ModalFooter } from '@ema-platform/ui'; import type { ResolvedAction } from '../config/actions'; /** Reason codes offered per action. Free text is always available too. */ @@ -290,7 +290,7 @@ export function DecisionConfirmModal({ /> )} - + @@ -310,7 +310,7 @@ export function DecisionConfirmModal({ > {t(action.labelKey)} - + ); diff --git a/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage.tsx b/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage.tsx index fe5713c3a..490e6a988 100644 --- a/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage.tsx +++ b/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage.tsx @@ -57,7 +57,7 @@ import { useScheduleInspectionMutation, type RemarkTargetType, } from '@ema-platform/api'; -import { ErrorState } from '@ema-platform/ui'; +import { ErrorState, ModalFooter } from '@ema-platform/ui'; import { usePermissions } from '@ema-platform/auth'; import { useAppSelector } from '../../../store/hooks'; import { DecisionBar } from '../components/DecisionBar'; @@ -894,7 +894,7 @@ export function LicenseReviewPage() { value={inspectionDate} onChange={(e) => setInspectionDate(e.currentTarget.value)} /> - + - + @@ -942,7 +942,7 @@ export function LicenseReviewPage() { autosize minRows={3} /> - + - + diff --git a/apps/backoffice/src/app/features/location/pages/LocationPage.tsx b/apps/backoffice/src/app/features/location/pages/LocationPage.tsx index 6e0fec706..0e2954a82 100644 --- a/apps/backoffice/src/app/features/location/pages/LocationPage.tsx +++ b/apps/backoffice/src/app/features/location/pages/LocationPage.tsx @@ -17,7 +17,7 @@ import { import { useDisclosure } from '@mantine/hooks'; import { IconSettings, IconMap, IconInfoCircle, IconPlus } from '@tabler/icons-react'; import { useTranslation } from 'react-i18next'; -import { notify, useErrorHandler } from '@ema-platform/ui'; +import { notify, useErrorHandler, ModalFooter } from '@ema-platform/ui'; import { LocationTree } from '../components/LocationTree'; import { LocationDetail } from '../components/LocationDetail'; import { LocationForm } from '../components/LocationForm'; @@ -226,14 +226,14 @@ export function LocationPage() { name: selectedLocation?.names[locale] ?? '', })} - + - + diff --git a/apps/backoffice/src/app/features/payment-config/pages/PaymentConfigPage.tsx b/apps/backoffice/src/app/features/payment-config/pages/PaymentConfigPage.tsx index 0f48fef41..4f425cfe7 100644 --- a/apps/backoffice/src/app/features/payment-config/pages/PaymentConfigPage.tsx +++ b/apps/backoffice/src/app/features/payment-config/pages/PaymentConfigPage.tsx @@ -26,7 +26,7 @@ import { IconInfoCircle, IconLock, } from '@tabler/icons-react'; -import { notify } from '@ema-platform/ui'; +import { notify, ModalFooter } from '@ema-platform/ui'; import { extractErrorMessage, localized, @@ -364,14 +364,14 @@ function FeeEditModal({ )} - + - + )} diff --git a/apps/backoffice/src/app/features/question/pages/QuestionPage.tsx b/apps/backoffice/src/app/features/question/pages/QuestionPage.tsx index 2e1dcaf8e..643d0fb50 100644 --- a/apps/backoffice/src/app/features/question/pages/QuestionPage.tsx +++ b/apps/backoffice/src/app/features/question/pages/QuestionPage.tsx @@ -18,7 +18,7 @@ import { import { useDisclosure } from '@mantine/hooks'; import { useTranslation } from 'react-i18next'; import { IconEdit, IconTrash, IconPlus, IconInfoCircle } from '@tabler/icons-react'; -import { notify, useErrorHandler, AdvancedTable, useServerTable, type AdvancedColumn } from '@ema-platform/ui'; +import { notify, useErrorHandler, AdvancedTable, useServerTable, ModalFooter, type AdvancedColumn } from '@ema-platform/ui'; import { useGetCertificationsQuery } from '../../certification/api/certification-api'; import { useGetQuestionsQuery, @@ -255,10 +255,10 @@ export function QuestionPage() { {t('question.deleteConfirmText')} - + - + ); diff --git a/apps/backoffice/src/app/features/result/components/RecordResultModal.tsx b/apps/backoffice/src/app/features/result/components/RecordResultModal.tsx index 34bed6acb..8b8335e02 100644 --- a/apps/backoffice/src/app/features/result/components/RecordResultModal.tsx +++ b/apps/backoffice/src/app/features/result/components/RecordResultModal.tsx @@ -17,7 +17,7 @@ import { Alert, } from '@mantine/core'; import { IconInfoCircle, IconCheck, IconX } from '@tabler/icons-react'; -import { notify, useErrorHandler } from '@ema-platform/ui'; +import { notify, useErrorHandler, ModalFooter } from '@ema-platform/ui'; import { useApiQuery } from '@ema-platform/api'; import { useCreateResultMutation, useUpdateResultMutation } from '../api/result-api'; import type { Exam } from '../../exam/types/exam'; @@ -195,12 +195,12 @@ export function RecordResultModal({ size="sm" /> - + - + )} diff --git a/apps/backoffice/src/app/features/result/pages/ResultPage.tsx b/apps/backoffice/src/app/features/result/pages/ResultPage.tsx index 927928202..162eb9aee 100644 --- a/apps/backoffice/src/app/features/result/pages/ResultPage.tsx +++ b/apps/backoffice/src/app/features/result/pages/ResultPage.tsx @@ -36,7 +36,7 @@ import { IconChartBar, IconSearch, } from '@tabler/icons-react'; -import { notify, BilingualInput, useErrorHandler, AdvancedTable, useServerTable, type AdvancedColumn } from '@ema-platform/ui'; +import { notify, BilingualInput, useErrorHandler, AdvancedTable, useServerTable, ModalFooter, type AdvancedColumn } from '@ema-platform/ui'; import type { BilingualValue } from '@ema-platform/ui'; import { useGetResultsQuery, useLazyGetResultQuery, useDeleteResultMutation, useUpdateResultMutation } from '../api/result-api'; import { useGetExamsQuery } from '../../exam/api/exam-api'; @@ -462,7 +462,7 @@ export function ResultPage() { )} - + - + ) : ( {t('result.noData')} @@ -481,10 +481,10 @@ export function ResultPage() { {t('result.deleteConfirmText')} - + - + {/* Choose exam, then record */} @@ -501,10 +501,10 @@ export function ResultPage() { searchable required /> - + - + diff --git a/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationFormBuilderPage.tsx b/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationFormBuilderPage.tsx index cb89cf001..37b9e3f97 100644 --- a/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationFormBuilderPage.tsx +++ b/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationFormBuilderPage.tsx @@ -30,7 +30,7 @@ import { IconSettings, IconTrash, } from '@tabler/icons-react'; -import { notify } from '@ema-platform/ui'; +import { notify, ModalFooter } from '@ema-platform/ui'; // --------------------------------------------------------------------------- // Types @@ -495,12 +495,12 @@ export function VesselRegistrationFormBuilderPage() { onChange={(e) => setDraftRequired(e.currentTarget.checked)} /> - + - + @@ -516,10 +516,10 @@ export function VesselRegistrationFormBuilderPage() { Are you sure you want to remove {deleteTarget?.label} from the form? This cannot be undone. - + - + diff --git a/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx b/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx index ede020f0c..6f265f1dc 100644 --- a/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx +++ b/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx @@ -49,6 +49,7 @@ import { type FieldErrors, type ValidationIssue, } from '@ema-platform/api'; +import { ModalFooter } from '@ema-platform/ui'; import { ConfigDrivenSection } from '../components/ConfigDrivenSection'; import { DocumentSlots } from '../components/DocumentSlots'; import { StaffEvidence } from '../components/StaffEvidence'; @@ -617,17 +618,19 @@ export function LicenseApplicationPage() { onChange={(v) => setNewStaff({ ...newStaff, yearsOfExperience: Number(v) || 0 })} min={0} /> - + + + diff --git a/apps/portal/src/app/features/profile/components/OperationsFormContent.tsx b/apps/portal/src/app/features/profile/components/OperationsFormContent.tsx index cc25ed0b8..a41668275 100644 --- a/apps/portal/src/app/features/profile/components/OperationsFormContent.tsx +++ b/apps/portal/src/app/features/profile/components/OperationsFormContent.tsx @@ -19,7 +19,7 @@ import { useGetMyOperatorTypesQuery, useUpdateMyOperatorTypesMutation, } from '@ema-platform/api'; -import { notify } from '@ema-platform/ui'; +import { notify, ModalFooter } from '@ema-platform/ui'; /** * The applicant's modes of operation — what they do, and therefore which @@ -199,7 +199,7 @@ export function OperationsFormContent({ Applications already filed carry on as they are, and licences already issued to you stay valid and can still be renewed. - + - + diff --git a/apps/portal/src/app/features/seafarer/pages/SeafarerProfilePage.tsx b/apps/portal/src/app/features/seafarer/pages/SeafarerProfilePage.tsx index 9fa5061d3..d226e6ce0 100644 --- a/apps/portal/src/app/features/seafarer/pages/SeafarerProfilePage.tsx +++ b/apps/portal/src/app/features/seafarer/pages/SeafarerProfilePage.tsx @@ -46,7 +46,7 @@ import { IconX, } from '@tabler/icons-react'; import { useNavigate, useParams } from 'react-router-dom'; -import { CountrySelect, notify, useErrorHandler } from '@ema-platform/ui'; +import { CountrySelect, notify, useErrorHandler, ModalFooter } from '@ema-platform/ui'; import type { Seafarer } from './SeafarerRegistryPage'; // --------------------------------------------------------------------------- @@ -479,10 +479,10 @@ function AddTrainingModal({ opened, onClose }: { opened: boolean; onClose: () =>