From d28625c6a47c595f16c059147814e3e130305cac Mon Sep 17 00:00:00 2001
From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Date: Wed, 8 Jul 2026 09:53:20 +0000
Subject: [PATCH 1/4] Migrate app from Angular 9 to React 18 + TypeScript with
Vite
Co-Authored-By: Eashan Sinha
---
.gitignore | 3 +
.travis.yml | 3 +-
README.md | 14 +-
angular.json | 134 -
browserslist | 12 -
e2e/protractor.conf.js | 32 -
e2e/src/app.e2e-spec.ts | 23 -
e2e/src/app.po.ts | 11 -
e2e/tsconfig.json | 13 -
eslint.config.js | 25 +
src/index.html => index.html | 35 +-
karma.conf.js | 32 -
ngsw-config.json | 29 -
package-lock.json | 9083 +++++++++++++++++
package.json | 70 +-
.../assets/icons/android-chrome-144x144.png | Bin
.../assets/icons/android-chrome-192x192.png | Bin
.../assets/icons/android-chrome-256x256.png | Bin
.../assets/icons/android-chrome-512x512.png | Bin
.../assets/icons/apple-touch-icon-120x120.png | Bin
.../assets/icons/apple-touch-icon-152x152.png | Bin
.../assets/icons/apple-touch-icon-180x180.png | Bin
.../assets/icons/apple-touch-icon-60x60.png | Bin
.../assets/icons/apple-touch-icon-76x76.png | Bin
.../assets/icons/apple-touch-icon.png | Bin
.../assets/icons/browserconfig.xml | 0
.../assets/icons/favicon-16x16.png | Bin
.../assets/icons/favicon-32x32.png | Bin
.../assets/icons/mstile-150x150.png | Bin
.../assets/icons/safari-pinned-tab.svg | 0
{src => public}/assets/images/cog.svg | 0
{src => public}/assets/images/logo-header.png | Bin
{src => public}/assets/images/logo.svg | 0
{src => public}/favicon.ico | Bin
{src => public}/manifest.json | 0
src/App.tsx | 55 +
src/app/app.component.html | 8 -
src/app/app.component.scss | 24 -
src/app/app.component.ts | 31 -
src/app/app.module.ts | 32 -
src/app/app.routes.ts | 43 -
src/app/core/core.module.ts | 13 -
src/app/core/footer/footer.component.html | 3 -
src/app/core/footer/footer.component.ts | 15 -
src/app/core/header/header.component.html | 25 -
src/app/core/header/header.component.scss | 149 -
src/app/core/header/header.component.ts | 28 -
src/app/core/settings/settings.component.html | 83 -
src/app/core/settings/settings.component.ts | 40 -
src/app/feeds/feed/feed.component.html | 24 -
src/app/feeds/feed/feed.component.scss | 108 -
src/app/feeds/feed/feed.component.ts | 49 -
src/app/feeds/item/item.component.html | 39 -
src/app/feeds/item/item.component.ts | 26 -
.../comment/comment.component.html | 22 -
.../comment/comment.component.scss | 85 -
.../item-details/comment/comment.component.ts | 19 -
.../item-details/item-details.component.html | 59 -
.../item-details/item-details.component.scss | 151 -
.../item-details/item-details.component.ts | 50 -
src/app/item-details/item-details.module.ts | 22 -
.../error-message.component.html | 12 -
.../error-message/error-message.component.ts | 16 -
.../components/loader/loader.component.html | 5 -
.../components/loader/loader.component.ts | 15 -
.../components/shared-components.module.ts | 11 -
src/app/shared/pipes/comment.pipe.ts | 15 -
src/app/shared/pipes/pipes.module.ts | 8 -
.../shared/services/hackernews-api.service.ts | 66 -
src/app/shared/services/settings.service.ts | 89 -
src/app/user/user.component.html | 19 -
src/app/user/user.component.scss | 89 -
src/app/user/user.component.ts | 37 -
src/app/user/user.module.ts | 21 -
src/components/CommentItem.tsx | 43 +
src/components/ErrorMessage.tsx | 21 +
src/components/Footer.tsx | 14 +
src/components/Header.tsx | 45 +
src/components/Item.test.tsx | 50 +
src/components/Item.tsx | 70 +
src/components/Loader.tsx | 9 +
src/components/SettingsPanel.tsx | 95 +
src/components/comment.scss | 89 +
.../error-message.scss} | 28 +-
.../footer.scss} | 7 +-
src/components/header.scss | 150 +
.../item.scss} | 15 +-
.../loader.scss} | 55 +-
.../settings.scss} | 33 +-
src/contexts/SettingsContext.tsx | 79 +
src/environments/environment.prod.ts | 3 -
src/environments/environment.ts | 16 -
src/main.ts | 12 -
src/main.tsx | 20 +
src/{app/shared => }/models/comment.ts | 2 +-
src/{app/shared => }/models/feed-type.type.ts | 0
src/{app/shared => }/models/poll-result.ts | 2 +-
src/{app/shared => }/models/settings.ts | 0
src/{app/shared => }/models/story.ts | 5 +-
src/{app/shared => }/models/user.ts | 2 +-
src/pages/Feed.tsx | 74 +
src/pages/ItemDetails.tsx | 134 +
src/pages/UserPage.tsx | 59 +
src/pages/feed.scss | 106 +
src/pages/item-details.scss | 153 +
src/pages/user.scss | 94 +
src/polyfills.ts | 63 -
src/services/hackernews-api.ts | 34 +
src/styles.scss | 43 -
src/{app/shared/scss => styles}/_media.scss | 0
.../scss => styles}/_theme_variables.scss | 0
src/{app/shared/scss => styles}/_themes.scss | 9 +-
src/styles/main.scss | 66 +
src/test.ts | 20 -
src/test/setup.ts | 16 +
src/utils/comment-label.test.ts | 15 +
src/utils/comment-label.ts | 6 +
tsconfig.app.json | 29 +-
tsconfig.app.tsbuildinfo | 1 +
tsconfig.json | 25 +-
tsconfig.node.json | 20 +
tsconfig.node.tsbuildinfo | 1 +
tsconfig.spec.json | 18 -
tslint.json | 92 -
vite.config.ts | 45 +
yarn.lock | 8973 ----------------
126 files changed, 10826 insertions(+), 11260 deletions(-)
delete mode 100644 angular.json
delete mode 100644 browserslist
delete mode 100644 e2e/protractor.conf.js
delete mode 100644 e2e/src/app.e2e-spec.ts
delete mode 100644 e2e/src/app.po.ts
delete mode 100644 e2e/tsconfig.json
create mode 100644 eslint.config.js
rename src/index.html => index.html (65%)
delete mode 100644 karma.conf.js
delete mode 100644 ngsw-config.json
create mode 100644 package-lock.json
rename {src => public}/assets/icons/android-chrome-144x144.png (100%)
rename {src => public}/assets/icons/android-chrome-192x192.png (100%)
rename {src => public}/assets/icons/android-chrome-256x256.png (100%)
rename {src => public}/assets/icons/android-chrome-512x512.png (100%)
rename {src => public}/assets/icons/apple-touch-icon-120x120.png (100%)
rename {src => public}/assets/icons/apple-touch-icon-152x152.png (100%)
rename {src => public}/assets/icons/apple-touch-icon-180x180.png (100%)
rename {src => public}/assets/icons/apple-touch-icon-60x60.png (100%)
rename {src => public}/assets/icons/apple-touch-icon-76x76.png (100%)
rename {src => public}/assets/icons/apple-touch-icon.png (100%)
rename {src => public}/assets/icons/browserconfig.xml (100%)
rename {src => public}/assets/icons/favicon-16x16.png (100%)
rename {src => public}/assets/icons/favicon-32x32.png (100%)
rename {src => public}/assets/icons/mstile-150x150.png (100%)
rename {src => public}/assets/icons/safari-pinned-tab.svg (100%)
rename {src => public}/assets/images/cog.svg (100%)
rename {src => public}/assets/images/logo-header.png (100%)
rename {src => public}/assets/images/logo.svg (100%)
rename {src => public}/favicon.ico (100%)
rename {src => public}/manifest.json (100%)
create mode 100644 src/App.tsx
delete mode 100644 src/app/app.component.html
delete mode 100644 src/app/app.component.scss
delete mode 100644 src/app/app.component.ts
delete mode 100644 src/app/app.module.ts
delete mode 100644 src/app/app.routes.ts
delete mode 100644 src/app/core/core.module.ts
delete mode 100644 src/app/core/footer/footer.component.html
delete mode 100644 src/app/core/footer/footer.component.ts
delete mode 100644 src/app/core/header/header.component.html
delete mode 100644 src/app/core/header/header.component.scss
delete mode 100644 src/app/core/header/header.component.ts
delete mode 100644 src/app/core/settings/settings.component.html
delete mode 100644 src/app/core/settings/settings.component.ts
delete mode 100644 src/app/feeds/feed/feed.component.html
delete mode 100644 src/app/feeds/feed/feed.component.scss
delete mode 100644 src/app/feeds/feed/feed.component.ts
delete mode 100644 src/app/feeds/item/item.component.html
delete mode 100644 src/app/feeds/item/item.component.ts
delete mode 100644 src/app/item-details/comment/comment.component.html
delete mode 100644 src/app/item-details/comment/comment.component.scss
delete mode 100644 src/app/item-details/comment/comment.component.ts
delete mode 100644 src/app/item-details/item-details.component.html
delete mode 100644 src/app/item-details/item-details.component.scss
delete mode 100644 src/app/item-details/item-details.component.ts
delete mode 100644 src/app/item-details/item-details.module.ts
delete mode 100644 src/app/shared/components/error-message/error-message.component.html
delete mode 100644 src/app/shared/components/error-message/error-message.component.ts
delete mode 100644 src/app/shared/components/loader/loader.component.html
delete mode 100644 src/app/shared/components/loader/loader.component.ts
delete mode 100644 src/app/shared/components/shared-components.module.ts
delete mode 100644 src/app/shared/pipes/comment.pipe.ts
delete mode 100644 src/app/shared/pipes/pipes.module.ts
delete mode 100644 src/app/shared/services/hackernews-api.service.ts
delete mode 100644 src/app/shared/services/settings.service.ts
delete mode 100644 src/app/user/user.component.html
delete mode 100644 src/app/user/user.component.scss
delete mode 100644 src/app/user/user.component.ts
delete mode 100644 src/app/user/user.module.ts
create mode 100644 src/components/CommentItem.tsx
create mode 100644 src/components/ErrorMessage.tsx
create mode 100644 src/components/Footer.tsx
create mode 100644 src/components/Header.tsx
create mode 100644 src/components/Item.test.tsx
create mode 100644 src/components/Item.tsx
create mode 100644 src/components/Loader.tsx
create mode 100644 src/components/SettingsPanel.tsx
create mode 100644 src/components/comment.scss
rename src/{app/shared/components/error-message/error-message.component.scss => components/error-message.scss} (81%)
rename src/{app/core/footer/footer.component.scss => components/footer.scss} (64%)
create mode 100644 src/components/header.scss
rename src/{app/feeds/item/item.component.scss => components/item.scss} (89%)
rename src/{app/shared/components/loader/loader.component.scss => components/loader.scss} (55%)
rename src/{app/core/settings/settings.component.scss => components/settings.scss} (73%)
create mode 100644 src/contexts/SettingsContext.tsx
delete mode 100644 src/environments/environment.prod.ts
delete mode 100644 src/environments/environment.ts
delete mode 100644 src/main.ts
create mode 100644 src/main.tsx
rename src/{app/shared => }/models/comment.ts (85%)
rename src/{app/shared => }/models/feed-type.type.ts (100%)
rename src/{app/shared => }/models/poll-result.ts (58%)
rename src/{app/shared => }/models/settings.ts (100%)
rename src/{app/shared => }/models/story.ts (86%)
rename src/{app/shared => }/models/user.ts (83%)
create mode 100644 src/pages/Feed.tsx
create mode 100644 src/pages/ItemDetails.tsx
create mode 100644 src/pages/UserPage.tsx
create mode 100644 src/pages/feed.scss
create mode 100644 src/pages/item-details.scss
create mode 100644 src/pages/user.scss
delete mode 100644 src/polyfills.ts
create mode 100644 src/services/hackernews-api.ts
delete mode 100644 src/styles.scss
rename src/{app/shared/scss => styles}/_media.scss (100%)
rename src/{app/shared/scss => styles}/_theme_variables.scss (100%)
rename src/{app/shared/scss => styles}/_themes.scss (95%)
create mode 100644 src/styles/main.scss
delete mode 100644 src/test.ts
create mode 100644 src/test/setup.ts
create mode 100644 src/utils/comment-label.test.ts
create mode 100644 src/utils/comment-label.ts
create mode 100644 tsconfig.app.tsbuildinfo
create mode 100644 tsconfig.node.json
create mode 100644 tsconfig.node.tsbuildinfo
delete mode 100644 tsconfig.spec.json
delete mode 100644 tslint.json
create mode 100644 vite.config.ts
delete mode 100644 yarn.lock
diff --git a/.gitignore b/.gitignore
index f4f46a5fe..2c8ef52be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,9 @@ speed-measure-plugin.json
!.vscode/extensions.json
.history/*
+# PWA dev service worker output
+dev-dist
+
# misc
/.sass-cache
/connect.lock
diff --git a/.travis.yml b/.travis.yml
index ff155b5fa..5c5a4e170 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: node_js
node_js:
- - "6.9"
+ - "20"
branches:
only:
@@ -8,7 +8,6 @@ branches:
before_script:
- npm install -g firebase-tools
- - npm install -g @angular/cli
script:
- npm run build
diff --git a/README.md b/README.md
index 67cef9e61..5faa19e96 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
- A progressive Hacker News client built with Angular
+ A progressive Hacker News client built with React and TypeScript
@@ -43,7 +43,7 @@
## Offline Support
-This app uses [Workbox](https://workboxjs.org/) to generate a service worker as part of the build step to load quickly and work offline.
+This app uses [Workbox](https://developer.chrome.com/docs/workbox) (via [vite-plugin-pwa](https://vite-pwa-org.netlify.app/)) to generate a service worker as part of the build step to load quickly and work offline.
## Manifest
@@ -73,16 +73,16 @@ Feel free to send me feedback on [twitter](https://twitter.com/hdjirdeh) or [fil
## Build process
-Note: This project has been ejected (with AOT + production settings) in order to customize Webpack configurations.
+This project is built with [Vite](https://vitejs.dev/), React and TypeScript.
- Clone or download the repo
- `npm install`
- - `npm start` to run the application with webpack-dev-server or `npm build` to kick off a fresh build and update the output directory (`dist/`)
+ - `npm start` to run the application with the Vite dev server, or `npm run build` to kick off a fresh production build into the output directory (`dist/`)
+ - `npm test` to run the unit tests with [Vitest](https://vitest.dev/), and `npm run lint` to lint with ESLint
Note: Any Service Worker changes will not be reflected when you run the application locally in development. To test service worker changes:
- - `npm build`
- - `npm run precache` to generate the service worker file
- - `npm run static-serve` to load the application along with the service worker asset using [live-server](https://github.com/tapio/live-server)
+ - `npm run build`
+ - `npm run preview` to serve the production build (including the generated service worker) locally
## Contributors
diff --git a/angular.json b/angular.json
deleted file mode 100644
index bae858c47..000000000
--- a/angular.json
+++ /dev/null
@@ -1,134 +0,0 @@
-{
- "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
- "version": 1,
- "newProjectRoot": "projects",
- "projects": {
- "angular-hnpwa": {
- "projectType": "application",
- "schematics": {
- "@schematics/angular:component": {
- "style": "scss"
- }
- },
- "root": "",
- "sourceRoot": "src",
- "prefix": "app",
- "architect": {
- "build": {
- "builder": "@angular-devkit/build-angular:browser",
- "options": {
- "aot": true,
- "outputPath": "dist/angular-hnpwa",
- "index": "src/index.html",
- "main": "src/main.ts",
- "polyfills": "src/polyfills.ts",
- "tsConfig": "tsconfig.app.json",
- "assets": [
- "src/favicon.ico",
- "src/assets",
- "src/manifest.json",
- "src/manifest.webmanifest"
- ],
- "styles": [
- "src/styles.scss"
- ],
- "scripts": []
- },
- "configurations": {
- "production": {
- "fileReplacements": [
- {
- "replace": "src/environments/environment.ts",
- "with": "src/environments/environment.prod.ts"
- }
- ],
- "optimization": true,
- "outputHashing": "all",
- "sourceMap": true,
- "extractCss": true,
- "namedChunks": true,
- "aot": true,
- "extractLicenses": true,
- "vendorChunk": false,
- "buildOptimizer": true,
- "budgets": [
- {
- "type": "initial",
- "maximumWarning": "2mb",
- "maximumError": "5mb"
- },
- {
- "type": "anyComponentStyle",
- "maximumWarning": "6kb"
- }
- ],
- "serviceWorker": true,
- "ngswConfigPath": "ngsw-config.json"
- }
- }
- },
- "serve": {
- "builder": "@angular-devkit/build-angular:dev-server",
- "options": {
- "browserTarget": "angular-hnpwa:build"
- },
- "configurations": {
- "production": {
- "browserTarget": "angular-hnpwa:build:production"
- }
- }
- },
- "extract-i18n": {
- "builder": "@angular-devkit/build-angular:extract-i18n",
- "options": {
- "browserTarget": "angular-hnpwa:build"
- }
- },
- "test": {
- "builder": "@angular-devkit/build-angular:karma",
- "options": {
- "main": "src/test.ts",
- "polyfills": "src/polyfills.ts",
- "tsConfig": "tsconfig.spec.json",
- "karmaConfig": "karma.conf.js",
- "assets": [
- "src/favicon.ico",
- "src/assets",
- "src/manifest.webmanifest"
- ],
- "styles": [
- "src/styles.scss"
- ],
- "scripts": []
- }
- },
- "lint": {
- "builder": "@angular-devkit/build-angular:tslint",
- "options": {
- "tsConfig": [
- "tsconfig.app.json",
- "tsconfig.spec.json",
- "e2e/tsconfig.json"
- ],
- "exclude": [
- "**/node_modules/**"
- ]
- }
- },
- "e2e": {
- "builder": "@angular-devkit/build-angular:protractor",
- "options": {
- "protractorConfig": "e2e/protractor.conf.js",
- "devServerTarget": "angular-hnpwa:serve"
- },
- "configurations": {
- "production": {
- "devServerTarget": "angular-hnpwa:serve:production"
- }
- }
- }
- }
- }
- },
- "defaultProject": "angular-hnpwa"
-}
diff --git a/browserslist b/browserslist
deleted file mode 100644
index 80848532e..000000000
--- a/browserslist
+++ /dev/null
@@ -1,12 +0,0 @@
-# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
-# For additional information regarding the format and rule options, please see:
-# https://github.com/browserslist/browserslist#queries
-
-# You can see what browsers were selected by your queries by running:
-# npx browserslist
-
-> 0.5%
-last 2 versions
-Firefox ESR
-not dead
-not IE 9-11 # For IE 9-11 support, remove 'not'.
\ No newline at end of file
diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js
deleted file mode 100644
index 73e4e6806..000000000
--- a/e2e/protractor.conf.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// @ts-check
-// Protractor configuration file, see link for more information
-// https://github.com/angular/protractor/blob/master/lib/config.ts
-
-const { SpecReporter } = require('jasmine-spec-reporter');
-
-/**
- * @type { import("protractor").Config }
- */
-exports.config = {
- allScriptsTimeout: 11000,
- specs: [
- './src/**/*.e2e-spec.ts'
- ],
- capabilities: {
- 'browserName': 'chrome'
- },
- directConnect: true,
- baseUrl: 'http://localhost:4200/',
- framework: 'jasmine',
- jasmineNodeOpts: {
- showColors: true,
- defaultTimeoutInterval: 30000,
- print: function() {}
- },
- onPrepare() {
- require('ts-node').register({
- project: require('path').join(__dirname, './tsconfig.json')
- });
- jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
- }
-};
\ No newline at end of file
diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts
deleted file mode 100644
index 0897abab0..000000000
--- a/e2e/src/app.e2e-spec.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { AppPage } from './app.po';
-import { browser, logging } from 'protractor';
-
-describe('workspace-project App', () => {
- let page: AppPage;
-
- beforeEach(() => {
- page = new AppPage();
- });
-
- it('should display welcome message', () => {
- page.navigateTo();
- expect(page.getTitleText()).toEqual('Welcome to angular-hnpwa!');
- });
-
- afterEach(async () => {
- // Assert that there are no errors emitted from the browser
- const logs = await browser.manage().logs().get(logging.Type.BROWSER);
- expect(logs).not.toContain(jasmine.objectContaining({
- level: logging.Level.SEVERE,
- } as logging.Entry));
- });
-});
diff --git a/e2e/src/app.po.ts b/e2e/src/app.po.ts
deleted file mode 100644
index 5776aa9eb..000000000
--- a/e2e/src/app.po.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { browser, by, element } from 'protractor';
-
-export class AppPage {
- navigateTo() {
- return browser.get(browser.baseUrl) as Promise;
- }
-
- getTitleText() {
- return element(by.css('app-root h1')).getText() as Promise;
- }
-}
diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json
deleted file mode 100644
index 39b800f78..000000000
--- a/e2e/tsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "compilerOptions": {
- "outDir": "../out-tsc/e2e",
- "module": "commonjs",
- "target": "es5",
- "types": [
- "jasmine",
- "jasminewd2",
- "node"
- ]
- }
-}
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 000000000..209849a3a
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,25 @@
+import js from '@eslint/js';
+import globals from 'globals';
+import reactHooks from 'eslint-plugin-react-hooks';
+import reactRefresh from 'eslint-plugin-react-refresh';
+import tseslint from 'typescript-eslint';
+
+export default tseslint.config(
+ { ignores: ['dist', 'dev-dist'] },
+ {
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
+ files: ['**/*.{ts,tsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
+ },
+ }
+);
diff --git a/src/index.html b/index.html
similarity index 65%
rename from src/index.html
rename to index.html
index 05e798ea4..1bb65abcd 100644
--- a/src/index.html
+++ b/index.html
@@ -3,24 +3,23 @@
Angular 2 HN
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -43,17 +42,14 @@
-
-
-
-
+

@@ -74,5 +70,6 @@