diff --git a/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx b/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx index c34e5975b..036d5f946 100644 --- a/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx +++ b/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx @@ -77,21 +77,23 @@ function ExamForm({ editing?.cuttingPoint ?? 0, ); const [status, setStatus] = useState(editing?.status ?? null); + const [activeTab, setActiveTab] = useState("basic"); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - if ( - !certificationId || - !titleEn || - !titleAm || - !date || - !type || - !form || - !venue || - !adminMethod || - !evalMethod - ) { - notify.error("Please fill all required fields"); + if (!certificationId || !titleEn || !titleAm || !date || !venue) { + setActiveTab("basic"); + notify.error(t("exam.form.fillRequiredBasic")); + return; + } + if ((directionEn || directionAm) && !(directionEn && directionAm)) { + setActiveTab("basic"); + notify.error(t("exam.form.directionBothLanguages")); + return; + } + if (!type || !form || !adminMethod || !evalMethod || !cuttingPoint) { + setActiveTab("settings"); + notify.error(t("exam.form.fillRequiredSettings")); return; } onSubmit( @@ -121,7 +123,7 @@ function ExamForm({ return (
- + }> {t("exam.form.basicInfo")} @@ -296,7 +298,7 @@ function ExamForm({ onChange={(v) => setCuttingPoint(Number(v))} min={0} size="sm" - required + withAsterisk /> {editing && ( diff --git a/apps/backoffice/src/app/i18n/locales/am.ts b/apps/backoffice/src/app/i18n/locales/am.ts index bc1c4e62e..ec586b2bd 100644 --- a/apps/backoffice/src/app/i18n/locales/am.ts +++ b/apps/backoffice/src/app/i18n/locales/am.ts @@ -257,6 +257,9 @@ export const am: Translations = { random: "በዘፈቀደ", cuttingPoint: "የማለፊያ ነጥብ", cuttingPointPlaceholder: "ለማለፍ ዝቅተኛ ነጥብ", + fillRequiredBasic: "በመሠረታዊ መረጃ ውስጥ ያሉ አስፈላጊ መስኮችን ይሙሉ።", + fillRequiredSettings: "በቅንብሮች ውስጥ ያሉ አስፈላጊ መስኮችን ይሙሉ — ዓይነት፣ ቅጽ፣ የአስተዳደር ዘዴ፣ የግምገማ ዘዴ እና የማለፊያ ነጥብ።", + directionBothLanguages: "መመሪያ በሁለቱም እንግሊዝኛ እና አማርኛ ጽሑፍ ያስፈልገዋል፣ ወይም ሁለቱንም ባዶ ይተዉ።", status: "ሁኔታ", statusPlaceholder: "የፈተና ሁኔታ", pending: "በመጠባበቅ ላይ", diff --git a/apps/backoffice/src/app/i18n/locales/en.ts b/apps/backoffice/src/app/i18n/locales/en.ts index 1fcdf661b..5f4b5f902 100644 --- a/apps/backoffice/src/app/i18n/locales/en.ts +++ b/apps/backoffice/src/app/i18n/locales/en.ts @@ -255,6 +255,9 @@ export const en = { random: 'Random', cuttingPoint: 'Cutting Point (Pass Mark)', cuttingPointPlaceholder: 'Minimum score to pass', + fillRequiredBasic: 'Please fill all required fields in Basic Info.', + fillRequiredSettings: 'Please fill all required fields in Settings — type, form, administration method, evaluation method, and cutting point.', + directionBothLanguages: 'Direction needs text in both English and Amharic, or leave both empty.', status: 'Status', statusPlaceholder: 'Exam status', pending: 'Pending', diff --git a/libs/api/src/lib/base-api/base-query-with-reauth.ts b/libs/api/src/lib/base-api/base-query-with-reauth.ts index 3f9d6a094..127159579 100644 --- a/libs/api/src/lib/base-api/base-query-with-reauth.ts +++ b/libs/api/src/lib/base-api/base-query-with-reauth.ts @@ -1,10 +1,11 @@ -import { fetchBaseQuery, type BaseQueryFn } from '@reduxjs/toolkit/query/react'; -import type { FetchArgs, FetchBaseQueryError } from '@reduxjs/toolkit/query'; -import { resolveSessionContext } from '../session'; +import { fetchBaseQuery, type BaseQueryFn } from "@reduxjs/toolkit/query/react"; +import type { FetchArgs, FetchBaseQueryError } from "@reduxjs/toolkit/query"; +import { resolveSessionContext } from "../session"; export const BASE_API_URL = - (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? - 'http://localhost:3001/api'; + (import.meta as { env?: Record }).env?.[ + "VITE_BASE_API_URL" + ] ?? "http://localhost:3000/api"; let _onTokenExpired: (() => Promise) | null = null; let _onAuthFailure: (() => void) | null = null; @@ -28,7 +29,7 @@ export const baseQueryWithReauth: BaseQueryFn< const { token, sessionHeaders } = resolveSessionContext( api.getState() as { auth?: { token?: string } }, ); - if (token) headers.set('Authorization', `Bearer ${token}`); + if (token) headers.set("Authorization", `Bearer ${token}`); Object.entries(sessionHeaders).forEach(([k, v]) => headers.set(k, v)); return headers; }, diff --git a/libs/api/src/lib/features/licensing/licensing.helpers.ts b/libs/api/src/lib/features/licensing/licensing.helpers.ts index 50aa8f5a8..bd11847ab 100644 --- a/libs/api/src/lib/features/licensing/licensing.helpers.ts +++ b/libs/api/src/lib/features/licensing/licensing.helpers.ts @@ -1,15 +1,16 @@ -import { resolveTokenFromStorage } from '../../session'; +import { resolveTokenFromStorage } from "../../session"; import type { Bilingual, FormSectionConfig, LicenseApplication, LicenseStatus, ValidationIssue, -} from './licensing.types'; +} from "./licensing.types"; const BASE_API_URL = - (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? - 'http://localhost:3001/api'; + (import.meta as { env?: Record }).env?.[ + "VITE_BASE_API_URL" + ] ?? "http://localhost:3000/api"; /** * Uploads a document straight to the API. @@ -20,12 +21,12 @@ const BASE_API_URL = */ export async function uploadDocument(params: { ownerType: - | 'APPLICATION' - | 'APPLICATION_STAFF' - | 'INSPECTION' - | 'LICENSE' - | 'SEA_SERVICE_RECORD' - | 'MEDICAL_CERTIFICATE'; + | "APPLICATION" + | "APPLICATION_STAFF" + | "INSPECTION" + | "LICENSE" + | "SEA_SERVICE_RECORD" + | "MEDICAL_CERTIFICATE"; ownerId: string; documentKey: string; file: File; @@ -34,17 +35,17 @@ export async function uploadDocument(params: { validTo?: string; }): Promise<{ ok: true } | { ok: false; error: string }> { const form = new FormData(); - form.append('ownerType', params.ownerType); - form.append('ownerId', params.ownerId); - form.append('documentKey', params.documentKey); - if (params.title) form.append('title', params.title); - if (params.validFrom) form.append('validFrom', params.validFrom); - if (params.validTo) form.append('validTo', params.validTo); - form.append('files', params.file, params.file.name); + form.append("ownerType", params.ownerType); + form.append("ownerId", params.ownerId); + form.append("documentKey", params.documentKey); + if (params.title) form.append("title", params.title); + if (params.validFrom) form.append("validFrom", params.validFrom); + if (params.validTo) form.append("validTo", params.validTo); + form.append("files", params.file, params.file.name); const token = resolveTokenFromStorage(); const res = await fetch(`${BASE_API_URL}/attachments`, { - method: 'POST', + method: "POST", headers: token ? { Authorization: `Bearer ${token}` } : {}, body: form, }); @@ -55,40 +56,40 @@ export async function uploadDocument(params: { /** Human label for a status, in the vocabulary the user stories use. */ export const STATUS_LABELS: Record = { - DRAFT: 'Draft', - SUBMITTED: 'Submitted', - UNDER_REVIEW: 'Under Review', - UNDER_EVALUATION: 'Under Evaluation', - RESUBMIT_REQUIRED: 'Resubmit Required', - INSPECTION_PENDING: 'Inspection Pending', - INSPECTION_COMPLETED: 'Inspection Completed', - APPROVED: 'Approved', - REJECTED: 'Rejected', - ON_HOLD: 'On Hold', - PAYMENT_PENDING: 'Payment Pending', - PAID: 'Paid', - PAYMENT_CONFIRMED: 'Preparing Certificate', - CERTIFICATE_ISSUED: 'Certificate Issued', - COMPLETED: 'Completed', + DRAFT: "Draft", + SUBMITTED: "Submitted", + UNDER_REVIEW: "Under Review", + UNDER_EVALUATION: "Under Evaluation", + RESUBMIT_REQUIRED: "Resubmit Required", + INSPECTION_PENDING: "Inspection Pending", + INSPECTION_COMPLETED: "Inspection Completed", + APPROVED: "Approved", + REJECTED: "Rejected", + ON_HOLD: "On Hold", + PAYMENT_PENDING: "Payment Pending", + PAID: "Paid", + PAYMENT_CONFIRMED: "Preparing Certificate", + CERTIFICATE_ISSUED: "Certificate Issued", + COMPLETED: "Completed", }; /** Mantine colour per status — green progresses, orange needs the applicant. */ export const STATUS_COLORS: Record = { - DRAFT: 'gray', - SUBMITTED: 'blue', - UNDER_REVIEW: 'indigo', - UNDER_EVALUATION: 'indigo', - RESUBMIT_REQUIRED: 'orange', - INSPECTION_PENDING: 'cyan', - INSPECTION_COMPLETED: 'cyan', - APPROVED: 'teal', - REJECTED: 'red', - ON_HOLD: 'gray', - PAYMENT_PENDING: 'yellow', - PAID: 'lime', - PAYMENT_CONFIRMED: 'teal', - CERTIFICATE_ISSUED: 'green', - COMPLETED: 'green', + DRAFT: "gray", + SUBMITTED: "blue", + UNDER_REVIEW: "indigo", + UNDER_EVALUATION: "indigo", + RESUBMIT_REQUIRED: "orange", + INSPECTION_PENDING: "cyan", + INSPECTION_COMPLETED: "cyan", + APPROVED: "teal", + REJECTED: "red", + ON_HOLD: "gray", + PAYMENT_PENDING: "yellow", + PAID: "lime", + PAYMENT_CONFIRMED: "teal", + CERTIFICATE_ISSUED: "green", + COMPLETED: "green", }; /** @@ -118,46 +119,59 @@ export const STATUS_PROGRESS: Record = { /** Statuses where nothing moves until the applicant does something. */ export const APPLICANT_ACTION_STATUSES: LicenseStatus[] = [ - 'DRAFT', - 'RESUBMIT_REQUIRED', - 'PAYMENT_PENDING', + "DRAFT", + "RESUBMIT_REQUIRED", + "PAYMENT_PENDING", ]; /** Statuses that are finished, whichever way they went. */ export const TERMINAL_STATUSES: LicenseStatus[] = [ - 'CERTIFICATE_ISSUED', - 'COMPLETED', - 'REJECTED', + "CERTIFICATE_ISSUED", + "COMPLETED", + "REJECTED", ]; /** Licence types with no `companyName` — these are filed by a person, not a * business, so display falls back to the applicant name captured in the form. */ export const APPLICANT_NAME_TYPE_KEYS = [ - 'SEAFARER_REGISTRATION', - 'CERTIFICATE_OF_COMPETENCY', - 'CERTIFICATE_OF_PROFICIENCY', - 'VESSEL_REGISTRATION', - 'VESSEL_OWNERSHIP_TRANSFER', - 'ENDORSEMENT_COC', - 'ENDORSEMENT_GOC', + "SEAFARER_REGISTRATION", + "CERTIFICATE_OF_COMPETENCY", + "CERTIFICATE_OF_PROFICIENCY", + "VESSEL_REGISTRATION", + "VESSEL_OWNERSHIP_TRANSFER", + "ENDORSEMENT_COC", + "ENDORSEMENT_GOC", ]; /** Company name, or applicant name for licence types that have no company. */ -export function applicantOrCompanyName(app: LicenseApplication): string | undefined { - if (!app.licenseType?.key || !APPLICANT_NAME_TYPE_KEYS.includes(app.licenseType.key)) { +export function applicantOrCompanyName( + app: LicenseApplication, +): string | undefined { + if ( + !app.licenseType?.key || + !APPLICANT_NAME_TYPE_KEYS.includes(app.licenseType.key) + ) { return app.companyName ?? undefined; } - const applicantName = (app.formData?.account as Record | undefined) - ?.applicantName; - return typeof applicantName === 'string' && applicantName ? applicantName : undefined; + const applicantName = ( + app.formData?.account as Record | undefined + )?.applicantName; + return typeof applicantName === "string" && applicantName + ? applicantName + : undefined; } /** Reads a bilingual value for the active language, falling back to English. */ -export function localized(value: Bilingual | undefined, language = 'en'): string { - if (!value) return ''; +export function localized( + value: Bilingual | undefined, + language = "en", +): string { + if (!value) return ""; // `||` not `??`: an empty Amharic string is "not translated", not a value — // editors save `am: ''` freely (ExamPage, CertificationPage, LocationForm). - return (language === 'am' ? value.am : value.en) || value.en || value.am || ''; + return ( + (language === "am" ? value.am : value.en) || value.en || value.am || "" + ); } /** @@ -167,7 +181,7 @@ export function localized(value: Bilingual | undefined, language = 'en'): string export function extractValidationIssues(error: unknown): ValidationIssue[] { const data = (error as { data?: Record })?.data; if (!data) return []; - const issues = data['issues']; + const issues = data["issues"]; return Array.isArray(issues) ? (issues as ValidationIssue[]) : []; } @@ -179,47 +193,54 @@ export function extractValidationIssues(error: unknown): ValidationIssue[] { */ const ERROR_MESSAGES: Record = { capital_verification_required: - 'Record the verified capital before approving — it must be checked against the bank letter.', + "Record the verified capital before approving — it must be checked against the bank letter.", capital_below_threshold: - 'The verified capital is below the minimum required for this licence type.', + "The verified capital is below the minimum required for this licence type.", inspection_required_before_approval: - 'A physical inspection must be recorded before this application can be approved.', - application_already_claimed: 'Another officer has already claimed this application.', + "A physical inspection must be recorded before this application can be approved.", + application_already_claimed: + "Another officer has already claimed this application.", assigned_to_another_officer: - 'This application is assigned to another officer, so only they can act on it.', - not_application_owner: 'This application belongs to another applicant.', - application_incomplete: 'The application is missing required information.', - unresolved_remarks: 'Every requested correction must be addressed first.', - adjustment_items_required: 'Flag at least one item before requesting an adjustment.', - remark_text_required: 'Each flagged item needs a remark explaining what to fix.', - rejection_reason_required: 'A rejection reason is required.', + "This application is assigned to another officer, so only they can act on it.", + not_application_owner: "This application belongs to another applicant.", + application_incomplete: "The application is missing required information.", + unresolved_remarks: "Every requested correction must be addressed first.", + adjustment_items_required: + "Flag at least one item before requesting an adjustment.", + remark_text_required: + "Each flagged item needs a remark explaining what to fix.", + rejection_reason_required: "A rejection reason is required.", section_not_flagged_for_adjustment: - 'Only the sections the officer flagged can be changed in this round.', - application_not_editable: 'This application can no longer be edited.', - invalid_transition: 'That action is not available at this stage.', + "Only the sections the officer flagged can be changed in this round.", + application_not_editable: "This application can no longer be edited.", + invalid_transition: "That action is not available at this stage.", inspection_not_required_use_final_approve: - 'This licence type needs no inspection — approve it directly.', + "This licence type needs no inspection — approve it directly.", application_not_awaiting_inspection: - 'This application is not waiting for an inspection.', - inspection_already_completed: 'This inspection has already been recorded.', - license_type_inactive: 'This licence type is not currently accepting applications.', + "This application is not waiting for an inspection.", + inspection_already_completed: "This inspection has already been recorded.", + license_type_inactive: + "This licence type is not currently accepting applications.", }; -export function extractErrorMessage(error: unknown, fallback = 'Something went wrong'): string { +export function extractErrorMessage( + error: unknown, + fallback = "Something went wrong", +): string { const data = (error as { data?: Record })?.data; - const raw = data?.['message']; + const raw = data?.["message"]; // The filter passes structured errors through as `{ message: { message } }`. const key = - typeof raw === 'string' + typeof raw === "string" ? raw - : typeof (raw as { message?: unknown })?.message === 'string' - ? ((raw as { message: string }).message) + : typeof (raw as { message?: unknown })?.message === "string" + ? (raw as { message: string }).message : undefined; if (key && ERROR_MESSAGES[key]) return ERROR_MESSAGES[key]; - if (typeof raw === 'string') return raw; - if (Array.isArray(raw)) return raw.join(', '); + if (typeof raw === "string") return raw; + if (Array.isArray(raw)) return raw.join(", "); return fallback; } @@ -227,7 +248,7 @@ export function extractErrorMessage(error: unknown, fallback = 'Something went w export interface WizardStep { key: string; label: string; - kind: 'sections' | 'staff' | 'documents' | 'review'; + kind: "sections" | "staff" | "documents" | "review"; sections: FormSectionConfig[]; } @@ -266,7 +287,7 @@ export function buildWizardSteps( steps.push({ key: `section:${section.key}`, label: localized(section.title, options?.language), - kind: 'sections', + kind: "sections", sections: [section], }); continue; @@ -279,7 +300,7 @@ export function buildWizardSteps( const step: WizardStep = { key: `group:${group}`, label: group, - kind: 'sections', + kind: "sections", sections: [section], }; byGroup.set(group, step); @@ -295,7 +316,7 @@ export function buildWizardSteps( // beside the summary rather than earning a step of their own. It is what // keeps a one-checkbox declaration from costing a whole page. const isReviewGroup = (step: WizardStep) => - step.sections.some((s) => s.group?.trim().toLowerCase() === 'review'); + step.sections.some((s) => s.group?.trim().toLowerCase() === "review"); const reviewSections = steps.filter(isReviewGroup).flatMap((s) => s.sections); const formSteps = steps.filter((step) => !isReviewGroup(step)); @@ -303,9 +324,21 @@ export function buildWizardSteps( ...formSteps, ...(options?.hasStaff === false ? [] - : [{ key: 'staff', label: 'Staff', kind: 'staff', sections: [] } as WizardStep]), - { key: 'documents', label: 'Documents', kind: 'documents', sections: [] }, - { key: 'review', label: 'Review', kind: 'review', sections: reviewSections }, + : [ + { + key: "staff", + label: "Staff", + kind: "staff", + sections: [], + } as WizardStep, + ]), + { key: "documents", label: "Documents", kind: "documents", sections: [] }, + { + key: "review", + label: "Review", + kind: "review", + sections: reviewSections, + }, ]; } @@ -320,7 +353,7 @@ export type FieldErrors = Record; export function validateSections( sections: FormSectionConfig[], formData: Record>, - language = 'en', + language = "en", ): FieldErrors { const errors: FieldErrors = {}; @@ -337,13 +370,13 @@ export function validateSections( const empty = value === undefined || value === null || - (typeof value === 'string' && value.trim() === '') || - (typeof value === 'boolean' && value === false) || + (typeof value === "string" && value.trim() === "") || + (typeof value === "boolean" && value === false) || (Array.isArray(value) && value.length === 0); if (field.required && empty) { errors[`${section.key}.${field.key}`] = - field.type === 'BOOLEAN' + field.type === "BOOLEAN" ? `${localized(field.label, language)} must be accepted` : `${localized(field.label, language)} is required`; continue; @@ -368,23 +401,33 @@ export function validateSections( /** Evaluates a config condition against the current form answers. */ export function conditionHolds( condition: - | { field: string; equals?: unknown; notEquals?: unknown; in?: (string | number)[]; isSet?: boolean } + | { + field: string; + equals?: unknown; + notEquals?: unknown; + in?: (string | number)[]; + isSet?: boolean; + } | undefined | null, formData: Record>, ): boolean { if (!condition?.field) return true; const value = condition.field - .split('.') + .split(".") .reduce( (acc, key) => - acc && typeof acc === 'object' ? (acc as Record)[key] : undefined, + acc && typeof acc === "object" + ? (acc as Record)[key] + : undefined, formData as unknown, ); - const isEmpty = value === undefined || value === null || value === ''; - if (condition.isSet !== undefined) return condition.isSet ? !isEmpty : isEmpty; + const isEmpty = value === undefined || value === null || value === ""; + if (condition.isSet !== undefined) + return condition.isSet ? !isEmpty : isEmpty; if (condition.equals !== undefined) return value === condition.equals; if (condition.notEquals !== undefined) return value !== condition.notEquals; - if (condition.in !== undefined) return condition.in.includes(value as string | number); + if (condition.in !== undefined) + return condition.in.includes(value as string | number); return true; } diff --git a/libs/auth/src/lib/components/AuthBootstrap.tsx b/libs/auth/src/lib/components/AuthBootstrap.tsx index 769864f6f..da68906c7 100644 --- a/libs/auth/src/lib/components/AuthBootstrap.tsx +++ b/libs/auth/src/lib/components/AuthBootstrap.tsx @@ -1,12 +1,13 @@ -import { useEffect, useState, type ReactNode } from 'react'; -import { useDispatch } from 'react-redux'; -import { authStorage } from '../utils/auth-storage'; -import { hydrateAuth, logout, setUser } from '../store/auth.slice'; -import type { AuthUser } from '../types/auth.types'; +import { useEffect, useState, type ReactNode } from "react"; +import { useDispatch } from "react-redux"; +import { authStorage } from "../utils/auth-storage"; +import { hydrateAuth, logout, setUser } from "../store/auth.slice"; +import type { AuthUser } from "../types/auth.types"; const BASE_API_URL = - (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? - 'http://localhost:3001/api'; + (import.meta as { env?: Record }).env?.[ + "VITE_BASE_API_URL" + ] ?? "http://localhost:3000/api"; /** * Restores the signed-in session before the router renders. diff --git a/libs/auth/src/lib/utils/refresh-token.ts b/libs/auth/src/lib/utils/refresh-token.ts index 6a6690b80..0ecef257b 100644 --- a/libs/auth/src/lib/utils/refresh-token.ts +++ b/libs/auth/src/lib/utils/refresh-token.ts @@ -3,7 +3,7 @@ import { authStorage } from "./auth-storage"; const BASE_API_URL = (import.meta as { env?: Record }).env?.[ "VITE_BASE_API_URL" - ] ?? "http://localhost:3001/api"; + ] ?? "http://localhost:3000/api"; interface RefreshResponse { token: string; @@ -17,8 +17,8 @@ export async function refreshAccessToken(): Promise { // The IAM service exposes this as `refresh-token`; posting to `/auth/refresh` // 404s, which the catch below turns into a silent logout. const response = await fetch(`${BASE_API_URL}/auth/refresh-token`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refreshToken }), }); diff --git a/package-lock.json b/package-lock.json index 05e109a97..8c1da012f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3858,9 +3858,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3881,9 +3878,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3904,9 +3898,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3927,9 +3918,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3950,9 +3938,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4013,9 +3998,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4299,9 +4281,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4316,9 +4295,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4333,9 +4309,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4350,9 +4323,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6512,9 +6482,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6528,9 +6495,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6544,9 +6508,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6560,9 +6521,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6576,9 +6534,6 @@ "cpu": [ "loong64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6592,9 +6547,6 @@ "cpu": [ "loong64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6608,9 +6560,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6624,9 +6573,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6640,9 +6586,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6656,9 +6599,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6672,9 +6612,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6688,9 +6625,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6704,9 +6638,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6846,9 +6777,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6863,9 +6791,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6880,9 +6805,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6897,9 +6819,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7515,9 +7434,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7534,9 +7450,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7553,9 +7466,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7572,9 +7482,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8182,6 +8089,18 @@ "undici-types": "~7.18.0" } }, + "node_modules/@tria-plc/iamui/node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, "node_modules/@tria-plc/iamui/node_modules/date-fns": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", @@ -11232,7 +11151,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "iconv-lite": "^0.6.2" @@ -12878,7 +12797,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -13620,9 +13539,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -13643,9 +13559,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -13666,9 +13579,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -13689,9 +13599,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -17072,7 +16979,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/sax": {