From 042b9db2ac7785ec0935b079a0c702f06f79f358 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:17:21 -0400 Subject: [PATCH] chore: correct Sea Haven branding and rewrite README (#25) * fix: correct Sea Haven branding in page title and wordmark * chore: use correct company name in docs and comments * docs: rewrite README * docs: add README badge block per github-standards * fix: update command syntax in deployment instructions * fix: replace template PWA icons and manifest branding * test: assert PWA manifest branding --- README.md | 230 ++++++++++++++------ config/pwa-manifest.test.ts | 33 +++ docs/ARCHITECTURE_PLAN.md | 2 +- docs/DESIGN_SYSTEM.md | 8 +- docs/DESIGN_SYSTEM_MIGRATION.md | 12 +- docs/FEATURE_PRIORITIZATION.md | 2 +- docs/UI_DOCUMENTATION.md | 6 +- index.html | 6 +- infra/cdk/README.md | 2 +- infra/cdk/lib/frontend-stack.ts | 2 +- infra/cdk/package.json | 2 +- public/favicon.ico | Bin 3870 -> 2882 bytes public/logo192.png | Bin 5347 -> 4190 bytes public/logo512.png | Bin 9664 -> 5406 bytes public/manifest.json | 8 +- src/components/common/brand-lockup.tsx | 2 +- src/components/domain/status-badge-utils.ts | 2 +- src/styles/fonts.css | 2 +- src/styles/theme.css | 4 +- tsconfig.node.json | 3 +- 20 files changed, 230 insertions(+), 96 deletions(-) create mode 100644 config/pwa-manifest.test.ts diff --git a/README.md b/README.md index c58d0fcd..e380b5df 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,180 @@ -# SeaHaven Admin +# SHOC Frontend (`shoc-frontend-new`) -Vite + React SPA for SeaHaven facility management (work orders, vendor portal, uplifts, and related admin features). +[![CI](https://github.com/Sea-Haven-Industries/shoc-frontend-new/actions/workflows/ci.yaml/badge.svg?branch=dev)](https://github.com/Sea-Haven-Industries/shoc-frontend-new/actions/workflows/ci.yaml) +[![Deploy](https://github.com/Sea-Haven-Industries/shoc-frontend-new/actions/workflows/deploy.yml/badge.svg)](https://github.com/Sea-Haven-Industries/shoc-frontend-new/actions/workflows/deploy.yml) +![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white) +![React](https://img.shields.io/badge/React-087EA4?logo=react&logoColor=white) +![Vite](https://img.shields.io/badge/Vite-646CFF?logo=vite&logoColor=white) +![AWS CDK](https://img.shields.io/badge/AWS_CDK-FF9900?logo=amazonwebservices&logoColor=white) -## Requirements +Vite + React SPA for Sea Haven facility management (SHOC): work orders, vendor +portal, uplifts, and related admin features. This is the selective rebuild of +the legacy SHOC frontend — new code follows the IrisLoan.Admin conventions +documented in [`docs/ARCHITECTURE_PLAN.md`](docs/ARCHITECTURE_PLAN.md). -- Node.js 22.22.1+ -- npm 11.16.0 - -## Setup - -```bash -npm ci -cp .env.example .env -``` - -### Environment variables - -| Variable | Description | Default | -| ----------------- | ----------------------------------------------- | ----------------------- | -| `VITE_API_URL` | Ky API prefix (baked into the production build) | `/api` | -| `VITE_API_TARGET` | Dev proxy target for `/api` (Vite only) | `http://localhost:5141` | - -`VITE_API_URL` is the full API base prefix. Route paths in `API_PATHS` do **not** include `/api` — only this variable supplies it. - -- **Development:** use `VITE_API_URL=/api`. The Vite dev server proxies `/api` to `VITE_API_TARGET`. -- **Production:** use an absolute URL that **ends with `/api`** (e.g. `https://api.example.com/api`). Absolute URLs without `/api` will produce broken API requests. - -See [`.env.example`](.env.example) and [`.env.production`](.env.production) for reference values. - -## Scripts - -| Command | Description | -| ---------------------- | ------------------------------------------ | -| `npm run dev` | Start Vite dev server on port 3000 | -| `npm run build` | Type-check and production build to `dist/` | -| `npm run preview` | Preview production build locally | -| `npm test` | Run Vitest unit tests | -| `npm run test:watch` | Run Vitest in watch mode | -| `npm run lint` | ESLint | -| `npm run lint:fix` | ESLint with auto-fix | -| `npm run format` | Prettier write | -| `npm run format:check` | Prettier check (used in CI) | +- **GitHub:** `Sea-Haven-Industries/shoc-frontend-new` +- **Hosted at:** (dev environment; the only environment today) +- **Backend API:** `https://api.dev.seahaven.com/api` (called directly, cross-origin) — source: `Sea-Haven-Industries/shoc-backend` ## Architecture -New code lives under `src/domain/`, `src/app/`, and `src/api/` following the IrisLoan conventions documented in `docs/ARCHITECTURE_PLAN.md`. +Static SPA hosting on AWS, provisioned by a CDK app local to this repo +([`infra/cdk/`](infra/cdk/README.md)). CloudFront serves the built `dist/` +from a private S3 bucket; the SPA calls the backend directly over HTTPS at +`VITE_API_URL` (no `/api` proxy at the CDN — the backend allows CORS). -## CI +```mermaid +graph LR + U[Browser] -->|HTTPS dev.seahaven.com| CF[CloudFront] + CF -->|OAC| S3[S3 seahaven-shoc-frontend-dev] + CF -.->|viewer-request fn| FN[SPA rewrite → /index.html] + U -->|HTTPS api.dev.seahaven.com/api CORS| API[SHOC backend API] + GH[GitHub Actions push to dev] -->|OIDC| ROLE[githubdeploy-shoc-frontend-new-dev] + ROLE -->|cdk deploy + s3 sync + invalidation| S3 +``` -GitHub Actions workflow (`.github/workflows/ci.yml`) runs on push and pull requests: +Frontend stack: React 19, TypeScript, Vite, Tailwind CSS 4 + MUI, TanStack +Query, React Router (via `@generouted/react-router`), React Hook Form + Zod, +Ky HTTP client. Source layout: `src/api/`, `src/domain/`, `src/app/` (see the +architecture plan for the keep/discard migration matrix). -1. `npm run format:check` -2. `npm run lint` -3. `npm run build` -4. `npm test` +## AWS Resources -Local pre-commit hooks (Husky + lint-staged) run ESLint and Prettier on staged files. +Stack **`shoc-frontend-dev`** — CDK, account `396287094661`, region +`us-east-1`. Defined in [`infra/cdk/lib/frontend-stack.ts`](infra/cdk/lib/frontend-stack.ts). -## Deployment (CI/CD) +| Resource | Name | Purpose | +| ----------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| S3 bucket | `seahaven-shoc-frontend-dev` | Private origin (BLOCK_ALL, SSE, versioned; OAC-only reads) | +| CloudFront distribution | (stack output `DistributionId`) | HTTPS static hosting on `dev.seahaven.com`, ACM `*.seahaven.com` | +| CloudFront Function | `SpaRewrite` | Viewer-request rewrite of extensionless paths to `/index.html` (deep links) | +| IAM role | `githubdeploy-shoc-frontend-new-dev` | GitHub Actions OIDC deploy role, trust scoped to `repo:Sea-Haven-Industries/shoc-frontend-new:ref:refs/heads/dev` | +| Route 53 records | A/AAAA apex alias in zone `dev.seahaven.com` (`Z07671212N75U4YLPWZR8`) | Points the custom domain at CloudFront | -The app is hosted on **AWS S3 + CloudFront**, provisioned by an **AWS CDK** app -local to this repo ([`infra/cdk/`](infra/cdk/README.md)). Deployment runs -through the org's reusable GitHub Actions workflow via **OIDC** (no stored AWS -keys): +No Lambdas, queues, or databases — this stack is static hosting only. -- Push to `dev` → `.github/workflows/deploy.yml` calls the org reusable - `cd-cdk.yaml`, which runs `cdk deploy` (infra) then `scripts/deploy-web.sh` - (builds the SPA, syncs `dist/` to S3, invalidates CloudFront). -- Served on the custom domain `dev.seahaven.com`; the SPA calls the backend - directly over HTTPS at `VITE_API_URL` (`https://api.dev.seahaven.com/api`, - cross-origin — the backend allows CORS). `VITE_API_URL` is baked into the - build, so it is per-environment. -- First-time provisioning (OIDC provider, CDK bootstrap, first local deploy, the - `AWS_DEPLOY_ROLE_ARN` secret) is a one-time admin task — see - [`infra/cdk/README.md`](infra/cdk/README.md). +## Configuration -## Development proxy +### Secrets -During `npm run dev`, requests to `/api` are proxied to `VITE_API_TARGET` (see `vite.config.ts`). +No Secrets Manager or SSM parameters. The one secret is a **GitHub Actions +repo secret**: + +| Secret | Purpose | +| --------------------- | ----------------------------------------------------------------------------------- | +| `AWS_DEPLOY_ROLE_ARN` | ARN of `githubdeploy-shoc-frontend-new-dev`, passed to the org reusable CD workflow | + +### Environment variables (build-time, `VITE_*`) + +| Variable | Description | Dev value | +| ----------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------- | +| `VITE_API_URL` | Ky API base prefix, **baked into the build** at `vite build` | `/api` (dev server) / `https://api.dev.seahaven.com/api` (production build) | +| `VITE_API_TARGET` | Dev-proxy target for `/api` (Vite dev server only) | `http://localhost:5141` | + +`VITE_API_URL` supplies the full API prefix — route paths in `API_PATHS` do +**not** include `/api`. Absolute values **must end with `/api`**; +`vite.config.ts` enforces this via `config/api-url-contract.ts` and fails the +build otherwise. See [`.env.example`](.env.example), +[`.env.development`](.env.development), and [`.env.production`](.env.production). + +CDK context (domain, certificate ARN, hosted zone) lives in +[`infra/cdk/cdk.json`](infra/cdk/cdk.json) so CI runs `cdk deploy` with no flags. + +## Local Development + +Requirements: Node.js ≥ 22.22.1 (CI/CD run Node 24), npm 11.16.0 (pinned via +`packageManager`). + +```bash +npm ci +cp .env.example .env # then set VITE_API_URL=/api for local dev +npm run dev # Vite dev server on port 3000, proxies /api → VITE_API_TARGET +``` + +The dev proxy expects the `shoc-backend` API at `http://localhost:5141`; +override with `VITE_API_TARGET` (e.g. `https://api.dev.seahaven.com` to use +the deployed dev API). + +| Command | Description | +| ------------------------------------------ | ----------------------------------------------------- | +| `npm run dev` | Start Vite dev server on port 3000 | +| `npm run build` | Type-check (`tsc -b`) and production build to `dist/` | +| `npm run preview` | Preview the production build locally | +| `npm test` / `npm run test:watch` | Vitest unit tests (once / watch) | +| `npm run test:e2e` / `npm run test:e2e:ui` | Playwright e2e tests (headless / UI mode) | +| `npm run lint` / `npm run lint:fix` | ESLint (check / auto-fix) | +| `npm run format` / `npm run format:check` | Prettier (write / check) | + +Husky + lint-staged run ESLint and Prettier on staged files at commit; +commitlint enforces conventional commit messages. Run `npx tsc --noEmit` (or +`npm run build`) before pushing to catch type errors early. + +## Contributing + +- Branch from `dev` with a kebab-case description and a prefix matching the + work: `feature/`, `bug/`, `hotfix/`, `chore/`, `docs/`, or `refactor/` + (e.g. `feature/vendor-portal-filters`, `chore/sea-haven-branding`). +- Commit messages follow + [Conventional Commits](https://www.conventionalcommits.org) — commitlint + rejects anything else at commit time. +- Open PRs against `dev`. Both `dev` and `main` are protected: every PR needs + a green CI run and an approving review from a code owner + (`@Sea-Haven-Industries/internal-dev`); new pushes dismiss stale approvals. + Merged branches are deleted automatically. +- Promotion flow: `feature/* → dev` (auto-deployed and verified on + `dev.seahaven.com`) `→ main` (production promotion — no prod environment + exists yet). + +## Deployment + +CI/CD uses the org's reusable workflows (no stored AWS keys — OIDC only): + +- **CI** ([`.github/workflows/ci.yaml`](.github/workflows/ci.yaml)) — on push + and PRs to `main`/`dev`, calls + `Sea-Haven-Industries/.github` → `ci-typescript-frontend.yaml` (Node 24): + format check, lint, build, tests. +- **CD** ([`.github/workflows/deploy.yml`](.github/workflows/deploy.yml)) — on + push to `dev`, calls `Sea-Haven-Industries/.github` → `cd-cdk.yaml`, which + runs `cdk deploy` on `infra/cdk` (stack `shoc-frontend-dev`, `us-east-1`) + and then [`scripts/deploy-web.sh`](scripts/deploy-web.sh): `npm run build`, + `aws s3 sync dist/` (hashed assets immutable, `index.html` never cached), + CloudFront invalidation. Both run as the OIDC deploy role. + +One-time provisioning (OIDC provider, CDK bootstrap, first local deploy, +setting `AWS_DEPLOY_ROLE_ARN`) is documented in +[`infra/cdk/README.md`](infra/cdk/README.md). + +Manual deploy (emergency/reference only — needs credentials for the +external-dev AWS account; the normal path is push to `dev`): + +```bash +(cd infra/cdk && npx cdk deploy) +STACK_NAME=shoc-frontend-dev AWS_REGION=us-east-1 bash scripts/deploy-web.sh +``` + +## Operations + +- **Verify:** open after a green **Deploy** run in + the Actions tab; confirm a deep link (e.g. a work-orders route) loads + directly and API calls succeed. +- **Logs:** deploy logs live in GitHub Actions (CI + Deploy workflows). There + are no CloudWatch application logs — the stack is static hosting; runtime + errors surface in the browser and on the backend API's side. +- **Common failure modes:** + - _Stale content after deploy_ — the CloudFront invalidation step failed or + is still propagating; re-run the Deploy workflow or invalidate `/*` manually. + - _OIDC `AssumeRole` errors_ — the trust policy is scoped to pushes to `dev` + on this repo; deploys from other branches/repos are rejected by design. + - _Broken API requests after a build_ — `VITE_API_URL` missing the `/api` + suffix or carrying the wrong environment's host (it is baked in at build time). + - _CORS errors_ — the backend must allow the frontend origin; CloudFront does + not proxy `/api`. +- **CI and CD both fire on push to `dev` in parallel** — a red-CI commit still + deploys (matches the org's push-time-CD model; gating deploy on CI is known + follow-up work). + +## Documentation + +- Infra one-time setup and stack details: [`infra/cdk/README.md`](infra/cdk/README.md) +- Rebuild strategy and conventions: [`docs/ARCHITECTURE_PLAN.md`](docs/ARCHITECTURE_PLAN.md); + design system and UI docs under [`docs/`](docs/) diff --git a/config/pwa-manifest.test.ts b/config/pwa-manifest.test.ts new file mode 100644 index 00000000..a509de14 --- /dev/null +++ b/config/pwa-manifest.test.ts @@ -0,0 +1,33 @@ +import { readFileSync, existsSync } from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +const root = path.resolve(__dirname, ".."); +const manifest = JSON.parse(readFileSync(path.join(root, "public/manifest.json"), "utf-8")) as { + name: string; + short_name: string; + theme_color: string; + icons: Array<{ src: string }>; +}; +const indexHtml = readFileSync(path.join(root, "index.html"), "utf-8"); + +describe("PWA install metadata", () => { + it("identifies the app as Sea Haven, not template branding", () => { + expect(manifest.name).toContain("Sea Haven"); + expect(manifest.short_name).not.toBe("React App"); + expect(manifest.name).not.toContain("Create React App"); + }); + + it("uses the design-system theme color, matching index.html", () => { + expect(manifest.theme_color).toBe("#1c75bc"); + expect(indexHtml).toContain(``); + }); + + it("only references icon files that exist in public/", () => { + for (const icon of manifest.icons) { + expect(existsSync(path.join(root, "public", icon.src)), `missing public/${icon.src}`).toBe( + true, + ); + } + }); +}); diff --git a/docs/ARCHITECTURE_PLAN.md b/docs/ARCHITECTURE_PLAN.md index 57a583b7..1e5d44ee 100644 --- a/docs/ARCHITECTURE_PLAN.md +++ b/docs/ARCHITECTURE_PLAN.md @@ -1,4 +1,4 @@ -# Architecture Plan — SeaHaven (Selective Rebuild) +# Architecture Plan — Sea Haven (Selective Rebuild) > Aligned with IrisLoan.Admin · Vite SPA + React Router · **no Next.js** · Tailwind + MUI diff --git a/docs/DESIGN_SYSTEM.md b/docs/DESIGN_SYSTEM.md index 51a2df76..28d3996a 100644 --- a/docs/DESIGN_SYSTEM.md +++ b/docs/DESIGN_SYSTEM.md @@ -1,4 +1,4 @@ -# SeaHaven Design System +# Sea Haven Design System Visual foundation for **seaheven.front**, aligned with [seahaven.desing](https://github.com) tokens and IrisLoan.Admin integration patterns (centralized CSS, `@theme inline`, `cn()`). @@ -83,7 +83,7 @@ MUI typography variants in `src/lib/theme/mui-theme.ts` mirror this hierarchy. ### Prefer themed MUI in forms and data tables -MUI components inherit Seahaven styling via `mui-theme.ts` overrides (`Button`, `TextField`, `Table`, `Chip`, `Paper`, `Dialog`, `Alert`). +MUI components inherit Sea Haven styling via `mui-theme.ts` overrides (`Button`, `TextField`, `Table`, `Chip`, `Paper`, `Dialog`, `Alert`). ### Prefer semantic Tailwind in layout shells @@ -119,9 +119,9 @@ import { cn } from "@/lib/utils"; | `StatusBadge` / `WorkOrderStatusBadge` | `components/domain/status-badge.tsx` | | `DocBadge` | `components/domain/doc-badge.tsx` | -## Legacy → Seahaven mapping +## Legacy → Sea Haven mapping -| Legacy | Seahaven | Consumption | +| Legacy | Sea Haven | Consumption | | ------------------------- | ------------------------------ | -------------------------------- | | `#0c4f6f` / `--blue-main` | `--primary` (`#1c75bc`) | MUI `primary.main`, `bg-primary` | | `#083a52` / `--blue-dark` | `--primary-hover` | Button hover | diff --git a/docs/DESIGN_SYSTEM_MIGRATION.md b/docs/DESIGN_SYSTEM_MIGRATION.md index 3446239e..21cfa639 100644 --- a/docs/DESIGN_SYSTEM_MIGRATION.md +++ b/docs/DESIGN_SYSTEM_MIGRATION.md @@ -1,12 +1,12 @@ # Design System Migration Log -Migration from legacy CSS (`index.css`, `#0c4f6f` palette) to Seahaven DS tokens. Strategy: **MUI-only**. +Migration from legacy CSS (`index.css`, `#0c4f6f` palette) to Sea Haven DS tokens. Strategy: **MUI-only**. ## Phase 1 — Foundation | File | Action | Reason | | --------------------------- | ---------- | --------------------------------------------------------------------- | -| `src/styles/theme.css` | Created | SoT: 131+ Seahaven tokens + layout/typography/elevation extensions | +| `src/styles/theme.css` | Created | SoT: 131+ Sea Haven tokens + layout/typography/elevation extensions | | `src/styles/fonts.css` | Created | Montserrat, DM Sans, JetBrains Mono via `@fontsource` | | `src/styles/typography.css` | Created | Base html/body/heading/caption/label styles | | `src/styles/motion.css` | Created | Duration and easing tokens | @@ -29,7 +29,7 @@ Migration from legacy CSS (`index.css`, `#0c4f6f` palette) to Seahaven DS tokens | `src/components/layout/app-topbar.tsx` | Created | MUI AppBar + header gradient; replaces `Topbar.js` | | `src/app/(protected)/_layout.tsx` | Modified | Uses new layout components | | `src/main.tsx` | Modified | Removed `index.css` import | -| `src/app/v/_components/vendor-portal.css` | Modified | Re-tokenized with Seahaven CSS vars | +| `src/app/v/_components/vendor-portal.css` | Modified | Re-tokenized with Sea Haven CSS vars | | `src/index.css` | Deleted | ~487 lines migrated to tokens + TSX shell | | `src/components/Sidebar.js` | Deleted | Replaced by `app-sidebar.tsx` | | `src/components/Topbar.js` | Deleted | Replaced by `app-topbar.tsx` | @@ -59,7 +59,7 @@ Migration from legacy CSS (`index.css`, `#0c4f6f` palette) to Seahaven DS tokens | `src/app/(protected)/settings/dropdowns.tsx` | Modified | Semantic sidebar + active states | | `src/app/(protected)/settings/task-templates.tsx` | Modified | Semantic borders/text | | `src/components/common/settings-nav.tsx` | Modified | `text-primary`, `border-border` | -| `src/components/common/calendar/calendar-utils.ts` | Modified | Bootstrap hex → Seahaven CSS vars | +| `src/components/common/calendar/calendar-utils.ts` | Modified | Bootstrap hex → Sea Haven CSS vars | | `src/components/common/calendar/calendar-action-bar.tsx` | Modified | Semantic surface classes | | `src/components/common/calendar/event-calendar.tsx` | Modified | Semantic surface classes | | `src/components/common/signature-capture.tsx` | Modified | Canvas colors from CSS vars | @@ -79,8 +79,8 @@ Migration from legacy CSS (`index.css`, `#0c4f6f` palette) to Seahaven DS tokens - Primary color: `#0c4f6f` → `#1c75bc` (expected, documented) - Body font: Segoe UI 13px → DM Sans 14px -- Sidebar: `#fbfbfb` → `#f6f8fb` with Seahaven active/hover states -- Header: flat blue gradient → Seahaven multi-stop gradient +- Sidebar: `#fbfbfb` → `#f6f8fb` with Sea Haven active/hover states +- Header: flat blue gradient → Sea Haven multi-stop gradient ## Not migrated (intentional) diff --git a/docs/FEATURE_PRIORITIZATION.md b/docs/FEATURE_PRIORITIZATION.md index 28bbb432..aa52e770 100644 --- a/docs/FEATURE_PRIORITIZATION.md +++ b/docs/FEATURE_PRIORITIZATION.md @@ -1,4 +1,4 @@ -# Feature Prioritization Matrix — SeaHaven +# Feature Prioritization Matrix — Sea Haven > Source of truth for legacy → `domain/` + `app/` migration order. > Machine-readable config: [`src/config/feature-priorities.ts`](../src/config/feature-priorities.ts). diff --git a/docs/UI_DOCUMENTATION.md b/docs/UI_DOCUMENTATION.md index c7d7b7b2..2d836181 100644 --- a/docs/UI_DOCUMENTATION.md +++ b/docs/UI_DOCUMENTATION.md @@ -1,4 +1,4 @@ -# SeaHaven UI - Complete Architecture Documentation +# Sea Haven UI - Complete Architecture Documentation > Modern React architecture with Redux Toolkit, React Query, and CSS Modules @@ -55,7 +55,7 @@ │ 5. BACKEND │ │ │ │ REST API │ -│ (SeaHaven Backend) │ +│ (Sea Haven Backend) │ └─────────────────────────────────────────────────────────────┘ ``` @@ -539,6 +539,6 @@ const debouncedSearch = useDebounce(search, DEBOUNCE_SEARCH); --- -**Welcome to the modern SeaHaven UI! 🎊** +**Welcome to the modern Sea Haven UI! 🎊** _Last updated: 2026_ diff --git a/index.html b/index.html index 6f00ee89..2e7fb09f 100644 --- a/index.html +++ b/index.html @@ -4,8 +4,8 @@ - - + + @@ -18,7 +18,7 @@ referrerpolicy="no-referrer" /> - Seahaven - Officetrax + Sea Haven - Work Orders diff --git a/infra/cdk/README.md b/infra/cdk/README.md index 8ecf1cd0..e876a0c7 100644 --- a/infra/cdk/README.md +++ b/infra/cdk/README.md @@ -1,4 +1,4 @@ -# Infrastructure & CI/CD — SeaHaven SHOC frontend +# Infrastructure & CI/CD — Sea Haven SHOC frontend AWS hosting for the Vite SPA, defined as an **AWS CDK** app local to this repo, deployed through the org's **reusable** GitHub Actions workflow. diff --git a/infra/cdk/lib/frontend-stack.ts b/infra/cdk/lib/frontend-stack.ts index be3a558c..be7a4681 100644 --- a/infra/cdk/lib/frontend-stack.ts +++ b/infra/cdk/lib/frontend-stack.ts @@ -38,7 +38,7 @@ export interface FrontendStackProps extends StackProps { } /** - * Static SPA hosting for the SeaHaven SHOC frontend: + * Static SPA hosting for the Sea Haven SHOC frontend: * - private S3 bucket (no public access; CloudFront reads it via OAC) * - CloudFront distribution (HTTPS, SPA deep-link fallback) * - a GitHub Actions OIDC deploy role diff --git a/infra/cdk/package.json b/infra/cdk/package.json index e773f265..b490e706 100644 --- a/infra/cdk/package.json +++ b/infra/cdk/package.json @@ -2,7 +2,7 @@ "name": "shoc-frontend-infra", "version": "0.1.0", "private": true, - "description": "CDK app provisioning S3 + CloudFront hosting and the GitHub OIDC deploy role for the SeaHaven SHOC frontend.", + "description": "CDK app provisioning S3 + CloudFront hosting and the GitHub OIDC deploy role for the Sea Haven SHOC frontend.", "bin": { "app": "bin/app.ts" }, diff --git a/public/favicon.ico b/public/favicon.ico index a11777cc471a4344702741ab1c8a588998b1311a..95e736b553693760b6f169dfa0f543d3f1525c40 100644 GIT binary patch literal 2882 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 literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ diff --git a/public/logo192.png b/public/logo192.png index fc44b0a3796c0e0a64c3d858ca038bd4570465d9..20a794a107cac94e3287c7e4f362a1f264c1af10 100644 GIT binary patch literal 4190 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 literal 5347 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 V^trND@Z1wP`J20QonzS5{{zv9&6EHD literal 9664 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN diff --git a/public/manifest.json b/public/manifest.json index 080d6c77..f058e498 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,10 +1,10 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "Work Orders", + "name": "Sea Haven - Work Orders", "icons": [ { "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", + "sizes": "64x64 32x32 16x16", "type": "image/x-icon" }, { @@ -20,6 +20,6 @@ ], "start_url": ".", "display": "standalone", - "theme_color": "#000000", + "theme_color": "#1c75bc", "background_color": "#ffffff" } diff --git a/src/components/common/brand-lockup.tsx b/src/components/common/brand-lockup.tsx index 85a5a517..326d99ff 100644 --- a/src/components/common/brand-lockup.tsx +++ b/src/components/common/brand-lockup.tsx @@ -37,7 +37,7 @@ export function BrandWordmark({ size = "default", className, ...props }: BrandWo size === "compact" ? "text-md" : "text-[16px]", )} > - SEAHAVEN + SEA HAVEN
= { open: "incomplete", incomplete: "incomplete", diff --git a/src/styles/fonts.css b/src/styles/fonts.css index eb862666..2163a14d 100644 --- a/src/styles/fonts.css +++ b/src/styles/fonts.css @@ -1,4 +1,4 @@ -/* Seahaven DS — self-hosted via @fontsource (matches theme.css stacks) */ +/* Sea Haven DS — self-hosted via @fontsource (matches theme.css stacks) */ @import "@fontsource/montserrat/600.css"; @import "@fontsource/montserrat/700.css"; diff --git a/src/styles/theme.css b/src/styles/theme.css index 0b950a4e..121225f3 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -45,7 +45,7 @@ --shadow-lg: 0 10px 15px -3px rgba(22, 30, 56, 0.1), 0 4px 6px -4px rgba(22, 30, 56, 0.08); --shadow-brand-mark: 0 2px 6px -1px rgba(28, 117, 188, 0.45); - /* ── SHADCN BASE (Seahaven DS) ──────────────────────────────────── */ + /* ── SHADCN BASE (Sea Haven DS) ──────────────────────────────────── */ --background: #ffffff; --foreground: #262262; --placeholder-foreground: color-mix(in srgb, var(--foreground) 50%, transparent); @@ -79,7 +79,7 @@ --chart-4: #7c3aed; --chart-5: #dc2626; - /* ── SIDEBAR (light surface — Seahaven DS) ──────────────────────── */ + /* ── SIDEBAR (light surface — Sea Haven DS) ──────────────────────── */ --sidebar: #f6f8fb; --sidebar-bg: #f6f8fb; --sidebar-foreground: #262262; diff --git a/tsconfig.node.json b/tsconfig.node.json index 3e43f464..5fc395e4 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -20,6 +20,7 @@ "vite.config.ts", "vitest.config.ts", "config/api-url-contract.ts", - "config/api-url-contract.test.ts" + "config/api-url-contract.test.ts", + "config/pwa-manifest.test.ts" ] }