theme
This commit is contained in:
parent
be67268546
commit
fd276b2c07
7
client/libs/shared/i18n/README.md
Normal file
7
client/libs/shared/i18n/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# I18n
|
||||||
|
|
||||||
|
This library provides internationalization and language selection functionality for the application.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test i18n` to execute the unit tests.
|
||||||
5
client/libs/shared/i18n/eslint.config.mjs
Normal file
5
client/libs/shared/i18n/eslint.config.mjs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
name: 'i18n',
|
||||||
|
extends: ['../../../eslint.base.config.mjs'],
|
||||||
|
};
|
||||||
22
client/libs/shared/i18n/jest.config.ts
Normal file
22
client/libs/shared/i18n/jest.config.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
displayName: 'i18n',
|
||||||
|
preset: '../../../jest.preset.js',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||||
|
coverageDirectory: '../../../coverage/libs/shared/i18n',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
|
'jest-preset-angular',
|
||||||
|
{
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||||
|
snapshotSerializers: [
|
||||||
|
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||||
|
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||||
|
'jest-preset-angular/build/serializers/html-comment',
|
||||||
|
],
|
||||||
|
};
|
||||||
21
client/libs/shared/i18n/project.json
Normal file
21
client/libs/shared/i18n/project.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "i18n",
|
||||||
|
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/shared/i18n/src",
|
||||||
|
"prefix": "lib",
|
||||||
|
"tags": [],
|
||||||
|
"projectType": "library",
|
||||||
|
"targets": {
|
||||||
|
"test": {
|
||||||
|
"executor": "@nx/jest:jest",
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/shared/i18n/jest.config.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/eslint:lint",
|
||||||
|
"outputs": ["{options.outputFile}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
client/libs/shared/i18n/src/index.ts
Normal file
2
client/libs/shared/i18n/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './lib/language.service';
|
||||||
|
export * from './lib/language-selector.component';
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatIcon } from '@angular/material/icon';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
import { LanguageService } from './language.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'lib-language-selector',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatButtonModule, MatIcon, MatMenuModule],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
template: `
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
[matMenuTriggerFor]="languageMenu"
|
||||||
|
class="action-button"
|
||||||
|
aria-label="Select language"
|
||||||
|
>
|
||||||
|
<mat-icon>translate</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #languageMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="languageService.setLanguage('en')">
|
||||||
|
<span>English</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="languageService.setLanguage('sl')">
|
||||||
|
<span>Slovenian</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
.action-button {
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.action-button:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class LanguageSelectorComponent {
|
||||||
|
protected languageService = inject(LanguageService);
|
||||||
|
}
|
||||||
39
client/libs/shared/i18n/src/lib/language.service.ts
Normal file
39
client/libs/shared/i18n/src/lib/language.service.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Injectable, signal } from '@angular/core';
|
||||||
|
|
||||||
|
export type SupportedLanguage = 'en' | 'sl';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class LanguageService {
|
||||||
|
private readonly LANGUAGE_KEY = 'selectedLanguage';
|
||||||
|
|
||||||
|
currentLanguage = signal<SupportedLanguage>(this.#getInitialLanguage());
|
||||||
|
|
||||||
|
#getInitialLanguage(): SupportedLanguage {
|
||||||
|
// Try to get language from localStorage
|
||||||
|
const storedLang = localStorage.getItem(
|
||||||
|
this.LANGUAGE_KEY
|
||||||
|
) as SupportedLanguage | null;
|
||||||
|
if (storedLang && (storedLang === 'en' || storedLang === 'sl')) {
|
||||||
|
return storedLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to detect from browser settings
|
||||||
|
const browserLang = navigator.language.substring(0, 2).toLowerCase();
|
||||||
|
if (browserLang === 'sl') {
|
||||||
|
return 'sl';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to English
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
setLanguage(lang: SupportedLanguage): void {
|
||||||
|
this.currentLanguage.set(lang);
|
||||||
|
localStorage.setItem(this.LANGUAGE_KEY, lang);
|
||||||
|
// This is where you would typically trigger translation changes
|
||||||
|
// in a real implementation
|
||||||
|
console.log(`Language set to: ${lang}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
client/libs/shared/i18n/tsconfig.json
Normal file
16
client/libs/shared/i18n/tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs"
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
client/libs/shared/i18n/tsconfig.lib.json
Normal file
15
client/libs/shared/i18n/tsconfig.lib.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"inlineSources": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"src/test-setup.ts",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"jest.config.ts",
|
||||||
|
"**/*.test.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
13
client/libs/shared/i18n/tsconfig.spec.json
Normal file
13
client/libs/shared/i18n/tsconfig.spec.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"jest.config.ts",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -3,10 +3,19 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatIcon } from '@angular/material/icon';
|
import { MatIcon } from '@angular/material/icon';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { ThemeToggleComponent } from '@org/shared/theme';
|
||||||
|
import { LanguageSelectorComponent } from '@org/shared/i18n';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'lib-navbar',
|
selector: 'lib-navbar',
|
||||||
imports: [MatToolbarModule, MatIcon, MatButtonModule, RouterModule],
|
imports: [
|
||||||
|
MatToolbarModule,
|
||||||
|
MatIcon,
|
||||||
|
MatButtonModule,
|
||||||
|
RouterModule,
|
||||||
|
ThemeToggleComponent,
|
||||||
|
LanguageSelectorComponent,
|
||||||
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
template: `
|
template: `
|
||||||
<mat-toolbar color="primary" class="navbar">
|
<mat-toolbar color="primary" class="navbar">
|
||||||
@ -15,8 +24,35 @@ import { RouterModule } from '@angular/router';
|
|||||||
<span class="brand">TaskManager</span>
|
<span class="brand">TaskManager</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
<a mat-button routerLink="/users" routerLinkActive="active-link">Users</a>
|
|
||||||
<a mat-button routerLink="/tasks" routerLinkActive="active-link">Tasks</a>
|
<div class="nav-links">
|
||||||
|
<a
|
||||||
|
mat-button
|
||||||
|
routerLink="/users"
|
||||||
|
routerLinkActive="active-link"
|
||||||
|
class="nav-button"
|
||||||
|
>
|
||||||
|
<mat-icon>people</mat-icon>
|
||||||
|
<span>Users</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
mat-button
|
||||||
|
routerLink="/tasks"
|
||||||
|
routerLinkActive="active-link"
|
||||||
|
class="nav-button"
|
||||||
|
>
|
||||||
|
<mat-icon>task</mat-icon>
|
||||||
|
<span>Tasks</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<!-- Theme toggle button -->
|
||||||
|
<lib-theme-toggle></lib-theme-toggle>
|
||||||
|
|
||||||
|
<!-- Language selector button -->
|
||||||
|
<lib-language-selector></lib-language-selector>
|
||||||
|
</div>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
`,
|
`,
|
||||||
styles: [
|
styles: [
|
||||||
@ -24,6 +60,8 @@ import { RouterModule } from '@angular/router';
|
|||||||
.navbar {
|
.navbar {
|
||||||
padding: 0 2rem;
|
padding: 0 2rem;
|
||||||
min-height: 64px;
|
min-height: 64px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.logo {
|
.logo {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -39,7 +77,48 @@ import { RouterModule } from '@angular/router';
|
|||||||
.spacer {
|
.spacer {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
.nav-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.nav-button.active-link {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.nav-button.active-link::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 10%;
|
||||||
|
right: 10%;
|
||||||
|
height: 3px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.action-button {
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.action-button:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class NavbarComponent {}
|
export class NavbarComponent {
|
||||||
|
// All functionality has been moved to separate components
|
||||||
|
}
|
||||||
|
|||||||
7
client/libs/shared/theme/README.md
Normal file
7
client/libs/shared/theme/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Theme
|
||||||
|
|
||||||
|
This library provides theme management functionality for the application.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test theme` to execute the unit tests.
|
||||||
5
client/libs/shared/theme/eslint.config.mjs
Normal file
5
client/libs/shared/theme/eslint.config.mjs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
name: 'theme',
|
||||||
|
extends: ['../../../eslint.base.config.mjs'],
|
||||||
|
};
|
||||||
22
client/libs/shared/theme/jest.config.ts
Normal file
22
client/libs/shared/theme/jest.config.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
displayName: 'theme',
|
||||||
|
preset: '../../../jest.preset.js',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||||
|
coverageDirectory: '../../../coverage/libs/shared/theme',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
|
'jest-preset-angular',
|
||||||
|
{
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||||
|
snapshotSerializers: [
|
||||||
|
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||||
|
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||||
|
'jest-preset-angular/build/serializers/html-comment',
|
||||||
|
],
|
||||||
|
};
|
||||||
21
client/libs/shared/theme/project.json
Normal file
21
client/libs/shared/theme/project.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "theme",
|
||||||
|
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/shared/theme/src",
|
||||||
|
"prefix": "lib",
|
||||||
|
"tags": [],
|
||||||
|
"projectType": "library",
|
||||||
|
"targets": {
|
||||||
|
"test": {
|
||||||
|
"executor": "@nx/jest:jest",
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/shared/theme/jest.config.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/eslint:lint",
|
||||||
|
"outputs": ["{options.outputFile}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
client/libs/shared/theme/src/index.ts
Normal file
2
client/libs/shared/theme/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './lib/theme.service';
|
||||||
|
export * from './lib/theme-toggle.component';
|
||||||
39
client/libs/shared/theme/src/lib/theme-toggle.component.ts
Normal file
39
client/libs/shared/theme/src/lib/theme-toggle.component.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatIcon } from '@angular/material/icon';
|
||||||
|
import { ThemeToggleService } from './theme.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'lib-theme-toggle',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatButtonModule, MatIcon],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
template: `
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
(click)="themeService.toggleTheme()"
|
||||||
|
class="action-button"
|
||||||
|
[attr.aria-label]="
|
||||||
|
themeService.isDark() ? 'Switch to light theme' : 'Switch to dark theme'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<mat-icon>{{
|
||||||
|
themeService.isDark() ? 'light_mode' : 'dark_mode'
|
||||||
|
}}</mat-icon>
|
||||||
|
</button>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
.action-button {
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.action-button:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ThemeToggleComponent {
|
||||||
|
protected themeService = inject(ThemeToggleService);
|
||||||
|
}
|
||||||
55
client/libs/shared/theme/src/lib/theme.service.ts
Normal file
55
client/libs/shared/theme/src/lib/theme.service.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||||
|
import { computed, DOCUMENT, effect, inject, Injectable } from '@angular/core';
|
||||||
|
import { stored } from '@mmstack/primitives';
|
||||||
|
|
||||||
|
function prefersDarkMode() {
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ThemeToggleService {
|
||||||
|
private readonly overlayCls =
|
||||||
|
inject(OverlayContainer).getContainerElement().classList;
|
||||||
|
private readonly dockument = inject(DOCUMENT);
|
||||||
|
|
||||||
|
private readonly theme = stored<'light' | 'dark'>(
|
||||||
|
prefersDarkMode() ? 'dark' : 'light',
|
||||||
|
{
|
||||||
|
key: 'user-theme',
|
||||||
|
syncTabs: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
public isDark = computed(() => this.theme() === 'dark');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
effect(() =>
|
||||||
|
this.isDark() ? this.addDarkThemeClass() : this.removeDarkThemeClass()
|
||||||
|
);
|
||||||
|
|
||||||
|
effect(() =>
|
||||||
|
this.dockument.body.setAttribute(
|
||||||
|
'style',
|
||||||
|
`color-scheme: ${this.isDark() ? 'dark' : 'light'}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleTheme() {
|
||||||
|
console.log(`Toggling theme to ${this.isDark() ? 'light' : 'dark'}`);
|
||||||
|
|
||||||
|
this.theme.set(this.isDark() ? 'light' : 'dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
private addDarkThemeClass() {
|
||||||
|
this.overlayCls.add('dark-theme');
|
||||||
|
this.dockument.body.classList.add('dark-theme');
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeDarkThemeClass() {
|
||||||
|
this.overlayCls.remove('dark-theme');
|
||||||
|
this.dockument.body.classList.remove('dark-theme');
|
||||||
|
}
|
||||||
|
}
|
||||||
30
client/libs/shared/theme/tsconfig.json
Normal file
30
client/libs/shared/theme/tsconfig.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"importHelpers": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"typeCheckHostBindings": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
16
client/libs/shared/theme/tsconfig.lib.json
Normal file
16
client/libs/shared/theme/tsconfig.lib.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/test-setup.ts",
|
||||||
|
"jest.config.ts",
|
||||||
|
"src/**/*.test.ts"
|
||||||
|
],
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
17
client/libs/shared/theme/tsconfig.spec.json
Normal file
17
client/libs/shared/theme/tsconfig.spec.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2016",
|
||||||
|
"types": ["jest", "node"],
|
||||||
|
"moduleResolution": "node10"
|
||||||
|
},
|
||||||
|
"files": ["src/test-setup.ts"],
|
||||||
|
"include": [
|
||||||
|
"jest.config.ts",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -87,13 +87,13 @@ import { TaskCommentComponent } from './task-comment.component';
|
|||||||
`
|
`
|
||||||
.task-panel {
|
.task-panel {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--mat-sys-level1);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-panel:hover {
|
.task-panel:hover {
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12);
|
box-shadow: var(--mat-sys-level3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-panel .mat-expansion-panel-header {
|
.task-panel .mat-expansion-panel-header {
|
||||||
@ -147,7 +147,7 @@ import { TaskCommentComponent } from './task-comment.component';
|
|||||||
|
|
||||||
.task-timestamp {
|
.task-timestamp {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: #757575;
|
color: var(--mat-sys-on-surface-variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-details {
|
.task-details {
|
||||||
@ -155,15 +155,15 @@ import { TaskCommentComponent } from './task-comment.component';
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-description {
|
.task-description {
|
||||||
font-size: 1rem;
|
font-size: var(--mat-sys-body-large-size);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
color: #424242;
|
color: var(--mat-sys-on-surface);
|
||||||
background-color: #f8f9fa;
|
background-color: var(--mat-sys-surface-container-low);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
border-left: 4px solid #1976d2;
|
border-left: 4px solid var(--mat-sys-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comments-section {
|
.comments-section {
|
||||||
@ -174,9 +174,9 @@ import { TaskCommentComponent } from './task-comment.component';
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
font-size: 1.2rem;
|
font-size: var(--mat-sys-title-medium-size);
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
color: #424242;
|
color: var(--mat-sys-on-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comments-section h3 mat-icon {
|
.comments-section h3 mat-icon {
|
||||||
|
|||||||
@ -111,16 +111,16 @@ import { CommentResponse, User } from '@org/shared/api';
|
|||||||
|
|
||||||
.comment-item {
|
.comment-item {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background-color: #f8f9fa;
|
background-color: var(--mat-sys-surface-container-low);
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
border: 1px solid #e0e0e0;
|
border: 1px solid var(--mat-sys-outline-variant);
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-item:hover {
|
.comment-item:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--mat-sys-level1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-header {
|
.comment-header {
|
||||||
@ -131,45 +131,45 @@ import { CommentResponse, User } from '@org/shared/api';
|
|||||||
}
|
}
|
||||||
|
|
||||||
.comment-author {
|
.comment-author {
|
||||||
font-weight: 600;
|
font-weight: var(--mat-sys-title-small-weight);
|
||||||
color: #1976d2;
|
color: var(--mat-sys-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-time {
|
.comment-time {
|
||||||
font-size: 0.8rem;
|
font-size: var(--mat-sys-body-small-size);
|
||||||
color: #757575;
|
color: var(--mat-sys-on-surface-variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-content {
|
.comment-content {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
color: #424242;
|
color: var(--mat-sys-on-surface);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-comments {
|
.no-comments {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: #757575;
|
color: var(--mat-sys-on-surface-variant);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
background-color: #f5f5f5;
|
background-color: var(--mat-sys-surface-container-low);
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
border: 1px dashed #e0e0e0;
|
border: 1px dashed var(--mat-sys-outline-variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-form {
|
.comment-form {
|
||||||
background-color: #f8f9fa;
|
background-color: var(--mat-sys-surface-container-low);
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
border: 1px solid #e0e0e0;
|
border: 1px solid var(--mat-sys-outline-variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-form h4 {
|
.comment-form h4 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
font-size: 1.1rem;
|
font-size: var(--mat-sys-title-medium-size);
|
||||||
color: #424242;
|
color: var(--mat-sys-on-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
|
|||||||
@ -81,8 +81,8 @@ import { ToastService } from '@org/shared/toast';
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-title {
|
.form-title {
|
||||||
font-weight: 700;
|
font-weight: var(--mat-sys-label-large-weight-prominent);
|
||||||
color: #1976d2;
|
color: var(--mat-sys-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-width {
|
.full-width {
|
||||||
|
|||||||
@ -94,8 +94,8 @@ import { TaskCardComponent } from './task-card.component';
|
|||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--mat-sys-level1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacer {
|
.spacer {
|
||||||
@ -114,19 +114,19 @@ import { TaskCardComponent } from './task-card.component';
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #f5f7fa;
|
background-color: var(--mat-sys-surface-container-low);
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
padding: 3rem 1.5rem;
|
padding: 3rem 1.5rem;
|
||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--mat-sys-level1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-icon {
|
.empty-icon {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
color: #bdbdbd;
|
color: var(--mat-sys-outline);
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -56,20 +56,20 @@ import { RouterModule } from '@angular/router';
|
|||||||
`
|
`
|
||||||
.user-card {
|
.user-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #fff;
|
background: var(--mat-sys-surface-container);
|
||||||
border-radius: 12px;
|
border-radius: var(--mat-sys-corner-medium);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07);
|
box-shadow: var(--mat-sys-level1);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.user-card:hover {
|
.user-card:hover {
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--mat-sys-level3);
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
}
|
}
|
||||||
.user-link {
|
.user-link {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #1976d2;
|
color: var(--mat-sys-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -145,23 +145,23 @@ import { UserTaskListComponent } from './user-task-list.component';
|
|||||||
}
|
}
|
||||||
|
|
||||||
mat-card {
|
mat-card {
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--mat-sys-level1);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasks-section {
|
.tasks-section {
|
||||||
background-color: #f9f9f9;
|
background-color: var(--mat-sys-surface-container-low);
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasks-section h2 {
|
.tasks-section h2 {
|
||||||
font-size: 1.25rem;
|
font-size: var(--mat-sys-title-large-size);
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid var(--mat-sys-outline-variant);
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -170,34 +170,34 @@ import { UserTaskListComponent } from './user-task-list.component';
|
|||||||
|
|
||||||
mat-list-item {
|
mat-list-item {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
border-left: 3px solid #3f51b5;
|
border-left: 3px solid var(--mat-sys-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-title {
|
.task-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 500;
|
font-weight: var(--mat-sys-title-medium-weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-description {
|
.task-description {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
color: rgba(0, 0, 0, 0.6);
|
color: var(--mat-sys-on-surface-variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-tasks {
|
.no-tasks {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
color: rgba(0, 0, 0, 0.5);
|
color: var(--mat-sys-on-surface-variant);
|
||||||
background-color: white;
|
background-color: var(--mat-sys-surface-container);
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
lib-task-form {
|
lib-task-form {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background-color: white;
|
background-color: var(--mat-sys-surface-container);
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
box-shadow: var(--mat-sys-level1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button {
|
.back-button {
|
||||||
|
|||||||
@ -107,8 +107,8 @@ type UserWithCounts = User & { taskCount: number; commentCount: number };
|
|||||||
.empty-state {
|
.empty-state {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
background-color: #f5f5f5;
|
background-color: var(--mat-sys-surface-container-low);
|
||||||
border-radius: 4px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
}
|
}
|
||||||
.user-grid {
|
.user-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -118,20 +118,20 @@ type UserWithCounts = User & { taskCount: number; commentCount: number };
|
|||||||
}
|
}
|
||||||
.user-card {
|
.user-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #fff;
|
background: var(--mat-sys-surface-container);
|
||||||
border-radius: 12px;
|
border-radius: var(--mat-sys-corner-medium);
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--mat-sys-level1);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.user-card:hover {
|
.user-card:hover {
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--mat-sys-level3);
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
}
|
}
|
||||||
.user-link {
|
.user-link {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #1976d2;
|
color: var(--mat-sys-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
@ -148,13 +148,13 @@ type UserWithCounts = User & { taskCount: number; commentCount: number };
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
font-size: 0.9rem;
|
font-size: var(--mat-sys-body-small-size);
|
||||||
color: #555;
|
color: var(--mat-sys-on-surface-variant);
|
||||||
}
|
}
|
||||||
.meta-icon {
|
.meta-icon {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 0.2em;
|
margin-right: 0.2em;
|
||||||
color: #1976d2;
|
color: var(--mat-sys-primary);
|
||||||
}
|
}
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.container {
|
.container {
|
||||||
|
|||||||
@ -53,16 +53,16 @@ import { TaskFormComponent } from '@org/web/tasks';
|
|||||||
styles: [
|
styles: [
|
||||||
`
|
`
|
||||||
.tasks-section {
|
.tasks-section {
|
||||||
background-color: #f9f9f9;
|
background-color: var(--mat-sys-surface-container-low);
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasks-section h2 {
|
.tasks-section h2 {
|
||||||
font-size: 1.25rem;
|
font-size: var(--mat-sys-title-large-size);
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid var(--mat-sys-outline-variant);
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -70,33 +70,33 @@ import { TaskFormComponent } from '@org/web/tasks';
|
|||||||
}
|
}
|
||||||
|
|
||||||
mat-list {
|
mat-list {
|
||||||
border-radius: 4px;
|
border-radius: var(--mat-sys-corner-extra-small);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: white;
|
background-color: var(--mat-sys-surface-container);
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
box-shadow: var(--mat-sys-level1);
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-list-item {
|
mat-list-item {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
border-left: 3px solid #3f51b5;
|
border-left: 3px solid var(--mat-sys-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-title {
|
.task-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 500;
|
font-weight: var(--mat-sys-title-medium-weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-description {
|
.task-description {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
color: rgba(0, 0, 0, 0.6);
|
color: var(--mat-sys-on-surface-variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-tasks {
|
.no-tasks {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
color: rgba(0, 0, 0, 0.5);
|
color: var(--mat-sys-on-surface-variant);
|
||||||
background-color: white;
|
background-color: var(--mat-sys-surface-container);
|
||||||
border-radius: 8px;
|
border-radius: var(--mat-sys-corner-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
lib-task-form {
|
lib-task-form {
|
||||||
|
|||||||
132
client/package-lock.json
generated
132
client/package-lock.json
generated
@ -19,6 +19,8 @@
|
|||||||
"@angular/platform-browser": "~20.0.0",
|
"@angular/platform-browser": "~20.0.0",
|
||||||
"@angular/platform-browser-dynamic": "~20.0.0",
|
"@angular/platform-browser-dynamic": "~20.0.0",
|
||||||
"@angular/router": "~20.0.0",
|
"@angular/router": "~20.0.0",
|
||||||
|
"@mmstack/primitives": "^20.0.1",
|
||||||
|
"@mmstack/translate": "^20.0.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.15.0"
|
||||||
},
|
},
|
||||||
@ -2685,6 +2687,84 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@formatjs/ecma402-abstract": {
|
||||||
|
"version": "2.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz",
|
||||||
|
"integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "2.2.7",
|
||||||
|
"@formatjs/intl-localematcher": "0.6.1",
|
||||||
|
"decimal.js": "^10.4.3",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/fast-memoize": {
|
||||||
|
"version": "2.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
|
||||||
|
"integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "2.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz",
|
||||||
|
"integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.3.4",
|
||||||
|
"@formatjs/icu-skeleton-parser": "1.8.14",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "1.8.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz",
|
||||||
|
"integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.3.4",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl": {
|
||||||
|
"version": "3.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-3.1.6.tgz",
|
||||||
|
"integrity": "sha512-tDkXnA4qpIFcDWac8CyVJq6oW8DR7W44QDUBsfXWIIJD/FYYen0QoH46W7XsVMFfPOVKkvbufjboZrrWbEfmww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.3.4",
|
||||||
|
"@formatjs/fast-memoize": "2.2.7",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.11.2",
|
||||||
|
"intl-messageformat": "10.7.16",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -3693,6 +3773,42 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@mmstack/object": {
|
||||||
|
"version": "20.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mmstack/object/-/object-20.0.0.tgz",
|
||||||
|
"integrity": "sha512-cLhbkhU+S6jNm+7RNJ8DxvprBtX45pE5ymw58Pj3PoB+UOCA7pZPBDiubQQWqKBpoQJO0X/LyT/uTDV1YKs1Jg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mmstack/primitives": {
|
||||||
|
"version": "20.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mmstack/primitives/-/primitives-20.0.1.tgz",
|
||||||
|
"integrity": "sha512-WAzQS+uf18Fjuz3HkWUnvJaB2nFt78/xV5f7vah9iR3DPe3IsWABngcEMNc1Wc+i4NHfytJQHfPlhSJXFMb3Gw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@mmstack/object": "^20.0.0",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": "~20.0.3",
|
||||||
|
"@angular/core": "~20.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mmstack/translate": {
|
||||||
|
"version": "20.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mmstack/translate/-/translate-20.0.1.tgz",
|
||||||
|
"integrity": "sha512-kFKRedsLLcU5GdYcJBhmT6vzPYVQMFRKlrVbaP/gawk+U56i9ZWK58o1CWYggf5WslTsBa98fNnEJyu2/3trYg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/core": "~20.0.3",
|
||||||
|
"@formatjs/intl": "~3.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@modern-js/node-bundle-require": {
|
"node_modules/@modern-js/node-bundle-require": {
|
||||||
"version": "2.67.6",
|
"version": "2.67.6",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -8722,7 +8838,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/decimal.js": {
|
"node_modules/decimal.js": {
|
||||||
"version": "10.6.0",
|
"version": "10.6.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dedent": {
|
"node_modules/dedent": {
|
||||||
@ -11162,6 +11277,19 @@
|
|||||||
"node": "^18.17.0 || >=20.5.0"
|
"node": "^18.17.0 || >=20.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/intl-messageformat": {
|
||||||
|
"version": "10.7.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz",
|
||||||
|
"integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.3.4",
|
||||||
|
"@formatjs/fast-memoize": "2.2.7",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.11.2",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ip-address": {
|
"node_modules/ip-address": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -17589,7 +17717,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.8.3",
|
"version": "5.8.3",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
|
|||||||
@ -19,6 +19,8 @@
|
|||||||
"@angular/platform-browser": "~20.0.0",
|
"@angular/platform-browser": "~20.0.0",
|
||||||
"@angular/platform-browser-dynamic": "~20.0.0",
|
"@angular/platform-browser-dynamic": "~20.0.0",
|
||||||
"@angular/router": "~20.0.0",
|
"@angular/router": "~20.0.0",
|
||||||
|
"@mmstack/primitives": "^20.0.1",
|
||||||
|
"@mmstack/translate": "^20.0.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.15.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -21,5 +21,6 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provide: API,
|
provide: API,
|
||||||
useValue: apiUrls,
|
useValue: apiUrls,
|
||||||
},
|
},
|
||||||
|
// No need to provide ThemeService and LanguageService as they use providedIn: 'root'
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,8 +7,19 @@ import { NavbarComponent } from '@org/shared/navbar';
|
|||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: `
|
template: `
|
||||||
<lib-navbar />
|
<lib-navbar />
|
||||||
|
<div class="content-container">
|
||||||
<router-outlet />
|
<router-outlet />
|
||||||
|
</div>
|
||||||
`,
|
`,
|
||||||
styles: [``],
|
styles: [
|
||||||
|
`
|
||||||
|
.content-container {
|
||||||
|
padding: 1.5rem;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
min-height: calc(100vh - 64px);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class App { }
|
export class App {}
|
||||||
|
|||||||
@ -22,21 +22,6 @@ body.dark-theme {
|
|||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-success {
|
|
||||||
background-color: #4caf50;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-error {
|
|
||||||
background-color: #f44336;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-info {
|
|
||||||
background-color: #2196f3;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-theme .mat-mdc-card {
|
body.dark-theme .mat-mdc-card {
|
||||||
--mdc-elevated-card-container-color: #424242;
|
--mdc-elevated-card-container-color: #424242;
|
||||||
color: rgba(255, 255, 255, 0.87);
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
|||||||
@ -9,8 +9,10 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@org/shared/api": ["libs/shared/api/src/index.ts"],
|
"@org/shared/api": ["libs/shared/api/src/index.ts"],
|
||||||
|
"@org/shared/i18n": ["libs/shared/i18n/src/index.ts"],
|
||||||
"@org/shared/navbar": ["libs/shared/navbar/src/index.ts"],
|
"@org/shared/navbar": ["libs/shared/navbar/src/index.ts"],
|
||||||
"@org/shared/stores": ["libs/shared/stores/src/index.ts"],
|
"@org/shared/stores": ["libs/shared/stores/src/index.ts"],
|
||||||
|
"@org/shared/theme": ["libs/shared/theme/src/index.ts"],
|
||||||
"@org/shared/toast": ["libs/shared/toast/src/index.ts"],
|
"@org/shared/toast": ["libs/shared/toast/src/index.ts"],
|
||||||
"@org/web/tasks": ["libs/web/tasks/src/index.ts"],
|
"@org/web/tasks": ["libs/web/tasks/src/index.ts"],
|
||||||
"@org/web/users": ["libs/web/users/src/index.ts"]
|
"@org/web/users": ["libs/web/users/src/index.ts"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user