forked from sombochea/verdaccio-ui
Compare commits
178 Commits
refactor/m
...
master
Author | SHA1 | Date | |
---|---|---|---|
563a6677f6 | |||
c2c94929a7 | |||
484543dabc | |||
3c5fd072f0 | |||
35be83f560 | |||
ee9ef143d2 | |||
b06ae66961 | |||
57f45e7867 | |||
37e3574fb1 | |||
|
7e56c8e7e2 | ||
|
b59840d352 | ||
|
2965973f77 | ||
|
c9910c97b0 | ||
|
1e060474c7 | ||
|
f8101ae90a | ||
|
bf54b4abab | ||
|
5e3c006cbd | ||
|
f44abd7dd0 | ||
|
730c3471c2 | ||
|
25def6ccd5 | ||
|
ae0546c0e2 | ||
|
675ee980ee | ||
|
b17368470d | ||
|
2a6ad969cc | ||
|
e0377991fa | ||
|
07620e5d4b | ||
|
d29aa05cc6 | ||
|
1e1c088ac3 | ||
|
7c45ac9f8d | ||
|
76115d2fdd | ||
|
03114a842b | ||
|
6552f4c13f | ||
|
9f275b7b00 | ||
|
f321f7b6fe | ||
|
e7db3e4967 | ||
|
e0eb6b0a3c | ||
|
cdad5cf70d | ||
|
91434cc814 | ||
|
4071e272af | ||
|
f81c1984da | ||
|
b2255fca88 | ||
|
8c2800e156 | ||
|
a5f06cb3af | ||
|
e27d59bff7 | ||
|
0abe1ef41c | ||
|
7428384b55 | ||
|
8d4b3cee7e | ||
|
d41ba981d2 | ||
|
26dbf3d921 | ||
|
7cb20fa699 | ||
|
e6aad5370f | ||
|
d481f54948 | ||
|
e6e9cfb2b4 | ||
|
6570e3fba1 | ||
|
d4f2720994 | ||
|
1eca1f4079 | ||
|
164cea6c10 | ||
|
dad44c46c0 | ||
|
222ffed022 | ||
|
ee1c3f08eb | ||
|
1531cb6226 | ||
|
e514ec95a6 | ||
|
6b322ad553 | ||
|
3fd0154da3 | ||
|
6bd38b8120 | ||
|
6e2bface93 | ||
|
6eeae630ef | ||
|
eaea5f2501 | ||
|
c1e4e739c8 | ||
|
4d31aff4a4 | ||
|
d9a9fc4b96 | ||
|
e4ecc4a2f9 | ||
|
bae9638b23 | ||
|
544b999f81 | ||
|
d554049699 | ||
|
787dda4a01 | ||
|
797c2381e4 | ||
|
3888a268e4 | ||
|
884d76d4a9 | ||
|
7b55ce5ea2 | ||
|
1ec62de0bf | ||
|
3fc5c38a8e | ||
|
53e1e63b12 | ||
|
ef2b50a329 | ||
|
61dc9b0783 | ||
|
0e3391ca3d | ||
|
277b44ab94 | ||
|
71276e15ef | ||
|
bf093cc27b | ||
|
3a9f66c023 | ||
|
eef2913dd5 | ||
|
2af2dfe91b | ||
|
d4e8dff40f | ||
|
cc22574100 | ||
|
580b47bea1 | ||
|
ea24e1b50e | ||
|
a74dc87628 | ||
|
cecb54c490 | ||
|
b8c68314e9 | ||
|
6bb37d6656 | ||
|
ac1a4fa46d | ||
|
3b228a2a0b | ||
|
fd99be6818 | ||
|
172e470780 | ||
|
a3b41747ca | ||
|
7e29182a15 | ||
|
8e89c82750 | ||
|
f1e468e7e4 | ||
|
28208d6633 | ||
|
398c5804cf | ||
|
4d9ac2bd04 | ||
|
52c941be09 | ||
|
89b554b07c | ||
|
43a6bc0133 | ||
|
e40280ffeb | ||
|
ae617a5c04 | ||
|
33f873a8c7 | ||
|
42d3bb8508 | ||
|
501845b5f8 | ||
|
474e9e18de | ||
|
ab810c8caa | ||
|
a3d7acfd73 | ||
|
6ba721446b | ||
|
09b831a40d | ||
|
742971db0d | ||
|
fcad6fa794 | ||
|
a7b5e6df99 | ||
|
c988f0fac7 | ||
|
c839970a25 | ||
|
200cc289e6 | ||
|
11d66b7df1 | ||
|
7c616fa81a | ||
|
e6dbf0a187 | ||
|
764e73bbe2 | ||
|
e60ab9e247 | ||
|
d37de29d36 | ||
|
764090dad3 | ||
|
bedcea9a83 | ||
|
76142ecda6 | ||
|
ddb3b15cf6 | ||
|
7a729d558f | ||
|
dc195a3446 | ||
|
a830403268 | ||
|
6a17a498e2 | ||
|
a301eb0e8d | ||
|
de983f9a13 | ||
|
6da3204c0b | ||
|
d4a17edc71 | ||
|
ba4299557e | ||
|
dc0cdbdb08 | ||
|
d955268c25 | ||
|
ee74474811 | ||
|
0d9232a92c | ||
|
0a48906fc8 | ||
|
58cf730b98 | ||
|
111f0c50e5 | ||
|
a0dcf87368 | ||
|
9ed5a833d9 | ||
|
1ed229363a | ||
|
34dff06bdb | ||
|
5d300cd9be | ||
|
acfc902a99 | ||
|
2f35eb7790 | ||
|
cf6c5e159d | ||
|
fa9e1d3487 | ||
|
d70c78f201 | ||
|
effde37c35 | ||
|
d65483401d | ||
|
5f80d00502 | ||
|
430608d276 | ||
|
9975edbb6f | ||
|
dbaa0c43b8 | ||
|
fd306def95 | ||
|
ec3f69a542 | ||
|
fc4a7ee12c | ||
|
5c6d9f68ca | ||
|
3e8af72193 | ||
|
3f58be32b3 |
7
.babelrc
7
.babelrc
@ -1,3 +1,8 @@
|
||||
{
|
||||
"presets": [["@verdaccio"]]
|
||||
"presets": [["@verdaccio"]],
|
||||
"plugins": [
|
||||
"emotion",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator"
|
||||
]
|
||||
}
|
||||
|
@ -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
|
||||
|
11
.eslintrc
11
.eslintrc
@ -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"],
|
||||
|
11
.github/workflows/security.yml
vendored
Normal file
11
.github/workflows/security.yml
vendored
Normal 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 }}
|
@ -3,7 +3,7 @@
|
||||
"files": null,
|
||||
"lines": null
|
||||
},
|
||||
"generated_at": "2019-09-29T18:19:50Z",
|
||||
"generated_at": "2020-05-05T12:14:08Z",
|
||||
"plugins_used": [
|
||||
{
|
||||
"name": "AWSKeyDetector"
|
||||
@ -23,6 +23,7 @@
|
||||
"name": "HexHighEntropyString"
|
||||
},
|
||||
{
|
||||
"keyword_exclude": null,
|
||||
"name": "KeywordDetector"
|
||||
},
|
||||
{
|
||||
@ -36,5 +37,9 @@
|
||||
}
|
||||
],
|
||||
"results": {},
|
||||
"version": "0.12.4"
|
||||
"version": "0.13.0",
|
||||
"word_list": {
|
||||
"file": null,
|
||||
"hash": null
|
||||
}
|
||||
}
|
||||
|
0
.sonarcloud.properties
Normal file
0
.sonarcloud.properties
Normal file
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -7,4 +7,4 @@
|
||||
"typescriptreact"
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
}
|
162
CHANGELOG.md
162
CHANGELOG.md
@ -2,6 +2,166 @@
|
||||
|
||||
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.7.1](https://github.com/verdaccio/ui/compare/v1.7.0...v1.7.1) (2020-04-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **i18n:** fixed current locale ([#462](https://github.com/verdaccio/ui/issues/462)) ([f44abd7](https://github.com/verdaccio/ui/commit/f44abd7dd08a8d68b1bfc2bf0c053f3e80a343d0))
|
||||
|
||||
## [1.7.0](https://github.com/verdaccio/ui/compare/v1.6.0...v1.7.0) (2020-04-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add japanese translations ([#460](https://github.com/verdaccio/ui/issues/460)) ([25def6c](https://github.com/verdaccio/ui/commit/25def6ccd5a42d43af1c33e7ace4bd7fdbec0e64))
|
||||
|
||||
## [1.6.0](https://github.com/verdaccio/ui/compare/v1.5.0...v1.6.0) (2020-04-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **lng:** Added change language on the fly ([#456](https://github.com/verdaccio/ui/issues/456)) ([675ee98](https://github.com/verdaccio/ui/commit/675ee980ee2c4c789e52d38553f751bb219d1270))
|
||||
* Add french language + minor english language fix ([#459](https://github.com/verdaccio/ui/issues/459)) ([b173684](https://github.com/verdaccio/ui/commit/b17368470d63878292aca3e6d2f9adc97748ebac))
|
||||
|
||||
## [1.5.0](https://github.com/verdaccio/ui/compare/v1.4.0...v1.5.0) (2020-04-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **flag:** Added Germany flag ([#454](https://github.com/verdaccio/ui/issues/454)) ([07620e5](https://github.com/verdaccio/ui/commit/07620e5d4b1ed54bae2266d936af5306bfbe2d8b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **styles:** Updated dark colors ([#455](https://github.com/verdaccio/ui/issues/455)) ([d29aa05](https://github.com/verdaccio/ui/commit/d29aa05cc6ef31cb871e79de10c1b1ddd74f023e))
|
||||
|
||||
## [1.4.0](https://github.com/verdaccio/ui/compare/v1.3.0...v1.4.0) (2020-04-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added zh-CN translations to the UI ([#448](https://github.com/verdaccio/ui/issues/448)) ([03114a8](https://github.com/verdaccio/ui/commit/03114a842b88ae0f482f389e7ae91af62e00bca4))
|
||||
|
||||
## [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)
|
||||
@ -286,4 +446,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)
|
32
README.md
32
README.md
@ -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,32 @@ 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.
|
||||
|
||||
Your PR should contain:
|
||||
|
||||
1 - A json file in the folder ```i18n/translations/*``` with the translations. The file must be named according to the new added language
|
||||
|
||||
2 - The files ```i18n/config.ts``` and ```LanguageSwitch.tsx``` updated with the new language. Please see the current structure
|
||||
|
||||
3 - The other translations containing the new language in the language of the file. Example:
|
||||
|
||||
New language: ```cs_CZ ```
|
||||
|
||||
The file ```pt-BR ``` should contain:
|
||||
```
|
||||
"lng": {
|
||||
...,
|
||||
"czech": "Tcheco"
|
||||
}
|
||||
```
|
||||
|
||||
4 - A SVG flag of the new translated language in the the folder ```src/components/Icon/img/*```. You maybe want to compress the svg file using https://jakearchibald.github.io/svgomg/
|
||||
|
||||
|
||||
|
||||
### License
|
||||
|
||||
Verdaccio is [MIT licensed](https://github.com/verdaccio/verdaccio/blob/master/LICENSE)
|
||||
|
65
i18n/config.ts
Normal file
65
i18n/config.ts
Normal file
@ -0,0 +1,65 @@
|
||||
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';
|
||||
import translationFR from './translations/fr-FR.json';
|
||||
import translationCN from './translations/zh-CN.json';
|
||||
import translationJP from './translations/ja-JP.json';
|
||||
import translationUA from './translations/uk-UA.json';
|
||||
import translationKM from './translations/km-KH.json';
|
||||
|
||||
const languages = {
|
||||
'en-US': {
|
||||
translation: translationEN,
|
||||
},
|
||||
'pt-BR': {
|
||||
translation: translationPT,
|
||||
},
|
||||
'es-ES': {
|
||||
translation: translationES,
|
||||
},
|
||||
'de-DE': {
|
||||
translation: translationDE,
|
||||
},
|
||||
'fr-FR': {
|
||||
translation: translationFR,
|
||||
},
|
||||
'zh-CN': {
|
||||
translation: translationCN,
|
||||
},
|
||||
'ja-JP': {
|
||||
translation: translationJP,
|
||||
},
|
||||
'uk-UA': {
|
||||
translation: translationUA,
|
||||
},
|
||||
'km-KH': {
|
||||
translation: translationKM,
|
||||
},
|
||||
};
|
||||
|
||||
type Language = keyof typeof languages;
|
||||
|
||||
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', 'fr-FR', 'zh-CN', 'ja-JP', 'uk-UA', 'km-KH'],
|
||||
load: 'currentOnly',
|
||||
resources: languages,
|
||||
debug: false,
|
||||
interpolation: {
|
||||
escapeValue: false, // react already safes from xss
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
export { Language };
|
152
i18n/translations/de-DE.json
Normal file
152
i18n/translations/de-DE.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"lng": {
|
||||
"english": "Englisch",
|
||||
"japanese": "Japanisch",
|
||||
"portuguese": "Portugiesisch",
|
||||
"spanish": "Spanisch",
|
||||
"german": "Deutsch",
|
||||
"chinese": "Chinesisch",
|
||||
"french": "Französisch",
|
||||
"ukraine": "Ukrainisch",
|
||||
"khmer": "Khmer"
|
||||
},
|
||||
"help-to-translate": "Hilfe beim Übersetzen",
|
||||
"change-language": "Sprache ändern"
|
||||
}
|
152
i18n/translations/en-US.json
Normal file
152
i18n/translations/en-US.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"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": "Registry 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 used correctly",
|
||||
"theme-context-not-correct-used": "The theme context was not used correctly",
|
||||
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
|
||||
},
|
||||
"lng": {
|
||||
"english": "English",
|
||||
"japanese": "Japanese",
|
||||
"portuguese": "Portuguese",
|
||||
"spanish": "Spanish",
|
||||
"german": "German",
|
||||
"chinese": "Chinese",
|
||||
"french": "French",
|
||||
"ukraine": "Ukraine",
|
||||
"khmer": "Khmer"
|
||||
},
|
||||
"help-to-translate": "Help to translate",
|
||||
"change-language": "Change language"
|
||||
}
|
152
i18n/translations/es-ES.json
Normal file
152
i18n/translations/es-ES.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"lng": {
|
||||
"english": "Inglés",
|
||||
"japanese": "Japonés",
|
||||
"portuguese": "Portugués",
|
||||
"spanish": "Español",
|
||||
"german": "Alemán",
|
||||
"chinese": "Chino",
|
||||
"french": "Francés",
|
||||
"ukraine": "Ucraniano",
|
||||
"khmer": "Khmer"
|
||||
},
|
||||
"help-to-translate": "Ayuda a traducir",
|
||||
"change-language": "Cambiar idioma"
|
||||
}
|
152
i18n/translations/fr-FR.json
Normal file
152
i18n/translations/fr-FR.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"copy-to-clipboard": "Copier dans le presse-papier",
|
||||
"author-anonymous": "Anonyme",
|
||||
"action-bar-action": {
|
||||
"visit-home-page": "Visiter la page d'accueil",
|
||||
"open-an-issue": "Ouvrir un ticket",
|
||||
"download-tarball": "Télécharger l'archive"
|
||||
},
|
||||
"dialog": {
|
||||
"registry-info": {
|
||||
"title": "Informations du Registry"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"documentation": "Documentation",
|
||||
"registry-info": "Informations du Registry",
|
||||
"greetings": "Bonjour "
|
||||
},
|
||||
"search": {
|
||||
"packages": "Rechercher des paquets"
|
||||
},
|
||||
"auto-complete": {
|
||||
"loading": "En cours de chargement...",
|
||||
"no-results-found": "Aucun resultat trouvé"
|
||||
},
|
||||
"tab": {
|
||||
"uplinks": "Uplinks",
|
||||
"versions": "Versions",
|
||||
"dependencies": "Dépendances",
|
||||
"readme": "Readme"
|
||||
},
|
||||
"uplinks": {
|
||||
"title": "Uplinks",
|
||||
"no-items": "{{name}} n'a pas de uplink."
|
||||
},
|
||||
"versions": {
|
||||
"current-tags": "Tags courants",
|
||||
"version-history": "Historique de version",
|
||||
"not-available": "Non disponible"
|
||||
},
|
||||
"package": {
|
||||
"published-on": "Publié le {{time}} •",
|
||||
"version": "v{{version}}",
|
||||
"visit-home-page": "Visiter la page d'accueil",
|
||||
"homepage": "Page d'accueil",
|
||||
"open-an-issue": "Ouvrir un ticket",
|
||||
"bugs": "Bugs",
|
||||
"download": "Télécharger {{what}}",
|
||||
"the-tar-file": "le fichier tar",
|
||||
"tarball": "Archive"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-no-dependencies": "{{package}} n'a aucune dépendance.",
|
||||
"dependency-block": "{{package}}@{{version}}"
|
||||
},
|
||||
"form": {
|
||||
"username": "Nom d'utilisateur",
|
||||
"password": "Mot de passe"
|
||||
},
|
||||
"form-placeholder": {
|
||||
"username": "Votre nom d'utilisateur",
|
||||
"password": "Votre mot de passe"
|
||||
},
|
||||
"form-validation": {
|
||||
"required-field": "Ce champ est obligatoire",
|
||||
"required-min-length": "Ce champ doit faire au moins {{length}} caractères",
|
||||
"unable-to-sign-in": "Connexion impossible",
|
||||
"username-or-password-cant-be-empty": "Le nom d'utilisateur ou mot de passe ne peut pas être vide!"
|
||||
},
|
||||
"help": {
|
||||
"title": "Aucun paquet publié pour l'instant.",
|
||||
"sub-title": "Pour publier votre premier paquet:",
|
||||
"first-step": "1. Se connecter",
|
||||
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
|
||||
"second-step": "2. Publier",
|
||||
"second-step-command-line": "npm publish --registry {{registryUrl}}",
|
||||
"third-step": "3. Recharger cette page."
|
||||
},
|
||||
"sidebar": {
|
||||
"detail": {
|
||||
"latest-version": "Dernière v{{version}}",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"installation": {
|
||||
"title": "Installation",
|
||||
"install-using-yarn": "Installer avec yarn",
|
||||
"install-using-yarn-command": "yarn add {{packageName}}",
|
||||
"install-using-npm": "Installer avec npm",
|
||||
"install-using-npm-command": "npm install {{packageName}}",
|
||||
"install-using-pnpm": "Installer avec pnpm",
|
||||
"install-using-pnpm-command": "pnpm install {{packageName}}"
|
||||
},
|
||||
"repository": {
|
||||
"title": "Dépôt"
|
||||
},
|
||||
"author": {
|
||||
"title": "Auteur"
|
||||
},
|
||||
"distribution": {
|
||||
"title": "Dernière distribution",
|
||||
"license": "Licence",
|
||||
"size": "Taille",
|
||||
"file-count": "nombre de fichiers"
|
||||
},
|
||||
"maintainers": {
|
||||
"title": "Mainteneurs"
|
||||
},
|
||||
"contributors": {
|
||||
"title": "Contributeurs"
|
||||
},
|
||||
"engines": {
|
||||
"npm-version": "Version NPM",
|
||||
"node-js": "NODE JS"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"powered-by": "Propulsé par",
|
||||
"made-with-love-on": "Fait avec <0>♥</0> sur"
|
||||
},
|
||||
"button": {
|
||||
"close": "Fermer",
|
||||
"cancel": "Annuler",
|
||||
"login": "Se connecter",
|
||||
"logout": "Se déconnecter",
|
||||
"go-to-the-home-page": "Aller à la page d'accueil",
|
||||
"learn-more": "En savoir plus",
|
||||
"fund-this-package": "<0>Financer</0> ce paquet"
|
||||
},
|
||||
"error": {
|
||||
"unspecific": "Quelque chose a mal tourné.",
|
||||
"404": {
|
||||
"page-not-found": "404 - Page non trouvée",
|
||||
"sorry-we-could-not-find-it": "Desolé, nous n'avons rien retrouvé..."
|
||||
},
|
||||
"app-context-not-correct-used": "Le contexte de l'application n'a pas été utilisé correctement",
|
||||
"theme-context-not-correct-used": "Le contexte du thème n'a pas été utilisé correctement",
|
||||
"package-meta-is-required-at-detail-context": "packageMeta est obligatoire à DetailContext"
|
||||
},
|
||||
"lng": {
|
||||
"english": "Anglais",
|
||||
"japanese": "Japonais",
|
||||
"portuguese": "Portugais",
|
||||
"spanish": "Espagnol",
|
||||
"german": "Allemand",
|
||||
"chinese": "Chinois",
|
||||
"french": "Français",
|
||||
"ukraine": "Ukrainien",
|
||||
"khmer": "Khmer"
|
||||
},
|
||||
"help-to-translate": "Aide à la traduction",
|
||||
"change-language": "Changer la langue"
|
||||
}
|
152
i18n/translations/ja-JP.json
Normal file
152
i18n/translations/ja-JP.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"copy-to-clipboard": "クリップボードにコピー",
|
||||
"author-anonymous": "匿名",
|
||||
"action-bar-action": {
|
||||
"visit-home-page": "ホームページへ移動",
|
||||
"open-an-issue": "課題を開く",
|
||||
"download-tarball": "tar形式でダウンロード"
|
||||
},
|
||||
"dialog": {
|
||||
"registry-info": {
|
||||
"title": "レジストリの設定方法"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"documentation": "ドキュメント",
|
||||
"registry-info": "レジストリ情報",
|
||||
"greetings": "こんにちは、"
|
||||
},
|
||||
"search": {
|
||||
"packages": "パッケージを検索"
|
||||
},
|
||||
"auto-complete": {
|
||||
"loading": "ロード中...",
|
||||
"no-results-found": "パッケージが見つかりませんでした"
|
||||
},
|
||||
"tab": {
|
||||
"uplinks": "アップリンク",
|
||||
"versions": "バージョン情報",
|
||||
"dependencies": "依存パッケージ",
|
||||
"readme": "Readme"
|
||||
},
|
||||
"uplinks": {
|
||||
"title": "アップリンク",
|
||||
"no-items": "{{name}}にアップリンクはありません"
|
||||
},
|
||||
"versions": {
|
||||
"current-tags": "タグ一覧",
|
||||
"version-history": "バージョン履歴",
|
||||
"not-available": "利用できません"
|
||||
},
|
||||
"package": {
|
||||
"published-on": "{{time}}に更新されました •",
|
||||
"version": "v{{version}}",
|
||||
"visit-home-page": "ホームページへ移動",
|
||||
"homepage": "ホームページ",
|
||||
"open-an-issue": "課題を開く",
|
||||
"bugs": "バグ",
|
||||
"download": "{{what}}ダウンロード",
|
||||
"the-tar-file": "tar形式のファイル",
|
||||
"tarball": "tar形式でダウンロード"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-no-dependencies": "{{package}}に依存パッケージはありません",
|
||||
"dependency-block": "{{package}}@{{version}}"
|
||||
},
|
||||
"form": {
|
||||
"username": "ユーザ名",
|
||||
"password": "パスワード"
|
||||
},
|
||||
"form-placeholder": {
|
||||
"username": "あなたのユーザ名",
|
||||
"password": "あなたのパスワード"
|
||||
},
|
||||
"form-validation": {
|
||||
"required-field": "この項目は必ず入力して下さい",
|
||||
"required-min-length": "この項目は{{length}}文字以上入力して下さい",
|
||||
"unable-to-sign-in": "サインインできません",
|
||||
"username-or-password-cant-be-empty": "ユーザ名とパスワードは空にできません"
|
||||
},
|
||||
"help": {
|
||||
"title": "まだパッケージが登録されていません",
|
||||
"sub-title": "以下の手順で最初のパッケージを登録しましょう",
|
||||
"first-step": "1. ログイン",
|
||||
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
|
||||
"second-step": "2. 登録",
|
||||
"second-step-command-line": "npm publish --registry {{registryUrl}}",
|
||||
"third-step": "3. このページを再読み込みして下さい"
|
||||
},
|
||||
"sidebar": {
|
||||
"detail": {
|
||||
"latest-version": "最新バージョンは{{version}}です",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"installation": {
|
||||
"title": "インストール方法",
|
||||
"install-using-yarn": "yarnでインストール",
|
||||
"install-using-yarn-command": "yarn add {{packageName}}",
|
||||
"install-using-npm": "npmでインストール",
|
||||
"install-using-npm-command": "npm install {{packageName}}",
|
||||
"install-using-pnpm": "pnpmでインストール",
|
||||
"install-using-pnpm-command": "pnpm install {{packageName}}"
|
||||
},
|
||||
"repository": {
|
||||
"title": "リポジトリ"
|
||||
},
|
||||
"author": {
|
||||
"title": "作者"
|
||||
},
|
||||
"distribution": {
|
||||
"title": "最新の配信内容",
|
||||
"license": "ライセンス",
|
||||
"size": "サイズ",
|
||||
"file-count": "ファイル数"
|
||||
},
|
||||
"maintainers": {
|
||||
"title": "パッケージメンテナ"
|
||||
},
|
||||
"contributors": {
|
||||
"title": "コントリビューター"
|
||||
},
|
||||
"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": "閉じる",
|
||||
"cancel": "キャンセル",
|
||||
"login": "ログイン",
|
||||
"logout": "ログアウト",
|
||||
"go-to-the-home-page": "トップページに戻る",
|
||||
"learn-more": "もっと知る",
|
||||
"fund-this-package": "このパッケージに<0>投資</0>"
|
||||
},
|
||||
"error": {
|
||||
"unspecific": "何か問題が発生したようです。",
|
||||
"404": {
|
||||
"page-not-found": "404 - Page not found",
|
||||
"sorry-we-could-not-find-it": "残念ながら、ご指定のページはありませんでした…。"
|
||||
},
|
||||
"app-context-not-correct-used": "AppContextが正しく使用されませんでした",
|
||||
"theme-context-not-correct-used": "ThemeContextが正しく使用されませんでした",
|
||||
"package-meta-is-required-at-detail-context": "DetailContextではpackageMetaが必要です"
|
||||
},
|
||||
"lng": {
|
||||
"english": "英語",
|
||||
"japanese": "日本語",
|
||||
"portuguese": "ポルトガル語",
|
||||
"spanish": "スペイン語",
|
||||
"german": "ドイツ語",
|
||||
"chinese": "中国語",
|
||||
"french": "フランス語",
|
||||
"ukraine": "ウクライナ",
|
||||
"khmer": "Khmer"
|
||||
},
|
||||
"help-to-translate": "翻訳を助ける",
|
||||
"change-language": "言語を変更"
|
||||
}
|
152
i18n/translations/km-KH.json
Normal file
152
i18n/translations/km-KH.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"copy-to-clipboard": "ចម្លងទៅក្ដារតម្បៀតខ្ទាស់",
|
||||
"author-anonymous": "គ្មានអត្តសញ្ញាណ",
|
||||
"action-bar-action": {
|
||||
"visit-home-page": "ទស្សនាគេហទំព័រ",
|
||||
"open-an-issue": "បើកកិច្ចការ",
|
||||
"download-tarball": "ទាញយកកញ្ចប់ឯកសារ"
|
||||
},
|
||||
"dialog": {
|
||||
"registry-info": {
|
||||
"title": "ព័ត៌មានការចុះឈ្មោះ"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"documentation": "ឯកសារ",
|
||||
"registry-info": "ព័ត៌មានការចុះឈ្មោះ",
|
||||
"greetings": "សួស្តី"
|
||||
},
|
||||
"search": {
|
||||
"packages": "ស្វែងរកកញ្ចប់"
|
||||
},
|
||||
"auto-complete": {
|
||||
"loading": "កំពុងផ្ទុក...",
|
||||
"no-results-found": "រកមិនឃើញលទ្ធផល"
|
||||
},
|
||||
"tab": {
|
||||
"uplinks": "ភ្ជាប់តំណទៅ",
|
||||
"versions": "កំណែ",
|
||||
"dependencies": "អាស្រ័យលើ",
|
||||
"readme": "អានខ្ញុំជាមុនសិន"
|
||||
},
|
||||
"uplinks": {
|
||||
"title": "ភ្ជាប់តំណទៅ",
|
||||
"no-items": "{{name}} គ្មានភ្ជាប់តំណទៅ។"
|
||||
},
|
||||
"versions": {
|
||||
"current-tags": "ស្លាកបច្ចុប្បន្ន",
|
||||
"version-history": "ប្រវត្តិសាស្រ្តជំនាន់",
|
||||
"not-available": "Not available"
|
||||
},
|
||||
"package": {
|
||||
"published-on": "បោះពុម្ភផ្សាយ {{time}}។",
|
||||
"version": "v{{version}}",
|
||||
"visit-home-page": "ទស្សនាគេហទំព័រ",
|
||||
"homepage": "គេហទំព័រ",
|
||||
"open-an-issue": "បើកកិច្ចការ",
|
||||
"bugs": "កំហុស",
|
||||
"download": "ទាញយក {{what}}",
|
||||
"the-tar-file": "ឯកសាជា tar",
|
||||
"tarball": "ឯកសារ Tarball"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-no-dependencies": "{{package}} មិនមានភាពអាស្រ័យ។",
|
||||
"dependency-block": "{{package}}@{{version}}"
|
||||
},
|
||||
"form": {
|
||||
"username": "ឈ្មោះអ្នកប្រើប្រាស់",
|
||||
"password": "ពាក្យសម្ងាត់"
|
||||
},
|
||||
"form-placeholder": {
|
||||
"username": "ឈ្មោះអ្នកប្រើរបស់អ្នក",
|
||||
"password": "ពាក្យសម្ងាត់ខ្លាំងរបស់អ្នក"
|
||||
},
|
||||
"form-validation": {
|
||||
"required-field": "ចន្លោះនេះត្រូវតែំពេញ",
|
||||
"required-min-length": "ប្រអប់នេះទាមទារប្រវែងអប្បបរមា {{length}}",
|
||||
"unable-to-sign-in": "មិនអាចចូលបានទេ",
|
||||
"username-or-password-cant-be-empty": "ឈ្មោះអ្នកប្រើឬពាក្យសម្ងាត់មិនអាចទទេ!"
|
||||
},
|
||||
"help": {
|
||||
"title": "មិនទាន់មានការបោះពុម្ពផ្សាយកញ្ចប់នៅឡើយទេ។",
|
||||
"sub-title": "ដើម្បីផ្សព្វផ្សាយកញ្ចប់ដំបូងរបស់អ្នកគ្រាន់តែ៖",
|
||||
"first-step": "1. ចូល",
|
||||
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
|
||||
"second-step": "2. ផ្សព្វផ្សាយ",
|
||||
"second-step-command-line": "npm publish --registry {{registryUrl}}",
|
||||
"third-step": "ផ្ទុកទំព័រនេះឡើងវិញ។"
|
||||
},
|
||||
"sidebar": {
|
||||
"detail": {
|
||||
"latest-version": "ចុងក្រោយ v{{version}}",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"installation": {
|
||||
"title": "ការដំឡើង",
|
||||
"install-using-yarn": "ដំឡើងដោយប្រើ yarn",
|
||||
"install-using-yarn-command": "yarn add {{packageName}}",
|
||||
"install-using-npm": "ដំឡើងដោយប្រើ npm",
|
||||
"install-using-npm-command": "npm install {{packageName}}",
|
||||
"install-using-pnpm": "ដំឡើងដោយប្រើ pnpm",
|
||||
"install-using-pnpm-command": "pnpm install {{packageName}}"
|
||||
},
|
||||
"repository": {
|
||||
"title": "ឃ្លាំង"
|
||||
},
|
||||
"author": {
|
||||
"title": "អ្នកនិពន្ធ"
|
||||
},
|
||||
"distribution": {
|
||||
"title": "ការចែកចាយចុងក្រោយ",
|
||||
"license": "អាជ្ញាប័ណ្ណ",
|
||||
"size": "ទំហំ",
|
||||
"file-count": "រាប់ឯកសារ"
|
||||
},
|
||||
"maintainers": {
|
||||
"title": "អ្នកថែរក្សា"
|
||||
},
|
||||
"contributors": {
|
||||
"title": "អ្នកចូលរួមអភិវឌ្ឍ"
|
||||
},
|
||||
"engines": {
|
||||
"npm-version": "NPM កំណែ",
|
||||
"node-js": "NODE JS"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"powered-by": "ឧបត្ថម្ភដោយ",
|
||||
"made-with-love-on": "ផលិតជាមួយ <0>♥</0> លើ"
|
||||
},
|
||||
"button": {
|
||||
"close": "បិទ",
|
||||
"cancel": "បោះបង់",
|
||||
"login": "ចូល",
|
||||
"logout": "ចាកចេញ",
|
||||
"go-to-the-home-page": "ទៅទំព័រដើម",
|
||||
"learn-more": "ស្វែងយល់បន្ថែម",
|
||||
"fund-this-package": "<0>Fund</0> កញ្ចប់នេះ"
|
||||
},
|
||||
"error": {
|
||||
"unspecific": "មានអ្វីមួយមិនប្រក្រតី។",
|
||||
"404": {
|
||||
"page-not-found": "៤០៤ - រកមិនឃើញទំព័រ",
|
||||
"sorry-we-could-not-find-it": "សូមទោសយើងមិនអាចរកវាឃើញទេ..."
|
||||
},
|
||||
"app-context-not-correct-used": "បរិបទកម្មវិធីមិនត្រូវបានប្រើត្រឹមត្រូវទេ",
|
||||
"theme-context-not-correct-used": "បរិបទទម្រង់មិនត្រូវបានប្រើត្រឹមត្រូវទេ",
|
||||
"package-meta-is-required-at-detail-context": "packageMeta ត្រូវបានទាមទារនៅ DetailContext"
|
||||
},
|
||||
"lng": {
|
||||
"english": "អង់គ្លេស",
|
||||
"japanese": "ជប៉ុន",
|
||||
"portuguese": "ព័រទុយហ្កាល់",
|
||||
"spanish": "អេស្ប៉ាញ",
|
||||
"german": "អាឡឺម៉ង់",
|
||||
"chinese": "ចិន",
|
||||
"french": "បារាំង",
|
||||
"ukraine": "អ៊ុយក្រែន",
|
||||
"khmer": "ខ្មែរ"
|
||||
},
|
||||
"help-to-translate": "ជួយបកប្រែ",
|
||||
"change-language": "ប្ដូរភាសា"
|
||||
}
|
152
i18n/translations/pt-BR.json
Normal file
152
i18n/translations/pt-BR.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"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": "Feito 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"
|
||||
},
|
||||
"lng": {
|
||||
"english": "Inglês",
|
||||
"japanese": "Japonês",
|
||||
"portuguese": "Português",
|
||||
"spanish": "Espanhol",
|
||||
"german": "Alemão",
|
||||
"chinese": "Chinês",
|
||||
"french": "Francês",
|
||||
"ukraine": "Ucraniano",
|
||||
"khmer": "Khmer"
|
||||
},
|
||||
"help-to-translate": "Ajude a traduzir",
|
||||
"change-language": "Mudar idioma"
|
||||
}
|
152
i18n/translations/uk-UA.json
Normal file
152
i18n/translations/uk-UA.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"copy-to-clipboard": "Копіювати в буфер обміну",
|
||||
"author-anonymous": "Анонімний",
|
||||
"action-bar-action": {
|
||||
"visit-home-page": "Відвідати домашню сторінку",
|
||||
"open-an-issue": "Відкрити питання, проблему, ...",
|
||||
"download-tarball": "Завантажити архів"
|
||||
},
|
||||
"dialog": {
|
||||
"registry-info": {
|
||||
"title": "Інформація про реєстр"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"documentation": "Документація",
|
||||
"registry-info": "Інформація про реєстр",
|
||||
"greetings": "Привіт "
|
||||
},
|
||||
"search": {
|
||||
"packages": "Пошук пакетів"
|
||||
},
|
||||
"auto-complete": {
|
||||
"loading": "Завантаження...",
|
||||
"no-results-found": "Нічого не знайдено"
|
||||
},
|
||||
"tab": {
|
||||
"uplinks": "Публікації",
|
||||
"versions": "Версії",
|
||||
"dependencies": "Залежності",
|
||||
"readme": "Опис"
|
||||
},
|
||||
"uplinks": {
|
||||
"title": "Публікації",
|
||||
"no-items": "{{name}} не опубліковано."
|
||||
},
|
||||
"versions": {
|
||||
"current-tags": "Поточні теги",
|
||||
"version-history": "Історія версій",
|
||||
"not-available": "Недоступний"
|
||||
},
|
||||
"package": {
|
||||
"published-on": "Опубліковано {{time}} •",
|
||||
"version": "v{{version}}",
|
||||
"visit-home-page": "Відвідати домашню сторінку",
|
||||
"homepage": "Домашня сторінка",
|
||||
"open-an-issue": "Відкрити питання, проблему, ...",
|
||||
"bugs": "Помилки",
|
||||
"download": "Завантажити {{what}}",
|
||||
"the-tar-file": "файл tar",
|
||||
"tarball": "Завантажити"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-no-dependencies": "{{package}} не має залежностей.",
|
||||
"dependency-block": "{{package}}@{{version}}"
|
||||
},
|
||||
"form": {
|
||||
"username": "Ім'я користувача",
|
||||
"password": "Пароль"
|
||||
},
|
||||
"form-placeholder": {
|
||||
"username": "Ваше ім'я користувача",
|
||||
"password": "Ваш надійний пароль"
|
||||
},
|
||||
"form-validation": {
|
||||
"required-field": "Це поле є обов'язковим",
|
||||
"required-min-length": "Для цього поля потрібна мінімальна довжина {{length}}",
|
||||
"unable-to-sign-in": "Неможливо ввійти",
|
||||
"username-or-password-cant-be-empty": "Ім'я користувача або пароль не можуть бути порожніми !"
|
||||
},
|
||||
"help": {
|
||||
"title": "Пакети ще не опубліковані",
|
||||
"sub-title": "Опублікувати свій перший пакет просто:",
|
||||
"first-step": "1. Підключіться",
|
||||
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
|
||||
"second-step": "2. Опублікуйте",
|
||||
"second-step-command-line": "npm publish --registry {{registryUrl}}",
|
||||
"third-step": "3. Оновіть цю сторінку."
|
||||
},
|
||||
"sidebar": {
|
||||
"detail": {
|
||||
"latest-version": "Остання версія v{{version}}",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"installation": {
|
||||
"title": "Встановлення",
|
||||
"install-using-yarn": "Встановлення за допомогою yarn",
|
||||
"install-using-yarn-command": "yarn add {{packageName}}",
|
||||
"install-using-npm": "Встановлення за допомогою npm",
|
||||
"install-using-npm-command": "npm install {{packageName}}",
|
||||
"install-using-pnpm": "Встановлення за допомогою pnpm",
|
||||
"install-using-pnpm-command": "pnpm install {{packageName}}"
|
||||
},
|
||||
"repository": {
|
||||
"title": "Сховище"
|
||||
},
|
||||
"author": {
|
||||
"title": "Автор"
|
||||
},
|
||||
"distribution": {
|
||||
"title": "Умови поширення",
|
||||
"license": "Ліцензія",
|
||||
"size": "Розмір",
|
||||
"file-count": "кількість файлів"
|
||||
},
|
||||
"maintainers": {
|
||||
"title": "Технічне супроводження"
|
||||
},
|
||||
"contributors": {
|
||||
"title": "Автори"
|
||||
},
|
||||
"engines": {
|
||||
"npm-version": "NPM версія",
|
||||
"node-js": "NODE JS"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"powered-by": "Працює на",
|
||||
"made-with-love-on": "Зроблено з <0> ♥ </0> на"
|
||||
},
|
||||
"button": {
|
||||
"close": "Закрити",
|
||||
"cancel": "Скасувати",
|
||||
"login": "Вхід",
|
||||
"logout": "Вийти",
|
||||
"go-to-the-home-page": "Перейдіть на головну сторінку",
|
||||
"learn-more": "Вивчайте більше",
|
||||
"fund-this-package": "<0>Фінансувати</0> цей пакет"
|
||||
},
|
||||
"error": {
|
||||
"unspecific": "Щось пішло не так.",
|
||||
"404": {
|
||||
"page-not-found": "404 - Сторінку не знайдено",
|
||||
"sorry-we-could-not-find-it": "На жаль, ми не змогли його знайти ..."
|
||||
},
|
||||
"app-context-not-correct-used": "Контекст програми не використовувався правильно",
|
||||
"theme-context-not-correct-used": "Контекст теми використано неправильно",
|
||||
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
|
||||
},
|
||||
"lng": {
|
||||
"english": "Англійська",
|
||||
"japanese": "Японська",
|
||||
"portuguese": "Португальська",
|
||||
"spanish": "Іспанська",
|
||||
"german": "Німецька",
|
||||
"chinese": "Китайська",
|
||||
"french": "Французька",
|
||||
"ukraine": "Українська",
|
||||
"khmer": "Khmer"
|
||||
},
|
||||
"help-to-translate": "Допоможіть перекласти",
|
||||
"change-language": "Змінити мову"
|
||||
}
|
151
i18n/translations/zh-CN.json
Normal file
151
i18n/translations/zh-CN.json
Normal file
@ -0,0 +1,151 @@
|
||||
{
|
||||
"copy-to-clipboard": "复制到粘贴板",
|
||||
"author-anonymous": "匿名",
|
||||
"action-bar-action": {
|
||||
"visit-home-page": "访问主页",
|
||||
"open-an-issue": "提交问题",
|
||||
"download-tarball": "下载 tarball"
|
||||
},
|
||||
"dialog": {
|
||||
"registry-info": {
|
||||
"title": "注册信息"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"documentation": "文档",
|
||||
"registry-info": "登记信息",
|
||||
"greetings": "您好 "
|
||||
},
|
||||
"search": {
|
||||
"packages": "查找Packages"
|
||||
},
|
||||
"auto-complete": {
|
||||
"loading": "加载中...",
|
||||
"no-results-found": "没有找到结果"
|
||||
},
|
||||
"tab": {
|
||||
"uplinks": "Uplinks",
|
||||
"versions": "版本",
|
||||
"dependencies": "依赖",
|
||||
"readme": "Readme"
|
||||
},
|
||||
"uplinks": {
|
||||
"title": "Uplinks",
|
||||
"no-items": "{{name}} has no uplinks."
|
||||
},
|
||||
"versions": {
|
||||
"current-tags": "当前标签",
|
||||
"version-history": "历史版本",
|
||||
"not-available": "不可用"
|
||||
},
|
||||
"package": {
|
||||
"published-on": "发表于 {{time}} •",
|
||||
"version": "v{{version}}",
|
||||
"visit-home-page": "访问主页",
|
||||
"homepage": "主页",
|
||||
"open-an-issue": "提交问题",
|
||||
"bugs": "Bugs",
|
||||
"download": "下载 {{what}}",
|
||||
"the-tar-file": "tar 文件",
|
||||
"tarball": "Tarball"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-no-dependencies": "{{package}} 没有依赖包.",
|
||||
"dependency-block": "{{package}}@{{version}}"
|
||||
},
|
||||
"form": {
|
||||
"username": "用户名",
|
||||
"password": "密码"
|
||||
},
|
||||
"form-placeholder": {
|
||||
"username": "您的用户名",
|
||||
"password": "您的密码"
|
||||
},
|
||||
"form-validation": {
|
||||
"required-field": "必填项",
|
||||
"required-min-length": "长度不能小于{{length}}",
|
||||
"unable-to-sign-in": "登录失败",
|
||||
"username-or-password-cant-be-empty": "用户名或密码不能为空!"
|
||||
},
|
||||
"help": {
|
||||
"title": "还没有发布任何NPM包.",
|
||||
"sub-title": "发布第一个NPM包:",
|
||||
"first-step": "1. 登录",
|
||||
"first-step-command-line": "npm adduser --registry {{registryUrl}}",
|
||||
"second-step": "2. 发布",
|
||||
"second-step-command-line": "npm publish --registry {{registryUrl}}",
|
||||
"third-step": "3. 刷新当前页面."
|
||||
},
|
||||
"sidebar": {
|
||||
"detail": {
|
||||
"latest-version": "最新版本 v{{version}}",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"installation": {
|
||||
"title": "安装",
|
||||
"install-using-yarn": "使用yarn安装",
|
||||
"install-using-yarn-command": "yarn add {{packageName}}",
|
||||
"install-using-npm": "使用npm安装",
|
||||
"install-using-npm-command": "npm install {{packageName}}",
|
||||
"install-using-pnpm": "使用pnpm安装",
|
||||
"install-using-pnpm-command": "pnpm install {{packageName}}"
|
||||
},
|
||||
"repository": {
|
||||
"title": "库"
|
||||
},
|
||||
"author": {
|
||||
"title": "作者"
|
||||
},
|
||||
"distribution": {
|
||||
"title": "最后发布",
|
||||
"license": "License",
|
||||
"size": "大小",
|
||||
"file-count": "文件数量"
|
||||
},
|
||||
"maintainers": {
|
||||
"title": "维护人"
|
||||
},
|
||||
"contributors": {
|
||||
"title": "贡献者"
|
||||
},
|
||||
"engines": {
|
||||
"npm-version": "NPM版本",
|
||||
"node-js": "NODE JS"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"powered-by": "Powered by",
|
||||
"made-with-love-on": "Made with <0>♥</0> on"
|
||||
},
|
||||
"button": {
|
||||
"close": "关闭",
|
||||
"cancel": "取消",
|
||||
"login": "登录",
|
||||
"logout": "注销",
|
||||
"go-to-the-home-page": "跳转到主页",
|
||||
"learn-more": "了解更多",
|
||||
"fund-this-package": "<0>Fund</0> this package"
|
||||
},
|
||||
"error": {
|
||||
"unspecific": "发生错误.",
|
||||
"404": {
|
||||
"page-not-found": "404 - 页面不存在",
|
||||
"sorry-we-could-not-find-it": "对不起, 我们没找到..."
|
||||
},
|
||||
"app-context-not-correct-used": "The app context was not correct used",
|
||||
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
|
||||
},
|
||||
"lng": {
|
||||
"english": "英语",
|
||||
"japanese": "日语",
|
||||
"portuguese": "葡萄牙语",
|
||||
"spanish": "西班牙语",
|
||||
"german": "德语",
|
||||
"chinese": "中文",
|
||||
"french": "法语",
|
||||
"ukraine": "烏克蘭",
|
||||
"khmer": "Khmer"
|
||||
},
|
||||
"help-to-translate": "幫助翻译",
|
||||
"change-language": "改变语言"
|
||||
}
|
@ -1 +1 @@
|
||||
require.requireActual('babel/polyfill');
|
||||
jest.requireActual('babel/polyfill');
|
||||
|
@ -6,15 +6,15 @@ 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;
|
||||
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": 0
|
||||
}
|
||||
}
|
@ -3,12 +3,19 @@
|
||||
*/
|
||||
|
||||
import { Base64 } from 'js-base64';
|
||||
import addHours from 'date-fns/addHours';
|
||||
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`;
|
||||
}
|
||||
|
@ -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',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
158
package.json
158
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/ui-theme",
|
||||
"version": "0.3.6",
|
||||
"version": "1.7.1",
|
||||
"description": "Verdaccio User Interface",
|
||||
"author": {
|
||||
"name": "Verdaccio Core Team",
|
||||
@ -13,74 +13,82 @@
|
||||
"homepage": "https://verdaccio.org",
|
||||
"main": "index.js",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "8.2.0",
|
||||
"@commitlint/config-conventional": "8.2.0",
|
||||
"@material-ui/core": "4.6.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.35.0",
|
||||
"@testing-library/react": "9.3.2",
|
||||
"@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.23",
|
||||
"@types/enzyme": "3.10.4",
|
||||
"@types/jest": "25.1.4",
|
||||
"@types/js-base64": "2.3.1",
|
||||
"@types/lodash": "4.14.147",
|
||||
"@types/node": "12.12.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.4",
|
||||
"@types/react-router-dom": "5.1.2",
|
||||
"@types/request": "2.48.3",
|
||||
"@types/validator": "10.11.3",
|
||||
"@types/webpack-env": "1.14.1",
|
||||
"@typescript-eslint/parser": "2.7.0",
|
||||
"@verdaccio/babel-preset": "8.2.0",
|
||||
"@verdaccio/commons-api": "8.3.0",
|
||||
"@verdaccio/eslint-config": "8.2.0",
|
||||
"@verdaccio/types": "8.3.0",
|
||||
"@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": "2.7.0",
|
||||
"css-loader": "3.4.2",
|
||||
"dayjs": "1.8.19",
|
||||
"detect-secrets": "1.0.5",
|
||||
"emotion": "9.2.12",
|
||||
"enzyme": "3.10.0",
|
||||
"enzyme-adapter-react-16": "1.15.1",
|
||||
"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.6.0",
|
||||
"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.1",
|
||||
"eslint-plugin-react": "7.16.0",
|
||||
"eslint-plugin-prettier": "3.1.2",
|
||||
"eslint-plugin-react": "7.17.0",
|
||||
"eslint-plugin-react-hooks": "2.3.0",
|
||||
"eslint-plugin-verdaccio": "8.2.0",
|
||||
"file-loader": "4.2.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",
|
||||
"in-publish": "2.0.1",
|
||||
"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": "9.4.3",
|
||||
"lint-staged": "9.5.0",
|
||||
"localstorage-memory": "1.0.3",
|
||||
"lockfile-lint": "2.2.0",
|
||||
"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.3",
|
||||
@ -89,36 +97,37 @@
|
||||
"puppeteer": "2.0.0",
|
||||
"react": "16.12.0",
|
||||
"react-autosuggest": "9.4.3",
|
||||
"react-dom": "16.12.0",
|
||||
"react-emotion": "9.2.12",
|
||||
"react-hot-loader": "4.12.17",
|
||||
"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.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.4",
|
||||
"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.2",
|
||||
"typescript": "3.7.4",
|
||||
"uglifyjs-webpack-plugin": "2.2.0",
|
||||
"url-loader": "2.2.0",
|
||||
"validator": "12.0.0",
|
||||
"verdaccio": "4.3.4",
|
||||
"verdaccio-auth-memory": "8.3.0",
|
||||
"verdaccio-memory": "8.3.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.10",
|
||||
"webpack-dev-server": "3.9.0",
|
||||
"webpack-dev-server": "3.10.1",
|
||||
"webpack-merge": "4.2.2",
|
||||
"whatwg-fetch": "3.0.0",
|
||||
"xss": "1.0.6"
|
||||
@ -131,11 +140,11 @@
|
||||
"bundlesize": [
|
||||
{
|
||||
"path": "./static/vendors.*.js",
|
||||
"maxSize": "180 kB"
|
||||
"maxSize": "210 kB"
|
||||
},
|
||||
{
|
||||
"path": "./static/main.*.js",
|
||||
"maxSize": "30 kB"
|
||||
"maxSize": "35 kB"
|
||||
},
|
||||
{
|
||||
"path": "./static/[0-9].*.{js,css}",
|
||||
@ -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",
|
||||
@ -179,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 . --ext .js,.ts,.tsx",
|
||||
"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",
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ getStdin()
|
||||
repo: repoName,
|
||||
tag_name: tag,
|
||||
body: changelog,
|
||||
draft: true,
|
||||
draft: false,
|
||||
})
|
||||
)
|
||||
.catch(err => {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
232
src/App/App.tsx
232
src/App/App.tsx
@ -1,188 +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 { 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 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 loadDayJSLocale from '../design-tokens/load-dayjs-locale';
|
||||
|
||||
import AppRoute from './AppRoute';
|
||||
import { AppProps, AppContextProvider } from './AppContext';
|
||||
import AppContextProvider from './AppContextProvider';
|
||||
import AppRoute, { history } from './AppRoute';
|
||||
|
||||
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 (
|
||||
<>
|
||||
<StyleBaseline />
|
||||
<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
18
src/App/AppContext.ts
Normal 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;
|
@ -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;
|
41
src/App/AppContextProvider.tsx
Normal file
41
src/App/AppContextProvider.tsx
Normal 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;
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
1514
src/App/__snapshots__/App.test.tsx.snap
Normal file
1514
src/App/__snapshots__/App.test.tsx.snap
Normal file
File diff suppressed because it is too large
Load Diff
@ -1 +1,2 @@
|
||||
export { default } from './App';
|
||||
export { default as AppContextProvider } from './AppContextProvider';
|
||||
|
@ -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',
|
||||
});
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -1,133 +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;
|
||||
|
67
src/components/ActionBar/ActionBarAction.tsx
Normal file
67
src/components/ActionBar/ActionBarAction.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
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 }>(({ theme }) => ({
|
||||
backgroundColor: theme?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.cyanBlue,
|
||||
color: theme?.palette.white,
|
||||
marginRight: 10,
|
||||
':hover': {
|
||||
color: theme?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.cyanBlue,
|
||||
background: theme?.palette.white,
|
||||
},
|
||||
}));
|
||||
|
||||
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;
|
@ -1,7 +1,123 @@
|
||||
// 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-z6z5me 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>"`;
|
||||
.emotion-0:hover {
|
||||
color: #4b5e40;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
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-z6z5me 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>"`;
|
||||
<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 no action bar data 1`] = `
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-77"
|
||||
/>
|
||||
`;
|
||||
|
18
src/components/ActionBar/download-tarball.ts
Normal file
18
src/components/ActionBar/download-tarball.ts
Normal 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;
|
@ -1 +1,2 @@
|
||||
export { default } from './ActionBar';
|
||||
export { default as downloadTarball } from './download-tarball';
|
||||
|
@ -1,17 +0,0 @@
|
||||
import styled from 'react-emotion';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
import ListItem from '../../muiComponents/ListItem';
|
||||
import FloatingActionButton from '../../muiComponents/FloatingActionButton';
|
||||
|
||||
export const ActionListItem = styled(ListItem)({
|
||||
paddingTop: 0,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
});
|
||||
|
||||
export const Fab = styled(FloatingActionButton)({
|
||||
backgroundColor: colors.primary,
|
||||
color: colors.white,
|
||||
marginRight: '10px',
|
||||
});
|
@ -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';
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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-zw46c6 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-fipixf 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-zw46c6 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-fipixf 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>"`;
|
||||
|
@ -1,14 +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 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,
|
||||
|
@ -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 { 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,72 +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;
|
||||
|
@ -1,60 +1,44 @@
|
||||
import React from 'react';
|
||||
import styled, { css } from 'react-emotion';
|
||||
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',
|
||||
});
|
||||
|
@ -1,37 +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 };
|
@ -1,4 +0,0 @@
|
||||
import { AvatarTooltip } from './AvatarTooltip';
|
||||
|
||||
export { AvatarTooltip };
|
||||
export default AvatarTooltip;
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>"`;
|
||||
|
@ -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)({});
|
||||
|
@ -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';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import CardContent from '../../muiComponents/CardContent';
|
||||
import { PackageDependencies } from '../../../types/packageMeta';
|
||||
@ -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;
|
||||
|
@ -1,18 +1,18 @@
|
||||
import styled from 'react-emotion';
|
||||
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',
|
||||
});
|
||||
|
||||
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',
|
||||
|
@ -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';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useState, ChangeEvent, useContext } from 'react';
|
||||
import React, { useState, useContext } from 'react';
|
||||
|
||||
import { DetailContext } from '../../pages/Version';
|
||||
import Box from '../../muiComponents/Box';
|
||||
@ -8,24 +8,19 @@ 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>
|
||||
);
|
||||
};
|
||||
|
@ -1,44 +1,31 @@
|
||||
import React, { ChangeEvent, useState, useEffect } from 'react';
|
||||
import styled from 'react-emotion';
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { default as MuiTabs } from '../../muiComponents/Tabs';
|
||||
import Tab from '../../muiComponents/Tab';
|
||||
|
||||
import { TabPosition } from './tabs';
|
||||
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,
|
||||
});
|
||||
|
@ -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"
|
||||
|
@ -1,6 +1,6 @@
|
||||
export enum TabPosition {
|
||||
README = 'Readme',
|
||||
DEPENDENCIES = 'Dependencies',
|
||||
VERSIONS = 'Versions',
|
||||
UPLINKS = 'Uplinks',
|
||||
README = 'readme',
|
||||
DEPENDENCIES = 'dependencies',
|
||||
VERSIONS = 'versions',
|
||||
UPLINKS = 'uplinks',
|
||||
}
|
||||
|
@ -1,80 +1,52 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
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 CardContent from '../../muiComponents/CardContent';
|
||||
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 = 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),
|
||||
}));
|
||||
|
103
src/components/DetailSidebar/DetailSidebarFundButton.test.tsx
Normal file
103
src/components/DetailSidebar/DetailSidebarFundButton.test.tsx
Normal 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();
|
||||
});
|
||||
});
|
48
src/components/DetailSidebar/DetailSidebarFundButton.tsx
Normal file
48
src/components/DetailSidebar/DetailSidebarFundButton.tsx
Normal 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';
|
||||
|
||||
/* 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;
|
||||
|
||||
const StyledLink = styled(Link)<{ theme?: Theme }>(({ theme }) => ({
|
||||
marginTop: theme?.spacing(1),
|
||||
marginBottom: theme?.spacing(1),
|
||||
textDecoration: 'none',
|
||||
display: 'block',
|
||||
}));
|
||||
|
||||
const StyledFavoriteIcon = styled(Favorite)<{ theme?: Theme }>(({ theme }) => ({
|
||||
color: theme?.palette.orange,
|
||||
}));
|
||||
|
||||
const StyledFundStrong = styled('strong')({
|
||||
marginRight: 3,
|
||||
});
|
38
src/components/DetailSidebar/DetailSidebarTitle.tsx
Normal file
38
src/components/DetailSidebar/DetailSidebarTitle.tsx
Normal 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,
|
||||
}));
|
@ -1,24 +0,0 @@
|
||||
import styled from 'react-emotion';
|
||||
|
||||
import ListItem from '../../muiComponents/ListItem';
|
||||
import ListItemText from '../../muiComponents/ListItemText';
|
||||
|
||||
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',
|
||||
});
|
@ -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>
|
||||
);
|
||||
|
||||
|
@ -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;
|
||||
|
30
src/components/Developers/DevelopersTitle.tsx
Normal file
30
src/components/Developers/DevelopersTitle.tsx
Normal 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',
|
||||
}));
|
File diff suppressed because it is too large
Load Diff
12
src/components/Developers/get-unique-developer-values.ts
Normal file
12
src/components/Developers/get-unique-developer-values.ts
Normal 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;
|
@ -1 +1,2 @@
|
||||
export { default } from './Developers';
|
||||
export { DeveloperType } from './types';
|
||||
|
@ -1,9 +1,8 @@
|
||||
import styled from 'react-emotion';
|
||||
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,13 +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(FloatingActionButton)({
|
||||
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,
|
||||
}));
|
||||
|
4
src/components/Developers/types.ts
Normal file
4
src/components/Developers/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum DeveloperType {
|
||||
CONTRIBUTORS = 'contributors',
|
||||
MAINTAINERS = 'maintainers',
|
||||
}
|
@ -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';
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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-1huthg8 estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-42zb18 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>file count</b>: 7</span></div><div class=\\"MuiChip-root css-42zb18 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-1huthg8 estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-42zb18 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>file count</b>: 7</span></div><div class=\\"MuiChip-root css-42zb18 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>size</b>: 10.00 Bytes</span></div><div class=\\"MuiChip-root css-42zb18 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-1huthg8 estxrtg1 MuiListItem-gutters MuiListItem-button\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><div class=\\"MuiChip-root css-42zb18 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>file count</b>: 7</span></div><div class=\\"MuiChip-root css-42zb18 estxrtg2\\"><span class=\\"MuiChip-label\\"><b>size</b>: 10.00 Bytes</span></div><div class=\\"MuiChip-root css-42zb18 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>"`;
|
||||
|
@ -1,16 +1,15 @@
|
||||
import styled from 'react-emotion';
|
||||
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,
|
||||
@ -18,11 +17,11 @@ export const DistListItem = styled(ListItem)({
|
||||
});
|
||||
|
||||
export const DistChips = styled(Chip)({
|
||||
marginRight: '5px',
|
||||
marginRight: 5,
|
||||
textTransform: 'capitalize',
|
||||
});
|
||||
|
||||
export const DownloadButton = styled(FloatingActionButton)({
|
||||
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,
|
||||
}));
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { mount } from '../../utils/test-enzyme';
|
||||
import { DetailContext } from '../../pages/Version';
|
||||
import { PackageMetaInterface } from '../../../types/packageMeta';
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { DetailContext } from '../../pages/Version';
|
||||
import Avatar from '../../muiComponents/Avatar';
|
||||
@ -12,19 +13,19 @@ import node from './img/node.png';
|
||||
|
||||
const Engine: React.FC = () => {
|
||||
const { packageMeta } = useContext(DetailContext);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const engines = packageMeta && packageMeta.latest && packageMeta.latest.engines;
|
||||
const engines = packageMeta?.latest?.engines;
|
||||
|
||||
if (!engines || (!engines.node && !engines.npm)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* eslint-disable react/jsx-max-depth */
|
||||
return (
|
||||
<Grid container={true}>
|
||||
{engines.node && (
|
||||
<Grid item={true} xs={6}>
|
||||
<List subheader={<StyledText variant={'subtitle1'}>{'node JS'}</StyledText>}>
|
||||
<List subheader={<StyledText variant={'subtitle1'}>{t('sidebar.engines.node-js')}</StyledText>}>
|
||||
<EngineListItem button={true}>
|
||||
<Avatar src={node} />
|
||||
<ListItemText primary={engines.node} />
|
||||
@ -35,7 +36,7 @@ const Engine: React.FC = () => {
|
||||
|
||||
{engines.npm && (
|
||||
<Grid item={true} xs={6}>
|
||||
<List subheader={<StyledText variant={'subtitle1'}>{'NPM version'}</StyledText>}>
|
||||
<List subheader={<StyledText variant={'subtitle1'}>{t('sidebar.engines.npm-version')}</StyledText>}>
|
||||
<EngineListItem button={true}>
|
||||
<Avatar src={npm} />
|
||||
<ListItemText primary={engines.npm} />
|
||||
@ -45,7 +46,6 @@ const Engine: React.FC = () => {
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
/* eslint-enable react/jsx-max-depth */
|
||||
};
|
||||
|
||||
export default Engine;
|
||||
|
@ -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-circle MuiAvatar-colorDefault\\"></div><div class=\\"MuiListItemText-root\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">>= 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-circle MuiAvatar-colorDefault\\"></div><div class=\\"MuiListItemText-root\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">>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\\">>= 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\\">>3</span></div><span class=\\"MuiTouchRipple-root\\"></span></div></ul></div></div>"`;
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -1,53 +1,39 @@
|
||||
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" />
|
||||
<Flag name="germany" 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;
|
||||
|
@ -1,3 +1,284 @@
|
||||
// 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-151fgib ek145dl0\\"><title>Earth</title><use xlink:href=\\"[object Object]#earth\\"></use></svg><span class=\\"css-8631ip ezbsl486\\"><svg class=\\"ezbsl488 css-1ah96gu ek145dl0\\"><title>Spain</title><use xlink:href=\\"[object Object]#spain\\"></use></svg><svg class=\\"ezbsl488 css-1ah96gu ek145dl0\\"><title>Nicaragua</title><use xlink:href=\\"[object Object]#nicaragua\\"></use></svg><svg class=\\"ezbsl488 css-1ah96gu ek145dl0\\"><title>India</title><use xlink:href=\\"[object Object]#india\\"></use></svg><svg class=\\"ezbsl488 css-1ah96gu ek145dl0\\"><title>Brazil</title><use xlink:href=\\"[object Object]#brazil\\"></use></svg><svg class=\\"ezbsl488 css-1ah96gu ek145dl0\\"><title>China</title><use xlink:href=\\"[object Object]#china\\"></use></svg><svg class=\\"ezbsl488 css-1ah96gu 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-ommwhu 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-41 {
|
||||
background: #f9f9f9;
|
||||
border-top: 1px solid #e3e3e3;
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.emotion-39 {
|
||||
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-39 {
|
||||
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-39 {
|
||||
max-width: 1240px;
|
||||
}
|
||||
}
|
||||
|
||||
.emotion-30 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width:768px) {
|
||||
.emotion-30 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.emotion-0 {
|
||||
color: #e25555;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.emotion-28 {
|
||||
position: relative;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.emotion-28:hover .emotion-27 {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.emotion-3 {
|
||||
box-sizing: initial;
|
||||
display: inline-block;
|
||||
cursor: default;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.emotion-26 {
|
||||
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-26: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-37 {
|
||||
-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-37 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.emotion-35 {
|
||||
box-sizing: initial;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.emotion-32 {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
<div
|
||||
class="emotion-41 emotion-42"
|
||||
>
|
||||
<div
|
||||
class="emotion-39 emotion-40"
|
||||
>
|
||||
<div
|
||||
class="emotion-30 emotion-31"
|
||||
>
|
||||
Made with
|
||||
<span
|
||||
class="emotion-0 emotion-1"
|
||||
>
|
||||
♥
|
||||
</span>
|
||||
on
|
||||
<span
|
||||
class="emotion-28 emotion-29"
|
||||
>
|
||||
<svg
|
||||
class="emotion-2 emotion-3 emotion-4"
|
||||
>
|
||||
<title>
|
||||
Earth
|
||||
</title>
|
||||
<use
|
||||
xlink:href="[object Object]#earth"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="emotion-26 emotion-27"
|
||||
>
|
||||
<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>
|
||||
<svg
|
||||
class="emotion-5 emotion-6 emotion-4"
|
||||
>
|
||||
<title>
|
||||
Germany
|
||||
</title>
|
||||
<use
|
||||
xlink:href="[object Object]#germany"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="emotion-37 emotion-38"
|
||||
>
|
||||
Powered by
|
||||
<span
|
||||
class="emotion-5 emotion-35 emotion-36"
|
||||
title="Verdaccio"
|
||||
>
|
||||
<img
|
||||
alt="Verdaccio"
|
||||
class="emotion-32 emotion-33"
|
||||
src="[object Object]"
|
||||
/>
|
||||
</span>
|
||||
/ v.1.0.0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -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 === 'light' ? theme?.palette.snow : theme?.palette.cyanBlue,
|
||||
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?.breakPoints.medium}px)`]: {
|
||||
minWidth: 400,
|
||||
maxWidth: 800,
|
||||
margin: 'auto',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
});
|
||||
[`@media (min-width: ${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?.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 }>(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
background: 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: `${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 }>(({ theme }) => ({
|
||||
color: theme?.palette.love,
|
||||
padding: '0 5px',
|
||||
}));
|
||||
|
||||
export const Flag = styled(Icon)({
|
||||
'&&': {
|
||||
padding: '0 5px',
|
||||
},
|
||||
padding: '0 5px',
|
||||
});
|
||||
|
||||
export const Logo = Flag;
|
||||
|
@ -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,82 +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>
|
||||
);
|
||||
|
||||
@ -103,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>
|
||||
);
|
||||
|
||||
@ -123,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>
|
||||
);
|
||||
|
||||
@ -136,13 +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')
|
||||
);
|
||||
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');
|
||||
});
|
||||
|
@ -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,31 +14,44 @@ 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>
|
||||
@ -51,10 +68,11 @@ const Header: React.FC<Props> = ({ logo, withoutSearch, username, onLogout, onTo
|
||||
<Search />
|
||||
</InnerMobileNavBar>
|
||||
<Button color="inherit" onClick={() => setShowMobileNavBar(false)}>
|
||||
{'Cancel'}
|
||||
{t('button.cancel')}
|
||||
</Button>
|
||||
</MobileNavBar>
|
||||
)}
|
||||
{!user && <LoginDialog onClose={() => setShowLoginModal(false)} open={showLoginModal} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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 />
|
||||
|
@ -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;
|
@ -1,4 +1,5 @@
|
||||
import React, { MouseEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
|
||||
import IconButton from '../../muiComponents/IconButton';
|
||||
@ -16,7 +17,6 @@ interface Props {
|
||||
onLoggedInMenuClose: () => void;
|
||||
}
|
||||
|
||||
/* eslint-disable react/jsx-max-depth */
|
||||
const HeaderMenu: React.FC<Props> = ({
|
||||
onLogout,
|
||||
username,
|
||||
@ -24,35 +24,38 @@ const HeaderMenu: React.FC<Props> = ({
|
||||
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',
|
||||
}}
|
||||
onClose={onLoggedInMenuClose}
|
||||
open={isMenuOpen}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}>
|
||||
<MenuItem disabled={true}>
|
||||
<HeaderGreetings username={username} />
|
||||
</MenuItem>
|
||||
<MenuItem button={true} id="header--button-logout" onClick={onLogout}>
|
||||
{'Logout'}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}) => {
|
||||
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>
|
||||
<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;
|
||||
|
@ -1,6 +1,9 @@
|
||||
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 LanguageSwitch from '../LanguageSwitch';
|
||||
|
||||
import { RightSide } from './styles';
|
||||
import HeaderToolTip from './HeaderToolTip';
|
||||
@ -23,9 +26,16 @@ const HeaderRight: React.FC<Props> = ({
|
||||
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]);
|
||||
@ -52,13 +62,26 @@ const HeaderRight: React.FC<Props> = ({
|
||||
onToggleLogin();
|
||||
};
|
||||
|
||||
const handleToggleDarkLightMode = () => {
|
||||
setTimeout(() => {
|
||||
themeContext.setIsDarkMode(!themeContext.isDarkMode);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
return (
|
||||
<RightSide>
|
||||
<RightSide data-testid="header-right">
|
||||
{!withoutSearch && (
|
||||
<HeaderToolTip onClick={onToggleMobileNav} title={'Search packages'} tooltipIconType={'search'} />
|
||||
<HeaderToolTip onClick={onToggleMobileNav} title={t('search.packages')} tooltipIconType={'search'} />
|
||||
)}
|
||||
<HeaderToolTip title={'Documentation'} tooltipIconType={'help'} />
|
||||
<HeaderToolTip onClick={onOpenRegistryInfoDialog} title={'Registry Information'} tooltipIconType={'info'} />
|
||||
<LanguageSwitch />
|
||||
<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}
|
||||
@ -70,7 +93,7 @@ const HeaderRight: React.FC<Props> = ({
|
||||
/>
|
||||
) : (
|
||||
<Button color="inherit" data-testid="header--button-login" onClick={handleToggleLogin}>
|
||||
{'Login'}
|
||||
{t('button.login')}
|
||||
</Button>
|
||||
)}
|
||||
</RightSide>
|
||||
|
@ -2,12 +2,14 @@ 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, StyledLink } from './styles';
|
||||
|
||||
export type TooltipIconType = 'search' | 'help' | 'info';
|
||||
export type TooltipIconType = 'search' | 'help' | 'info' | 'dark-mode' | 'light-mode';
|
||||
interface Props {
|
||||
tooltipIconType: TooltipIconType;
|
||||
onClick?: () => void;
|
||||
@ -50,6 +52,21 @@ const HeaderToolTipIcon = forwardRef<HeaderToolTipIconRef, Props>(function Heade
|
||||
<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;
|
||||
}
|
||||
|
@ -1,28 +1,188 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Header /> component with logged in state should load the component in logged in state 1`] = `
|
||||
.emotion-28 {
|
||||
background-color: #4b5e40;
|
||||
color: #fff;
|
||||
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-28 .emotion-13 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.emotion-28 .emotion-17 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.emotion-28 .e1jf5lit4 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:1024px) {
|
||||
.emotion-28 .emotion-27 {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
@media (min-width:1275px) {
|
||||
.emotion-28 .emotion-27 {
|
||||
max-width: 1240px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emotion-26 {
|
||||
-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-24 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.emotion-16 {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.emotion-20 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width:768px) {
|
||||
.emotion-20 {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.emotion-18 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width:768px) {
|
||||
.emotion-18 {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.emotion-22 {
|
||||
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-28 emotion-29 MuiAppBar-colorPrimary"
|
||||
data-testid="header"
|
||||
>
|
||||
<div
|
||||
class="MuiToolbar-root MuiToolbar-regular css-1bjere7 emotion-8 MuiToolbar-gutters"
|
||||
class="MuiToolbar-root MuiToolbar-regular emotion-26 emotion-27 MuiToolbar-gutters"
|
||||
>
|
||||
<div
|
||||
class="MuiToolbar-root MuiToolbar-regular css-i5xjw9 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-12prohx emotion-3"
|
||||
class="emotion-12 emotion-13"
|
||||
>
|
||||
<div
|
||||
class="css-1crzyyo emotion-2"
|
||||
class="emotion-10 emotion-11"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
@ -34,13 +194,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-fvu7gn MuiInputAdornment-positionStart"
|
||||
class="MuiInputAdornment-root emotion-4 emotion-5 MuiInputAdornment-positionStart"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@ -57,7 +217,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 +225,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 +234,11 @@ exports[`<Header /> component with logged in state should load the component in
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiToolbar-root MuiToolbar-regular css-1qii1b7 emotion-7 MuiToolbar-gutters"
|
||||
class="MuiToolbar-root MuiToolbar-regular emotion-24 emotion-25 MuiToolbar-gutters"
|
||||
data-testid="header-right"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiIconButton-root css-13o7eu2 emotion-5 MuiIconButton-colorInherit"
|
||||
class="MuiButtonBase-root MuiIconButton-root emotion-16 emotion-17 MuiIconButton-colorInherit"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
@ -100,8 +261,53 @@ exports[`<Header /> component with logged in state should load the component in
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="emotion-20 emotion-21"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit"
|
||||
tabindex="0"
|
||||
title="Change language"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label ForwardRef(Button)-label-322"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="emotion-18 emotion-19"
|
||||
>
|
||||
English
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall"
|
||||
focusable="false"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<a
|
||||
class="css-kbn7if emotion-6"
|
||||
class="emotion-22 emotion-23"
|
||||
data-testid="header--tooltip-documentation"
|
||||
href="https://verdaccio.org/docs/en/installation"
|
||||
rel="noopener noreferrer"
|
||||
@ -164,7 +370,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"
|
||||
@ -194,28 +424,188 @@ 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-28 {
|
||||
background-color: #4b5e40;
|
||||
color: #fff;
|
||||
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-28 .emotion-13 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.emotion-28 .emotion-17 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.emotion-28 .e1jf5lit4 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:1024px) {
|
||||
.emotion-28 .emotion-27 {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
@media (min-width:1275px) {
|
||||
.emotion-28 .emotion-27 {
|
||||
max-width: 1240px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emotion-26 {
|
||||
-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-24 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.emotion-16 {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.emotion-20 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width:768px) {
|
||||
.emotion-20 {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.emotion-18 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width:768px) {
|
||||
.emotion-18 {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.emotion-22 {
|
||||
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-28 emotion-29 MuiAppBar-colorPrimary"
|
||||
data-testid="header"
|
||||
>
|
||||
<div
|
||||
class="MuiToolbar-root MuiToolbar-regular css-1bjere7 emotion-8 MuiToolbar-gutters"
|
||||
class="MuiToolbar-root MuiToolbar-regular emotion-26 emotion-27 MuiToolbar-gutters"
|
||||
>
|
||||
<div
|
||||
class="MuiToolbar-root MuiToolbar-regular css-i5xjw9 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-12prohx emotion-3"
|
||||
class="emotion-12 emotion-13"
|
||||
>
|
||||
<div
|
||||
class="css-1crzyyo emotion-2"
|
||||
class="emotion-10 emotion-11"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
@ -227,13 +617,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-fvu7gn MuiInputAdornment-positionStart"
|
||||
class="MuiInputAdornment-root emotion-4 emotion-5 MuiInputAdornment-positionStart"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@ -250,7 +640,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=""
|
||||
@ -258,7 +648,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"
|
||||
/>
|
||||
@ -267,10 +657,11 @@ exports[`<Header /> component with logged in state should load the component in
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiToolbar-root MuiToolbar-regular css-1qii1b7 emotion-7 MuiToolbar-gutters"
|
||||
class="MuiToolbar-root MuiToolbar-regular emotion-24 emotion-25 MuiToolbar-gutters"
|
||||
data-testid="header-right"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiIconButton-root css-13o7eu2 emotion-5 MuiIconButton-colorInherit"
|
||||
class="MuiButtonBase-root MuiIconButton-root emotion-16 emotion-17 MuiIconButton-colorInherit"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
@ -293,8 +684,53 @@ exports[`<Header /> component with logged in state should load the component in
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="emotion-20 emotion-21"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit"
|
||||
tabindex="0"
|
||||
title="Change language"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label ForwardRef(Button)-label-119"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="emotion-18 emotion-19"
|
||||
>
|
||||
English
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall"
|
||||
focusable="false"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<a
|
||||
class="css-kbn7if emotion-6"
|
||||
class="emotion-22 emotion-23"
|
||||
data-testid="header--tooltip-documentation"
|
||||
href="https://verdaccio.org/docs/en/installation"
|
||||
rel="noopener noreferrer"
|
||||
@ -355,6 +791,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"
|
||||
|
@ -1,7 +1,7 @@
|
||||
import styled, { css } from 'react-emotion';
|
||||
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 AppBar from '../../muiComponents/AppBar';
|
||||
import Toolbar from '../../muiComponents/Toolbar';
|
||||
@ -26,22 +26,22 @@ export const LeftSide = styled(RightSide)({
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
export const MobileNavBar = styled('div')({
|
||||
export const MobileNavBar = styled('div')<{ theme?: Theme }>(props => ({
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
borderBottom: `1px solid ${colors.greyLight}`,
|
||||
borderBottom: `1px solid ${props.theme && props.theme.palette.greyLight}`,
|
||||
padding: '8px',
|
||||
position: 'relative',
|
||||
});
|
||||
}));
|
||||
|
||||
export const InnerMobileNavBar = styled('div')({
|
||||
export const InnerMobileNavBar = styled('div')<{ theme?: Theme }>(props => ({
|
||||
borderRadius: '4px',
|
||||
backgroundColor: colors.greyLight,
|
||||
color: colors.white,
|
||||
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',
|
||||
@ -53,41 +53,37 @@ export const SearchWrapper = styled('div')({
|
||||
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?.palette.type === 'light' ? theme?.palette.primary.main : theme?.palette.cyanBlue,
|
||||
color: theme?.palette.white,
|
||||
minHeight: 60,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
[`@media (min-width: ${theme?.breakPoints.medium}px)`]: css`
|
||||
${SearchWrapper} {
|
||||
display: flex;
|
||||
}
|
||||
${IconSearchButton} {
|
||||
display: none;
|
||||
}
|
||||
${MobileNavBar} {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
[`@media (min-width: ${theme?.breakPoints.large}px)`]: css`
|
||||
${InnerNavBar} {
|
||||
padding: 0 20px;
|
||||
}
|
||||
`,
|
||||
[`@media (min-width: ${theme?.breakPoints.xlarge}px)`]: css`
|
||||
${InnerNavBar} {
|
||||
max-width: 1240px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`,
|
||||
}));
|
||||
|
||||
export const StyledLink = styled(Link)({
|
||||
color: 'white',
|
||||
});
|
||||
export const StyledLink = styled(Link)<{ theme?: Theme }>(({ theme }) => ({
|
||||
color: theme?.palette.white,
|
||||
}));
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user