Merge branch 'WorkflowChange' of https://github.com/Tria-plc/emaui into estif-branch-1

This commit is contained in:
Estifo77
2026-08-21 12:17:20 +03:00
6 changed files with 180 additions and 120 deletions

View File

@@ -33,14 +33,15 @@ jobs:
BUILD_ENV_FILE: ${{ matrix.build_env_file }}
DOCKER_BUILDKIT: "1"
COMPOSE_DOCKER_CLI_BUILD: "1"
ENV_MANAGER_TOKEN: ${{ secrets.ENV_MANAGER_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Sync environment from server
- name: Sync environment from Env Manager App
run: |
chmod +x scripts/deploy/*.sh
./scripts/deploy/sync-env-from-server.sh "${{ matrix.service }}"
./scripts/deploy/sync-env-from-env-manager.sh "${{ matrix.service }}"
- name: Set compose project name
run: |

View File

@@ -1,18 +1,19 @@
FROM node:24-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY local-packages/ ./local-packages/
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install --legacy-peer-deps
pnpm install --frozen-lockfile
FROM deps AS base
COPY . .
FROM base AS portal-build
RUN npm run build:portal
RUN pnpm run build:portal
FROM base AS backoffice-build
RUN npm run build:backoffice
RUN pnpm run build:backoffice
FROM nginx:1.29-alpine AS portal
COPY --from=portal-build /app/dist/apps/portal /usr/share/nginx/html

View File

@@ -30,7 +30,12 @@ import {
IconShieldCheck,
} from '@tabler/icons-react';
import { authStorage, useCurrentProfile } from '@ema-platform/auth';
import { useApiQuery } from '@ema-platform/api';
import {
extractErrorMessage,
useApiQuery,
useBypassPaymentMutation,
useGetPaymentCapabilitiesQuery,
} from '@ema-platform/api';
import {
useGetMySeaServiceRecordsQuery,
useGetMyMedicalCertificatesQuery,
@@ -146,11 +151,32 @@ export function CertificatesPage() {
const [previewTitle, setPreviewTitle] = useState('');
const [loading, setLoading] = useState(false);
const { pay, isPaying } = useApplicationPayment();
// Dev/test only — the API reports false in production and the button is
// never rendered. Same shortcut My Applications offers.
const { data: capabilities } = useGetPaymentCapabilitiesQuery();
const [bypassPayment, { isLoading: bypassing }] = useBypassPaymentMutation();
const { data } = useApiQuery<CertificatesOverview>({
const { data, refetch } = useApiQuery<CertificatesOverview>({
url: '/certificates/my',
method: 'GET',
});
const handleBypass = async (applicationId: string) => {
try {
const result = await bypassPayment(applicationId).unwrap();
notifications.show({
color: 'teal',
title: 'Payment bypassed',
message: result.certificateIssued
? 'The certificate has been issued.'
: `Application is now ${humanStatus(result.status)}.`,
});
// Generic query, not tag-driven: refresh it by hand.
refetch();
} catch (err) {
notifications.show({ color: 'red', title: 'Bypass failed', message: extractErrorMessage(err) });
}
};
const certificates = data?.certificates ?? [];
const applications = data?.applications ?? [];
@@ -331,6 +357,17 @@ export function CertificatesPage() {
Pay {app.feeAmount.toLocaleString()} {app.feeCurrency}
</Button>
)}
{app.feeAmount !== null && capabilities?.bypassEnabled && (
<Button
size="xs"
variant="default"
loading={bypassing}
onClick={() => handleBypass(app.applicationId)}
title="Testing only — marks the fee paid without a provider"
>
Bypass payment
</Button>
)}
<Text
fz="xs"
c="blue"

161
package-lock.json generated
View File

@@ -31,6 +31,7 @@
"i18n-nationality": "^1.4.0",
"i18next": "^25.6.0",
"js-cookie": "^3.0.8",
"libphonenumber-js": "^1.13.11",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.71.2",
@@ -44,6 +45,8 @@
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/eslintrc": "3.3.6",
"@eslint/js": "^10.0.1",
"@nx/eslint": "^22.5.4",
"@nx/eslint-plugin": "^22.5.4",
"@nx/react": "^22.5.4",
@@ -2799,16 +2802,24 @@
}
},
"node_modules/@eslint/js": {
"version": "9.39.5",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz",
"integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==",
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
"integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://eslint.org/donate"
},
"peerDependencies": {
"eslint": "^10.0.0"
},
"peerDependenciesMeta": {
"eslint": {
"optional": true
}
}
},
"node_modules/@eslint/object-schema": {
@@ -3858,9 +3869,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -3881,9 +3889,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -3904,9 +3909,6 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -3927,9 +3929,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -3950,9 +3949,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4013,9 +4009,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4299,9 +4292,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4316,9 +4306,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4333,9 +4320,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4350,9 +4334,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6512,9 +6493,6 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6528,9 +6506,6 @@
"cpu": [
"arm"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6544,9 +6519,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6560,9 +6532,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6576,9 +6545,6 @@
"cpu": [
"loong64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6592,9 +6558,6 @@
"cpu": [
"loong64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6608,9 +6571,6 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6624,9 +6584,6 @@
"cpu": [
"ppc64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6640,9 +6597,6 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6656,9 +6610,6 @@
"cpu": [
"riscv64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6672,9 +6623,6 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6688,9 +6636,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6704,9 +6649,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6846,9 +6788,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6863,9 +6802,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6880,9 +6816,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6897,9 +6830,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -7515,9 +7445,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -7534,9 +7461,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -7553,9 +7477,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -7572,9 +7493,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -8182,6 +8100,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 +11162,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"
@@ -11565,6 +11495,19 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/@eslint/js": {
"version": "9.39.5",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz",
"integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
}
},
"node_modules/eslint/node_modules/ajv": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
@@ -12878,7 +12821,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"
@@ -13484,6 +13427,12 @@
"node": ">= 0.8.0"
}
},
"node_modules/libphonenumber-js": {
"version": "1.13.11",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.11.tgz",
"integrity": "sha512-ETER2kMaIFTI/Nh1a8Gk03dUF/SL0VZqtI+CcVHZxp5WIHYwNS7S+uiYZDYCvLy3lOR4/DAD5jf0h5WkePPpqg==",
"license": "MIT"
},
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
@@ -13620,9 +13569,6 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -13643,9 +13589,6 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -13666,9 +13609,6 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -13689,9 +13629,6 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -17072,7 +17009,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": {

View File

@@ -1,3 +1,7 @@
packages:
- 'apps/*'
- 'libs/*'
allowBuilds:
canvas: set this to true or false
core-js: set this to true or false

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# Sync .env files from the Env Manager API into the repo.
#
# Usage:
# ENV_MANAGER_TOKEN=xxx ./scripts/deploy/sync-env-from-server.sh freight-api freight-portal freight-backoffice
#
# You normally only need to pass the token via the action secret, and BRANCH
# via the job-level env (e.g. `BRANCH: ${{ github.ref_name }}` in the workflow):
# env:
# BRANCH: ${{ github.ref_name }}
# ENV_MANAGER_TOKEN: ${{ secrets.ENV_MANAGER_TOKEN }}
#
# API layout (one endpoint per service):
# GET https://env.smart.aaca.gov.et/api/env/edr/<branch>/<service>?format=dotenv
# Header: Authorization: Bearer <ENV_MANAGER_TOKEN>
set -euo pipefail
PROJECT="ema"
ENV_MANAGER_URL="https://env.smart.aaca.gov.et"
BRANCH="${BRANCH:?BRANCH is required}"
ENV_MANAGER_TOKEN="${ENV_MANAGER_TOKEN:?ENV_MANAGER_TOKEN is required}"
declare -A SERVICE_ENV_TARGET=(
["ema_portal"]="apps/portal/.env"
["ema_backoffice"]="apps/backoffice/.env"
)
for service in "$@"; do
branch_api_name="${BRANCH//-/_}"
service_api_name="${service//-/_}"
dest="${SERVICE_ENV_TARGET[${service_api_name}]:-}"
if [[ -z "${dest}" ]]; then
echo "Unknown service: ${service}" >&2
exit 1
fi
url="${ENV_MANAGER_URL}/api/env/${PROJECT}/${branch_api_name}/${service_api_name}?format=dotenv"
mkdir -p "$(dirname "${dest}")"
tmp_file="$(mktemp)"
trap 'rm -f "${tmp_file}"' RETURN 2>/dev/null || true
http_status=$(curl -fsS -o "${tmp_file}" -w "%{http_code}" \
-H "Authorization: Bearer ${ENV_MANAGER_TOKEN}" \
"${url}") || {
echo "Failed to fetch env for '${service}' from ${url}" >&2
rm -f "${tmp_file}"
exit 1
}
if [[ "${http_status}" != "200" ]]; then
echo "Env Manager returned HTTP ${http_status} for '${service}' (${url})" >&2
rm -f "${tmp_file}"
exit 1
fi
if [[ ! -s "${tmp_file}" ]]; then
echo "Env Manager returned an empty response for '${service}' (${url})" >&2
rm -f "${tmp_file}"
exit 1
fi
mv "${tmp_file}" "${dest}"
echo "Synced ${url} -> ${dest}"
port_value=$(sed -n -E 's/^[[:space:]]*PORT[[:space:]]*=[[:space:]]*"?([^"#]+)"?[[:space:]]*(#.*)?$/\1/p' "${dest}" | head -n1 | tr -d '[:space:]')
if [[ -z "${port_value}" ]]; then
echo "Missing required PORT in env file for '${service}' (${dest})" >&2
exit 1
fi
if [[ -n "${GITHUB_ENV:-}" ]]; then
service_var=$(echo "${service}" | tr '[:lower:]-' '[:upper:]_')
echo "${service_var}_PORT=${port_value}" >> "${GITHUB_ENV}"
echo "Exported ${service_var}_PORT from ${dest}"
# Forward NEXT_PUBLIC_* and VITE_* vars so docker compose build can inject them as build args.
grep -E '^[[:space:]]*(NEXT_PUBLIC_|VITE_)[A-Za-z0-9_]+=' "${dest}" \
| sed -E 's/^[[:space:]]*//' >> "${GITHUB_ENV}" || true
fi
done