feat(portal-admin): skeleton app per ADR-0020
First step of the admin track. Scaffold the second Angular SPA in
the workspace alongside `portal-shell`, with the architectural
decisions from the v1 ADRs already wired so subsequent feature PRs
can drop straight into modules.
What lands:
- App generated via `nx g @nx/angular:application` (with the
matching e2e project skeleton). Standalone, zoneless-ready,
prefix `app`, scss component styles, css root stylesheet, no SSR.
- Tags `scope:portal-admin, type:app` so module-boundary lint
treats `portal-admin` distinctly from `portal-shell` and only
allows it to depend on `scope:portal-admin` + `scope:shared`
libs.
- Tailwind v4 wired (`.postcssrc.json` + `@import 'tailwindcss'`),
class-based dark mode (`@custom-variant dark ...`), brand palette
imported from `libs/shared/tokens/src/brand-tokens.css` —
identical visual baseline to portal-shell. ADR-0016 a11y baseline
applies; ADR-0017 perf budget relaxed to **500 KB gzip initial**
per ADR-0020 §"Performance budgets".
- i18n configured to mirror portal-shell (per ADR-0019): sourceLocale
`en`, target `fr`, baseHref `/{locale}/` per locale,
`@angular/localize/init` polyfill, `localize: ["en", "fr"]` on
production, `i18nMissingTranslation: "error"` so CI blocks any PR
that adds a marker without translating it. Seed `messages.fr.xlf`
carries the four marked strings used by the placeholder page +
skip-link + route title.
- Skeleton home page at `/` with a placeholder `<h1>`, intro
paragraph, and a small "Skeleton — coming soon" status chip in
the brand-accent palette. Marked for i18n.
- Skip-link landmark (WCAG 2.4.1) at the App level, with the same
styling as portal-shell (component-scoped scss).
- Wildcard catch-all route bounces unknown paths to home, same
defensive pattern as PR #96 on portal-shell.
- Dev server on port **4300** (vs 4200 for portal-shell) so both
can run in parallel during cross-app development.
- `serve-static` target without `spa: true` — locale-prefixed
routing in production will be handled by the reverse proxy (per
ADR-0019), same as portal-shell since PR #92.
Production build: 62 KB gzip initial per locale (vs the 500 KB
relaxed budget). Both `dist/apps/portal-admin/browser/{en,fr}/`
emitted with the right `<html lang>` and `<base href>`.
Out of scope (each its own follow-up PR):
- Admin app shell (header / sidebar / footer with "Admin" badge).
- OpenTelemetry tracing setup (will register as
`service.name=portal-admin` per ADR-0012).
- `environment.ts` wiring (per ADR-0018) for the admin's own BFF
endpoints.
- BFF `AdminModule` + `AdminRoleGuard` + first `/api/admin/me`
smoke route (per ADR-0020 §"Auth").
- First functional module (CMS / menu / users / audit viewer).
CLAUDE.md picks up the second app in its naming + commands sections.
This commit is contained in:
@@ -32,7 +32,7 @@ These constraints were set by the project lead at kickoff. They apply to every c
|
||||
The structural, security, observability, and quality choices are recorded as ADRs and summarized below. Any change to these requires updating the corresponding ADR.
|
||||
|
||||
- **Workspace:** Nx monorepo with the `apps` preset, managed by pnpm — see [ADR-0002](docs/decisions/0002-adopt-nx-monorepo-apps-preset.md).
|
||||
- **Naming:** workspace `apf-portal`; apps `portal-shell` (frontend) and `portal-bff` (backend); libs `feature-<name>` and `shared-<scope>` — see [ADR-0003](docs/decisions/0003-workspace-and-app-naming-convention.md).
|
||||
- **Naming:** workspace `apf-portal`; apps `portal-shell` (end-user SPA), `portal-admin` (admin SPA, skeleton in place — see ADR-0020), and `portal-bff` (backend); libs `feature-<name>` and `shared-<scope>` — see [ADR-0003](docs/decisions/0003-workspace-and-app-naming-convention.md).
|
||||
- **Frontend (`portal-shell`):** Angular at the latest LTS major — standalone APIs, zoneless change detection, Signals, **CSR only (no SSR)**, Vitest, SCSS — see [ADR-0004](docs/decisions/0004-frontend-stack-angular-csr-zoneless-signals.md).
|
||||
- **Backend (`portal-bff`):** NestJS at the latest stable major, mounted on the Express adapter (Fastify adapter swappable later) — see [ADR-0005](docs/decisions/0005-backend-stack-nestjs.md).
|
||||
- **Persistence:** PostgreSQL (latest stable major) via Prisma — see [ADR-0006](docs/decisions/0006-persistence-postgresql-prisma.md).
|
||||
@@ -60,7 +60,7 @@ If asked to "build", "test", or "run" anything, first verify whether the workspa
|
||||
|
||||
## Commands once the workspace exists
|
||||
|
||||
App-scoped — `<app>` is one of `portal-shell`, `portal-bff`:
|
||||
App-scoped — `<app>` is one of `portal-shell`, `portal-admin`, `portal-bff`:
|
||||
|
||||
```bash
|
||||
pnpm nx serve <app> # dev server
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import playwright from 'eslint-plugin-playwright';
|
||||
import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
playwright.configs['flat/recommended'],
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.js'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,68 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
|
||||
// For CI, you may want to set BASE_URL to the deployed application.
|
||||
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
baseURL,
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'pnpm exec nx run portal-admin:serve',
|
||||
url: 'http://localhost:4200',
|
||||
reuseExistingServer: true,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
|
||||
// Uncomment for mobile browsers support
|
||||
/* {
|
||||
name: 'Mobile Chrome',
|
||||
use: { ...devices['Pixel 5'] },
|
||||
},
|
||||
{
|
||||
name: 'Mobile Safari',
|
||||
use: { ...devices['iPhone 12'] },
|
||||
}, */
|
||||
|
||||
// Uncomment for branded browsers
|
||||
/* {
|
||||
name: 'Microsoft Edge',
|
||||
use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
},
|
||||
{
|
||||
name: 'Google Chrome',
|
||||
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
} */
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "portal-admin-e2e",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"sourceRoot": "apps/portal-admin-e2e/src",
|
||||
"implicitDependencies": ["portal-admin"],
|
||||
"// targets": "to see all targets run: nx show project portal-admin-e2e --web",
|
||||
"targets": {}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('has title', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Expect h1 to contain a substring.
|
||||
expect(await page.locator('h1').innerText()).toContain('Welcome');
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"sourceMap": false,
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.js",
|
||||
"playwright.config.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"plugins": {
|
||||
"@tailwindcss/postcss": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'app',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'app',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"name": "portal-admin",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"sourceRoot": "apps/portal-admin/src",
|
||||
"tags": ["scope:portal-admin", "type:app"],
|
||||
"i18n": {
|
||||
"sourceLocale": {
|
||||
"code": "en",
|
||||
"baseHref": "/en/"
|
||||
},
|
||||
"locales": {
|
||||
"fr": {
|
||||
"translation": "apps/portal-admin/src/locale/messages.fr.xlf",
|
||||
"baseHref": "/fr/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@angular/build:application",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/apps/portal-admin",
|
||||
"browser": "apps/portal-admin/src/main.ts",
|
||||
"polyfills": ["@angular/localize/init"],
|
||||
"tsConfig": "apps/portal-admin/tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/portal-admin/public"
|
||||
}
|
||||
],
|
||||
"styles": ["apps/portal-admin/src/styles.css"]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"localize": ["en", "fr"],
|
||||
"i18nMissingTranslation": "error",
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "1.5mb",
|
||||
"maximumError": "1.5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyScript",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "500kb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "5kb",
|
||||
"maximumError": "6kb"
|
||||
},
|
||||
{
|
||||
"type": "bundle",
|
||||
"name": "styles",
|
||||
"maximumWarning": "150kb",
|
||||
"maximumError": "150kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"executor": "@angular/build:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "portal-admin:build",
|
||||
"outputPath": "apps/portal-admin/src/locale",
|
||||
"outFile": "messages.xlf"
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"continuous": true,
|
||||
"executor": "@angular/build:dev-server",
|
||||
"options": {
|
||||
"port": 4300
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "portal-admin:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "portal-admin:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
},
|
||||
"test": {
|
||||
"executor": "@angular/build:unit-test",
|
||||
"options": {
|
||||
"watch": false
|
||||
},
|
||||
"configurations": {
|
||||
"watch": {
|
||||
"watch": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"continuous": true,
|
||||
"executor": "@nx/web:file-server",
|
||||
"options": {
|
||||
"buildTarget": "portal-admin:build",
|
||||
"port": 4300,
|
||||
"staticFilePath": "dist/apps/portal-admin/browser"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,7 @@
|
||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { appRoutes } from './app.routes';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideBrowserGlobalErrorListeners(), provideRouter(appRoutes)],
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
<a class="skip-link" href="#main-content" i18n="@@app.skipLink">Skip to main content</a>
|
||||
<main id="main-content" tabindex="-1" class="min-h-screen bg-gray-50 dark:bg-gray-950">
|
||||
<router-outlet />
|
||||
</main>
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
const homeTitle = $localize`:@@route.home.title:APF Portal Admin`;
|
||||
|
||||
export const appRoutes: Route[] = [
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
loadComponent: () => import('./pages/home/home').then((m) => m.Home),
|
||||
title: homeTitle,
|
||||
},
|
||||
// Catch-all — unknown paths bounce to home. Same role as the
|
||||
// wildcard in portal-shell (per PR #96): in production each locale
|
||||
// bundle ships with its own `<base href="/{locale}/">` and the
|
||||
// router never sees the locale segment, so this only fires for
|
||||
// genuine 404 paths; in dev (`nx serve`) it stops the router from
|
||||
// throwing NG04002 when a stray `/fr/`-style URL is typed.
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: '',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,23 @@
|
||||
// Skip-link (WCAG 2.4.1 "Bypass Blocks"). Hidden by default, fully
|
||||
// visible and focusable when reached via Tab from the address bar.
|
||||
// Plain CSS rather than the `sr-only` Tailwind utilities so the
|
||||
// visual state on focus is as predictable as possible across themes.
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
left: 0.75rem;
|
||||
z-index: 50;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: var(--color-brand-primary-500);
|
||||
color: #fff;
|
||||
border-radius: 0.25rem;
|
||||
text-decoration: none;
|
||||
transition: top 0.15s ease-out;
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
top: 0.5rem;
|
||||
outline: 2px solid var(--color-brand-accent-300);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { App } from './app';
|
||||
|
||||
describe('App', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [App],
|
||||
providers: [provideRouter([])],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('renders the layout shell — skip link, main landmark', async () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
await fixture.whenStable();
|
||||
const root = fixture.nativeElement as HTMLElement;
|
||||
expect(root.querySelector('a.skip-link')).not.toBeNull();
|
||||
expect(root.querySelector('main#main-content')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class App {}
|
||||
@@ -0,0 +1,24 @@
|
||||
<section class="mx-auto max-w-3xl px-6 py-12">
|
||||
<h1
|
||||
class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-gray-100"
|
||||
i18n="@@page.home.title"
|
||||
>
|
||||
APF Portal Admin
|
||||
</h1>
|
||||
<p
|
||||
class="mt-4 text-base leading-relaxed text-gray-700 dark:text-gray-300"
|
||||
i18n="@@page.home.intro"
|
||||
>
|
||||
Administrative back-office for the APF Portal. The first functional modules — content
|
||||
management, menu administration, user list, audit log viewer — land in upcoming PRs per
|
||||
ADR-0020.
|
||||
</p>
|
||||
|
||||
<p
|
||||
class="mt-6 inline-flex items-center rounded-full border border-brand-accent-300 bg-brand-accent-50 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-brand-accent-700 dark:border-brand-accent-700 dark:bg-brand-accent-950/40 dark:text-brand-accent-200"
|
||||
role="status"
|
||||
i18n="@@page.home.status"
|
||||
>
|
||||
Skeleton — coming soon
|
||||
</p>
|
||||
</section>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Smoke-test landing page for portal-admin.
|
||||
*
|
||||
* v1 ships a placeholder. The real admin shell (header + sidebar +
|
||||
* footer with the "Admin" badge, per ADR-0020) lands in a follow-up
|
||||
* PR together with the first functional module (CMS pages, menu
|
||||
* management, user list, or audit log viewer).
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: './home.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class Home {}
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>APF Portal Admin</title>
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" target-language="fr" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
<!-- app -->
|
||||
<trans-unit id="app.skipLink" datatype="html">
|
||||
<source>Skip to main content</source>
|
||||
<target>Aller au contenu principal</target>
|
||||
</trans-unit>
|
||||
|
||||
<!-- route -->
|
||||
<trans-unit id="route.home.title" datatype="html">
|
||||
<source>APF Portal Admin</source>
|
||||
<target>Administration APF Portal</target>
|
||||
</trans-unit>
|
||||
|
||||
<!-- page.home -->
|
||||
<trans-unit id="page.home.title" datatype="html">
|
||||
<source> APF Portal Admin </source>
|
||||
<target> Administration APF Portal </target>
|
||||
</trans-unit>
|
||||
<trans-unit id="page.home.intro" datatype="html">
|
||||
<source> Administrative back-office for the APF Portal. The first functional modules — content management, menu administration, user list, audit log viewer — land in upcoming PRs per ADR-0020. </source>
|
||||
<target> Back-office d’administration pour le portail APF. Les premiers modules fonctionnels — gestion de contenu, gestion du menu, liste des utilisateurs, visualiseur d’audit log — arrivent dans les PRs à venir conformément à l’ADR-0020. </target>
|
||||
</trans-unit>
|
||||
<trans-unit id="page.home.status" datatype="html">
|
||||
<source> Skeleton — coming soon </source>
|
||||
<target> Squelette — bientôt disponible </target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { App } from './app/app';
|
||||
|
||||
bootstrapApplication(App, appConfig).catch((err) => console.error(err));
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Global styles for portal-admin.
|
||||
*
|
||||
* Same baseline as portal-shell — Tailwind CSS 4 (CSS-first config),
|
||||
* class-based dark mode, brand palette tokens shared from
|
||||
* `libs/shared/tokens`. The two apps wear the same visual chrome so
|
||||
* an APF user moving between portal and admin doesn't feel a context
|
||||
* switch in the design system itself; what differentiates the admin
|
||||
* surface is the data density and the "Admin" badge in the shell
|
||||
* (added once the admin shell PR ships).
|
||||
*/
|
||||
|
||||
@import 'tailwindcss';
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
/* Brand palette tokens — shared with portal-shell. */
|
||||
@import '../../../libs/shared/tokens/src/brand-tokens.css';
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["@angular/localize"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["vitest/globals", "@angular/localize"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user