diff --git a/.env b/.env new file mode 100644 index 000000000..7e424154e --- /dev/null +++ b/.env @@ -0,0 +1,24 @@ +REACT_APP_KERROKANTASI_API_BASE="https://kerrokantasi.api.dev.hel.ninja" +REACT_APP_HERO_IMAGE_URL="http://materialbank.myhelsinki.fi/detail/1192/download/7" +REACT_APP_OPENID_CLIENT_ID="kerrokantasi-ui-dev" +REACT_APP_OPENID_AUDIENCE="kerrokantasi-api-dev" +REACT_APP_OPENID_AUTHORITY="https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus" +REACT_APP_OPENID_APITOKEN_URL="https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus/protocol/openid-connect/token" +REACT_APP_OPENID_SCOPE="openid profile email" +REACT_APP_ENABLE_STRONG_AUTH=false +REACT_APP_CITY_CONFIG="cities/helsinki" +REACT_APP_SHOW_ACCESSIBILITY_INFO=true +REACT_APP_SHOW_SOCIAL_MEDIA_SHARING=true +REACT_APP_ENABLE_COOKIES=true +REACT_APP_ENABLE_COOKIEBOT=false +REACT_APP_COOKIEBOT_DATA_CBID= +REACT_APP_ADMIN_HELP_URL="https://drive.google.com/open?id=1vtUNzbJNVcp7K9JPrE6XP8yTmkBLW3N3FGEsR1NbbIw" +REACT_APP_EMPTY_COMMENT_STRING="-" +REACT_APP_MAINTENANCE_SHOW_NOTIFICATION=false +REACT_APP_MAINTENANCE_DISABLE_LOGIN=false +REACT_APP_MAINTENANCE_DISABLE_COMMENTS=false +REACT_APP_MATOMO_COOKIE_DOMAIN= +REACT_APP_MATOMO_DOMAINS= +REACT_APP_MATOMO_SIDE_ID= +REACT_APP_MATOMO_SCRIPT_URL= +REACT_APP_MATOMO_SCRIPT_FILENAME= diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 000000000..ac48f88dd --- /dev/null +++ b/.env.local.example @@ -0,0 +1,24 @@ +REACT_APP_KERROKANTASI_API_BASE="http://localhost:8080/" +REACT_APP_HERO_IMAGE_URL= +REACT_APP_OPENID_CLIENT_ID= +REACT_APP_OPENID_AUDIENCE= +REACT_APP_OPENID_AUTHORITY= +REACT_APP_OPENID_APITOKEN_URL= +REACT_APP_OPENID_SCOPE= +REACT_APP_ENABLE_STRONG_AUTH=false +REACT_APP_CITY_CONFIG= +REACT_APP_SHOW_ACCESSIBILITY_INFO=false +REACT_APP_SHOW_SOCIAL_MEDIA_SHARING=true +REACT_APP_ENABLE_COOKIES=true +REACT_APP_ENABLE_COOKIEBOT=false +REACT_APP_COOKIEBOT_DATA_CBID= +REACT_APP_ADMIN_HELP_URL= +REACT_APP_EMPTY_COMMENT_STRING= +REACT_APP_MAINTENANCE_SHOW_NOTIFICATION=false +REACT_APP_MAINTENANCE_DISABLE_LOGIN=false +REACT_APP_MAINTENANCE_DISABLE_COMMENTS=false +REACT_APP_MATOMO_COOKIE_DOMAIN= +REACT_APP_MATOMO_DOMAINS= +REACT_APP_MATOMO_SIDE_ID= +REACT_APP_MATOMO_SCRIPT_URL= +REACT_APP_MATOMO_SCRIPT_FILENAME= diff --git a/.env.test b/.env.test new file mode 100644 index 000000000..316639dd8 --- /dev/null +++ b/.env.test @@ -0,0 +1 @@ +REACT_APP_KERROKANTASI_API_BASE='http://example.com/' diff --git a/.eslintrc.json b/.eslintrc.json index 2228bd727..0348f748e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,8 @@ { "env": { "browser": true, - "node": true + "node": true, + "vitest-globals/env": true }, "extends": [ "react-app", @@ -10,6 +11,7 @@ "plugin:import/warnings", "airbnb", "prettier", + "plugin:vitest-globals/recommended", "plugin:sonarjs/recommended" ], "parser": "@babel/eslint-parser", diff --git a/.gitignore b/.gitignore index 001601c62..4b0c42e60 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,12 @@ report/ /playwright-report/ /blob-report/ /playwright/.cache/ + +.env.local +/build + +public/env-config.js +public/test-env-config.js + +cities/**/env-config.js +cities/**/test-env-config.js diff --git a/.husky/pre-commit b/.husky/pre-commit index bfc028b72..bc3cea573 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -yarn test +yarn test --watch=false diff --git a/.prod/nginx.conf b/.prod/nginx.conf new file mode 100644 index 000000000..662d837fd --- /dev/null +++ b/.prod/nginx.conf @@ -0,0 +1,40 @@ +pid /tmp/nginx.pid; #for running as non-root + +events { + worker_connections 4096; ## Default: 1024 +} +http { + #for running as non-root + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server { + listen 8086; + server_name localhost; + + location /healthz { + return 200 'OK'; + } + + location /readiness { + return 200 'OK'; + } + + location / { + root /usr/share/nginx/html; + try_files $uri /index.html; + include /etc/nginx/env/nginx_env.conf; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +} diff --git a/.prod/nginx_env.conf b/.prod/nginx_env.conf new file mode 100644 index 000000000..1c9f2f6d3 --- /dev/null +++ b/.prod/nginx_env.conf @@ -0,0 +1,2 @@ +# This is a placeholder file for additional environment configuration +# Actual content is defined by pipeline. diff --git a/Dockerfile b/Dockerfile index 04a1a2e02..ed8ddda47 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -# ========================================== -FROM registry.access.redhat.com/ubi8/nodejs-18 AS deployable -# ========================================== +# =============================================== +FROM registry.access.redhat.com/ubi9/nodejs-18 as appbase +# =============================================== WORKDIR /app @@ -15,25 +15,80 @@ ARG NODE_ENV=production ENV NODE_ENV $NODE_ENV # Yarn -ENV YARN_VERSION 1.19.1 +ENV YARN_VERSION 1.22.19 RUN yarn policies set-version $YARN_VERSION # Most files from source tree are needed at runtime -COPY . /app/ +# COPY . /app/ RUN chown -R default:root /app # Install npm dependencies and build the bundle USER default -RUN yarn cache clean --force -RUN yarn +COPY --chown=default:root package.json yarn.lock /app/ +COPY --chown=default:root ./scripts /app/scripts +COPY --chown=default:root ./public /app/public +COPY --chown=default:root ./cities /app/cities +COPY --chown=default:root ./assets /app/assets + +RUN yarn config set network-timeout 300000 +RUN yarn && yarn cache clean --force + +COPY --chown=default:root index.html vite.config.mjs .eslintrc.json .eslintignore .prettierrc .env* /app/ +COPY --chown=default:root ./src /app/src + +# ============================= +FROM appbase as development +# ============================= + +WORKDIR /app + +# Set NODE_ENV to development in the development container +ARG NODE_ENV=development +ENV NODE_ENV $NODE_ENV + +# Bake package.json start command into the image +CMD yarn start + +# =================================== +FROM appbase as staticbuilder +# =================================== + +WORKDIR /app + RUN yarn build -# Run the frontend server using arbitrary user to simulate -# Openshift when running using fe. Docker. Under actual -# Openshift, the user will be random -USER 158435:0 -CMD [ "yarn", "start" ] +# ============================= +FROM registry.access.redhat.com/ubi9/nginx-122 as production +# ============================= + +USER root + +RUN chgrp -R 0 /usr/share/nginx/html && \ + chmod -R g=u /usr/share/nginx/html + +# Copy static build +COPY --from=staticbuilder /app/build /usr/share/nginx/html + +# Copy nginx config +COPY .prod/nginx.conf /etc/nginx/nginx.conf +RUN mkdir /etc/nginx/env +COPY .prod/nginx_env.conf /etc/nginx/env/ + +WORKDIR /usr/share/nginx/html + +# Copy default environment config and setup script +COPY ./scripts/env.sh . +COPY .env . + +# Copy package.json so env.sh can read it +COPY package.json . + +RUN chmod +x env.sh + +USER 1001 + +CMD ["/bin/bash", "-c", "/usr/share/nginx/html/env.sh && nginx -g \"daemon off;\""] # Expose port 8086 EXPOSE 8086 diff --git a/README.md b/README.md index a7aad6c19..d651eb946 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Kerrokantasi UI -[![codecov](https://codecov.io/gh/City-of-Helsinki/kerrokantasi-ui/branch/main/graph/badge.svg)](https://codecov.io/gh/City-of-Helsinki/kerrokantasi-ui) - Kerrokantasi UI is the user interface powering kerrokantasi.hel.fi service. It is a full featured interface for both answering and creating questionnaires as supported by Kerrokantasi API. @@ -16,47 +14,61 @@ supported by Kerrokantasi API. ### Configuration -`config_dev.toml` is used for configuration when NODE_ENV != "production". It -is in TOML-format, which for our purposes is `Key=Value` format. - -When NODE_ENV=="production", only environment variables are used for -configuration. This is done because we've had several painful accidents -with leftover configuration files. The environment variables are named -identically to the ones used in config_dev.toml. Do note that the variables -are case insensitive, ie. `KeRRokanTasi_aPi_bASe` is a valid name. Go wild! - -In the repository root there is `config_dev.toml.example` which contains -every setting and comments explaining their use. If you only want to give -kerrokantasi-ui a test, all configuration you need to do is: -`cp config_dev.toml.example config_dev.toml`. That will give you a partially -working configuration for browsing test questionnaires in our test API. +`.env` is the preliminary configuration file. To use local configuration, create `.env.local` which will override other configuration (see `.env.local.example`). ### Running development server ``` -yarn build yarn start ``` -No separate build step is required to start the development server. -It is somewhat unstable, but provides hot reloading: - -``` -yarn run dev -``` +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. -The server will output the URL for accessing kerrokantasi-ui. +You will also see any lint errors in the console. -### Running in production +https://vitejs.dev/guide/cli.html#vite -You can use your favorite process manager to run `yarn build` and `yarn start`. -Node-specific managers can also directly run `compile(/index.js)` & `server(/index.js)`. +Scripts generates first environment variables to `public/env-config.js` with `scripts/update-runtime-env.js`, which contains the +actual used variables when running the app. App is not using default `process.env` way to refer of variables but +`window._env_` object. ### Other commands +```yarn build``` + +Builds the app for production to the `build` folder.
+It correctly bundles app in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [build](https://vitejs.dev/guide/cli.html#build) for more information. + +Note that running built application locally you need to generate also `public/env-config.js` file. It can be done with +`yarn update-runtime-env`. By default it's generated for development environment if no `NODE_ENV` is set. + +```yarn run test``` + +See the section about [running tests](https://vitest.dev/guide/) for more information. + +Scripts generates first environment variables to `public/test-env-config.js` with `scripts/update-runtime-env.js`, which contains the +actual used variables when running the app. App is not using default `process.env` way to refer of variables but +`window._env_` object. + +```yarn test:e2e``` + +Runs end-to-end tests using [Playwright](https://playwright.dev). + +It is recommended to run these tests to ensure the overall functionality and user experience of the application. + +```yarn update-runtime-env``` + +Generates variable object used when app is running. Generated object is stored at `public/env-config.js` and available +as `window._env_` object. + +Generation uses values from either +[environment variables or files](https://vitejs.dev/guide/env-and-mode.html). -- `yarn run fetch-plugins`: fetch optional plugins (see below) -- `yarn run test`: run tests -- `yarn test -- -u` update tests +At the production deployment same generation is done with [`env.sh`](scripts/env.sh). ### Running service in Docker @@ -124,7 +136,7 @@ and execute the following four commands inside your docker container: #### Configure Tunnistamo to frontend -Change the following configuration in `config_dev.toml` +Change the following configuration in `.env.local` ``` kerrokantasi_api_base="http://localhost:8080" @@ -138,7 +150,7 @@ openid_scope="openid profile https://api.hel.fi/auth/kerrokantasi" #### Configure Helsinki Profiili to frontend -Change the following configuration in `config_dev.toml` +Change the following configuration in `.env.local` ``` kerrokantasi_api_base="http://localhost:8080" @@ -188,7 +200,7 @@ The styles are based on Bootstrap version 3 (Sass). ## Using the whitelabel theme assets -To have a non city specific theme, change the `city_config` config value to `whitelabel` +To have a non city specific theme, change the `REACT_APP_CITY_CONFIG` config value to `whitelabel` ## Installing city specific assets @@ -209,14 +221,14 @@ a solution to this, please fix. ``` 2. In the `kerrokantasi-ui` project run `yarn add ../` 3. Edit files in `kerrokantasi-ui/node_modules/` for changes to be reflected -4. Set the `city_config` config to `` +4. Set the `REACT_APP_CITY_CONFIG` config to `` **Production installation:** 1. Add the project to the local `kerrokantasi-ui` project either by installing it the same way as in the dev environment, or from GitHub or if the package is published to npm, then install it from there. -2. Set the `city_config` config to `` +2. Set the `REACT_APP_CITY_CONFIG` config to `` ## Creating city specific assets diff --git a/assets/sass/app.scss b/assets/sass/app.scss index 885266392..0e913ca2d 100644 --- a/assets/sass/app.scss +++ b/assets/sass/app.scss @@ -11,4 +11,4 @@ @import "kerrokantasi/kerrokantasi"; ///// HDS React base styles -@import "~hds-core/lib/base.min.css"; \ No newline at end of file +@import "hds-core/lib/base.min.css"; diff --git a/assets/sass/kerrokantasi/bootstrap.scss b/assets/sass/kerrokantasi/bootstrap.scss index b8a632f3f..fc2dd8232 100644 --- a/assets/sass/kerrokantasi/bootstrap.scss +++ b/assets/sass/kerrokantasi/bootstrap.scss @@ -1,44 +1,44 @@ ///// Bootstrap -@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/mixins"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/print"; -//@import "~bootstrap-sass/assets/stylesheets/bootstrap/glyphicons"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/scaffolding"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/type"; -//@import "~bootstrap-sass/assets/stylesheets/bootstrap/code"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/grid"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/tables"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/forms"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/buttons"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/component-animations"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/dropdowns"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/button-groups"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/input-groups"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/navs"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/navbar"; -//@import "~bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs"; -//@import "~bootstrap-sass/assets/stylesheets/bootstrap/pagination"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/pager"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/labels"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/badges"; -//@import "~bootstrap-sass/assets/stylesheets/bootstrap/jumbotron"; -//@import "~bootstrap-sass/assets/stylesheets/bootstrap/thumbnails"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/alerts"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/progress-bars"; -//@import "~bootstrap-sass/assets/stylesheets/bootstrap/media"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/list-group"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/panels"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-embed"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/wells"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/close"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/variables"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/mixins"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/normalize"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/print"; +//@import "bootstrap-sass/assets/stylesheets/bootstrap/glyphicons"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/scaffolding"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/type"; +//@import "bootstrap-sass/assets/stylesheets/bootstrap/code"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/grid"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/tables"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/forms"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/buttons"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/component-animations"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/dropdowns"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/button-groups"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/input-groups"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/navs"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/navbar"; +//@import "bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs"; +//@import "bootstrap-sass/assets/stylesheets/bootstrap/pagination"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/pager"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/labels"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/badges"; +//@import "bootstrap-sass/assets/stylesheets/bootstrap/jumbotron"; +//@import "bootstrap-sass/assets/stylesheets/bootstrap/thumbnails"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/alerts"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/progress-bars"; +//@import "bootstrap-sass/assets/stylesheets/bootstrap/media"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/list-group"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/panels"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/responsive-embed"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/wells"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/close"; ///// Bootstrap Components w/ JavaScript -@import "~bootstrap-sass/assets/stylesheets/bootstrap/modals"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/tooltip"; -//@import "~bootstrap-sass/assets/stylesheets/bootstrap/popovers"; -//@import "~bootstrap-sass/assets/stylesheets/bootstrap/carousel"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/modals"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/tooltip"; +//@import "bootstrap-sass/assets/stylesheets/bootstrap/popovers"; +//@import "bootstrap-sass/assets/stylesheets/bootstrap/carousel"; ///// Bootstrap Utility classes -@import "~bootstrap-sass/assets/stylesheets/bootstrap/utilities"; -@import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/utilities"; +@import "bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities"; diff --git a/assets/sass/kerrokantasi/kerrokantasi.scss b/assets/sass/kerrokantasi/kerrokantasi.scss index b9c0ee0c1..445082155 100644 --- a/assets/sass/kerrokantasi/kerrokantasi.scss +++ b/assets/sass/kerrokantasi/kerrokantasi.scss @@ -1,15 +1,15 @@ ///// Font Awesome -@import "~font-awesome/scss/font-awesome"; +@import "font-awesome/scss/font-awesome"; ///// Leaflet -@import "~leaflet/dist/leaflet.css"; -@import "~leaflet-draw/dist/leaflet.draw.css"; +@import "leaflet/dist/leaflet.css"; +@import "leaflet-draw/dist/leaflet.draw.css"; ///// React Datetime -@import "~react-datetime/css/react-datetime.css"; +@import "react-datetime/css/react-datetime.css"; ///// Alertify -@import "~alertifyjs/build/css/alertify.css"; +@import "alertifyjs/build/css/alertify.css"; ///// Kerrokantasi styles @import "common"; diff --git a/assets/sass/kerrokantasi/variables.scss b/assets/sass/kerrokantasi/variables.scss index d4367ade3..3283eea34 100644 --- a/assets/sass/kerrokantasi/variables.scss +++ b/assets/sass/kerrokantasi/variables.scss @@ -1,4 +1,4 @@ -$fa-font-path: "~font-awesome/fonts"; +$fa-font-path: "font-awesome/fonts"; $gray-100: #f8f9fa !default; $gray-200: #e9ecef !default; diff --git a/assets/settings.json b/assets/settings.json index 96604402a..f338f9c57 100644 --- a/assets/settings.json +++ b/assets/settings.json @@ -1,4 +1,7 @@ { + "meta": { + "title": "Kerrokantasi" + }, "footer": { "korosType": "calm" } diff --git a/cities/helsinki/assets/sass/app.scss b/cities/helsinki/assets/sass/app.scss index 14574b391..0eebd7b52 100644 --- a/cities/helsinki/assets/sass/app.scss +++ b/cities/helsinki/assets/sass/app.scss @@ -1,21 +1,21 @@ ///// Helsinki brand variables -@import "~hel-bootstrap-3/src/sass/helsinki-variables"; +@import "hel-bootstrap-3/src/sass/helsinki-variables"; ///// Application specific theme variables @import "hel/variables"; ///// Helsinki Bootstrap 3 theme variables -@import "~hel-bootstrap-3/src/sass/theme-bootstrap-variables"; +@import "hel-bootstrap-3/src/sass/theme-bootstrap-variables"; @import "../../../../assets/sass/kerrokantasi/variables"; @import "../../../../assets/sass/kerrokantasi/bootstrap"; ///// hel-bootstrap-3 Bootstrap overrides -@import "~hel-bootstrap-3/src/sass/theme-custom-styles"; +@import "hel-bootstrap-3/src/sass/theme-custom-styles"; ///// Kerrokantasi Base styles @import "../../../../assets/sass/kerrokantasi/kerrokantasi"; ///// HDS React base styles -@import "~hds-core/lib/base.min.css"; +@import "hds-core/lib/base.min.css"; ///// Helsinki overwrites diff --git a/cities/helsinki/assets/settings.json b/cities/helsinki/assets/settings.json index 87e894738..b4d1fa966 100644 --- a/cities/helsinki/assets/settings.json +++ b/cities/helsinki/assets/settings.json @@ -1,4 +1,7 @@ { + "meta": { + "title": "Kerrokantasi" + }, "footer": { "korosType": "basic" } diff --git a/cities/helsinki/assets/favicon/android-chrome-192x192.png b/cities/helsinki/public/android-chrome-192x192.png similarity index 100% rename from cities/helsinki/assets/favicon/android-chrome-192x192.png rename to cities/helsinki/public/android-chrome-192x192.png diff --git a/cities/helsinki/assets/favicon/android-chrome-512x512.png b/cities/helsinki/public/android-chrome-512x512.png similarity index 100% rename from cities/helsinki/assets/favicon/android-chrome-512x512.png rename to cities/helsinki/public/android-chrome-512x512.png diff --git a/cities/helsinki/assets/favicon/apple-touch-icon.png b/cities/helsinki/public/apple-touch-icon.png similarity index 100% rename from cities/helsinki/assets/favicon/apple-touch-icon.png rename to cities/helsinki/public/apple-touch-icon.png diff --git a/cities/helsinki/assets/favicon/browserconfig.xml b/cities/helsinki/public/browserconfig.xml similarity index 100% rename from cities/helsinki/assets/favicon/browserconfig.xml rename to cities/helsinki/public/browserconfig.xml diff --git a/cities/helsinki/assets/favicon/favicon-16x16.png b/cities/helsinki/public/favicon-16x16.png similarity index 100% rename from cities/helsinki/assets/favicon/favicon-16x16.png rename to cities/helsinki/public/favicon-16x16.png diff --git a/cities/helsinki/assets/favicon/favicon-32x32.png b/cities/helsinki/public/favicon-32x32.png similarity index 100% rename from cities/helsinki/assets/favicon/favicon-32x32.png rename to cities/helsinki/public/favicon-32x32.png diff --git a/cities/helsinki/assets/favicon/favicon.ico b/cities/helsinki/public/favicon.ico similarity index 100% rename from cities/helsinki/assets/favicon/favicon.ico rename to cities/helsinki/public/favicon.ico diff --git a/cities/helsinki/assets/favicon/manifest.json b/cities/helsinki/public/manifest.json similarity index 100% rename from cities/helsinki/assets/favicon/manifest.json rename to cities/helsinki/public/manifest.json diff --git a/cities/helsinki/assets/favicon/mstile-144x144.png b/cities/helsinki/public/mstile-144x144.png similarity index 100% rename from cities/helsinki/assets/favicon/mstile-144x144.png rename to cities/helsinki/public/mstile-144x144.png diff --git a/cities/helsinki/assets/favicon/mstile-150x150.png b/cities/helsinki/public/mstile-150x150.png similarity index 100% rename from cities/helsinki/assets/favicon/mstile-150x150.png rename to cities/helsinki/public/mstile-150x150.png diff --git a/cities/helsinki/assets/favicon/mstile-310x150.png b/cities/helsinki/public/mstile-310x150.png similarity index 100% rename from cities/helsinki/assets/favicon/mstile-310x150.png rename to cities/helsinki/public/mstile-310x150.png diff --git a/cities/helsinki/assets/favicon/mstile-310x310.png b/cities/helsinki/public/mstile-310x310.png similarity index 100% rename from cities/helsinki/assets/favicon/mstile-310x310.png rename to cities/helsinki/public/mstile-310x310.png diff --git a/cities/helsinki/assets/favicon/mstile-70x70.png b/cities/helsinki/public/mstile-70x70.png similarity index 100% rename from cities/helsinki/assets/favicon/mstile-70x70.png rename to cities/helsinki/public/mstile-70x70.png diff --git a/cities/helsinki/assets/favicon/safari-pinned-tab.svg b/cities/helsinki/public/safari-pinned-tab.svg similarity index 100% rename from cities/helsinki/assets/favicon/safari-pinned-tab.svg rename to cities/helsinki/public/safari-pinned-tab.svg diff --git a/compile/compile.js b/compile/compile.js deleted file mode 100644 index 11d432e10..000000000 --- a/compile/compile.js +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable no-console */ -import {getCompiler} from '../server/bundler'; -import getSettings from '../server/getSettings'; - -export default function compile() { - const settings = getSettings(); - let compiler = getCompiler(settings, true); - - compiler.run((err) => { - if (err) { - throw new Error(`Webpack error: ${err}`); - } - // Throw the webpack into the well (if this was the last reference to it, we reclaim plenty of memory) - compiler = null; - }); -} diff --git a/compile/index.js b/compile/index.js deleted file mode 100644 index 7b38e7452..000000000 --- a/compile/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable import/no-unresolved */ -/* eslint-disable global-require */ -/* eslint-disable no-empty */ -try { - require('heapdump'); -} catch (exc) { -} - -require('@babel/register'); -require('./compile').default(); diff --git a/conf/assetPaths.js b/conf/assetPaths.js deleted file mode 100644 index 496ac2c1a..000000000 --- a/conf/assetPaths.js +++ /dev/null @@ -1,24 +0,0 @@ -const getOptions = require('../server/getSettings').default; -const path = require('path'); - -const settings = getOptions(); - -let cityConfig; -if (!settings.city_config || settings.city_config === 'cities/helsinki') { - cityConfig = path.resolve(__dirname, `../cities/helsinki/`); -} else if (settings.city_config === 'whitelabel') { - cityConfig = path.resolve(__dirname, `../src`); -} else { - cityConfig = path.resolve(__dirname, `../node_modules/${settings.city_config}/`); -} -let cityAssets = path.resolve(cityConfig, 'assets/'); -const cityi18n = path.resolve(cityConfig, 'i18n/'); - -if (settings.city_config === 'whitelabel') { - cityAssets = path.resolve(cityConfig, '../assets/'); -} - -const cityImages = path.resolve(cityAssets, 'images/'); - - -module.exports = {cityConfig, cityAssets, cityi18n, cityImages}; diff --git a/conf/paths.js b/conf/paths.js deleted file mode 100644 index 4b40c0b1d..000000000 --- a/conf/paths.js +++ /dev/null @@ -1,13 +0,0 @@ -const path = require('path'); - -const ROOT = path.resolve(__dirname, '..'); -const SRC = path.resolve(ROOT, 'src'); -const ASSETS = path.resolve(ROOT, 'assets'); - -module.exports = { - ROOT, - SRC, - ASSETS, - ENTRY: './src/index.js', - OUTPUT: path.resolve(ROOT, 'dist'), -}; diff --git a/conf/webpack/common.js b/conf/webpack/common.js deleted file mode 100644 index 5d062bbaf..000000000 --- a/conf/webpack/common.js +++ /dev/null @@ -1,128 +0,0 @@ -const webpack = require('webpack'); -const paths = require('../paths'); -const path = require('path'); -const assetPaths = require('../assetPaths'); - - -const plugins = [ - new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /en|fi|sv/), -]; - -if (process.env.BUNDLE_ANALYZER) { - // eslint-disable-next-line import/no-extraneous-dependencies - const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); - plugins.push(new BundleAnalyzerPlugin()); -} - - -module.exports = { - context: paths.ROOT, - optimization: { - moduleIds: 'named', - }, - resolve: { - extensions: ['.js', '.jsx', '.sass', '.json'], - alias: { - '@city-config': assetPaths.cityConfig, - '@city-assets': assetPaths.cityAssets, - '@city-i18n': assetPaths.cityi18n, - '@city-images': assetPaths.cityImages, - 'kerrokantasi-ui': path.resolve(__dirname, '../../'), - 'kerrokantasi-ui-modules': path.resolve(__dirname, '../../node_modules'), - }, - fallback: { - "buffer": require.resolve("buffer/"), - "crypto": require.resolve("crypto-browserify"), - "stream": require.resolve("stream-browserify") - } - }, - entry: [ - 'babel-polyfill', - ], - output: { - hashFunction: "xxhash64", - path: paths.OUTPUT, - publicPath: '/', - filename: 'app.[contenthash].js', - chunkFilename: '[name].[contenthash].js' - }, - module: { - rules: [ - { - test: /\.png$/, - use: [ - { - loader: 'url-loader', - options: { - limit: 1000000, - mimetype: 'image/png', - } - } - ] - }, - { - test: /\.svg(\?v=.+)?$/, - type: 'asset/inline', - }, - { - test: /\.gif$/, - use: [ - { - loader: 'url-loader', - options: { - limit: 1000000, - mimetype: 'image/gif' - } - } - ] - }, - { - test: /\.jpg$/, - use: [ - { - loader: 'file-loader', - } - ] - }, - { - test: /\.woff(2)?(\?v=.+)?$/, - type: 'asset/resource' - }, - { - test: /\.(ttf|eot)(\?v=.+)?$/, - use: [ - { - loader: 'url-loader', - options: { - limit: 1000000, - mimetype: 'image/gif' - } - } - ] - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader', 'postcss-loader'] - }, - { - test: /\.scss$/, - use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'] - }, - { - test: /\.md$/, - use: [ - { - loader: 'html-loader', - options: { - esModule: false, - } - }, - { - loader: 'markdown-loader', - }, - ] - }, - ] - }, - plugins: plugins, -}; diff --git a/conf/webpack/dev.js b/conf/webpack/dev.js deleted file mode 100644 index c3405e7d5..000000000 --- a/conf/webpack/dev.js +++ /dev/null @@ -1,43 +0,0 @@ -const { merge } = require('webpack-merge'); -const webpack = require('webpack'); -const common = require('./common'); -const paths = require('../paths'); - -module.exports = merge(common, { - entry: [ - 'webpack-hot-middleware/client', - paths.ENTRY, - ], - devServer: { - host: '0.0.0.0' - }, - mode: 'development', - devtool: 'eval-cheap-module-source-map', - module: { - rules: [ - { - test: /\.(js|jsx)$/, - include: paths.SRC, - use: [ - { - loader: 'react-hot-loader/webpack', - options: { - presets: ['@babel/preset-react', '@babel/preset-env'] - }, - }, - { - loader: 'babel-loader', - } - ], - }, - ], - }, - plugins: [ - new webpack.DefinePlugin({ - __DEVTOOLS__: true, - 'process.env': { NODE_ENV: JSON.stringify('development') } - }), - new webpack.HotModuleReplacementPlugin(), - new webpack.LoaderOptionsPlugin({ debug: true }), - ], -}); diff --git a/conf/webpack/prod.js b/conf/webpack/prod.js deleted file mode 100644 index e30553690..000000000 --- a/conf/webpack/prod.js +++ /dev/null @@ -1,38 +0,0 @@ -const { merge } = require('webpack-merge'); -const TerserPlugin = require('terser-webpack-plugin'); -const webpack = require('webpack'); -const common = require('./common'); -const paths = require('../paths'); - -module.exports = merge(common, { - entry: [paths.ENTRY], - devtool: 'source-map', - mode: 'production', - optimization: { - minimizer: [ - new TerserPlugin({ - parallel: true, - terserOptions: { - sourceMap: true, // Must be set to true if using source-maps in production - // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions - } - }), - ], - splitChunks: { - chunks: 'async', - } - }, - module: { - rules: [ - { - test: /\.(js|jsx)$/, - exclude: /node_modules/, - use: [ - { - loader: 'babel-loader' - } - ] - } - ], - }, -}); diff --git a/config_dev.toml.example b/config_dev.toml.example deleted file mode 100644 index badfa205c..000000000 --- a/config_dev.toml.example +++ /dev/null @@ -1,108 +0,0 @@ -# Address and port the server will listen to. The ones below are defaults -# useful for development. On a more formal occasion you will likely to want -# change these. -#listen_address="localhost" -#listen_port="8086" - -# Base URL of the API, without version or trailing slash (/) -kerrokantasi_api_base="https://api.hel.fi/kerrokantasi-test" - -# Image to use for the hero of the front page -hero_image_url="http://materialbank.myhelsinki.fi/detail/1192/download/7" - -# Specifies the canonical URL for this service. Kerrokantasi-UI currently -# uses this for generating the requested callback address from SSO. This -# will need to match whatever you enter in the browser address bar to reach -# the UI and the SSO must also be configured to accept this. -# Default is again for development. -public_url="http://localhost:8086" - -# ExpressJS uses this secret for purposes. We are not sure if kerrokantasi-ui -# actually uses it for something. It is still required -expressjs_session_secret="dev-secret-do-not-use-in-production" - -# OpenId Connect configuration -openid_client_id="client-id-configured-to-your-opendid-provider" -openid_audience="https://auth-url.domain/path" -openid_authority="your-authority" -openid_apitoken_url="https://auth-url.domain/api-tokens/" -openid_scope="space delimitted scopes" - -# Makes `Strongly authenticated` option visible as a commenting option for hearings -# Default: false -# enable_strong_auth=false - -# City specific configuration -# For whitelabel styles use "whitelabel". -# For city assets installed to node_modules us the name of the package -# for instance "kerrokantasi-ui-turku". -# Default: "cities/helsinki" -city_config="cities/helsinki" - -# Should display accessibility page link in the footer and accessibility page content -# Default: false -# show_accessibility_info=true - -# Should display social media share buttons on a hearing header -# Default: true -# show_social_media_sharing=false - -# Enable high-contrast toggle button in navbar next to language switcher. -# To use just add .scss rules to city specific files ie -# .high-contrast { -# nav { -# background-color: white; -# ... -# } -# main { -# .... -# } -#} -# Default: false -# enable_highcontrast=true - -# Are cookies in use? -# If enabled then cookie scripts are injected into the html and the default cookie consent bar is visible. -# Everything related to cookies is disabled by default unless this value is changed. -# Default: false -# enable_cookies=true - -# Matomo cookie domains -# If using Matomo for tracking, setup the matomo parameters here -# -# matomo_cookie_domain="*.kerrokantasi.hel.fi" -# matomo_domains="*.kerrokantasi.hel.fi,*.kerrokantasi.hel.fi" -# matomo_site_id=380 - - -# Matomo script configuration -# matomo_script_url = '//webanalytics.digiaiiris.com/js/' -# matomo_script_filename = 'piwik.min.js' - -# Cookiebot consent management platform configuration. -# If enabled, default cookie bar is disabled and replaced with a Cookie Bot modal. -# To use Cookiebot set both enable_cookies and enable_cookiebot to true. -# Default: false -# enable_cookiebot=false -# cookiebot_data_cbid="your-unique-cookiebot-domain-group-id" - -# Address for hearing admin help link -# Default: "https://drive.google.com/open?id=1vtUNzbJNVcp7K9JPrE6XP8yTmkBLW3N3FGEsR1NbbIw" -# admin_help_url= - - -# String value that is considered as an empty comment -# Default: '-' -# empty_comment_string= - -# Show maintanance message below page navigation -# Default: false -# maintenance_show_notification= - -# Disable login during maintenance -# Default: false -# maintenance_disable_login= - -# Disable comments during maintenance -# Default: false -# maintenance_disable_comments= diff --git a/index.html b/index.html new file mode 100644 index 000000000..dd989fe0f --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + + + + Helsingin Kaupunki | Kerrokantasi + + + +
+ + + diff --git a/package.json b/package.json index 43f38a2e9..30e213f0a 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,26 @@ { "name": "kerrokantasi-ui", "version": "1.9.0", + "license": "MIT", "main": "index.js", "scripts": { - "start": "NODE_ENV=production; if test $NODE_MAX_MEMORY_MIB ; then node --max-old-space-size=$NODE_MAX_MEMORY_MIB server ; else node server ; fi", - "build": "rm -rf ./dist && node compile", + "start": "if test $NODE_MAX_MEMORY_MIB ; then node --max-old-space-size=$NODE_MAX_MEMORY_MIB yarn clear-babel-cache && yarn update-runtime-env && vite ; else yarn clear-babel-cache && yarn update-runtime-env && vite ; fi", + "build": "vite build", + "serve": "vite preview", "fetch-plugins": "node deploy/fetch_plugins.js", - "dev": "node server --dev --host 0.0.0.0", - "lint": "eslint \"src/**/*.{js,jsx}\" && eslint \"server/*.{js,jsx}\"", - "lint:fix": "eslint \"src/**/*.{js,jsx}\" --fix && eslint \"server/*.{js,jsx}\" --fix", - "lint:complexity": "eslint -c complexity.eslintrc.js --no-eslintrc src server", - "test": "jest", - "test:watch": "jest --watch", - "test:cov": "jest --coverage", + "lint": "eslint \"src/**/*.{js,jsx}\"", + "lint:fix": "eslint \"src/**/*.{js,jsx}\" --fix", + "lint:complexity": "eslint -c complexity.eslintrc.js --no-eslintrc src", + "test": "cross-env NODE_ENV=test yarn update-runtime-env && vitest", + "test:cov": "yarn test run --coverage --watch=false", "test:e2e:install": "yarn playwright install", "test:e2e:ci": "npx playwright test", "test:e2e:start": "npx playwright test", "test:e2e:record": "npx playwright codegen https://kerrokantasi.dev.hel.ninja/", "snyk-protect": "snyk protect", - "coverage": "jest --coverage --coverageReporters=cobertura", - "postinstall": "if test \"$NODE_ENV\" != \"production\" ; then husky install ; fi" + "postinstall": "if test \"$NODE_ENV\" != \"production\" ; then husky install ; fi && yarn update-runtime-env", + "update-runtime-env": "esrun scripts/update-runtime-env.js", + "clear-babel-cache": "rm -rf ./node_modules/.cache/@babel" }, "repository": { "type": "git", @@ -33,11 +34,10 @@ "@babel/eslint-parser": "^7.22.15", "@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/polyfill": "^7.0.0", "@babel/preset-env": "^7.22.2", "@babel/preset-react": "^7.22.3", - "@babel/register": "^7.0.0", "@draft-js-plugins/drag-n-drop": "^4.2.0", "@draft-js-plugins/editor": "^4.1.0", "@draft-js-plugins/focus": "^4.1.1", @@ -48,28 +48,21 @@ "@testing-library/react": "^15.0.7", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.5.2", + "@vitejs/plugin-react-swc": "^3.7.1", "alertifyjs": "^1.13.1", "autoprefixer": "^10.4.15", - "babel-loader": "^9.0.0", "babel-plugin-dynamic-import-node": "^2.3.3", - "babel-plugin-syntax-hermes-parser": "^0.15.1", "babel-plugin-transform-imports": "^2.0.0", - "babel-polyfill": "^6.26.0", - "body-parser": "^1.19.0", "bootstrap-sass": "3.4.3", "browser-image-compression": "^2.0.2", "buffer": "^6.0.3", "classnames": "^2.3.1", - "cookie-parser": "^1.4.5", - "cookie-session": "^2.0.0-alpha.1", "cross-fetch": "^4.0.0", - "crypto-browserify": "^3.12.0", - "css-loader": "^7.1.1", "debug": "^4.3.1", + "dotenv": "^16.4.5", "draft-convert": "^2.1.10", "draft-js": "^0.11.7", "draft-js-export-html": "^1.4.1", - "es6-promise": "^4.2.8", "eslint": "^8.47.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^9.0.0", @@ -79,14 +72,14 @@ "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react": "^7.28.0", "eslint-plugin-sonarjs": "^0.21.0", - "express": "^4.19.2", - "file-loader": "^6.2.0", + "eslint-plugin-vitest-globals": "^1.5.0", + "esmodule": "^0.2.1", + "esrun": "^3.2.26", "font-awesome": "^4.7.0", "hds-core": "^3.8.0", "hds-react": "^3.8.0", "hel-bootstrap-3": "1.0.0-beta.4", "history": "^5.3.0", - "html-loader": "^4.2.0", "immutability-helper": "^3.1.1", "immutable": "^4.3.2", "intl": "^1.2.5", @@ -96,39 +89,30 @@ "markdown-loader": "^8.0.0", "minimist": "^1.2.6", "moment": "^2.29.2", - "morgan": "^1.10.0", - "nconf": "^0.12.0", - "nconf-toml": "^0.0.2", "normalizr": "^3.6.1", - "oaf-react-router": "^4.1.0", "oidc-client": "^1.11.5", "postcss": "^8.1.0", - "postcss-loader": "^7.3.3", "progress": "^2.0.3", "proj4": "^2.7.2", "proj4leaflet": "^1.0.2", "prop-types": "^15.7.2", "qs": "^6.11.2", - "query-string": "^8.1.0", "querystring-es3": "^0.2.1", "react": "^18.0.0", "react-anchor-link-smooth-scroll": "^1.0.12", "react-bootstrap": "0.33.1", - "react-bootstrap-switch": "^15.5.3", "react-datetime": "^2.2.0", "react-device-detect": "^2.2.3", "react-dom": "^18.0.0", "react-dropzone": "^14.2.3", "react-helmet": "^6.1.0", - "react-hot-loader": "^4.13.1", "react-image-lightbox": "^5.1.1", "react-intl": "^5.0.0", "react-leaflet": "^4.2.1", "react-leaflet-draw": "^0.20.4", + "react-markdown": "^9.0.1", "react-nl2br": "^1.0.4", - "react-overlays": "^0.9.2", "react-redux": "^8.0.0", - "react-router-bootstrap": "^0.25.0", "react-router-dom": "^6.23.1", "react-router-hash-link": "^2.4.3", "react-select": "^5.8.0", @@ -139,70 +123,35 @@ "redux-actions": "^2.6.5", "redux-oidc": "^3.1.7", "redux-thunk": "^3.1.0", + "remark-gfm": "^4.0.0", "sass": "^1.62.1", - "sass-loader": "^13.3.2", "scrolltop": "0.0.1", "simple-git": "^3.3.0", "snyk": "^1.695.0", - "stream-browserify": "^3.0.0", - "style-loader": "^3.3.3", - "terser-webpack-plugin": "^5.2.5", "updeep": "^1.2.0", "url": "^0.11.1", - "url-loader": "^4.1.1", "url-parse": "^1.5.9", "uuid": "^9.0.0", - "webpack": "^5.54.0", - "webpack-dev-middleware": "^6.1.2", - "webpack-hot-middleware": "^2.25.1", - "webpack-merge": "^5.2.0" + "vite": "^5.4.10", + "vite-plugin-eslint": "^1.8.1", + "vitest": "^2.1.4", + "vitest-fetch-mock": "^0.4.1" }, "devDependencies": { - "@babel/plugin-proposal-export-default-from": "^7.22.5", - "@babel/plugin-proposal-private-property-in-object": "^7.21.11", - "@babel/plugin-proposal-throw-expressions": "^7.22.5", "@commitlint/cli": "^18.4.3", "@commitlint/config-conventional": "^18.4.3", "@playwright/test": "^1.43.1", "@testing-library/dom": "7.21.4", "@testing-library/jest-dom": "^6.3.0", "@types/node": "^18.19.3", - "babel-jest": "^29.6.3", + "@vitest/coverage-istanbul": "^2.1.4", + "cross-env": "^7.0.3", "husky": "^8.0.3", - "jest": "^29.5.0", - "jest-environment-jsdom": "^29.7.0", - "jest-fetch-mock": "^3.0.3", - "jest-localstorage-mock": "^2.4.26", "jsdom": "^20.0.3", - "react-addons-test-utils": "^15.6.0", - "react-test-renderer": "^16.13.1", - "redux-mock-store": "^1.5.4", - "tsconfig-paths-webpack-plugin": "^4.1.0", - "webpack-bundle-analyzer": "^4.9.0", - "webpack-cli": "^5.1.4" + "redux-mock-store": "^1.5.4" }, - "jest": { - "collectCoverageFrom": [ - "server/**/*.{js,jsx}", - "src/**/*.{js,jsx}" - ], - "setupFilesAfterEnv": [ - "./src/setupTests.js", - "jest-localstorage-mock" - ], - "moduleNameMapper": { - "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/src/__mocks__/fileMock.js", - "\\.(css|less)$": "/src/__mocks__/styleMock.js", - "^@city-i18n(.*)$": "/src/i18n$1", - "^@city-assets(.*)$": "/assets$1", - "^@city-images(.*)$": "/assets/images$1", - "react-leaflet-draw": "/src/__mocks__/reactLeafletDrawMock.jsx", - "react-leaflet": "/src/__mocks__/reactLeafletMock.jsx" - }, - "testEnvironment": "jest-environment-jsdom", - "testPathIgnorePatterns": [ - "e2e" - ] + "resolutions": { + "rollup": "^4.24.4" }, "snyk": true } diff --git a/public/.gitkeep b/public/.gitkeep new file mode 100644 index 000000000..b4ffa9070 --- /dev/null +++ b/public/.gitkeep @@ -0,0 +1 @@ +# Keep this directory diff --git a/assets/favicon/android-chrome-144x144.png b/public/android-chrome-144x144.png similarity index 100% rename from assets/favicon/android-chrome-144x144.png rename to public/android-chrome-144x144.png diff --git a/assets/favicon/android-chrome-192x192.png b/public/android-chrome-192x192.png similarity index 100% rename from assets/favicon/android-chrome-192x192.png rename to public/android-chrome-192x192.png diff --git a/assets/favicon/android-chrome-256x256.png b/public/android-chrome-256x256.png similarity index 100% rename from assets/favicon/android-chrome-256x256.png rename to public/android-chrome-256x256.png diff --git a/assets/favicon/android-chrome-36x36.png b/public/android-chrome-36x36.png similarity index 100% rename from assets/favicon/android-chrome-36x36.png rename to public/android-chrome-36x36.png diff --git a/assets/favicon/android-chrome-384x384.png b/public/android-chrome-384x384.png similarity index 100% rename from assets/favicon/android-chrome-384x384.png rename to public/android-chrome-384x384.png diff --git a/assets/favicon/android-chrome-48x48.png b/public/android-chrome-48x48.png similarity index 100% rename from assets/favicon/android-chrome-48x48.png rename to public/android-chrome-48x48.png diff --git a/assets/favicon/android-chrome-512x512.png b/public/android-chrome-512x512.png similarity index 100% rename from assets/favicon/android-chrome-512x512.png rename to public/android-chrome-512x512.png diff --git a/assets/favicon/android-chrome-72x72.png b/public/android-chrome-72x72.png similarity index 100% rename from assets/favicon/android-chrome-72x72.png rename to public/android-chrome-72x72.png diff --git a/assets/favicon/android-chrome-96x96.png b/public/android-chrome-96x96.png similarity index 100% rename from assets/favicon/android-chrome-96x96.png rename to public/android-chrome-96x96.png diff --git a/assets/favicon/apple-touch-icon-1024x1024.png b/public/apple-touch-icon-1024x1024.png similarity index 100% rename from assets/favicon/apple-touch-icon-1024x1024.png rename to public/apple-touch-icon-1024x1024.png diff --git a/assets/favicon/apple-touch-icon-114x114.png b/public/apple-touch-icon-114x114.png similarity index 100% rename from assets/favicon/apple-touch-icon-114x114.png rename to public/apple-touch-icon-114x114.png diff --git a/assets/favicon/apple-touch-icon-120x120.png b/public/apple-touch-icon-120x120.png similarity index 100% rename from assets/favicon/apple-touch-icon-120x120.png rename to public/apple-touch-icon-120x120.png diff --git a/assets/favicon/apple-touch-icon-144x144.png b/public/apple-touch-icon-144x144.png similarity index 100% rename from assets/favicon/apple-touch-icon-144x144.png rename to public/apple-touch-icon-144x144.png diff --git a/assets/favicon/apple-touch-icon-152x152.png b/public/apple-touch-icon-152x152.png similarity index 100% rename from assets/favicon/apple-touch-icon-152x152.png rename to public/apple-touch-icon-152x152.png diff --git a/assets/favicon/apple-touch-icon-167x167.png b/public/apple-touch-icon-167x167.png similarity index 100% rename from assets/favicon/apple-touch-icon-167x167.png rename to public/apple-touch-icon-167x167.png diff --git a/assets/favicon/apple-touch-icon-180x180.png b/public/apple-touch-icon-180x180.png similarity index 100% rename from assets/favicon/apple-touch-icon-180x180.png rename to public/apple-touch-icon-180x180.png diff --git a/assets/favicon/apple-touch-icon-57x57.png b/public/apple-touch-icon-57x57.png similarity index 100% rename from assets/favicon/apple-touch-icon-57x57.png rename to public/apple-touch-icon-57x57.png diff --git a/assets/favicon/apple-touch-icon-60x60.png b/public/apple-touch-icon-60x60.png similarity index 100% rename from assets/favicon/apple-touch-icon-60x60.png rename to public/apple-touch-icon-60x60.png diff --git a/assets/favicon/apple-touch-icon-72x72.png b/public/apple-touch-icon-72x72.png similarity index 100% rename from assets/favicon/apple-touch-icon-72x72.png rename to public/apple-touch-icon-72x72.png diff --git a/assets/favicon/apple-touch-icon-76x76.png b/public/apple-touch-icon-76x76.png similarity index 100% rename from assets/favicon/apple-touch-icon-76x76.png rename to public/apple-touch-icon-76x76.png diff --git a/assets/favicon/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png similarity index 100% rename from assets/favicon/apple-touch-icon-precomposed.png rename to public/apple-touch-icon-precomposed.png diff --git a/assets/favicon/apple-touch-icon.png b/public/apple-touch-icon.png similarity index 100% rename from assets/favicon/apple-touch-icon.png rename to public/apple-touch-icon.png diff --git a/assets/favicon/apple-touch-startup-image-1182x2208.png b/public/apple-touch-startup-image-1182x2208.png similarity index 100% rename from assets/favicon/apple-touch-startup-image-1182x2208.png rename to public/apple-touch-startup-image-1182x2208.png diff --git a/assets/favicon/apple-touch-startup-image-1242x2148.png b/public/apple-touch-startup-image-1242x2148.png similarity index 100% rename from assets/favicon/apple-touch-startup-image-1242x2148.png rename to public/apple-touch-startup-image-1242x2148.png diff --git a/assets/favicon/apple-touch-startup-image-1496x2048.png b/public/apple-touch-startup-image-1496x2048.png similarity index 100% rename from assets/favicon/apple-touch-startup-image-1496x2048.png rename to public/apple-touch-startup-image-1496x2048.png diff --git a/assets/favicon/apple-touch-startup-image-1536x2008.png b/public/apple-touch-startup-image-1536x2008.png similarity index 100% rename from assets/favicon/apple-touch-startup-image-1536x2008.png rename to public/apple-touch-startup-image-1536x2008.png diff --git a/assets/favicon/apple-touch-startup-image-320x460.png b/public/apple-touch-startup-image-320x460.png similarity index 100% rename from assets/favicon/apple-touch-startup-image-320x460.png rename to public/apple-touch-startup-image-320x460.png diff --git a/assets/favicon/apple-touch-startup-image-640x1096.png b/public/apple-touch-startup-image-640x1096.png similarity index 100% rename from assets/favicon/apple-touch-startup-image-640x1096.png rename to public/apple-touch-startup-image-640x1096.png diff --git a/assets/favicon/apple-touch-startup-image-640x920.png b/public/apple-touch-startup-image-640x920.png similarity index 100% rename from assets/favicon/apple-touch-startup-image-640x920.png rename to public/apple-touch-startup-image-640x920.png diff --git a/assets/favicon/apple-touch-startup-image-748x1024.png b/public/apple-touch-startup-image-748x1024.png similarity index 100% rename from assets/favicon/apple-touch-startup-image-748x1024.png rename to public/apple-touch-startup-image-748x1024.png diff --git a/assets/favicon/apple-touch-startup-image-750x1294.png b/public/apple-touch-startup-image-750x1294.png similarity index 100% rename from assets/favicon/apple-touch-startup-image-750x1294.png rename to public/apple-touch-startup-image-750x1294.png diff --git a/assets/favicon/apple-touch-startup-image-768x1004.png b/public/apple-touch-startup-image-768x1004.png similarity index 100% rename from assets/favicon/apple-touch-startup-image-768x1004.png rename to public/apple-touch-startup-image-768x1004.png diff --git a/assets/favicon/browserconfig.xml b/public/browserconfig.xml similarity index 100% rename from assets/favicon/browserconfig.xml rename to public/browserconfig.xml diff --git a/assets/favicon/coast-228x228.png b/public/coast-228x228.png similarity index 100% rename from assets/favicon/coast-228x228.png rename to public/coast-228x228.png diff --git a/assets/favicon/favicon-16x16.png b/public/favicon-16x16.png similarity index 100% rename from assets/favicon/favicon-16x16.png rename to public/favicon-16x16.png diff --git a/assets/favicon/favicon-32x32.png b/public/favicon-32x32.png similarity index 100% rename from assets/favicon/favicon-32x32.png rename to public/favicon-32x32.png diff --git a/assets/favicon/favicon.ico b/public/favicon.ico similarity index 100% rename from assets/favicon/favicon.ico rename to public/favicon.ico diff --git a/assets/favicon/firefox_app_128x128.png b/public/firefox_app_128x128.png similarity index 100% rename from assets/favicon/firefox_app_128x128.png rename to public/firefox_app_128x128.png diff --git a/assets/favicon/firefox_app_512x512.png b/public/firefox_app_512x512.png similarity index 100% rename from assets/favicon/firefox_app_512x512.png rename to public/firefox_app_512x512.png diff --git a/assets/favicon/firefox_app_60x60.png b/public/firefox_app_60x60.png similarity index 100% rename from assets/favicon/firefox_app_60x60.png rename to public/firefox_app_60x60.png diff --git a/assets/favicon/kerrokantasi-favicon.png b/public/kerrokantasi-favicon.png similarity index 100% rename from assets/favicon/kerrokantasi-favicon.png rename to public/kerrokantasi-favicon.png diff --git a/assets/favicon/manifest.json b/public/manifest.json similarity index 100% rename from assets/favicon/manifest.json rename to public/manifest.json diff --git a/assets/favicon/manifest.webapp b/public/manifest.webapp similarity index 100% rename from assets/favicon/manifest.webapp rename to public/manifest.webapp diff --git a/assets/favicon/mstile-144x144.png b/public/mstile-144x144.png similarity index 100% rename from assets/favicon/mstile-144x144.png rename to public/mstile-144x144.png diff --git a/assets/favicon/mstile-150x150.png b/public/mstile-150x150.png similarity index 100% rename from assets/favicon/mstile-150x150.png rename to public/mstile-150x150.png diff --git a/assets/favicon/mstile-310x150.png b/public/mstile-310x150.png similarity index 100% rename from assets/favicon/mstile-310x150.png rename to public/mstile-310x150.png diff --git a/assets/favicon/mstile-310x310.png b/public/mstile-310x310.png similarity index 100% rename from assets/favicon/mstile-310x310.png rename to public/mstile-310x310.png diff --git a/assets/favicon/mstile-70x70.png b/public/mstile-70x70.png similarity index 100% rename from assets/favicon/mstile-70x70.png rename to public/mstile-70x70.png diff --git a/assets/favicon/yandex-browser-50x50.png b/public/yandex-browser-50x50.png similarity index 100% rename from assets/favicon/yandex-browser-50x50.png rename to public/yandex-browser-50x50.png diff --git a/assets/favicon/yandex-browser-manifest.json b/public/yandex-browser-manifest.json similarity index 100% rename from assets/favicon/yandex-browser-manifest.json rename to public/yandex-browser-manifest.json diff --git a/scripts/env.sh b/scripts/env.sh new file mode 100755 index 000000000..945f30d5b --- /dev/null +++ b/scripts/env.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +ENVDIR=${1:-.} +TARGETDIR=${2:-.} +# Recreate config file +rm -f $TARGETDIR/env-config.js +touch $TARGETDIR/env-config.js + +REACT_APP_VERSION=$(grep -m1 version $ENVDIR/package.json | awk -F: '{ print $2 }' | sed 's/[", ]//g') +REACT_APP_APPLICATION_NAME=$(grep -m1 name $ENVDIR/package.json | awk -F: '{ print $2 }' | sed 's/[", ]//g') + +# Add assignment +echo "window._env_ = {" >> $TARGETDIR/env-config.js + +# Read each line in .env file +# Each line represents key=value pairs +while read -r line || [[ -n "$line" ]]; +do + # Split env variables by character `=` + if printf '%s\n' "$line" | grep -q -e '='; then + varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') + varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') + fi + + # Read value of current variable if exists as Environment variable + value=$(printf '%s\n' "${!varname}") + # Otherwise use value from .env file + [[ -z $value ]] && value=${varvalue} + #Remove quotes around the value to make it easier to use value + value=${value%\"} + value=${value#\"} + # Append configuration property to JS file + echo " $varname: \"$(echo ${value} | envsubst)\"," >> $TARGETDIR/env-config.js + + # Store value to current subshell to allow usage of it at later values + export $varname="$(echo ${value} | envsubst)" +done < $ENVDIR/.env + +echo "}" >> $TARGETDIR/env-config.js diff --git a/scripts/update-runtime-env.js b/scripts/update-runtime-env.js new file mode 100644 index 000000000..9a0ca1834 --- /dev/null +++ b/scripts/update-runtime-env.js @@ -0,0 +1,58 @@ +#!/usr/bin/env ts-node-script +import * as path from 'path'; +import fs from 'fs'; +import util from 'util'; + +import dotenv from 'dotenv'; + +import { getCityConfig, getCityPublic } from './utils'; + +const USE_TEST_ENV = process.env.NODE_ENV === 'test'; +const defaultNodeEnv = USE_TEST_ENV ? 'test' : 'development'; + +/* @ts-ignore */ +import.meta.env = {}; + +import.meta.env.NODE_ENV = process.env.NODE_ENV || defaultNodeEnv; + +dotenv.config({ + processEnv: import.meta.env, + ...(USE_TEST_ENV + ? { path: ['.env', '.env.test'] } + : { path: ['.env', '.env.local'] }), + override: true, +}); + +// Prevent collision if app is running while tests are started +const configFile = USE_TEST_ENV ? 'test-env-config.js' : 'env-config.js'; + +const cityConfig = getCityConfig(import.meta.env); +const cityPublic = getCityPublic(import.meta.env, cityConfig); + +const configurationFile = path.resolve( + __dirname, + `${cityPublic}/${configFile}` +); + +const start = async () => { + try { + const envVariables = import.meta.env; + + fs.writeFile( + configurationFile, + `window._env_ = ${util.inspect(envVariables, false, 2, false)}`, + // eslint-disable-next-line consistent-return + (err) => { + if (err) { + return console.error(err); + } + console.log('File created!'); + } + ); + } catch (err) { + console.error(err.message); // eslint-disable-line + process.exit(1); + } +}; + +start(); diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644 index 000000000..763d81fcb --- /dev/null +++ b/scripts/utils.js @@ -0,0 +1,28 @@ +import path from 'path'; + +export const getCityConfig = (env) => { + if (!env.REACT_APP_CITY_CONFIG || env.REACT_APP_CITY_CONFIG === 'cities/helsinki') { + return path.resolve(__dirname, `../cities/helsinki/`); + } + if (env.REACT_APP_CITY_CONFIG === 'whitelabel') { + return path.resolve(__dirname, `../src`); + } + + return path.resolve(__dirname, `../node_modules/${env.REACT_APP_CITY_CONFIG}/`); +} + +export const getCityAssets = (env, config) => { + if (env.REACT_APP_CITY_CONFIG === 'whitelabel') { + return path.resolve(config, '../assets/'); + } + + return path.resolve(config, 'assets/'); +} + +export const getCityPublic = (env, config) => { + if (env.REACT_APP_CITY_CONFIG === 'whitelabel') { + return path.resolve(config, '../public/'); + } + + return path.resolve(config, 'public/'); +} diff --git a/server/.babelrc b/server/.babelrc deleted file mode 100644 index b0bbcf987..000000000 --- a/server/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - "@babel/preset-env", - "@babel/preset-react" - ], - "plugins": [ - "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-object-rest-spread" - ] -} \ No newline at end of file diff --git a/server/Html.jsx b/server/Html.jsx deleted file mode 100644 index 3e795ca27..000000000 --- a/server/Html.jsx +++ /dev/null @@ -1,136 +0,0 @@ -/* eslint-disable react/forbid-prop-types */ -/* eslint-disable react/no-danger */ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class Html extends React.Component { - // Fetches site's default metadata, these are often overwritten - // with more specific values - getDefaultMeta() { - const { heroImageURL } = this.props; - return { - title: 'Kerrokantasi', - url: heroImageURL, - description: `Kaupungin Kerrokantasi-palvelussa kaupunkilaisilta - kerätään mielipiteitä valmistelussa olevista asioista.`, - }; - } - - render() { - const { - apiBaseUrl, - publicUrl, - bundleSrc, - content, - head, - heroImageURL, - initialState, - showAccessibilityInfo, - showSocialMediaSharing, - enableCookies, - matomoCookieDomain, - matomoDomains, - matomoSiteId, - matomoScriptFilename, - matomoScriptUrl, - enableCookiebot, - cookiebotDataCbid, - uiConfig, - openIdClientId, - openIdAudience, - openIdAuthority, - openIdApiTokenUrl, - openIdScope, - enableHighContrast, - enableStrongAuth, - adminHelpUrl, - emptyCommentString, - maintenanceShowNotification, - maintenanceDisableLogin, - maintenanceDisableComments - } = this.props; - const initialStateHtml = ` - window.STATE = ${JSON.stringify(initialState || {})}; - window.API_BASE_URL = ${JSON.stringify(apiBaseUrl)}; - window.PUBLIC_URL = ${JSON.stringify(publicUrl)}; - window.HERO_IMAGE_URL = ${JSON.stringify(heroImageURL)}; - window.UI_CONFIG = ${JSON.stringify(uiConfig)}; - window.SHOW_ACCESSIBILITY_INFO = ${JSON.stringify(showAccessibilityInfo)}; - window.OPENID_CLIENT_ID = ${JSON.stringify(openIdClientId)}; - window.OPENID_AUDIENCE = ${JSON.stringify(openIdAudience)}; - window.OPENID_AUTHORITY = ${JSON.stringify(openIdAuthority)}; - window.OPENID_APITOKEN_URL = ${JSON.stringify(openIdApiTokenUrl)}; - window.OPENID_SCOPE = ${JSON.stringify(openIdScope)} - window.SHOW_SOCIAL_MEDIA_SHARING = ${JSON.stringify(showSocialMediaSharing)}; - window.ENABLE_HIGHCONTRAST = ${JSON.stringify(enableHighContrast)} - window.ENABLE_COOKIES = ${JSON.stringify(enableCookies)}; - window.MATOMO_COOKIE_DOMAIN = ${JSON.stringify(matomoCookieDomain)}; - window.MATOMO_DOMAINS = ${JSON.stringify(matomoDomains)}; - window.MATOMO_SITE_ID = ${JSON.stringify(matomoSiteId)}; - window.MATOMO_SCRIPT_URL = ${JSON.stringify(matomoScriptUrl)}; - window.MATOMO_SCRIPT_FILENAME = ${JSON.stringify(matomoScriptFilename)}; - window.ENABLE_COOKIEBOT = ${JSON.stringify(enableCookiebot)}; - window.COOKIEBOT_DATA_CBID = ${JSON.stringify(cookiebotDataCbid)}; - window.ENABLE_STRONG_AUTH = ${JSON.stringify(enableStrongAuth)} - window.ADMIN_HELP_URL = ${JSON.stringify(adminHelpUrl)}; - window.EMPTY_COMMENT_STRING = ${JSON.stringify(emptyCommentString)}; - window.MAINTENANCE_SHOW_NOTIFICATION = ${JSON.stringify(maintenanceShowNotification)}; - window.MAINTENANCE_DISABLE_LOGIN = ${JSON.stringify(maintenanceDisableLogin)}; - window.MAINTENANCE_DISABLE_COMMENTS = ${JSON.stringify(maintenanceDisableComments)}; - `; - const { title, description, url } = this.getDefaultMeta(); - return ( - - - - - - {head ? head.meta.toComponent() : null} - {head ? head.link.toComponent() : null} - - - - - - -
-