diff --git a/.docker/production/Dockerfile.gha b/.docker/production/Dockerfile.gha index f71c2a1..fcd8fee 100644 --- a/.docker/production/Dockerfile.gha +++ b/.docker/production/Dockerfile.gha @@ -15,7 +15,13 @@ ENV BUNDLER_VERSION=$BUNDLER_VERSION # Only install what's needed that isn't in the base image # https://github.com/docker-library/ruby/blob/master/2.7/slim-buster/Dockerfile -RUN apt-get update \ +# Buster is EOL; point apt sources to the Debian archive and disable +# Release file validity checks so updates/installations work. +RUN sed -i \ + -e 's|deb.debian.org/debian|archive.debian.org/debian|g' \ + -e 's|security.debian.org/debian-security|archive.debian.org/debian-security|g' \ + /etc/apt/sources.list \ + && apt-get -o Acquire::Check-Valid-Until=false update \ && apt-get -yq dist-upgrade \ && apt-get install -y \ fontconfig \ diff --git a/clients/html/.node-version b/clients/html/.node-version new file mode 100644 index 0000000..b5e8f5c --- /dev/null +++ b/clients/html/.node-version @@ -0,0 +1 @@ +24.6.0 diff --git a/clients/html/.npmrc b/clients/html/.npmrc new file mode 100644 index 0000000..1057027 --- /dev/null +++ b/clients/html/.npmrc @@ -0,0 +1,4 @@ +engine-strict=true +save-exact=true +update-notifier=false +fund=false diff --git a/clients/html/FILE_REPLACEMENTS.md b/clients/html/FILE_REPLACEMENTS.md new file mode 100644 index 0000000..589aa52 --- /dev/null +++ b/clients/html/FILE_REPLACEMENTS.md @@ -0,0 +1,105 @@ +### File replacements by Angular build configuration + +This note summarizes current file replacements defined in `angular.json` and the simplified approach using a centralized brand config. + +#### Current simplified replacements + +- **me** + - `src/app/brand.config.ts` → `src/app/brand.config.me.ts` +- **ma** (default) + - `src/app/brand.config.ts` → `src/app/brand.config.ma.ts` +- **dc** + - `src/app/brand.config.ts` → `src/app/brand.config.dc.ts` +- **me-production** + - `src/environments/environment.ts` → `src/environments/environment.prod.ts` + - `src/app/brand.config.ts` → `src/app/brand.config.me.ts` +- **ma-production** + - `src/environments/environment.ts` → `src/environments/environment.prod.ts` + - `src/app/brand.config.ts` → `src/app/brand.config.ma.ts` +- **dc-production** + - `src/environments/environment.ts` → `src/environments/environment.prod.ts` + - `src/app/brand.config.ts` → `src/app/brand.config.dc.ts` + +#### Using configuration flags + +Build commands select replacements via `--configuration`: + +- `ng serve --configuration=me` +- `ng build --configuration=me` +- `ng serve --configuration=dc` +- `ng build --configuration=me-production` +- `ng build --configuration=dc-production` + +#### Brand configuration files + +Each brand has its own config file that exports the same `BRAND_CONFIG` constant: + +**`src/app/brand.config.ts`** (default - MA) + +```typescript +export const BRAND_CONFIG: BrandConfig = { + logoPath: 'assets/images/mhc_logo.svg', + faviconPath: 'assets/images/favicon.png', + rosterTemplatePath: 'assets/roster_upload_template.xlsx', + zipCodeDataPath: 'data/zipCode.json', +}; +``` + +**`src/app/brand.config.me.ts`** (Maine) + +```typescript +export const BRAND_CONFIG: BrandConfig = { + logoPath: 'assets/images/logo_me.svg', + faviconPath: 'assets/images/favicon_me.png', + rosterTemplatePath: 'assets/roster_upload_template_me.xlsx', + zipCodeDataPath: 'data/zipCodeME.json', +}; +``` + +**`src/app/brand.config.dc.ts`** (District of Columbia) + +```typescript +export const BRAND_CONFIG: BrandConfig = { + logoPath: 'assets/images/mhc_logo.svg', // Keep default for now + faviconPath: 'assets/images/favicon.png', // Keep default for now + rosterTemplatePath: 'assets/roster_upload_template.xlsx', // Keep default for now + zipCodeDataPath: 'data/zipCode.json', // Keep default for now +}; +``` + +#### Implementation + +Components now reference `BRAND_CONFIG` instead of hardcoded paths: + +- `HeaderComponent` uses `BRAND_CONFIG.logoPath` +- `EmployerDetailsComponent` exposes `brand = BRAND_CONFIG` +- Template binds roster template link to `brand.rosterTemplatePath` + +#### Benefits of this approach + +- **Single file replacement**: Only swap `brand.config.ts` per brand +- **No code changes needed**: Components automatically use the correct paths +- **Easy maintenance**: Update paths in one place per brand +- **Production builds**: Combine brand config + environment swap +- **Future expansion**: Add new brand-specific assets by updating config files + +#### Example npm scripts + +```json +{ + "scripts": { + "start:me": "ng serve --configuration=me", + "build:me": "ng build --configuration=me", + "build:me:prod": "ng build --configuration=me-production", + "start:dc": "ng serve --configuration=dc", + "build:dc:prod": "ng build --configuration=dc-production" + } +} +``` + +#### Next steps + +1. Add brand-specific assets (logos, favicons, templates, zip code data) +2. Update brand config files with correct paths +3. Test builds with `--configuration` flags +4. Consider adding brand-specific SCSS variables for theming diff --git a/clients/html/angular.json b/clients/html/angular.json index e576fd9..fdc01f8 100755 --- a/clients/html/angular.json +++ b/clients/html/angular.json @@ -120,6 +120,10 @@ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" + }, + { + "replace": "src/app/brand.config.ts", + "with": "src/app/brand.config.me.ts" } ], "optimization": true, @@ -135,7 +139,7 @@ } }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "options": { "browserTarget": "quoting-tool:build" }, @@ -155,13 +159,13 @@ } }, "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", + "builder": "@angular/build:extract-i18n", "options": { "browserTarget": "quoting-tool:build" } }, "test": { - "builder": "@angular-devkit/build-angular:karma", + "builder": "@angular/build:karma", "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", diff --git a/clients/html/src/app/brand.config.dc.ts b/clients/html/src/app/brand.config.dc.ts new file mode 100644 index 0000000..7278e94 --- /dev/null +++ b/clients/html/src/app/brand.config.dc.ts @@ -0,0 +1,9 @@ +import { BrandConfig } from './brand.types'; + +export const BRAND_CONFIG: BrandConfig = { + // DC (District of Columbia) brand configuration + logoPath: 'assets/images/mhc_logo.svg', // Keep default logo for now + faviconPath: 'assets/images/favicon.png', // Keep default favicon for now + rosterTemplatePath: 'assets/roster_upload_template.xlsx', // Keep default template for now + zipCodeDataPath: 'data/zipCode.json', // Keep default zip codes for now +}; diff --git a/clients/html/src/app/brand.config.ma.ts b/clients/html/src/app/brand.config.ma.ts new file mode 100644 index 0000000..a2f70ed --- /dev/null +++ b/clients/html/src/app/brand.config.ma.ts @@ -0,0 +1,9 @@ +import { BrandConfig } from './brand.types'; + +export const BRAND_CONFIG: BrandConfig = { + // MA (Maine) brand configuration - default + logoPath: 'assets/images/mhc_logo.svg', + faviconPath: 'assets/images/favicon.png', + rosterTemplatePath: 'assets/roster_upload_template.xlsx', + zipCodeDataPath: 'data/zipCode.json', +}; diff --git a/clients/html/src/app/brand.config.me.ts b/clients/html/src/app/brand.config.me.ts new file mode 100644 index 0000000..97c68eb --- /dev/null +++ b/clients/html/src/app/brand.config.me.ts @@ -0,0 +1,9 @@ +import { BrandConfig } from './brand.types'; + +export const BRAND_CONFIG: BrandConfig = { + // ME (Maine) brand configuration + logoPath: 'assets/images/logo_me.svg', + faviconPath: 'assets/images/favicon_me.png', + rosterTemplatePath: 'assets/roster_upload_template_me.xlsx', + zipCodeDataPath: 'data/zipCodeME.json', +}; diff --git a/clients/html/src/app/brand.config.ts b/clients/html/src/app/brand.config.ts new file mode 100644 index 0000000..a59f07f --- /dev/null +++ b/clients/html/src/app/brand.config.ts @@ -0,0 +1,9 @@ +import { BrandConfig } from './brand.types'; + +export const BRAND_CONFIG: BrandConfig = { + // Default brand: MA (Maine) + logoPath: 'assets/images/mhc_logo.svg', + faviconPath: 'assets/images/favicon.png', + rosterTemplatePath: 'assets/roster_upload_template.xlsx', + zipCodeDataPath: 'data/zipCode.json', +}; diff --git a/clients/html/src/app/brand.types.ts b/clients/html/src/app/brand.types.ts new file mode 100644 index 0000000..597918e --- /dev/null +++ b/clients/html/src/app/brand.types.ts @@ -0,0 +1,6 @@ +export interface BrandConfig { + logoPath: string; + faviconPath: string; + rosterTemplatePath: string; + zipCodeDataPath: string; +} diff --git a/clients/html/src/assets/images/favicon_me.png b/clients/html/src/assets/images/favicon_me.png new file mode 100644 index 0000000..2a7fcb3 Binary files /dev/null and b/clients/html/src/assets/images/favicon_me.png differ diff --git a/clients/html/src/assets/scss/branding/ma/client-styles.scss b/clients/html/src/assets/scss/branding/ma/client-styles.scss new file mode 100644 index 0000000..204c205 --- /dev/null +++ b/clients/html/src/assets/scss/branding/ma/client-styles.scss @@ -0,0 +1,64 @@ +// MA (Maine) Brand Variables - Default +// $client-primary: #007bff; +// $client-bg-color: #007bff; +// $client-bg-color-lighten: lighten($client-bg-color, 5%); + +// // Additional brand-specific variables +// $brand-primary: $client-primary; +// $brand-secondary: #6c757d; +// $brand-accent: #28a745; +// $brand-text: #212529; +// $brand-background: #ffffff; + +// // Brand-specific overrides +// $logo-width: 126px; +// $logo-height: 40px; + +/*Theme Colors*/ + +:root { + --white: #ffffff; + --black: #000000; + + --gray: #6c757d; + --gray-light: #f8f9fa; + --gray-dark: #343a40; + --gray-100: #f1f1f1; + --gray-200: #e9ecef; + --gray-300: #dee2e6; + --gray-400: #ced4da; + --gray-500: #adb5bd; + --gray-600: #6c757d; + --gray-700: #495057; + --gray-800: #343a40; + --gray-900: #212529; + + --main-color: #007bff; + --main-color-light: #0056b3; + --secondary-color: #6c757d; + --secondary-color-light: #5a6268; + --destructive-color: #a00723; + --success-color: #28a745; + + --background-color: #ffffff; + --border-color: var(--gray-200); + + --link-color: #0036cb; + + --text-light: var(--white); + --text-dark: #212529; + + --shadow-color: var(--gray-200); + --shadow-black-10: rgba(0, 0, 0, 0.12); + --shadow-black-20: rgba(0, 0, 0, 0.24); +} + +.nav { + --bs-nav-link-color: var(--secondary-color) !important; + --bs-nav-link-hover-color: var(--main-color); +} + +/* MA BRAND SPECIFIC STYLES */ +body { + background-color: var(--background-color) !important; +} diff --git a/clients/html/src/assets/scss/client-styles.scss b/clients/html/src/assets/scss/client-styles.scss new file mode 100644 index 0000000..8190adb --- /dev/null +++ b/clients/html/src/assets/scss/client-styles.scss @@ -0,0 +1,45 @@ +/* Default Client Styles - Used when no specific brand configuration is specified */ + +/*Theme Colors*/ + +:root { + --white: #ffffff; + --black: #000000; + + --gray: #6c757d; + --gray-light: #f8f9fa; + --gray-dark: #343a40; + --gray-100: #f1f1f1; + --gray-200: #e9ecef; + --gray-300: #dee2e6; + --gray-400: #ced4da; + --gray-500: #adb5bd; + --gray-600: #6c757d; + --gray-700: #495057; + --gray-800: #343a40; + --gray-900: #212529; + + --main-color: #c56724; + --main-color-light: #dc8140; + --secondary-color: #0f5e82; + --secondary-color-light: #005e81; + --destructive-color: #a00723; + --success-color: #008000; + + --background-color: #f2efef; + --border-color: var(--gray-200); + + --link-color: #0036cb; + + --text-light: var(--white); + --text-dark: #3e5569; + + --shadow-color: var(--gray-200); + --shadow-black-10: rgba(0, 0, 0, 0.12); + --shadow-black-20: rgba(0, 0, 0, 0.24); +} + +.nav { + --bs-nav-link-color: var(--secondary-color) !important; + --bs-nav-link-hover-color: var(--main-color); +} diff --git a/clients/html/src/karma.conf.js b/clients/html/src/karma.conf.js index cd06d5b..a08eeb0 100755 --- a/clients/html/src/karma.conf.js +++ b/clients/html/src/karma.conf.js @@ -10,7 +10,7 @@ module.exports = function(config) { require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma'), + ], client: { clearContext: false, // leave Jasmine Spec Runner output visible in browser