Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 51 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Foundry Stack
# Next Template Angular Parity

Static-first Angular showcase and template website with an optional server/backend path.
Localized Angular application foundation modeled after the deployed `next-template` reference. It includes locale routing, navigation menus, hotkeys, auth screens, form and table demos, communication notes, uploads, repo-managed content, problem reporting, static data providers, and optional server/API providers.

## Scripts

Expand All @@ -10,35 +10,65 @@ npm run build
npm run build:github-pages
npm run build:connected
npm run build:server
npm test -- --watch=false
npm run test:unit
npm run test:integration
npm run test:e2e
npm run test:ci
```

## Public routes

- `/` redirects to `/en`
- `/:locale`
- `/:locale/about`
- `/:locale/login`
- `/:locale/register`
- `/:locale/examples/forms`
- `/:locale/examples/story`
- `/:locale/examples/communication`
- `/:locale/examples/uploads`
- `/:locale/table`
- `/:locale/remocn`
- `/:locale/blog`
- `/:locale/changelog`
- `/:locale/report-problem`
- `/:locale/**`

Supported locales are `en` and `de`. Locale links preserve the current path when switching languages.

## Feature surface

- Reference-style app shell with Discover and Workspace menus.
- Alt-key navigation shortcuts and a hotkey dialog.
- Light/dark theme persistence with an inline startup script to reduce theme flash.
- Privacy consent persistence for necessary-only or analytics opt-in.
- Reactive auth, registration, reset, employee profile, newsletter, and report-problem forms.
- Upload queue with file classification and strategy suggestions.
- Employee table with repository-backed data and loading/error/empty states.
- Static blog and changelog content stored as typed records.

## Deployment modes

- `build`: browser-only production build for static hosting.
- `build:github-pages`: static build plus `404.html` fallback copy for GitHub Pages.
- `build:connected`: same frontend with API-backed repository providers.
- `build:server`: browser + server bundles with Express endpoints under `/api/*`.

## GitHub Actions
## Server endpoints

- `.github/workflows/github-pages.yml`: tests the app, builds the static site, and deploys `dist/angular2/browser` to GitHub Pages.
- `.github/workflows/server-build.yml`: tests the app, builds the optional server variant, and uploads `dist/angular2` as an artifact.

## GitHub Pages setup

- Enable GitHub Pages in the repository settings and set the source to `GitHub Actions`.
- The Pages workflow derives `baseHref` from the repository name automatically.
- For local verification you can override the path:

```bash
GITHUB_PAGES_BASE_HREF=/angular/ npm run build:github-pages
```
- `POST /api/auth/login`
- `POST /api/auth/register`
- `POST /api/auth/password-reset`
- `POST /api/newsletter`
- `POST /api/problem-reports`
- `GET /api/employees`
- Existing template, showcase, and contact endpoints remain available.

## Architecture

- Standalone, lazy-loaded Angular routes.
- Signals for local page state.
- Typed in-repo content for templates and showcases.
- Repository abstraction to switch between static and API data.
- Optional Express service endpoints for templates, showcases, and contact submissions.
- Standalone lazy-loaded Angular routes.
- URL-based locale service for `en` and `de`.
- Signals for local UI state.
- Reactive Forms for user input.
- Repository abstraction for static, connected, and server-backed behavior.
- GitHub Pages fallback routing through a copied `404.html`.
78 changes: 36 additions & 42 deletions e2e/app.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,46 @@
import { expect, test } from '@playwright/test';

test('navigates from the home page through templates to the contact flow', async ({ page }) => {
test('redirects to localized home and supports locale switching plus hotkeys', async ({ page }) => {
await page.goto('/');

await expect(
page.getByRole('heading', {
level: 1,
name: 'Build once for GitHub Pages, then grow into backend services without rewriting the UI.',
}),
).toBeVisible();
await expect(page).toHaveURL(/\/en$/);
await expect(page.getByRole('heading', { level: 1, name: 'One template, multiple production-ready starting points.' })).toBeVisible();

await page.getByRole('link', { name: 'Browse templates' }).click();
await expect(page).toHaveURL(/\/templates$/);
await page.getByRole('link', { name: 'DE' }).click();
await expect(page).toHaveURL(/\/de$/);
await expect(page.getByRole('heading', { level: 1, name: 'Eine Vorlage mit mehreren produktionsnahen Ausgangspunkten.' })).toBeVisible();

await page.getByRole('searchbox', { name: 'Search' }).fill('Atlas');
await expect(page.getByText('1 templates matched.')).toBeVisible();
await page.keyboard.press('Alt+F');
await expect(page).toHaveURL(/\/de\/examples\/forms$/);
await expect(page.getByRole('heading', { level: 1, name: 'Mitarbeiterprofil-Formular' })).toBeVisible();

await page.getByRole('link', { name: 'View template' }).click();
await expect(page).toHaveURL(/\/templates\/atlas-launch-kit$/);
await expect(page.getByRole('heading', { level: 1, name: 'Atlas Launch Kit' })).toBeVisible();

await page.getByRole('link', { name: 'Request implementation' }).click();
await expect(page).toHaveURL(/\/contact$/);

await page.getByRole('textbox', { name: 'Name' }).fill('Alex');
await page.getByRole('textbox', { name: 'Email' }).fill('alex@example.com');
await page.getByRole('textbox', { name: 'Company' }).fill('Foundry');
await page.getByRole('combobox', { name: 'Project type' }).selectOption('Migration');
await page
.getByRole('textbox', { name: 'Message' })
.fill('We need a migration path that keeps the public site static while backend services are phased in.');

await page.getByRole('button', { name: 'Send request' }).click();

await expect(
page.getByText(
'Message captured in static mode. Swap to the connected or server build to send it to a backend.',
),
).toBeVisible();
await page.getByRole('button', { name: /Show navigation hotkeys/ }).click();
await expect(page.getByRole('dialog', { name: /Jump between routes/ })).toBeVisible();
});

test('serves deep links and the not-found route from the built app', async ({ page }) => {
await page.goto('/showcase/northstar-ops');

await expect(page.getByRole('heading', { level: 1, name: 'Northstar Ops' })).toBeVisible();
await expect(page.getByText('This project maps back to a reusable template system.')).toBeVisible();

await page.goto('/missing-route');

await expect(page.getByRole('heading', { level: 1, name: 'That page does not exist in this build.' })).toBeVisible();
test('runs auth, upload, report, and not-found flows', async ({ page }) => {
await page.goto('/en/login');
await page.getByRole('textbox', { name: 'Email' }).fill('alex@example.com');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByText('Signed in with the static auth adapter')).toBeVisible();

await page.goto('/en/examples/uploads');
const chooserPromise = page.waitForEvent('filechooser');
await page.getByText('Choose files').click();
const chooser = await chooserPromise;
await chooser.setFiles({
name: 'records.json',
mimeType: 'application/json',
buffer: Buffer.from('{}'),
});
await expect(page.getByText('Validate schema')).toBeVisible();

await page.goto('/en/report-problem');
await page.getByRole('textbox', { name: 'What happened?' }).fill('Saving changes closed the modal before the confirmation was visible.');
await page.getByRole('button', { name: 'Send report' }).click();
await expect(page.getByText(/Reference: STATIC-/)).toBeVisible();

await page.goto('/en/missing-route');
await expect(page.getByRole('heading', { level: 1, name: 'The page does not exist.' })).toBeVisible();
});
Loading