Compare commits

...

41 Commits

Author SHA1 Message Date
Alejandro Celaya
2a7c2474cd Merge pull request #336 from acelaya-forks/feature/fix-visits
Feature/fix visits
2020-11-14 13:09:51 +01:00
Alejandro Celaya
c890124e67 Updated changelog 2020-11-14 13:02:28 +01:00
Alejandro Celaya
3e21cccb14 Fixed visits getting accumulated every time the visits page is opened 2020-11-14 13:01:35 +01:00
Alejandro Celaya
dafebc3df9 Merge pull request #332 from acelaya-forks/feature/update-deps
Feature/update deps
2020-11-14 12:21:49 +01:00
Alejandro Celaya
6619e7cdb6 Updated changelog 2020-11-14 12:13:28 +01:00
Alejandro Celaya
c54f314424 Updated react-datepicker to latest version 2020-11-14 12:10:42 +01:00
Alejandro Celaya
4964f28169 Updated more production dependencies 2020-11-14 11:00:41 +01:00
Alejandro Celaya
dead22c332 Updated reactstrap 2020-11-14 10:33:32 +01:00
Alejandro Celaya
aba65346b4 Updated react-dev-utils 2020-11-14 10:24:15 +01:00
Alejandro Celaya
4621246cec Updated color-picker and fixed error when left open and modal is closed 2020-11-14 09:16:26 +01:00
Alejandro Celaya
f83280068b Updated more dev dependencies 2020-11-14 08:59:20 +01:00
Alejandro Celaya
0671fa6567 Updated to stryker v4 2020-11-13 23:06:03 +01:00
Alejandro Celaya
5c80e853c6 #325 Updated to Typescript 4 2020-11-13 22:46:17 +01:00
Alejandro Celaya
6c90d7072f #325 Updated to react 17 2020-11-13 22:44:26 +01:00
Alejandro Celaya
18bccab27a Moved to official docker github actions for docker-image-build 2020-11-10 19:25:20 +01:00
Alejandro Celaya
b9213952d3 Added npm ci when generating release 2020-11-01 10:39:09 +01:00
Alejandro Celaya
f1ae68a300 Allow empty changelog when publishing release 2020-11-01 10:34:53 +01:00
Alejandro Celaya
3f0409f25a Merge pull request #331 from acelaya-forks/feature/automatic-release
Feature/automatic release
2020-11-01 10:32:41 +01:00
Alejandro Celaya
6f568a16bf Moved tag releasing from travis to github workflow 2020-11-01 10:27:33 +01:00
Alejandro Celaya
39ae3b4762 Updated chanegelog to more strictly endorse to keepachangelog spec 2020-11-01 10:21:44 +01:00
Alejandro Celaya
14e31ed2c3 Merge pull request #330 from acelaya-forks/feature/fix-switch-alignment
Removed hardcoded display: inline for BooleanControls
2020-10-31 17:28:19 +01:00
Alejandro Celaya
ff1fb0dd12 Removed hardcoded display: inline for BooleanControls 2020-10-31 17:18:51 +01:00
Alejandro Celaya
2e6a35181d Merge pull request #329 from acelaya-forks/feature/fix-too-long-cache
Feature/fix too long cache
2020-10-31 13:47:43 +01:00
Alejandro Celaya
22cca598ca Updated changelog 2020-10-31 13:38:37 +01:00
Alejandro Celaya
de906bf370 Added proper caching rules to nginx config included in docker image 2020-10-31 13:36:53 +01:00
Alejandro Celaya
498594c31b Deleted service worker 2020-10-31 13:22:39 +01:00
Alejandro Celaya
cfbd246cfc Merge pull request #327 from acelaya-forks/feature/dart-sass
Feature/dart sass
2020-10-31 13:07:52 +01:00
Alejandro Celaya
3f91c556e4 Explicitly installed node 14 in scrutinizer env 2020-10-31 13:00:09 +01:00
Alejandro Celaya
4d1622607c Enabled all platforms back on docker image builds 2020-10-31 12:34:42 +01:00
Alejandro Celaya
eacdee293c Replaced node-sass with dart-sass 2020-10-31 12:27:24 +01:00
Alejandro Celaya
f4b115cffd Merge pull request #326 from acelaya-forks/feature/node-14
Updated to node 14
2020-10-31 12:08:35 +01:00
Alejandro Celaya
7dcd623513 Updated to node 14 2020-10-31 11:58:07 +01:00
Alejandro Celaya
8b00d1aaae Updated reference from travis-ci.org to travis-ci.com 2020-10-31 09:11:43 +01:00
Alejandro Celaya
facfd33e96 Merge pull request #319 from acelaya-forks/feature/calendar-z-index
Feature/calendar z index
2020-10-03 11:28:20 +02:00
Alejandro Celaya
a841dc7531 Updated changelog 2020-10-03 11:23:08 +02:00
Alejandro Celaya
28ebd55b69 Fixed z-index in react-datepicker 2020-10-03 11:22:21 +02:00
Alejandro Celaya
3eade5a0c0 Merge pull request #318 from acelaya-forks/feature/manifest-basic-auth
Feature/manifest basic auth
2020-10-03 11:10:57 +02:00
Alejandro Celaya
caf74cd87b Updated changelog 2020-10-03 11:03:17 +02:00
Alejandro Celaya
049510f513 Added crossorigin=use-credentials to manifest.json, so that credentials are passed and it is propery downloaded 2020-10-03 11:00:56 +02:00
Alejandro Celaya
b151b7eedb Added missing condition for github release to work on travis build 2020-09-20 12:46:51 +02:00
Alejandro Celaya
4e22e9c092 Added script step to publish release travis job 2020-09-20 12:38:47 +02:00
157 changed files with 12421 additions and 8230 deletions

View File

@@ -23,6 +23,7 @@
}],
"no-mixed-operators": "off",
"react/display-name": "off",
"react/react-in-jsx-scope": "off",
"@typescript-eslint/require-array-sort-compare": "off"
}
}

View File

@@ -13,12 +13,16 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v1
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
buildx-version: latest
version: latest
- name: Login to docker hub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build the image
run: bash ./scripts/docker/build

28
.github/workflows/publish-release.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Publish release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Use node.js 14.15
uses: actions/setup-node@v1
with:
node-version: 14.15
- name: Generate release assets
run: npm ci && npm run build ${GITHUB_REF#refs/tags/v}
- name: Publish release with assets
uses: docker://antonyurchenko/git-release:latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ALLOW_TAG_PREFIX: "true"
ALLOW_EMPTY_CHANGELOG: "true"
with:
args: |
dist/shlink-web-client_*_dist.zip

View File

@@ -1,6 +1,3 @@
build:
environment:
node: v12.14.1
tools:
external_code_coverage:
timeout: 1200

View File

@@ -11,11 +11,12 @@ cache:
- node_modules
node_js:
- '12.16.3'
- '14.15.0'
jobs:
fast_finish: true
allow_failures:
- name: 'Lint'
- name: 'Mutation tests'
include:
@@ -40,13 +41,3 @@ jobs:
services:
- docker
script: docker build -t shlink-web-client:test .
- name: 'Publish release'
if: tag IS present
before_deploy: npm run build ${TRAVIS_TAG#?}
deploy:
provider: releases
api_key:
secure: jBvPwC7EAbViaNR83rwMSt5XQDK0Iu9rgvEMa7GoyShbHcvUCCPd73Tu9quNpKi6NKsDY3INHgtch3vgonjGNGDGJ+yDyIBzXcvsAX2x3UcHpRbgY12uiINVmQxBI1+OVQB016Nm+cKC/i5Z36K4EmDbYfo+MrKndngM6AjcQFTwI8EwniIMaQgg4gNes//K8NhP5u0c3gwG+Q6jEGnq6uH3kcRgh6/epIZYpQyxjqWqKwF77sgcYj+X2Nf6XxtB5neuCi301UKLoLx8G0skh/Lm6KAIO4s9iIhIFa3UpoF21Ka0TxLpd2JxalLryCnFGlWWE6lxC9Htmc0TeRowJQlGdJXCskJ37xT9MljKY0fwNMu06VS/FUgykuCv+jP3zQu51pKu7Ew7+WeNPjautoOTu54VkdGyHcf2ThBNEyJQuiEwAQe4u7yAxY6R5ovEdvHBSIg4w1E5/Mxy5SMTCUlIAv6H7QQ1X9Z/zJm9HH5KeKz5tsHvQ/RIdSpgHXq/tC8o4Yup/LCFucXfrgvy/8pJoO1UpOlmvm62974NFfo0EG5YWwv6brUqz3QXpMjb8sWqgjltYMYJX3J7WZ34rIc+zt4NAmfhqgczaOC4pUGCiJ8jX3rMWIaQRn1AJ+5V337jL9fNDpTHny4phQjHrMJ1e0HZuNp0Xb5Q8wgqDPM=
file: "./dist/shlink-web-client_${TRAVIS_TAG#?}_dist.zip"
skip_cleanup: true

View File

@@ -4,10 +4,46 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
## 2.6.0 - 2020-09-20
## [2.6.2] - 2020-11-14
### Added
* *Nothing*
#### Added
### Changed
* [#325](https://github.com/shlinkio/shlink-web-client/issues/325) and [#294](https://github.com/shlinkio/shlink-web-client/issues/294) Updated all dependencies, including React 17, Typescript 4, react-datepicker 3 and Stryker 4.
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#334](https://github.com/shlinkio/shlink-web-client/issues/334) Fixed color-picker making the app crash when closing the modal without closing the color-picker, and then trying to open the modal again.
* [#333](https://github.com/shlinkio/shlink-web-client/issues/333) Fixed visits getting accumulated every time the visits page is opened.
## [2.6.1] - 2020-10-31
### Added
* *Nothing*
### Changed
* [#292](https://github.com/shlinkio/shlink-web-client/issues/292) Improved a bit how caching works by removing the service worker and adding proper HTTP caching config on nginx inside docker image.
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#316](https://github.com/shlinkio/shlink-web-client/issues/316) Fixed manifest.json file not getting downloaded after passing credentials when the app is protected with basic auth.
* [#311](https://github.com/shlinkio/shlink-web-client/issues/311) Fixed datepicker showing below other components.
* [#306](https://github.com/shlinkio/shlink-web-client/issues/306) Fixed multi-arch docker builds by replacing node-sass with dart-sass.
* [#328](https://github.com/shlinkio/shlink-web-client/issues/328) Fixed toggle switches getting broken in mobile resolutions.
## [2.6.0] - 2020-09-20
### Added
* [#289](https://github.com/shlinkio/shlink-web-client/issues/289) Client and server version constraints are now links to the corresponding project release notes.
* [#293](https://github.com/shlinkio/shlink-web-client/issues/293) Shlink versions are now always displayed in footer, hiding the server version when there's no connected server.
* [#250](https://github.com/shlinkio/shlink-web-client/issues/250) Added support to group real time updates in fixed intervals.
@@ -18,57 +54,45 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
* [#277](https://github.com/shlinkio/shlink-web-client/issues/277) Added highlighting capabilities to the visits line chart.
#### Changed
### Changed
* [#150](https://github.com/shlinkio/shlink-web-client/issues/150) The list of short URLs is now ordered by the creation date, showing newest results first.
* [#248](https://github.com/shlinkio/shlink-web-client/issues/248) Numbers displayed application-wide are now prettified.
* [#40](https://github.com/shlinkio/shlink-web-client/issues/40) Migrated project to TypeScript.
* [#297](https://github.com/shlinkio/shlink-web-client/issues/297) Moved docker image building to github actions.
* [#305](https://github.com/shlinkio/shlink-web-client/issues/305) Split travis build so that every step is run in a parallel job.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#295](https://github.com/shlinkio/shlink-web-client/issues/295) Fixed custom slug field not being disabled when selecting a short code length.
* [#301](https://github.com/shlinkio/shlink-web-client/issues/301) Fixed tags visits loading not being cancelled when leaving visits page.
## 2.5.1 - 2020-06-06
#### Added
## [2.5.1] - 2020-06-06
### Added
* *Nothing*
#### Changed
### Changed
* [#254](https://github.com/shlinkio/shlink-web-client/issues/254) Reduced duplication on code to handle mercure topics binding.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#276](https://github.com/shlinkio/shlink-web-client/issues/276) Fixed default grouping used for visits line chart, making it be dynamic depending on how old the short URL is.
* [#280](https://github.com/shlinkio/shlink-web-client/issues/280) Fixed shlink-web-client version not being properly passed when building stable tags of the docker image.
* [#269](https://github.com/shlinkio/shlink-web-client/issues/269) Fixed doughnut chart legends getting to big and hiding charts on mobile devices.
## 2.5.0 - 2020-05-31
#### Added
## [2.5.0] - 2020-05-31
### Added
* [#148](https://github.com/shlinkio/shlink-web-client/issues/148) Added support for real-time updates when consuming a Shlink version that is integrated with a mercure hub server.
The integration is transparent. When a server is opened, shlink-web-client will try to get the mercure info from it.
@@ -88,28 +112,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
* [#149](https://github.com/shlinkio/shlink-web-client/issues/149) and [#198](https://github.com/shlinkio/shlink-web-client/issues/198) Added new line chart to visits and tags stats which displays amount of visits during selected time period, grouped by month, week, day or hour.
#### Changed
### Changed
* [#218](https://github.com/shlinkio/shlink-web-client/issues/218) Added back button to sections not displayed in left menu.
* [#255](https://github.com/shlinkio/shlink-web-client/issues/255) Improved how servers and settings are persisted in the local storage.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#262](https://github.com/shlinkio/shlink-web-client/issues/262) Fixed charts displaying decimal numbers, when visits are absolute and that makes no sense.
## 2.4.0 - 2020-04-10
#### Added
## [2.4.0] - 2020-04-10
### Added
* [#199](https://github.com/shlinkio/shlink-web-client/issues/199) Added table to visits page which displays the information in a paginated, sortable and filterable list.
It also supports selecting multiple visits in the table which makes the corresponding data to be highlighted in the visits charts.
@@ -124,432 +142,330 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
* [#234](https://github.com/shlinkio/shlink-web-client/issues/234) Allowed short code length to be edited on any new short URL when using Shlink 2.1 or higher.
* [#235](https://github.com/shlinkio/shlink-web-client/issues/235) Allowed editing the long URL for any existing short URL when suing Shlink 2.1 or higher.
#### Changed
### Changed
* [#205](https://github.com/shlinkio/shlink-web-client/issues/205) Replaced `jest-each` package by jet's native `test.each` function.
* [#209](https://github.com/shlinkio/shlink-web-client/issues/209) Replaced `Unknown` by `Direct` for visits from undetermined referrers.
* [#212](https://github.com/shlinkio/shlink-web-client/issues/212) Moved copy-to-clipboard next to short URL.
* [#208](https://github.com/shlinkio/shlink-web-client/issues/208) Short URLs list paginator is now progressive.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#243](https://github.com/shlinkio/shlink-web-client/issues/243) Fixed loading state and resetting on short URL creation form.
* [#239](https://github.com/shlinkio/shlink-web-client/issues/239) Fixed how user agents are parsed, reducing false results.
## 2.3.1 - 2020-02-08
#### Added
## [2.3.1] - 2020-02-08
### Added
* *Nothing*
#### Changed
### Changed
* [#191](https://github.com/shlinkio/shlink-web-client/issues/191) Created `ForServerVersion` helper component which dynamically renders children if current server conditions are met.
* [#189](https://github.com/shlinkio/shlink-web-client/issues/189) Simplified short url tags and short url deletion components and reducers, by removing redundant actions.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#193](https://github.com/shlinkio/shlink-web-client/issues/193) Fixed `maxVisits` being set to 0 when trying to reset it from having a value to `null`.
* [#196](https://github.com/shlinkio/shlink-web-client/issues/196) Included apache `.htaccess` file which takes care of falling back to index.html when reloading the page on a client-side handled route.
* [#179](https://github.com/shlinkio/shlink-web-client/issues/179) Ensured domain is provided to Shlink server when editing, deleting or fetching short URLs which do not belong to default domain.
* [#202](https://github.com/shlinkio/shlink-web-client/issues/202) Fixed domain not passed when dispatching actions that affect a single short URL (edit tags, edit meta and delete), which cased the list not to be properly updated.
## 2.3.0 - 2020-01-19
#### Added
## [2.3.0] - 2020-01-19
### Added
* [#174](https://github.com/shlinkio/shlink-web-client/issues/174) Added complete support for Shlink v2.x together with currently supported Shlink versions.
* [#164](https://github.com/shlinkio/shlink-web-client/issues/164) Added max visits control on those URLs which have `maxVisits`.
* [#178](https://github.com/shlinkio/shlink-web-client/issues/178) Short URLs list can now be filtered by date range.
* [#46](https://github.com/shlinkio/shlink-web-client/issues/46) Allowed short URL's metadata to be edited (`maxVisits`, `validSince` and `validUntil`).
#### Changed
### Changed
* *Nothing*
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#170](https://github.com/shlinkio/shlink-web-client/issues/170) Fixed apple icon referencing to incorrect file names.
## 2.2.2 - 2019-10-21
#### Added
## [2.2.2] - 2019-10-21
### Added
* *Nothing*
#### Changed
### Changed
* *Nothing*
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#167](https://github.com/shlinkio/shlink-web-client/issues/167) Fixed `/servers.json` path not being ignored when returning something other than an array.
## 2.2.1 - 2019-10-18
#### Added
## [2.2.1] - 2019-10-18
### Added
* *Nothing*
#### Changed
### Changed
* *Nothing*
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#165](https://github.com/shlinkio/shlink-web-client/issues/165) Fixed error thrown when opening "create" page while using a Shlink version which does not return a valid SemVer version (like `latest` docker image, or any development instance).
## 2.2.0 - 2019-10-05
#### Added
## [2.2.0] - 2019-10-05
### Added
* [#144](https://github.com/shlinkio/shlink-web-client/issues/144) Added domain input to create domain page.
#### Changed
### Changed
* [#140](https://github.com/shlinkio/shlink-web-client/issues/140) Updated project dependencies.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* *Nothing*
## 2.1.1 - 2019-09-22
#### Added
## [2.1.1] - 2019-09-22
### Added
* *Nothing*
#### Changed
### Changed
* [#142](https://github.com/shlinkio/shlink-web-client/issues/142) Updated to newer versions of base docker images for dev and production.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#151](https://github.com/shlinkio/shlink-web-client/issues/151) Fixed "order by" indicator (caret) still indicate ASC on column header when no order is specified.
* [#157](https://github.com/shlinkio/shlink-web-client/issues/157) Fixed pagination control on graphs expanding too much when lots of pages need to be rendered.
* [#155](https://github.com/shlinkio/shlink-web-client/issues/155) Fixed client-side paths resolve to 404 when served from nginx in docker image instead of falling back to `index.html`.
## 2.1.0 - 2019-05-19
#### Added
## [2.1.0] - 2019-05-19
### Added
* [#101](https://github.com/shlinkio/shlink-web-client/issues/101) Added checkbox to short URL creation form that allows to determine the value of the `findIfExists` flag introduced in Shlink v1.16.0.
* [#105](https://github.com/shlinkio/shlink-web-client/issues/105) Added support to pre-configure servers. See [how to pre-configure servers](README.md#pre-configuring-servers) to get more details on how to do it.
#### Changed
### Changed
* [#125](https://github.com/shlinkio/shlink-web-client/issues/125) Refactored reducers to replace `switch` statements by `handleActions` from [redux-actions](https://github.com/redux-utilities/redux-actions).
* [#116](https://github.com/shlinkio/shlink-web-client/issues/116) Removed sinon in favor of jest mocks.
* [#72](https://github.com/shlinkio/shlink-web-client/issues/72) Increased code coverage up to 80%.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* *Nothing*
## 2.0.3 - 2019-03-16
#### Added
## [2.0.3] - 2019-03-16
### Added
* *Nothing*
#### Changed
### Changed
* *Nothing*
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#120](https://github.com/shlinkio/shlink-web-client/issues/120) Fixed crash when visits page is loaded and there are no visits with known cities.
* [#113](https://github.com/shlinkio/shlink-web-client/issues/113) Ensured visits loading is cancelled when the visits page is unmounted. Requests on flight will still finish.
* [#118](https://github.com/shlinkio/shlink-web-client/issues/118) Fixed chart crashing when trying to render lots of bars by adding pagination.
## 2.0.2 - 2019-03-04
#### Added
## [2.0.2] - 2019-03-04
### Added
* *Nothing*
#### Changed
### Changed
* *Nothing*
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#103](https://github.com/shlinkio/shlink-web-client/issues/103) Fixed visits page getting freezed when loading large amounts of visits.
* [#111](https://github.com/shlinkio/shlink-web-client/issues/111) Fixed crash when trying to load a map modal with only one location.
* [#115](https://github.com/shlinkio/shlink-web-client/issues/115) Created `ErrorHandler` component which will prevent crashes in app to make it unusable.
## 2.0.1 - 2019-03-03
#### Added
## [2.0.1] - 2019-03-03
### Added
* *Nothing*
#### Changed
### Changed
* [#106](https://github.com/shlinkio/shlink-web-client/issues/106) Reduced size of docker image by using a multi-stage build Dockerfile.
* [#95](https://github.com/shlinkio/shlink-web-client/issues/95) Tested docker image build during travis executions.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#104](https://github.com/shlinkio/shlink-web-client/issues/104) Fixed blank page being showed when not-found paths are loaded.
* [#94](https://github.com/shlinkio/shlink-web-client/issues/94) Fixed initial zoom and center on maps.
* [#93](https://github.com/shlinkio/shlink-web-client/issues/93) Prevented side menu to be swipeable while a modal window is displayed.
## 2.0.0 - 2019-01-13
#### Added
## [2.0.0] - 2019-01-13
### Added
* [#54](https://github.com/shlinkio/shlink-web-client/issues/54) Added stats by city graphic in visits page.
* [#55](https://github.com/shlinkio/shlink-web-client/issues/55) Added map in visits page locating cities from which visits have occurred.
#### Changed
### Changed
* [#87](https://github.com/shlinkio/shlink-web-client/issues/87) and [#89](https://github.com/shlinkio/shlink-web-client/issues/89) Updated all dependencies to latest major versions.
* [#96](https://github.com/shlinkio/shlink-web-client/issues/96) Updated visits page to load visits in multiple paginated requests of `5000` visits when used shlink server supports it. This will prevent shlink to hang when trying to load big amounts of visits.
* [#71](https://github.com/shlinkio/shlink-web-client/issues/71) Improved tests and increased code coverage.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* [#59](https://github.com/shlinkio/shlink-web-client/issues/59) Dropped support for old browsers. Internet explorer and dead browsers are no longer supported.
* [#97](https://github.com/shlinkio/shlink-web-client/issues/97) Dropped support for authentication via `Authorization` header with Bearer type and JWT, which will make this version no longer work with shlink earlier than v1.13.0.
#### Fixed
### Fixed
* *Nothing*
## 1.2.1 - 2018-12-21
#### Added
## [1.2.1] - 2018-12-21
### Added
* *Nothing*
#### Changed
### Changed
* [#80](https://github.com/shlinkio/shlink-web-client/issues/80) Deeply refactored app to do true dependency injection with an IoC container.
* [#79](https://github.com/shlinkio/shlink-web-client/issues/79) Updated to nginx 1.15.7 as the base docker image.
* [#75](https://github.com/shlinkio/shlink-web-client/issues/75) Prevented duplicated `yarn build` in travis when a tag exists.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#77](https://github.com/shlinkio/shlink-web-client/issues/77) Sortable graphs ordering is now case insensitive.
## 1.2.0 - 2018-11-01
#### Added
## [1.2.0] - 2018-11-01
### Added
* [#65](https://github.com/shlinkio/shlink-web-client/issues/65) Added sorting to both countries and referrers stats graphs.
* [#14](https://github.com/shlinkio/shlink-web-client/issues/14) Documented how to build the project so that it can be served from a subpath.
#### Changed
### Changed
* [#50](https://github.com/shlinkio/shlink-web-client/issues/50) Improved tests and increased code coverage.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#66](https://github.com/shlinkio/shlink-web-client/issues/66) Fixed tooltips in graphs with too small bars not being displayed.
## 1.1.1 - 2018-10-20
#### Added
## [1.1.1] - 2018-10-20
### Added
* [#57](https://github.com/shlinkio/shlink-web-client/issues/57) Automated release generation in travis build.
#### Changed
### Changed
* *Nothing*
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#63](https://github.com/shlinkio/shlink-web-client/issues/63) Improved how bar charts are rendered in stats page, making them try to calculate a bigger height for big data sets.
* [#56](https://github.com/shlinkio/shlink-web-client/issues/56) Ensured `ColorGenerator` matches keys in a case insensitive way.
* [#53](https://github.com/shlinkio/shlink-web-client/issues/53) Fixed missing margin between date fields in visits page for mobile devices.
## 1.1.0 - 2018-09-16
#### Added
## [1.1.0] - 2018-09-16
### Added
* [#47](https://github.com/shlinkio/shlink-web-client/issues/47) Added support to delete short URLs (requires [shlink v1.12.0](https://github.com/shlinkio/shlink/releases/tag/v1.12.0) or greater).
#### Changed
### Changed
* [#35](https://github.com/shlinkio/shlink-web-client/issues/35) Visits component split into two, which makes the header not to be refreshed when filtering by date, and also the visits global counter now reflects the actual number of visits which fulfill current filter.
* [#36](https://github.com/shlinkio/shlink-web-client/issues/36) Tags selector now autocompletes existing tag names, to prevent typos and ease reusing existing tags.
* [#39](https://github.com/shlinkio/shlink-web-client/issues/39) Defined `propTypes` as static properties in class components.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#49](https://github.com/shlinkio/shlink-web-client/issues/49) Ensured filtering parameters are reseted when list component is unmounted so that params are not mixed when coming back.
* [#45](https://github.com/shlinkio/shlink-web-client/issues/45) Ensured graphs x-axis start at `0` and don't use decimals.
* [#51](https://github.com/shlinkio/shlink-web-client/issues/51) When editing short URL tags, the value returned form server is used when refreshing the list, which is normalized.
## 1.0.1 - 2018-09-02
#### Added
## [1.0.1] - 2018-09-02
### Added
* *Nothing*
#### Changed
### Changed
* *Nothing*
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#42](https://github.com/shlinkio/shlink-web-client/issues/42) Fixed selected tags lost when navigating between pages in short URLs list.
* [#43](https://github.com/shlinkio/shlink-web-client/issues/43) Fixed "List short URLs" menu item only selected when in first page.
## 1.0.0 - 2018-08-26
#### Added
## [1.0.0] - 2018-08-26
### Added
* [#4](https://github.com/shlinkio/shlink-web-client/issues/4) Now it is possible to export and import servers.
* Export all servers in a CSV file.
@@ -566,69 +482,53 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
* [#22](https://github.com/shlinkio/shlink-web-client/issues/22) Improved code coverage.
* [#28](https://github.com/shlinkio/shlink-web-client/issues/28) Added integration with [Scrutinizer](https://scrutinizer-ci.com/g/shlinkio/shlink-web-client/).
#### Changed
### Changed
* [#33](https://github.com/shlinkio/shlink-web-client/issues/33) Changed to [adidas coding style](https://github.com/adidas/js-linter-configs) for Javascript.
* [#32](https://github.com/shlinkio/shlink-web-client/issues/32) Changed to [adidas coding style](https://github.com/adidas/js-linter-configs) for stylesheets.
* [#26](https://github.com/shlinkio/shlink-web-client/issues/26) The tags input now displays tags using their actual color.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* *Nothing*
## 0.2.0 - 2018-08-12
#### Added
## [0.2.0] - 2018-08-12
### Added
* [#12](https://github.com/shlinkio/shlink-web-client/issues/12) Improved code coverage
* [#20](https://github.com/shlinkio/shlink-web-client/issues/20) Added servers list in welcome page, as well as added link to create one when none exist.
#### Changed
### Changed
* [#11](https://github.com/shlinkio/shlink-web-client/issues/11) Improved app icons fro progressive web apps.
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* [#19](https://github.com/shlinkio/shlink-web-client/issues/19) Added workaround in tags input so that it is possible to add tags on Android devices.
* [#17](https://github.com/shlinkio/shlink-web-client/issues/17) Fixed short URLs list not being sortable in mobile resolutions.
* [#13](https://github.com/shlinkio/shlink-web-client/issues/13) Improved visits page on mobile resolutions.
## 0.1.1 - 2018-08-06
#### Added
## [0.1.1] - 2018-08-06
### Added
* [#15](https://github.com/shlinkio/shlink-web-client/issues/15) Added a `Dockerfile` that can be used to generate a distributable docker image
#### Changed
### Changed
* *Nothing*
#### Deprecated
### Deprecated
* *Nothing*
#### Removed
### Removed
* *Nothing*
#### Fixed
### Fixed
* *Nothing*

View File

@@ -1,11 +1,11 @@
FROM node:12.16.3-alpine as node
FROM node:14.15.0-alpine as node
COPY . /shlink-web-client
ARG VERSION="latest"
ENV VERSION ${VERSION}
RUN cd /shlink-web-client && \
npm install && npm run build -- ${VERSION} --no-dist
FROM nginx:1.17.10-alpine
FROM nginx:1.19.3-alpine
LABEL maintainer="Alejandro Celaya <alejandro@alejandrocelaya.com>"
RUN rm -r /usr/share/nginx/html && rm /etc/nginx/conf.d/default.conf
COPY config/docker/nginx.conf /etc/nginx/conf.d/default.conf

View File

@@ -1,6 +1,6 @@
# shlink-web-client
[![Build Status](https://img.shields.io/travis/shlinkio/shlink-web-client.svg?style=flat-square)](https://travis-ci.org/shlinkio/shlink-web-client)
[![Build Status](https://img.shields.io/travis/com/shlinkio/shlink-web-client.svg?style=flat-square)](https://travis-ci.com/shlinkio/shlink-web-client)
[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/shlinkio/shlink-web-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/shlinkio/shlink-web-client/)
[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/shlinkio/shlink-web-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/shlinkio/shlink-web-client/)
[![GitHub release](https://img.shields.io/github/release/shlinkio/shlink-web-client.svg?style=flat-square)](https://github.com/shlinkio/shlink-web-client/releases/latest)

View File

@@ -4,11 +4,26 @@ server {
root /usr/share/nginx/html;
index index.html;
# Expire rules for static content
# HTML files should never be cached. There's only one here, which is the entry point (index.html)
location ~* \.(?:manifest|appcache|html?|xml|json)$ {
expires -1;
}
# Images and other binary assets can be saved for a month
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
add_header Cache-Control "public";
}
# JS and CSS files can be saved for a year, as they are always hashed. New versions will include a new hash anyway, forcing the download
location ~* \.(?:css|js)$ {
expires 1y;
add_header Cache-Control "public";
}
# When requesting static paths with extension, try them, and return a 404 if not found
location ~* .+\.(css|js|html|png|jpe?g|gif|bmp|ico|json|csv|otf|eot|svg|svgz|ttf|woff|woff2|ijmap|pdf|tif|map) {
try_files $uri $uri/ =404;
}
# When requesting a path without extension, try it, and return the index if not found
# This allows HTML5 history paths to be handled by the client application
location / {

View File

@@ -1,4 +1,4 @@
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
Enzyme.configure({ adapter: new Adapter() });

View File

@@ -13,7 +13,6 @@ const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
@@ -611,25 +610,6 @@ module.exports = (webpackEnv) => {
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build.
isEnvProduction &&
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
exclude: [ /\.map$/, /asset-manifest\.json$/ ],
importWorkboxFrom: 'cdn',
navigateFallback: `${publicUrl}/index.html`,
navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'),
// Exclude URLs containing a dot, as they're likely a resource in
// public/ and not a SPA route
new RegExp('/[^/]+\\.[^/]+$'),
],
}),
// TypeScript type checking
useTypeScript &&
new ForkTsCheckerWebpackPlugin({

View File

@@ -110,7 +110,7 @@ module.exports = function(proxy, allowedHost) {
// We do this in development to avoid hitting the production cache if
// it used the same host and port.
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
app.use(noopServiceWorkerMiddleware());
app.use(noopServiceWorkerMiddleware(paths.publicUrl));
},
};
};

View File

@@ -3,7 +3,7 @@ version: '3'
services:
shlink_web_client_node:
container_name: shlink_web_client_node
image: node:12.16.3-alpine
image: node:14.15.0-alpine
command: /bin/sh -c "cd /home/shlink/www && npm install && npm run start"
volumes:
- ./:/home/shlink/www

19385
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,146 +17,148 @@
"test": "node scripts/test.js --env=jsdom --colors",
"test:ci": "npm run test -- --coverage --coverageReporters=text --coverageReporters=text-summary --coverageReporters=clover",
"test:pretty": "npm run test -- --coverage --coverageReporters=text --coverageReporters=text-summary --coverageReporters=html",
"mutate": "./node_modules/.bin/stryker run",
"mutate": "./node_modules/.bin/stryker run --concurrency 4",
"mutate:ci": "npm run mutate -- --mutate=$MUTATION_FILES"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.14.0",
"@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-regular-svg-icons": "^5.14.0",
"@fortawesome/free-solid-svg-icons": "^5.14.0",
"@fortawesome/react-fontawesome": "^0.1.11",
"array-filter": "^1.0.0",
"array-map": "^0.0.0",
"array-reduce": "^0.0.0",
"axios": "^0.20.0",
"bootstrap": "^4.5.2",
"@fortawesome/fontawesome-free": "^5.15.1",
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/react-fontawesome": "^0.1.12",
"axios": "^0.21.0",
"bootstrap": "^4.5.3",
"bottlejs": "^2.0.0",
"bowser": "^2.10.0",
"chart.js": "^2.9.3",
"bowser": "^2.11.0",
"chart.js": "^2.9.4",
"classnames": "^2.2.6",
"compare-versions": "^3.6.0",
"csvjson": "^5.1.0",
"event-source-polyfill": "^1.0.17",
"event-source-polyfill": "^1.0.21",
"leaflet": "^1.7.1",
"moment": "^2.27.0",
"promise": "^8.0.3",
"moment": "^2.29.1",
"promise": "^8.1.0",
"qs": "^6.9.4",
"ramda": "^0.27.1",
"react": "^16.13.1",
"react-autosuggest": "^10.0.2",
"react-chartjs-2": "^2.10.0",
"react-color": "^2.18.1",
"react": "^17.0.1",
"react-autosuggest": "^10.0.3",
"react-chartjs-2": "^2.11.1",
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.0.2",
"react-datepicker": "~1.5.0",
"react-dom": "^16.13.1",
"react-datepicker": "^3.3.0",
"react-dom": "^17.0.1",
"react-external-link": "^1.1.1",
"react-leaflet": "^2.7.0",
"react-moment": "^0.9.7",
"react-redux": "^7.2.1",
"react-leaflet": "^3.0.2",
"react-moment": "^1.0.0",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0",
"react-swipeable": "^5.5.1",
"react-swipeable": "^6.0.0",
"react-tagsinput": "^3.19.0",
"reactstrap": "^8.0.1",
"redux": "^4.0.4",
"redux-localstorage-simple": "^2.2.0",
"reactstrap": "^8.7.1",
"redux": "^4.0.5",
"redux-localstorage-simple": "^2.3.1",
"redux-thunk": "^2.3.0",
"uuid": "^3.3.3"
"uuid": "^8.3.1"
},
"devDependencies": {
"@babel/core": "^7.6.2",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
"@babel/plugin-proposal-optional-chaining": "^7.11.0",
"@babel/core": "^7.12.3",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1",
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
"@shlinkio/eslint-config-js-coding-standard": "~1.1.0",
"@stryker-mutator/core": "^3.2.4",
"@stryker-mutator/typescript": "^3.2.4",
"@stryker-mutator/jest-runner": "^3.2.4",
"@svgr/webpack": "^4.3.3",
"@types/chart.js": "^2.9.24",
"@types/classnames": "^2.2.10",
"@types/enzyme": "^3.10.5",
"@types/jest": "^26.0.10",
"@types/leaflet": "^1.5.17",
"@stryker-mutator/core": "^4.1.2",
"@stryker-mutator/jest-runner": "^4.1.2",
"@stryker-mutator/typescript-checker": "^4.1.2",
"@svgr/webpack": "^5.4.0",
"@types/chart.js": "^2.9.27",
"@types/classnames": "^2.2.11",
"@types/enzyme": "^3.10.8",
"@types/jest": "^26.0.15",
"@types/leaflet": "^1.5.19",
"@types/moment": "^2.13.0",
"@types/qs": "^6.9.4",
"@types/ramda": "^0.27.14",
"@types/react": "^16.9.46",
"@types/react-autosuggest": "^10.0.0",
"@types/react-color": "^2.17.4",
"@types/qs": "^6.9.5",
"@types/ramda": "^0.27.32",
"@types/react": "^16.9.56",
"@types/react-autosuggest": "^10.0.1",
"@types/react-color": "^3.0.4",
"@types/react-copy-to-clipboard": "^4.3.0",
"@types/react-datepicker": "~1.8.0",
"@types/react-dom": "^16.9.8",
"@types/react-datepicker": "^3.1.1",
"@types/react-dom": "^16.9.9",
"@types/react-leaflet": "^2.5.2",
"@types/react-redux": "^7.1.9",
"@types/react-router-dom": "^5.1.5",
"@types/react-redux": "^7.1.11",
"@types/react-router-dom": "^5.1.6",
"@types/react-tagsinput": "^3.19.7",
"@types/reactstrap": "^8.5.1",
"@types/uuid": "^8.3.0",
"adm-zip": "^0.4.13",
"autoprefixer": "^9.6.3",
"@typescript-eslint/parser": "^4.7.0",
"@wojtekmaj/enzyme-adapter-react-17": "^0.3.1",
"adm-zip": "^0.4.16",
"autoprefixer": "^10.0.2",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "^26.3.0",
"babel-loader": "^8.0.6",
"babel-plugin-named-asset-import": "^0.3.4",
"babel-preset-react-app": "^9.0.2",
"babel-jest": "^26.6.3",
"babel-loader": "^8.2.1",
"babel-plugin-named-asset-import": "^0.3.7",
"babel-preset-react-app": "^10.0.0",
"babel-runtime": "^6.26.0",
"bfj": "^7.0.1",
"case-sensitive-paths-webpack-plugin": "^2.2.0",
"chalk": "^2.4.2",
"css-loader": "^3.2.0",
"dotenv": "^8.1.0",
"bfj": "^7.0.2",
"case-sensitive-paths-webpack-plugin": "^2.3.0",
"chalk": "^4.1.0",
"css-loader": "^5.0.1",
"dart-sass": "^1.25.0",
"dotenv": "^8.2.0",
"dotenv-expand": "^5.1.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"eslint": "^6.8.0",
"eslint-loader": "^3.0.2",
"file-loader": "^4.2.0",
"eslint": "^7.13.0",
"eslint-loader": "^4.0.2",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin-alt": "^0.4.14",
"fs-extra": "^8.1.0",
"html-webpack-plugin": "^4.0.0-beta.8",
"fs-extra": "^9.0.1",
"html-webpack-plugin": "^4.5.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.4.2",
"jest": "^26.6.3",
"jest-pnp-resolver": "^1.2.2",
"jest-resolve": "^26.4.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.14.1",
"jest-resolve": "^26.6.2",
"mini-css-extract-plugin": "^1.3.1",
"object-assign": "^4.1.1",
"ocular.js": "^0.1.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"pnp-webpack-plugin": "^1.5.0",
"postcss": "^7.0.18",
"postcss-flexbugs-fixes": "^4.1.0",
"optimize-css-assets-webpack-plugin": "^5.0.4",
"pnp-webpack-plugin": "^1.6.4",
"postcss": "^8.1.7",
"postcss-flexbugs-fixes": "^4.2.1",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"postcss-safe-parser": "^4.0.1",
"postcss-safe-parser": "^5.0.2",
"raf": "^3.4.1",
"react-app-polyfill": "^1.0.6",
"react-dev-utils": "^9.1.0",
"resolve": "^1.12.0",
"sass-loader": "^10.0.2",
"react-app-polyfill": "^2.0.0",
"react-dev-utils": "^11.0.0",
"resolve": "^1.19.0",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
"serve": "^11.3.2",
"stryker-cli": "^1.0.0",
"style-loader": "^1.2.1",
"stylelint": "^13.7.0",
"style-loader": "^2.0.0",
"stylelint": "^13.7.2",
"stylelint-config-adidas": "^1.3.0",
"stylelint-config-adidas-bem": "^1.2.0",
"stylelint-config-recommended-scss": "^4.2.0",
"stylelint-scss": "^3.18.0",
"sw-precache-webpack-plugin": "^0.11.5",
"terser-webpack-plugin": "^2.1.2",
"ts-jest": "^26.3.0",
"sw-precache-webpack-plugin": "^1.0.0",
"terser-webpack-plugin": "^4.2.3",
"ts-jest": "^26.4.4",
"ts-mockery": "^1.2.0",
"typescript": "^3.9.7",
"url-loader": "^2.2.0",
"webpack": "^4.41.0",
"webpack-dev-server": "^3.8.2",
"typescript": "^4.0.5",
"url-loader": "^4.1.1",
"webpack": "^4.44.2",
"webpack-dev-server": "^3.11.0",
"webpack-manifest-plugin": "^2.2.0",
"whatwg-fetch": "^3.0.0",
"workbox-webpack-plugin": "^4.3.1"
"whatwg-fetch": "^3.5.0"
},
"babel": {
"presets": [
"react-app"
[
"react-app",
{
"runtime": "automatic"
}
]
],
"plugins": [
"@babel/plugin-proposal-optional-chaining",

View File

@@ -9,7 +9,7 @@
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" crossorigin="use-credentials">
<!-- FavIcon itself -->
<link rel="icon" type="image/x-icon" href="%PUBLIC_URL%/favicon.ico">

View File

@@ -2,8 +2,7 @@
set -ex
# PLATFORMS="linux/arm/v7,linux/arm64/v8,linux/amd64"
PLATFORMS="linux/amd64"
PLATFORMS="linux/arm/v7,linux/arm64/v8,linux/amd64"
DOCKER_IMAGE="shlinkio/shlink-web-client"
if [[ "$GITHUB_REF" == *"main"* ]]; then

View File

@@ -1,4 +1,4 @@
import React, { useEffect, FC } from 'react';
import { useEffect, FC } from 'react';
import { Route, Switch } from 'react-router-dom';
import NotFound from './common/NotFound';
import { ServersMap } from './servers/data';

View File

@@ -5,7 +5,7 @@ import {
faPen as editIcon,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { FC } from 'react';
import { FC } from 'react';
import { NavLink, NavLinkProps } from 'react-router-dom';
import classNames from 'classnames';
import { Location } from 'history';

View File

@@ -1,4 +1,4 @@
import React, { ReactNode } from 'react';
import { Component, ReactNode } from 'react';
import { Button } from 'reactstrap';
import './ErrorHandler.scss';
@@ -9,7 +9,7 @@ interface ErrorHandlerState {
const ErrorHandler = (
{ location }: Window,
{ error }: Console,
) => class ErrorHandler extends React.Component<any, ErrorHandlerState> {
) => class ErrorHandler extends Component<any, ErrorHandlerState> {
public constructor(props: object) {
super(props);
this.state = { hasError: false };

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { isEmpty, values } from 'ramda';
import { Link } from 'react-router-dom';
import ServersListGroup from '../servers/ServersListGroup';

View File

@@ -1,6 +1,6 @@
import { faPlus as plusIcon, faChevronDown as arrowIcon, faCogs as cogsIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { FC, useEffect } from 'react';
import { FC, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import classNames from 'classnames';

View File

@@ -1,6 +1,6 @@
import React, { FC, useEffect } from 'react';
import { FC, useEffect } from 'react';
import { Route, Switch } from 'react-router-dom';
import { EventData, Swipeable } from 'react-swipeable';
import { useSwipeable } from 'react-swipeable';
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames';
@@ -33,7 +33,7 @@ const MenuLayout = (
const burgerClasses = classNames('menu-layout__burger-icon', {
'menu-layout__burger-icon--active': sidebarVisible,
});
const swipeMenuIfNoModalExists = (callback: () => void) => (e: EventData) => {
const swipeMenuIfNoModalExists = (callback: () => void) => (e: any) => {
const swippedOnVisitsTable = (e.event.composedPath() as HTMLElement[]).some(
({ classList }) => classList?.contains('visits-table'),
);
@@ -44,17 +44,17 @@ const MenuLayout = (
callback();
};
const swipeableProps = useSwipeable({
delta: 40,
onSwipedLeft: swipeMenuIfNoModalExists(hideSidebar),
onSwipedRight: swipeMenuIfNoModalExists(showSidebar),
});
return (
<React.Fragment>
<>
<FontAwesomeIcon icon={burgerIcon} className={burgerClasses} onClick={toggleSidebar} />
<Swipeable
delta={40}
className="menu-layout__swipeable"
onSwipedLeft={swipeMenuIfNoModalExists(hideSidebar)}
onSwipedRight={swipeMenuIfNoModalExists(showSidebar)}
>
<div {...swipeableProps} className="menu-layout__swipeable">
<div className="row menu-layout__swipeable-inner">
<AsideMenu className="col-lg-2 col-md-3" selectedServer={selectedServer} showOnMobile={sidebarVisible} />
<div className="col-lg-10 offset-lg-2 col-md-9 offset-md-3" onClick={() => hideSidebar()}>
@@ -72,8 +72,8 @@ const MenuLayout = (
</div>
</div>
</div>
</Swipeable>
</React.Fragment>
</div>
</>
);
}, ServerError);

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import './NoMenuLayout.scss';
const NoMenuLayout: FC = ({ children }) => <div className="no-menu-wrapper">{children}</div>;

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { Link } from 'react-router-dom';
interface NotFoundProps {

View File

@@ -1,4 +1,4 @@
import React, { PropsWithChildren, useEffect } from 'react';
import { PropsWithChildren, useEffect } from 'react';
import { RouteComponentProps } from 'react-router';
const ScrollToTop = () => ({ location, children }: PropsWithChildren<RouteComponentProps>) => {
@@ -6,7 +6,7 @@ const ScrollToTop = () => ({ location, children }: PropsWithChildren<RouteCompon
scrollTo(0, 0);
}, [ location ]);
return <React.Fragment>{children}</React.Fragment>;
return <>{children}</>;
};
export default ScrollToTop;

View File

@@ -1,4 +1,3 @@
import React from 'react';
import classNames from 'classnames';
import { pipe } from 'ramda';
import { ExternalLink } from 'react-external-link';
@@ -28,7 +27,7 @@ const ShlinkVersions = (
return (
<small className={classNames('text-muted', className)}>
{isReachableServer(selectedServer) &&
<React.Fragment>Server: <VersionLink project="shlink" version={selectedServer.printableVersion} /> - </React.Fragment>
<>Server: <VersionLink project="shlink" version={selectedServer.printableVersion} /> - </>
}
Client: <VersionLink project="shlink-web-client" version={normalizedClientVersion} />
</small>

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import classNames from 'classnames';
import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
import {

View File

@@ -34,6 +34,10 @@ body,
display: block !important;
}
.react-datepicker-popper {
z-index: 2;
}
.navbar-brand {
@media (max-width: $smMax) {
margin: 0 auto !important;

View File

@@ -1,9 +1,7 @@
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { homepage } from '../package.json';
import registerServiceWorker from './registerServiceWorker';
import container from './container';
import store from './container/store';
import { fixLeafletIcons } from './utils/helpers/leaflet';
@@ -30,4 +28,3 @@ render(
</Provider>,
document.getElementById('root'),
);
registerServiceWorker();

View File

@@ -1,4 +1,4 @@
import React, { FC, useEffect } from 'react';
import { FC, useEffect } from 'react';
import { pipe } from 'ramda';
import { CreateVisit } from '../../visits/types';
import { MercureInfo } from '../reducers/mercureInfo';

View File

@@ -1,126 +0,0 @@
/* eslint-disable @typescript-eslint/promise-function-async, @typescript-eslint/no-misused-promises */
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
/* eslint no-console: "off" */
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
),
);
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
return navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://goo.gl/SC7cgQ',
);
});
}
// Is not local host. Just register service worker
return registerValidSW(swUrl);
});
}
}
function registerValidSW(swUrl) {
return navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch((error) => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then((response) => {
const NOT_FOUND_STATUS = 404;
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === NOT_FOUND_STATUS ||
response.headers.get('content-type').includes('javascript')
) {
// No service worker found. Probably a different app. Reload the page.
return navigator.serviceWorker.ready.then((registration) =>
registration.unregister().then(() => {
window.location.reload();
}));
}
// Service worker found. Proceed as normal.
return registerValidSW(swUrl);
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.',
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister();
}).catch(() => {});
}
}

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { v4 as uuid } from 'uuid';
import { RouterProps } from 'react-router';
import classNames from 'classnames';

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { faMinusCircle as deleteIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useToggle } from '../utils/helpers/hooks';
@@ -17,14 +17,14 @@ const DeleteServerButton = (DeleteServerModal: FC<DeleteServerModalProps>): FC<D
const [ isModalOpen, , showModal, hideModal ] = useToggle();
return (
<React.Fragment>
<>
<span className={className} onClick={showModal}>
{!children && <FontAwesomeIcon icon={deleteIcon} />}
<span className={textClassName}>{children ?? 'Remove this server'}</span>
</span>
<DeleteServerModal server={server} isOpen={isModalOpen} toggle={hideModal} />
</React.Fragment>
</>
);
};

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { RouterProps } from 'react-router';
import { ServerWithId } from './data';

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { Button } from 'reactstrap';
import NoMenuLayout from '../common/NoMenuLayout';
import { ServerForm } from './helpers/ServerForm';

View File

@@ -1,5 +1,4 @@
import { isEmpty, values } from 'ramda';
import React from 'react';
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
import { Link } from 'react-router-dom';
import ServersExporter from './services/ServersExporter';
@@ -19,7 +18,7 @@ const ServersDropdown = (serversExporter: ServersExporter) => ({ servers, select
}
return (
<React.Fragment>
<>
{serversList.map(({ name, id }) => (
<DropdownItem
key={id}
@@ -34,7 +33,7 @@ const ServersDropdown = (serversExporter: ServersExporter) => ({ servers, select
<DropdownItem className="servers-dropdown__export-item" onClick={async () => serversExporter.exportServers()}>
Export servers
</DropdownItem>
</React.Fragment>
</>
);
};

View File

@@ -1,10 +1,10 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { ListGroup, ListGroupItem } from 'reactstrap';
import { Link } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons';
import './ServersListGroup.scss';
import { ServerWithId } from './data';
import './ServersListGroup.scss';
interface ServersListGroup {
servers: ServerWithId[];
@@ -18,7 +18,7 @@ const ServerListItem = ({ id, name }: { id: string; name: string }) => (
);
const ServersListGroup: FC<ServersListGroup> = ({ servers, children }) => (
<React.Fragment>
<>
<div className="container">
<h5>{children}</h5>
</div>
@@ -27,7 +27,7 @@ const ServersListGroup: FC<ServersListGroup> = ({ servers, children }) => (
{servers.map(({ id, name }) => <ServerListItem key={id} id={id} name={name} />)}
</ListGroup>
)}
</React.Fragment>
</>
);
export default ServersListGroup;

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { versionMatch, Versions } from '../../utils/helpers/version';
import { isReachableServer, SelectedServer } from '../data';
@@ -18,7 +18,7 @@ const ForServerVersion: FC<ForServerVersionProps> = ({ minVersion, maxVersion, s
return null;
}
return <React.Fragment>{children}</React.Fragment>;
return <>{children}</>;
};
export default ForServerVersion;

View File

@@ -1,4 +1,4 @@
import React, { useRef, RefObject, ChangeEvent, MutableRefObject } from 'react';
import { useRef, RefObject, ChangeEvent, MutableRefObject } from 'react';
import { UncontrolledTooltip } from 'reactstrap';
import ServersImporter from '../services/ServersImporter';
import { ServerData } from '../data';
@@ -33,7 +33,7 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ({
.catch(onImportError);
return (
<React.Fragment>
<>
<button
type="button"
className="btn btn-outline-secondary mr-2"
@@ -47,7 +47,7 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ({
</UncontrolledTooltip>
<input type="file" accept="text/csv" className="create-server__csv-select" ref={ref} onChange={onChange} />
</React.Fragment>
</>
);
};

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { Link } from 'react-router-dom';
import Message from '../../utils/Message';
import ServersListGroup from '../ServersListGroup';
@@ -19,10 +19,10 @@ export const ServerError = (DeleteServerButton: FC<DeleteServerButtonProps>): FC
<Message type="error">
{!isServerWithId(selectedServer) && 'Could not find this Shlink server.'}
{isServerWithId(selectedServer) && (
<React.Fragment>
<>
<p>Oops! Could not connect to this Shlink server.</p>
Make sure you have internet connection, and the server is properly configured and on-line.
</React.Fragment>
</>
)}
</Message>
</div>

View File

@@ -1,4 +1,4 @@
import React, { FC, useEffect, useState } from 'react';
import { FC, useEffect, useState } from 'react';
import { HorizontalFormGroup } from '../../utils/HorizontalFormGroup';
import { handleEventPreventingDefault } from '../../utils/utils';
import { ServerData } from '../data';

View File

@@ -1,4 +1,4 @@
import React, { FC, useEffect } from 'react';
import { FC, useEffect } from 'react';
import { RouteComponentProps } from 'react-router';
import Message from '../../utils/Message';
import { isNotFoundServer, SelectedServer } from '../data';

View File

@@ -1,4 +1,4 @@
import React, { FC, useEffect } from 'react';
import { FC, useEffect } from 'react';
interface WithoutSelectedServerProps {
resetSelectedServer: Function;

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Card, CardBody, CardHeader, FormGroup, Input } from 'reactstrap';
import classNames from 'classnames';
import ToggleSwitch from '../utils/ToggleSwitch';

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import NoMenuLayout from '../common/NoMenuLayout';
const Settings = (RealTimeUpdates: FC) => () => (

View File

@@ -1,7 +1,7 @@
import { faAngleDoubleDown as downIcon, faAngleDoubleUp as upIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEmpty, pipe, replace, trim } from 'ramda';
import React, { FC, useState } from 'react';
import { FC, useState } from 'react';
import { Collapse, FormGroup, Input } from 'reactstrap';
import { InputType } from 'reactstrap/lib/Input';
import * as m from 'moment';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
import { pageIsEllipsis, keyForPage, progressivePagination, prettifyPageNumber } from '../utils/helpers/pagination';

View File

@@ -1,6 +1,6 @@
import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { FC } from 'react';
import { FC } from 'react';
import { isEmpty, pipe } from 'ramda';
import moment from 'moment';
import SearchField from '../utils/SearchField';

View File

@@ -1,4 +1,4 @@
import React, { FC, useEffect, useState } from 'react';
import { FC, useEffect, useState } from 'react';
import { ShlinkShortUrlsResponse } from '../utils/services/types';
import Paginator from './Paginator';
import { ShortUrlsListProps, WithList } from './ShortUrlsList';
@@ -20,13 +20,13 @@ const ShortUrls = (SearchBar: FC, ShortUrlsList: FC<ShortUrlsListProps & WithLis
}, [ serverId, page ]);
return (
<React.Fragment>
<>
<div className="form-group"><SearchBar /></div>
<div>
<ShortUrlsList {...props} shortUrlsList={data} key={urlsListKey} />
<Paginator paginator={pagination} serverId={serverId} />
</div>
</React.Fragment>
</>
);
};

View File

@@ -1,7 +1,7 @@
import { faCaretDown as caretDownIcon, faCaretUp as caretUpIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { head, isEmpty, keys, values } from 'ramda';
import React, { FC, useEffect, useState } from 'react';
import { FC, useEffect, useState } from 'react';
import qs from 'qs';
import { RouteComponentProps } from 'react-router';
import SortingDropdown from '../utils/SortingDropdown';
@@ -107,7 +107,7 @@ const ShortUrlsList = (ShortUrlsRow: FC<ShortUrlsRowProps>) => boundToMercureHub
}, []);
return (
<React.Fragment>
<>
<div className="d-block d-md-none mb-3">
<SortingDropdown
items={SORTABLE_FIELDS}
@@ -154,7 +154,7 @@ const ShortUrlsList = (ShortUrlsRow: FC<ShortUrlsRowProps>) => boundToMercureHub
{renderShortUrls()}
</tbody>
</table>
</React.Fragment>
</>
);
}, () => 'https://shlink.io/new-visit');

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
import { Modal, ModalBody, ModalHeader } from 'reactstrap';
@@ -41,12 +40,12 @@ const UseExistingIfFoundInfoIcon = () => {
const [ isModalOpen, toggleModal ] = useToggle();
return (
<React.Fragment>
<>
<span title="What does this mean?">
<FontAwesomeIcon icon={infoIcon} style={{ cursor: 'pointer' }} onClick={toggleModal} />
</span>
<InfoModal isOpen={isModalOpen} toggle={toggleModal} />
</React.Fragment>
</>
);
};

View File

@@ -1,7 +1,7 @@
import { faCopy as copyIcon } from '@fortawesome/free-regular-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isNil } from 'ramda';
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import CopyToClipboard from 'react-copy-to-clipboard';
import { Card, CardBody, Tooltip } from 'reactstrap';
import { ShortUrlCreation } from '../reducers/shortUrlCreation';

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { identity, pipe } from 'ramda';
import { ShortUrlDeletion } from '../reducers/shortUrlDeletion';

View File

@@ -1,4 +1,4 @@
import React, { ChangeEvent, useState } from 'react';
import { ChangeEvent, useState } from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, UncontrolledTooltip } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
@@ -17,10 +17,10 @@ interface EditMetaModalConnectProps extends ShortUrlModalProps {
editShortUrlMeta: (shortCode: string, domain: OptionalString, meta: Nullable<ShortUrlMeta>) => Promise<void>;
}
const dateOrUndefined = (shortUrl: ShortUrl | undefined, dateName: 'validSince' | 'validUntil') => {
const dateOrNull = (shortUrl: ShortUrl | undefined, dateName: 'validSince' | 'validUntil') => {
const date = shortUrl?.meta?.[dateName];
return date ? moment(date) : undefined;
return date ? moment(date) : null;
};
const EditMetaModal = (
@@ -28,8 +28,8 @@ const EditMetaModal = (
) => {
const { saving, error } = shortUrlMeta;
const url = shortUrl && (shortUrl.shortUrl || '');
const [ validSince, setValidSince ] = useState(dateOrUndefined(shortUrl, 'validSince'));
const [ validUntil, setValidUntil ] = useState(dateOrUndefined(shortUrl, 'validUntil'));
const [ validSince, setValidSince ] = useState(dateOrNull(shortUrl, 'validSince'));
const [ validUntil, setValidUntil ] = useState(dateOrNull(shortUrl, 'validUntil'));
const [ maxVisits, setMaxVisits ] = useState(shortUrl?.meta?.maxVisits);
const close = pipe(resetShortUrlMeta, toggle);
@@ -56,7 +56,7 @@ const EditMetaModal = (
selected={validSince}
maxDate={validUntil}
isClearable
onChange={setValidSince as any}
onChange={setValidSince}
/>
</FormGroup>
<FormGroup>

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, Button } from 'reactstrap';
import { ExternalLink } from 'react-external-link';
import { ShortUrlEdition } from '../reducers/shortUrlEdition';

View File

@@ -1,4 +1,4 @@
import React, { FC, useEffect, useState } from 'react';
import { FC, useEffect, useState } from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { ExternalLink } from 'react-external-link';
import { ShortUrlTags } from '../reducers/shortUrlTags';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Modal, ModalBody, ModalHeader } from 'reactstrap';
import { ExternalLink } from 'react-external-link';
import { ShortUrlModalProps } from '../data';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Modal, ModalBody, ModalHeader } from 'reactstrap';
import { ExternalLink } from 'react-external-link';
import { ShortUrlModalProps } from '../data';

View File

@@ -1,4 +1,4 @@
import React, { useRef } from 'react';
import { useRef } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
import { UncontrolledTooltip } from 'reactstrap';
@@ -32,7 +32,7 @@ const ShortUrlVisitsCount = ({ visitsCount, shortUrl, selectedServer, active = f
const tooltipRef = useRef<HTMLElement | null>();
return (
<React.Fragment>
<>
<span className="indivisible">
{visitsLink}
<small
@@ -50,7 +50,7 @@ const ShortUrlVisitsCount = ({ visitsCount, shortUrl, selectedServer, active = f
<UncontrolledTooltip target={(() => tooltipRef.current) as any} placement="bottom">
This short URL will not accept more than <b>{prettifiedMaxVisits}</b> visits.
</UncontrolledTooltip>
</React.Fragment>
</>
);
};

View File

@@ -1,5 +1,5 @@
import { isEmpty } from 'ramda';
import React, { FC, useEffect, useRef } from 'react';
import { FC, useEffect, useRef } from 'react';
import Moment from 'react-moment';
import { ExternalLink } from 'react-external-link';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

View File

@@ -9,7 +9,7 @@ import {
faLink as linkIcon,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { FC } from 'react';
import { FC } from 'react';
import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
import { useToggle } from '../../utils/helpers/hooks';
import { ShortUrl, ShortUrlModalProps } from '../data';

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { Link } from 'react-router-dom';
import { isServerWithId, SelectedServer, ServerWithId } from '../../servers/data';
import { ShortUrl } from '../data';

View File

@@ -1,7 +1,7 @@
import { Card, CardHeader, CardBody, Button, Collapse } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash as deleteIcon, faPencilAlt as editIcon, faLink, faEye } from '@fortawesome/free-solid-svg-icons';
import React, { FC } from 'react';
import { FC } from 'react';
import { Link } from 'react-router-dom';
import { prettify } from '../utils/helpers/numbers';
import { useToggle } from '../utils/helpers/hooks';

View File

@@ -1,4 +1,4 @@
import React, { FC, useEffect, useState } from 'react';
import { FC, useEffect, useState } from 'react';
import { splitEvery } from 'ramda';
import Message from '../utils/Message';
import SearchField from '../utils/SearchField';
@@ -48,7 +48,7 @@ const TagsList = (TagCard: FC<TagCardProps>) => boundToMercureHub((
const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags);
return (
<React.Fragment>
<>
{tagsGroups.map((group, index) => (
<div key={index} className="col-md-6 col-xl-3">
{group.map((tag) => (
@@ -63,17 +63,17 @@ const TagsList = (TagCard: FC<TagCardProps>) => boundToMercureHub((
))}
</div>
))}
</React.Fragment>
</>
);
};
return (
<React.Fragment>
<>
{!tagsList.loading && <SearchField className="mb-3" placeholder="Search tags..." onChange={filterTags} />}
<div className="row">
{renderContent()}
</div>
</React.Fragment>
</>
);
}, () => 'https://shlink.io/new-visit');

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { TagDeletion } from '../reducers/tagDelete';
import { TagModalProps } from '../data';

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader, Popover } from 'reactstrap';
import { ChromePicker } from 'react-color';
import { faPalette as colorIcon } from '@fortawesome/free-solid-svg-icons';
@@ -21,14 +21,14 @@ const EditTagModal = ({ getColorForKey }: ColorGenerator) => (
) => {
const [ newTagName, setNewTagName ] = useState(tag);
const [ color, setColor ] = useState(getColorForKey(tag));
const [ showColorPicker, toggleColorPicker ] = useToggle();
const [ showColorPicker, toggleColorPicker, , hideColorPicker ] = useToggle();
const saveTag = handleEventPreventingDefault(async () => editTag(tag, newTagName, color)
.then(() => tagEdited(tag, newTagName, color))
.then(toggle)
.catch(() => {}));
return (
<Modal isOpen={isOpen} toggle={toggle} centered>
<Modal isOpen={isOpen} toggle={toggle} centered onClosed={hideColorPicker}>
<form onSubmit={saveTag}>
<ModalHeader toggle={toggle}>Edit tag</ModalHeader>
<ModalBody>

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import ColorGenerator from '../../utils/services/ColorGenerator';
import './Tag.scss';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import ColorGenerator from '../../utils/services/ColorGenerator';
import './TagBullet.scss';

View File

@@ -1,4 +1,4 @@
import React, { ChangeEvent, useEffect } from 'react';
import { ChangeEvent, useEffect } from 'react';
import TagsInput, { RenderInputProps, RenderTagProps } from 'react-tagsinput';
import Autosuggest, { ChangeEvent as AutoChangeEvent, SuggestionSelectedEventData } from 'react-autosuggest';
import ColorGenerator from '../../utils/services/ColorGenerator';
@@ -50,10 +50,10 @@ const TagsSelector = (colorGenerator: ColorGenerator) => (
shouldRenderSuggestions={(value: string) => value.trim().length > 0}
getSuggestionValue={(suggestion) => suggestion}
renderSuggestion={(suggestion) => (
<React.Fragment>
<>
<TagBullet tag={suggestion} colorGenerator={colorGenerator} />
{suggestion}
</React.Fragment>
</>
)}
onSuggestionsFetchRequested={() => {}}
onSuggestionsClearRequested={() => {}}

View File

@@ -1,4 +1,4 @@
import React, { ChangeEvent, FC } from 'react';
import { ChangeEvent, FC } from 'react';
import classNames from 'classnames';
import { v4 as uuid } from 'uuid';
import { identity } from 'ramda';
@@ -24,7 +24,7 @@ const BooleanControl: FC<BooleanControlWithTypeProps> = (
};
return (
<span className={classNames('custom-control', typeClasses, className)} style={{ display: 'inline' }}>
<span className={classNames('custom-control', typeClasses, className)}>
<input type="checkbox" className="custom-control-input" id={id} checked={checked} onChange={onChecked} />
<label className="custom-control-label" htmlFor={id}>{children}</label>
</span>

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import BooleanControl, { BooleanControlProps } from './BooleanControl';
const Checkbox: FC<BooleanControlProps> = (props) => <BooleanControl type="checkbox" {...props} />;

View File

@@ -1,26 +1,45 @@
import React, { Component, RefObject } from 'react';
import { isNil } from 'ramda';
import { useRef } from 'react';
import { isNil, dissoc } from 'ramda';
import DatePicker, { ReactDatePickerProps } from 'react-datepicker';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCalendarAlt as calendarIcon } from '@fortawesome/free-regular-svg-icons';
import classNames from 'classnames';
import moment from 'moment';
import './DateInput.scss';
export interface DateInputProps extends ReactDatePickerProps {
ref?: RefObject<Component<ReactDatePickerProps> & { input: HTMLInputElement }>;
interface DatePropsInterface {
endDate?: moment.Moment | null;
maxDate?: moment.Moment | null;
minDate?: moment.Moment | null;
selected?: moment.Moment | null;
startDate?: moment.Moment | null;
onChange?: (date: moment.Moment | null) => void;
}
export type DateInputProps = DatePropsInterface & Omit<ReactDatePickerProps, keyof DatePropsInterface>;
const transformProps = (props: DateInputProps): ReactDatePickerProps => ({
...dissoc('ref', props),
endDate: props.endDate?.toDate(),
maxDate: props.maxDate?.toDate(),
minDate: props.minDate?.toDate(),
selected: props.selected?.toDate(),
startDate: props.startDate?.toDate(),
onChange: (date: Date | null) => props.onChange?.(date && moment(date)),
});
const DateInput = (props: DateInputProps) => {
const { className, isClearable, selected, ref = React.createRef() } = props;
const { className, isClearable, selected } = props;
const showCalendarIcon = !isClearable || isNil(selected);
const ref = useRef<{ input: HTMLInputElement }>();
return (
<div className="date-input-container">
<DatePicker
{...props}
{...transformProps(props)}
dateFormat="yyyy-MM-dd"
className={classNames('date-input-container__input form-control', className)}
dateFormat="YYYY-MM-DD"
readOnly
// @ts-expect-error
ref={ref}
/>
{showCalendarIcon && (

View File

@@ -1,4 +1,3 @@
import React from 'react';
import moment from 'moment';
import DateInput from './DateInput';
import './DateRangeRow.scss';

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { v4 as uuid } from 'uuid';
import { InputType } from 'reactstrap/lib/Input';

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { Card } from 'reactstrap';
import classNames from 'classnames';
import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
interface PaginationDropdownProps {

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch as searchIcon } from '@fortawesome/free-solid-svg-icons';
import classNames from 'classnames';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { toPairs } from 'ramda';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

View File

@@ -1,4 +1,4 @@
import React, { FC } from 'react';
import { FC } from 'react';
import BooleanControl, { BooleanControlProps } from './BooleanControl';
const ToggleSwitch: FC<BooleanControlProps> = (props) => <BooleanControl type="switch" {...props} />;

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import qs from 'qs';
import { RouteComponentProps } from 'react-router';
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';

View File

@@ -1,6 +1,5 @@
import { UncontrolledTooltip } from 'reactstrap';
import Moment from 'react-moment';
import React from 'react';
import { ExternalLink } from 'react-external-link';
import { ShortUrlDetail } from './reducers/shortUrlDetail';
import { ShortUrlVisits } from './reducers/shortUrlVisits';
@@ -30,9 +29,9 @@ const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }: ShortU
</span>
);
const visitsStatsTitle = (
<React.Fragment>
<>
Visits for <ExternalLink href={shortLink} />
</React.Fragment>
</>
);
return (

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { RouteComponentProps } from 'react-router';
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
import ColorGenerator from '../utils/services/ColorGenerator';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import Tag from '../tags/helpers/Tag';
import ColorGenerator from '../utils/services/ColorGenerator';
import VisitsHeader from './VisitsHeader';

View File

@@ -1,5 +1,5 @@
import { Button, Card } from 'reactstrap';
import React, { FC, ReactNode } from 'react';
import { FC, ReactNode } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import ShortUrlVisitsCount from '../short-urls/helpers/ShortUrlVisitsCount';

View File

@@ -1,5 +1,5 @@
import { isEmpty, propEq, values } from 'ramda';
import React, { useState, useEffect, useMemo, FC } from 'react';
import { useState, useEffect, useMemo, FC } from 'react';
import { Button, Card, Collapse, Progress } from 'reactstrap';
import classNames from 'classnames';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -186,7 +186,7 @@ const VisitsStats: FC<VisitsStatsProps> = (
};
return (
<React.Fragment>
<>
{children}
<section className="mt-4">
@@ -244,7 +244,7 @@ const VisitsStats: FC<VisitsStatsProps> = (
<section>
{renderVisitsContent()}
</section>
</React.Fragment>
</>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import Moment from 'react-moment';
import classNames from 'classnames';
import { min, splitEvery } from 'ramda';

View File

@@ -1,4 +1,4 @@
import React, { useRef } from 'react';
import { useRef } from 'react';
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
import { keys, values } from 'ramda';
import classNames from 'classnames';

View File

@@ -1,5 +1,5 @@
import { Card, CardHeader, CardBody, CardFooter } from 'reactstrap';
import React, { ReactNode } from 'react';
import { ReactNode } from 'react';
import DefaultChart, { DefaultChartProps } from './DefaultChart';
import './GraphCard.scss';

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo } from 'react';
import { useState, useMemo } from 'react';
import {
Card,
CardHeader,
@@ -199,7 +199,6 @@ const LineChartCard = (
},
tooltips: {
intersect: false,
// @ts-expect-error
axis: 'x',
callbacks: {
label: renderNonDoughnutChartLabel('yLabel'),

View File

@@ -1,6 +1,6 @@
import React, { FC } from 'react';
import { FC } from 'react';
import { Modal, ModalBody } from 'reactstrap';
import { Map, TileLayer, Marker, Popup } from 'react-leaflet';
import { MapContainer, TileLayer, Marker, Popup, MapContainerProps } from 'react-leaflet';
import { prop } from 'ramda';
import { CityStats } from '../types';
import './MapModal.scss';
@@ -19,7 +19,7 @@ const OpenStreetMapTile: FC = () => (
/>
);
const calculateMapProps = (locations: CityStats[]): any => {
const calculateMapProps = (locations: CityStats[]): MapContainerProps => {
if (locations.length === 0) {
return {};
}
@@ -42,14 +42,14 @@ const MapModal = ({ toggle, isOpen, title, locations = [] }: MapModalProps) => (
{title}
<button type="button" className="close" onClick={toggle}>&times;</button>
</h3>
<Map {...calculateMapProps(locations)}>
<MapContainer {...calculateMapProps(locations)}>
<OpenStreetMapTile />
{locations.map(({ cityName, latLong, count }, index) => (
<Marker key={index} position={latLong}>
<Popup><b>{count}</b> visit{count > 1 ? 's' : ''} from <b>{cityName}</b></Popup>
</Marker>
))}
</Map>
</MapContainer>
</ModalBody>
</Modal>
);

View File

@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react';
import { useRef, useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faMapMarkedAlt as mapIcon } from '@fortawesome/free-solid-svg-icons';
import { Dropdown, DropdownItem, DropdownMenu, UncontrolledTooltip } from 'reactstrap';
@@ -36,7 +36,7 @@ const OpenMapModalBtn = ({ modalTitle, activeCities, locations = [] }: OpenMapMo
};
return (
<React.Fragment>
<>
<button className="btn btn-link open-map-modal-btn__btn" ref={buttonRef as any} onClick={onClick}>
<FontAwesomeIcon icon={mapIcon} />
</button>
@@ -48,7 +48,7 @@ const OpenMapModalBtn = ({ modalTitle, activeCities, locations = [] }: OpenMapMo
</DropdownMenu>
</Dropdown>
<MapModal toggle={closeMap} isOpen={mapIsOpened} title={modalTitle} locations={locationsToShow} />
</React.Fragment>
</>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { fromPairs, pipe, reverse, sortBy, splitEvery, toLower, toPairs, type, zipObj } from 'ramda';
import SortingDropdown from '../../utils/SortingDropdown';
import PaginationDropdown from '../../utils/PaginationDropdown';
@@ -94,7 +94,7 @@ const SortableBarGraph = ({
);
const activeCities = Object.keys(currentPageStats);
const computeTitle = () => (
<React.Fragment>
<>
{title}
<div className="float-right">
<SortingDropdown
@@ -127,7 +127,7 @@ const SortableBarGraph = ({
{extraHeaderContent(pagination ? activeCities : undefined)}
</div>
)}
</React.Fragment>
</>
);
return (

View File

@@ -1,4 +1,4 @@
import { isNil, map, reduce } from 'ramda';
import { isNil, map } from 'ramda';
import { extractDomain, parseUserAgent } from '../../utils/helpers/visits';
import { hasValue } from '../../utils/utils';
import { CityStats, NormalizedVisit, Stats, Visit, VisitsStats } from '../types';
@@ -53,7 +53,7 @@ const updateCitiesForMapForVisit = (citiesForMapStats: Record<string, CityStats>
citiesForMapStats[city] = currentCity;
};
export const processStatsFromVisits = reduce(
export const processStatsFromVisits = (visits: NormalizedVisit[]) => visits.reduce(
(stats: VisitsStats, visit: NormalizedVisit) => {
// We mutate the original object because it has a big performance impact when large data sets are processed
updateOsStatsForVisit(stats.os, visit);

View File

@@ -2,7 +2,8 @@ const jestConfig = require(`${__dirname}/jest.config.js`);
module.exports = {
mutate: jestConfig.collectCoverageFrom,
mutator: 'typescript',
checkers: [ 'typescript' ],
tsconfigFile: 'tsconfig.json',
testRunner: 'jest',
reporters: [ 'progress', 'clear-text' ],
coverageAnalysis: 'off',

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { Route } from 'react-router-dom';
import { identity } from 'ramda';

View File

@@ -1,5 +1,4 @@
import { shallow, ShallowWrapper } from 'enzyme';
import React from 'react';
import { Mock } from 'ts-mockery';
import asideMenuCreator from '../../src/common/AsideMenu';
import { ServerWithId } from '../../src/servers/data';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { Button } from 'reactstrap';
import { Mock } from 'ts-mockery';

View File

@@ -1,5 +1,4 @@
import { shallow, ShallowWrapper } from 'enzyme';
import React from 'react';
import { Mock } from 'ts-mockery';
import Home, { HomeProps } from '../../src/common/Home';
import { ServerWithId } from '../../src/servers/data';

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { Link } from 'react-router-dom';
import NotFound from '../../src/common/NotFound';

Some files were not shown because too many files have changed in this diff Show More