feat: update Dockerfiles to use explicit image paths and enhance localStorage handling in ThemeService

This commit is contained in:
nessar committed 2026-08-17 14:18:15 +02:00
1 parent 030d00ef67
commit 3dca10b56f
7 files changed
+59 -10

No files matched your search

+3 -3
View File
@@ -1,7 +1,7 @@
# ==========================================
# Stage 1: Build using Node.js + Bun
# ==========================================
FROM node:26-alpine AS build
FROM docker.io/library/node:26-alpine AS build
WORKDIR /app
RUN npm install -g bun
@@ -19,10 +19,10 @@ RUN bun run build
# ==========================================
# Stage 2: Serve the app using Nginx
# ==========================================
FROM nginx:alpine
FROM docker.io/library/nginx:alpine
RUN rm -rf /usr/share/nginx/html/*
COPY --from=build /app/dist/lmnp-frontend/browser /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
CMD ["nginx", "-g", "daemon off;"]
+1 -1
View File
@@ -83,7 +83,7 @@
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html", "e2e/**/*.ts"]
}
}
}
+12 -2
View File
@@ -58,7 +58,13 @@ test('persists a dark appearance preference from Settings', async ({ page }) =>
await page.getByRole('radio', { name: 'Sombre' }).check();
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
expect(await page.evaluate(() => localStorage.getItem('lmnp-theme'))).toBe('dark');
expect(
await page.evaluate(
// This callback runs in the browser page, where localStorage is guaranteed by the test.
// eslint-disable-next-line no-restricted-globals
() => localStorage.getItem('lmnp-theme'),
),
).toBe('dark');
await page.reload();
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
@@ -192,7 +198,11 @@ test('keeps the secured API check and logout available', async ({ page }) => {
});
test('follows operating-system changes for the System theme', async ({ page }) => {
await page.addInitScript(() => localStorage.setItem('lmnp-theme', 'system'));
await page.addInitScript(
// This callback runs in the browser page, where localStorage is guaranteed by the test.
// eslint-disable-next-line no-restricted-globals
() => localStorage.setItem('lmnp-theme', 'system'),
);
await page.emulateMedia({ colorScheme: 'dark' });
await mockUser(page, true);
await mockEmptyApartmentList(page);
+32 -1
View File
@@ -16,11 +16,36 @@ module.exports = defineConfig([
processor: angular.processInlineTemplates,
languageOptions: {
parserOptions: {
project: ['./tsconfig.json', './tsconfig.app.json', './tsconfig.spec.json'],
project: [
'./tsconfig.json',
'./tsconfig.app.json',
'./tsconfig.spec.json',
'./tsconfig.e2e.json',
],
tsconfigRootDir: __dirname,
},
},
rules: {
'no-restricted-globals': [
'error',
{
name: 'localStorage',
message: 'Use getBrowserStorage() for storage that works outside a browser.',
},
],
'no-restricted-properties': [
'error',
{
object: 'window',
property: 'localStorage',
message: 'Use getBrowserStorage() for storage that works outside a browser.',
},
{
object: 'globalThis',
property: 'localStorage',
message: 'Use getBrowserStorage() for storage that works outside a browser.',
},
],
'@typescript-eslint/no-deprecated': 'error',
'@angular-eslint/directive-selector': [
'error',
@@ -40,6 +65,12 @@ module.exports = defineConfig([
],
},
},
{
files: ['**/services/browser-storage.ts'],
rules: {
'no-restricted-properties': 'off',
},
},
{
files: ['**/*.html'],
extends: [angular.configs.templateRecommended, angular.configs.templateAccessibility],
@@ -0,0 +1,7 @@
export function getBrowserStorage(): Storage | null {
try {
return typeof globalThis.localStorage === 'undefined' ? null : globalThis.localStorage;
} catch {
return null;
}
}
+2 -1
View File
@@ -1,5 +1,6 @@
import { DOCUMENT } from '@angular/common';
import { effect, inject, Injectable, signal } from '@angular/core';
import { getBrowserStorage } from './browser-storage';
export type ThemePreference = 'light' | 'dark' | 'system';
@@ -8,7 +9,7 @@ const THEME_STORAGE_KEY = 'lmnp-theme';
@Injectable({ providedIn: 'root' })
export class ThemeService {
private readonly document = inject(DOCUMENT);
private readonly storage = typeof localStorage === 'undefined' ? null : localStorage;
private readonly storage = getBrowserStorage();
private readonly colorSchemeQuery =
typeof window.matchMedia === 'function'
? window.matchMedia('(prefers-color-scheme: dark)')