1
0
mirror of https://github.com/SomboChea/ui synced 2026-01-12 14:15:47 +07:00

Compare commits

...

194 Commits

Author SHA1 Message Date
Priscila Oliveira
6552f4c13f chore(release): 1.3.0 2020-04-01 13:33:44 +02:00
Priscila Oliveira
9f275b7b00 chore(release): 1.1.0 2020-04-01 13:31:05 +02:00
Priscila Oliveira
f321f7b6fe fix(chore): droped release (#449) 2020-04-01 13:27:39 +02:00
Priscila Oliveira
e7db3e4967 chore(release): 1.1.1 2020-04-01 13:02:08 +02:00
Priscila Oliveira
e0eb6b0a3c chore(release): 1.1.0 2020-04-01 13:00:51 +02:00
Priscila Oliveira
cdad5cf70d feat(style): added dark mode (#446) 2020-03-31 08:44:59 +02:00
Juan Picado @jotadeveloper
91434cc814 chore(release): 1.0.4 2020-03-17 06:52:50 +01:00
Priscila Oliveira
4071e272af Update README.md 2020-03-16 08:26:48 +01:00
Priscila Oliveira
f81c1984da Update README.md 2020-03-16 08:26:11 +01:00
Priscila Oliveira
b2255fca88 ref(readme): added i18n section (#443) 2020-03-16 08:24:36 +01:00
dependabot-preview[bot]
8c2800e156 build(deps): [security] bump acorn from 5.7.3 to 5.7.4 (#442)
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4. **This update includes a security fix.**
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-14 07:01:07 +01:00
Juan Picado @jotadeveloper
a5f06cb3af chore(release): 1.0.0 2020-03-13 07:35:00 +01:00
Priscila Oliveira
e27d59bff7 feat(de-translations): added de-DE translations to the UI (#441) 2020-03-12 11:40:12 +01:00
Juan Picado @jotadeveloper
0abe1ef41c feat: spanish translations to UI (#440)
* feat: spanish translations to UI

* chore: enable spanish
2020-03-09 07:01:01 +01:00
Priscila Oliveira
7428384b55 feat(i18n): added i18next for user interface translations (#432) 2020-03-08 16:45:07 +01:00
dependabot-preview[bot]
8d4b3cee7e build(deps-dev): bump @commitlint/cli from 8.3.4 to 8.3.5 (#436)
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint) from 8.3.4 to 8.3.5.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/compare/v8.3.4...v8.3.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: Priscila Oliveira <priscilawebdev@gmail.com>
2020-03-03 09:32:26 +01:00
dependabot-preview[bot]
d41ba981d2 build(deps-dev): bump react-dom from 16.12.0 to 16.13.0 (#437) 2020-03-02 21:29:16 +01:00
dependabot-preview[bot]
26dbf3d921 build(deps-dev): [security] bump codecov from 3.6.1 to 3.6.5 (#434)
Bumps [codecov](https://github.com/codecov/codecov-node) from 3.6.1 to 3.6.5. **This update includes a security fix.**
- [Release notes](https://github.com/codecov/codecov-node/releases)
- [Commits](https://github.com/codecov/codecov-node/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-20 07:36:02 +01:00
dependabot-preview[bot]
7cb20fa699 build(deps-dev): bump jest-environment-node from 24.9.0 to 25.1.0 (#430)
Bumps [jest-environment-node](https://github.com/facebook/jest/tree/HEAD/packages/jest-environment-node) from 24.9.0 to 25.1.0.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v25.1.0/packages/jest-environment-node)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: Priscila Oliveira <priscilawebdev@gmail.com>
Co-authored-by: Juan Picado @jotadeveloper <juanpicado19@gmail.com>
2020-02-02 09:21:49 +01:00
Juan Picado @jotadeveloper
e6aad5370f chore(release): 0.3.13 2020-02-02 08:37:41 +01:00
Daniel Ruf
d481f54948 fix: do not capitalize heading - closes #428 (#431) 2020-02-02 08:36:55 +01:00
dependabot-preview[bot]
e6e9cfb2b4 build(deps-dev): bump @typescript-eslint/parser from 2.15.0 to 2.18.0 (#429) 2020-02-01 22:17:53 +01:00
Juan Picado @jotadeveloper
6570e3fba1 chore: add snyk as an action (#230) 2020-01-19 09:46:48 +01:00
Alfonso Austin
d4f2720994 chore(build): add missing export (#417)
Co-authored-by: Juan Picado @jotadeveloper <juanpicado19@gmail.com>
2020-01-14 07:43:31 +01:00
Juan Picado @jotadeveloper
1eca1f4079 fix: reload packages on log in (#421)
related https://github.com/verdaccio/ui/pull/415
2020-01-14 07:16:39 +01:00
James George
164cea6c10 fix: typo (#423)
Co-authored-by: Juan Picado @jotadeveloper <juanpicado19@gmail.com>
2020-01-14 04:11:13 +01:00
James George
dad44c46c0 docs: add link to license file (#422) 2020-01-14 03:47:14 +01:00
coolsp
222ffed022 fix: package list refresh based on logged-in user (#415)
* fix: package list refresh based on logged-in user

description:
In `pages/home/Home.tsx` now monitoring any change in a user log-in/out which will trigger a new `API.request` to get the _packages_ from the Verdaccio-server.  This is done by creating a `useEffect` on **isUserLoggedIn**.  Code has been transplanted from `App/App.tsx` including the use of the Loading component during the XHR.  The use of **packages** was removed from other components as no longer needed and tests updated.

Resolves issue #414

* fix: package list refresh based on logged-in user

description:
In `pages/home/Home.tsx` now monitoring any change in a user log-in/out which will trigger a new `API.request` to get the _packages_ from the Verdaccio-server. This is done by creating a `useEffect` on **isUserLoggedIn**. Code has been transplanted from `App/App.tsx` including the use of the Loading component during the XHR. The use of **packages** was removed from other components as no longer needed and tests updated.
Test snapshots updated

Resolves issue #414

Co-authored-by: Juan Picado @jotadeveloper <juanpicado19@gmail.com>
2020-01-12 22:21:29 +01:00
Juan Picado @jotadeveloper
ee1c3f08eb fix: update dependencies (#420)
- just minor updates
- verdaccio internal deps (we know the reason of the major change)
2020-01-12 19:43:05 +01:00
Alfonso Austin
1531cb6226 chore/401 add new script to update tests (#416)
* chore: add new script to update tests #401

* chore: update script name
2020-01-11 22:22:48 +01:00
Juan Picado @jotadeveloper
e514ec95a6 chore(release): 0.3.12 2020-01-09 06:14:09 +01:00
Daniel Ruf
6b322ad553 fix: generate correct registry URL (#413)
* Revert "Revert "fix(#300): correctly reference registry url from options" (#311)"

This reverts commit d955268c25.

* fix: generate full URL from path
2020-01-09 06:12:50 +01:00
Juan Picado @jotadeveloper
3fd0154da3 chore(release): 0.3.11 2020-01-08 19:08:48 +01:00
Juan Picado @jotadeveloper
6bd38b8120 fix: remove prevent default and use react context (#411)
* fix: remove prevent default and use react context

* chore: remove string check
2020-01-08 12:16:13 +01:00
Daniel Ruf
6e2bface93 test: add SonarCloud (#408) 2020-01-03 21:51:40 +01:00
Priscila Oliveira
6eeae630ef fix: removed unused style file (#406) 2020-01-03 00:18:42 +01:00
dependabot-preview[bot]
eaea5f2501 build(deps-dev): bump style-loader from 1.0.2 to 1.1.2 (#405) 2020-01-02 21:41:19 +01:00
dependabot-preview[bot]
c1e4e739c8 build(deps-dev): bump mini-css-extract-plugin from 0.8.2 to 0.9.0 (#394)
Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 0.8.2 to 0.9.0.
- [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v0.8.2...v0.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-02 08:38:59 +01:00
dependabot-preview[bot]
4d31aff4a4 build(deps-dev): bump @types/react from 16.9.16 to 16.9.17 (#392)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 16.9.16 to 16.9.17.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-02 08:38:37 +01:00
dependabot-preview[bot]
d9a9fc4b96 build(deps-dev): bump webpack-dev-server from 3.9.0 to 3.10.1 (#391)
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 3.9.0 to 3.10.1.
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/v3.10.1/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v3.9.0...v3.10.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-02 08:37:46 +01:00
Juan Picado @jotadeveloper
e4ecc4a2f9 chore(release): 0.3.10 2019-12-30 10:13:47 +01:00
Daniel Ruf
bae9638b23 fix: add missing trailing slash to publicPath - closes #395 (#396)
* fix: add missing trailing slash to publicPath - closes #395

* test: update snapshot

(cherry picked from commit fe6494fec7225928cc510e112c80e4b171160a09)

Co-authored-by: Juan Picado @jotadeveloper <juanpicado19@gmail.com>
2019-12-30 10:12:23 +01:00
Daniel Ruf
544b999f81 fix: remove whitespace from logo image - closes #374 (#400)
* fix: remove whitespace from logo image - closes #374

* test: update snapshot

Co-authored-by: Juan Picado @jotadeveloper <juanpicado19@gmail.com>
2019-12-30 10:12:00 +01:00
Juan Picado @jotadeveloper
d554049699 fix: engine warning on console for ui (#403)
* fix: engine warning on console for ui

* chore: update snapshots

* chore: remove u flag

* chore: add readme note

* chore: update README.md

Co-Authored-By: Daniel Ruf <827205+DanielRuf@users.noreply.github.com>

Co-authored-by: Daniel Ruf <827205+DanielRuf@users.noreply.github.com>
2019-12-30 10:04:42 +01:00
Daniel Ruf
787dda4a01 fix: remove background from styled Avatar components - closes #371 (#398) 2019-12-28 09:33:00 +01:00
Daniel Ruf
797c2381e4 fix: remove double padding and add missing background color - closes #373 (#399)
* fix: remove double padding - closes #373

* fix: add missing background color
2019-12-28 09:32:07 +01:00
dependabot-preview[bot]
3888a268e4 build(deps-dev): bump @types/jest from 24.0.23 to 24.0.24 (#387) 2019-12-20 15:21:47 -03:00
dependabot-preview[bot]
884d76d4a9 build(deps-dev): bump react-hook-form from 3.28.15 to 3.29.4 (#388) 2019-12-20 15:21:26 -03:00
dependabot-preview[bot]
7b55ce5ea2 build(deps-dev): bump webpack from 4.41.3 to 4.41.4 (#389) 2019-12-20 15:21:00 -03:00
dependabot-preview[bot]
1ec62de0bf build(deps-dev): bump @typescript-eslint/parser from 2.11.0 to 2.12.0 (#386) 2019-12-19 17:23:07 -03:00
dependabot-preview[bot]
3fc5c38a8e build(deps-dev): bump style-loader from 1.0.1 to 1.0.2 (#385) 2019-12-19 17:22:52 -03:00
Priscila Oliveira
53e1e63b12 chore: updated @material-ui/core (#384) 2019-12-18 19:49:56 -03:00
dependabot-preview[bot]
ef2b50a329 build(deps-dev): bump css-loader from 3.3.2 to 3.4.0 (#383) 2019-12-18 17:54:27 -03:00
dependabot-preview[bot]
61dc9b0783 build(deps-dev): bump concurrently from 5.0.1 to 5.0.2 (#379) 2019-12-18 17:33:05 -03:00
dependabot-preview[bot]
0e3391ca3d build(deps-dev): bump eslint-plugin-prettier from 3.1.1 to 3.1.2 (#381) 2019-12-18 17:32:22 -03:00
dependabot-preview[bot]
277b44ab94 build(deps-dev): bump dayjs from 1.8.17 to 1.8.18 (#380) 2019-12-18 17:32:12 -03:00
dependabot-preview[bot]
71276e15ef build(deps-dev): bump @types/node from 12.12.20 to 12.12.21 (#382) 2019-12-18 17:31:27 -03:00
Priscila Oliveira
bf093cc27b Feat: added "Fund this package" button (#375) 2019-12-18 17:30:42 -03:00
Priscila Oliveira
3a9f66c023 Refactor: Updated developers component structure (#360) 2019-12-17 22:57:53 -03:00
dependabot-preview[bot]
eef2913dd5 build(deps-dev): bump mini-css-extract-plugin from 0.8.0 to 0.8.2 (#377) 2019-12-17 21:46:04 -03:00
dependabot-preview[bot]
2af2dfe91b build(deps-dev): bump @octokit/rest from 16.35.0 to 16.35.2 (#368) 2019-12-17 19:35:36 -03:00
dependabot-preview[bot]
d4e8dff40f build(deps-dev): bump @types/node from 12.12.17 to 12.12.20 (#376) 2019-12-17 19:35:07 -03:00
dependabot-preview[bot]
cc22574100 build(deps-dev): bump @types/request from 2.48.3 to 2.48.4 (#378) 2019-12-17 19:33:39 -03:00
dependabot-preview[bot]
580b47bea1 build(deps-dev): bump @testing-library/react from 9.3.3 to 9.4.0 (#370) 2019-12-17 07:54:36 -03:00
dependabot-preview[bot]
ea24e1b50e build(deps-dev): bump webpack from 4.41.2 to 4.41.3 (#366)
Bumps [webpack](https://github.com/webpack/webpack) from 4.41.2 to 4.41.3.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.41.2...v4.41.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-16 21:43:31 +01:00
dependabot-preview[bot]
a74dc87628 build(deps-dev): bump verdaccio from 4.3.5 to 4.4.0 (#367)
Bumps [verdaccio](https://github.com/verdaccio/verdaccio) from 4.3.5 to 4.4.0.
- [Release notes](https://github.com/verdaccio/verdaccio/releases)
- [Changelog](https://github.com/verdaccio/verdaccio/blob/master/CHANGELOG.md)
- [Commits](https://github.com/verdaccio/verdaccio/compare/v4.3.5...v4.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-16 21:24:47 +01:00
dependabot-preview[bot]
cecb54c490 build(deps-dev): bump node-mocks-http from 1.8.0 to 1.8.1 (#364)
Bumps [node-mocks-http](https://github.com/howardabrams/node-mocks-http) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/howardabrams/node-mocks-http/releases)
- [Changelog](https://github.com/howardabrams/node-mocks-http/blob/master/HISTORY.md)
- [Commits](https://github.com/howardabrams/node-mocks-http/compare/v1.8.0...v1.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-14 16:05:04 +01:00
dependabot-preview[bot]
b8c68314e9 build(deps-dev): bump css-loader from 3.3.0 to 3.3.2 (#365)
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 3.3.0 to 3.3.2.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/v3.3.2/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v3.3.0...v3.3.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-14 16:04:45 +01:00
Juan Picado @jotadeveloper
6bb37d6656 chore(release): 0.3.9 2019-12-14 15:06:07 +01:00
Juan Picado @jotadeveloper
ac1a4fa46d chore: test publish 2019-12-14 15:05:36 +01:00
Juan Picado @jotadeveloper
3b228a2a0b chore(release): 0.3.8 2019-12-14 14:34:45 +01:00
Priscila Oliveira
fd99be6818 Refactor: move styles utils to theme (#363) 2019-12-12 12:10:27 -03:00
dependabot-preview[bot]
172e470780 build(deps-dev): bump @types/node from 12.12.16 to 12.12.17 (#362) 2019-12-12 11:23:34 -03:00
dependabot-preview[bot]
a3b41747ca build(deps-dev): bump @material-ui/core from 4.7.1 to 4.7.2 (#350) 2019-12-12 11:22:38 -03:00
dependabot-preview[bot]
7e29182a15 build(deps-dev): bump concurrently from 5.0.0 to 5.0.1 (#353) 2019-12-11 10:34:57 -03:00
dependabot-preview[bot]
8e89c82750 build(deps-dev): bump css-loader from 3.2.1 to 3.3.0 (#354) 2019-12-11 10:34:23 -03:00
dependabot-preview[bot]
f1e468e7e4 build(deps-dev): bump eslint-plugin-import from 2.18.2 to 2.19.1 (#349) 2019-12-11 10:32:28 -03:00
dependabot-preview[bot]
28208d6633 build(deps-dev): bump @babel/plugin-proposal-optional-chaining (#348) 2019-12-11 10:32:17 -03:00
dependabot-preview[bot]
398c5804cf build(deps-dev): bump @types/react from 16.9.15 to 16.9.16 (#355) 2019-12-11 10:31:47 -03:00
dependabot-preview[bot]
4d9ac2bd04 build(deps-dev): bump @typescript-eslint/parser from 2.10.0 to 2.11.0 (#356) 2019-12-11 10:19:30 -03:00
dependabot-preview[bot]
52c941be09 build(deps-dev): bump react-hook-form from 3.28.12 to 3.28.15 (#352) 2019-12-11 10:06:20 -03:00
dependabot-preview[bot]
89b554b07c build(deps-dev): bump @types/enzyme from 3.10.3 to 3.10.4 (#351) 2019-12-11 09:00:52 -03:00
dependabot-preview[bot]
43a6bc0133 build(deps-dev): bump @testing-library/react from 9.3.2 to 9.3.3 (#359)
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 9.3.2 to 9.3.3.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v9.3.2...v9.3.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 09:02:36 +01:00
dependabot-preview[bot]
e40280ffeb build(deps-dev): bump @types/node from 12.12.14 to 12.12.16 (#357) 2019-12-10 15:42:35 +01:00
Priscila Oliveira
ae617a5c04 fix: removed deade import (#346) 2019-12-07 08:45:51 +01:00
Thomas Klein
33f873a8c7 fix: formatDate (#308) 2019-12-06 18:28:37 +01:00
Priscila Oliveira
42d3bb8508 feat: login Dialog Component - Replaced class by func. comp + added react-hook-form (#341)
* refactor: convert class to func

* refactor: changed login form logic

* refactor: conver to testing-library tests

* refactor: moved dependency

* refactor: replaced uglifyjs-webpack-plugin by terser-webpack-plugin

* fix: fixed e2e errors

* fix: fixed e2e test

* Delete settings.json

* fix: vscode settings rollback

* refactor: rollback webpack config

* refactor: updated eslint rule

* fix: removed --fix

* refactor: incresed the bundle size
2019-12-06 18:09:01 +01:00
Priscila Oliveira
501845b5f8 refactor: replaced date fns with dayjs (#345) 2019-12-06 17:58:24 +01:00
dependabot-preview[bot]
474e9e18de build(deps-dev): bump stylelint-webpack-plugin from 1.1.1 to 1.1.2 (#344) 2019-12-05 15:38:06 +01:00
dependabot-preview[bot]
ab810c8caa build(deps-dev): bump typescript from 3.7.2 to 3.7.3 (#342) 2019-12-05 15:37:41 +01:00
dependabot-preview[bot]
a3d7acfd73 build(deps-dev): bump @types/react from 16.9.14 to 16.9.15 (#343) 2019-12-05 15:37:30 +01:00
Priscila Oliveira
6ba721446b Search Component - Replaced class by func. comp (#339) 2019-12-04 17:09:02 +01:00
Priscila Oliveira
09b831a40d fix: updated actionbar snap (#340) 2019-12-03 14:24:10 +01:00
Priscila Oliveira
742971db0d ActionBar Component - Replaced class by func. comp (#330) 2019-12-03 09:44:44 +01:00
dependabot-preview[bot]
fcad6fa794 build(deps-dev): bump css-loader from 3.2.0 to 3.2.1 (#337) 2019-12-03 09:42:09 +01:00
dependabot-preview[bot]
a7b5e6df99 build(deps-dev): bump @typescript-eslint/parser from 2.9.0 to 2.10.0 (#338) 2019-12-03 09:41:39 +01:00
dependabot-preview[bot]
c988f0fac7 build(deps-dev): bump jest-emotion from 10.0.25 to 10.0.26 (#335) 2019-12-03 09:41:23 +01:00
dependabot-preview[bot]
c839970a25 build(deps-dev): bump @types/react from 16.9.13 to 16.9.14 (#336) 2019-12-03 09:40:54 +01:00
dependabot-preview[bot]
200cc289e6 build(deps-dev): bump @material-ui/core from 4.6.1 to 4.7.1 (#331) 2019-12-02 16:05:25 +01:00
dependabot-preview[bot]
11d66b7df1 build(deps-dev): bump eslint from 6.7.1 to 6.7.2 (#333) 2019-12-02 15:28:06 +01:00
dependabot-preview[bot]
7c616fa81a build(deps-dev): bump stylelint-webpack-plugin from 1.1.0 to 1.1.1 (#334) 2019-12-02 15:27:26 +01:00
dependabot-preview[bot]
e6dbf0a187 build(deps-dev): bump jest-emotion from 10.0.17 to 10.0.25 (#332) 2019-12-02 15:27:05 +01:00
dependabot-preview[bot]
764e73bbe2 build(deps-dev): bump codeceptjs from 2.3.5 to 2.3.6 (#318) 2019-12-01 16:19:34 +01:00
Priscila Oliveira
e60ab9e247 Repository Component - Replaced class by func. comp (#323) 2019-12-01 16:14:17 +01:00
dependabot-preview[bot]
d37de29d36 build(deps-dev): bump file-loader from 4.3.0 to 5.0.2 (#322) 2019-12-01 16:12:02 +01:00
dependabot-preview[bot]
764090dad3 build(deps-dev): bump url-loader from 2.3.0 to 3.0.0 (#324) 2019-12-01 16:08:08 +01:00
dependabot-preview[bot]
bedcea9a83 build(deps-dev): bump eslint-plugin-react from 7.16.0 to 7.17.0 (#329) 2019-12-01 15:43:07 +01:00
dependabot-preview[bot]
76142ecda6 build(deps-dev): bump lockfile-lint from 3.0.1 to 3.0.3 (#328) 2019-12-01 15:42:43 +01:00
dependabot-preview[bot]
ddb3b15cf6 build(deps-dev): bump style-loader from 1.0.0 to 1.0.1 (#327) 2019-12-01 13:19:48 +01:00
dependabot-preview[bot]
7a729d558f build(deps-dev): bump lockfile-lint from 2.2.0 to 3.0.1 (#316) 2019-11-27 15:09:59 +01:00
dependabot-preview[bot]
dc195a3446 build(deps-dev): bump lint-staged from 9.4.3 to 9.5.0 (#325) 2019-11-27 15:09:21 +01:00
dependabot-preview[bot]
a830403268 build(deps-dev): bump @types/react-router-dom from 5.1.2 to 5.1.3 (#326) 2019-11-27 15:08:54 +01:00
dependabot-preview[bot]
6a17a498e2 build(deps-dev): bump stylelint-processor-styled-components (#313) 2019-11-26 13:50:25 +01:00
dependabot-preview[bot]
a301eb0e8d build(deps-dev): bump eslint from 6.7.0 to 6.7.1 (#312) 2019-11-26 13:49:43 +01:00
dependabot-preview[bot]
de983f9a13 build(deps-dev): bump @typescript-eslint/parser from 2.8.0 to 2.9.0 (#321) 2019-11-26 13:46:55 +01:00
dependabot-preview[bot]
6da3204c0b build(deps-dev): bump @types/node from 12.12.11 to 12.12.14 (#319) 2019-11-26 13:45:42 +01:00
dependabot-preview[bot]
d4a17edc71 build(deps-dev): bump @types/validator from 12.0.0 to 12.0.1 (#320) 2019-11-26 13:42:27 +01:00
dependabot-preview[bot]
ba4299557e build(deps-dev): bump @types/react from 16.9.11 to 16.9.13 (#317) 2019-11-26 13:41:48 +01:00
Juan Picado @jotadeveloper
dc0cdbdb08 chore(release): 0.3.7 2019-11-24 21:05:59 +01:00
Priscila Oliveira
d955268c25 Revert "fix(#300): correctly reference registry url from options" (#311) 2019-11-24 20:17:28 +01:00
Michael Mok
ee74474811 fix(#300): correctly reference registry url from options 2019-11-24 19:43:19 +01:00
Priscila Oliveira
0d9232a92c Refactor(#209): Converted App component from class to func 2019-11-24 19:21:08 +01:00
Juan Picado @jotadeveloper
0a48906fc8 chore: enable optional chaining and nullish (#306) 2019-11-23 20:15:14 +01:00
dependabot-preview[bot]
58cf730b98 build(deps-dev): bump lint-staged from 8.2.1 to 9.4.3 (#289)
* build(deps-dev): bump lint-staged from 8.2.1 to 9.4.3

Bumps [lint-staged](https://github.com/okonet/lint-staged) from 8.2.1 to 9.4.3.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v8.2.1...v9.4.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* fix: updated lint-staged conf
2019-11-23 15:40:24 +01:00
Priscila Oliveira
111f0c50e5 feat: Added Theme and migrate to emotion@10.x 🚀 (#286)
* chore: updated emotion dependency

* feat: introduced theme

* refactor: updated emotion styles

* fix: fixed emotion error

* fix: fixed tests

* chore: add missing types

Co-Authored-By: Thomas Klein <tmkn@users.noreply.github.com>
2019-11-23 13:41:14 +01:00
dependabot-preview[bot]
a0dcf87368 build(deps-dev): bump url-loader from 2.2.0 to 2.3.0 (#303) 2019-11-22 13:11:01 +01:00
dependabot-preview[bot]
9ed5a833d9 build(deps-dev): bump date-fns from 2.8.0 to 2.8.1 (#304) 2019-11-22 13:10:35 +01:00
dependabot-preview[bot]
1ed229363a build(deps-dev): bump file-loader from 4.2.0 to 4.3.0 (#305) 2019-11-22 13:10:06 +01:00
dependabot-preview[bot]
34dff06bdb build(deps-dev): bump verdaccio from 4.3.4 to 4.3.5 (#302) 2019-11-22 13:08:38 +01:00
dependabot-preview[bot]
5d300cd9be build(deps-dev): bump eslint-plugin-codeceptjs from 1.1.0 to 1.2.0 (#299) 2019-11-21 12:01:47 +01:00
dependabot-preview[bot]
acfc902a99 build(deps-dev): bump validator from 12.0.0 to 12.1.0 (#298) 2019-11-21 12:01:22 +01:00
dependabot-preview[bot]
2f35eb7790 build(deps-dev): bump stylelint from 11.1.1 to 12.0.0 (#297) 2019-11-21 12:01:07 +01:00
dependabot-preview[bot]
cf6c5e159d build(deps-dev): bump @typescript-eslint/parser from 2.7.0 to 2.8.0 (#293) 2019-11-20 09:40:06 +01:00
dependabot-preview[bot]
fa9e1d3487 build(deps-dev): bump @types/validator from 10.11.3 to 12.0.0 (#292) 2019-11-20 09:39:47 +01:00
dependabot-preview[bot]
d70c78f201 build(deps-dev): bump date-fns from 2.7.0 to 2.8.0 (#296) 2019-11-20 09:38:57 +01:00
dependabot-preview[bot]
effde37c35 build(deps-dev): bump stylelint-webpack-plugin from 1.0.4 to 1.1.0 (#294) 2019-11-20 09:38:29 +01:00
dependabot-preview[bot]
d65483401d build(deps-dev): bump @types/node from 12.12.8 to 12.12.11 (#295) 2019-11-20 09:36:12 +01:00
dependabot-preview[bot]
5f80d00502 build(deps-dev): bump standard-version from 7.0.0 to 7.0.1 (#291)
Bumps [standard-version](https://github.com/conventional-changelog/standard-version) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/conventional-changelog/standard-version/releases)
- [Changelog](https://github.com/conventional-changelog/standard-version/blob/master/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/standard-version/compare/v7.0.0...v7.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-20 06:40:57 +01:00
dependabot-preview[bot]
430608d276 build(deps-dev): bump @types/lodash from 4.14.148 to 4.14.149 (#288)
Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.14.148 to 4.14.149.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-20 06:39:50 +01:00
dependabot-preview[bot]
9975edbb6f build(deps): [security] bump https-proxy-agent from 2.2.2 to 2.2.4 (#287) 2019-11-19 08:46:47 +01:00
Juan Picado @jotadeveloper
dbaa0c43b8 fix: restore lint-staged@8.2.1 2019-11-19 05:46:04 +01:00
Juan Picado @jotadeveloper
fd306def95 fix: update snapshots 2019-11-19 05:45:38 +01:00
Priscila Oliveira
ec3f69a542 fix (#285): fixed tests errors 2019-11-18 22:47:57 +01:00
dependabot-preview[bot]
fc4a7ee12c build(deps-dev): bump @types/lodash from 4.14.147 to 4.14.148 (#282) 2019-11-18 09:41:44 +01:00
dependabot-preview[bot]
5c6d9f68ca build(deps-dev): bump husky from 3.0.9 to 3.1.0 (#281) 2019-11-18 09:38:52 +01:00
dependabot-preview[bot]
3e8af72193 build(deps-dev): bump react-hot-loader from 4.12.17 to 4.12.18 (#283) 2019-11-18 09:20:49 +01:00
dependabot-preview[bot]
3f58be32b3 build(deps-dev): bump @types/node from 12.12.7 to 12.12.8 (#284) 2019-11-18 09:20:25 +01:00
dependabot-preview[bot]
042571e72f build(deps-dev): bump react-hot-loader from 4.12.16 to 4.12.17 (#270) 2019-11-15 23:19:04 +01:00
dependabot-preview[bot]
dd6ba2cbe9 build(deps-dev): bump ora from 4.0.2 to 4.0.3 (#273) 2019-11-15 23:06:26 +01:00
dependabot-preview[bot]
bf613231f4 build(deps-dev): bump @types/lodash from 4.14.146 to 4.14.147 (#274) 2019-11-15 23:06:00 +01:00
dependabot-preview[bot]
7074eddf27 build(deps-dev): bump lint-staged from 9.4.2 to 9.4.3 (#275) 2019-11-15 23:04:57 +01:00
dependabot-preview[bot]
d3ddd439d1 build(deps-dev): bump stylelint-webpack-plugin from 1.0.3 to 1.0.4 (#276) 2019-11-15 23:03:46 +01:00
dependabot-preview[bot]
26724bb20e build(deps-dev): bump react-dom from 16.11.0 to 16.12.0 (#277) 2019-11-15 22:57:31 +01:00
dependabot-preview[bot]
cba41ceead build(deps-dev): bump eslint-plugin-react-hooks from 2.2.0 to 2.3.0 (#278) 2019-11-15 22:50:16 +01:00
dependabot-preview[bot]
2688b59f5b build(deps-dev): bump react from 16.11.0 to 16.12.0 (#279) 2019-11-15 22:38:50 +01:00
dependabot-preview[bot]
4d285dbb00 build(deps-dev): bump @types/jest from 24.0.22 to 24.0.23 (#271) 2019-11-13 09:07:22 +01:00
dependabot-preview[bot]
739333b1f1 build(deps-dev): bump @material-ui/core from 4.6.0 to 4.6.1 (#272) 2019-11-13 09:05:08 +01:00
dependabot-preview[bot]
5809a9f7cb build(deps-dev): bump @octokit/rest from 16.34.1 to 16.35.0 (#261) 2019-11-12 21:19:34 +01:00
dependabot-preview[bot]
e1c5e30b4c build(deps-dev): bump @typescript-eslint/parser from 2.5.0 to 2.7.0 (#264) 2019-11-12 20:49:49 +01:00
dependabot-preview[bot]
5a3ea02449 build(deps-dev): bump @verdaccio/commons-api from 8.2.0 to 8.3.0 (#265) 2019-11-12 20:49:35 +01:00
dependabot-preview[bot]
535d8f9c85 build(deps-dev): bump typescript from 3.7.1-rc to 3.7.2 (#266) 2019-11-12 20:49:15 +01:00
dependabot-preview[bot]
8a46678698 build(deps-dev): bump resolve-url-loader from 3.1.0 to 3.1.1 (#267) 2019-11-12 20:48:52 +01:00
dependabot-preview[bot]
3265ed561d build(deps-dev): bump @types/node from 12.12.6 to 12.12.7 (#268) 2019-11-12 20:48:29 +01:00
dependabot-preview[bot]
3a6c6f7fb9 build(deps-dev): bump @testing-library/react from 9.3.0 to 9.3.2 (#255) 2019-11-12 08:27:54 +01:00
Priscila Oliveira
09fe1db850 Refactor(#240): Created Reset CSS and added local fonts 2019-11-12 08:18:05 +01:00
dependabot-preview[bot]
f265b6ba33 build(deps-dev): bump validator from 11.1.0 to 12.0.0 (#256) 2019-11-11 13:50:38 +01:00
dependabot-preview[bot]
185b2016d3 build(deps-dev): bump react-hot-loader from 4.12.15 to 4.12.16 (#257) 2019-11-11 13:50:22 +01:00
dependabot-preview[bot]
3751acef1c build(deps-dev): bump lint-staged from 8.2.1 to 9.4.2 (#258) 2019-11-11 13:50:04 +01:00
dependabot-preview[bot]
50fa39f7d6 build(deps-dev): bump date-fns from 2.6.0 to 2.7.0 (#260) 2019-11-11 13:49:41 +01:00
dependabot-preview[bot]
cd2e36513e build(deps-dev): bump prettier from 1.18.2 to 1.19.1 (#254) 2019-11-11 13:49:22 +01:00
dependabot-preview[bot]
b20fe3f44a build(deps-dev): bump @types/lodash from 4.14.144 to 4.14.146 (#253) 2019-11-11 13:49:05 +01:00
dependabot-preview[bot]
be30cbdd14 build(deps-dev): bump @types/jest from 24.0.20 to 24.0.22 (#262) 2019-11-11 13:45:56 +01:00
dependabot-preview[bot]
c6e3fd0b92 build(deps-dev): bump detect-secrets from 1.0.4 to 1.0.5 (#252) 2019-11-11 13:15:39 +01:00
dependabot-preview[bot]
f27254ca6b build(deps-dev): bump @types/react-router-dom from 5.1.0 to 5.1.2 (#247) 2019-11-10 22:08:27 +01:00
dependabot-preview[bot]
f4dd8b01b4 build(deps-dev): bump verdaccio-auth-memory from 8.2.0 to 8.3.0 (#250)
Bumps [verdaccio-auth-memory](https://github.com/verdaccio/monorepo/tree/HEAD/plugins/auth-memory) from 8.2.0 to 8.3.0.
- [Release notes](https://github.com/verdaccio/monorepo/releases)
- [Changelog](https://github.com/verdaccio/monorepo/blob/master/plugins/auth-memory/CHANGELOG.md)
- [Commits](https://github.com/verdaccio/monorepo/commits/v8.3.0/plugins/auth-memory)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-09 08:22:39 +01:00
dependabot-preview[bot]
a94485e614 build(deps-dev): bump @types/react-dom from 16.9.3 to 16.9.4 (#248)
Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 16.9.3 to 16.9.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-09 00:18:50 +01:00
dependabot-preview[bot]
ce4b13d3b5 build(deps-dev): bump verdaccio-memory from 8.2.0 to 8.3.0 (#249)
Bumps [verdaccio-memory](https://github.com/verdaccio/monorepo/tree/HEAD/plugins/memory) from 8.2.0 to 8.3.0.
- [Release notes](https://github.com/verdaccio/monorepo/releases)
- [Changelog](https://github.com/verdaccio/monorepo/blob/master/plugins/memory/CHANGELOG.md)
- [Commits](https://github.com/verdaccio/monorepo/commits/v8.3.0/plugins/memory)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-09 00:09:57 +01:00
dependabot-preview[bot]
fcd471ef6f build(deps-dev): bump lockfile-lint from 2.1.6 to 2.2.0 (#244)
Bumps [lockfile-lint](https://github.com/lirantal/lockfile-lint) from 2.1.6 to 2.2.0.
- [Release notes](https://github.com/lirantal/lockfile-lint/releases)
- [Commits](https://github.com/lirantal/lockfile-lint/compare/lockfile-lint@2.1.6...lockfile-lint@2.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-08 07:07:01 +01:00
dependabot-preview[bot]
9915fb6193 build(deps-dev): bump @verdaccio/types from 8.1.0 to 8.3.0 (#242)
Bumps [@verdaccio/types](https://github.com/verdaccio/monorepo/tree/HEAD/core/types) from 8.1.0 to 8.3.0.
- [Release notes](https://github.com/verdaccio/monorepo/releases)
- [Changelog](https://github.com/verdaccio/monorepo/blob/master/core/types/CHANGELOG.md)
- [Commits](https://github.com/verdaccio/monorepo/commits/v8.3.0/core/types)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-08 06:47:52 +01:00
dependabot-preview[bot]
886684817b build(deps-dev): bump @types/node from 12.11.7 to 12.12.6 (#243)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 12.11.7 to 12.12.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-08 06:44:58 +01:00
dependabot-preview[bot]
76d11d4674 build(deps-dev): bump @octokit/rest from 16.34.0 to 16.34.1 (#241)
Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 16.34.0 to 16.34.1.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v16.34.0...v16.34.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-08 06:44:22 +01:00
dependabot-preview[bot]
322197dc70 build(deps-dev): bump webpack-cli from 3.3.9 to 3.3.10 (#245)
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 3.3.9 to 3.3.10.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/v3.3.10/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/v3.3.9...v3.3.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-08 06:37:20 +01:00
Juan Picado @jotadeveloper
d2e9d68c6d chore(release): 0.3.6 2019-11-08 06:36:19 +01:00
Juan Picado @jotadeveloper
f1971edf6d build: fix e2e (#246)
* fix: e2e testing

* fix: e2e testing

* fix: e2e testing

* fix: e2e testing

* fix: e2e testing

* fix: e2e testing
2019-11-08 06:23:22 +01:00
Juan Picado @jotadeveloper
c9f6bf43ae chore(release): 0.3.5 2019-11-07 18:11:26 +01:00
Anix
bdef686914 feat: added download tarball button at list (#237)
* chore: added download button at packagelist

* feat: added download btn in package list
2019-11-07 18:11:08 +01:00
Priscila Oliveira
84257e1a84 chore: updated material-ui dependencies (#239)
* chore: update material-ui dependencies

* fix: updated snaps
2019-11-07 17:55:20 +01:00
Alfonso Austin
b74ca2285e fix: refactor/116 RegistryInfoContent is converted to functional component (#229)
* refactor:116[PackageList] component is converted to functional

* Refactor:#116 - Registry info content is converted to functional component

* refactor/116 - fix lint error

* refactor:116 - more lint errors

* refactor/116 - lint error

* refactor:116 - remove snapshot

* refactor: address code review comments #116

* refactor: fix lint error

* refactor: code review changes

* refactor add missed file

* refactor: lint error

* refactor: lint

* refactor: lint

* refactor: fix lint error
2019-11-02 17:53:21 +01:00
Ayush Sharma
803da1c532 fix: adds unit tests for api service (#235)
* refactor: adds unit tests for api service

* refactor: fix test dummy url
2019-11-01 07:18:55 +01:00
Andrew Hughson
5cb47ed49e fix: convert Engine component to hooks (#233)
* refactor: convert Engine component to hooks

* inline engine test data only used by one test

* remove  from engines tests

* remove confusing test abstraction

* change tests to not use mutations
2019-10-31 22:17:16 +01:00
Priscila Oliveira
b56e43846b fix: rest MUI components - Introduced ForwardRef (#224)
* refactor(162): added forwardRef Card

* refactor(162): introduced forwardRefDivider

* refactor(162): introduced forwardRef MuiComponents

* refactor(162): introducing forwardRef

* refactor(162): introduced forwardRef

* refactor(162): introduced forwardRef

* fix(162): fixed link

* fix: fixed port number

* fix: fixed duplicated id

* fix: fixed ref iconbutton

* fix: updated snaps

* fix: fixed port

* fix: fixed eslint errors

* fix: the item should be a button

* fix: fixed eslint errors
2019-10-31 08:12:18 +01:00
Ayush Sharma
a4cdd145d2 feat: update date fns to v2 (#232)
* chore: updates date-fns@2

* chore: updates date-fns@2  to new apis

* chore: updates date-fns@2, updates format
2019-10-30 07:02:07 +01:00
Juan Picado @jotadeveloper
0eb0566cde chore(#228): add canary package generator 2019-10-28 10:35:40 +01:00
Juan Picado @jotadeveloper
e6b53c0479 chore: migrate eslint@6.6.0 (#227)
* chore: migrate to eslint6

* chore: migrate to eslint6
2019-10-27 15:49:30 +01:00
280 changed files with 18168 additions and 6087 deletions

View File

@@ -1,3 +1,8 @@
{
"presets": [["@verdaccio"]]
"presets": [["@verdaccio"]],
"plugins": [
"emotion",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator"
]
}

View File

@@ -136,7 +136,7 @@ jobs:
command: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
- run:
name: Publish
command: yarn publish
command: npm publish
workflows:
version: 2

View File

@@ -45,11 +45,8 @@
}
}
],
"@typescript-eslint/explicit-function-return-type": ["warn",
{
"allowExpressions": true,
"allowTypedFunctionExpressions": true
}],
"@typescript-eslint/explicit-function-return-type": 0,
"react/display-name": 0,
"react/no-deprecated": 1,
"react/jsx-no-target-blank": 1,
"react/destructuring-assignment": ["error", "always"],
@@ -75,7 +72,7 @@
"arrow": "parens",
"condition": "parens",
"logical": "parens",
"prop": "parens"
"prop": "ignore"
}],
"react/jsx-boolean-value": ["error", "always"],
"react/jsx-closing-tag-location": ["error"],
@@ -86,7 +83,7 @@
"react/jsx-indent": ["error", 2],
"react/jsx-indent-props": ["error", 2],
"react/jsx-key": ["error"],
"react/jsx-max-depth": ["error", { "max": 2}],
"react/jsx-max-depth":["error", { "max": 5}],
"react/jsx-max-props-per-line": ["error", {"maximum": 3, "when": "multiline" }],
"react/jsx-no-bind": ["error"],
"react/jsx-no-comment-textnodes": ["error"],

50
.github/workflows/canary.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: CI Canary
on: [pull_request]
jobs:
build_test_lint:
name: Node Smoke Test Befor Canary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: '12.x'
registry-url: 'https://registry.verdaccio.org'
- name: Install
run: yarn install --frozen-lockfile
- name: Lint
run: yarn lint
- name: Build
run: yarn build
- name: Archive production artifacts
uses: actions/upload-artifact@v1
with:
name: static
path: static
canary:
name: Publish Canary Version of a PR
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Download math result for job 2
uses: actions/download-artifact@v1
with:
name: static
- uses: verdaccio/github-actions/canary@v0.4.0
with:
message: 'Thanks for your PR, the @verdaccio/ui package will be accessible from here for testing purposes:'
is-global: false
package-name: '@verdaccio/ui-theme'
registry: 'https://registry.verdaccio.org'
repo-token: ${{ secrets.GITHUB_TOKEN }}
bot-token: ${{ secrets.VERDACCIO_BOT_TOKEN }}
- uses: actions/setup-node@v1
with:
node-version: '12.x'
registry-url: 'https://registry.verdaccio.org'
- run: npm publish --tag canary
env:
NODE_AUTH_TOKEN: ${{ secrets.VERDACCIO_TOKEN }}
needs: build_test_lint

View File

@@ -9,20 +9,20 @@ jobs:
strategy:
fail-fast: false
matrix:
node_version: [10, 12]
node_version: [12, 13]
os: [ubuntu-latest, windows-latest, macOS-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node_version }}
- name: Install
run: yarn install --frozen-lockfile
- name: Build
run: yarn build
- name: Lint
run: yarn lint
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node_version }}
- name: Install
run: yarn install --frozen-lockfile
- name: Lint
run: yarn lint
- name: Build
run: yarn build

11
.github/workflows/security.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
name: Security Flow
on: push
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@0.1.0
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

View File

@@ -37,4 +37,4 @@
],
"results": {},
"version": "0.12.4"
}
}

0
.sonarcloud.properties Normal file
View File

View File

@@ -7,4 +7,4 @@
"typescriptreact"
],
"typescript.tsdk": "node_modules/typescript/lib"
}
}

View File

@@ -1 +1,2 @@
save-prefix ""
registry "https://registry.verdaccio.org"

View File

@@ -2,6 +2,143 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [1.3.0](https://github.com/verdaccio/ui/compare/v1.0.4...v1.3.0) (2020-04-01)
### Features
* **style:** added dark mode ([#446](https://github.com/verdaccio/ui/issues/446)) ([cdad5cf](https://github.com/verdaccio/ui/commit/cdad5cf70d69b7bb045fce461a32108def81721d))
### Bug Fixes
* **chore:** droped release ([#449](https://github.com/verdaccio/ui/issues/449)) ([f321f7b](https://github.com/verdaccio/ui/commit/f321f7b6fe1ac44897753f0bfdbbaa6ca7eca515))
## [1.2.0](https://github.com/verdaccio/ui/compare/v1.0.4...v1.2.0) (2020-04-01)
### Features
* **style:** added dark mode ([#446](https://github.com/verdaccio/ui/issues/446)) ([cdad5cf](https://github.com/verdaccio/ui/commit/cdad5cf70d69b7bb045fce461a32108def81721d))
### Bug Fixes
* **chore:** droped release ([#449](https://github.com/verdaccio/ui/issues/449)) ([f321f7b](https://github.com/verdaccio/ui/commit/f321f7b6fe1ac44897753f0bfdbbaa6ca7eca515))
## [1.1.0](https://github.com/verdaccio/ui/compare/v1.0.4...v1.1.0) (2020-04-01)
### Features
* **style:** added dark mode ([#446](https://github.com/verdaccio/ui/issues/446)) ([cdad5cf](https://github.com/verdaccio/ui/commit/cdad5cf70d69b7bb045fce461a32108def81721d))
### Bug Fixes
* **chore:** droped release ([#449](https://github.com/verdaccio/ui/issues/449)) ([f321f7b](https://github.com/verdaccio/ui/commit/f321f7b6fe1ac44897753f0bfdbbaa6ca7eca515))
### [1.0.4](https://github.com/verdaccio/ui/compare/v1.0.0...v1.0.4) (2020-03-17)
## [1.0.0](https://github.com/verdaccio/ui/compare/v0.3.13...v1.0.0) (2020-03-13)
### Features
* **de-translations:** added de-DE translations to the UI ([#441](https://github.com/verdaccio/ui/issues/441)) ([e27d59b](https://github.com/verdaccio/ui/commit/e27d59bff7039473e566090fa0f825f7e462aa4e))
* spanish translations to UI ([#440](https://github.com/verdaccio/ui/issues/440)) ([0abe1ef](https://github.com/verdaccio/ui/commit/0abe1ef41ca93b900ddda72e2d873ee52078221c))
* **i18n:** added i18next for user interface translations ([#432](https://github.com/verdaccio/ui/issues/432)) ([7428384](https://github.com/verdaccio/ui/commit/7428384b55e6089dbe45e6b216eee0b670dff576))
### [0.3.13](https://github.com/verdaccio/ui/compare/v0.3.12...v0.3.13) (2020-02-02)
### Bug Fixes
* do not capitalize heading - closes [#428](https://github.com/verdaccio/ui/issues/428) ([#431](https://github.com/verdaccio/ui/issues/431)) ([d481f54](https://github.com/verdaccio/ui/commit/d481f549484361c1d1bc011e0858e8f99b8a2528))
* package list refresh based on logged-in user ([#415](https://github.com/verdaccio/ui/issues/415)) ([222ffed](https://github.com/verdaccio/ui/commit/222ffed0226f5aaa62f2d5b91bb08717b2aa24ef)), closes [#414](https://github.com/verdaccio/ui/issues/414) [#414](https://github.com/verdaccio/ui/issues/414)
* reload packages on log in ([#421](https://github.com/verdaccio/ui/issues/421)) ([1eca1f4](https://github.com/verdaccio/ui/commit/1eca1f40797790e87d9592204ca061527d09c4ae))
* typo ([#423](https://github.com/verdaccio/ui/issues/423)) ([164cea6](https://github.com/verdaccio/ui/commit/164cea6c10804c1d2097c2a582eb3e1e51814d4a))
* update dependencies ([#420](https://github.com/verdaccio/ui/issues/420)) ([ee1c3f0](https://github.com/verdaccio/ui/commit/ee1c3f08eb16da2313d8841cfab18358d7f4ea10))
### [0.3.12](https://github.com/verdaccio/ui/compare/v0.3.11...v0.3.12) (2020-01-09)
### Bug Fixes
* generate correct registry URL ([#413](https://github.com/verdaccio/ui/issues/413)) ([6b322ad](https://github.com/verdaccio/ui/commit/6b322ad553e9fb3ee65b2968dcfe856ba42a0bfb)), closes [#300](https://github.com/verdaccio/ui/issues/300) [#311](https://github.com/verdaccio/ui/issues/311)
### [0.3.11](https://github.com/verdaccio/ui/compare/v0.3.10...v0.3.11) (2020-01-08)
### Bug Fixes
* remove prevent default and use react context ([#411](https://github.com/verdaccio/ui/issues/411)) ([6bd38b8](https://github.com/verdaccio/ui/commit/6bd38b812032857bb19af8978d48f6f8969af6cf))
* removed unused style file ([#406](https://github.com/verdaccio/ui/issues/406)) ([6eeae63](https://github.com/verdaccio/ui/commit/6eeae630ef441a871d06b888b6a21178e36e0db7))
### [0.3.10](https://github.com/verdaccio/ui/compare/v0.3.9...v0.3.10) (2019-12-30)
### Features
* added "Fund this package" button ([#375](https://github.com/verdaccio/ui/issues/375)) ([bf093cc](https://github.com/verdaccio/ui/commit/bf093cc27b8625cdc50dbfc9b8dd7e37f4e24da9))
### Bug Fixes
* add missing trailing slash to publicPath - closes [#395](https://github.com/verdaccio/ui/issues/395) ([#396](https://github.com/verdaccio/ui/issues/396)) ([bae9638](https://github.com/verdaccio/ui/commit/bae9638b23b70eff78b78b8ca52ff40162333354))
* engine warning on console for ui ([#403](https://github.com/verdaccio/ui/issues/403)) ([d554049](https://github.com/verdaccio/ui/commit/d554049699494e946f4caf345177839b4f0cba8b))
* remove background from styled Avatar components - closes [#371](https://github.com/verdaccio/ui/issues/371) ([#398](https://github.com/verdaccio/ui/issues/398)) ([787dda4](https://github.com/verdaccio/ui/commit/787dda4a016a1fcd1142bd4b705e2c71e232d13e))
* remove double padding and add missing background color - closes [#373](https://github.com/verdaccio/ui/issues/373) ([#399](https://github.com/verdaccio/ui/issues/399)) ([797c238](https://github.com/verdaccio/ui/commit/797c2381e453d4f40e1703402f192eb7675d6fbe))
* remove whitespace from logo image - closes [#374](https://github.com/verdaccio/ui/issues/374) ([#400](https://github.com/verdaccio/ui/issues/400)) ([544b999](https://github.com/verdaccio/ui/commit/544b999f81e39557e0fc002d21b24c512cfebc54))
### [0.3.9](https://github.com/verdaccio/ui/compare/v0.3.8...v0.3.9) (2019-12-14)
### [0.3.8](https://github.com/verdaccio/ui/compare/v0.3.7...v0.3.8) (2019-12-14)
### Features
* login Dialog Component - Replaced class by func. comp + added react-hook-form ([#341](https://github.com/verdaccio/ui/issues/341)) ([42d3bb8](https://github.com/verdaccio/ui/commit/42d3bb8508c666c28250432ada734d58ccb0eca8))
### Bug Fixes
* formatDate ([#308](https://github.com/verdaccio/ui/issues/308)) ([33f873a](https://github.com/verdaccio/ui/commit/33f873a8c78e419a36e3a29f7ea216714172b174))
* removed deade import ([#346](https://github.com/verdaccio/ui/issues/346)) ([ae617a5](https://github.com/verdaccio/ui/commit/ae617a5c04ad1b82309d36d3bdcf6b6b6fd925d0))
* updated actionbar snap ([#340](https://github.com/verdaccio/ui/issues/340)) ([09b831a](https://github.com/verdaccio/ui/commit/09b831a40d4e82a122f8fae3e45bdd161a3281bb))
### [0.3.7](https://github.com/verdaccio/ui/compare/v0.3.6...v0.3.7) (2019-11-24)
### Features
* Added Theme and migrate to emotion@10.x 🚀 ([#286](https://github.com/verdaccio/ui/issues/286)) ([111f0c5](https://github.com/verdaccio/ui/commit/111f0c50e5053202ca55fe4f3f28dd30e4932240))
### Bug Fixes
* **#300:** correctly reference registry url from options ([ee74474](https://github.com/verdaccio/ui/commit/ee74474811eb609072e1678bcb90db33756dcf38)), closes [#300](https://github.com/verdaccio/ui/issues/300)
* restore lint-staged@8.2.1 ([dbaa0c4](https://github.com/verdaccio/ui/commit/dbaa0c43b8104b350e4907387f89d4e9e719741f))
* update snapshots ([fd306de](https://github.com/verdaccio/ui/commit/fd306def9535d9168dc79ab020ec288a4d5df1a8))
### [0.3.6](https://github.com/verdaccio/ui/compare/v0.3.5...v0.3.6) (2019-11-08)
### [0.3.5](https://github.com/verdaccio/ui/compare/v0.3.4...v0.3.5) (2019-11-07)
### Bug Fixes
* adds unit tests for api service ([#235](https://github.com/verdaccio/ui/issues/235)) ([803da1c](https://github.com/verdaccio/ui/commit/803da1c))
* convert Engine component to hooks ([#233](https://github.com/verdaccio/ui/issues/233)) ([5cb47ed](https://github.com/verdaccio/ui/commit/5cb47ed))
* refactor/116 RegistryInfoContent is converted to functional component ([#229](https://github.com/verdaccio/ui/issues/229)) ([b74ca22](https://github.com/verdaccio/ui/commit/b74ca22)), closes [#116](https://github.com/verdaccio/ui/issues/116) [#116](https://github.com/verdaccio/ui/issues/116)
* rest MUI components - Introduced ForwardRef ([#224](https://github.com/verdaccio/ui/issues/224)) ([b56e438](https://github.com/verdaccio/ui/commit/b56e438))
### Features
* added download tarball button at list ([#237](https://github.com/verdaccio/ui/issues/237)) ([bdef686](https://github.com/verdaccio/ui/commit/bdef686))
* update date fns to v2 ([#232](https://github.com/verdaccio/ui/issues/232)) ([a4cdd14](https://github.com/verdaccio/ui/commit/a4cdd14))
### [0.3.4](https://github.com/verdaccio/ui/compare/v0.3.3...v0.3.4) (2019-10-26)
@@ -268,4 +405,4 @@ All notable changes to this project will be documented in this file. See [standa
<a name="0.0.3"></a>
## 0.0.3 (2019-04-04)
## 0.0.3 (2019-04-04)

View File

@@ -12,7 +12,7 @@
[![stackshare](https://img.shields.io/badge/Follow%20on-StackShare-blue.svg?logo=stackshare&style=flat)](https://stackshare.io/verdaccio)
[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/)
[![node](https://img.shields.io/node/v/@verdaccio/ui-theme/latest.svg)](https://www.npmjs.com/package/@verdaccio/ui-theme)
![MIT](https://img.shields.io/github/license/mashape/apistatus.svg)
[![MIT](https://img.shields.io/github/license/mashape/apistatus.svg)](./LICENSE)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/verdaccio/localized.svg)](https://crowdin.com/project/verdaccio)
[![codecov](https://codecov.io/gh/verdaccio/ui/branch/master/graph/badge.svg)](https://codecov.io/gh/verdaccio/ui)
@@ -22,7 +22,7 @@
## Contributing
We use `>=yarn@1.13.0`, keep on mind we use lock file.
We use `>=yarn@1.13.0`, keep in mind that we use lockfiles and use at least Node `v10.13.0` to be able to build the project.
For development run the following command, it will execute `webpack` and `verdaccio` to
@@ -93,7 +93,7 @@ If you have any issue you can try the following options, do no desist to ask or
* [Blog](https://medium.com/verdaccio)
* [Donations](https://opencollective.com/verdaccio)
* [Roadmaps](https://github.com/verdaccio/verdaccio/projects)
* [Roadmaps](https://github.com/verdaccio/ui/projects)
* [Reporting an issue](https://github.com/verdaccio/verdaccio/blob/master/CONTRIBUTING.md#reporting-a-bug)
* [Running discussions](https://github.com/verdaccio/verdaccio/issues?q=is%3Aissue+is%3Aopen+label%3Adiscuss)
* [Chat](http://chat.verdaccio.org/)
@@ -101,6 +101,10 @@ If you have any issue you can try the following options, do no desist to ask or
* [FAQ](https://github.com/verdaccio/verdaccio/issues?utf8=%E2%9C%93&q=is%3Aissue%20label%3Aquestion%20)
* [Docker Examples](https://github.com/verdaccio/docker-examples)
### Translations
Translations are handled locally. I18n files can be found in the folder ```i18n/translations/*``` of this repository. We would love to provide translations from other languages, embracing all our users, but unfortunately we cannot do this without your help. Would you like to help us? Please feel **super welcome** to add a locale by opening a pull request.
### License
Verdaccio is [MIT licensed](https://github.com/verdaccio/verdaccio/blob/master/LICENSE)

40
i18n/config.ts Normal file
View File

@@ -0,0 +1,40 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import translationEN from './translations/en-US.json';
import translationPT from './translations/pt-BR.json';
import translationES from './translations/es-ES.json';
import translationDE from './translations/de-DE.json';
i18n
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
// in case window.VEDACCIO_LANGUAGE is undefined,it will fall back to 'en-US'
lng: window?.__VERDACCIO_BASENAME_UI_OPTIONS?.language,
fallbackLng: 'en-US',
whitelist: ['en-US', 'pt-BR', 'es-ES', 'de-DE'],
load: 'currentOnly',
resources: {
'en-US': {
translation: translationEN,
},
'pt-BR': {
translation: translationPT,
},
'es-ES': {
translation: translationES,
},
'de-DE': {
translation: translationDE,
},
},
debug: false,
interpolation: {
escapeValue: false, // react already safes from xss
},
});
export default i18n;

View File

@@ -0,0 +1,139 @@
{
"copy-to-clipboard": "In die Zwischenablage kopieren",
"author-anonymous": "Anonymus",
"action-bar-action": {
"visit-home-page": "Zur Homepage",
"open-an-issue": "Einen Fehler melden",
"download-tarball": "Archiv (Tarball) herunterladen"
},
"dialog": {
"registry-info": {
"title": "Registrierungsinformationen"
}
},
"header": {
"documentation": "Dokumentation",
"registry-info": "Registrierungsinformationen",
"greetings": "Hallo "
},
"search": {
"packages": "Pakete suchen"
},
"auto-complete": {
"loading": "wird geladen...",
"no-results-found": "Kein Ergebnis gefunden"
},
"tab": {
"uplinks": "Uplinks",
"versions": "Versionen",
"dependencies": "Abhängigkeiten",
"readme": "Liesmich"
},
"uplinks": {
"title": "Uplinks",
"no-items": "{{name}} hat keine Uplinks."
},
"versions": {
"current-tags": "Aktuelle Tags",
"version-history": "Versionsgeschichte",
"not-available": "Nicht verfĂĽgbar"
},
"package": {
"published-on": "Veröffentlicht am {{time}} •",
"version": "v{{version}}",
"visit-home-page": "Zur Homepage",
"homepage": "Homepage",
"open-an-issue": "Einen Fehler melden",
"bugs": "Fehler",
"download": "{{what}} herunterladen",
"the-tar-file": "die tar-Datei",
"tarball": "Archiv (Tarball)"
},
"dependencies": {
"has-no-dependencies": "{{package}} hat keine Abhängigkeiten",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "Benutzername",
"password": "Passwort"
},
"form-placeholder": {
"username": "Dein Benutzername",
"password": "Dein sicheres Passwort"
},
"form-validation": {
"required-field": "Dieses Feld ist erforderlich",
"required-min-length": "Dieses Feld erfordert eine Mindestlänge von {{length}}",
"unable-to-sign-in": "Anmeldung nicht möglich",
"username-or-password-cant-be-empty": "Benutzername und Passwort dĂĽrfen nicht leer sein!"
},
"help": {
"title": "Noch kein Paket publiziert.",
"sub-title": "Um dein erstes Paket einfach zu publizieren:",
"first-step": "1. Einloggen",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. Publizieren",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. Diese Seite aktualisieren."
},
"sidebar": {
"detail": {
"latest-version": "Letzte v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "Installierung",
"install-using-yarn": "Mit yarn installieren",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "Mit npm installieren",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "Mit pnpm installieren",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "Repository"
},
"author": {
"title": "Autor(in)"
},
"distribution": {
"title": "Neueste Distribution",
"license": "Lizenz",
"size": "Größe",
"file-count": "Anzahl der Dateien"
},
"maintainers": {
"title": "Maintainer"
},
"contributors": {
"title": "Contributor"
},
"engines": {
"npm-version": "NPM Version",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "UnterstĂĽtzt von",
"made-with-love-on": "Gemacht mit <0>♥</0> in"
},
"button": {
"close": "SchlieĂźen",
"cancel": "Abbrechen",
"login": "Einloggen",
"logout": "Ausloggen",
"go-to-the-home-page": "Zur Homepage",
"learn-more": "Mehr erfahren",
"fund-this-package": "Dieses Paket <0>finanzieren</0>"
},
"error": {
"unspecific": "Etwas ist schief gelaufen.",
"404": {
"page-not-found": "404 - Seite nicht gefunden",
"sorry-we-could-not-find-it": "Entschuldigung, wir konnten es nicht finden..."
},
"app-context-not-correct-used": "Der App-Kontext wurde nicht korrekt verwendet",
"theme-context-not-correct-used": "Der Theme-Kontext wurde nicht korrekt verwendet",
"package-meta-is-required-at-detail-context": "packageMeta wird bei DetailContext benötigt"
}
}

View File

@@ -0,0 +1,139 @@
{
"copy-to-clipboard": "Copy to clipboard",
"author-anonymous": "Anonymous",
"action-bar-action": {
"visit-home-page": "Visit homepage",
"open-an-issue": "Open an issue",
"download-tarball": "Download tarball"
},
"dialog": {
"registry-info": {
"title": "Register Info"
}
},
"header": {
"documentation": "Documentation",
"registry-info": "Registry Information",
"greetings": "Hi "
},
"search": {
"packages": "Search Packages"
},
"auto-complete": {
"loading": "Loading...",
"no-results-found": "No results found"
},
"tab": {
"uplinks": "Uplinks",
"versions": "Versions",
"dependencies": "Dependencies",
"readme": "Readme"
},
"uplinks": {
"title": "Uplinks",
"no-items": "{{name}} has no uplinks."
},
"versions": {
"current-tags": "Current Tags",
"version-history": "Version history",
"not-available": "Not available"
},
"package": {
"published-on": "Published on {{time}} •",
"version": "v{{version}}",
"visit-home-page": "Visit homepage",
"homepage": "Homepage",
"open-an-issue": "Open an issue",
"bugs": "Bugs",
"download": "Download {{what}}",
"the-tar-file": "the tar file",
"tarball": "Tarball"
},
"dependencies": {
"has-no-dependencies": "{{package}} has no dependencies.",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "Username",
"password": "Password"
},
"form-placeholder": {
"username": "Your username",
"password": "Your strong password"
},
"form-validation": {
"required-field": "This field is required",
"required-min-length": "This field required the min length of {{length}}",
"unable-to-sign-in": "Unable to sign in",
"username-or-password-cant-be-empty": "Username or password can't be empty!"
},
"help": {
"title": "No Package Published Yet.",
"sub-title": "To publish your first package just:",
"first-step": "1. Login",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. Publish",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. Refresh this page."
},
"sidebar": {
"detail": {
"latest-version": "Latest v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "Installation",
"install-using-yarn": "Install using yarn",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "Install using npm",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "Install using pnpm",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "Repository"
},
"author": {
"title": "Author"
},
"distribution": {
"title": "Latest Distribution",
"license": "License",
"size": "Size",
"file-count": "file count"
},
"maintainers": {
"title": "Maintainers"
},
"contributors": {
"title": "Contributors"
},
"engines": {
"npm-version": "NPM Version",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "Powered by",
"made-with-love-on": "Made with <0>♥</0> on"
},
"button": {
"close": "Close",
"cancel": "Cancel",
"login": "Login",
"logout": "Logout",
"go-to-the-home-page": "Go to the home page",
"learn-more": "Learn More",
"fund-this-package": "<0>Fund</0> this package"
},
"error": {
"unspecific": "Something went wrong.",
"404": {
"page-not-found": "404 - Page not found",
"sorry-we-could-not-find-it": "Sorry, we couldn't find it..."
},
"app-context-not-correct-used": "The app context was not correct used",
"theme-context-not-correct-used": "The theme context was not correct used",
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
}
}

View File

@@ -0,0 +1,139 @@
{
"copy-to-clipboard": "Copiar al portapapeles",
"author-anonymous": "AnĂłnimo",
"action-bar-action": {
"visit-home-page": "Visitar página principal",
"open-an-issue": "Reportar un error",
"download-tarball": "Descargar libreria"
},
"dialog": {
"registry-info": {
"title": "InformaciĂłn del Registro"
}
},
"header": {
"documentation": "DocumentaciĂłn",
"registry-info": "InformaciĂłn del Registro",
"greetings": "Hola "
},
"search": {
"packages": "Buscar paquetes"
},
"auto-complete": {
"loading": "Cargando...",
"no-results-found": "Sin resultados encontrados"
},
"tab": {
"uplinks": "Remoto",
"versions": "Versiones",
"dependencies": "Dependencias",
"readme": "Léeme"
},
"uplinks": {
"title": "Remoto",
"no-items": "{{name}} not tiene remotos."
},
"versions": {
"current-tags": "Etiquetas actuales",
"version-history": "Historial de versiones",
"not-available": "No disponible"
},
"package": {
"published-on": "Publicado en {{time}} •",
"version": "v{{version}}",
"visit-home-page": "Ir a la página principal",
"homepage": "Página pricinpal",
"open-an-issue": "Reportar un problema",
"bugs": "Errores",
"download": "Descargar {{what}}",
"the-tar-file": "el archivo tar",
"tarball": "Libreria"
},
"dependencies": {
"has-no-dependencies": "{{package}} no tiene dependencias.",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "Usuario",
"password": "Contraseña"
},
"form-placeholder": {
"username": "Tu usuario",
"password": "Tu fuerte conntraseña"
},
"form-validation": {
"required-field": "Este campo es requerido",
"required-min-length": "Este campo es requerido y la mĂ­nima longitud es {{length}}",
"unable-to-sign-in": "No se ha podido iniciar sesiĂłn",
"username-or-password-cant-be-empty": "Nombre de usuario o contraseña no puede estar vacio!"
},
"help": {
"title": "NingĂşn paquete ha sido publicado aun.",
"sub-title": "Para publicar tu primer paquete:",
"first-step": "1. Inicia sesiĂłn",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. Publica",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. Refresca la página."
},
"sidebar": {
"detail": {
"latest-version": "Ăšltima v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "InstalaciĂłn",
"install-using-yarn": "Instala usando yarn",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "Instala usando npm",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "Instala usando pnpm",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "Repositorio"
},
"author": {
"title": "Autor"
},
"distribution": {
"title": "Ăšltima distribuciĂłn",
"license": "Licencia",
"size": "Tamaño",
"file-count": "archivo cuenta"
},
"maintainers": {
"title": "Mantenedores"
},
"contributors": {
"title": "Colaboradores"
},
"engines": {
"npm-version": "Version NPM",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "Hecho con",
"made-with-love-on": "Hecho con <0>♥</0> on"
},
"button": {
"close": "Cerrar",
"cancel": "Cancelar",
"login": "Iniciar sesiĂłn",
"logout": "Cerrar sesiĂłn",
"go-to-the-home-page": "Ir a la página principal",
"learn-more": "Aprender más",
"fund-this-package": "<0>Donar</0> a este paquete"
},
"error": {
"unspecific": "Algo ha salido mal.",
"404": {
"page-not-found": "404 - Paquete no encontrado",
"sorry-we-could-not-find-it": "Lo siento, no hemos podido encontrarlo..."
},
"app-context-not-correct-used": "El contexto de la aplicaciĂłn no fue correctamente usado",
"theme-context-not-correct-used": "El contexto del tema no fue correctamente usado",
"package-meta-is-required-at-detail-context": "packageMeta es requerido en DetailContext"
}
}

View File

@@ -0,0 +1,139 @@
{
"copy-to-clipboard": "Copiar para área de transferência",
"author-anonymous": "AnĂ´nimo(a)",
"action-bar-action": {
"visit-home-page": "Visitar a página inicial",
"open-an-issue": "Criar um incidente",
"download-tarball": "Baixar Tarball"
},
"dialog": {
"registry-info": {
"title": "Informações do Registro"
}
},
"header": {
"documentation": "Documentação",
"registry-info": "Informações do Registro",
"greetings": "Oi "
},
"search": {
"packages": "Pesquisar Pacotes"
},
"auto-complete": {
"loading": "Carregando...",
"no-results-found": "Nenhum resultado encontrado"
},
"tab": {
"uplinks": "Uplinks",
"versions": "Versões",
"dependencies": "DependĂŞncias",
"readme": "Leia-me"
},
"uplinks": {
"title": "Uplinks",
"no-items": "{{name}} nĂŁo tem uplinks."
},
"versions": {
"current-tags": "Tags atuais",
"version-history": "Histórico de versões",
"not-available": "NĂŁo disponĂ­vel"
},
"package": {
"published-on": "Publicado em {{time}} •",
"version": "v{{version}}",
"visit-home-page": "Visitar a página inicial",
"homepage": "Página inicial",
"open-an-issue": "Criar um incidente",
"bugs": "Erros",
"download": "Baixar {{what}}",
"the-tar-file": "o arquivo tar",
"tarball": "Tarball"
},
"dependencies": {
"has-no-dependencies": "{{package}} nĂŁo tem dependĂŞncias.",
"dependency-block": "{{package}}@{{version}}"
},
"form": {
"username": "Nome do usuário",
"password": "Senha"
},
"form-placeholder": {
"username": "O seu nome",
"password": "A sua senha forte"
},
"form-validation": {
"required-field": "Este campo Ă© obrigatĂłrio",
"required-min-length": "Este campo requer o mĂ­nimo de {{length}} caracteres",
"unable-to-sign-in": "NĂŁo foi possĂ­vel fazer login",
"username-or-password-cant-be-empty": "Nome de usuário ou senha não podem estar vazios!"
},
"help": {
"title": "Nenhum pacote publicado ainda.",
"sub-title": "Para publicar seu primeiro pacote apenas:",
"first-step": "1. Faça login",
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
"second-step": "2. Publique",
"second-step-command-line": "npm publish --registry {{registryUrl}}",
"third-step": "3. Atualize esta página."
},
"sidebar": {
"detail": {
"latest-version": "Ăšltima versĂŁo: v{{version}}",
"version": "v{{version}}"
},
"installation": {
"title": "Instalação",
"install-using-yarn": "Instale usando yarn",
"install-using-yarn-command": "yarn add {{packageName}}",
"install-using-npm": "Instale usando npm",
"install-using-npm-command": "npm install {{packageName}}",
"install-using-pnpm": "Instale usando pnpm",
"install-using-pnpm-command": "pnpm install {{packageName}}"
},
"repository": {
"title": "RepositĂłrio"
},
"author": {
"title": "Autor(a)"
},
"distribution": {
"title": "Distribuição mais recente",
"license": "Licença",
"size": "Tamanho",
"file-count": "Contagem de arquivos"
},
"maintainers": {
"title": "Mantenedores(as)"
},
"contributors": {
"title": "Contribuidores(as)"
},
"engines": {
"npm-version": "VersĂŁo NPM",
"node-js": "NODE JS"
}
},
"footer": {
"powered-by": "DistribuĂ­do por",
"made-with-love-on": "Feito com amor <0>♥</0> no(a)"
},
"button": {
"close": "Fechar",
"cancel": "Cancelar",
"login": "Conectar",
"logout": "Desconectar",
"go-to-the-home-page": "Ir para a página inicial",
"learn-more": "Leia mais",
"fund-this-package": "<0>Financie</0> este pacote"
},
"error": {
"unspecific": "Algo deu errado.",
"404": {
"page-not-found": "404 - Página não encontrada",
"sorry-we-could-not-find-it": "Desculpe, nĂŁo conseguimos encontrar..."
},
"app-context-not-correct-used": "O contexto do aplicativo nĂŁo foi usado corretamente",
"theme-context-not-correct-used": "O contexto do tema nĂŁo foi usado corretamente",
"package-meta-is-required-at-detail-context": "packageMeta Ă© requerido em DetailContext"
}
}

View File

@@ -1 +1 @@
require.requireActual('babel/polyfill');
jest.requireActual('babel/polyfill');

View File

@@ -6,14 +6,16 @@ import 'raf/polyfill';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { GlobalWithFetchMock } from 'jest-fetch-mock';
import 'mutationobserver-shim';
// @ts-ignore : Only a void function can be called with the 'new' keyword
configure({ adapter: new Adapter() });
// @ts-ignore : Property '__APP_VERSION__' does not exist on type 'Global'.
global.__APP_VERSION__ = '1.0.0';
// @ts-ignore : Property '__VERDACCIO_BASENAME_UI_OPTIONS' does not exist on type 'Global'.
global.__VERDACCIO_BASENAME_UI_OPTIONS = {};
global.__VERDACCIO_BASENAME_UI_OPTIONS = { base: 'http://localhost' };
// @ts-ignore : Property 'VERDACCIO_API_URL' does not exist on type 'Global'.
global.VERDACCIO_API_URL = 'https://verdaccio.tld';
const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock;
customGlobal.fetch = require('jest-fetch-mock');

View File

@@ -1,5 +0,0 @@
{
"rules": {
"@typescript-eslint/explicit-function-return-type": 0
}
}

View File

@@ -3,12 +3,19 @@
*/
import { Base64 } from 'js-base64';
import addHours from 'date-fns/add_hours';
import dayjs from 'dayjs';
export function generateTokenWithTimeRange(limit = 0) {
export function generateTokenWithTimeRange(amount = 0) {
const payload = {
username: 'verdaccio',
exp: Number.parseInt(String(addHours(new Date(), limit).getTime() / 1000), 10),
exp: Number.parseInt(
String(
dayjs(new Date())
.add(amount, 'hour')
.valueOf() / 1000
),
10
),
};
return `xxxxxx.${Base64.encode(JSON.stringify(payload))}.xxxxxx`;
}

View File

@@ -192,7 +192,7 @@ export const packageMeta = {
jest: { snapshotSerializers: ['jest-serializer-enzyme'] },
engines: { node: '>=4.6.1', npm: '>=2.15.9' },
preferGlobal: true,
publishConfig: { registry: 'http://localhost:4873/' },
publishConfig: { registry: 'https://registry.verdaccio.org' },
license: 'WTFPL',
contributors: [
{
@@ -578,7 +578,7 @@ export const packageMeta = {
_npmUser: {},
dist: {
shasum: '958c919180e7f2ed6775f48d4ec64bd8de2a14df',
tarball: 'http://localhost:4873/verdaccio/-/verdaccio-2.7.1.tgz',
tarball: 'https://registry.verdaccio.org/verdaccio/-/verdaccio-2.7.1.tgz',
},
},
};

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/ui-theme",
"version": "0.3.4",
"version": "1.3.0",
"description": "Verdaccio User Interface",
"author": {
"name": "Verdaccio Core Team",
@@ -13,112 +13,121 @@
"homepage": "https://verdaccio.org",
"main": "index.js",
"devDependencies": {
"@commitlint/cli": "8.2.0",
"@commitlint/config-conventional": "8.2.0",
"@material-ui/core": "4.5.1",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.8.0",
"@babel/plugin-proposal-optional-chaining": "7.8.0",
"@commitlint/cli": "8.3.5",
"@commitlint/config-conventional": "8.3.4",
"@emotion/core": "10.0.22",
"@emotion/styled": "10.0.23",
"@material-ui/core": "4.8.0",
"@material-ui/icons": "4.5.1",
"@octokit/rest": "16.34.0",
"@testing-library/react": "9.3.0",
"@octokit/rest": "16.35.2",
"@testing-library/jest-dom": "4.2.4",
"@testing-library/react": "9.4.0",
"@types/autosuggest-highlight": "3.1.0",
"@types/enzyme": "3.10.3",
"@types/jest": "24.0.20",
"@types/enzyme": "3.10.4",
"@types/jest": "24.0.24",
"@types/js-base64": "2.3.1",
"@types/lodash": "4.14.144",
"@types/node": "12.11.7",
"@types/react": "16.9.11",
"@types/lodash": "4.14.149",
"@types/node": "13.1.6",
"@types/react": "16.9.17",
"@types/react-autosuggest": "9.3.13",
"@types/react-dom": "16.9.3",
"@types/react-router-dom": "5.1.0",
"@types/request": "2.48.3",
"@types/validator": "10.11.3",
"@types/webpack-env": "1.14.1",
"@typescript-eslint/parser": "2.4.0",
"@verdaccio/babel-preset": "8.2.0",
"@verdaccio/commons-api": "8.2.0",
"@verdaccio/eslint-config": "2.0.0",
"@verdaccio/types": "8.1.0",
"@types/react-dom": "16.9.4",
"@types/react-router-dom": "5.1.3",
"@types/request": "2.48.4",
"@types/validator": "12.0.1",
"@types/webpack-env": "1.15.0",
"@typescript-eslint/parser": "2.18.0",
"@verdaccio/babel-preset": "9.0.0",
"@verdaccio/commons-api": "9.0.0",
"@verdaccio/eslint-config": "8.4.2",
"@verdaccio/types": "9.0.0",
"autosuggest-highlight": "3.1.1",
"babel-loader": "8.0.6",
"bundlesize": "0.18.0",
"codeceptjs": "2.3.5",
"codecov": "3.6.1",
"concurrently": "5.0.0",
"codeceptjs": "2.4.0",
"codecov": "3.6.5",
"concurrently": "5.0.2",
"cross-env": "6.0.3",
"css-loader": "3.2.0",
"date-fns": "1.30.1",
"detect-secrets": "1.0.4",
"emotion": "9.2.12",
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.15.1",
"css-loader": "3.4.2",
"dayjs": "1.8.19",
"detect-secrets": "1.0.5",
"emotion": "10.0.27",
"emotion-theming": "10.0.27",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.2",
"enzyme-to-json": "3.4.3",
"eslint": "6.5.1",
"eslint-plugin-codeceptjs": "1.1.0",
"eslint-plugin-import": "2.18.2",
"eslint": "6.7.2",
"eslint-plugin-codeceptjs": "1.2.0",
"eslint-plugin-import": "2.19.1",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-prettier": "3.1.0",
"eslint-plugin-react": "7.14.3",
"eslint-plugin-react-hooks": "2.0.1",
"eslint-plugin-verdaccio": "2.0.0",
"file-loader": "4.2.0",
"eslint-plugin-prettier": "3.1.2",
"eslint-plugin-react": "7.17.0",
"eslint-plugin-react-hooks": "2.3.0",
"eslint-plugin-verdaccio": "8.4.2",
"file-loader": "5.0.2",
"friendly-errors-webpack-plugin": "1.7.0",
"get-stdin": "7.0.0",
"github-markdown-css": "3.0.1",
"html-webpack-plugin": "3.2.0",
"husky": "3.0.9",
"husky": "3.1.0",
"i18next": "19.1.0",
"identity-obj-proxy": "3.0.0",
"in-publish": "2.0.0",
"jest": "24.9.0",
"jest-emotion": "10.0.17",
"jest-emotion": "10.0.27",
"jest-environment-jsdom": "24.9.0",
"jest-environment-jsdom-global": "1.2.0",
"jest-environment-node": "24.9.0",
"jest-fetch-mock": "2.1.2",
"jest-environment-node": "25.1.0",
"jest-fetch-mock": "3.0.1",
"js-base64": "2.5.1",
"js-yaml": "3.13.1",
"lint-staged": "8.2.1",
"lint-staged": "9.5.0",
"localstorage-memory": "1.0.3",
"lockfile-lint": "2.1.6",
"lockfile-lint": "3.0.5",
"lodash": "^4.17.15",
"mini-css-extract-plugin": "0.8.0",
"node-mocks-http": "1.8.0",
"mini-css-extract-plugin": "0.9.0",
"mutationobserver-shim": "0.3.3",
"node-mocks-http": "1.8.1",
"normalize.css": "8.0.1",
"optimize-css-assets-webpack-plugin": "5.0.3",
"ora": "4.0.2",
"prettier": "1.18.2",
"ora": "4.0.3",
"prettier": "1.19.1",
"prop-types": "15.7.2",
"puppeteer": "1.8.0",
"react": "16.11.0",
"puppeteer": "2.0.0",
"react": "16.12.0",
"react-autosuggest": "9.4.3",
"react-dom": "16.11.0",
"react-emotion": "9.2.12",
"react-hot-loader": "4.12.15",
"react-dom": "16.13.0",
"react-hook-form": "3.29.4",
"react-hot-loader": "4.12.18",
"react-i18next": "11.3.1",
"react-router-dom": "5.1.2",
"request": "2.88.0",
"resolve-url-loader": "3.1.0",
"resolve-url-loader": "3.1.1",
"rimraf": "3.0.0",
"source-map-loader": "0.2.4",
"standard-version": "7.0.0",
"style-loader": "1.0.0",
"stylelint": "11.1.1",
"standard-version": "7.0.1",
"style-loader": "1.1.2",
"stylelint": "12.0.0",
"stylelint-config-recommended": "3.0.0",
"stylelint-config-styled-components": "0.1.1",
"stylelint-processor-styled-components": "1.8.0",
"stylelint-webpack-plugin": "1.0.3",
"stylelint-processor-styled-components": "1.9.0",
"stylelint-webpack-plugin": "1.1.2",
"supertest": "4.0.2",
"typeface-roboto": "0.0.75",
"typescript": "3.7.1-rc",
"typescript": "3.7.4",
"uglifyjs-webpack-plugin": "2.2.0",
"url-loader": "2.2.0",
"validator": "11.1.0",
"verdaccio": "4.3.4",
"verdaccio-auth-memory": "8.2.0",
"verdaccio-memory": "8.2.0",
"url-loader": "3.0.0",
"validator": "12.1.0",
"verdaccio": "4.4.2",
"verdaccio-auth-memory": "9.0.0",
"verdaccio-memory": "9.0.0",
"wait-on": "3.3.0",
"webpack": "4.41.2",
"webpack": "4.41.5",
"webpack-bundle-analyzer": "3.6.0",
"webpack-bundle-size-analyzer": "3.1.0",
"webpack-cli": "3.3.9",
"webpack-dev-server": "3.9.0",
"webpack-cli": "3.3.10",
"webpack-dev-server": "3.10.1",
"webpack-merge": "4.2.2",
"whatwg-fetch": "3.0.0",
"xss": "1.0.6"
@@ -131,7 +140,7 @@
"bundlesize": [
{
"path": "./static/vendors.*.js",
"maxSize": "180 kB"
"maxSize": "200 kB"
},
{
"path": "./static/main.*.js",
@@ -164,6 +173,7 @@
"test:acceptance:server": "concurrently --kill-others \"npm run verdaccio:server\" \"npm run test:acceptance\"",
"test:e2e": "cross-env BABEL_ENV=test jest --config ./test/jest.config.e2e.js",
"test": "cross-env NODE_ENV=test BABEL_ENV=test TZ=UTC jest --config ./jest/jest.config.js --maxWorkers 2 --passWithNoTests",
"test:update-snapshot": "npm run test -- -u",
"test:size": "bundlesize",
"lint": "npm run lint:js && npm run lint:css && npm run lint:lockfile",
"lint:js": "npm run type-check && eslint . --ext .js,.ts,.tsx",
@@ -171,7 +181,6 @@
"lint:lockfile": "lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts verdaccio npm yarn",
"coverage:publish": "codecov",
"pre:webpack": "rimraf static/*",
"prepublish": "in-publish && npm run build || not-in-publish",
"dev:web": "cross-env BABEL_ENV=ui babel-node tools/dev.server.js",
"verdaccio:server": "node tools/verdaccio.js",
"build": "npm run pre:webpack && cross-env BABEL_ENV=ui webpack --config tools/webpack.prod.config.babel.js",
@@ -180,29 +189,23 @@
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run verdaccio:server\""
},
"engines": {
"node": ">=8",
"node": ">= 8",
"npm": ">=5"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-commit": "lint-staged --relative",
"commit-msg": "commitlint -e $GIT_PARAMS"
}
},
"lint-staged": {
"relative": true,
"linters": {
"*.{js,tsx,ts}": [
"eslint .",
"prettier --write"
],
"*": [
"detect-secrets-launcher --baseline .secrets-baseline",
"git add"
]
},
"ignore": [
"*.json"
"*.{js,tsx,ts}": [
"eslint . --ext .js,.ts,.tsx",
"prettier --write"
],
"*": [
"detect-secrets-launcher --baseline .secrets-baseline",
"git add"
]
},
"license": "MIT",

View File

@@ -4976,8 +4976,12 @@
"_attachments": {
"jquery-1.5.1.tgz": {
"shasum": "2ae2d661e906c1a01e044a71bb5b2743942183e5"
},
"jquery-3.3.1.tgz": {
"shasum": "958ce29e81c9790f31be7792df5d4d95fc57fbca"
}
},
"_rev": "60-fed4915c27b9c1e6",
"readme": "# jQuery\n\n> jQuery is a fast, small, and feature-rich JavaScript library.\n\nFor information on how to get started and how to use jQuery, please see [jQuery's documentation](http://api.jquery.com/).\nFor source files and issues, please visit the [jQuery repo](https://github.com/jquery/jquery).\n\nIf upgrading, please see the [blog post for 3.3.1](https://blog.jquery.com/2017/03/20/jquery-3.3.1-now-available/). This includes notable differences from the previous version and a more readable changelog.\n\n## Including jQuery\n\nBelow are some of the most common ways to include jQuery.\n\n### Browser\n\n#### Script tag\n\n```html\n<script src=\"https://code.jquery.com/jquery-3.3.1.min.js\"></script>\n```\n\n#### Babel\n\n[Babel](http://babeljs.io/) is a next generation JavaScript compiler. One of the features is the ability to use ES6/ES2015 modules now, even though browsers do not yet support this feature natively.\n\n```js\nimport $ from \"jquery\";\n```\n\n#### Browserify/Webpack\n\nThere are several ways to use [Browserify](http://browserify.org/) and [Webpack](https://webpack.github.io/). For more information on using these tools, please refer to the corresponding project's documention. In the script, including jQuery will usually look like this...\n\n```js\nvar $ = require(\"jquery\");\n```\n\n#### AMD (Asynchronous Module Definition)\n\nAMD is a module format built for the browser. For more information, we recommend [require.js' documentation](http://requirejs.org/docs/whyamd.html).\n\n```js\ndefine([\"jquery\"], function($) {\n\n});\n```\n\n### Node\n\nTo include jQuery in [Node](nodejs.org), first install with npm.\n\n```sh\nnpm install jquery\n```\n\nFor jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/tmpvar/jsdom). This can be useful for testing purposes.\n\n```js\nrequire(\"jsdom\").env(\"\", function(err, window) {\n\tif (err) {\n\t\tconsole.error(err);\n\t\treturn;\n\t}\n\n\tvar $ = require(\"jquery\")(window);\n});\n```"
}
"_rev": "61-e6be890a78963127",
"readme": "# jQuery\n\n> jQuery is a fast, small, and feature-rich JavaScript library.\n\nFor information on how to get started and how to use jQuery, please see [jQuery's documentation](http://api.jquery.com/).\nFor source files and issues, please visit the [jQuery repo](https://github.com/jquery/jquery).\n\nIf upgrading, please see the [blog post for 3.3.1](https://blog.jquery.com/2017/03/20/jquery-3.3.1-now-available/). This includes notable differences from the previous version and a more readable changelog.\n\n## Including jQuery\n\nBelow are some of the most common ways to include jQuery.\n\n### Browser\n\n#### Script tag\n\n```html\n<script src=\"https://code.jquery.com/jquery-3.3.1.min.js\"></script>\n```\n\n#### Babel\n\n[Babel](http://babeljs.io/) is a next generation JavaScript compiler. One of the features is the ability to use ES6/ES2015 modules now, even though browsers do not yet support this feature natively.\n\n```js\nimport $ from \"jquery\";\n```\n\n#### Browserify/Webpack\n\nThere are several ways to use [Browserify](http://browserify.org/) and [Webpack](https://webpack.github.io/). For more information on using these tools, please refer to the corresponding project's documention. In the script, including jQuery will usually look like this...\n\n```js\nvar $ = require(\"jquery\");\n```\n\n#### AMD (Asynchronous Module Definition)\n\nAMD is a module format built for the browser. For more information, we recommend [require.js' documentation](http://requirejs.org/docs/whyamd.html).\n\n```js\ndefine([\"jquery\"], function($) {\n\n});\n```\n\n### Node\n\nTo include jQuery in [Node](nodejs.org), first install with npm.\n\n```sh\nnpm install jquery\n```\n\nFor jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/tmpvar/jsdom). This can be useful for testing purposes.\n\n```js\nrequire(\"jsdom\").env(\"\", function(err, window) {\n\tif (err) {\n\t\tconsole.error(err);\n\t\treturn;\n\t}\n\n\tvar $ = require(\"jquery\")(window);\n});\n```",
"_id": "jquery"
}

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { render, waitForElement, fireEvent } from '../utils/test-react-testing-library';
import storage from '../utils/storage';
// eslint-disable-next-line jest/no-mocks-import
import { generateTokenWithTimeRange } from '../../jest/unit/components/__mocks__/token';
import App from './App';
import { AppProps } from './AppContext';
jest.mock('../utils/storage', () => {
class LocalStorageMock {
@@ -30,66 +30,75 @@ jest.mock('../utils/storage', () => {
});
jest.mock('../utils/api', () => ({
// eslint-disable-next-line jest/no-mocks-import
request: require('../../jest/unit/components/__mocks__/api').default.request,
}));
describe('App', () => {
let wrapper: ReactWrapper<{}, AppProps, App>;
/* eslint-disable react/jsx-no-bind*/
describe('<App />', () => {
test('should display the Loading component at the beginning ', () => {
const { container, queryByTestId } = render(<App />);
beforeEach(() => {
wrapper = mount(<App />);
expect(container.firstChild).toMatchSnapshot();
expect(queryByTestId('loading')).toBeTruthy();
});
test('toggleLoginModal: should toggle the value in state', () => {
const { handleToggleLoginModal } = wrapper.instance();
expect(wrapper.state().showLoginModal).toBeFalsy();
handleToggleLoginModal();
expect(wrapper.state('showLoginModal')).toBeTruthy();
expect(wrapper.state('error')).toEqual(undefined);
test('should display the Header component ', async () => {
const { container, queryByTestId } = render(<App />);
expect(container.firstChild).toMatchSnapshot();
expect(queryByTestId('loading')).toBeTruthy();
// wait for the Header component appearance and return the element
const headerElement = await waitForElement(() => queryByTestId('header'));
expect(headerElement).toBeTruthy();
});
test('handleLogout - logouts the user and clear localstorage', async () => {
storage.setItem('username', 'verdaccio');
storage.setItem('token', generateTokenWithTimeRange(24));
const { queryByTestId } = render(<App />);
// wait for the Account's circle element component appearance and return the element
const accountCircleElement = await waitForElement(() => queryByTestId('header--menu-accountcircle'));
expect(accountCircleElement).toBeTruthy();
if (accountCircleElement) {
fireEvent.click(accountCircleElement);
// wait for the Button's logout element component appearance and return the element
const buttonLogoutElement = await waitForElement(() => queryByTestId('header--button-logout'));
expect(buttonLogoutElement).toBeTruthy();
if (buttonLogoutElement) {
fireEvent.click(buttonLogoutElement);
expect(queryByTestId('greetings-label')).toBeFalsy();
}
}
});
test('isUserAlreadyLoggedIn: token already available in storage', async () => {
storage.setItem('username', 'verdaccio');
storage.setItem('token', generateTokenWithTimeRange(24));
const { isUserAlreadyLoggedIn } = wrapper.instance();
isUserAlreadyLoggedIn();
const { queryByTestId, queryAllByText } = render(<App />);
expect(wrapper.state('user').username).toEqual('verdaccio');
});
// wait for the Account's circle element component appearance and return the element
const accountCircleElement = await waitForElement(() => queryByTestId('header--menu-accountcircle'));
expect(accountCircleElement).toBeTruthy();
test('handleLogout - logouts the user and clear localstorage', async () => {
const { handleLogout } = wrapper.instance();
storage.setItem('username', 'verdaccio');
storage.setItem('token', 'xxxx.TOKEN.xxxx');
if (accountCircleElement) {
fireEvent.click(accountCircleElement);
await handleLogout();
expect(wrapper.state('user')).toEqual({});
expect(wrapper.state('isUserLoggedIn')).toBeFalsy();
});
// wait for the Greeting's label element component appearance and return the element
const greetingsLabelElement = await waitForElement(() => queryByTestId('greetings-label'));
expect(greetingsLabelElement).toBeTruthy();
test('handleDoLogin - login the user successfully', async () => {
const { handleDoLogin } = wrapper.instance();
await handleDoLogin('sam', '1234');
const result = {
username: 'sam',
};
expect(wrapper.state('isUserLoggedIn')).toBeTruthy();
expect(wrapper.state('showLoginModal')).toBeFalsy();
expect(storage.getItem('username')).toEqual('sam');
expect(storage.getItem('token')).toEqual('TEST_TOKEN');
expect(wrapper.state('user')).toEqual(result);
});
test('handleDoLogin - authentication failure', async () => {
const { handleDoLogin } = wrapper.instance();
await handleDoLogin('sam', '12345');
const result = {
description: 'bad username/password, access denied',
title: 'Unable to login',
type: 'error',
};
expect(wrapper.state('user')).toEqual({});
expect(wrapper.state('error')).toEqual(result);
if (greetingsLabelElement) {
expect(queryAllByText('verdaccio')).toBeTruthy();
}
}
});
});

View File

@@ -1,172 +1,86 @@
import React, { Component, ReactElement } from 'react';
/* eslint-disable react/jsx-max-depth */
import React, { useState, useEffect, Suspense } from 'react';
import styled from '@emotion/styled';
import isNil from 'lodash/isNil';
import 'normalize.css';
import 'typeface-roboto/index.css';
import { Router } from 'react-router-dom';
import '../../i18n/config';
import storage from '../utils/storage';
import { makeLogin, isTokenExpire } from '../utils/login';
import Loading from '../components/Loading';
import LoginModal from '../components/Login';
import { isTokenExpire } from '../utils/login';
import Header from '../components/Header';
import { Container, Content } from '../components/Layout';
import API from '../utils/api';
import '../utils/styles/global';
import Footer from '../components/Footer';
import Loading from '../components/Loading';
import Box from '../muiComponents/Box';
import StyleBaseline from '../design-tokens/StyleBaseline';
import { Theme } from '../design-tokens/theme';
import AppRoute from './AppRoute';
import { AppProps, AppContextProvider } from './AppContext';
import AppContextProvider from './AppContextProvider';
import AppRoute, { history } from './AppRoute';
import loadDayJSLocale from './load-dayjs-locale';
export default class App extends Component<{}, AppProps> {
public state: AppProps = {
logoUrl: window.VERDACCIO_LOGO,
user: {},
scope: window.VERDACCIO_SCOPE || '',
showLoginModal: false,
isUserLoggedIn: false,
packages: [],
isLoading: true,
};
const StyledBox = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
backgroundColor: theme?.palette.background.default,
}));
public componentDidMount(): void {
this.isUserAlreadyLoggedIn();
this.loadOnHandler();
}
// eslint-disable-next-line no-unused-vars
public componentDidUpdate(_: AppProps, prevState: AppProps): void {
const { isUserLoggedIn } = this.state;
if (prevState.isUserLoggedIn !== isUserLoggedIn) {
this.loadOnHandler();
}
}
public render(): React.ReactElement<HTMLDivElement> {
const { isLoading, isUserLoggedIn, packages, logoUrl, user, scope } = this.state;
const context = { isUserLoggedIn, packages, logoUrl, user, scope };
return (
<Container isLoading={isLoading}>
{isLoading ? <Loading /> : <AppContextProvider value={context}>{this.renderContent()}</AppContextProvider>}
{this.renderLoginModal()}
</Container>
);
}
public isUserAlreadyLoggedIn = () => {
// checks for token validity
const token = storage.getItem('token');
const username: string = storage.getItem('username') as string;
if (isTokenExpire(token) || isNil(username)) {
this.handleLogout();
} else {
this.setState({
user: { username },
isUserLoggedIn: true,
});
}
};
public loadOnHandler = async () => {
try {
const packages = await API.request<any[]>('packages', 'GET');
// @ts-ignore: FIX THIS TYPE: Type 'any[]' is not assignable to type '[]'
this.setState({
packages,
isLoading: false,
});
} catch (error) {
// FIXME: add dialog
console.error({
title: 'Warning',
message: `Unable to load package list: ${error.message}`,
});
this.setLoading(false);
}
};
public setLoading = (isLoading: boolean) =>
this.setState({
isLoading,
});
const StyledBoxContent = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
[`@media screen and (min-width: ${theme && theme.breakPoints.container}px)`]: {
maxWidth: theme && theme.breakPoints.container,
width: '100%',
marginLeft: 'auto',
marginRight: 'auto',
},
}));
/* eslint-disable react/jsx-no-bind */
/* eslint-disable react-hooks/exhaustive-deps */
const App: React.FC = () => {
const [user, setUser] = useState();
/**
* Toggles the login modal
* Required by: <LoginModal /> <Header />
*/
public handleToggleLoginModal = () => {
this.setState(prevState => ({
showLoginModal: !prevState.showLoginModal,
}));
};
/**
* handles login
* Logout user
* Required by: <Header />
*/
public handleDoLogin = async (usernameValue: string, passwordValue: string) => {
const { username, token, error } = await makeLogin(usernameValue, passwordValue);
if (username && token) {
storage.setItem('username', username);
storage.setItem('token', token);
this.setLoggedUser(username);
}
if (error) {
this.setState({
user: {},
error,
});
}
};
public setLoggedUser = (username: string) => {
this.setState({
user: {
username,
},
isUserLoggedIn: true, // close login modal after successful login
showLoginModal: false, // set isUserLoggedIn to true
});
};
/**
* Logouts user
* Required by: <Header />
*/
public handleLogout = () => {
const logout = () => {
storage.removeItem('username');
storage.removeItem('token');
this.setState({
user: {},
isUserLoggedIn: false,
});
setUser(undefined);
};
public renderLoginModal = (): ReactElement<HTMLElement> => {
const { error, showLoginModal } = this.state;
return <LoginModal error={error} onCancel={this.handleToggleLoginModal} onSubmit={this.handleDoLogin} visibility={showLoginModal} />;
const checkUserAlreadyLoggedIn = () => {
// checks for token validity
const token = storage.getItem('token');
const username = storage.getItem('username');
if (isTokenExpire(token) || isNil(username)) {
logout();
return;
}
setUser({ username });
};
public renderContent = (): ReactElement<HTMLElement> => {
return (
<>
<Content>
<AppRoute>{this.renderHeader()}</AppRoute>
</Content>
<Footer />
</>
);
};
useEffect(() => {
checkUserAlreadyLoggedIn();
loadDayJSLocale();
}, []);
public renderHeader = (): ReactElement<HTMLElement> => {
const {
logoUrl,
user: { username },
scope,
} = this.state;
return (
<Suspense fallback={<Loading />}>
<StyleBaseline />
<StyledBox display="flex" flexDirection="column" height="100%">
<>
<Router history={history}>
<AppContextProvider user={user}>
<Header />
<StyledBoxContent flexGrow={1}>
<AppRoute />
</StyledBoxContent>
</AppContextProvider>
</Router>
<Footer />
</>
</StyledBox>
</Suspense>
);
};
return <Header logo={logoUrl} onLogout={this.handleLogout} onToggleLoginModal={this.handleToggleLoginModal} scope={scope} username={username} />;
};
}
export default App;

18
src/App/AppContext.ts Normal file
View File

@@ -0,0 +1,18 @@
import { createContext } from 'react';
export interface AppProps {
user?: User;
scope: string;
}
export interface User {
username: string;
}
export interface AppContextProps extends AppProps {
setUser: (user?: User) => void;
}
const AppContext = createContext<undefined | AppContextProps>(undefined);
export default AppContext;

View File

@@ -1,20 +0,0 @@
import { createContext } from 'react';
import { FormError } from '../components/Login/Login';
export interface AppProps {
error?: FormError;
logoUrl: string;
user: {
username?: string;
};
scope: string;
showLoginModal: boolean;
isUserLoggedIn: boolean;
packages: [];
isLoading: boolean;
}
export const AppContext = createContext<Partial<AppProps>>({});
export const AppContextProvider = AppContext.Provider;
export const AppContextConsumer = AppContext.Consumer;

View File

@@ -0,0 +1,41 @@
import React, { useState, useEffect } from 'react';
import AppContext, { AppProps, User } from './AppContext';
interface Props {
user?: User;
}
/* eslint-disable react-hooks/exhaustive-deps */
const AppContextProvider: React.FC<Props> = ({ children, user }) => {
const [state, setState] = useState<AppProps>({
scope: window.VERDACCIO_SCOPE || '',
user,
});
useEffect(() => {
setState({
...state,
user,
});
}, [user]);
const setUser = (user?: User) => {
setState({
...state,
user,
});
};
return (
<AppContext.Provider
value={{
...state,
setUser,
}}>
{children}
</AppContext.Provider>
);
};
export default AppContextProvider;

View File

@@ -1,10 +1,9 @@
import React, { lazy, useContext, Suspense } from 'react';
import React, { lazy, useContext } from 'react';
import { Route as ReactRouterDomRoute, Switch, Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import { useTranslation } from 'react-i18next';
import Loading from '../components/Loading';
import { AppContext } from './AppContext';
import AppContext from './AppContext';
const NotFound = lazy(() => import('../components/NotFound'));
const VersionContextProvider = lazy(() => import('../pages/Version/VersionContextProvider'));
@@ -19,48 +18,52 @@ enum Route {
PACKAGE_VERSION = '/-/web/detail/:package/v/:version',
}
const history = createBrowserHistory({
export const history = createBrowserHistory({
basename: window.__VERDACCIO_BASENAME_UI_OPTIONS && window.__VERDACCIO_BASENAME_UI_OPTIONS.url_prefix,
});
/* eslint react/jsx-max-depth: 0 */
const AppRoute: React.FC = ({ children }) => {
const AppRoute: React.FC = () => {
const appContext = useContext(AppContext);
const { isUserLoggedIn, packages } = appContext;
const { t } = useTranslation();
if (!appContext) {
throw Error(t('app-context-not-correct-used'));
}
const { user } = appContext;
const isUserLoggedIn = user && user.username;
return (
<Router history={history}>
<Suspense fallback={<Loading />}>
{children}
<Switch>
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
<HomePage isUserLoggedIn={!!isUserLoggedIn} packages={packages || []} />
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.PACKAGE_VERSION}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE_VERSION}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute>
<NotFound />
</ReactRouterDomRoute>
</Switch>
</Suspense>
<Switch>
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
<HomePage isUserLoggedIn={!!isUserLoggedIn} />
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.PACKAGE_VERSION}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE_VERSION}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE}>
<VersionContextProvider>
<VersionPage />
</VersionContextProvider>
</ReactRouterDomRoute>
<ReactRouterDomRoute>
<NotFound />
</ReactRouterDomRoute>
</Switch>
</Router>
);
};

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,2 @@
export { default } from './App';
export { default as AppContextProvider } from './AppContextProvider';

View File

@@ -0,0 +1,52 @@
import dayjs from 'dayjs';
import i18n from 'i18next';
function getFallFackLanguage(): string | undefined {
const fallbackLanguage = i18n.options.fallbackLng;
if (Array.isArray(fallbackLanguage)) {
return fallbackLanguage[0];
}
if (typeof fallbackLanguage === 'string') {
return fallbackLanguage;
}
return undefined;
}
function loadDayJSLocale() {
const fallbackLanguage = getFallFackLanguage();
const locale = i18n.language || fallbackLanguage;
// dayjs loades en-US by default
if (!locale || locale === 'en-US') {
return;
}
switch (locale.toLowerCase()) {
// At the moment we only support pt-BR, please see: i18n/translations/*
case 'pt-br':
{
require('dayjs/locale/pt-br');
dayjs.locale('pt-br');
}
break;
case 'de':
{
require('dayjs/locale/de');
dayjs.locale('de');
}
break;
case 'es-es':
{
require('dayjs/locale/es');
dayjs.locale('es');
}
break;
default:
break;
}
}
export default loadDayJSLocale;

View File

@@ -1,18 +0,0 @@
import { css } from 'emotion';
import colors from '../utils/styles/colors';
export const alertError = css({
backgroundColor: `${colors.red} !important`,
minWidth: 'inherit !important',
});
export const alertErrorMsg = css({
display: 'flex',
alignItems: 'center',
});
export const alertIcon = css({
opacity: 0.9,
marginRight: '8px',
});

View File

@@ -1,89 +1,71 @@
import React from 'react';
import { mount } from 'enzyme';
import api from '../../utils/api';
import { render, cleanup } from '../../utils/test-react-testing-library';
import { DetailContext, DetailContextProps } from '../../pages/Version';
import { ActionBar } from './ActionBar';
import ActionBar from './ActionBar';
const mockPackageMeta: jest.Mock = jest.fn(() => ({
latest: {
homepage: 'https://verdaccio.tld',
bugs: {
url: 'https://verdaccio.tld/bugs',
},
dist: {
tarball: 'https://verdaccio.tld/download',
const detailContextValue: DetailContextProps = {
packageName: 'foo',
readMe: 'test',
enableLoading: () => {},
isLoading: false,
hasNotBeenFound: false,
packageMeta: {
_uplinks: {},
latest: {
name: '@verdaccio/local-storage',
version: '8.0.1-next.1',
dist: { fileCount: 0, unpackedSize: 0, tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz' },
homepage: 'https://verdaccio.org',
bugs: {
url: 'https://github.com/verdaccio/monorepo/issues',
},
},
},
}));
};
jest.mock('../../pages/Version', () => ({
DetailContextConsumer: component => {
return component.children({ packageMeta: mockPackageMeta() });
},
}));
const ComponentToBeRendered: React.FC<{ contextValue: DetailContextProps }> = ({ contextValue }) => (
<DetailContext.Provider value={contextValue}>
<ActionBar />
</DetailContext.Provider>
);
describe('<ActionBar /> component', () => {
beforeEach(() => {
jest.resetModules();
jest.resetAllMocks();
afterEach(() => {
cleanup();
});
test('should render the component in default state', () => {
const wrapper = mount(<ActionBar />);
expect(wrapper.html()).toMatchSnapshot();
const { container } = render(<ComponentToBeRendered contextValue={detailContextValue} />);
expect(container.firstChild).toMatchSnapshot();
});
test('when there is no action bar data', () => {
mockPackageMeta.mockImplementation(() => ({
latest: {},
}));
const packageMeta = {
...detailContextValue.packageMeta,
latest: {
...detailContextValue.packageMeta.latest,
homepage: undefined,
bugs: undefined,
dist: {
...detailContextValue.packageMeta.latest.dist,
tarball: undefined,
},
},
};
const wrapper = mount(<ActionBar />);
// FIXME: this only renders the DetailContextConsumer, thus
// the wrapper will be always empty
expect(wrapper.html()).toEqual('');
});
test('when there is no latest property in package meta', () => {
mockPackageMeta.mockImplementation(() => ({}));
const wrapper = mount(<ActionBar />);
expect(wrapper.html()).toEqual('');
const { container } = render(<ComponentToBeRendered contextValue={{ ...detailContextValue, packageMeta }} />);
expect(container.firstChild).toMatchSnapshot();
});
test('when there is a button to download a tarball', () => {
mockPackageMeta.mockImplementation(() => ({
latest: {
dist: {
tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
},
},
}));
const wrapper = mount(<ActionBar />);
expect(wrapper.html()).toMatchSnapshot();
const button = wrapper.find('button');
expect(button).toHaveLength(1);
const spy = jest.spyOn(api, 'request');
button.simulate('click');
expect(spy).toHaveBeenCalled();
const { getByTitle } = render(<ComponentToBeRendered contextValue={{ ...detailContextValue }} />);
expect(getByTitle('Download tarball')).toBeTruthy();
});
test('when there is a button to open an issue', () => {
mockPackageMeta.mockImplementation(() => ({
latest: {
bugs: {
url: 'https://verdaccio.tld/bugs',
},
},
}));
const wrapper = mount(<ActionBar />);
expect(wrapper.html()).toMatchSnapshot();
const button = wrapper.find('button');
expect(button).toHaveLength(1);
const { getByTitle } = render(<ComponentToBeRendered contextValue={{ ...detailContextValue }} />);
expect(getByTitle('Open an issue')).toBeTruthy();
});
});

View File

@@ -1,132 +1,44 @@
import React, { Component, ReactElement } from 'react';
import BugReportIcon from '@material-ui/icons/BugReport';
import DownloadIcon from '@material-ui/icons/CloudDownload';
import HomeIcon from '@material-ui/icons/Home';
import React from 'react';
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/Version';
import { isURL, extractFileName, downloadFile } from '../../utils/url';
import api from '../../utils/api';
import Tooltip from '../../muiComponents/Tooltip';
import List from '../../muiComponents/List';
import { DetailContext } from '../../pages/Version';
import { isURL } from '../../utils/url';
import Box from '../../muiComponents/Box';
import { Fab, ActionListItem } from './styles';
import ActionBarAction, { ActionBarActionProps } from './ActionBarAction';
export interface Action {
icon: string;
title: string;
handler?: Function;
}
/* eslint-disable verdaccio/jsx-spread */
const ActionBar: React.FC = () => {
const detailContext = React.useContext(DetailContext);
export async function downloadHandler(link: string): Promise<void> {
const fileStream: Blob = await api.request(link, 'GET', {
headers: {
['accept']: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
},
credentials: 'include',
});
const fileName = extractFileName(link);
downloadFile(fileStream, fileName);
}
const { packageMeta } = detailContext;
const ACTIONS = {
homepage: {
icon: <HomeIcon />,
title: 'Visit homepage',
},
issue: {
icon: <BugReportIcon />,
title: 'Open an issue',
},
tarball: {
icon: <DownloadIcon />,
title: 'Download tarball',
handler: downloadHandler,
},
if (!packageMeta?.latest) {
return null;
}
const { homepage, bugs, dist } = packageMeta.latest;
const actions: Array<ActionBarActionProps> = [];
if (homepage && isURL(homepage)) {
actions.push({ type: 'VISIT_HOMEPAGE', link: homepage });
}
if (bugs?.url && isURL(bugs.url)) {
actions.push({ type: 'OPEN_AN_ISSUE', link: bugs.url });
}
if (dist?.tarball && isURL(dist.tarball)) {
actions.push({ type: 'DOWNLOAD_TARBALL', link: dist.tarball });
}
return (
<Box alignItems="center" display="flex" marginBottom="8px">
{actions.map(action => (
<ActionBarAction key={action.link} {...action} />
))}
</Box>
);
};
class ActionBar extends Component {
public render(): ReactElement<HTMLElement> {
return (
<DetailContextConsumer>
{context => {
const { packageMeta } = context;
if (!packageMeta) {
return null;
}
return this.renderActionBar(context as VersionPageConsumerProps);
}}
</DetailContextConsumer>
);
}
private renderIconsWithLink(link: string, component: JSX.Element): ReactElement<HTMLElement> {
return (
<a href={link} target={'_blank'}>
{component}
</a>
);
}
private renderActionBar = ({ packageMeta }) => {
const { latest } = packageMeta;
if (!latest) {
return null;
}
const { homepage, bugs, dist } = latest;
const actionsMap = {
homepage,
issue: bugs ? bugs.url : null,
tarball: dist ? dist.tarball : null,
};
const renderList = Object.keys(actionsMap).reduce((component: React.ReactElement[], value, key) => {
const link = actionsMap[value];
if (link && isURL(link)) {
const actionItem: Action = ACTIONS[value];
if (actionItem.handler) {
const fab = (
<Tooltip key={key} title={actionItem['title']}>
<Fab
/* eslint-disable react/jsx-no-bind */
onClick={() => {
/* eslint-disable @typescript-eslint/no-non-null-assertion */
actionItem.handler!(link);
}}
size={'small'}>
{actionItem['icon']}
</Fab>
</Tooltip>
);
component.push(fab);
} else {
const fab = <Fab size={'small'}>{actionItem['icon']}</Fab>;
component.push(
<Tooltip key={key} title={actionItem['title']}>
<>{this.renderIconsWithLink(link, fab)}</>
</Tooltip>
);
}
}
return component;
}, []);
if (renderList.length > 0) {
return (
<List>
<ActionListItem alignItems={'flex-start'} button={true}>
{renderList}
</ActionListItem>
</List>
);
}
return null;
};
}
export { ActionBar };
export default ActionBar;

View File

@@ -0,0 +1,63 @@
import React from 'react';
import styled from '@emotion/styled';
import BugReportIcon from '@material-ui/icons/BugReport';
import DownloadIcon from '@material-ui/icons/CloudDownload';
import HomeIcon from '@material-ui/icons/Home';
import { useTranslation } from 'react-i18next';
import Tooltip from '../../muiComponents/Tooltip';
import Link from '../Link';
import FloatingActionButton from '../../muiComponents/FloatingActionButton';
import { Theme } from '../../design-tokens/theme';
import downloadTarball from './download-tarball';
export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(props => ({
backgroundColor: props.theme && props.theme.palette.primary.main,
color: props.theme && props.theme.palette.white,
marginRight: 10,
}));
type ActionType = 'VISIT_HOMEPAGE' | 'OPEN_AN_ISSUE' | 'DOWNLOAD_TARBALL';
export interface ActionBarActionProps {
type: ActionType;
link: string;
}
/* eslint-disable react/jsx-no-bind */
const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
const { t } = useTranslation();
switch (type) {
case 'VISIT_HOMEPAGE':
return (
<Tooltip title={t('action-bar-action.visit-home-page')}>
<Link external={true} to={link}>
<Fab size="small">
<HomeIcon />
</Fab>
</Link>
</Tooltip>
);
case 'OPEN_AN_ISSUE':
return (
<Tooltip title={t('action-bar-action.open-an-issue')}>
<Link external={true} to={link}>
<Fab size="small">
<BugReportIcon />
</Fab>
</Link>
</Tooltip>
);
case 'DOWNLOAD_TARBALL':
return (
<Tooltip title={t('action-bar-action.download-tarball')}>
<Fab data-testid="download-tarball-btn" onClick={downloadTarball(link)} size="small">
<DownloadIcon />
</Fab>
</Tooltip>
);
}
};
export default ActionBarAction;

View File

@@ -1,7 +1,118 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ActionBar /> component should render the component in default state 1`] = `""`;
exports[`<ActionBar /> component should render the component in default state 1`] = `
.emotion-0 {
background-color: #4b5e40;
color: #fff;
margin-right: 10px;
}
exports[`<ActionBar /> component when there is a button to download a tarball 1`] = `"<ul class=\\"MuiList-root MuiList-padding\\"><div class=\\"MuiButtonBase-root MuiListItem-root css-1br2q5z eux6shq0 MuiListItem-gutters MuiListItem-button MuiListItem-alignItemsFlexStart\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><button class=\\"MuiButtonBase-root MuiFab-root css-96oxa0 eux6shq1 MuiFab-sizeSmall\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Download tarball\\"><span class=\\"MuiFab-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
<div
class="MuiBox-root MuiBox-root-2"
>
<a
class=""
href="https://verdaccio.org"
rel="noopener noreferrer"
target="_blank"
title="Visit homepage"
>
<h6
class="MuiTypography-root MuiTypography-subtitle1"
>
<button
class="MuiButtonBase-root MuiFab-root emotion-0 emotion-1 MuiFab-sizeSmall"
tabindex="0"
type="button"
>
<span
class="MuiFab-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</h6>
</a>
<a
class=""
href="https://github.com/verdaccio/monorepo/issues"
rel="noopener noreferrer"
target="_blank"
title="Open an issue"
>
<h6
class="MuiTypography-root MuiTypography-subtitle1"
>
<button
class="MuiButtonBase-root MuiFab-root emotion-0 emotion-1 MuiFab-sizeSmall"
tabindex="0"
type="button"
>
<span
class="MuiFab-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</h6>
</a>
<button
class="MuiButtonBase-root MuiFab-root emotion-0 emotion-1 MuiFab-sizeSmall"
data-testid="download-tarball-btn"
tabindex="0"
title="Download tarball"
type="button"
>
<span
class="MuiFab-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
`;
exports[`<ActionBar /> component when there is a button to open an issue 1`] = `"<ul class=\\"MuiList-root MuiList-padding\\"><div class=\\"MuiButtonBase-root MuiListItem-root css-1br2q5z eux6shq0 MuiListItem-gutters MuiListItem-button MuiListItem-alignItemsFlexStart\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><a href=\\"https://verdaccio.tld/bugs\\" target=\\"_blank\\"><button class=\\"MuiButtonBase-root MuiFab-root css-96oxa0 eux6shq1 MuiFab-sizeSmall\\" tabindex=\\"0\\" type=\\"button\\"><span class=\\"MuiFab-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></a><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<ActionBar /> component when there is no action bar data 1`] = `
<div
class="MuiBox-root MuiBox-root-77"
/>
`;

View File

@@ -0,0 +1,18 @@
import api from '../../utils/api';
import { extractFileName, downloadFile } from '../../utils/url';
function downloadTarball(link: string) {
return async function downloadHandler(): Promise<void> {
const fileStream: Blob = await api.request(link, 'GET', {
headers: {
['accept']:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
},
credentials: 'include',
});
const fileName = extractFileName(link);
downloadFile(fileStream, fileName);
};
}
export default downloadTarball;

View File

@@ -1 +1,2 @@
export { default } from './ActionBar';
export { default as downloadTarball } from './download-tarball';

View File

@@ -1,19 +0,0 @@
import styled from 'react-emotion';
import { default as MuiFab } from '@material-ui/core/Fab';
import colors from '../../utils/styles/colors';
import ListItem from '../../muiComponents/ListItem';
export const ActionListItem = styled(ListItem)({
paddingTop: 0,
paddingLeft: 0,
paddingRight: 0,
});
export const Fab = styled(MuiFab)({
'&&': {
backgroundColor: colors.primary,
color: colors.white,
marginRight: '10px',
},
});

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import { mount } from '../../utils/test-enzyme';
import { DetailContext } from '../../pages/Version';
import Authors from './Author';

View File

@@ -1,14 +1,17 @@
import React, { FC, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { DetailContext } from '../../pages/Version';
import { isEmail } from '../../utils/url';
import Avatar from '../../muiComponents/Avatar';
import List from '../../muiComponents/List';
import { getAuthorName } from '../../utils/package';
import { StyledText, AuthorListItem, AuthorListItemText } from './styles';
const Author: FC = () => {
const { packageMeta } = useContext(DetailContext);
const { t } = useTranslation();
if (!packageMeta) {
return null;
@@ -25,7 +28,7 @@ const Author: FC = () => {
const avatarComponent = <Avatar alt={author.name} src={author.avatar} />;
return (
<List subheader={<StyledText variant={'subtitle1'}>{'Author'}</StyledText>}>
<List subheader={<StyledText variant={'subtitle1'}>{t('sidebar.author.title')}</StyledText>}>
<AuthorListItem button={true}>
{!email || !isEmail(email) ? (
avatarComponent
@@ -34,8 +37,7 @@ const Author: FC = () => {
{avatarComponent}
</a>
)}
<AuthorListItemText primary={name} />
{name && <AuthorListItemText primary={getAuthorName(name)} />}
</AuthorListItem>
</List>
);

View File

@@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Author /> component should render the component in default state 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-b8upko e1xuehjw0 MuiTypography-subtitle1\\">Author</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-xugzlj e1xuehjw1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><a href=\\"mailto:verdaccio.user@verdaccio.org?subject=verdaccio@4.0.0\\" target=\\"_top\\"><div class=\\"MuiAvatar-root\\"><img alt=\\"verdaccio user\\" src=\\"https://www.gravatar.com/avatar/000000\\" class=\\"MuiAvatar-img\\"></div></a><div class=\\"MuiListItemText-root css-1vhg3jx e1xuehjw2\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">verdaccio user</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Author /> component should render the component in default state 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-5wp24z-StyledText e1xuehjw0 MuiTypography-subtitle1\\">sidebar.author.title</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1k45khb-AuthorListItem e1xuehjw1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><a href=\\"mailto:verdaccio.user@verdaccio.org?subject=verdaccio@4.0.0\\" target=\\"_top\\"><div class=\\"MuiAvatar-root MuiAvatar-circle\\"><img alt=\\"verdaccio user\\" src=\\"https://www.gravatar.com/avatar/000000\\" class=\\"MuiAvatar-img\\"></div></a><div class=\\"MuiListItemText-root css-1cnlq5d-AuthorListItemText e1xuehjw2\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">verdaccio user</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Author /> component should render the component when there is no author email 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-b8upko e1xuehjw0 MuiTypography-subtitle1\\">Author</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-xugzlj e1xuehjw1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root\\"><img alt=\\"verdaccio user\\" src=\\"https://www.gravatar.com/avatar/000000\\" class=\\"MuiAvatar-img\\"></div><div class=\\"MuiListItemText-root css-1vhg3jx e1xuehjw2\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">verdaccio user</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Author /> component should render the component when there is no author email 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-5wp24z-StyledText e1xuehjw0 MuiTypography-subtitle1\\">sidebar.author.title</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1k45khb-AuthorListItem e1xuehjw1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-circle\\"><img alt=\\"verdaccio user\\" src=\\"https://www.gravatar.com/avatar/000000\\" class=\\"MuiAvatar-img\\"></div><div class=\\"MuiListItemText-root css-1cnlq5d-AuthorListItemText e1xuehjw2\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">verdaccio user</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;

View File

@@ -1,27 +1,22 @@
import styled from 'react-emotion';
import ListItemText from '@material-ui/core/ListItemText';
import styled from '@emotion/styled';
import { fontWeight } from '../../utils/styles/sizes';
import ListItem from '../../muiComponents/ListItem';
import Text from '../../muiComponents/Text';
import ListItemText from '../../muiComponents/ListItemText';
import { Theme } from '../../design-tokens/theme';
export const StyledText = styled(Text)({
fontWeight: fontWeight.bold,
textTransform: 'capitalize',
});
export const StyledText = styled(Text)<{ theme?: Theme }>(props => ({
fontWeight: props.theme && props.theme.fontWeight.bold,
}));
export const AuthorListItem = styled(ListItem)({
'&&': {
padding: 0,
},
'&&:hover': {
padding: 0,
':hover': {
backgroundColor: 'transparent',
},
});
export const AuthorListItemText = styled(ListItemText)({
'&&': {
padding: '0 10px',
margin: 0,
},
padding: '0 10px',
margin: 0,
});

View File

@@ -1,21 +1,29 @@
import React, { KeyboardEvent } from 'react';
import { css } from 'emotion';
import React, { KeyboardEvent, memo } from 'react';
import styled from '@emotion/styled';
import Autosuggest, { SuggestionSelectedEventData, InputProps, ChangeEvent } from 'react-autosuggest';
import match from 'autosuggest-highlight/match';
import parse from 'autosuggest-highlight/parse';
import MenuItem from '@material-ui/core/MenuItem';
import { useTranslation } from 'react-i18next';
import { fontWeight } from '../../utils/styles/sizes';
import MenuItem from '../../muiComponents/MenuItem';
import { Theme } from '../../design-tokens/theme';
import { Wrapper, InputField, SuggestionContainer } from './styles';
const StyledAnchor = styled('a')<{ highlight: boolean; theme?: Theme }>(props => ({
fontWeight: props.theme && props.highlight ? props.theme.fontWeight.semiBold : props.theme.fontWeight.light,
}));
const StyledMenuItem = styled(MenuItem)({
cursor: 'pointer',
});
interface Props {
suggestions: unknown[];
suggestionsLoading?: boolean;
suggestionsLoaded?: boolean;
suggestionsError?: boolean;
apiLoading?: boolean;
color?: string;
value?: string;
placeholder?: string;
startAdornment?: JSX.Element;
@@ -54,23 +62,17 @@ const renderSuggestion = (suggestion, { query, isHighlighted }): JSX.Element =>
const matches = match(suggestion.name, query);
const parts = parse(suggestion.name, matches);
return (
<MenuItem component="div" selected={isHighlighted}>
<StyledMenuItem component="div" selected={isHighlighted}>
<div>
{parts.map((part, index) => {
const fw = part.highlight ? fontWeight.semiBold : fontWeight.light;
return (
<a
className={css`
font-weight: ${fw};
`}
href={suggestion.link}
key={String(index)}>
<StyledAnchor highlight={part.highlight} key={String(index)}>
{part.text}
</a>
</StyledAnchor>
);
})}
</div>
</MenuItem>
</StyledMenuItem>
);
};
@@ -82,67 +84,68 @@ const renderMessage = (message): JSX.Element => {
);
};
const SUGGESTIONS_RESPONSE = {
LOADING: 'Loading...',
FAILURE: 'Something went wrong.',
NO_RESULT: 'No results found.',
};
const AutoComplete = ({
suggestions,
startAdornment,
onChange,
onSuggestionsFetch,
onCleanSuggestions,
value = '',
placeholder = '',
disableUnderline = false,
color,
onClick,
onKeyDown,
onBlur,
suggestionsLoading = false,
suggestionsLoaded = false,
suggestionsError = false,
}: Props): JSX.Element => {
const autosuggestProps = {
renderInputComponent,
const AutoComplete = memo(
({
suggestions,
getSuggestionValue,
renderSuggestion,
onSuggestionsFetchRequested: onSuggestionsFetch,
onSuggestionsClearRequested: onCleanSuggestions,
};
const inputProps: InputProps<unknown> = {
value,
onChange,
placeholder,
// material-ui@4.5.1 introduce better types for TextInput, check readme
// @ts-ignore
startAdornment,
disableUnderline,
color,
onChange,
onSuggestionsFetch,
onCleanSuggestions,
value = '',
placeholder = '',
disableUnderline = false,
onClick,
onKeyDown,
onBlur,
};
suggestionsLoading = false,
suggestionsLoaded = false,
suggestionsError = false,
}: Props) => {
const { t } = useTranslation();
const autosuggestProps = {
renderInputComponent,
suggestions,
getSuggestionValue,
renderSuggestion,
onSuggestionsFetchRequested: onSuggestionsFetch,
onSuggestionsClearRequested: onCleanSuggestions,
};
const inputProps: InputProps<unknown> = {
value,
onChange,
placeholder,
// material-ui@4.5.1 introduce better types for TextInput, check readme
// @ts-ignore
startAdornment,
disableUnderline,
onKeyDown,
onBlur,
};
// this format avoid arrow function eslint rule
function renderSuggestionsContainer({ containerProps, children, query }): JSX.Element {
return (
<SuggestionContainer {...containerProps} square={true}>
{suggestionsLoaded && children === null && query && renderMessage(t('auto-complete.no-results-found'))}
{suggestionsLoading && query && renderMessage(t('auto-complete.loading'))}
{suggestionsError && renderMessage(t('error.unspecific'))}
{children}
</SuggestionContainer>
);
}
// this format avoid arrow function eslint rule
function renderSuggestionsContainer({ containerProps, children, query }): JSX.Element {
return (
<SuggestionContainer {...containerProps} square={true}>
{suggestionsLoaded && children === null && query && renderMessage(SUGGESTIONS_RESPONSE.NO_RESULT)}
{suggestionsLoading && query && renderMessage(SUGGESTIONS_RESPONSE.LOADING)}
{suggestionsError && renderMessage(SUGGESTIONS_RESPONSE.FAILURE)}
{children}
</SuggestionContainer>
<Wrapper>
<Autosuggest
{...autosuggestProps}
inputProps={inputProps}
onSuggestionSelected={onClick}
renderSuggestionsContainer={renderSuggestionsContainer}
/>
</Wrapper>
);
}
return (
<Wrapper>
<Autosuggest {...autosuggestProps} inputProps={inputProps} onSuggestionSelected={onClick} renderSuggestionsContainer={renderSuggestionsContainer} />
</Wrapper>
);
};
);
export default AutoComplete;

View File

@@ -1,60 +1,44 @@
import React from 'react';
import styled, { css } from 'react-emotion';
import Paper from '@material-ui/core/Paper';
import styled from '@emotion/styled';
import TextField from '../../muiComponents/TextField';
import Paper from '../../muiComponents/Paper';
import { Theme } from '../../design-tokens/theme';
export interface InputFieldProps {
color: string;
}
export const Wrapper = styled('div')({
'&&': {
width: '100%',
height: '32px',
position: 'relative',
zIndex: 1,
},
width: '100%',
height: '32px',
position: 'relative',
zIndex: 1,
});
export const StyledTextField = styled(TextField)<{ theme?: Theme }>(props => ({
'& .MuiInputBase-root': {
':before': {
content: "''",
border: 'none',
},
':after': {
borderColor: props.theme && props.theme.palette.white,
},
':hover:before': {
content: 'none',
},
},
'& .MuiInputBase-input': {
color: props.theme && props.theme.palette.white,
},
}));
/* eslint-disable verdaccio/jsx-spread */
export const InputField: React.FC<InputFieldProps> = ({ color, ...others }) => (
<TextField
{...others}
classes={{
// @ts-ignore
input: css`
&& {
${color &&
css`
color: ${color};
`};
}
`,
root: css`
&& {
&:before {
content: '';
border: none;
}
&:after {
${color &&
css`
border-color: ${color};
`};
}
&:hover:before {
content: none;
}
}
`,
}}
/>
);
// @ts-ignore types of color are incompatible
export const InputField: React.FC<InputFieldProps> = ({ ...others }) => <StyledTextField {...others} />;
export const SuggestionContainer = styled(Paper)({
'&&': {
maxHeight: '500px',
overflowY: 'auto',
},
maxHeight: '500px',
overflowY: 'auto',
});

View File

@@ -1,32 +0,0 @@
import React, { FC } from 'react';
import { isEmail } from '../../utils/url';
import Tooltip from '../../muiComponents/Tooltip';
import Avatar from '../../muiComponents/Avatar';
export interface AvatarDeveloper {
name: string;
packageName: string;
version: string;
avatar: string;
email: string;
}
const AvatarTooltip: FC<AvatarDeveloper> = ({ name, packageName, version, avatar, email }) => {
const avatarComponent = <Avatar aria-label={name} src={avatar} />;
function renderLinkForMail(email: string, avatarComponent: JSX.Element, packageName: string, version: string): JSX.Element {
if (!email || isEmail(email) === false) {
return avatarComponent;
}
return (
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
{avatarComponent}
</a>
);
}
return <Tooltip title={name}>{renderLinkForMail(email, avatarComponent, packageName, version)}</Tooltip>;
};
export { AvatarTooltip };

View File

@@ -1,4 +0,0 @@
import { AvatarTooltip } from './AvatarTooltip';
export { AvatarTooltip };
export default AvatarTooltip;

View File

@@ -1,7 +1,8 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { ReactWrapper } from 'enzyme';
import { copyToClipBoardUtility } from '../../utils/cli-utils';
import { mount } from '../../utils/test-enzyme';
import CopyToClipBoard from './CopyToClipBoard';
import { CopyIcon } from './styles';
@@ -16,7 +17,7 @@ describe('<CopyToClipBoard /> component', () => {
wrapper = mount(<CopyToClipBoard text={copyText} />);
});
test('render the component', () => {
test('should load the component in default state', () => {
expect(wrapper.html()).toMatchSnapshot();
});

View File

@@ -1,8 +1,8 @@
import FileCopy from '@material-ui/icons/FileCopy';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { copyToClipBoardUtility } from '../../utils/cli-utils';
import { TEXT } from '../../utils/constants';
import Tooltip from '../../muiComponents/Tooltip';
import { ClipBoardCopy, ClipBoardCopyText, CopyIcon } from './styles';
@@ -20,19 +20,16 @@ const renderText = (text: string, children: React.ReactNode): JSX.Element => {
return <ClipBoardCopyText>{text}</ClipBoardCopyText>;
};
const renderToolTipFileCopy = (text: string): React.ReactElement<HTMLElement> => (
<Tooltip disableFocusListener={true} title={TEXT.CLIPBOARD_COPY}>
<CopyIcon onClick={copyToClipBoardUtility(text)}>
<FileCopy />
</CopyIcon>
</Tooltip>
);
const CopyToClipBoard: React.FC<Props> = ({ text, children }) => {
const { t } = useTranslation();
return (
<ClipBoardCopy>
{renderText(text, children)}
{renderToolTipFileCopy(text)}
<Tooltip disableFocusListener={true} title={t('copy-to-clipboard')}>
<CopyIcon onClick={copyToClipBoardUtility(text)}>
<FileCopy />
</CopyIcon>
</Tooltip>
</ClipBoardCopy>
);
};

View File

@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<CopyToClipBoard /> component render the component 1`] = `"<div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">copy text</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div>"`;
exports[`<CopyToClipBoard /> component should load the component in default state 1`] = `"<div class=\\"css-1in239f-ClipBoardCopy eb8w2fo0\\"><span class=\\"css-7gar9h-ClipBoardCopyText eb8w2fo1\\">copy text</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-1fs86cq-CopyIcon eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"copy-to-clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div>"`;

View File

@@ -1,24 +1,20 @@
import styled from 'react-emotion';
import styled from '@emotion/styled';
import IconButton from '../../muiComponents/IconButton';
export const ClipBoardCopy = styled('div')({
'&&': {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
},
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
});
export const ClipBoardCopyText = styled('span')({
'&&': {
display: 'inline-block',
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
height: '21px',
fontSize: '1rem',
},
display: 'inline-block',
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
height: '21px',
fontSize: '1rem',
});
export const CopyIcon = styled(IconButton)({});

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { render } from '@testing-library/react';
import { HashRouter } from 'react-router-dom';
import { DetailContextProvider } from '../../pages/Version';
import { render } from '../../utils/test-react-testing-library';
import Dependencies from './Dependencies';

View File

@@ -1,7 +1,8 @@
import React, { useContext } from 'react';
import { useHistory } from 'react-router-dom';
import CardContent from '@material-ui/core/CardContent';
import { useTranslation } from 'react-i18next';
import CardContent from '../../muiComponents/CardContent';
import { PackageDependencies } from '../../../types/packageMeta';
import { DetailContext } from '../../pages/Version';
import NoItems from '../NoItems';
@@ -16,6 +17,7 @@ interface DependencyBlockProps {
const DependencyBlock: React.FC<DependencyBlockProps> = ({ title, dependencies }) => {
const { enableLoading } = useContext(DetailContext);
const history = useHistory();
const { t } = useTranslation();
const deps = Object.entries(dependencies);
@@ -31,8 +33,14 @@ const DependencyBlock: React.FC<DependencyBlockProps> = ({ title, dependencies }
<StyledText variant="subtitle1">{`${title} (${deps.length})`}</StyledText>
<Tags>
{deps.map(([name, version]) => (
// eslint-disable-next-line
<Tag className={'dep-tag'} clickable={true} key={name} label={`${name}@${version}`} onClick={() => handleClick(name)} />
<Tag
className={'dep-tag'}
clickable={true}
key={name}
label={t('dependencies.dependency-block', { package: name, version })}
// eslint-disable-next-line
onClick={() => handleClick(name)}
/>
))}
</Tags>
</CardContent>
@@ -46,9 +54,10 @@ function hasKeys(object?: { [key: string]: any }): boolean {
const Dependencies: React.FC<{}> = () => {
const { packageMeta } = useContext(DetailContext);
const { t } = useTranslation();
if (!packageMeta) {
throw new Error('packageMeta is required at DetailContext');
throw new Error(t('error.package-meta-is-required-at-detail-context'));
}
const { latest } = packageMeta;
@@ -72,7 +81,7 @@ const Dependencies: React.FC<{}> = () => {
);
}
return <NoItems className="no-dependencies" text={`${name} has no dependencies.`} />;
return <NoItems className="no-dependencies" text={t('dependencies.has-no-dependencies', { package: name })} />;
};
export default Dependencies;

View File

@@ -1,32 +1,26 @@
import styled from 'react-emotion';
import Chip from '@material-ui/core/Chip';
import styled from '@emotion/styled';
import { fontWeight } from '../../utils/styles/sizes';
import Text from '../../muiComponents/Text';
import Card from '../../muiComponents/Card';
import Chip from '../../muiComponents/Chip';
import { Theme } from '../../design-tokens/theme';
export const CardWrap = styled(Card)({
'&&': {
margin: '0 0 16px',
},
margin: '0 0 16px',
});
export const StyledText = styled(Text)({
fontWeight: fontWeight.bold,
export const StyledText = styled(Text)<{ theme?: Theme }>(props => ({
fontWeight: props.theme && props.theme.fontWeight.bold,
textTransform: 'capitalize',
});
}));
export const Tags = styled('div')({
'&&': {
display: 'flex',
justifyContent: 'start',
flexWrap: 'wrap',
margin: '0 -5px',
},
display: 'flex',
justifyContent: 'start',
flexWrap: 'wrap',
margin: '0 -5px',
});
export const Tag = styled(Chip)({
'&&': {
margin: '5px',
},
margin: '5px',
});

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { render } from '@testing-library/react';
import { render } from '../../utils/test-react-testing-library';
import DetailContainer from './DetailContainer';

View File

@@ -1,31 +1,26 @@
import React, { useCallback, useState, ChangeEvent, useContext } from 'react';
import Box from '@material-ui/core/Box';
import React, { useState, useContext } from 'react';
import { DetailContext } from '../../pages/Version';
import Box from '../../muiComponents/Box';
import DetailContainerTabs from './DetailContainerTabs';
import DetailContainerContent from './DetailContainerContent';
import { TabPosition } from './tabs';
const DetailContainer: React.FC = () => {
const [tabPosition, setTabPosition] = useState(TabPosition.README);
const tabs = Object.values(TabPosition);
const [tabPosition, setTabPosition] = useState(0);
const detailContext = useContext(DetailContext);
const { readMe } = detailContext;
const handleChangeTabPosition = useCallback(
(event: ChangeEvent<{}>) => {
event.preventDefault();
const eventTarget = event.target as HTMLSpanElement;
const chosentab = eventTarget.innerText as TabPosition;
setTabPosition(TabPosition[chosentab]);
},
[setTabPosition]
);
const handleChange = (event, newValue) => {
setTabPosition(newValue);
};
return (
<Box component="div" display="flex" flexDirection="column" padding={2}>
<DetailContainerTabs onChangeTabPosition={handleChangeTabPosition} tabPosition={tabPosition} />
<DetailContainerContent readDescription={readMe} tabPosition={tabPosition} />
<DetailContainerTabs onChange={handleChange} tabPosition={tabPosition} />
<DetailContainerContent readDescription={readMe} tabPosition={tabs[tabPosition]} />
</Box>
);
};

View File

@@ -1,37 +1,31 @@
import React, { ChangeEvent, useState, useEffect } from 'react';
import { default as MuiTabs } from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import styled from 'react-emotion';
import React from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import { TabPosition } from './tabs';
import { default as MuiTabs } from '../../muiComponents/Tabs';
import Tab from '../../muiComponents/Tab';
import { Theme } from '../../design-tokens/theme';
interface Props {
tabPosition: TabPosition;
onChangeTabPosition: (event: ChangeEvent<{}>) => void;
onChange: (event, newValue) => void;
tabPosition: number;
}
const Tabs = styled(MuiTabs)({
marginBottom: 16,
});
const getTabIndex = (tabPosition: TabPosition): number => Object.keys(TabPosition).findIndex(position => position === String(tabPosition).toUpperCase());
const DetailContainerTabs: React.FC<Props> = ({ tabPosition, onChangeTabPosition }) => {
const [tabPositionIndex, setTabPositionIndex] = useState(0);
useEffect(() => {
const tabIndex = getTabIndex(tabPosition);
setTabPositionIndex(tabIndex);
}, [tabPosition]);
const DetailContainerTabs: React.FC<Props> = ({ tabPosition, onChange }) => {
const { t } = useTranslation();
return (
<Tabs indicatorColor={'primary'} onChange={onChangeTabPosition} textColor={'primary'} value={tabPositionIndex} variant={'fullWidth'}>
<Tab data-testid={'readme-tab'} id={'readme-tab'} label={TabPosition.README} />
<Tab data-testid={'dependencies-tab'} id={'dependencies-tab'} label={TabPosition.DEPENDENCIES} />
<Tab data-testid={'versions-tab'} id={'versions-tab'} label={TabPosition.VERSIONS} />
<Tab data-testid={'uplinks-tab'} id={'uplinks-tab'} label={TabPosition.UPLINKS} />
<Tabs color={'primary'} indicatorColor={'primary'} onChange={onChange} value={tabPosition} variant={'fullWidth'}>
<Tab data-testid={'readme-tab'} id={'readme-tab'} label={t('tab.readme')} />
<Tab data-testid={'dependencies-tab'} id={'dependencies-tab'} label={t('tab.dependencies')} />
<Tab data-testid={'versions-tab'} id={'versions-tab'} label={t('tab.versions')} />
<Tab data-testid={'uplinks-tab'} id={'uplinks-tab'} label={t('tab.uplinks')} />
</Tabs>
);
};
export default DetailContainerTabs;
const Tabs = styled(MuiTabs)<{ theme?: Theme }>({
marginBottom: 16,
});

View File

@@ -1,11 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DetailContainer renders correctly 1`] = `
.emotion-0 {
margin-bottom: 16px;
}
<div
class="MuiBox-root MuiBox-root-2"
>
<div
class="MuiTabs-root css-1qm1lh emotion-0"
class="MuiTabs-root emotion-0 emotion-1"
color="primary"
>
<div
class="MuiTabs-scroller MuiTabs-fixed"
@@ -17,7 +22,7 @@ exports[`DetailContainer renders correctly 1`] = `
>
<button
aria-selected="true"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary Mui-selected MuiTab-fullWidth"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit Mui-selected MuiTab-fullWidth"
data-testid="readme-tab"
id="readme-tab"
role="tab"
@@ -35,7 +40,7 @@ exports[`DetailContainer renders correctly 1`] = `
</button>
<button
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit MuiTab-fullWidth"
data-testid="dependencies-tab"
id="dependencies-tab"
role="tab"
@@ -53,7 +58,7 @@ exports[`DetailContainer renders correctly 1`] = `
</button>
<button
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit MuiTab-fullWidth"
data-testid="versions-tab"
id="versions-tab"
role="tab"
@@ -71,7 +76,7 @@ exports[`DetailContainer renders correctly 1`] = `
</button>
<button
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit MuiTab-fullWidth"
data-testid="uplinks-tab"
id="uplinks-tab"
role="tab"

View File

@@ -1,6 +1,6 @@
export enum TabPosition {
README = 'Readme',
DEPENDENCIES = 'Dependencies',
VERSIONS = 'Versions',
UPLINKS = 'Uplinks',
README = 'readme',
DEPENDENCIES = 'dependencies',
VERSIONS = 'versions',
UPLINKS = 'uplinks',
}

View File

@@ -1,77 +1,52 @@
import React, { ReactElement } from 'react';
import CardContent from '@material-ui/core/CardContent';
import React, { useContext } from 'react';
import styled from '@emotion/styled';
import { ActionBar } from '../ActionBar/ActionBar';
import Author from '../Author';
import Developers from '../Developers';
import Dist from '../Dist/Dist';
import Engine from '../Engines/Engines';
import Install from '../Install';
import Repository from '../Repository/Repository';
import { DetailContext } from '../../pages/Version';
import List from '../../muiComponents/List';
import Card from '../../muiComponents/Card';
import Paper from '../../muiComponents/Paper';
import ActionBar from '../ActionBar';
import Repository from '../Repository';
import Engines from '../Engines';
import Dist from '../Dist';
import Install from '../Install';
import Author from '../Author';
import Developers, { DeveloperType } from '../Developers';
import { Theme } from '../../design-tokens/theme';
import { TitleListItem, TitleListItemText, PackageDescription, PackageVersion } from './styles';
import DetailSidebarTitle from './DetailSidebarTitle';
import DetailSidebarFundButton from './DetailSidebarFundButton';
const renderLatestDescription = (description, version, isLatest: boolean = true): JSX.Element => {
return (
<>
<PackageDescription>{description}</PackageDescription>
{version ? (
<PackageVersion>
<small>{`${isLatest ? 'Latest v' : 'v'}${version}`}</small>
</PackageVersion>
) : null}
</>
);
};
const DetailSidebar: React.FC = () => {
const detailContext = useContext(DetailContext);
const renderCopyCLI = (): JSX.Element => <Install />;
const renderMaintainers = (): JSX.Element => <Developers type="maintainers" />;
const renderContributors = (): JSX.Element => <Developers type="contributors" />;
const renderRepository = (): JSX.Element => <Repository />;
const renderAuthor = (): JSX.Element => <Author />;
const renderEngine = (): JSX.Element => <Engine />;
const renderDist = (): JSX.Element => <Dist />;
const renderActionBar = (): JSX.Element => <ActionBar />;
const renderTitle = (packageName, packageVersion, packageMeta): JSX.Element => {
const version = packageVersion ? packageVersion : packageMeta.latest.version;
const isLatest = typeof packageVersion === 'undefined';
const { packageMeta, packageName, packageVersion } = detailContext;
if (!packageMeta || !packageName) {
return null;
}
return (
<List className="detail-info">
<TitleListItem alignItems="flex-start" button={true}>
<TitleListItemText primary={<b>{packageName}</b>} secondary={renderLatestDescription(packageMeta.latest.description, version, isLatest)} />
</TitleListItem>
</List>
<StyledPaper className={'sidebar-info'}>
<DetailSidebarTitle
description={packageMeta.latest?.description}
isLatest={typeof packageVersion === 'undefined'}
packageName={packageName}
version={packageVersion || packageMeta.latest.version}
/>
<ActionBar />
<Install />
<DetailSidebarFundButton />
<Repository />
<Engines />
<Dist />
<Author />
<Developers type={DeveloperType.MAINTAINERS} />
<Developers type={DeveloperType.CONTRIBUTORS} />
</StyledPaper>
);
};
function renderSideBar(packageName, packageVersion, packageMeta): ReactElement<HTMLElement> {
return (
<div className={'sidebar-info'}>
<Card>
<CardContent>
{renderTitle(packageName, packageVersion, packageMeta)}
{renderActionBar()}
{renderCopyCLI()}
{renderRepository()}
{renderEngine()}
{renderDist()}
{renderAuthor()}
{renderMaintainers()}
{renderContributors()}
</CardContent>
</Card>
</div>
);
}
const DetailSidebar = (): JSX.Element => {
const { packageName, packageMeta, packageVersion } = React.useContext(DetailContext);
return renderSideBar(packageName, packageVersion, packageMeta);
};
export default DetailSidebar;
const StyledPaper = styled(Paper)<{ theme?: Theme }>(({ theme }) => ({
padding: theme?.spacing(3, 2),
}));

View File

@@ -0,0 +1,103 @@
import React from 'react';
import _ from 'lodash';
import { render } from '../../utils/test-react-testing-library';
import { DetailContext, DetailContextProps } from '../../pages/Version';
import DetailSidebarFundButton from './DetailSidebarFundButton';
const ComponentToBeRendered: React.FC<{ contextValue: DetailContextProps }> = ({ contextValue }) => (
<DetailContext.Provider value={contextValue}>
<DetailSidebarFundButton />
</DetailContext.Provider>
);
const detailContextValue: DetailContextProps = {
packageName: 'foo',
readMe: 'test',
enableLoading: () => {},
isLoading: false,
hasNotBeenFound: false,
packageMeta: {
_uplinks: {},
latest: {
name: '@verdaccio/local-storage',
version: '8.0.1-next.1',
dist: { fileCount: 0, unpackedSize: 0, tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz' },
homepage: 'https://verdaccio.org',
bugs: {
url: 'https://github.com/verdaccio/monorepo/issues',
},
},
},
};
describe('test DetailSidebarFundButton', () => {
test('should not display the button if fund is missing', () => {
const wrapper = render(<ComponentToBeRendered contextValue={detailContextValue} />);
expect(wrapper.queryByText('Fund')).toBeNull();
});
test('should not display the button if url is missing', () => {
const value = _.merge(detailContextValue, {
packageMeta: {
latest: {
funding: {},
},
},
});
const wrapper = render(<ComponentToBeRendered contextValue={value} />);
expect(wrapper.queryByText('Fund')).toBeNull();
});
test('should not display the button if url is not a string', () => {
const value = _.merge(detailContextValue, {
packageMeta: {
latest: {
funding: {
url: null,
},
},
},
});
const wrapper = render(<ComponentToBeRendered contextValue={value} />);
expect(wrapper.queryByText('Fund')).toBeNull();
});
test('should not display the button if url is not an url', () => {
const value = _.merge(detailContextValue, {
packageMeta: {
latest: {
funding: {
url: 'somethign different as url',
},
},
},
});
const wrapper = render(<ComponentToBeRendered contextValue={value} />);
expect(wrapper.queryByText('Fund')).toBeNull();
});
test('should display the button if url is a valid url', () => {
const value = _.merge(detailContextValue, {
packageMeta: {
latest: {
funding: {
url: 'https://opencollective.com/verdaccio',
},
},
},
});
const wrapper = render(<ComponentToBeRendered contextValue={value} />);
expect(wrapper.getByText('Fund')).toBeTruthy();
});
});

View File

@@ -0,0 +1,48 @@
import React, { useContext } from 'react';
import styled from '@emotion/styled';
import Favorite from '@material-ui/icons/Favorite';
import { Trans } from 'react-i18next';
import Button from '../../muiComponents/Button';
import Link from '../Link';
import { isURL } from '../../utils/url';
import { Theme } from '../../design-tokens/theme';
import { DetailContext } from '../../pages/Version';
const StyledLink = styled(Link)<{ theme?: Theme }>(({ theme }) => ({
marginTop: theme && theme.spacing(1),
marginBottom: theme && theme.spacing(1),
textDecoration: 'none',
display: 'block',
}));
const StyledFavoriteIcon = styled(Favorite)<{ theme?: Theme }>(({ theme }) => ({
color: theme && theme.palette.orange,
}));
const StyledFundStrong = styled('strong')({
marginRight: 3,
});
/* eslint-disable react/jsx-no-bind */
const DetailSidebarFundButton: React.FC = () => {
const detailContext = useContext(DetailContext);
const { packageMeta } = detailContext;
const fundingUrl = packageMeta?.latest?.funding?.url as string;
if (!isURL(fundingUrl)) {
return null;
}
return (
<StyledLink external={true} to={fundingUrl}>
<Button color="primary" fullWidth={true} startIcon={<StyledFavoriteIcon />} variant="outlined">
<Trans components={[<StyledFundStrong key="fund" />]} i18nKey="button.fund-this-package" />
</Button>
</StyledLink>
);
};
export default DetailSidebarFundButton;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import Box from '../../muiComponents/Box';
import Heading from '../../muiComponents/Heading';
import { Theme } from '../../design-tokens/theme';
interface Props {
packageName: string;
description?: string;
version: string;
isLatest: boolean;
}
const DetailSidebarTitle: React.FC<Props> = ({ description, packageName, version, isLatest }) => {
const { t } = useTranslation();
return (
<Box className={'detail-info'} display="flex" flexDirection="column" marginBottom="8px">
<StyledHeading>{packageName}</StyledHeading>
{description && <div>{description}</div>}
<StyledBoxVersion>
{isLatest ? t('sidebar.detail.latest-version', { version }) : t('sidebar.detail.version', { version })}
</StyledBoxVersion>
</Box>
);
};
export default DetailSidebarTitle;
const StyledHeading = styled(Heading)({
fontSize: '1rem',
fontWeight: 700,
});
const StyledBoxVersion = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
color: theme && theme.palette.text.secondary,
}));

View File

@@ -1,30 +0,0 @@
import styled from 'react-emotion';
import ListItemText from '@material-ui/core/ListItemText';
import ListItem from '../../muiComponents/ListItem';
export const TitleListItem = styled(ListItem)({
paddingLeft: 0,
paddingRight: 0,
paddingBottom: 0,
});
export const TitleListItemText = styled(ListItemText)({
'&&': {
paddingLeft: 0,
paddingRight: 0,
paddingTop: '8px',
},
});
export const PackageDescription = styled('span')({
'&&': {
display: 'block',
},
});
export const PackageVersion = styled('span')({
'&&': {
display: 'block',
},
});

View File

@@ -1,10 +1,10 @@
import React from 'react';
import { mount } from 'enzyme';
import { mount } from '../../utils/test-enzyme';
import { DetailContextProvider } from '../../pages/Version';
import Developers, { DevelopersType } from './Developers';
import { Fab } from './styles';
import Developers, { Fab } from './Developers';
import { DeveloperType } from './types';
describe('test Developers', () => {
const packageMeta = {
@@ -35,14 +35,13 @@ describe('test Developers', () => {
};
test('should render the component with no items', () => {
const type: DevelopersType = 'maintainers';
const packageMeta = {
latest: {},
};
const wrapper = mount(
// @ts-ignore
<DetailContextProvider value={{ packageMeta }}>
<Developers type={type} />
<Developers type={DeveloperType.MAINTAINERS} />
</DetailContextProvider>
);
@@ -50,11 +49,10 @@ describe('test Developers', () => {
});
test('should render the component for maintainers with items', () => {
const type: DevelopersType = 'maintainers';
const wrapper = mount(
// @ts-ignore
<DetailContextProvider value={{ packageMeta }}>
<Developers type={type} />
<Developers type={DeveloperType.MAINTAINERS} />
</DetailContextProvider>
);
@@ -62,11 +60,10 @@ describe('test Developers', () => {
});
test('should render the component for contributors with items', () => {
const type: DevelopersType = 'contributors';
const wrapper = mount(
// @ts-ignore
<DetailContextProvider value={{ packageMeta }}>
<Developers type={type} />
<Developers type={DeveloperType.CONTRIBUTORS} />
</DetailContextProvider>
);
@@ -74,7 +71,6 @@ describe('test Developers', () => {
});
test('should test onClick the component avatar', () => {
const type: DevelopersType = 'contributors';
const packageMeta = {
latest: {
packageName: 'foo',
@@ -95,7 +91,7 @@ describe('test Developers', () => {
const wrapper = mount(
// @ts-ignore
<DetailContextProvider value={{ packageMeta }}>
<Developers type={type} visibleMax={1} />
<Developers type={DeveloperType.CONTRIBUTORS} visibleMax={1} />
</DetailContextProvider>
);

View File

@@ -1,60 +1,81 @@
import React, { FC, Fragment } from 'react';
import React, { useState, useCallback, useContext, useEffect, useMemo } from 'react';
import Add from '@material-ui/icons/Add';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import { DetailContext } from '../../pages/Version';
import { AvatarTooltip } from '../AvatarTooltip';
import Tooltip from '../../muiComponents/Tooltip';
import Avatar from '../../muiComponents/Avatar';
import Box from '../../muiComponents/Box';
import FloatingActionButton from '../../muiComponents/FloatingActionButton';
import { Theme } from '../../design-tokens/theme';
import { Details, StyledText, Content, Fab } from './styles';
import getUniqueDeveloperValues from './get-unique-developer-values';
import DevelopersTitle from './DevelopersTitle';
import { DeveloperType } from './types';
export type DevelopersType = 'contributors' | 'maintainers';
export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(props => ({
backgroundColor: props.theme && props.theme.palette.primary.main,
color: props.theme && props.theme.palette.white,
}));
interface Props {
type: DevelopersType;
type: DeveloperType;
visibleMax?: number;
}
const StyledBox = styled(Box)({
'> *': {
margin: 5,
},
});
export const VISIBLE_MAX = 6;
const Developers: FC<Props> = ({ type, visibleMax }) => {
const [visibleDevs, setVisibleDevs] = React.useState<number>(visibleMax || VISIBLE_MAX);
const { packageMeta } = React.useContext(DetailContext);
const Developers: React.FC<Props> = ({ type, visibleMax = VISIBLE_MAX }) => {
const detailContext = useContext(DetailContext);
const { t } = useTranslation();
const handleLoadMore = (): void => {
setVisibleDevs(visibleDevs + VISIBLE_MAX);
};
const renderDeveloperDetails = ({ name, avatar, email }, packageMeta): JSX.Element => {
const { name: packageName, version } = packageMeta.latest;
return <AvatarTooltip avatar={avatar} email={email} name={name} packageName={packageName} version={version} />;
};
const renderDevelopers = (developers, packageMeta): JSX.Element => {
const listVisibleDevelopers = developers.slice(0, visibleDevs);
return (
<Fragment>
<StyledText variant={'subtitle1'}>{type}</StyledText>
<Content>
{listVisibleDevelopers.map(developer => (
<Details key={developer.email}>{renderDeveloperDetails(developer, packageMeta)}</Details>
))}
{visibleDevs < developers.length && (
<Fab onClick={handleLoadMore} size="small">
<Add />
</Fab>
)}
</Content>
</Fragment>
);
};
const developerList = packageMeta && packageMeta.latest[type];
if (!developerList || developerList.length === 0) {
return null;
if (!detailContext) {
throw Error(t('app-context-not-correct-used'));
}
return renderDevelopers(developerList, packageMeta);
const developers = useMemo(() => getUniqueDeveloperValues(detailContext.packageMeta?.latest[type]), [
detailContext.packageMeta,
type,
]);
const [visibleDevelopersMax, setVisibleDevelopersMax] = useState(visibleMax);
const [visibleDevelopers, setVisibleDevelopers] = useState(developers);
useEffect(() => {
if (!developers) return;
setVisibleDevelopers(developers.slice(0, visibleDevelopersMax));
}, [developers, visibleDevelopersMax]);
const handleSetVisibleDevelopersMax = useCallback(() => {
setVisibleDevelopersMax(visibleDevelopersMax + VISIBLE_MAX);
}, [visibleDevelopersMax]);
if (!visibleDevelopers || !developers) return null;
return (
<>
<DevelopersTitle type={type} />
<StyledBox display="flex" flexWrap="wrap" margin="10px 0 10px 0">
{visibleDevelopers.map(visibleDeveloper => (
<Tooltip key={visibleDeveloper.email} title={visibleDeveloper.name}>
<Avatar alt={visibleDeveloper.name} src={visibleDeveloper.avatar} />
</Tooltip>
))}
{visibleDevelopersMax < developers.length && (
<Fab onClick={handleSetVisibleDevelopersMax} size="small">
<Add />
</Fab>
)}
</StyledBox>
</>
);
};
export default Developers;

View File

@@ -0,0 +1,30 @@
import React from 'react';
import styled from '@emotion/styled';
import { useTranslation } from 'react-i18next';
import Text from '../../muiComponents/Text';
import { Theme } from '../../design-tokens/theme';
import { DeveloperType } from './types';
interface Props {
type: DeveloperType;
}
const DevelopersTitle: React.FC<Props> = ({ type }) => {
const { t } = useTranslation();
switch (type) {
case DeveloperType.CONTRIBUTORS:
return <StyledText variant={'subtitle1'}>{t('sidebar.contributors.title')}</StyledText>;
case DeveloperType.MAINTAINERS:
return <StyledText variant={'subtitle1'}>{t('sidebar.maintainers.title')}</StyledText>;
return null;
}
};
export default DevelopersTitle;
const StyledText = styled(Text)<{ theme?: Theme }>(({ theme }) => ({
fontWeight: theme && theme.fontWeight.bold,
marginBottom: '10px',
}));

View File

@@ -0,0 +1,12 @@
import { Developer } from '../../../types/packageMeta';
function getUniqueDeveloperValues(developers?: Array<Developer>): undefined | Array<Developer> {
if (!developers) return;
return developers.reduce(
(accumulator: Array<Developer>, current: Developer) =>
accumulator.some(developer => developer.email === current.email) ? accumulator : [...accumulator, current],
[]
);
}
export default getUniqueDeveloperValues;

View File

@@ -1 +1,2 @@
export { default } from './Developers';
export { DeveloperType } from './types';

View File

@@ -1,9 +1,8 @@
import styled from 'react-emotion';
import { default as MuiFab } from '@material-ui/core/Fab';
import styled from '@emotion/styled';
import colors from '../../utils/styles/colors';
import { fontWeight } from '../../utils/styles/sizes';
import Text from '../../muiComponents/Text';
import FloatingActionButton from '../../muiComponents/FloatingActionButton';
import { Theme } from '../../design-tokens/theme';
export const Details = styled('span')({
display: 'flex',
@@ -20,15 +19,13 @@ export const Content = styled('div')({
},
});
export const StyledText = styled(Text)({
fontWeight: fontWeight.bold,
export const StyledText = styled(Text)<{ theme?: Theme }>(props => ({
fontWeight: props.theme && props.theme.fontWeight.bold,
marginBottom: '10px',
textTransform: 'capitalize',
});
}));
export const Fab = styled(MuiFab)({
'&&': {
backgroundColor: colors.primary,
color: colors.white,
},
});
export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(props => ({
backgroundColor: props.theme && props.theme.palette.primary.main,
color: props.theme && props.theme.palette.white,
}));

View File

@@ -0,0 +1,4 @@
export enum DeveloperType {
CONTRIBUTORS = 'contributors',
MAINTAINERS = 'maintainers',
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import { mount } from '../../utils/test-enzyme';
import { DetailContext } from '../../pages/Version';
import Dist from './Dist';

View File

@@ -1,4 +1,5 @@
import React, { FC, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { DetailContext } from '../../pages/Version';
import fileSizeSI from '../../utils/file-size';
@@ -10,8 +11,6 @@ import { StyledText, DistListItem, DistChips } from './styles';
const DistChip: FC<{ name: string }> = ({ name, children }) =>
children ? (
<DistChips
// lint rule conflicting with prettier
/* eslint-disable react/jsx-wrap-multilines */
label={
<>
<b>{name}</b>
@@ -19,12 +18,12 @@ const DistChip: FC<{ name: string }> = ({ name, children }) =>
{children}
</>
}
/* eslint-enable */
/>
) : null;
const Dist: FC = () => {
const { packageMeta } = useContext(DetailContext);
const { t } = useTranslation();
if (!packageMeta) {
return null;
@@ -33,11 +32,11 @@ const Dist: FC = () => {
const { dist, license } = packageMeta && packageMeta.latest;
return (
<List subheader={<StyledText variant="subtitle1">{'Latest Distribution'}</StyledText>}>
<List subheader={<StyledText variant="subtitle1">{t('sidebar.distribution.title')}</StyledText>}>
<DistListItem button={true}>
<DistChip name="file count">{dist.fileCount}</DistChip>
<DistChip name="size">{dist.unpackedSize && fileSizeSI(dist.unpackedSize)}</DistChip>
<DistChip name="license">{formatLicense(license)}</DistChip>
<DistChip name={t('sidebar.distribution.file-count')}>{dist.fileCount}</DistChip>
<DistChip name={t('sidebar.distribution.size')}>{dist.unpackedSize && fileSizeSI(dist.unpackedSize)}</DistChip>
<DistChip name={t('sidebar.distribution.license')}>{formatLicense(license)}</DistChip>
</DistListItem>
</List>
);

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Dist /> component should render the component in default state 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-b8upko estxrtg0 MuiTypography-subtitle1\\">Latest Distribution</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-z8a2h0 estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-1le6jk6 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>file count</b>: 7</span></div><div class=\\"MuiChip-root css-1le6jk6 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>size</b>: 10.00 Bytes</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Dist /> component should render the component in default state 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText estxrtg0 MuiTypography-subtitle1\\">sidebar.distribution.title</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1mms18p-DistListItem estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.file-count</b>: 7</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.size</b>: 10.00 Bytes</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Dist /> component should render the component with license as object 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-b8upko estxrtg0 MuiTypography-subtitle1\\">Latest Distribution</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-z8a2h0 estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-1le6jk6 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>file count</b>: 7</span></div><div class=\\"MuiChip-root css-1le6jk6 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>size</b>: 10.00 Bytes</span></div><div class=\\"MuiChip-root css-1le6jk6 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>license</b>: MIT</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Dist /> component should render the component with license as object 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText estxrtg0 MuiTypography-subtitle1\\">sidebar.distribution.title</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1mms18p-DistListItem estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.file-count</b>: 7</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.size</b>: 10.00 Bytes</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.license</b>: MIT</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Dist /> component should render the component with license as string 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-b8upko estxrtg0 MuiTypography-subtitle1\\">Latest Distribution</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-z8a2h0 estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-1le6jk6 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>file count</b>: 7</span></div><div class=\\"MuiChip-root css-1le6jk6 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>size</b>: 10.00 Bytes</span></div><div class=\\"MuiChip-root css-1le6jk6 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>license</b>: MIT</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
exports[`<Dist /> component should render the component with license as string 1`] = `"<ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText estxrtg0 MuiTypography-subtitle1\\">sidebar.distribution.title</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-1mms18p-DistListItem estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.file-count</b>: 7</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.size</b>: 10.00 Bytes</span></div><div class=\\"MuiChip-root css-e2le7v-DistChips estxrtg2\\"><span class=\\"MuiChip-label\\"><b>sidebar.distribution.license</b>: MIT</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;

View File

@@ -1,34 +1,27 @@
import styled from 'react-emotion';
import { default as MuiFab } from '@material-ui/core/Fab';
import Chip from '@material-ui/core/Chip';
import styled from '@emotion/styled';
import colors from '../../utils/styles/colors';
import { fontWeight } from '../../utils/styles/sizes';
import ListItem from '../../muiComponents/ListItem';
import Text from '../../muiComponents/Text';
import FloatingActionButton from '../../muiComponents/FloatingActionButton';
import Chip from '../../muiComponents/Chip';
import { Theme } from '../../design-tokens/theme';
export const StyledText = styled(Text)({
fontWeight: fontWeight.bold,
export const StyledText = styled(Text)<{ theme?: Theme }>(props => ({
fontWeight: props.theme && props.theme.fontWeight.bold,
textTransform: 'capitalize',
});
}));
export const DistListItem = styled(ListItem)({
'&&': {
paddingLeft: 0,
paddingRight: 0,
},
paddingLeft: 0,
paddingRight: 0,
});
export const DistChips = styled(Chip)({
'&&': {
marginRight: '5px',
textTransform: 'capitalize',
},
marginRight: 5,
textTransform: 'capitalize',
});
export const DownloadButton = styled(MuiFab)({
'&&': {
backgroundColor: colors.primary,
color: colors.white,
},
});
export const DownloadButton = styled(FloatingActionButton)<{ theme?: Theme }>(props => ({
backgroundColor: props.theme && props.theme.palette.primary.main,
color: props.theme && props.theme.palette.white,
}));

View File

@@ -1,71 +1,61 @@
import React from 'react';
import { mount } from 'enzyme';
import { mount } from '../../utils/test-enzyme';
import { DetailContext } from '../../pages/Version';
import { PackageMetaInterface } from '../../../types/packageMeta';
import Engine from './Engines';
jest.mock('./img/node.png', () => '');
jest.mock('../Install/img/npm.svg', () => '');
const mockPackageMeta: jest.Mock = jest.fn(() => ({
const mockPackageMeta = (engines?: PackageMetaInterface['latest']['engines']): PackageMetaInterface => ({
latest: {
homepage: 'https://verdaccio.tld',
bugs: {
url: 'https://verdaccio.tld/bugs',
},
name: 'verdaccio',
version: '0.0.0',
dist: {
tarball: 'https://verdaccio.tld/download',
fileCount: 1,
unpackedSize: 1,
},
...(engines && { engines }),
},
}));
jest.mock('../../pages/Version', () => ({
DetailContextConsumer: component => {
return component.children({ packageMeta: mockPackageMeta() });
},
}));
_uplinks: {},
});
describe('<Engines /> component', () => {
beforeEach(() => {
jest.resetAllMocks();
});
test('should render the component in default state', () => {
const packageMeta = {
latest: {
engines: {
node: '>= 0.1.98',
npm: '>3',
},
},
};
const packageMeta = mockPackageMeta({
node: '>= 0.1.98',
npm: '>3',
});
mockPackageMeta.mockImplementation(() => packageMeta);
const wrapper = mount(<Engine />);
const wrapper = mount(
<DetailContext.Provider value={{ packageMeta }}>
<Engine />
</DetailContext.Provider>
);
expect(wrapper.html()).toMatchSnapshot();
});
test('should render the component when there is no engine key in package meta', () => {
const packageMeta = {
latest: {},
};
const packageMeta = mockPackageMeta();
mockPackageMeta.mockImplementation(() => packageMeta);
const wrapper = mount(<Engine />);
expect(wrapper.html()).toEqual('');
const wrapper = mount(
<DetailContext.Provider value={{ packageMeta }}>
<Engine />
</DetailContext.Provider>
);
expect(wrapper.html()).toBeNull();
});
test('should render the component when there is no keys in engine in package meta', () => {
const packageMeta = {
latest: {
engines: {},
},
};
const packageMeta = mockPackageMeta({});
mockPackageMeta.mockImplementation(() => packageMeta);
const wrapper = mount(<Engine />);
expect(wrapper.html()).toEqual('');
const wrapper = mount(
<DetailContext.Provider value={{ packageMeta }}>
<Engine />
</DetailContext.Provider>
);
expect(wrapper.html()).toBeNull();
});
});

View File

@@ -1,73 +1,51 @@
import React, { Component, ReactElement } from 'react';
import Grid from '@material-ui/core/Grid';
import ListItemText from '@material-ui/core/ListItemText';
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { VersionPageConsumerProps, DetailContextConsumer } from '../../pages/Version';
import { DetailContext } from '../../pages/Version';
import Avatar from '../../muiComponents/Avatar';
import List from '../../muiComponents/List';
import npm from '../Install/img/npm.svg';
import ListItemText from '../../muiComponents/ListItemText';
import Grid from '../../muiComponents/Grid';
import { StyledText, EngineListItem } from './styles';
// @ts-ignore
import node from './img/node.png';
const ICONS = {
'node-JS': <Avatar src={node} />,
'NPM-version': <Avatar src={npm} />,
};
const Engine: React.FC = () => {
const { packageMeta } = useContext(DetailContext);
const { t } = useTranslation();
class Engine extends Component {
public render(): ReactElement<HTMLElement> {
return (
<DetailContextConsumer>
{context => {
return this.renderEngine(context as VersionPageConsumerProps);
}}
</DetailContextConsumer>
);
const engines = packageMeta?.latest?.engines;
if (!engines || (!engines.node && !engines.npm)) {
return null;
}
private renderEngine = ({ packageMeta }): ReactElement<HTMLElement> | null => {
const { engines } = packageMeta.latest;
if (!engines) {
return null;
}
return (
<Grid container={true}>
{engines.node && (
<Grid item={true} xs={6}>
<List subheader={<StyledText variant={'subtitle1'}>{t('sidebar.engines.node-js')}</StyledText>}>
<EngineListItem button={true}>
<Avatar src={node} />
<ListItemText primary={engines.node} />
</EngineListItem>
</List>
</Grid>
)}
const engineDict = {
'node-JS': engines.node,
'NPM-version': engines.npm,
};
const accumulator: React.ReactNode[] = [];
const items = Object.keys(engineDict).reduce((markup, text, key) => {
const heading = engineDict[text];
if (heading) {
markup.push(
<Grid item={true} key={key} xs={6}>
{this.renderListItems(heading, text)}
</Grid>
);
}
return markup;
}, accumulator);
if (items.length < 1) {
return null;
}
return <Grid container={true}>{items}</Grid>;
};
private renderListItems = (heading: string, text: string) => {
return (
<List subheader={<StyledText variant={'subtitle1'}>{text.split('-').join(' ')}</StyledText>}>
<EngineListItem button={true}>
{ICONS[text]}
<ListItemText primary={heading} />
</EngineListItem>
</List>
);
};
}
{engines.npm && (
<Grid item={true} xs={6}>
<List subheader={<StyledText variant={'subtitle1'}>{t('sidebar.engines.npm-version')}</StyledText>}>
<EngineListItem button={true}>
<Avatar src={npm} />
<ListItemText primary={engines.npm} />
</EngineListItem>
</List>
</Grid>
)}
</Grid>
);
};
export default Engine;

View File

@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Engines /> component should render the component in default state 1`] = `"<div class=\\"MuiGrid-root MuiGrid-container\\"><div class=\\"MuiGrid-root MuiGrid-item MuiGrid-grid-xs-6\\"><ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-b8upko et66bt70 MuiTypography-subtitle1\\">node JS</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-131yq1t et66bt71 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-colorDefault\\"></div><div class=\\"MuiListItemText-root\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">&gt;= 0.1.98</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul></div><div class=\\"MuiGrid-root MuiGrid-item MuiGrid-grid-xs-6\\"><ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-b8upko et66bt70 MuiTypography-subtitle1\\">NPM version</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-131yq1t et66bt71 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-colorDefault\\"></div><div class=\\"MuiListItemText-root\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">&gt;3</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul></div></div>"`;
exports[`<Engines /> component should render the component in default state 1`] = `"<div class=\\"MuiGrid-root MuiGrid-container\\"><div class=\\"MuiGrid-root MuiGrid-item MuiGrid-grid-xs-6\\"><ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText et66bt70 MuiTypography-subtitle1\\">sidebar.engines.node-js</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-18b06t0-EngineListItem et66bt71 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-circle MuiAvatar-colorDefault\\"><svg class=\\"MuiSvgIcon-root MuiAvatar-fallback\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\\"></path></svg></div><div class=\\"MuiListItemText-root\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">&gt;= 0.1.98</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul></div><div class=\\"MuiGrid-root MuiGrid-item MuiGrid-grid-xs-6\\"><ul class=\\"MuiList-root MuiList-padding MuiList-subheader\\"><h6 class=\\"MuiTypography-root css-1na337r-StyledText et66bt70 MuiTypography-subtitle1\\">sidebar.engines.npm-version</h6><div class=\\"MuiButtonBase-root MuiListItem-root css-18b06t0-EngineListItem et66bt71 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiAvatar-root MuiAvatar-circle MuiAvatar-colorDefault\\"><svg class=\\"MuiSvgIcon-root MuiAvatar-fallback\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\\"></path></svg></div><div class=\\"MuiListItemText-root\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">&gt;3</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul></div></div>"`;

View File

@@ -1,13 +1,13 @@
import styled from 'react-emotion';
import styled from '@emotion/styled';
import { fontWeight } from '../../utils/styles/sizes';
import ListItem from '../../muiComponents/ListItem';
import Text from '../../muiComponents/Text';
import { Theme } from '../../design-tokens/theme';
export const StyledText = styled(Text)({
fontWeight: fontWeight.bold,
export const StyledText = styled(Text)<{ theme?: Theme }>(props => ({
fontWeight: props.theme && props.theme.fontWeight.bold,
textTransform: 'capitalize',
});
}));
export const EngineListItem = styled(ListItem)({
paddingLeft: 0,

View File

@@ -1,21 +1,20 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { render } from '../../utils/test-react-testing-library';
import Footer from './Footer';
jest.mock('../../../package.json', () => ({
version: '4.0.0-alpha.3',
}));
describe('<Footer /> component', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
beforeAll(() => {
window.VERDACCIO_VERSION = 'v.1.0.0';
wrapper = mount(<Footer />);
});
afterAll(() => {
delete window.VERDACCIO_VERSION;
});
test('should load the initial state of Footer component', () => {
expect(wrapper.html()).toMatchSnapshot();
const { container } = render(<Footer />);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -1,53 +1,38 @@
import React from 'react';
import { useTranslation, Trans } from 'react-i18next';
import { goToVerdaccioWebsite } from '../../utils/windows';
import { Wrapper, Left, Right, Earth, Flags, Love, Flag, Logo, Inner, ToolTip } from './styles';
const renderTooltip = (): JSX.Element => (
<ToolTip>
<Earth name="earth" size="md" />
<Flags>
<Flag name="spain" size="md" />
<Flag name="nicaragua" size="md" />
<Flag name="india" size="md" />
<Flag name="brazil" size="md" />
<Flag name="china" size="md" />
<Flag name="austria" size="md" />
</Flags>
</ToolTip>
);
const POWERED_LABEL = 'Powered by';
const MADEWITH_LABEL = ' Made with';
const ON_LABEL = 'on';
const HEARTH_EMOJI = '♥';
const renderRight = (version = window.VERDACCIO_VERSION): JSX.Element => {
/* eslint-disable react/jsx-key */
const Footer: React.FC = () => {
const { t } = useTranslation();
return (
<Right>
{POWERED_LABEL}
<Logo img={true} name="verdaccio" onClick={goToVerdaccioWebsite} pointer={true} size="md" />
{`/ ${version}`}
</Right>
<Wrapper>
<Inner>
<Left>
<Trans components={[<Love />]} i18nKey="footer.made-with-love-on" />
<ToolTip>
<Earth name="earth" size="md" />
<Flags>
<Flag name="spain" size="md" />
<Flag name="nicaragua" size="md" />
<Flag name="india" size="md" />
<Flag name="brazil" size="md" />
<Flag name="china" size="md" />
<Flag name="austria" size="md" />
</Flags>
</ToolTip>
</Left>
<Right>
{t('footer.powered-by')}
<Logo img={true} name="verdaccio" onClick={goToVerdaccioWebsite} pointer={true} size="md" />
{`/ ${window.VERDACCIO_VERSION}`}
</Right>
</Inner>
</Wrapper>
);
};
const renderLeft = (): JSX.Element => (
<Left>
{MADEWITH_LABEL}
<Love>{HEARTH_EMOJI}</Love>
{ON_LABEL}
{renderTooltip()}
</Left>
);
const Footer: React.FC = () => (
<Wrapper>
<Inner>
{renderLeft()}
{renderRight()}
</Inner>
</Wrapper>
);
export default Footer;

View File

@@ -1,3 +1,274 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Footer /> component should load the initial state of Footer component 1`] = `"<div class=\\"css-i0nj2g ezbsl480\\"><div class=\\"css-hzfs9b ezbsl481\\"><div class=\\"css-d8nsp7 ezbsl482\\"> Made with<span class=\\"css-1so4oe0 ezbsl487\\">♥</span>on<span class=\\"css-1ie354y ezbsl484\\"><svg class=\\"ezbsl485 css-tsfgle ek145dl0\\"><title>Earth</title><use xlink:href=\\"[object Object]#earth\\"></use></svg><span class=\\"css-8631ip ezbsl486\\"><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Spain</title><use xlink:href=\\"[object Object]#spain\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Nicaragua</title><use xlink:href=\\"[object Object]#nicaragua\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>India</title><use xlink:href=\\"[object Object]#india\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Brazil</title><use xlink:href=\\"[object Object]#brazil\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>China</title><use xlink:href=\\"[object Object]#china\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg></span></span></div><div class=\\"css-1wbzdyy ezbsl483\\">Powered by<span class=\\"ezbsl488 css-i15wza ek145dl1\\" name=\\"verdaccio\\" title=\\"Verdaccio\\"><img alt=\\"Verdaccio\\" src=\\"[object Object]\\" class=\\"css-1ncdhax ek145dl2\\"></span>/ v.1.0.0</div></div></div>"`;
exports[`<Footer /> component should load the initial state of Footer component 1`] = `
.emotion-38 {
background: #f9f9f9;
border-top: 1px solid #e3e3e3;
color: #999999;
font-size: 14px;
padding: 20px;
}
.emotion-36 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end;
width: 100%;
}
@media (min-width:768px) {
.emotion-36 {
min-width: 400px;
max-width: 800px;
margin: auto;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
}
@media (min-width:1024px) {
.emotion-36 {
max-width: 1240px;
}
}
.emotion-27 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: none;
}
@media (min-width:768px) {
.emotion-27 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
}
.emotion-0 {
color: #e25555;
padding: 0 5px;
}
.emotion-25 {
position: relative;
height: 18px;
}
.emotion-25:hover .emotion-24 {
visibility: visible;
}
.emotion-3 {
box-sizing: initial;
display: inline-block;
cursor: default;
width: 18px;
height: 18px;
padding: 0 10px;
}
.emotion-23 {
position: absolute;
background: #d3dddd;
padding: 1px 4px;
border-radius: 3px;
height: 20px;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
visibility: hidden;
top: -2px;
}
.emotion-23:before {
content: '';
position: absolute;
top: 29%;
left: -4px;
margin-left: -5px;
border: 5px solid;
border-color: #d3dddd transparent transparent transparent;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.emotion-6 {
box-sizing: initial;
display: inline-block;
cursor: default;
width: 18px;
height: 18px;
padding: 0 5px;
}
.emotion-34 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: none;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
@media (min-width:768px) {
.emotion-34 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
}
.emotion-32 {
box-sizing: initial;
display: inline-block;
cursor: pointer;
width: 18px;
height: 18px;
padding: 0 5px;
}
.emotion-29 {
width: 100%;
height: auto;
}
<div
class="emotion-38 emotion-39"
>
<div
class="emotion-36 emotion-37"
>
<div
class="emotion-27 emotion-28"
>
Made with
<span
class="emotion-0 emotion-1"
>
♥
</span>
on
<span
class="emotion-25 emotion-26"
>
<svg
class="emotion-2 emotion-3 emotion-4"
>
<title>
Earth
</title>
<use
xlink:href="[object Object]#earth"
/>
</svg>
<span
class="emotion-23 emotion-24"
>
<svg
class="emotion-5 emotion-6 emotion-4"
>
<title>
Spain
</title>
<use
xlink:href="[object Object]#spain"
/>
</svg>
<svg
class="emotion-5 emotion-6 emotion-4"
>
<title>
Nicaragua
</title>
<use
xlink:href="[object Object]#nicaragua"
/>
</svg>
<svg
class="emotion-5 emotion-6 emotion-4"
>
<title>
India
</title>
<use
xlink:href="[object Object]#india"
/>
</svg>
<svg
class="emotion-5 emotion-6 emotion-4"
>
<title>
Brazil
</title>
<use
xlink:href="[object Object]#brazil"
/>
</svg>
<svg
class="emotion-5 emotion-6 emotion-4"
>
<title>
China
</title>
<use
xlink:href="[object Object]#china"
/>
</svg>
<svg
class="emotion-5 emotion-6 emotion-4"
>
<title>
Austria
</title>
<use
xlink:href="[object Object]#austria"
/>
</svg>
</span>
</span>
</div>
<div
class="emotion-34 emotion-35"
>
Powered by
<span
class="emotion-5 emotion-32 emotion-33"
title="Verdaccio"
>
<img
alt="Verdaccio"
class="emotion-29 emotion-30"
src="[object Object]"
/>
</span>
/ v.1.0.0
</div>
</div>
</div>
`;

View File

@@ -1,110 +1,87 @@
import styled, { css } from 'react-emotion';
import styled from '@emotion/styled';
import mq from '../../utils/styles/media';
import Icon from '../Icon/Icon';
import colors from '../../utils/styles/colors';
import { Theme } from '../../design-tokens/theme';
export const Wrapper = styled('div')({
'&&': {
background: colors.snow,
borderTop: `1px solid ${colors.greyGainsboro}`,
color: colors.nobel01,
fontSize: '14px',
padding: '20px',
export const Wrapper = styled('div')<{ theme?: Theme }>(({ theme }) => ({
background: theme?.palette.type === 'dark' ? theme?.palette.primary.main : theme?.palette.snow,
borderTop: `1px solid ${theme?.palette.greyGainsboro}`,
color: theme?.palette.type === 'dark' ? theme?.palette.white : theme?.palette.nobel01,
fontSize: '14px',
padding: '20px',
}));
export const Inner = styled('div')<{ theme?: Theme }>(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
width: '100%',
[`@media (min-width: ${theme && theme.breakPoints.medium}px)`]: {
minWidth: 400,
maxWidth: 800,
margin: 'auto',
justifyContent: 'space-between',
},
});
[`@media (min-width: ${theme && theme.breakPoints.large}px)`]: {
maxWidth: 1240,
},
}));
export const Inner = styled('div')`
&& {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
${() => {
return mq.medium(css`
min-width: 400px;
max-width: 800px;
margin: auto;
justify-content: space-between;
`);
}};
${() => {
return mq.large(css`
max-width: 1240px;
`);
}};
}
`;
export const Left = styled('div')`
&& {
align-items: center;
display: none;
${() => {
return mq.medium(css`
display: flex;
`);
}};
}
`;
export const Right = styled(Left)({
'&&': {
export const Left = styled('div')<{ theme?: Theme }>(({ theme }) => ({
alignItems: 'center',
display: 'none',
[`@media (min-width: ${theme && theme.breakPoints.medium}px)`]: {
display: 'flex',
},
});
}));
export const ToolTip = styled('span')({
'&&': {
position: 'relative',
height: '18px',
},
export const Right = styled(Left)({
display: 'flex',
});
export const Earth = styled(Icon)({
'&&': {
padding: '0 10px',
padding: '0 10px',
});
export const Flags = styled('span')<{ theme?: Theme }>(props => ({
position: 'absolute',
background: props.theme && props.theme.palette.greyAthens,
padding: '1px 4px',
borderRadius: 3,
height: 20,
display: 'inline-flex',
alignItems: 'center',
visibility: 'hidden',
top: -2,
':before': {
content: "''",
position: 'absolute',
top: '29%',
left: -4,
marginLeft: -5,
border: '5px solid',
borderColor: `${props.theme && props.theme.palette.greyAthens} transparent transparent transparent`,
transform: 'rotate(90deg)',
},
}));
export const ToolTip = styled('span')({
position: 'relative',
height: '18px',
':hover': {
[`${Flags}`]: {
visibility: 'visible',
},
},
});
export const Flags = styled('span')`
&& {
position: absolute;
background: ${colors.greyAthens};
padding: 1px 4px;
border-radius: 3px;
height: 20px;
display: inline-flex;
align-items: center;
visibility: hidden;
top: -2px;
:before {
content: '';
position: absolute;
top: 29%;
left: -4px;
margin-left: -5px;
border: 5px solid;
border-color: ${colors.greyAthens} transparent transparent transparent;
transform: rotate(90deg);
}
${/* sc-selector */ ToolTip}:hover & {
visibility: visible;
}
}
`;
export const Love = styled('span')({
'&&': {
color: colors.love,
padding: '0 5px',
},
});
export const Love = styled('span')<{ theme?: Theme }>(props => ({
color: props.theme && props.theme.palette.love,
padding: '0 5px',
}));
export const Flag = styled(Icon)({
'&&': {
padding: '0 5px',
},
padding: '0 5px',
});
export const Logo = Flag;

View File

@@ -1,15 +1,17 @@
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { render, fireEvent, waitForElementToBeRemoved, waitForElement } from '@testing-library/react';
import { render, fireEvent, waitForElement, waitForElementToBeRemoved } from '../../utils/test-react-testing-library';
import { AppContextProvider } from '../../App';
import translationEN from '../../../i18n/translations/en-US.json';
import Header from './Header';
const headerProps = {
username: 'verddacio-user',
scope: 'test scope',
withoutSearch: true,
handleToggleLoginModal: jest.fn(),
handleLogout: jest.fn(),
const props = {
user: {
username: 'verddacio-user',
},
packages: [],
};
/* eslint-disable react/jsx-no-bind*/
@@ -17,74 +19,70 @@ describe('<Header /> component with logged in state', () => {
test('should load the component in logged out state', () => {
const { container, queryByTestId, getByText } = render(
<Router>
<Header onLogout={headerProps.handleLogout} onToggleLoginModal={headerProps.handleToggleLoginModal} scope={headerProps.scope} />
<AppContextProvider>
<Header />
</AppContextProvider>
</Router>
);
expect(container.firstChild).toMatchSnapshot();
expect(queryByTestId('header--menu-acountcircle')).toBeNull();
expect(queryByTestId('header--menu-accountcircle')).toBeNull();
expect(getByText('Login')).toBeTruthy();
});
test('should load the component in logged in state', () => {
const { container, getByTestId, queryByText } = render(
<Router>
<Header
onLogout={headerProps.handleLogout}
onToggleLoginModal={headerProps.handleToggleLoginModal}
scope={headerProps.scope}
username={headerProps.username}
/>
<AppContextProvider user={props.user}>
<Header />
</AppContextProvider>
</Router>
);
expect(container.firstChild).toMatchSnapshot();
expect(getByTestId('header--menu-acountcircle')).toBeTruthy();
expect(getByTestId('header--menu-accountcircle')).toBeTruthy();
expect(queryByText('Login')).toBeNull();
});
test('should open login dialog', async () => {
const { getByText } = render(
const { getByTestId } = render(
<Router>
<Header onLogout={headerProps.handleLogout} onToggleLoginModal={headerProps.handleToggleLoginModal} scope={headerProps.scope} />
<AppContextProvider>
<Header />
</AppContextProvider>
</Router>
);
const loginBtn = getByText('Login');
const loginBtn = getByTestId('header--button-login');
fireEvent.click(loginBtn);
expect(headerProps.handleToggleLoginModal).toHaveBeenCalled();
const loginDialog = await waitForElement(() => getByTestId('login--dialog'));
expect(loginDialog).toBeTruthy();
});
test('should logout the user', async () => {
const { getByText, getByTestId } = render(
<Router>
<Header
onLogout={headerProps.handleLogout}
onToggleLoginModal={headerProps.handleToggleLoginModal}
scope={headerProps.scope}
username={headerProps.username}
/>
<AppContextProvider user={props.user}>
<Header />
</AppContextProvider>
</Router>
);
const headerMenuAccountCircle = getByTestId('header--menu-acountcircle');
const headerMenuAccountCircle = getByTestId('header--menu-accountcircle');
fireEvent.click(headerMenuAccountCircle);
// wait for button Logout's appearance and return the element
const logoutBtn = await waitForElement(() => getByText('Logout'));
fireEvent.click(logoutBtn);
expect(headerProps.handleLogout).toHaveBeenCalled();
expect(getByText('Login')).toBeTruthy();
});
test("The question icon should open a new tab of verdaccio's website - installation doc", async () => {
test("The question icon should open a new tab of verdaccio's website - installation doc", () => {
const { getByTestId } = render(
<Router>
<Header
onLogout={headerProps.handleLogout}
onToggleLoginModal={headerProps.handleToggleLoginModal}
scope={headerProps.scope}
username={headerProps.username}
/>
<AppContextProvider user={props.user}>
<Header />
</AppContextProvider>
</Router>
);
@@ -95,12 +93,9 @@ describe('<Header /> component with logged in state', () => {
test('should open the registrationInfo modal when clicking on the info icon', async () => {
const { getByTestId } = render(
<Router>
<Header
onLogout={headerProps.handleLogout}
onToggleLoginModal={headerProps.handleToggleLoginModal}
scope={headerProps.scope}
username={headerProps.username}
/>
<AppContextProvider user={props.user}>
<Header />
</AppContextProvider>
</Router>
);
@@ -115,12 +110,9 @@ describe('<Header /> component with logged in state', () => {
test('should close the registrationInfo modal when clicking on the button close', async () => {
const { getByTestId, getByText, queryByTestId } = render(
<Router>
<Header
onLogout={headerProps.handleLogout}
onToggleLoginModal={headerProps.handleToggleLoginModal}
scope={headerProps.scope}
username={headerProps.username}
/>
<AppContextProvider user={props.user}>
<Header />
</AppContextProvider>
</Router>
);
@@ -128,11 +120,13 @@ describe('<Header /> component with logged in state', () => {
fireEvent.click(infoBtn);
// wait for Close's button of registrationInfo modal appearance and return the element
const closeBtn = await waitForElement(() => getByText('CLOSE'));
const closeBtn = await waitForElement(() => getByText(translationEN.button.close));
fireEvent.click(closeBtn);
const hasRegistrationInfoModalBeenRemoved = await waitForElementToBeRemoved(() => queryByTestId('registryInfo--dialog'));
const hasRegistrationInfoModalBeenRemoved = await waitForElementToBeRemoved(() =>
queryByTestId('registryInfo--dialog')
);
expect(hasRegistrationInfoModalBeenRemoved).toBeTruthy();
test.todo('autocompletion should display suggestions according to the type value');
});
test.todo('autocompletion should display suggestions according to the type value');
});

View File

@@ -1,8 +1,12 @@
import React, { useState } from 'react';
import React, { useState, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import Search from '../Search';
import storage from '../../utils/storage';
import { getRegistryURL } from '../../utils/url';
import Button from '../../muiComponents/Button';
import AppContext from '../../App/AppContext';
import LoginDialog from '../LoginDialog';
import Search from '../Search';
import { NavBar, InnerNavBar, MobileNavBar, InnerMobileNavBar } from './styles';
import HeaderLeft from './HeaderLeft';
@@ -10,44 +14,65 @@ import HeaderRight from './HeaderRight';
import HeaderInfoDialog from './HeaderInfoDialog';
interface Props {
logo?: string;
username?: string;
onLogout: () => void;
onToggleLoginModal: () => void;
scope: string;
withoutSearch?: boolean;
}
/* eslint-disable react/jsx-max-depth */
/* eslint-disable react/jsx-no-bind*/
const Header: React.FC<Props> = ({ logo, withoutSearch, username, onLogout, onToggleLoginModal, scope }) => {
const Header: React.FC<Props> = ({ withoutSearch }) => {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const [isInfoDialogOpen, setOpenInfoDialog] = useState();
const [showMobileNavBar, setShowMobileNavBar] = useState();
const [showLoginModal, setShowLoginModal] = useState(false);
if (!appContext) {
throw Error(t('app-context-not-correct-used'));
}
const { user, scope, setUser } = appContext;
/**
* Logouts user
* Required by: <Header />
*/
const handleLogout = () => {
storage.removeItem('username');
storage.removeItem('token');
setUser(undefined);
};
return (
<>
<NavBar position="static">
<NavBar data-testid="header" position="static">
<InnerNavBar>
<HeaderLeft logo={logo} />
<HeaderLeft />
<HeaderRight
onLogout={onLogout}
onLogout={handleLogout}
onOpenRegistryInfoDialog={() => setOpenInfoDialog(true)}
onToggleLogin={onToggleLoginModal}
onToggleLogin={() => setShowLoginModal(!showLoginModal)}
onToggleMobileNav={() => setShowMobileNavBar(!showMobileNavBar)}
username={username}
username={user && user.username}
withoutSearch={withoutSearch}
/>
</InnerNavBar>
<HeaderInfoDialog isOpen={isInfoDialogOpen} onCloseDialog={() => setOpenInfoDialog(false)} registryUrl={getRegistryURL()} scope={scope} />
<HeaderInfoDialog
isOpen={isInfoDialogOpen}
onCloseDialog={() => setOpenInfoDialog(false)}
registryUrl={getRegistryURL()}
scope={scope}
/>
</NavBar>
{showMobileNavBar && !withoutSearch && (
<MobileNavBar>
<InnerMobileNavBar>
<Search />
</InnerMobileNavBar>
<Button color="inherit">{'Cancel'}</Button>
<Button color="inherit" onClick={() => setShowMobileNavBar(false)}>
{t('button.cancel')}
</Button>
</MobileNavBar>
)}
{!user && <LoginDialog onClose={() => setShowLoginModal(false)} open={showLoginModal} />}
</>
);
};

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import Label from '../Label';
@@ -8,11 +9,14 @@ interface Props {
username: string;
}
const HeaderGreetings: React.FC<Props> = ({ username }) => (
<>
<Greetings>{'Hi,'}</Greetings>
<Label capitalize={true} text={username} weight="bold" />
</>
);
const HeaderGreetings: React.FC<Props> = ({ username }) => {
const { t } = useTranslation();
return (
<>
<Greetings>{t('header.greetings')}</Greetings>
<Label capitalize={true} data-testid="greetings-label" text={username} weight="bold" />
</>
);
};
export default HeaderGreetings;

View File

@@ -1,26 +1,25 @@
import React from 'react';
import { css } from 'emotion';
import styled from '@emotion/styled';
import { Link } from 'react-router-dom';
import Search from '../Search/';
import Logo from '../Logo';
import HeaderLogo from './HeaderLogo';
import { LeftSide, SearchWrapper } from './styles';
interface Props {
withoutSearch?: boolean;
logo?: string;
}
const HeaderLeft: React.FC<Props> = ({ withoutSearch = false, logo }) => (
const StyledLink = styled(Link)({
marginRight: '1em',
});
const HeaderLeft: React.FC<Props> = ({ withoutSearch = false }) => (
<LeftSide>
<Link
className={css`
margin-right: 1em;
`}
to={'/'}>
<HeaderLogo logo={logo} />
</Link>
<StyledLink to={'/'}>
<Logo />
</StyledLink>
{!withoutSearch && (
<SearchWrapper>
<Search />

View File

@@ -1,17 +0,0 @@
import React from 'react';
import Logo from '../Logo';
interface Props {
logo?: string;
}
const HeaderLogo: React.FC<Props> = ({ logo }) => {
if (logo) {
return <img alt="logo" height="40px" src={logo} />;
}
return <Logo />;
};
export default HeaderLogo;

View File

@@ -1,9 +1,10 @@
import React, { MouseEvent } from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import Menu from '@material-ui/core/Menu';
import { useTranslation } from 'react-i18next';
import AccountCircle from '@material-ui/icons/AccountCircle';
import IconButton from '../../muiComponents/IconButton';
import MenuItem from '../../muiComponents/MenuItem';
import Menu from '../../muiComponents/Menu';
import HeaderGreetings from './HeaderGreetings';
@@ -16,33 +17,45 @@ interface Props {
onLoggedInMenuClose: () => void;
}
/* eslint-disable react/jsx-max-depth */
const HeaderMenu: React.FC<Props> = ({ onLogout, username, isMenuOpen = false, anchorEl, onLoggedInMenu, onLoggedInMenuClose }) => (
<>
<IconButton color="inherit" data-testid="header--menu-acountcircle" id="header--button-account" onClick={onLoggedInMenu}>
<AccountCircle />
</IconButton>
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
id="header--button-account"
onClose={onLoggedInMenuClose}
open={isMenuOpen}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}>
<MenuItem disabled={true}>
<HeaderGreetings username={username} />
</MenuItem>
<MenuItem id="header--button-logout" onClick={onLogout}>
{'Logout'}
</MenuItem>
</Menu>
</>
);
const HeaderMenu: React.FC<Props> = ({
onLogout,
username,
isMenuOpen = false,
anchorEl,
onLoggedInMenu,
onLoggedInMenuClose,
}) => {
const { t } = useTranslation();
return (
<>
<IconButton
color="inherit"
data-testid="header--menu-accountcircle"
id="header--button-account"
onClick={onLoggedInMenu}>
<AccountCircle />
</IconButton>
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
onClose={onLoggedInMenuClose}
open={isMenuOpen}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}>
<MenuItem disabled={true}>
<HeaderGreetings username={username} />
</MenuItem>
<MenuItem button={true} data-testid="header--button-logout" id="header--button-logout" onClick={onLogout}>
{t('button.logout')}
</MenuItem>
</Menu>
</>
);
};
export default HeaderMenu;

View File

@@ -1,6 +1,8 @@
import React, { useState, useEffect, MouseEvent } from 'react';
import React, { useState, useEffect, useContext, MouseEvent } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '../../muiComponents/Button';
import ThemeContext from '../../design-tokens/ThemeContext';
import { RightSide } from './styles';
import HeaderToolTip from './HeaderToolTip';
@@ -15,10 +17,24 @@ interface Props {
onLogout: () => void;
}
const HeaderRight: React.FC<Props> = ({ withoutSearch = false, username, onToggleLogin, onLogout, onToggleMobileNav, onOpenRegistryInfoDialog }) => {
const HeaderRight: React.FC<Props> = ({
withoutSearch = false,
username,
onToggleLogin,
onLogout,
onToggleMobileNav,
onOpenRegistryInfoDialog,
}) => {
const themeContext = useContext(ThemeContext);
const [anchorEl, setAnchorEl] = useState();
const [isMenuOpen, setIsMenuOpen] = useState();
const { t } = useTranslation();
if (!themeContext) {
throw Error(t('theme-context-not-correct-used'));
}
useEffect(() => {
setIsMenuOpen(Boolean(anchorEl));
}, [anchorEl]);
@@ -45,11 +61,25 @@ const HeaderRight: React.FC<Props> = ({ withoutSearch = false, username, onToggl
onToggleLogin();
};
const handleToggleDarkLightMode = () => {
setTimeout(() => {
themeContext.setIsDarkMode(!themeContext.isDarkMode);
}, 300);
};
return (
<RightSide>
{!withoutSearch && <HeaderToolTip onClick={onToggleMobileNav} title={'Search packages'} tooltipIconType={'search'} />}
<HeaderToolTip title={'Documentation'} tooltipIconType={'help'} />
<HeaderToolTip onClick={onOpenRegistryInfoDialog} title={'Registry Information'} tooltipIconType={'info'} />
<RightSide data-testid="header-right">
{!withoutSearch && (
<HeaderToolTip onClick={onToggleMobileNav} title={t('search.packages')} tooltipIconType={'search'} />
)}
<HeaderToolTip title={t('header.documentation')} tooltipIconType={'help'} />
<HeaderToolTip onClick={onOpenRegistryInfoDialog} title={t('header.registry-info')} tooltipIconType={'info'} />
<HeaderToolTip
onClick={handleToggleDarkLightMode}
title={t('header.documentation')}
tooltipIconType={themeContext.isDarkMode ? 'dark-mode' : 'light-mode'}
/>
{username ? (
<HeaderMenu
anchorEl={anchorEl}
@@ -61,7 +91,7 @@ const HeaderRight: React.FC<Props> = ({ withoutSearch = false, username, onToggl
/>
) : (
<Button color="inherit" data-testid="header--button-login" onClick={handleToggleLogin}>
{'Login'}
{t('button.login')}
</Button>
)}
</RightSide>

View File

@@ -1,32 +1,48 @@
import React from 'react';
import React, { forwardRef } from 'react';
import Info from '@material-ui/icons/Info';
import Help from '@material-ui/icons/Help';
import Search from '@material-ui/icons/Search';
import NightsStay from '@material-ui/icons/NightsStay';
import WbSunny from '@material-ui/icons/WbSunny';
import IconButton from '../../muiComponents/IconButton';
import { IconSearchButton, StyledExternalLink } from './styles';
export type TooltipIconType = 'search' | 'help' | 'info';
import { IconSearchButton, StyledLink } from './styles';
export type TooltipIconType = 'search' | 'help' | 'info' | 'dark-mode' | 'light-mode';
interface Props {
tooltipIconType: TooltipIconType;
onClick?: () => void;
}
const HeaderToolTipIcon: React.FC<Props> = ({ tooltipIconType, onClick }) => {
type HeaderToolTipIconRef = HTMLButtonElement;
/* eslint-disable react/jsx-no-undef */
/* eslint-disable react/display-name */
const HeaderToolTipIcon = forwardRef<HeaderToolTipIconRef, Props>(function HeaderToolTipIcon(
{ tooltipIconType, onClick },
ref
) {
switch (tooltipIconType) {
case 'help':
return (
<StyledExternalLink blank={true} data-testid={'header--tooltip-documentation'} to={'https://verdaccio.org/docs/en/installation'}>
<StyledLink
data-testid={'header--tooltip-documentation'}
external={true}
to={'https://verdaccio.org/docs/en/installation'}>
<IconButton color={'inherit'}>
<Help />
</IconButton>
</StyledExternalLink>
</StyledLink>
);
case 'info':
return (
<IconButton color="inherit" data-testid={'header--tooltip-info'} id="header--button-registryInfo" onClick={onClick}>
<IconButton
color="inherit"
data-testid={'header--tooltip-info'}
id="header--button-registryInfo"
onClick={onClick}
ref={ref}>
<Info />
</IconButton>
);
@@ -36,9 +52,24 @@ const HeaderToolTipIcon: React.FC<Props> = ({ tooltipIconType, onClick }) => {
<Search />
</IconSearchButton>
);
case 'dark-mode':
// todo(Priscila): Add Zoom transition effect
return (
<IconButton color="inherit" onClick={onClick} ref={ref}>
<NightsStay />
</IconButton>
);
case 'light-mode':
// todo(Priscila): Add Zoom transition effect
return (
<IconButton color="inherit" onClick={onClick} ref={ref}>
<WbSunny />
</IconButton>
);
default:
return null;
}
};
});
export default HeaderToolTipIcon;

View File

@@ -1,28 +1,167 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Header /> component with logged in state should load the component in logged in state 1`] = `
.emotion-24 {
background-color: #4b5e40;
min-height: 60px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
@media (min-width:768px) {
.emotion-24 .emotion-13 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.emotion-24 .emotion-17 {
display: none;
}
.emotion-24 .e1jf5lit4 {
display: none;
}
}
@media (min-width:1024px) {
.emotion-24 .emotion-23 {
padding: 0 20px;
}
@media (min-width:1275px) {
.emotion-24 .emotion-23 {
max-width: 1240px;
width: 100%;
margin: 0 auto;
}
}
}
.emotion-22 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 0 15px;
}
.emotion-14 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
.emotion-2 {
margin-right: 1em;
}
.emotion-0 {
display: inline-block;
vertical-align: middle;
box-sizing: border-box;
background-position: center;
background-size: contain;
background-image: url([object Object]);
background-repeat: no-repeat;
width: 40px;
height: 40px;
}
.emotion-12 {
display: none;
max-width: 393px;
width: 100%;
}
.emotion-10 {
width: 100%;
height: 32px;
position: relative;
z-index: 1;
}
.emotion-6 .MuiInputBase-root:before {
content: '';
border: none;
}
.emotion-6 .MuiInputBase-root:after {
border-color: #fff;
}
.emotion-6 .MuiInputBase-root:hover:before {
content: none;
}
.emotion-6 .MuiInputBase-input {
color: #fff;
}
.emotion-4 {
color: #fff;
}
.emotion-8 {
max-height: 500px;
overflow-y: auto;
}
.emotion-20 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
}
.emotion-16 {
display: block;
}
.emotion-18 {
color: #fff;
}
<header
class="MuiPaper-root MuiPaper-elevation4 MuiAppBar-root MuiAppBar-positionStatic css-rfunvc emotion-9 MuiAppBar-colorPrimary"
class="MuiPaper-root MuiPaper-elevation4 MuiAppBar-root MuiAppBar-positionStatic emotion-24 emotion-25 MuiAppBar-colorPrimary"
data-testid="header"
>
<div
class="MuiToolbar-root MuiToolbar-regular css-1pwdmmq emotion-8 MuiToolbar-gutters"
class="MuiToolbar-root MuiToolbar-regular emotion-22 emotion-23 MuiToolbar-gutters"
>
<div
class="MuiToolbar-root MuiToolbar-regular css-1vacr9s emotion-4 MuiToolbar-gutters"
class="MuiToolbar-root MuiToolbar-regular emotion-14 emotion-15 MuiToolbar-gutters"
>
<a
class="css-1dk30lc"
class="emotion-2 emotion-3"
href="/"
>
<div
class="css-1sifsqk emotion-0"
class="emotion-0 emotion-1"
/>
</a>
<div
class="css-13zpdre emotion-3"
class="emotion-12 emotion-13"
>
<div
class="css-1crzyyo emotion-2"
class="emotion-10 emotion-11"
>
<div
aria-expanded="false"
@@ -34,13 +173,13 @@ exports[`<Header /> component with logged in state should load the component in
<div
aria-autocomplete="list"
aria-controls="react-autowhatever-1"
class="MuiFormControl-root MuiTextField-root react-autosuggest__input MuiFormControl-fullWidth"
class="MuiFormControl-root MuiTextField-root react-autosuggest__input emotion-6 emotion-7 MuiFormControl-fullWidth"
>
<div
class="MuiInputBase-root MuiInput-root css-n9ojyg MuiInput-underline MuiInputBase-fullWidth MuiInput-fullWidth MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedStart"
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-fullWidth MuiInput-fullWidth MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedStart"
>
<div
class="MuiInputAdornment-root css-16qv2i2 MuiInputAdornment-positionStart"
class="MuiInputAdornment-root emotion-4 emotion-5 MuiInputAdornment-positionStart"
>
<svg
aria-hidden="true"
@@ -57,7 +196,7 @@ exports[`<Header /> component with logged in state should load the component in
<input
aria-invalid="false"
autocomplete="off"
class="MuiInputBase-input MuiInput-input css-hodoyq MuiInputBase-inputAdornedStart"
class="MuiInputBase-input MuiInput-input MuiInputBase-inputAdornedStart"
placeholder="Search Packages"
type="text"
value=""
@@ -65,7 +204,7 @@ exports[`<Header /> component with logged in state should load the component in
</div>
</div>
<div
class="MuiPaper-root MuiPaper-elevation1 react-autosuggest__suggestions-container css-cfo6a emotion-1"
class="MuiPaper-root MuiPaper-elevation1 react-autosuggest__suggestions-container emotion-8 emotion-9"
id="react-autowhatever-1"
role="listbox"
/>
@@ -74,10 +213,11 @@ exports[`<Header /> component with logged in state should load the component in
</div>
</div>
<div
class="MuiToolbar-root MuiToolbar-regular css-m61s5i emotion-7 MuiToolbar-gutters"
class="MuiToolbar-root MuiToolbar-regular emotion-20 emotion-21 MuiToolbar-gutters"
data-testid="header-right"
>
<button
class="MuiButtonBase-root MuiIconButton-root css-1y1xi9f emotion-5 MuiIconButton-colorInherit"
class="MuiButtonBase-root MuiIconButton-root emotion-16 emotion-17 MuiIconButton-colorInherit"
tabindex="0"
type="button"
>
@@ -101,35 +241,40 @@ exports[`<Header /> component with logged in state should load the component in
/>
</button>
<a
class="css-1aacqdd emotion-6"
class="emotion-18 emotion-19"
data-testid="header--tooltip-documentation"
href="https://verdaccio.org/docs/en/installation"
rel="noopener noreferrer"
target="_blank"
>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
tabindex="0"
type="button"
<h6
class="MuiTypography-root MuiTypography-subtitle1"
>
<span
class="MuiIconButton-label"
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
<span
class="MuiIconButton-label"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</h6>
</a>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
@@ -159,7 +304,31 @@ exports[`<Header /> component with logged in state should load the component in
</button>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
data-testid="header--menu-acountcircle"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
data-testid="header--menu-accountcircle"
id="header--button-account"
tabindex="0"
type="button"
@@ -189,28 +358,167 @@ exports[`<Header /> component with logged in state should load the component in
`;
exports[`<Header /> component with logged in state should load the component in logged out state 1`] = `
.emotion-24 {
background-color: #4b5e40;
min-height: 60px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
@media (min-width:768px) {
.emotion-24 .emotion-13 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.emotion-24 .emotion-17 {
display: none;
}
.emotion-24 .e1jf5lit4 {
display: none;
}
}
@media (min-width:1024px) {
.emotion-24 .emotion-23 {
padding: 0 20px;
}
@media (min-width:1275px) {
.emotion-24 .emotion-23 {
max-width: 1240px;
width: 100%;
margin: 0 auto;
}
}
}
.emotion-22 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 0 15px;
}
.emotion-14 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
.emotion-2 {
margin-right: 1em;
}
.emotion-0 {
display: inline-block;
vertical-align: middle;
box-sizing: border-box;
background-position: center;
background-size: contain;
background-image: url([object Object]);
background-repeat: no-repeat;
width: 40px;
height: 40px;
}
.emotion-12 {
display: none;
max-width: 393px;
width: 100%;
}
.emotion-10 {
width: 100%;
height: 32px;
position: relative;
z-index: 1;
}
.emotion-6 .MuiInputBase-root:before {
content: '';
border: none;
}
.emotion-6 .MuiInputBase-root:after {
border-color: #fff;
}
.emotion-6 .MuiInputBase-root:hover:before {
content: none;
}
.emotion-6 .MuiInputBase-input {
color: #fff;
}
.emotion-4 {
color: #fff;
}
.emotion-8 {
max-height: 500px;
overflow-y: auto;
}
.emotion-20 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
}
.emotion-16 {
display: block;
}
.emotion-18 {
color: #fff;
}
<header
class="MuiPaper-root MuiPaper-elevation4 MuiAppBar-root MuiAppBar-positionStatic css-rfunvc emotion-9 MuiAppBar-colorPrimary"
class="MuiPaper-root MuiPaper-elevation4 MuiAppBar-root MuiAppBar-positionStatic emotion-24 emotion-25 MuiAppBar-colorPrimary"
data-testid="header"
>
<div
class="MuiToolbar-root MuiToolbar-regular css-1pwdmmq emotion-8 MuiToolbar-gutters"
class="MuiToolbar-root MuiToolbar-regular emotion-22 emotion-23 MuiToolbar-gutters"
>
<div
class="MuiToolbar-root MuiToolbar-regular css-1vacr9s emotion-4 MuiToolbar-gutters"
class="MuiToolbar-root MuiToolbar-regular emotion-14 emotion-15 MuiToolbar-gutters"
>
<a
class="css-1dk30lc"
class="emotion-2 emotion-3"
href="/"
>
<div
class="css-1sifsqk emotion-0"
class="emotion-0 emotion-1"
/>
</a>
<div
class="css-13zpdre emotion-3"
class="emotion-12 emotion-13"
>
<div
class="css-1crzyyo emotion-2"
class="emotion-10 emotion-11"
>
<div
aria-expanded="false"
@@ -222,13 +530,13 @@ exports[`<Header /> component with logged in state should load the component in
<div
aria-autocomplete="list"
aria-controls="react-autowhatever-1"
class="MuiFormControl-root MuiTextField-root react-autosuggest__input MuiFormControl-fullWidth"
class="MuiFormControl-root MuiTextField-root react-autosuggest__input emotion-6 emotion-7 MuiFormControl-fullWidth"
>
<div
class="MuiInputBase-root MuiInput-root css-n9ojyg MuiInput-underline MuiInputBase-fullWidth MuiInput-fullWidth MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedStart"
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-fullWidth MuiInput-fullWidth MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedStart"
>
<div
class="MuiInputAdornment-root css-16qv2i2 MuiInputAdornment-positionStart"
class="MuiInputAdornment-root emotion-4 emotion-5 MuiInputAdornment-positionStart"
>
<svg
aria-hidden="true"
@@ -245,7 +553,7 @@ exports[`<Header /> component with logged in state should load the component in
<input
aria-invalid="false"
autocomplete="off"
class="MuiInputBase-input MuiInput-input css-hodoyq MuiInputBase-inputAdornedStart"
class="MuiInputBase-input MuiInput-input MuiInputBase-inputAdornedStart"
placeholder="Search Packages"
type="text"
value=""
@@ -253,7 +561,7 @@ exports[`<Header /> component with logged in state should load the component in
</div>
</div>
<div
class="MuiPaper-root MuiPaper-elevation1 react-autosuggest__suggestions-container css-cfo6a emotion-1"
class="MuiPaper-root MuiPaper-elevation1 react-autosuggest__suggestions-container emotion-8 emotion-9"
id="react-autowhatever-1"
role="listbox"
/>
@@ -262,10 +570,11 @@ exports[`<Header /> component with logged in state should load the component in
</div>
</div>
<div
class="MuiToolbar-root MuiToolbar-regular css-m61s5i emotion-7 MuiToolbar-gutters"
class="MuiToolbar-root MuiToolbar-regular emotion-20 emotion-21 MuiToolbar-gutters"
data-testid="header-right"
>
<button
class="MuiButtonBase-root MuiIconButton-root css-1y1xi9f emotion-5 MuiIconButton-colorInherit"
class="MuiButtonBase-root MuiIconButton-root emotion-16 emotion-17 MuiIconButton-colorInherit"
tabindex="0"
type="button"
>
@@ -289,35 +598,40 @@ exports[`<Header /> component with logged in state should load the component in
/>
</button>
<a
class="css-1aacqdd emotion-6"
class="emotion-18 emotion-19"
data-testid="header--tooltip-documentation"
href="https://verdaccio.org/docs/en/installation"
rel="noopener noreferrer"
target="_blank"
>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
tabindex="0"
type="button"
<h6
class="MuiTypography-root MuiTypography-subtitle1"
>
<span
class="MuiIconButton-label"
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
<span
class="MuiIconButton-label"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</h6>
</a>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
@@ -345,6 +659,30 @@ exports[`<Header /> component with logged in state should load the component in
class="MuiTouchRipple-root"
/>
</button>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit"
data-testid="header--button-login"

View File

@@ -1,111 +1,88 @@
import styled, { css } from 'react-emotion';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import styled from '@emotion/styled';
import { css } from '@emotion/core';
import colors from '../../utils/styles/colors';
import mq from '../../utils/styles/media';
import { Theme } from '../../design-tokens/theme';
import IconButton from '../../muiComponents/IconButton';
import ExternalLink from '../Link';
import AppBar from '../../muiComponents/AppBar';
import Toolbar from '../../muiComponents/Toolbar';
import Link from '../Link';
export const InnerNavBar = styled(Toolbar)({
'&&': {
justifyContent: 'space-between',
alignItems: 'center',
padding: '0 15px',
},
justifyContent: 'space-between',
alignItems: 'center',
padding: '0 15px',
});
export const Greetings = styled('span')({
'&&': {
margin: '0 5px 0 0',
},
margin: '0 5px 0 0',
});
export const RightSide = styled(Toolbar)({
'&&': {
display: 'flex',
padding: 0,
},
display: 'flex',
padding: 0,
});
export const LeftSide = styled(RightSide)({
'&&': {
flex: 1,
},
flex: 1,
});
export const MobileNavBar = styled('div')({
'&&': {
alignItems: 'center',
display: 'flex',
borderBottom: `1px solid ${colors.greyLight}`,
padding: '8px',
position: 'relative',
},
});
export const MobileNavBar = styled('div')<{ theme?: Theme }>(props => ({
alignItems: 'center',
display: 'flex',
borderBottom: `1px solid ${props.theme && props.theme.palette.greyLight}`,
padding: '8px',
position: 'relative',
}));
export const InnerMobileNavBar = styled('div')({
'&&': {
borderRadius: '4px',
backgroundColor: colors.greyLight,
color: colors.white,
width: '100%',
padding: '0 5px',
margin: '0 10px 0 0',
},
});
export const InnerMobileNavBar = styled('div')<{ theme?: Theme }>(props => ({
borderRadius: '4px',
backgroundColor: props.theme && props.theme.palette.greyLight,
color: props.theme && props.theme.palette.white,
width: '100%',
padding: '0 5px',
margin: '0 10px 0 0',
}));
export const IconSearchButton = styled(IconButton)({
'&&': {
display: 'block',
},
display: 'block',
});
export const SearchWrapper = styled('div')({
'&&': {
display: 'none',
maxWidth: '393px',
width: '100%',
},
display: 'none',
maxWidth: '393px',
width: '100%',
});
export const NavBar = styled(AppBar)`
&& {
background-color: ${colors.primary};
min-height: 60px;
display: flex;
justify-content: center;
${() =>
mq.medium(css`
${SearchWrapper} {
display: flex;
}
${IconSearchButton} {
display: none;
}
${MobileNavBar} {
display: none;
}
`)};
${() =>
mq.large(css`
${InnerNavBar} {
padding: 0 20px;
}
`)};
${() =>
mq.xlarge(css`
${InnerNavBar} {
max-width: 1240px;
width: 100%;
margin: 0 auto;
}
`)};
}
`;
export const NavBar = styled(AppBar)<{ theme?: Theme }>(({ theme }) => ({
backgroundColor: theme && theme.palette.primary.main,
minHeight: 60,
display: 'flex',
justifyContent: 'center',
[`@media (min-width: ${theme && theme.breakPoints.medium}px)`]: css`
${SearchWrapper} {
display: flex;
}
${IconSearchButton} {
display: none;
}
${MobileNavBar} {
display: none;
}
`,
[`@media (min-width: ${theme && theme.breakPoints.large}px)`]: css`
${InnerNavBar} {
padding: 0 20px;
}
`,
[`@media (min-width: ${theme && theme.breakPoints.xlarge}px)`]: css`
${InnerNavBar} {
max-width: 1240px;
width: 100%;
margin: 0 auto;
}
`,
}));
export const StyledExternalLink = styled(ExternalLink)({
'&&': {
color: 'white',
},
});
export const StyledLink = styled(Link)<{ theme?: Theme }>(props => ({
color: props.theme && props.theme.palette.white,
}));

View File

@@ -1,11 +1,12 @@
import React from 'react';
import { mount } from 'enzyme';
import { render } from '../../utils/test-react-testing-library';
import Help from './Help';
describe('<Help /> component', () => {
test('should render the component in default state', () => {
const wrapper = mount(<Help />);
expect(wrapper.html()).toMatchSnapshot();
test('should load the component in default state', () => {
const { container } = render(<Help />);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -1,15 +1,19 @@
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import React, { Fragment } from 'react';
import { useTranslation } from 'react-i18next';
import { getRegistryURL } from '../../utils/url';
import CopyToClipBoard from '../CopyToClipBoard';
import Button from '../../muiComponents/Button';
import CardContent from '../../muiComponents/CardContent';
import { default as Typography } from '../../muiComponents/Heading';
import CardActions from '../../muiComponents/CardActions';
import Text from '../../muiComponents/Text';
import { CardStyled as Card, HelpTitle } from './styles';
export const HELP_TITLE = 'No Package Published Yet.';
export const COMPONENT_HELP_ID = 'help-card__title';
function renderHeadingClipboardSegments(title: string, text: string): React.ReactNode {
return (
<Fragment>
@@ -21,23 +25,24 @@ function renderHeadingClipboardSegments(title: string, text: string): React.Reac
const Help: React.FC = () => {
const registryUrl = getRegistryURL();
const { t } = useTranslation();
return (
<Card id="help-card">
<CardContent>
<Typography component="h2" gutterBottom={true} id="help-card__title" variant="h5">
{'No Package Published Yet.'}
<Typography component="h2" gutterBottom={true} id={COMPONENT_HELP_ID} variant="h5">
{t('help.title')}
</Typography>
<HelpTitle color="textSecondary" gutterBottom={true}>
{'To publish your first package just:'}
{t('help.sub-title')}
</HelpTitle>
{renderHeadingClipboardSegments('1. Login', `npm adduser --registry ${registryUrl}`)}
{renderHeadingClipboardSegments('2. Publish', `npm publish --registry ${registryUrl}`)}
<Text variant="body2">{'3. Refresh this page.'}</Text>
{renderHeadingClipboardSegments(t('help.first-step'), t('help.first-step-command-line', { registryUrl }))}
{renderHeadingClipboardSegments(t('help.second-step'), t('help.second-step-command-line', { registryUrl }))}
<Text variant="body2">{t('help.third-step')}</Text>
</CardContent>
<CardActions>
<Button color="primary" href="https://verdaccio.org/docs/en/installation" size="small">
{'Learn More'}
{t('button.learn-more')}
</Button>
</CardActions>
</Card>

View File

@@ -1,3 +1,159 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Help /> component should render the component in default state 1`] = `"<div class=\\"MuiPaper-root MuiPaper-elevation1 MuiCard-root css-ryznli e1wgaou60 MuiPaper-rounded\\" id=\\"help-card\\"><div class=\\"MuiCardContent-root\\"><h2 class=\\"MuiTypography-root MuiTypography-h5 MuiTypography-gutterBottom\\" id=\\"help-card__title\\">No Package Published Yet.</h2><h6 class=\\"MuiTypography-root css-zg2fwz e1wgaou61 MuiTypography-h6 MuiTypography-colorTextSecondary MuiTypography-gutterBottom\\">To publish your first package just:</h6><p class=\\"MuiTypography-root MuiTypography-body1\\">1. Login</p><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm adduser --registry http://localhost</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><p class=\\"MuiTypography-root MuiTypography-body1\\">2. Publish</p><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm publish --registry http://localhost</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><p class=\\"MuiTypography-root MuiTypography-body2\\">3. Refresh this page.</p></div><div class=\\"MuiCardActions-root MuiCardActions-spacing\\"><a class=\\"MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-textSizeSmall MuiButton-sizeSmall\\" tabindex=\\"0\\" aria-disabled=\\"false\\" href=\\"https://verdaccio.org/docs/en/installation\\"><span class=\\"MuiButton-label\\">Learn More</span><span class=\\"MuiTouchRipple-root\\"></span></a></div></div>"`;
exports[`<Help /> component should load the component in default state 1`] = `
.emotion-14 {
width: 600px;
margin: auto;
}
.emotion-0 {
margin-bottom: 20px;
}
.emotion-6 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.emotion-2 {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
height: 21px;
font-size: 1rem;
}
<div
class="MuiPaper-root MuiPaper-elevation1 MuiCard-root emotion-14 emotion-15 MuiPaper-rounded"
id="help-card"
>
<div
class="MuiCardContent-root"
>
<h2
class="MuiTypography-root MuiTypography-h5 MuiTypography-gutterBottom"
id="help-card__title"
>
No Package Published Yet.
</h2>
<h6
class="MuiTypography-root emotion-0 emotion-1 MuiTypography-h6 MuiTypography-colorTextSecondary MuiTypography-gutterBottom"
>
To publish your first package just:
</h6>
<p
class="MuiTypography-root MuiTypography-body1"
>
1. Login
</p>
<div
class="emotion-6 emotion-7"
>
<span
class="emotion-2 emotion-3"
>
npm adduser --registry http://localhost
</span>
<button
class="MuiButtonBase-root MuiIconButton-root emotion-4 emotion-5"
tabindex="0"
title="Copy to clipboard"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<p
class="MuiTypography-root MuiTypography-body1"
>
2. Publish
</p>
<div
class="emotion-6 emotion-7"
>
<span
class="emotion-2 emotion-3"
>
npm publish --registry http://localhost
</span>
<button
class="MuiButtonBase-root MuiIconButton-root emotion-4 emotion-5"
tabindex="0"
title="Copy to clipboard"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<p
class="MuiTypography-root MuiTypography-body2"
>
3. Refresh this page.
</p>
</div>
<div
class="MuiCardActions-root MuiCardActions-spacing"
>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-textSizeSmall MuiButton-sizeSmall"
href="https://verdaccio.org/docs/en/installation"
tabindex="0"
>
<span
class="MuiButton-label"
>
Learn More
</span>
<span
class="MuiTouchRipple-root"
/>
</a>
</div>
</div>
`;

View File

@@ -1,17 +1,13 @@
import styled from 'react-emotion';
import styled from '@emotion/styled';
import { default as Typography } from '../../muiComponents/Heading';
import Card from '../../muiComponents/Card';
export const CardStyled = styled(Card)({
'&&': {
width: '600px',
margin: 'auto',
},
width: 600,
margin: 'auto',
});
export const HelpTitle = styled(Typography)({
'&&': {
marginBottom: '20px',
},
marginBottom: 20,
});

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