Skip to content

Commit e592181

Browse files
author
Flatlogic Bot
committed
refactor: migrate app architecture and UI to modern Angular stack
1 parent 028bfdc commit e592181

485 files changed

Lines changed: 9038 additions & 5440 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.browserslistrc

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2-
# For additional information regarding the format and rule options, please see:
3-
# https://github.com/browserslist/browserslist#queries
4-
5-
# You can see what browsers were selected by your queries by running:
6-
# npx browserslist
7-
8-
> 0.5%
9-
last 2 versions
1+
# Modern browser targets aligned with Angular 21 support.
2+
last 2 Chrome versions
3+
last 1 Firefox version
4+
last 2 Edge major versions
5+
last 2 Safari major versions
6+
last 2 iOS major versions
107
Firefox ESR
11-
not dead
12-
not IE 9-11 # For IE 9-11 support, remove 'not'.
8+
not IE 11

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build
2+
node_modules

.eslintrc.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"root": true,
3+
"ignorePatterns": ["build/**/*", "node_modules/**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts"],
7+
"parserOptions": {
8+
"project": ["tsconfig.json"],
9+
"createDefaultProgram": true
10+
},
11+
"extends": [
12+
"eslint:recommended",
13+
"plugin:@typescript-eslint/recommended",
14+
"plugin:@angular-eslint/recommended",
15+
"plugin:@angular-eslint/template/process-inline-templates"
16+
],
17+
"rules": {
18+
"@angular-eslint/directive-selector": "off",
19+
"@angular-eslint/component-selector": "off",
20+
"@angular-eslint/no-empty-lifecycle-method": "off",
21+
"@angular-eslint/prefer-inject": "off",
22+
"@angular-eslint/prefer-standalone": "off",
23+
"@angular-eslint/use-lifecycle-interface": "off",
24+
"@typescript-eslint/ban-types": "off",
25+
"@typescript-eslint/ban-ts-comment": "off",
26+
"@typescript-eslint/no-empty-function": "off",
27+
"@typescript-eslint/no-extra-semi": "off",
28+
"@typescript-eslint/no-unused-expressions": "off",
29+
"@typescript-eslint/no-wrapper-object-types": "off",
30+
"@typescript-eslint/no-inferrable-types": "off",
31+
"@typescript-eslint/no-explicit-any": "off",
32+
"@typescript-eslint/explicit-function-return-type": "off",
33+
"no-extra-boolean-cast": "off",
34+
"no-extra-semi": "off",
35+
"prefer-const": "off",
36+
"prefer-spread": "off",
37+
"@typescript-eslint/no-unused-vars": [
38+
"warn",
39+
{
40+
"argsIgnorePattern": "^_",
41+
"varsIgnorePattern": "^_"
42+
}
43+
]
44+
}
45+
},
46+
{
47+
"files": ["*.html"],
48+
"extends": ["plugin:@angular-eslint/template/recommended"],
49+
"rules": {
50+
"@angular-eslint/template/prefer-control-flow": "off"
51+
}
52+
}
53+
]
54+
}

.github/workflows/ci.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
quality:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
16+
- name: Setup Node
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: 22
20+
cache: npm
21+
22+
- name: Install dependencies
23+
run: npm ci --legacy-peer-deps
24+
25+
- name: Lint
26+
run: npm run lint -- --quiet
27+
28+
- name: Typecheck
29+
run: npm run typecheck
30+
31+
- name: Build
32+
run: npm run build
33+
34+
- name: Test
35+
run: npm run test -- --watch=false --browsers=ChromeHeadless
36+
37+
e2e:
38+
runs-on: ubuntu-latest
39+
needs: quality
40+
steps:
41+
- name: Checkout
42+
uses: actions/checkout@v4
43+
44+
- name: Setup Node
45+
uses: actions/setup-node@v4
46+
with:
47+
node-version: 22
48+
cache: npm
49+
50+
- name: Install dependencies
51+
run: npm ci --legacy-peer-deps
52+
53+
- name: Install Playwright Chromium
54+
run: npx playwright install --with-deps chromium
55+
56+
- name: E2E smoke
57+
run: npm run e2e

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# compiled output
44
/dist
5+
/build
56
/tmp
67
/out-tsc
78
# Only exists if Bazel was run
@@ -38,10 +39,11 @@ speed-measure-plugin*.json
3839
/libpeerconnection.log
3940
npm-debug.log
4041
yarn-error.log
42+
yarn.lock
4143
testem.log
4244
/typings
4345

4446
# System Files
4547
.DS_Store
4648
Thumbs.db
47-
/.angular
49+
/.angular

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
22

angular.json

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,16 @@
2424
"aot": true,
2525
"assets": ["src/favicon.ico", "src/assets"],
2626
"styles": [
27-
"./node_modules/font-awesome/scss/font-awesome.scss",
27+
"./node_modules/@fortawesome/fontawesome-free/css/all.min.css",
28+
"./node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css",
2829
"./node_modules/ngx-toastr/toastr.css",
30+
"./node_modules/angular-calendar/css/angular-calendar.css",
31+
"./node_modules/leaflet/dist/leaflet.css",
2932
"src/custom-theme.scss"
3033
],
34+
"allowedCommonJsDependencies": [
35+
"leaflet"
36+
],
3137
"scripts": []
3238
},
3339
"configurations": {
@@ -38,7 +44,14 @@
3844
"with": "src/environments/environment.prod.ts"
3945
}
4046
],
41-
"optimization": true,
47+
"optimization": {
48+
"scripts": true,
49+
"styles": {
50+
"minify": true,
51+
"inlineCritical": true
52+
},
53+
"fonts": false
54+
},
4255
"outputHashing": "all",
4356
"sourceMap": false,
4457
"namedChunks": false,
@@ -48,8 +61,8 @@
4861
"budgets": [
4962
{
5063
"type": "initial",
51-
"maximumWarning": "2mb",
52-
"maximumError": "5mb"
64+
"maximumWarning": "12mb",
65+
"maximumError": "15mb"
5366
},
5467
{
5568
"type": "anyComponentStyle",
@@ -65,44 +78,75 @@
6578
"with": "src/environments/environment.hmr.ts"
6679
}
6780
]
81+
},
82+
"backend": {
83+
"fileReplacements": [
84+
{
85+
"replace": "src/environments/environment.ts",
86+
"with": "src/environments/environment.backend.ts"
87+
}
88+
]
6889
}
6990
}
7091
},
7192
"serve": {
7293
"builder": "@angular-devkit/build-angular:dev-server",
7394
"options": {
74-
"browserTarget": "angular-material-admin:build",
75-
"port": 3000
95+
"port": 3000,
96+
"buildTarget": "angular-material-admin:build"
7697
},
7798
"configurations": {
7899
"production": {
79-
"browserTarget": "angular-material-admin:build:production"
100+
"buildTarget": "angular-material-admin:build:production"
80101
},
81102
"hmr": {
82103
"hmr": true,
83-
"browserTarget": "angular-material-admin:build:hmr"
104+
"buildTarget": "angular-material-admin:build:hmr"
105+
},
106+
"backend": {
107+
"buildTarget": "angular-material-admin:build:backend"
84108
}
85109
}
86110
},
87111
"extract-i18n": {
88112
"builder": "@angular-devkit/build-angular:extract-i18n",
89113
"options": {
90-
"browserTarget": "angular-material-admin:build"
114+
"buildTarget": "angular-material-admin:build"
91115
}
92116
},
93117
"lint": {
94-
"builder": "@angular-devkit/build-angular:tslint",
118+
"builder": "@angular-eslint/builder:lint",
95119
"options": {
96-
"tsConfig": [
97-
"tsconfig.app.json",
98-
"tsconfig.spec.json",
99-
"e2e/tsconfig.json"
100-
],
101-
"exclude": ["**/node_modules/**"]
120+
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
102121
}
103122
}
104123
}
105124
}
106125
},
107-
"defaultProject": "angular-material-admin"
126+
"schematics": {
127+
"@schematics/angular:component": {
128+
"type": "component"
129+
},
130+
"@schematics/angular:directive": {
131+
"type": "directive"
132+
},
133+
"@schematics/angular:service": {
134+
"type": "service"
135+
},
136+
"@schematics/angular:guard": {
137+
"typeSeparator": "."
138+
},
139+
"@schematics/angular:interceptor": {
140+
"typeSeparator": "."
141+
},
142+
"@schematics/angular:module": {
143+
"typeSeparator": "."
144+
},
145+
"@schematics/angular:pipe": {
146+
"typeSeparator": "."
147+
},
148+
"@schematics/angular:resolver": {
149+
"typeSeparator": "."
150+
}
151+
}
108152
}

e2e/playwright.config.cjs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Smoke config with built-in web server bootstrap for local and CI runs.
2+
const { defineConfig } = require('@playwright/test');
3+
4+
module.exports = defineConfig({
5+
testDir: '.',
6+
testMatch: /.*\.spec\.js/,
7+
timeout: 30_000,
8+
expect: {
9+
timeout: 10_000,
10+
},
11+
use: {
12+
baseURL: process.env.E2E_BASE_URL || 'http://127.0.0.1:3001',
13+
headless: true,
14+
trace: 'on-first-retry',
15+
},
16+
webServer: {
17+
command: 'npm run start:nobackend -- --host 127.0.0.1 --port 3001',
18+
url: process.env.E2E_BASE_URL || 'http://127.0.0.1:3001',
19+
reuseExistingServer: !process.env.CI,
20+
timeout: 300_000,
21+
},
22+
reporter: [['list']],
23+
});

e2e/smoke.spec.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
const { test, expect } = require('@playwright/test');
2+
3+
async function mockAuthenticatedSession(page) {
4+
await page.addInitScript(() => {
5+
window.localStorage.setItem('token', 'e2e-token');
6+
window.localStorage.setItem(
7+
'user',
8+
JSON.stringify({ email: 'admin@flatlogic.com' }),
9+
);
10+
});
11+
}
12+
13+
async function openProtectedRoute(page, path) {
14+
await page.goto('/dashboard', { waitUntil: 'networkidle' });
15+
await page.evaluate((targetPath) => {
16+
window.history.pushState({}, '', targetPath);
17+
window.dispatchEvent(new PopStateEvent('popstate'));
18+
}, path);
19+
}
20+
21+
test.describe('Core smoke', () => {
22+
test('login page renders', async ({ page }) => {
23+
await page.goto('/login');
24+
await expect(page).toHaveURL(/\/login$/);
25+
await expect(page.getByRole('button', { name: /login/i })).toBeVisible();
26+
});
27+
28+
test('dashboard page renders', async ({ page }) => {
29+
await mockAuthenticatedSession(page);
30+
await page.goto('/dashboard');
31+
await expect(page).toHaveURL(/\/dashboard$/);
32+
await expect(page.getByText(/overview/i).first()).toBeVisible();
33+
});
34+
35+
test('users list route renders', async ({ page }) => {
36+
await mockAuthenticatedSession(page);
37+
await openProtectedRoute(page, '/admin/users');
38+
await expect(page).toHaveURL(/\/admin\/users(\/list)?$/);
39+
await expect(
40+
page.locator(
41+
'button:has-text("New"), button:has-text("Add filter"), button:has-text("Add"), p.table-title:has-text("Users"), table.table, table[mat-table]',
42+
).first(),
43+
).toBeVisible();
44+
});
45+
46+
test('users list -> create -> back to list', async ({ page }) => {
47+
await mockAuthenticatedSession(page);
48+
await openProtectedRoute(page, '/admin/users');
49+
await expect(page).toHaveURL(/\/admin\/users(\/list)?$/);
50+
51+
await page.getByRole('button', { name: /^\s*new\s*$/i }).first().click();
52+
await expect(page).toHaveURL(/\/admin\/users\/new$/);
53+
await expect(
54+
page.locator('h4:has-text("New Users"), button:has-text("Create")').first(),
55+
).toBeVisible();
56+
57+
await page.getByRole('button', { name: /^\s*cancel\s*$/i }).first().click();
58+
await expect(page).toHaveURL(/\/admin\/users(\/list)?$/);
59+
});
60+
61+
test('profile route renders', async ({ page }) => {
62+
await mockAuthenticatedSession(page);
63+
await openProtectedRoute(page, '/user/profile');
64+
await expect(page).toHaveURL(/\/user\/profile$/);
65+
await expect(
66+
page.locator('p:has-text("Views"), p:has-text("Updates")').first(),
67+
).toBeVisible();
68+
});
69+
70+
test('change-password route renders', async ({ page }) => {
71+
await mockAuthenticatedSession(page);
72+
await openProtectedRoute(page, '/app/change-password');
73+
await expect(page).toHaveURL(/\/app\/change-password$/);
74+
await expect(page.getByRole('heading', { name: /change password/i })).toBeVisible();
75+
await expect(
76+
page.getByRole('button', { name: /change password/i }).first(),
77+
).toBeVisible();
78+
});
79+
});

0 commit comments

Comments
 (0)