chore(dev): serve the portal on 3000 for the Fayda redirect

The Fayda redirect URI registered for local testing is
http://localhost:3000/callback, matched exactly by the provider, so the portal
has to be the thing listening there. Its dev and preview servers move from 4200
to 3000 and the API moves to 3001.

Every hardcoded fallback to http://localhost:3000/api follows — six copies of
the same default across libs/api, libs/auth, the portal and the backoffice —
otherwise a developer without a .env would have had the app calling itself.

e2e is unaffected: it binds its own ports (3011/4302/4303) explicitly.
This commit is contained in:
mihretue
2026-08-25 19:04:27 +00:00
parent 16a5dec734
commit baea9f2b57
11 changed files with 23 additions and 19 deletions

View File

@@ -12,8 +12,10 @@
# frontend never speaks to Fayda directly.
# Base URL of the emaapi backend, including the /api prefix.
# Defaults to http://localhost:3000/api when unset.
VITE_BASE_API_URL=http://localhost:3000/api
# 3001, not 3000: the portal itself takes 3000 in development, because that is
# the port in the Fayda redirect URI registered for local testing. Set PORT=3001
# in emaapi's .env to match.
VITE_BASE_API_URL=http://localhost:3001/api
# Serve fixture data instead of calling the API. Any value other than "true"
# uses the real backend.
@@ -23,7 +25,7 @@ VITE_USE_MOCKS=false
# Host ports published by docker-compose.yml. It also expects per-app env files
# at apps/portal/.env and apps/backoffice/.env, which can each be a copy of this
# file. Ignored when running the Vite dev servers, which serve the portal on
# 4200 and the backoffice on 4201.
# 3000 and the backoffice on 4201.
# EMA_PORTAL_PORT=8021
# EMA_BACKOFFICE_PORT=8022
@@ -32,7 +34,6 @@ VITE_USE_MOCKS=false
# page at /callback and /signup/fayda/callback, and whichever path is registered
# with Fayda must match the API's FAYDA_REDIRECT_URI exactly.
#
# The value being registered first is http://localhost:3000/callback, which is
# also emaapi's default port — so when testing that redirect, serve the portal
# on 3000 (`npx nx serve portal --port 3000`) and move the API elsewhere, or
# register a URI that points at the port the portal actually runs on.
# The value being registered first is http://localhost:3000/callback, so the
# portal's dev server now listens on 3000 and emaapi moves to 3001. Nothing
# extra to run — `nx serve portal` already binds the right port.

View File

@@ -3,7 +3,7 @@ import type { Bilingual, LicenseCategory, LicenseTemplate, LicenseType } from '@
/** Same resolution — and same fallback — the shared RTK Query baseQuery uses. */
export const API_BASE_URL =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3000/api';
'http://localhost:3001/api';
export const STATUS_COLOR: Record<LicenseTemplate['status'], string> = {
DRAFT: 'gray',

View File

@@ -17,7 +17,7 @@ export default defineConfig({
// port: 4201,
// proxy: {
// '/api': {
// target: 'http://localhost:3000', // change from 'https://ema-api-dev.triaplc.com'
// target: 'http://localhost:3001', // change from 'https://ema-api-dev.triaplc.com'
// changeOrigin: true,
// },
// },

View File

@@ -22,7 +22,7 @@ own ports, against its own database:
| Backoffice | 4303 | same |
| Database | — | `ema_e2e` |
This is deliberate. A developer's stack is usually already up on 3000/4200/4201,
This is deliberate. A developer's stack is usually already up on 3000/3001/4201,
and `dev/start.sh` **rewrites** `emaapi/apps/server/emaapi/.env` and the apps'
`.env.local` on every run — a suite that read those files would point at
whichever stack was started last. The API is launched with `DATABASE_NAME`,

View File

@@ -122,7 +122,7 @@ function formatDate(value: string | null | undefined): string {
const API_BASE =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3000/api';
'http://localhost:3001/api';
async function generateCertificate(profileId: string): Promise<Blob> {
const token = authStorage.getToken();

View File

@@ -9,17 +9,20 @@ export default defineConfig({
// built-in default.
envDir: '../../',
cacheDir: '../../node_modules/.vite/apps/portal',
server: { port: 4200, host: 'localhost' },
// 3000, not the usual 4200: the Fayda redirect URI registered for local
// testing is http://localhost:3000/callback, and the provider matches it
// exactly. The API moves to 3001 to make room.
server: { port: 3000, host: 'localhost' },
// server: {
// port: 4200,
// proxy: {
// '/api': {
// target: 'http://localhost:3000', // change from 'https://ema-api-dev.triaplc.com'
// target: 'http://localhost:3001', // change from 'https://ema-api-dev.triaplc.com'
// changeOrigin: true,
// },
// },
// },
preview: { port: 4200, host: 'localhost' },
preview: { port: 3000, host: 'localhost' },
plugins: [react(), nxViteTsPaths()],
resolve: {
dedupe: ['react', 'react-dom', 'react-router-dom', '@tanstack/react-query'],

View File

@@ -5,7 +5,7 @@ import { resolveSessionContext } from "../session";
export const BASE_API_URL =
(import.meta as { env?: Record<string, string> }).env?.[
"VITE_BASE_API_URL"
] ?? "http://localhost:3000/api";
] ?? "http://localhost:3001/api";
let _onTokenExpired: (() => Promise<string>) | null = null;
let _onAuthFailure: (() => void) | null = null;

View File

@@ -12,7 +12,7 @@ import type {
const BASE_API_URL =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3000/api';
'http://localhost:3001/api';
/**
* Uploads a document straight to the API.

View File

@@ -11,7 +11,7 @@ export const SESSION_HEADER_KEYS = {
* Which app this bundle is, so it reads its own session and no one else's.
*
* Set by each app's store via `configureSessionScope`. Cookies ignore the
* port, so `localhost:4200` and `localhost:4201` share one jar: without a
* port, so `localhost:3000` and `localhost:4201` share one jar: without a
* scope the backoffice would happily authenticate as whoever last signed into
* the portal, and render a staff console with an applicant's permissions.
*/

View File

@@ -8,7 +8,7 @@ import type { AuthUser } from '../types/auth.types';
const BASE_API_URL =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3000/api';
'http://localhost:3001/api';
/**
* Restores the signed-in session before the router renders.

View File

@@ -3,7 +3,7 @@ import { authStorage } from "./auth-storage";
const BASE_API_URL =
(import.meta as { env?: Record<string, string> }).env?.[
"VITE_BASE_API_URL"
] ?? "http://localhost:3000/api";
] ?? "http://localhost:3001/api";
interface RefreshResponse {
token: string;