Compare commits
96 Commits
feature/se
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e9e9611f80 | ||
0c054687ba | |||
3b147e50a1 | |||
![]() |
e94514ed02 | ||
![]() |
0032e51858 | ||
8d6a9ab74c | |||
07c4ae8cbd | |||
a782930d3d | |||
4c1877caa5 | |||
e9324f6c14 | |||
![]() |
9fcee44e0c | ||
![]() |
9939b1c824 | ||
00deb05850 | |||
b8849945b9 | |||
08f5ee272b | |||
35aa957b35 | |||
472e8c2ea5 | |||
f17d5f3079 | |||
![]() |
4f9b33e5b9 | ||
![]() |
753223c7a9 | ||
![]() |
28cc9b1a71 | ||
![]() |
47472868e1 | ||
![]() |
f965eaddbc | ||
![]() |
2075426ff7 | ||
![]() |
209507833e | ||
eff20ae459 | |||
dcfe260f0e | |||
1af206c360 | |||
![]() |
932d23befa | ||
![]() |
e52026cf86 | ||
033a1f31ad | |||
9c79f449c5 | |||
24652d0908 | |||
944baced6c | |||
![]() |
01a873477b | ||
![]() |
d1b17592c3 | ||
1475d2db27 | |||
1ba9dc28b5 | |||
1817946fe6 | |||
de5b6ad60c | |||
![]() |
31706ca4e8 | ||
![]() |
969749c0c0 | ||
![]() |
5667f30a92 | ||
![]() |
d77c1c6388 | ||
![]() |
bd6fbbebfd | ||
![]() |
2ffce71e74 | ||
35900bec5d | |||
![]() |
c63c19e1c7 | ||
![]() |
b5443aa033 | ||
![]() |
18d02945f3 | ||
558a1fd5a7 | |||
![]() |
bee5499816 | ||
8a2a7f0c53 | |||
15b9ed1f20 | |||
cad2174bc5 | |||
0c0a79d81d | |||
df4b4d5c32 | |||
92dddab82a | |||
93cefd9ce3 | |||
373d55a543 | |||
![]() |
8a075e926e | ||
![]() |
705ae6471f | ||
![]() |
64a6e6ab20 | ||
![]() |
3cac7c7d04 | ||
![]() |
9154b1fb96 | ||
![]() |
b390ae7a2e | ||
![]() |
3c739247d2 | ||
904730dfd9 | |||
dcda165488 | |||
49d15e538b | |||
![]() |
da389cc337 | ||
3f9a76815e | |||
c99e9217f1 | |||
4479300027 | |||
e97d3abe96 | |||
![]() |
eff1a2a04f | ||
81faa7121e | |||
bbe6bcca51 | |||
c161606e08 | |||
5550b7c241 | |||
![]() |
86073d5718 | ||
85f227f7d9 | |||
![]() |
779b091bd5 | ||
![]() |
a26faeeb02 | ||
50a42955d6 | |||
9a3e545cf0 | |||
87ad19fa1a | |||
1d5eb72826 | |||
011f1564a0 | |||
89146993c7 | |||
a9895cc6dd | |||
![]() |
5afd4ced4d | ||
![]() |
f09cddff75 | ||
![]() |
bd1771e234 | ||
![]() |
6cdd4ed730 | ||
![]() |
12043e9123 |
@ -1,6 +1,6 @@
|
|||||||
REACT_APP_CMS_BASE_URL=EXT_CMS_BASE_URL
|
REACT_APP_CMS_BASE_URL=http://scipaper.ru
|
||||||
REACT_APP_CMS_APP_NAME=EXT_CMS_APP_NAME
|
REACT_APP_CMS_APP_NAME=scipaper
|
||||||
REACT_APP_OPENID_PROVIDER_URL=EXT_OPENID_PROVIDER_URL
|
REACT_APP_OPENID_PROVIDER_URL=http://auth.techpal.ru/auth/realms/master/protocol/openid-connect/auth?client_id=techpal&response_type=code
|
||||||
REACT_APP_INTEGRATOR_URL=EXT_INTEGRATOR_URL
|
REACT_APP_INTEGRATOR_URL=http://scipaper.ru
|
||||||
REACT_APP_INTEGRATOR_API_VERSION=EXT_INTEGRATOR_API_VERSION
|
REACT_APP_INTEGRATOR_API_VERSION=/v1
|
||||||
REACT_APP_GRAPHQL_URL=EXT_GRAPHQL_URL
|
REACT_APP_GRAPHQL_URL=/graphql
|
||||||
|
27
Dockerfile
27
Dockerfile
@ -17,22 +17,25 @@ COPY . .
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Bundle static assets with nginx
|
# Bundle static assets with nginx
|
||||||
FROM nginx:1.21.6 as production
|
FROM node:16-alpine as production
|
||||||
# Copy built assets from builder
|
# Copy built assets from builder
|
||||||
COPY --from=builder /app/build /usr/share/nginx/html
|
WORKDIR /app
|
||||||
# Add nginx.config
|
COPY --from=builder /app/build .
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
# Expose ports
|
# Expose ports
|
||||||
EXPOSE 80
|
EXPOSE 3000
|
||||||
|
|
||||||
COPY entrypoint.sh .
|
|
||||||
COPY .env.production .
|
COPY .env.production .
|
||||||
|
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
|
ENV USER_NAME=node_user USER_UID=2000 GROUP_NAME=node_group GROUP_UID=2000
|
||||||
|
|
||||||
# Execute script
|
RUN npm i -g serve \
|
||||||
RUN ["chmod", "+x", "./entrypoint.sh"]
|
&& deluser --remove-home node \
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
&& addgroup --g ${GROUP_UID} -S ${GROUP_NAME} \
|
||||||
|
&& adduser -D -S -s /sbin/nologin -u ${USER_UID} -G ${GROUP_NAME} ${USER_NAME}\
|
||||||
|
&& chown -R ${USER_NAME}:${GROUP_NAME} "/app/"
|
||||||
|
|
||||||
# Start serving
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
USER "${USER_NAME}"
|
||||||
|
CMD serve -s .
|
2
Makefile
2
Makefile
@ -1,4 +1,4 @@
|
|||||||
PROJECT_NAME=freeland-frontend
|
PROJECT_NAME=scipaper-frontend
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
npm i
|
npm i
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#! /bin/bash
|
||||||
# no verbose
|
# no verbose
|
||||||
set +x
|
set +x
|
||||||
# config
|
# config
|
||||||
|
259
package-lock.json
generated
259
package-lock.json
generated
@ -8,10 +8,15 @@
|
|||||||
"name": "freeland",
|
"name": "freeland",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||||
|
"@fortawesome/free-brands-svg-icons": "^6.2.0",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||||
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.6.6",
|
"@headlessui/react": "^1.6.6",
|
||||||
"@reduxjs/toolkit": "^1.8.3",
|
"@reduxjs/toolkit": "^1.8.3",
|
||||||
"@types/node": "^16.11.47",
|
"@types/node": "^16.11.47",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18.0.15",
|
||||||
|
"@types/react-copy-to-clipboard": "^5.0.4",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@uiw/react-md-editor": "^3.18.1",
|
"@uiw/react-md-editor": "^3.18.1",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
@ -25,10 +30,12 @@
|
|||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hotkeys": "^2.0.0",
|
"react-hotkeys": "^2.0.0",
|
||||||
"react-i18next": "^11.18.3",
|
"react-i18next": "^11.18.3",
|
||||||
"react-loading-skeleton": "^3.1.0",
|
"react-loading-skeleton": "^3.1.0",
|
||||||
|
"react-lottie": "^1.2.3",
|
||||||
"react-markdown": "^8.0.3",
|
"react-markdown": "^8.0.3",
|
||||||
"react-redux": "^8.0.2",
|
"react-redux": "^8.0.2",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
@ -63,6 +70,7 @@
|
|||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/react-lottie": "^1.2.6",
|
||||||
"@types/react-syntax-highlighter": "^15.5.5",
|
"@types/react-syntax-highlighter": "^15.5.5",
|
||||||
"autoprefixer": "^10.4.8",
|
"autoprefixer": "^10.4.8",
|
||||||
"babel-plugin-named-exports-order": "^0.0.2",
|
"babel-plugin-named-exports-order": "^0.0.2",
|
||||||
@ -2382,6 +2390,63 @@
|
|||||||
"js-yaml": "bin/js-yaml.js"
|
"js-yaml": "bin/js-yaml.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-rBevIsj2nclStJ7AxTdfsa3ovHb1H+qApwrxcTVo+NNdeJiB9V75hsKfrkG5AwNcRUNxrPPiScGYCNmLMoh8pg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-Cf2mAAeMWFMzpLC7Y9H1I4o3wEU+XovVJhTiNG8ZNgSQj53yl7OCJaS80K4YjrABWZzbAHVaoHE1dVJ27AAYXw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "6.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/free-brands-svg-icons": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-fm1y4NyZ2qKYNmYhdMz9VAWRw1Et7PMHNunSw3W0SVAwKwv6o0qiJworLH3Y9SnmhHzAymXJwCX1op22FFvGiA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "6.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-UjCILHIQ4I8cN46EiQn0CZL/h8AwCGgR//1c4R96Q5viSRwuKVo0NdQEc4bm+69ZwC0dUvjbDqAHF1RR5FA3XA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "6.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/react-fontawesome": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
|
||||||
|
"react": ">=16.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@gar/promisify": {
|
"node_modules/@gar/promisify": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||||
@ -11479,6 +11544,14 @@
|
|||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-copy-to-clipboard": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react-dom": {
|
"node_modules/@types/react-dom": {
|
||||||
"version": "18.0.6",
|
"version": "18.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz",
|
||||||
@ -11487,6 +11560,15 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-lottie": {
|
||||||
|
"version": "1.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-lottie/-/react-lottie-1.2.6.tgz",
|
||||||
|
"integrity": "sha512-fvGJHD7SeUdVESHo7f7erRnXkTWaa/6Mo5TB+R0/ieSftKoFspA4sMlF2qMH6BljXI7ehFJbBtrD5bzDxPCkGg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react-syntax-highlighter": {
|
"node_modules/@types/react-syntax-highlighter": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.5.tgz",
|
||||||
@ -13529,6 +13611,27 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/babel-runtime": {
|
||||||
|
"version": "6.26.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
|
||||||
|
"integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": "^2.4.0",
|
||||||
|
"regenerator-runtime": "^0.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/babel-runtime/node_modules/core-js": {
|
||||||
|
"version": "2.6.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
|
||||||
|
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
|
||||||
|
"deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
|
||||||
|
"hasInstallScript": true
|
||||||
|
},
|
||||||
|
"node_modules/babel-runtime/node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||||
|
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
|
||||||
|
},
|
||||||
"node_modules/bail": {
|
"node_modules/bail": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
|
||||||
@ -15021,6 +15124,14 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/copy-to-clipboard": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==",
|
||||||
|
"dependencies": {
|
||||||
|
"toggle-selection": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/core-js": {
|
"node_modules/core-js": {
|
||||||
"version": "3.24.1",
|
"version": "3.24.1",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
|
||||||
@ -25567,6 +25678,11 @@
|
|||||||
"loose-envify": "cli.js"
|
"loose-envify": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lottie-web": {
|
||||||
|
"version": "5.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.9.6.tgz",
|
||||||
|
"integrity": "sha512-JFs7KsHwflugH5qIXBpB4905yC1Sub2MZWtl/elvO/QC6qj1ApqbUZJyjzJseJUtVpgiDaXQLjBlIJGS7UUUXA=="
|
||||||
|
},
|
||||||
"node_modules/loud-rejection": {
|
"node_modules/loud-rejection": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
|
||||||
@ -30025,6 +30141,18 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-copy-to-clipboard": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
|
||||||
|
"dependencies": {
|
||||||
|
"copy-to-clipboard": "^3.3.1",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^15.3.0 || 16 || 17 || 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dev-utils": {
|
"node_modules/react-dev-utils": {
|
||||||
"version": "12.0.1",
|
"version": "12.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
||||||
@ -30292,6 +30420,21 @@
|
|||||||
"react": ">=16.8.0"
|
"react": ">=16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-lottie": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-lottie/-/react-lottie-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-qLCERxUr8M+4mm1LU0Ruxw5Y5Fn/OmYkGfnA+JDM/dZb3oKwVAJCjwnjkj9TMHtzR2U6sMEUD3ZZ1RaHagM7kA==",
|
||||||
|
"dependencies": {
|
||||||
|
"babel-runtime": "^6.26.0",
|
||||||
|
"lottie-web": "^5.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"npm": "^3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^0.14.7 || ^15.0.0 || ^16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-markdown": {
|
"node_modules/react-markdown": {
|
||||||
"version": "8.0.3",
|
"version": "8.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.3.tgz",
|
||||||
@ -37574,6 +37717,11 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/toggle-selection": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
|
||||||
|
},
|
||||||
"node_modules/toidentifier": {
|
"node_modules/toidentifier": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
@ -41454,6 +41602,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@fortawesome/fontawesome-common-types": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-rBevIsj2nclStJ7AxTdfsa3ovHb1H+qApwrxcTVo+NNdeJiB9V75hsKfrkG5AwNcRUNxrPPiScGYCNmLMoh8pg=="
|
||||||
|
},
|
||||||
|
"@fortawesome/fontawesome-svg-core": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-Cf2mAAeMWFMzpLC7Y9H1I4o3wEU+XovVJhTiNG8ZNgSQj53yl7OCJaS80K4YjrABWZzbAHVaoHE1dVJ27AAYXw==",
|
||||||
|
"requires": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "6.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@fortawesome/free-brands-svg-icons": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-fm1y4NyZ2qKYNmYhdMz9VAWRw1Et7PMHNunSw3W0SVAwKwv6o0qiJworLH3Y9SnmhHzAymXJwCX1op22FFvGiA==",
|
||||||
|
"requires": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "6.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@fortawesome/free-solid-svg-icons": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-UjCILHIQ4I8cN46EiQn0CZL/h8AwCGgR//1c4R96Q5viSRwuKVo0NdQEc4bm+69ZwC0dUvjbDqAHF1RR5FA3XA==",
|
||||||
|
"requires": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "6.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@fortawesome/react-fontawesome": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@gar/promisify": {
|
"@gar/promisify": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||||
@ -48448,6 +48633,14 @@
|
|||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-copy-to-clipboard": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-dom": {
|
"@types/react-dom": {
|
||||||
"version": "18.0.6",
|
"version": "18.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz",
|
||||||
@ -48456,6 +48649,15 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-lottie": {
|
||||||
|
"version": "1.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-lottie/-/react-lottie-1.2.6.tgz",
|
||||||
|
"integrity": "sha512-fvGJHD7SeUdVESHo7f7erRnXkTWaa/6Mo5TB+R0/ieSftKoFspA4sMlF2qMH6BljXI7ehFJbBtrD5bzDxPCkGg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-syntax-highlighter": {
|
"@types/react-syntax-highlighter": {
|
||||||
"version": "15.5.5",
|
"version": "15.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.5.tgz",
|
||||||
@ -50064,6 +50266,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"babel-runtime": {
|
||||||
|
"version": "6.26.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
|
||||||
|
"integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
|
||||||
|
"requires": {
|
||||||
|
"core-js": "^2.4.0",
|
||||||
|
"regenerator-runtime": "^0.11.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": {
|
||||||
|
"version": "2.6.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
|
||||||
|
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
|
||||||
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||||
|
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"bail": {
|
"bail": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
|
||||||
@ -51233,6 +51456,14 @@
|
|||||||
"integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==",
|
"integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"copy-to-clipboard": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==",
|
||||||
|
"requires": {
|
||||||
|
"toggle-selection": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"core-js": {
|
"core-js": {
|
||||||
"version": "3.24.1",
|
"version": "3.24.1",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
|
||||||
@ -59181,6 +59412,11 @@
|
|||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lottie-web": {
|
||||||
|
"version": "5.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.9.6.tgz",
|
||||||
|
"integrity": "sha512-JFs7KsHwflugH5qIXBpB4905yC1Sub2MZWtl/elvO/QC6qj1ApqbUZJyjzJseJUtVpgiDaXQLjBlIJGS7UUUXA=="
|
||||||
|
},
|
||||||
"loud-rejection": {
|
"loud-rejection": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
|
||||||
@ -62246,6 +62482,15 @@
|
|||||||
"whatwg-fetch": "^3.6.2"
|
"whatwg-fetch": "^3.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-copy-to-clipboard": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
|
||||||
|
"requires": {
|
||||||
|
"copy-to-clipboard": "^3.3.1",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-dev-utils": {
|
"react-dev-utils": {
|
||||||
"version": "12.0.1",
|
"version": "12.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
||||||
@ -62445,6 +62690,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.1.0.tgz",
|
||||||
"integrity": "sha512-j1U1CWWs68nBPOg7tkQqnlFcAMFF6oEK6MgqAo15f8A5p7mjH6xyKn2gHbkcimpwfO0VQXqxAswnSYVr8lWzjw=="
|
"integrity": "sha512-j1U1CWWs68nBPOg7tkQqnlFcAMFF6oEK6MgqAo15f8A5p7mjH6xyKn2gHbkcimpwfO0VQXqxAswnSYVr8lWzjw=="
|
||||||
},
|
},
|
||||||
|
"react-lottie": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-lottie/-/react-lottie-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-qLCERxUr8M+4mm1LU0Ruxw5Y5Fn/OmYkGfnA+JDM/dZb3oKwVAJCjwnjkj9TMHtzR2U6sMEUD3ZZ1RaHagM7kA==",
|
||||||
|
"requires": {
|
||||||
|
"babel-runtime": "^6.26.0",
|
||||||
|
"lottie-web": "^5.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-markdown": {
|
"react-markdown": {
|
||||||
"version": "8.0.3",
|
"version": "8.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.3.tgz",
|
||||||
@ -67797,6 +68051,11 @@
|
|||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"toggle-selection": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
|
||||||
|
},
|
||||||
"toidentifier": {
|
"toidentifier": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
|
@ -3,10 +3,15 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||||
|
"@fortawesome/free-brands-svg-icons": "^6.2.0",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||||
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.6.6",
|
"@headlessui/react": "^1.6.6",
|
||||||
"@reduxjs/toolkit": "^1.8.3",
|
"@reduxjs/toolkit": "^1.8.3",
|
||||||
"@types/node": "^16.11.47",
|
"@types/node": "^16.11.47",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18.0.15",
|
||||||
|
"@types/react-copy-to-clipboard": "^5.0.4",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@uiw/react-md-editor": "^3.18.1",
|
"@uiw/react-md-editor": "^3.18.1",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
@ -20,10 +25,12 @@
|
|||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hotkeys": "^2.0.0",
|
"react-hotkeys": "^2.0.0",
|
||||||
"react-i18next": "^11.18.3",
|
"react-i18next": "^11.18.3",
|
||||||
"react-loading-skeleton": "^3.1.0",
|
"react-loading-skeleton": "^3.1.0",
|
||||||
|
"react-lottie": "^1.2.3",
|
||||||
"react-markdown": "^8.0.3",
|
"react-markdown": "^8.0.3",
|
||||||
"react-redux": "^8.0.2",
|
"react-redux": "^8.0.2",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
@ -103,6 +110,7 @@
|
|||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/react-lottie": "^1.2.6",
|
||||||
"@types/react-syntax-highlighter": "^15.5.5",
|
"@types/react-syntax-highlighter": "^15.5.5",
|
||||||
"autoprefixer": "^10.4.8",
|
"autoprefixer": "^10.4.8",
|
||||||
"babel-plugin-named-exports-order": "^0.0.2",
|
"babel-plugin-named-exports-order": "^0.0.2",
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>Freeland</title>
|
<title>Scipaper</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"serv": {
|
"serv": {
|
||||||
"goHome": "Home",
|
"goHome": "Home page",
|
||||||
"noSuchPath": "We don't have this page"
|
"noSuchPath": "We don't have such a page"
|
||||||
},
|
},
|
||||||
"sidemenu": {
|
"sidemenu": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
@ -19,45 +19,130 @@
|
|||||||
"hellousr": "Hello, {{username}}",
|
"hellousr": "Hello, {{username}}",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"selectLanguage": "Select language",
|
"selectLanguage": "Select a language",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"account": {
|
"account": {
|
||||||
"info": "Personal information",
|
"info": "Personal Information",
|
||||||
"mail": "Mail",
|
"mail": "Mail",
|
||||||
"connect": "Add account",
|
"connect": "Add Account",
|
||||||
"connectedAccounts_one": "Connected account",
|
"connectedAccounts_one": "Linked Account",
|
||||||
"connectedAccounts_other": "Connected accounts",
|
"connectedAccounts_other": "Linked Accounts",
|
||||||
"settings": "Account settings"
|
"settings": "Account Settings"
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"password": {
|
"password": {
|
||||||
"caption": "Password",
|
"caption": "Password",
|
||||||
"twoFactor": "Two factor authentication (2FA)",
|
"twoFactor": "Two-factor authentication (2FA)",
|
||||||
"description": "Keep your account secure by enabling 2FA via SMS or using a temporary one-time passcode (TOTP) from an authenticator app."
|
"description": "Protect your account by enabling 2FA via SMS or using a temporary one-time password (OTP) from the authentication app."
|
||||||
},
|
},
|
||||||
"activity": {
|
"activity": {
|
||||||
"caption": "Device activity"
|
"caption": "Device activity"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"label": "Search for something.."
|
"label": "We will find something.."
|
||||||
},
|
},
|
||||||
"subscriptions": {
|
"subscriptions": {
|
||||||
"subscribed": "Service have been connected"
|
"subscribed": "The service is attached to the account"
|
||||||
},
|
},
|
||||||
"viewHistory": "View history",
|
"viewHistory": "View history",
|
||||||
"logOutEverywhere": "log out from all devices",
|
"logOutEverywhere": "Log out from all devices",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"logOut": "Log out",
|
"logOut": "Exit",
|
||||||
"failures": {
|
"failures": {
|
||||||
"subscriptions": {
|
"subscriptions": {
|
||||||
"failure": "Failed to connect service",
|
"failure": "Failed to attach the service to your account",
|
||||||
"exists": "Service have already been connected",
|
"exists": "The service was already attached to your account earlier",
|
||||||
"confirmation": "Invalid confirmation information provided"
|
"confirmation": "Invalid password"
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"fork": "Failed to authenticate in service"
|
"fork": "Failed to perform authorization in the service"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"articlePage": {
|
||||||
|
"abstract": "Abstract",
|
||||||
|
"keywords": "Keywords",
|
||||||
|
"interactionButtons":{
|
||||||
|
"abstract": "Abstract",
|
||||||
|
"readFile": "Read File",
|
||||||
|
"download" : "Download",
|
||||||
|
"share" : "Share",
|
||||||
|
"cite" : "Cite",
|
||||||
|
"copied": "Copied"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
"navbar": {
|
||||||
|
"createNew": "Create New",
|
||||||
|
"about": {
|
||||||
|
"navTitle": "About",
|
||||||
|
"aboutProject": "About Scipaper",
|
||||||
|
"contacts": "Contacts",
|
||||||
|
"help": "Help"
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"navTitle": "My library",
|
||||||
|
"publications": "Publications",
|
||||||
|
"favorites": "Favorites",
|
||||||
|
"collections": "Collections",
|
||||||
|
"recentViewed": "History"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"signIn": "Sign In",
|
||||||
|
"signUp": "Sign Up"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"accountSettings": "Account Settings",
|
||||||
|
"about": "About Scipaper",
|
||||||
|
"help": "Help",
|
||||||
|
"contactUs": "Contacts",
|
||||||
|
"allRightsReserved": "All rights reserved",
|
||||||
|
"termsOfUse": "Terms of Use",
|
||||||
|
"privacyPolicy": "Privacy Policy",
|
||||||
|
"coockiesPolicy": "coockies Usage Policy",
|
||||||
|
"supportedBy": "Created"
|
||||||
|
},
|
||||||
|
"mainPage": {
|
||||||
|
"title": "Scientific Library with Free Access",
|
||||||
|
"search": "Search",
|
||||||
|
"article_one": "Articles",
|
||||||
|
"article_few": "Articles",
|
||||||
|
"article_many": "Articles",
|
||||||
|
"advancedSearch": "Advanced search",
|
||||||
|
"featuredArticles": {
|
||||||
|
"title": "Featured articles",
|
||||||
|
"descriptionPart1": "Select the category of science you are interested in",
|
||||||
|
"descriptionPart2": "Scientific category",
|
||||||
|
"categories": {
|
||||||
|
"Medical": "Medical",
|
||||||
|
"TechnicsAndTechlonogies": "Technics and Technology",
|
||||||
|
"Fundamental": "Fundamental",
|
||||||
|
"Humanitarian": "Humanitarian",
|
||||||
|
"Agricultural": "Agricultural",
|
||||||
|
"Social": "Social"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"featuredAuthors": "Featured authors",
|
||||||
|
"more": "See More ",
|
||||||
|
"showAll": "Show all"
|
||||||
|
},
|
||||||
|
"searchResults": {
|
||||||
|
"title": "Search results",
|
||||||
|
"totalResults":"Total results",
|
||||||
|
"nothingFound": "Nothing found"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"authors":"Authors",
|
||||||
|
"publicationsType": "Publications Type",
|
||||||
|
"content":"Content",
|
||||||
|
"publisher":"Publisher",
|
||||||
|
"publicationTopic":"Publication Topic",
|
||||||
|
"appliedFitlers":"Applied Fitlers",
|
||||||
|
"clearAll":"Clear All",
|
||||||
|
"enterAuthorsName":"Enter Author Name",
|
||||||
|
"showAll":"Show All"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -59,5 +59,87 @@
|
|||||||
"services": {
|
"services": {
|
||||||
"fork": "Не удалось выполнить авторизацию в сервисе"
|
"fork": "Не удалось выполнить авторизацию в сервисе"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"articlePage": {
|
||||||
|
"abstract": "Введение",
|
||||||
|
"keywords": "Ключевые слова",
|
||||||
|
"interactionButtons":{
|
||||||
|
"abstract": "Развернуть",
|
||||||
|
"readFile": "Читать",
|
||||||
|
"download" : "Скачать",
|
||||||
|
"share" : "Поделиться",
|
||||||
|
"cite" : "Цитировать",
|
||||||
|
"copied": "Скопировано"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navbar": {
|
||||||
|
"createNew": "Создать статью",
|
||||||
|
"about": {
|
||||||
|
"navTitle": "О проекте",
|
||||||
|
"aboutProject": "О Scipaper",
|
||||||
|
"contacts": "Контакты",
|
||||||
|
"help": "Помощь"
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"navTitle": "Моя библиотека",
|
||||||
|
"publications": "Публикации",
|
||||||
|
"favorites": "Избранное",
|
||||||
|
"collections": "Коллекции",
|
||||||
|
"recentViewed": "История"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"signIn": "Вход",
|
||||||
|
"signUp": "Регистрация"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"accountSettings": "Настройки аккаунта",
|
||||||
|
"about": "О scipaper",
|
||||||
|
"help": "Помощь",
|
||||||
|
"contactUs": "Контакты",
|
||||||
|
"allRightsReserved": "Все права защищены",
|
||||||
|
"termsOfUse": "Правила использования",
|
||||||
|
"privacyPolicy": "Политика конфиденциальности",
|
||||||
|
"coockiesPolicy": "Политика использования coockies",
|
||||||
|
"supportedBy": "Создано"
|
||||||
|
},
|
||||||
|
"mainPage": {
|
||||||
|
"title": "Библиотека научных статей с бесплатным доступом",
|
||||||
|
"search": "Поиск",
|
||||||
|
"article_one": "статьи",
|
||||||
|
"article_few": "статей",
|
||||||
|
"article_many": "статей",
|
||||||
|
"advancedSearch": "Расширенный поиск",
|
||||||
|
"featuredArticles": {
|
||||||
|
"title": "Популярные статьи",
|
||||||
|
"descriptionPart1": "Выберете интересующую вас ",
|
||||||
|
"descriptionPart2": "научную категорию",
|
||||||
|
"categories": {
|
||||||
|
"Medical": "Медицина",
|
||||||
|
"TechnicsAndTechlonogies": "Техника и технологии",
|
||||||
|
"Fundamental": "Естественые",
|
||||||
|
"Humanitarian": "Гуманитарные",
|
||||||
|
"Agricultural": "Аuрокультурa",
|
||||||
|
"Social": "Социальные"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"featuredAuthors": "Популярные авторы",
|
||||||
|
"more": "Больше",
|
||||||
|
"showAll": "Показать все"
|
||||||
|
},
|
||||||
|
"searchResults": {
|
||||||
|
"title": "Результаты поиска",
|
||||||
|
"totalResults":"Всего найдено",
|
||||||
|
"nothingFound": "Ничего не найдено"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"authors":"Авторы",
|
||||||
|
"publicationsType": "Публикации",
|
||||||
|
"publisher":"Издатель",
|
||||||
|
"publicationTopic":"Тема публикации",
|
||||||
|
"appliedFitlers":"Фильтры",
|
||||||
|
"clearAll":"Очистить всё",
|
||||||
|
"enterAuthorsName":"Введите имя автора",
|
||||||
|
"showAll":"Показать все"
|
||||||
}
|
}
|
||||||
}
|
}
|
24
src/article/controller/articleViewModel.ts
Normal file
24
src/article/controller/articleViewModel.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type { ArticleStore } from "../domain/articleStore";
|
||||||
|
import { getArticleUseCase } from "../useCases/getArticleUseCase";
|
||||||
|
import { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
|
function useArticleViewModel(store: ArticleStore) {
|
||||||
|
const _getArticle = useCallback(
|
||||||
|
(id: string) => getArticleUseCase(store.getArticle, store.setArticle, id),
|
||||||
|
[store.getArticle, store.setArticle]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (store.article != undefined) {
|
||||||
|
_getArticle(store.article.id);
|
||||||
|
}
|
||||||
|
}, [store.article?.id]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
article: store.article,
|
||||||
|
shouldShowLoading: typeof store.article === "undefined" || store.isLoading,
|
||||||
|
hasError: store.hasError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useArticleViewModel };
|
36
src/article/data/articleAPIService.ts
Normal file
36
src/article/data/articleAPIService.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { Article } from "../domain/articleEntity";
|
||||||
|
import { create } from "../domain/articleModel";
|
||||||
|
import { FetchArticleByIdDTO } from "./dto/fetch_article_by_id_dto";
|
||||||
|
import Failure from "core/failure";
|
||||||
|
import { integratorApiClient } from "core/httpClient";
|
||||||
|
|
||||||
|
const articleEndpoint = "/papers/"
|
||||||
|
|
||||||
|
async function getArticle(id: string): Promise<Article> {
|
||||||
|
try {
|
||||||
|
const response = await integratorApiClient.get<FetchArticleByIdDTO>(
|
||||||
|
// `https://run.mocky.io/v3/62cd4581-d864-4d46-b1d6-02b45b5d1994/${id}`
|
||||||
|
// `https://jsonplaceholder.typicode.com/posts/${id}`
|
||||||
|
// `https://run.mocky.io/v3/066be3d8-0568-439a-8b20-062deed49a97`
|
||||||
|
articleEndpoint + id
|
||||||
|
);
|
||||||
|
const dto = response.data;
|
||||||
|
return create({
|
||||||
|
id: dto.id,
|
||||||
|
topic: [dto.topic],
|
||||||
|
title: dto.title,
|
||||||
|
authors: dto.authors,
|
||||||
|
tags: dto.tags,
|
||||||
|
summary: dto.summary,
|
||||||
|
content: dto.content,
|
||||||
|
});
|
||||||
|
} catch (reason) {
|
||||||
|
if (axios.isAxiosError(reason)) {
|
||||||
|
throw Failure.fromReason(reason, "failures.services.load");
|
||||||
|
}
|
||||||
|
throw reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getArticle };
|
4
src/article/data/articleActionTypes.ts
Normal file
4
src/article/data/articleActionTypes.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const SET_ARTICLE = "SET_ARTICLE";
|
||||||
|
export const GET_ARTICLE = "GET_ARTICLE";
|
||||||
|
export const GET_ARTICLE_SUCCESS = "GET_ARTICLE.success";
|
||||||
|
export const GET_ARTICLE_FAILURE = "GET_ARTICLE.failure";
|
23
src/article/data/articleActions.ts
Normal file
23
src/article/data/articleActions.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { Article } from "../domain/articleEntity";
|
||||||
|
import { getArticle as getArticleAPI } from "./articleAPIService";
|
||||||
|
import * as actionTypes from "./articleActionTypes";
|
||||||
|
import { dispatchStatus } from "../../store/index";
|
||||||
|
|
||||||
|
const setArticleAction = (article: Article) => (dispatch: any) =>
|
||||||
|
dispatch({ type: actionTypes.SET_ARTICLE, article });
|
||||||
|
|
||||||
|
const getArticleAction = (id: string) => (dispatch: any) => {
|
||||||
|
dispatch({ type: actionTypes.GET_ARTICLE });
|
||||||
|
|
||||||
|
return getArticleAPI(id)
|
||||||
|
.then((article) => {
|
||||||
|
dispatchStatus(actionTypes.GET_ARTICLE, ".success", article)(dispatch);
|
||||||
|
return article;
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
dispatchStatus(actionTypes.GET_ARTICLE, ".failure", reason)(dispatch);
|
||||||
|
return reason;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { setArticleAction, getArticleAction };
|
39
src/article/data/articleCommonStateStore.ts
Normal file
39
src/article/data/articleCommonStateStore.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React, { useCallback, useState } from "react";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { ArticleStore } from "../domain/articleStore";
|
||||||
|
import type { Article } from "../domain/articleEntity";
|
||||||
|
import { getArticle as getArticleAPI } from "./articleAPIService";
|
||||||
|
|
||||||
|
const useArticleCommonStore = (): ArticleStore => {
|
||||||
|
const [isLoading, setLoading] = useState<boolean>(false);
|
||||||
|
const [hasError, setError] = useState<boolean>(false);
|
||||||
|
const [article, setArticleState] = useState<Article | undefined>();
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const getArticle = useCallback(
|
||||||
|
async (id: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const article = await getArticleAPI(id);
|
||||||
|
setArticleState(article);
|
||||||
|
setLoading(false);
|
||||||
|
return article;
|
||||||
|
} catch (error) {
|
||||||
|
setError(true);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
article: article,
|
||||||
|
isLoading,
|
||||||
|
hasError,
|
||||||
|
setArticle: setArticleState,
|
||||||
|
getArticle,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useArticleCommonStore };
|
32
src/article/data/articleReducer.ts
Normal file
32
src/article/data/articleReducer.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { AnyAction } from "@reduxjs/toolkit";
|
||||||
|
import type { ArticleStore } from "../domain/articleStore";
|
||||||
|
import * as actionTypes from "./articleActionTypes";
|
||||||
|
|
||||||
|
type ArticleStoreState = Omit<ArticleStore, "getArticle" | "setArticle">;
|
||||||
|
|
||||||
|
const INITIAL_STATE: ArticleStoreState = {
|
||||||
|
article: undefined,
|
||||||
|
isLoading: false,
|
||||||
|
hasError: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const articleReducer = (
|
||||||
|
state: ArticleStoreState = INITIAL_STATE,
|
||||||
|
action: AnyAction
|
||||||
|
): ArticleStoreState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case actionTypes.SET_ARTICLE:
|
||||||
|
return { ...state, article: action.article };
|
||||||
|
case actionTypes.GET_ARTICLE:
|
||||||
|
return { ...state, isLoading: true };
|
||||||
|
case actionTypes.GET_ARTICLE_SUCCESS:
|
||||||
|
return { ...state, isLoading: false, article: action.payload };
|
||||||
|
case actionTypes.GET_ARTICLE_FAILURE:
|
||||||
|
return { ...state, hasError: true, isLoading: false };
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { articleReducer };
|
||||||
|
export type { ArticleStoreState };
|
35
src/article/data/articleStoreImplementation.ts
Normal file
35
src/article/data/articleStoreImplementation.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React, { useCallback, useState } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { ArticleStore } from "../domain/articleStore";
|
||||||
|
import type { Article } from "../domain/articleEntity";
|
||||||
|
import type { ArticleStoreState } from "../data/articleReducer";
|
||||||
|
import { getArticleAction, setArticleAction } from "./articleActions";
|
||||||
|
import { RootState, useAppSelector } from "store";
|
||||||
|
|
||||||
|
const articleSelector = (state: RootState): ArticleStoreState => state.article;
|
||||||
|
|
||||||
|
const useArticleStore = (): ArticleStore => {
|
||||||
|
const { isLoading, article, hasError } = useAppSelector(articleSelector);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const setArticle = useCallback(
|
||||||
|
(article: Article) => setArticleAction(article)(dispatch),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getArticle = useCallback(
|
||||||
|
(id: string) => getArticleAction(id)(dispatch),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
article: article,
|
||||||
|
isLoading,
|
||||||
|
hasError,
|
||||||
|
setArticle,
|
||||||
|
getArticle,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useArticleStore };
|
9
src/article/data/dto/fetch_article_by_id_dto.ts
Normal file
9
src/article/data/dto/fetch_article_by_id_dto.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface FetchArticleByIdDTO {
|
||||||
|
id: string;
|
||||||
|
topic: string;
|
||||||
|
title: string;
|
||||||
|
authors: string[];
|
||||||
|
tags: string[];
|
||||||
|
summary: string;
|
||||||
|
content: string;
|
||||||
|
}
|
11
src/article/domain/ArticleEntity.ts → src/article/domain/articleEntity.ts
Executable file → Normal file
11
src/article/domain/ArticleEntity.ts → src/article/domain/articleEntity.ts
Executable file → Normal file
@ -1,10 +1,9 @@
|
|||||||
export interface Article {
|
export interface Article {
|
||||||
id?: string;
|
id: string;
|
||||||
title?: string;
|
title: string;
|
||||||
authors?: string[];
|
content: string;
|
||||||
topic?: string[];
|
topic?: string[];
|
||||||
summary?: string;
|
authors?: string[];
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
content?: string;
|
summary?: string;
|
||||||
publisher?: string;
|
|
||||||
}
|
}
|
14
src/article/domain/articleModel.ts
Normal file
14
src/article/domain/articleModel.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { CreateArticleParams } from "article/useCases/params/create_article_params";
|
||||||
|
import { Article } from "./articleEntity";
|
||||||
|
|
||||||
|
const create = (props: CreateArticleParams): Article => ({
|
||||||
|
id: props.id,
|
||||||
|
topic: props.topic,
|
||||||
|
title: props.title,
|
||||||
|
authors: props.authors,
|
||||||
|
tags: props.tags,
|
||||||
|
summary: props.summary,
|
||||||
|
content: props.content,
|
||||||
|
});
|
||||||
|
|
||||||
|
export { create };
|
13
src/article/domain/articleStore.ts
Normal file
13
src/article/domain/articleStore.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Article } from './articleEntity';
|
||||||
|
interface ArticleStore {
|
||||||
|
// State
|
||||||
|
article: Article | undefined;
|
||||||
|
isLoading: boolean;
|
||||||
|
hasError: boolean;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
setArticle(article?: Article): void;
|
||||||
|
getArticle(identifier: string): Promise<Article | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { ArticleStore };
|
16
src/article/useCases/getArticleUseCase.ts
Normal file
16
src/article/useCases/getArticleUseCase.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Article } from "article/domain/articleEntity";
|
||||||
|
import type { ArticleStore } from "../domain/articleStore";
|
||||||
|
|
||||||
|
const getArticleUseCase = async (
|
||||||
|
getArticle: ArticleStore["getArticle"],
|
||||||
|
setArticle: ArticleStore["setArticle"],
|
||||||
|
id: Article["id"]
|
||||||
|
): Promise<Article | null> => {
|
||||||
|
const article = await getArticle(id);
|
||||||
|
if (article) {
|
||||||
|
await setArticle(article);
|
||||||
|
}
|
||||||
|
return article;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getArticleUseCase };
|
9
src/article/useCases/params/create_article_params.ts
Normal file
9
src/article/useCases/params/create_article_params.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface CreateArticleParams {
|
||||||
|
id: string;
|
||||||
|
topic: string[];
|
||||||
|
title: string;
|
||||||
|
authors: string[];
|
||||||
|
tags: string[];
|
||||||
|
summary: string;
|
||||||
|
content: string;
|
||||||
|
}
|
1
src/assets/lotties/notFoundAnimation.json
Normal file
1
src/assets/lotties/notFoundAnimation.json
Normal file
File diff suppressed because one or more lines are too long
@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import Breadcrumbs from "components/breadcrumbs";
|
import Breadcrumbs from "components/breadcrumbs";
|
||||||
import Logo from "components/Logo";
|
import Logo from "components/Logo";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
@ -26,7 +26,7 @@ export function ArticleKeywords({
|
|||||||
"hover:text-blue-600",
|
"hover:text-blue-600",
|
||||||
{
|
{
|
||||||
"text-xs text-gray-500 leading-5": emphasis === "low",
|
"text-xs text-gray-500 leading-5": emphasis === "low",
|
||||||
"text-base text-gray-900 px-2 border rounded hover:border-blue-600 border-gray-900":
|
"text-base text-gray-900 px-2 border rounded hover:border-blue-600 border-gray-200":
|
||||||
emphasis === "high",
|
emphasis === "high",
|
||||||
},
|
},
|
||||||
className
|
className
|
||||||
|
@ -12,14 +12,11 @@ import {
|
|||||||
} from "components/icons";
|
} from "components/icons";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
|
import Link from "components/typography/Link";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ShareButton } from "./ArticleShareButton";
|
||||||
|
|
||||||
const interactionButtonsStore = [
|
const interactionButtonsStore = [
|
||||||
{
|
|
||||||
icon: <SVGFiletext />,
|
|
||||||
title: "Read file",
|
|
||||||
buttonEmphasis: "high",
|
|
||||||
iconClassName: "h-6 fill-white stroke-white",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: <SVGDownload />,
|
icon: <SVGDownload />,
|
||||||
title: "Download",
|
title: "Download",
|
||||||
@ -41,11 +38,12 @@ const interactionButtonsStore = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
type ArticleButtonProps = {
|
type ArticleButtonProps = {
|
||||||
isAbstractOpen: boolean;
|
isAbstractOpen?: boolean;
|
||||||
openAbstract: () => void;
|
openAbstract?: () => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
emphasis?: "high" | "low";
|
emphasis?: "high" | "low";
|
||||||
|
articleID?: string;
|
||||||
} & Omit<React.ComponentPropsWithoutRef<"button">, "">;
|
} & Omit<React.ComponentPropsWithoutRef<"button">, "">;
|
||||||
|
|
||||||
export function ArticleInteractionButtons({
|
export function ArticleInteractionButtons({
|
||||||
@ -53,9 +51,14 @@ export function ArticleInteractionButtons({
|
|||||||
children,
|
children,
|
||||||
openAbstract = () => {},
|
openAbstract = () => {},
|
||||||
className,
|
className,
|
||||||
|
articleID,
|
||||||
emphasis = "high", //to change displaying of component
|
emphasis = "high", //to change displaying of component
|
||||||
...props
|
...props
|
||||||
}: ArticleButtonProps) {
|
}: ArticleButtonProps) {
|
||||||
|
const [t, i18next] = useTranslation("translation", {
|
||||||
|
keyPrefix: "articlePage.interactionButtons",
|
||||||
|
});
|
||||||
|
/* ----------------------------- Abstract Button ---------------------------- */
|
||||||
const abstractButton = (
|
const abstractButton = (
|
||||||
<Button
|
<Button
|
||||||
emphasis="medium"
|
emphasis="medium"
|
||||||
@ -63,32 +66,66 @@ export function ArticleInteractionButtons({
|
|||||||
onClick={openAbstract}
|
onClick={openAbstract}
|
||||||
>
|
>
|
||||||
<Typography fontWeightVariant="bold" className="pr-2">
|
<Typography fontWeightVariant="bold" className="pr-2">
|
||||||
Abstract
|
{t("abstract")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button.Icon>
|
<Button.Icon>
|
||||||
{!isAbstractOpen ? <SVGArrowDown className="w-4 fill-blue-700 stroke-blue-700" /> : <SVGArrowUp className="w-4 fill-blue-700 stroke-blue-700" />}
|
{!isAbstractOpen ? (
|
||||||
|
<SVGArrowDown className="w-4 fill-blue-700 stroke-blue-700" />
|
||||||
|
) : (
|
||||||
|
<SVGArrowUp className="w-4 fill-blue-700 stroke-blue-700" />
|
||||||
|
)}
|
||||||
</Button.Icon>
|
</Button.Icon>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
const fileInteractionButtons = interactionButtonsStore.map((button) => {
|
/* ---------------------------- Read file button ---------------------------- */
|
||||||
return (
|
const readFileButton = (
|
||||||
<Button
|
<Link
|
||||||
emphasis={button.buttonEmphasis === "high" ? "high" : "low"}
|
to={`/article/content/${articleID}`}
|
||||||
className="h-max mx-1 px-2"
|
className="rounded active:outline focus:outline focus:outline-blue-400/10 outline-8 active:outline-blue-400/10"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
className="bg-blue-600
|
||||||
|
hover:bg-blue-500
|
||||||
|
active:bg-blue-700
|
||||||
|
focus:shadow-lg shadow-blue-500
|
||||||
|
p-2 rounded
|
||||||
|
flex flex-row space-x-2
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SVGFiletext className="w-6 fill-white stroke-white" />
|
||||||
|
{emphasis === "high" && <Typography fontWeightVariant="semibold" className="text-white">{t("readFile")}</Typography>}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
/* ----------------------------- Download button ---------------------------- */
|
||||||
|
const downLoadButton = (
|
||||||
|
<Button emphasis="low" className="px-2 space-x-2">
|
||||||
<Button.Icon>
|
<Button.Icon>
|
||||||
{React.cloneElement(button.icon, { className: button.iconClassName })}
|
<SVGDownload className="w-6 fill-gray-900 stroke-gray-900" />
|
||||||
</Button.Icon>
|
</Button.Icon>
|
||||||
{emphasis === "high" ? <Typography>{button.title}</Typography> : null}
|
{emphasis === "high" && <Typography fontWeightVariant="semibold" className="text-gray-900">{t("download")}</Typography>}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
/* ------------------------------- Cite button ------------------------------ */
|
||||||
|
|
||||||
|
const citeButton = (
|
||||||
|
<Button emphasis="low" className="px-2 space-x-2">
|
||||||
|
<Button.Icon>
|
||||||
|
<SVGCite className="w-6 fill-gray-900 stroke-gray-900" />
|
||||||
|
</Button.Icon>
|
||||||
|
{emphasis === "high" && <Typography fontWeightVariant="semibold" className="text-gray-900">{t("cite")}</Typography>}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row space-x-2">
|
||||||
{emphasis === "low" && !children ? abstractButton : null}
|
{emphasis != "high" && abstractButton}
|
||||||
{children ? children : fileInteractionButtons}
|
{readFileButton}
|
||||||
|
{downLoadButton}
|
||||||
|
{citeButton}
|
||||||
|
<ShareButton emphasis={emphasis} linktoCopy={`/article/content/${articleID}`} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
import { BASE_URL } from "core/httpClient";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Button } from "../../../Button/Button";
|
||||||
|
import { SVGCopy, SVGShare, SVGXmark } from "../../../icons";
|
||||||
|
import Typography from "../../../typography/Typography";
|
||||||
|
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||||
|
import { Popover } from "@headlessui/react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
emphasis?: "high" | "low";
|
||||||
|
linktoCopy?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ShareButton({ emphasis, linktoCopy }: Props) {
|
||||||
|
const [t, i18next] = useTranslation("translation", {
|
||||||
|
keyPrefix: "articlePage.interactionButtons",
|
||||||
|
});
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const copyValue =
|
||||||
|
BASE_URL != undefined && linktoCopy != undefined
|
||||||
|
? BASE_URL + linktoCopy
|
||||||
|
: t("searchResults.nothingFound");
|
||||||
|
|
||||||
|
function onCopy() {
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopied(false);
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFocus = (event: any) => event.target.select();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover className="relative">
|
||||||
|
<Popover.Button>
|
||||||
|
<Button emphasis="low" className="px-2 space-x-2">
|
||||||
|
<Button.Icon>
|
||||||
|
<SVGShare className="w-6 fill-gray-900 stroke-gray-900" />
|
||||||
|
</Button.Icon>
|
||||||
|
{emphasis === "high" && <Typography fontWeightVariant="semibold" className="text-gray-900">{t("share")}</Typography>}
|
||||||
|
</Button>
|
||||||
|
</Popover.Button>
|
||||||
|
|
||||||
|
<Popover.Panel className="absolute z-10 -right-3/4 mt-3 pl-2 bg-white border-2 border-gray-300 shadow-lg rounded">
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<input
|
||||||
|
className="focus:outline-none active:outline-none p-1 mr-1"
|
||||||
|
type="text"
|
||||||
|
readOnly
|
||||||
|
value={copyValue}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
/>
|
||||||
|
<div className="relative">
|
||||||
|
<CopyToClipboard text={copyValue} onCopy={onCopy}>
|
||||||
|
<Button
|
||||||
|
emphasis="low"
|
||||||
|
className="items-center border-l-2 rounded-none "
|
||||||
|
>
|
||||||
|
<Button.Icon>
|
||||||
|
<SVGCopy className="w-5 fill-gray-900 stroke-gray-900" />
|
||||||
|
</Button.Icon>
|
||||||
|
</Button>
|
||||||
|
</CopyToClipboard>
|
||||||
|
|
||||||
|
{copied && (
|
||||||
|
<div className="absolute z-11 -bottom-full bg-gray-900 text-white px-2 py-1 rounded before:content[''] before:absolute before:bg-gray-900 before:rotate-45 before:w-3 before:h-3 before:-top-1 before:left-4 before:block">
|
||||||
|
{t("copied")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Popover.Button>
|
||||||
|
<Button
|
||||||
|
emphasis="low"
|
||||||
|
className="items-center border-l-2 rounded-none "
|
||||||
|
>
|
||||||
|
<Button.Icon>
|
||||||
|
<SVGXmark className="w-5 fill-gray-900 stroke-gray-900" />
|
||||||
|
</Button.Icon>
|
||||||
|
</Button>
|
||||||
|
</Popover.Button>
|
||||||
|
</div>
|
||||||
|
</Popover.Panel>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Article } from "components/Article/Article";
|
import { Article } from "components/Article/Article";
|
||||||
import { Article as ArticleTypes } from "article/domain/ArticleEntity";
|
import { Article as ArticleTypes } from "article/domain/articleEntity";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import { useSearchStoreImplementation } from "searchResults/data/searchStoreImplementation";
|
import { useSearchStoreImplementation } from "searchResults/data/searchStoreImplementation";
|
||||||
@ -37,7 +37,7 @@ export const ArticleSearchResult = ({ searchItem }: Props) => {
|
|||||||
<Article.SubscriptionsButtons />
|
<Article.SubscriptionsButtons />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Article.Title linkTo={searchItem.id} className="text-2xl">
|
<Article.Title linkTo={`/article/info/${searchItem.id}`} className="text-2xl">
|
||||||
{searchItem.title}
|
{searchItem.title}
|
||||||
</Article.Title>
|
</Article.Title>
|
||||||
<Article.Authors emphasis="low" className="flex flex-wrap flex-row">
|
<Article.Authors emphasis="low" className="flex flex-wrap flex-row">
|
||||||
@ -47,10 +47,11 @@ export const ArticleSearchResult = ({ searchItem }: Props) => {
|
|||||||
{searchItem.tags}
|
{searchItem.tags}
|
||||||
</Article.Keywords>
|
</Article.Keywords>
|
||||||
<Article.InteractionButtons
|
<Article.InteractionButtons
|
||||||
className="py-2 "
|
className="py-2"
|
||||||
emphasis="low"
|
emphasis="low"
|
||||||
openAbstract={open}
|
openAbstract={open}
|
||||||
isAbstractOpen={openAbstract}
|
isAbstractOpen={openAbstract}
|
||||||
|
articleID={searchItem.id}
|
||||||
/>
|
/>
|
||||||
<Article.Description
|
<Article.Description
|
||||||
emphasis="low"
|
emphasis="low"
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { StyleType } from "core/_variants";
|
import { StyleType } from "core/_variants";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ReactComponent as DeleteIcon } from "../assets/svg/xmark.svg";
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Component props */
|
/* Component props */
|
||||||
@ -15,7 +16,8 @@ type Props = {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
iconed?: boolean;
|
iconed?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: (element: any) => void;
|
||||||
|
closeOption?: Boolean;
|
||||||
} & Omit<React.MouseEventHandler<HTMLSpanElement>, "">;
|
} & Omit<React.MouseEventHandler<HTMLSpanElement>, "">;
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
@ -27,17 +29,18 @@ function Badge({
|
|||||||
children,
|
children,
|
||||||
onClick,
|
onClick,
|
||||||
emphasis = "low",
|
emphasis = "low",
|
||||||
|
closeOption = false,
|
||||||
...props
|
...props
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<span
|
<span
|
||||||
onClick={onClick}
|
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"border p-2 rounded-sm text-xs text-center",
|
"border-none p-2 rounded-md text-xs text-center",
|
||||||
{
|
{
|
||||||
"cursor-pointer": onClick,
|
"cursor-pointer": onClick,
|
||||||
"border-transparent": emphasis == "low",
|
"border-transparent": emphasis == "low",
|
||||||
"bg-blue-400 text-white": emphasis == "high",
|
"bg-blue-400 text-white rounded-xs": emphasis == "high",
|
||||||
"border-gray-400 background-gray-200": emphasis == "medium",
|
"border-gray-400 background-gray-200": emphasis == "medium",
|
||||||
},
|
},
|
||||||
className
|
className
|
||||||
@ -45,7 +48,18 @@ function Badge({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
{closeOption && (
|
||||||
|
<button onClick={onClick} className="h-4 w-5">
|
||||||
|
<div>
|
||||||
|
<DeleteIcon
|
||||||
|
className="relative top-1 left-1 h-4 w-5 fill-white hover:fill-white stroke-white
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default Badge;
|
export default Badge;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { joinClassnames } from "core/helpers";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Footer } from "./parts/Footer";
|
import { Footer } from "./parts/Footer";
|
||||||
import Header from "./parts/Header";
|
import Header from "./parts/Header";
|
||||||
@ -10,10 +11,10 @@ type Props = {
|
|||||||
|
|
||||||
function BaseLayout({ header, footer, children, className }: Props) {
|
function BaseLayout({ header, footer, children, className }: Props) {
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={joinClassnames(className, 'flex min-h-screen flex-col justify-between')}>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<main>{children}</main>
|
<main className="flex-1">{children}</main>
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
198
src/components/Burger.tsx
Normal file
198
src/components/Burger.tsx
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
|
import React, { Fragment } from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { Disclosure } from "@headlessui/react";
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Components */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
import ContextMenuAction from "./drop-down-menu/ContextMenuAction";
|
||||||
|
import ContextMenu from "./drop-down-menu/ContextMenu";
|
||||||
|
import { Button } from "./Button/Button";
|
||||||
|
import Link from "./typography/Link";
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Icons */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
import { ReactComponent as SVGFavoriteOutlined } from "assets/svg/favorite-outlined.svg";
|
||||||
|
import { ReactComponent as SVGHamburger } from "assets/svg/hamburger.svg";
|
||||||
|
import { ReactComponent as SVGFolder } from "assets/svg/folder.svg";
|
||||||
|
import { ReactComponent as SVGFile } from "assets/svg/file.svg";
|
||||||
|
import { ReactComponent as SVGEye } from "assets/svg/eye.svg";
|
||||||
|
import { ReactComponent as SVGArrowUp } from "assets/svg/arrow-up.svg";
|
||||||
|
import { ReactComponent as SVGCaretDown } from "assets/svg/caret-down.svg";
|
||||||
|
|
||||||
|
type Props = React.ComponentPropsWithoutRef<"div">;
|
||||||
|
|
||||||
|
const Burger = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<div {...props}>
|
||||||
|
<Menu as="div" className="relative inline-block text-left z-30">
|
||||||
|
<div>
|
||||||
|
<Menu.Button as={Button} emphasis="low">
|
||||||
|
<Button.Icon>
|
||||||
|
<SVGHamburger className="h-6 w-6" />
|
||||||
|
</Button.Icon>
|
||||||
|
</Menu.Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-100"
|
||||||
|
enterFrom="transform opacity-0 scale-95"
|
||||||
|
enterTo="transform opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items
|
||||||
|
className="origin-top-right absolute right-0 mt-5 w-44 rounded-md
|
||||||
|
shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||||
|
>
|
||||||
|
<div className="py-1">
|
||||||
|
<Disclosure>
|
||||||
|
<Disclosure.Button className="uppercase text-base px-2 py-1">
|
||||||
|
<Link className="text-[#096DD9]">create new</Link>
|
||||||
|
</Disclosure.Button>
|
||||||
|
</Disclosure>
|
||||||
|
<hr />
|
||||||
|
<Disclosure>
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Disclosure.Button
|
||||||
|
className="uppercase px-2 flex justify-between w-full items-center
|
||||||
|
py-1
|
||||||
|
hover:bg-gray-100
|
||||||
|
text-base
|
||||||
|
"
|
||||||
|
>
|
||||||
|
my library
|
||||||
|
<SVGArrowUp
|
||||||
|
className={`${
|
||||||
|
open ? "rotate-180 transform" : "rotate-360"
|
||||||
|
} h-5 w-5 `}
|
||||||
|
/>
|
||||||
|
</Disclosure.Button>
|
||||||
|
|
||||||
|
<Link to="#" className="w-full">
|
||||||
|
<Disclosure.Panel
|
||||||
|
className="px-2 flex items-center font-normal gap-1 py-1 w-full ease-in-out duration-300
|
||||||
|
rounded
|
||||||
|
hover:bg-gray-200
|
||||||
|
text-base
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SVGFile className="stroke-black w-4 h-4" />
|
||||||
|
My Publications
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link to="#" className="w-full">
|
||||||
|
<Disclosure.Panel
|
||||||
|
className="px-2 flex items-center font-normal gap-1 py-1 w-full ease-in-out duration-300
|
||||||
|
rounded
|
||||||
|
hover:bg-gray-200
|
||||||
|
text-base
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SVGFavoriteOutlined className="stroke-black w-4 h-4" />
|
||||||
|
My Favorites
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link to="#" className="w-full">
|
||||||
|
<Disclosure.Panel
|
||||||
|
className="px-2 flex items-center font-normal gap-1 py-1 w-full ease-in-out duration-300
|
||||||
|
rounded
|
||||||
|
hover:bg-gray-200
|
||||||
|
text-base
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SVGFolder className="stroke-black fill-black w-4 h-4" />
|
||||||
|
My Collections
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link to="#" className="w-full">
|
||||||
|
<Disclosure.Panel
|
||||||
|
className="px-2 flex items-center font-normal gap-1 py-1 w-full ease-in-out duration-300
|
||||||
|
rounded
|
||||||
|
hover:bg-gray-200
|
||||||
|
text-base
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SVGEye className="stroke-black w-4 h-4" />
|
||||||
|
Recent Viewed
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Disclosure>
|
||||||
|
<hr />
|
||||||
|
{/* Third list - start */}
|
||||||
|
<Disclosure>
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Disclosure.Button
|
||||||
|
className="uppercase px-2 flex justify-between w-full items-center
|
||||||
|
py-1
|
||||||
|
hover:bg-gray-100
|
||||||
|
text-base
|
||||||
|
"
|
||||||
|
>
|
||||||
|
About
|
||||||
|
<SVGArrowUp
|
||||||
|
className={`${
|
||||||
|
open ? "rotate-180 transform" : "rotate-360"
|
||||||
|
} h-5 w-5 `}
|
||||||
|
/>
|
||||||
|
</Disclosure.Button>
|
||||||
|
|
||||||
|
<Link to="#" className="w-full">
|
||||||
|
<Disclosure.Panel
|
||||||
|
className="px-2 flex items-center font-normal gap-1 py-1 w-full ease-in-out duration-300
|
||||||
|
rounded
|
||||||
|
hover:bg-gray-200
|
||||||
|
text-base
|
||||||
|
"
|
||||||
|
>
|
||||||
|
About Freeland
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link to="#" className="w-full">
|
||||||
|
<Disclosure.Panel
|
||||||
|
className="px-2 flex items-center font-normal gap-1 py-1 w-full ease-in-out duration-300
|
||||||
|
rounded
|
||||||
|
hover:bg-gray-200
|
||||||
|
text-base
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Contact Us
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link to="#" className="w-full">
|
||||||
|
<Disclosure.Panel
|
||||||
|
className="px-2 flex items-center font-normal gap-1 py-1 w-full ease-in-out duration-300
|
||||||
|
rounded
|
||||||
|
hover:bg-gray-200
|
||||||
|
text-base
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Help
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Disclosure>
|
||||||
|
{/* Third list - end */}
|
||||||
|
</div>
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Burger;
|
@ -3,7 +3,7 @@ import { SVGMedicine } from "../icons";
|
|||||||
import Typography from "components/typography/Typography";
|
import Typography from "components/typography/Typography";
|
||||||
import { Button } from "components/Button/Button";
|
import { Button } from "components/Button/Button";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { JsxElement } from "typescript";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
count?: number;
|
count?: number;
|
||||||
@ -13,6 +13,7 @@ type Props = {
|
|||||||
} & Omit<React.ComponentPropsWithoutRef<"div">, "">;
|
} & Omit<React.ComponentPropsWithoutRef<"div">, "">;
|
||||||
|
|
||||||
function CategoryCard({ count, title, iconChild, className, ...props }: Props) {
|
function CategoryCard({ count, title, iconChild, className, ...props }: Props) {
|
||||||
|
const [t, i18next] = useTranslation();
|
||||||
const iconChildStyle =
|
const iconChildStyle =
|
||||||
"h-7 fill-gray-500 stroke-gray-500 group-focus:fill-blue-600 group-active:fill-blue-600 group-focus:stroke-blue-600 group-active:stroke-blue-600";
|
"h-7 fill-gray-500 stroke-gray-500 group-focus:fill-blue-600 group-active:fill-blue-600 group-focus:stroke-blue-600 group-active:stroke-blue-600";
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ function CategoryCard({ count, title, iconChild, className, ...props }: Props) {
|
|||||||
fontWeightVariant="bold"
|
fontWeightVariant="bold"
|
||||||
className="text-sm leading-6 min-w-max group-active:text-blue-600 group-focus:text-blue-600"
|
className="text-sm leading-6 min-w-max group-active:text-blue-600 group-focus:text-blue-600"
|
||||||
>
|
>
|
||||||
{title}
|
{t(title)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-max ">
|
<div className="max-w-max ">
|
||||||
@ -42,7 +43,8 @@ function CategoryCard({ count, title, iconChild, className, ...props }: Props) {
|
|||||||
fontWeightVariant="normal"
|
fontWeightVariant="normal"
|
||||||
className="text-xs text-gray-500 group-active:text-blue-600 group-focus:text-blue-600"
|
className="text-xs text-gray-500 group-active:text-blue-600 group-focus:text-blue-600"
|
||||||
>
|
>
|
||||||
{count} Items
|
{count}{" "}
|
||||||
|
{t("mainPage.article_many", { count: count }).toString()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { ReactComponent as Checkmark } from "assets/svg/check.svg";
|
import { ReactComponent as Checkmark } from "../assets/svg/arrow-down.svg";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
/**
|
/**
|
||||||
* Control the state of checkbox
|
* Control the state of checkbox
|
||||||
*/
|
*/
|
||||||
isChecked: boolean;
|
isChecked?: boolean;
|
||||||
/**
|
/**
|
||||||
* An Element next to the checkbox
|
* An Element next to the checkbox
|
||||||
*/
|
*/
|
||||||
@ -22,7 +22,7 @@ const Checkbox = ({ children, className, isChecked, ...props }: Props) => {
|
|||||||
)}
|
)}
|
||||||
htmlFor={props.id}
|
htmlFor={props.id}
|
||||||
>
|
>
|
||||||
<div className="w-6 h-6 relative">
|
<div className="w-5 h-5 relative">
|
||||||
<input
|
<input
|
||||||
className="peer appearance-none transition-colors bg-transparent border-2 border-gray-300 w-6 h-6
|
className="peer appearance-none transition-colors bg-transparent border-2 border-gray-300 w-6 h-6
|
||||||
rounded checked:bg-blue-500 checked:border-blue-500
|
rounded checked:bg-blue-500 checked:border-blue-500
|
||||||
@ -33,12 +33,8 @@ const Checkbox = ({ children, className, isChecked, ...props }: Props) => {
|
|||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
<div //
|
<div className="h-2 w-2 absolute top-0.5 left-0.5">
|
||||||
className=" w-4 h-3 leading-[0] absolute top-1.5 left-1 opacity-0
|
<Checkmark className="h-5 w-5 fill-white hover:fill-white stroke-white" />
|
||||||
pointer-events-none focus:pointer-events-auto flex items-center justify-center
|
|
||||||
fill-white peer-disabled:fill-gray-500 "
|
|
||||||
>
|
|
||||||
<Checkmark className="fill-inherit" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
|
55
src/components/Disclosure.tsx
Normal file
55
src/components/Disclosure.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Imports */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
import React from "react";
|
||||||
|
import { Disclosure as Disclose } from "@headlessui/react";
|
||||||
|
import { ReactComponent as SelectIcon } from "../assets/svg/arrow-down.svg";
|
||||||
|
import classNames from "classnames";
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Component props */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
type Props = {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
caption?: string;
|
||||||
|
};
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* styles */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
const ButtonStyle = `
|
||||||
|
flex w-full
|
||||||
|
justify-between
|
||||||
|
items-center
|
||||||
|
py-2 text-left
|
||||||
|
text-base
|
||||||
|
font-medium
|
||||||
|
text-black-400
|
||||||
|
`;
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Component implementation */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
export default function Disclosure({ children, className, caption }: Props) {
|
||||||
|
return (
|
||||||
|
<div className={classNames("top-16 ", className)}>
|
||||||
|
<div className="mx-auto w-full bg-white py-2">
|
||||||
|
<Disclose as="div">
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Disclose.Button className={`${ButtonStyle}`}>
|
||||||
|
<span>{caption}</span>
|
||||||
|
<SelectIcon
|
||||||
|
className={`${
|
||||||
|
open ? "rotate-180 transform" : ""
|
||||||
|
} h-4 w-5 fill-black hover:fill-black stroke-black`}
|
||||||
|
/>
|
||||||
|
</Disclose.Button>
|
||||||
|
<Disclose.Panel className="py-2 text-sm text-gray-500 ">
|
||||||
|
{children}
|
||||||
|
</Disclose.Panel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Disclose>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
69
src/components/Filters/AppiledFilters.tsx
Normal file
69
src/components/Filters/AppiledFilters.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Imports */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
import React from "react";
|
||||||
|
import Badge from "components/Badge";
|
||||||
|
import { IProduct } from "./IProdutct";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Component props */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
type Props = {
|
||||||
|
hints?: IProduct[];
|
||||||
|
delFilter: (element: IProduct) => void;
|
||||||
|
clearAll: () => void;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Component implementation */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
export default function AppiledFilters({
|
||||||
|
hints,
|
||||||
|
delFilter,
|
||||||
|
clearAll,
|
||||||
|
className,
|
||||||
|
}: Props) {
|
||||||
|
|
||||||
|
const [t, i18next] = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"w-full py-3 h-36 max-h-36 overflow-hidden ",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center justify-between space-x-3 ">
|
||||||
|
<div className="font-medium">{t("filters.appliedFitlers")}</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={clearAll}
|
||||||
|
className="font-normal text-sm text-gray-400"
|
||||||
|
>
|
||||||
|
{t("filters.clearAll")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul className="list-none mt-2 h-24 max-h-32 overflow-auto">
|
||||||
|
{hints &&
|
||||||
|
hints.length > 0 &&
|
||||||
|
hints.map((item: IProduct) => (
|
||||||
|
<li className="mt-1" key={item.id}>
|
||||||
|
<Badge
|
||||||
|
className="py-1 cursor-default"
|
||||||
|
emphasis="high"
|
||||||
|
closeOption={true}
|
||||||
|
onClick={() => delFilter(item)}
|
||||||
|
>
|
||||||
|
{item.brand}
|
||||||
|
</Badge>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
112
src/components/Filters/Filter.tsx
Normal file
112
src/components/Filters/Filter.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Disclosure from "components/Disclosure";
|
||||||
|
import AppiledFilters from "./AppiledFilters";
|
||||||
|
import SearchFilterBar from "./SearchFilterBar";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useDebounce } from "./functions/debounce";
|
||||||
|
import { IProduct } from "./IProdutct";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Fiter({ className }: Props) {
|
||||||
|
const [checkedId, setCheckedId] = React.useState<Array<number>>([]);
|
||||||
|
const [activeFilter, setActiveFilter] = React.useState<Array<IProduct>>([]);
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Test request to demonstrate the basic functionality of the filter */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
const [query, setQuery] = React.useState("");
|
||||||
|
const [hints, setHints] = React.useState<Array<IProduct>>([]);
|
||||||
|
|
||||||
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setQuery(e.target.value);
|
||||||
|
};
|
||||||
|
const debounced = useDebounce(query);
|
||||||
|
|
||||||
|
const [t, i18next] = useTranslation();
|
||||||
|
|
||||||
|
async function fetchProducts() {
|
||||||
|
const response = await axios.get(
|
||||||
|
`https://dummyjson.com/products/search?q=${debounced}`
|
||||||
|
);
|
||||||
|
setHints(response.data.products);
|
||||||
|
}
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchProducts();
|
||||||
|
}, [debounced, query]);
|
||||||
|
|
||||||
|
const isChecked = (item: number) => (checkedId.includes(item) ? true : false);
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const selectedId = parseInt(e.target.value);
|
||||||
|
|
||||||
|
/* ------------- Check if "checkedId" contains "selectedIds" ------------- */
|
||||||
|
/* -------------- If true, this checkbox is already checked -------------- */
|
||||||
|
|
||||||
|
if (checkedId.includes(selectedId)) {
|
||||||
|
setCheckedId(checkedId.filter((id) => id !== selectedId));
|
||||||
|
setActiveFilter(activeFilter.filter((item) => item.id !== selectedId));
|
||||||
|
} else {
|
||||||
|
const newId = [...checkedId];
|
||||||
|
newId.push(selectedId);
|
||||||
|
setCheckedId(newId);
|
||||||
|
|
||||||
|
/* --------------- Adding checked filters to AppliedFilters component -------------- */
|
||||||
|
|
||||||
|
hints.forEach((hint) => {
|
||||||
|
if (hint.id === selectedId)
|
||||||
|
activeFilter.push({ id: selectedId, brand: hint.brand });
|
||||||
|
setActiveFilter(activeFilter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ------------- Delete filter from AppliedFilters component ------------- */
|
||||||
|
|
||||||
|
const DelFilter = (e: IProduct) => {
|
||||||
|
setActiveFilter(activeFilter.filter((item) => item.id !== e.id));
|
||||||
|
setCheckedId(checkedId.filter((id) => id !== e.id));
|
||||||
|
};
|
||||||
|
const clearAll = () => {
|
||||||
|
setActiveFilter([]);
|
||||||
|
setCheckedId([]);
|
||||||
|
setQuery("");
|
||||||
|
};
|
||||||
|
//console.log(activeFilter)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="pl-2">
|
||||||
|
<div className="max-w-[268px] mx-auto divide-y divide-solid">
|
||||||
|
<AppiledFilters
|
||||||
|
hints={activeFilter}
|
||||||
|
delFilter={DelFilter}
|
||||||
|
clearAll={clearAll}
|
||||||
|
></AppiledFilters>
|
||||||
|
<Disclosure caption={t("filters.authors")}>
|
||||||
|
<SearchFilterBar
|
||||||
|
hints={hints}
|
||||||
|
isChecked={isChecked}
|
||||||
|
handleChange={handleChange}
|
||||||
|
onChange={onChange}
|
||||||
|
query={query}
|
||||||
|
placeHolder={t("filters.enterAuthorsName")}
|
||||||
|
/>
|
||||||
|
</Disclosure>
|
||||||
|
<Disclosure caption={t("filters.publicationsType")}>
|
||||||
|
<p>{t("filters.content")}</p>
|
||||||
|
</Disclosure>
|
||||||
|
<Disclosure caption={t("filters.publisher")}>
|
||||||
|
<p>{t("filters.content")}</p>
|
||||||
|
</Disclosure>
|
||||||
|
<Disclosure caption={t("filters.publicationTopic")}>
|
||||||
|
<p>{t("filters.content")}</p>
|
||||||
|
</Disclosure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
23
src/components/Filters/IProdutct.ts
Normal file
23
src/components/Filters/IProdutct.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export interface IProduct {
|
||||||
|
id?: number;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
price?: number;
|
||||||
|
discountPercentage?: number;
|
||||||
|
rating?: number;
|
||||||
|
stock?: number;
|
||||||
|
brand?: string;
|
||||||
|
category?: string;
|
||||||
|
thumbnail?: string;
|
||||||
|
images?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface serverResponse<T> {
|
||||||
|
products: T[];
|
||||||
|
total: number;
|
||||||
|
skip: number;
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
120
src/components/Filters/SearchFilterBar.tsx
Normal file
120
src/components/Filters/SearchFilterBar.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { useState, useCallback, Fragment } from "react";
|
||||||
|
import { Combobox, Transition } from "@headlessui/react";
|
||||||
|
import { IProduct } from "./IProdutct";
|
||||||
|
import Checkbox from "components/Checkbox";
|
||||||
|
import { ReactComponent as SearchIcon } from "assets/svg/search.svg";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
hints: IProduct[];
|
||||||
|
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
isChecked: (item: number) => boolean;
|
||||||
|
className?: string;
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
placeHolder: string;
|
||||||
|
query: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SearchFilterBar({
|
||||||
|
hints,
|
||||||
|
isChecked,
|
||||||
|
handleChange,
|
||||||
|
className,
|
||||||
|
onChange,
|
||||||
|
placeHolder,
|
||||||
|
query,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const [checkList, setCheckList] = useState(hints);
|
||||||
|
const [showFilters, SetShowFilters] = useState(false);
|
||||||
|
const [t, i18next] = useTranslation()
|
||||||
|
|
||||||
|
const ShowAllFilters = useCallback(() => {
|
||||||
|
SetShowFilters((prev) => !prev);
|
||||||
|
}, [SetShowFilters]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Combobox value={checkList} onChange={setCheckList}>
|
||||||
|
{({ open }) => (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"flex flex-row w-full items-center border border-solid rounded border-gray-200",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="basis-6 ">
|
||||||
|
<Combobox.Button>
|
||||||
|
<div className="pl-1 pt-1.5">
|
||||||
|
<SearchIcon className="h-5 w-6 fill-gray-400 hover:fill-gray-400 stroke-gray-400" />
|
||||||
|
</div>
|
||||||
|
</Combobox.Button>
|
||||||
|
</div>
|
||||||
|
<div className="grow">
|
||||||
|
<Combobox.Input
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder={placeHolder}
|
||||||
|
className="w-full text-sm py-1 focus:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
leave="transition ease-in duration-100"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<Combobox.Options static={true}>
|
||||||
|
{open && (
|
||||||
|
<ul
|
||||||
|
className={classNames([
|
||||||
|
"w-[276px] relative h-40 top-0 right-2",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"max-h-60 h-60 overflow-auto": showFilters === true,
|
||||||
|
"max-h-40 h-40 overflow-hidden":
|
||||||
|
showFilters === false || hints.length <= 5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
<li className="">
|
||||||
|
{hints.length >= 6 && query !== "" && (
|
||||||
|
<button
|
||||||
|
onClick={ShowAllFilters}
|
||||||
|
className="text-right text-blue-500 pl-2 text-sm font-medium"
|
||||||
|
>
|
||||||
|
{t("filters.showAll")}({hints.length})
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
{hints.length > 0 ? (
|
||||||
|
hints.map((item) => (
|
||||||
|
<li key={item.id} className="mt-1 pl-2">
|
||||||
|
<Checkbox
|
||||||
|
className="z-10 mt-1 w-full max-h-56"
|
||||||
|
value={item.id}
|
||||||
|
isChecked={item.id ? isChecked(item.id) : false}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<p>{item.brand}</p>
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-blue-300 pl-2">Ничего не найдено</p>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</Combobox.Options>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Combobox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
11
src/components/Filters/functions/debounce.ts
Normal file
11
src/components/Filters/functions/debounce.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function useDebounce(value: string, delay: number = 300) {
|
||||||
|
const [debounced, setDebouced] = React.useState(value);
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handler = setTimeout(() => setDebouced(value), delay);
|
||||||
|
return () => clearTimeout(handler);
|
||||||
|
}, [value, delay]);
|
||||||
|
|
||||||
|
return debounced;
|
||||||
|
}
|
89
src/components/LocalizationButton.tsx
Normal file
89
src/components/LocalizationButton.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import React, { Fragment, useState } from "react";
|
||||||
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { Button } from "./Button/Button";
|
||||||
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faLanguage } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
type Props = React.ComponentPropsWithoutRef<"div">;
|
||||||
|
|
||||||
|
const LocalizationButton = (props: Props) => {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const changeLanguage = (lng: string) => {
|
||||||
|
i18n.changeLanguage(lng);
|
||||||
|
setLanguage(lng);
|
||||||
|
};
|
||||||
|
const [language, setLanguage] = useState("en");
|
||||||
|
return (
|
||||||
|
<div {...props}>
|
||||||
|
<Menu as="div" className="relative inline-block text-left">
|
||||||
|
<div>
|
||||||
|
<Menu.Button
|
||||||
|
as={Button}
|
||||||
|
emphasis="low"
|
||||||
|
className="flex items-center gap-2 border"
|
||||||
|
>
|
||||||
|
<div className=" w-4">
|
||||||
|
<FontAwesomeIcon icon={faLanguage} />
|
||||||
|
</div>
|
||||||
|
{/* {language} */}
|
||||||
|
</Menu.Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-100"
|
||||||
|
enterFrom="transform opacity-0 scale-95"
|
||||||
|
enterTo="transform opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items
|
||||||
|
className="origin-top-right absolute right-0 mt-5 w-12 rounded-md
|
||||||
|
shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||||
|
>
|
||||||
|
<div className="py-1">
|
||||||
|
<Menu.Item>
|
||||||
|
{({ active }) => (
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
changeLanguage("en");
|
||||||
|
}}
|
||||||
|
className={classNames(
|
||||||
|
active ? "bg-gray-100 text-gray-900" : "text-gray-700",
|
||||||
|
"block px-4 py-2 text-sm"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
En
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
{({ active }) => (
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
changeLanguage("ru");
|
||||||
|
}}
|
||||||
|
|
||||||
|
className={classNames(
|
||||||
|
active ? "bg-gray-100 text-gray-900" : "text-gray-700",
|
||||||
|
"block px-4 py-2 text-sm"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Ru
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</Menu.Item>
|
||||||
|
</div>
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LocalizationButton;
|
@ -7,30 +7,53 @@ export type Props = {
|
|||||||
|
|
||||||
const Logo = ({ className, fillColors = "blue" }: Props) => {
|
const Logo = ({ className, fillColors = "blue" }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<svg
|
||||||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
className={classNames(className, "group")}
|
||||||
|
viewBox="0 0 40 40"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M14.4144 23.7511C8.27781 15.6345 1.97719 21.8024 3.62218 27.4753C7.75871 36.521 18.4462 40.5011 27.4937 36.364C30.6123 34.9384 33.2592 32.6525 35.1232 29.7747C30.3791 34.2166 21.7659 33.4741 14.4144 23.7497V23.7511Z"
|
d="M14.4144 23.7511C8.27781 15.6345 1.97719 21.8024 3.62218 27.4753C7.75871 36.521 18.4462 40.5011 27.4937 36.364C30.6123 34.9384 33.2592 32.6525 35.1232 29.7747C30.3791 34.2166 21.7659 33.4741 14.4144 23.7497V23.7511Z"
|
||||||
className={fillColors === "blue" ? "fill-blue-800" : "fill-gray-800"}
|
className={
|
||||||
|
fillColors === "blue"
|
||||||
|
? "fill-blue-800"
|
||||||
|
: "transition-colors fill-gray-800 group-hover:fill-blue-800"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M19.9003 18.1623C11.4604 5.36346 1.99988 12.1818 1.99988 20.0029C1.99656 22.5922 2.55489 25.1515 3.63638 27.5043C2.51332 22.2054 8.60318 18.1213 14.273 25.7253C22.5092 36.7684 30.5857 34.8254 35.1069 29.8009C36.5583 27.5725 37.4959 25.0493 37.8517 22.414V22.4486C35.9103 28.4694 27.3438 29.4487 19.9003 18.1623Z"
|
d="M19.9003 18.1623C11.4604 5.36346 1.99988 12.1818 1.99988 20.0029C1.99656 22.5922 2.55489 25.1515 3.63638 27.5043C2.51332 22.2054 8.60318 18.1213 14.273 25.7253C22.5092 36.7684 30.5857 34.8254 35.1069 29.8009C36.5583 27.5725 37.4959 25.0493 37.8517 22.414V22.4486C35.9103 28.4694 27.3438 29.4487 19.9003 18.1623Z"
|
||||||
className={fillColors === "blue" ? "fill-blue-700" : "fill-gray-700"}
|
className={
|
||||||
|
fillColors === "blue"
|
||||||
|
? "fill-blue-700"
|
||||||
|
: "transition-colors fill-gray-700 group-hover:fill-blue-700"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M25.6847 12.4357C19.4173 2.09746 11.4696 3.03152 6.09469 8.56867C3.44128 11.788 1.99328 15.8313 1.99989 20.0029C2.56566 12.4371 11.7164 7.16935 20.0007 20.0029C27.3643 31.4243 36.5384 28.4489 37.8432 22.4812V22.4239C37.9578 21.6113 38.0065 20.7908 37.9889 19.9703V19.1522C34.8 21.434 30.6062 20.553 25.6832 12.435L25.6847 12.4357Z"
|
d="M25.6847 12.4357C19.4173 2.09746 11.4696 3.03152 6.09469 8.56867C3.44128 11.788 1.99328 15.8313 1.99989 20.0029C2.56566 12.4371 11.7164 7.16935 20.0007 20.0029C27.3643 31.4243 36.5384 28.4489 37.8432 22.4812V22.4239C37.9578 21.6113 38.0065 20.7908 37.9889 19.9703V19.1522C34.8 21.434 30.6062 20.553 25.6832 12.435L25.6847 12.4357Z"
|
||||||
className={fillColors === "blue" ? "fill-blue-600" : "fill-gray-500"}
|
className={
|
||||||
|
fillColors === "blue"
|
||||||
|
? "fill-blue-600"
|
||||||
|
: "transition-colors fill-gray-500 group-hover:fill-blue-600"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M25.7278 14.2762C31.4676 23.5022 36.416 21.1419 38.0009 19.1685C37.9371 17.7893 37.714 16.4222 37.3361 15.0943C34.6607 15.1515 34.0369 14.704 31.404 10.6969C27.3806 4.53119 22.3119 0.369312 13.4568 3.23299C10.5865 4.35751 8.05111 6.19656 6.09116 8.57569C12.1761 2.97702 19.6422 4.49654 25.7278 14.2762Z"
|
d="M25.7278 14.2762C31.4676 23.5022 36.416 21.1419 38.0009 19.1685C37.9371 17.7893 37.714 16.4222 37.3361 15.0943C34.6607 15.1515 34.0369 14.704 31.404 10.6969C27.3806 4.53119 22.3119 0.369312 13.4568 3.23299C10.5865 4.35751 8.05111 6.19656 6.09116 8.57569C12.1761 2.97702 19.6422 4.49654 25.7278 14.2762Z"
|
||||||
className={fillColors === "blue" ? "fill-blue-400" : "fill-gray-200"}
|
className={
|
||||||
|
fillColors === "blue"
|
||||||
|
? "fill-blue-400"
|
||||||
|
: "transition-colors fill-gray-200 group-hover:fill-blue-400"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M31.0114 11.4889C33.6359 15.5037 35.2335 15.708 37.336 15.095C34.6097 5.51969 24.6358 -0.0323048 15.0593 2.69349C14.5162 2.84763 13.982 3.02747 13.4568 3.23299C21.367 0.737706 26.9774 5.32312 31.0114 11.4889Z"
|
d="M31.0114 11.4889C33.6359 15.5037 35.2335 15.708 37.336 15.095C34.6097 5.51969 24.6358 -0.0323048 15.0593 2.69349C14.5162 2.84763 13.982 3.02747 13.4568 3.23299C21.367 0.737706 26.9774 5.32312 31.0114 11.4889Z"
|
||||||
className={fillColors === "blue" ? "fill-blue-200" : "fill-gray-75"}
|
className={
|
||||||
|
fillColors === "blue"
|
||||||
|
? "fill-blue-200"
|
||||||
|
: "transition-colors fill-gray-75 group-hover:fill-blue-200"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
39
src/components/LogoScipaper.tsx
Executable file
39
src/components/LogoScipaper.tsx
Executable file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -31,6 +31,7 @@ import "swiper/css/pagination";
|
|||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
// import "./styles.css";
|
// import "./styles.css";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Article mock data */
|
/* Article mock data */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
@ -97,9 +98,10 @@ const FeaturedArticlesCards = () => {
|
|||||||
const navigationPrevRef = useRef(null);
|
const navigationPrevRef = useRef(null);
|
||||||
const navigationNextRef = useRef(null);
|
const navigationNextRef = useRef(null);
|
||||||
const paginationRef = useRef(null);
|
const paginationRef = useRef(null);
|
||||||
|
const [t, i18next] = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="slider-wrapper articles">
|
<div className="slider-wrapper articles px-8">
|
||||||
<div className="flex justify-end gap-2 my-2">
|
<div className="flex justify-end gap-2 my-2">
|
||||||
<div
|
<div
|
||||||
className="prev inline-flex justify-center items-center
|
className="prev inline-flex justify-center items-center
|
||||||
@ -166,11 +168,12 @@ const FeaturedArticlesCards = () => {
|
|||||||
</Card.CardContent>
|
</Card.CardContent>
|
||||||
|
|
||||||
<Card.CardAction href={Articale.Link}>
|
<Card.CardAction href={Articale.Link}>
|
||||||
<Link to="*">
|
<Typography
|
||||||
<Typography className="text-blue-500 font-bold">
|
className="text-blue-500 font-bold"
|
||||||
Read More
|
fontWeightVariant="bold"
|
||||||
|
>
|
||||||
|
{t("mainPage.more")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Link>
|
|
||||||
<SVGCaretRight className="fill-blue-500 w-4 h-4" />
|
<SVGCaretRight className="fill-blue-500 w-4 h-4" />
|
||||||
</Card.CardAction>
|
</Card.CardAction>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -9,22 +9,25 @@ import {
|
|||||||
SVGTechnicsAndTechology,
|
SVGTechnicsAndTechology,
|
||||||
SVGFundamental,
|
SVGFundamental,
|
||||||
} from "components/icons";
|
} from "components/icons";
|
||||||
|
import { useTranslation} from "react-i18next";
|
||||||
|
|
||||||
const categories = [
|
const categories = [
|
||||||
{ id: 1, title: "Medical", count: 5617813, icon: <SVGMedicine /> },
|
{ id: 1, title: "mainPage.featuredArticles.categories.Medical", count: 5617813, icon: <SVGMedicine /> },
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: "Technics and Technology",
|
title: "mainPage.featuredArticles.categories.TechnicsAndTechlonogies",
|
||||||
count: 5617813,
|
count: 5617813,
|
||||||
icon: <SVGTechnicsAndTechology />,
|
icon: <SVGTechnicsAndTechology />,
|
||||||
},
|
},
|
||||||
{ id: 3, title: "Fundamental", count: 5617813, icon: <SVGFundamental /> },
|
{ id: 3, title: "mainPage.featuredArticles.categories.Fundamental", count: 5617813, icon: <SVGFundamental /> },
|
||||||
{ id: 4, title: "Humanitarian", count: 5617813, icon: <SVGHumanitarian /> },
|
{ id: 4, title: "mainPage.featuredArticles.categories.Humanitarian", count: 5617813, icon: <SVGHumanitarian /> },
|
||||||
{ id: 5, title: "Agricultural", count: 5617813, icon: <SVGAgricultural /> },
|
{ id: 5, title: "mainPage.featuredArticles.categories.Agricultural", count: 5617813, icon: <SVGAgricultural /> },
|
||||||
{ id: 6, title: "Social", count: 5617813, icon: <SVGSocials /> },
|
{ id: 6, title: "mainPage.featuredArticles.categories.Social", count: 5617813, icon: <SVGSocials /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function FeaturedArticlesCategories() {
|
export function FeaturedArticlesCategories() {
|
||||||
|
const [t, i18next] = useTranslation();
|
||||||
|
|
||||||
const categoryCards = useMemo(
|
const categoryCards = useMemo(
|
||||||
() =>
|
() =>
|
||||||
categories.map((category) => (
|
categories.map((category) => (
|
||||||
@ -45,11 +48,11 @@ export function FeaturedArticlesCategories() {
|
|||||||
fontWeightVariant="semibold"
|
fontWeightVariant="semibold"
|
||||||
className="text-3xl mb-2"
|
className="text-3xl mb-2"
|
||||||
>
|
>
|
||||||
Featured articles
|
{t("mainPage.featuredArticles.title")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography htmlTag="h2" className="text-base text-gray-500">
|
<Typography htmlTag="h2" className="text-base text-gray-500">
|
||||||
Select the category of science <br className="visible sm:hidden" />
|
{t("mainPage.featuredArticles.descriptionPart1")}<br className="visible sm:hidden" />
|
||||||
you are interested in
|
{t("mainPage.featuredArticles.descriptionPart2")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-8 px-10 flex md:flex justify-start md:justify-center md:flex-wrap overflow-x-scroll md:overflow-hidden snap-x scroll-smooth overscroll-x-contain">
|
<div className="py-8 px-10 flex md:flex justify-start md:justify-center md:flex-wrap overflow-x-scroll md:overflow-hidden snap-x scroll-smooth overscroll-x-contain">
|
||||||
|
@ -17,12 +17,9 @@ import "swiper/css/navigation";
|
|||||||
// import "./styles.css";
|
// import "./styles.css";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* Icons */
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
import { ReactComponent as SVGCaretRight } from "assets/svg/caret-right.svg";
|
import { ReactComponent as SVGCaretRight } from "assets/svg/caret-right.svg";
|
||||||
import Link from "../../typography/Link";
|
import Link from "../../typography/Link";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Variables */
|
/* Variables */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
@ -73,13 +70,17 @@ if (authors.length == 2) {
|
|||||||
* Featured authors component to display ...
|
* Featured authors component to display ...
|
||||||
*/
|
*/
|
||||||
export default function FeaturedAuthorsCards(): JSX.Element {
|
export default function FeaturedAuthorsCards(): JSX.Element {
|
||||||
|
const [t, i18next] = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* The Title of Featured Authors section */}
|
{/* The Title of Featured Authors section */}
|
||||||
<Heading className="text-center my-8">Featured Authors</Heading>
|
<Heading className="text-center my-8 text-3xl font-semibold">
|
||||||
|
{t("mainPage.featuredAuthors")}
|
||||||
|
</Heading>
|
||||||
|
|
||||||
{/* Featured Authors section */}
|
{/* Featured Authors section */}
|
||||||
<div className="slider-wrapper Authors">
|
<div className="slider-wrapper Authors px-8">
|
||||||
<Swiper
|
<Swiper
|
||||||
slidesPerView={1.25}
|
slidesPerView={1.25}
|
||||||
slidesPerGroup={1}
|
slidesPerGroup={1}
|
||||||
@ -134,7 +135,7 @@ export default function FeaturedAuthorsCards(): JSX.Element {
|
|||||||
|
|
||||||
<Card.CardAction href={card.Link}>
|
<Card.CardAction href={card.Link}>
|
||||||
<Link className="text-blue-500 font-bold" to="*">
|
<Link className="text-blue-500 font-bold" to="*">
|
||||||
See More
|
{t("mainPage.more")}
|
||||||
</Link>
|
</Link>
|
||||||
<SVGCaretRight className="fill-blue-500 w-4 h-4" />
|
<SVGCaretRight className="fill-blue-500 w-4 h-4" />
|
||||||
</Card.CardAction>
|
</Card.CardAction>
|
||||||
@ -188,7 +189,7 @@ export default function FeaturedAuthorsCards(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button emphasis="high" className="font-bold m-auto my-8">
|
<Button emphasis="high" className="font-bold m-auto my-8">
|
||||||
Show All
|
{t("mainPage.showAll")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,24 +2,36 @@
|
|||||||
/* Imports */
|
/* Imports */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { t as nextT } from "i18next";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { SearchBar } from "../../search/SearchBar";
|
import { SearchBar } from "../../search/SearchBar";
|
||||||
|
import { formatNumber } from "core/helpers";
|
||||||
|
|
||||||
export default function MainSection() {
|
export default function MainSection() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const amountArticles = 4202020;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="bg-main bg-center bg-cover bg-origin-border bg-no-repeat min-h-[100vh] py-32 px-2 sm:px-6 md:px-6 lg:px-0 items-center flex justify-center ">
|
<section className="bg-main bg-center bg-cover bg-origin-border bg-no-repeat min-h-[100vh] py-32 px-2 sm:px-6 md:px-6 lg:px-0 items-center flex justify-center ">
|
||||||
<div className="flex-col">
|
<div className="flex-col">
|
||||||
<div className="m-auto text-center font-bold text-4xl ">
|
<div className="m-auto text-center font-bold text-4xl ">
|
||||||
Scientific Library with Free Access
|
{t("mainPage.title")}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center justify-center space-x-3 pt-2">
|
<div className="flex flex-row items-center justify-center space-x-3 pt-2">
|
||||||
<div className=" text-2xl text-gray-400">Search</div>
|
<div className=" text-2xl text-gray-400">{t("mainPage.search")}</div>
|
||||||
<div className=" text-3xl text-blue-500">320 455</div>
|
<div className=" text-3xl text-blue-500">
|
||||||
<div className=" text-2xl text-gray-400">Items</div>
|
{formatNumber(amountArticles)}
|
||||||
|
</div>
|
||||||
|
<div className=" text-2xl text-gray-400">
|
||||||
|
{nextT("mainPage.article_many", {
|
||||||
|
count: amountArticles,
|
||||||
|
}).toString()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-xl m-auto pt-16 ">
|
<div className="max-w-xl m-auto pt-16 ">
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
<div className="mt-7 pr-1 text-right font-semibold text-sm">
|
<div className="mt-7 pr-1 text-right font-semibold text-sm">
|
||||||
Advanced Search
|
{t("mainPage.advancedSearch")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* MarkDown */
|
/* MarkDown */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
@ -11,15 +9,14 @@ import remarkGfm from "remark-gfm";
|
|||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
import Typography from "./typography/Typography";
|
import Typography from "./typography/Typography";
|
||||||
import Heading from "./typography/Heading";
|
import Heading from "./typography/Heading";
|
||||||
import Link from "./Link";
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Code */
|
/* Code */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
import vsc from "react-syntax-highlighter/dist/esm/styles/prism/vsc-dark-plus";
|
|
||||||
import { darcula } from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
||||||
import docco from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
||||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
|
import { dark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||||
|
import Link from "./typography/Link";
|
||||||
|
import style from "react-syntax-highlighter/dist/esm/styles/hljs/a11y-dark";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
markdown: string;
|
markdown: string;
|
||||||
@ -31,29 +28,134 @@ const Markdown = ({ markdown }: Props) => {
|
|||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
children={markdown}
|
children={markdown}
|
||||||
components={{
|
components={{
|
||||||
h1: Heading,
|
ul: ({ node, ...props }) => (
|
||||||
h2: Typography,
|
<ul
|
||||||
a: (props) => {
|
style={{
|
||||||
return (
|
listStyleType: "disc",
|
||||||
<Link
|
}}
|
||||||
href={props.href}
|
className="mx-8"
|
||||||
className="text-sky-600 font-bold text-base"
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
/>
|
||||||
{props.children}
|
),
|
||||||
</Link>
|
|
||||||
);
|
ol: ({ node, ...props }) => (
|
||||||
},
|
<ol className="list-decimal mx-8" {...props} />
|
||||||
|
),
|
||||||
|
|
||||||
|
h1: ({ node, ...props }) => (
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginInlineEnd: 0,
|
||||||
|
marginInlineStart: 0,
|
||||||
|
marginBlockStart: 11,
|
||||||
|
marginBlockEnd: 11,
|
||||||
|
|
||||||
|
fontSize: 32,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
|
||||||
|
h2: ({ node, ...props }) => (
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
|
||||||
|
marginInlineEnd: 0,
|
||||||
|
marginInlineStart: 0,
|
||||||
|
|
||||||
|
marginBlockStart: 13.28,
|
||||||
|
marginBlockEnd: 13.28,
|
||||||
|
|
||||||
|
fontSize: 24,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
|
||||||
|
h3: ({ node, ...props }) => (
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
|
||||||
|
marginInlineEnd: 0,
|
||||||
|
marginInlineStart: 0,
|
||||||
|
|
||||||
|
marginBlockStart: 16,
|
||||||
|
marginBlockEnd: 16,
|
||||||
|
|
||||||
|
fontSize: 18.72,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
|
||||||
|
h4: ({ node, ...props }) => (
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
|
||||||
|
marginInlineEnd: 0,
|
||||||
|
marginInlineStart: 0,
|
||||||
|
|
||||||
|
marginBlockStart: 21.28,
|
||||||
|
marginBlockEnd: 21.28,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
|
||||||
|
h5: ({ node, ...props }) => (
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
|
||||||
|
marginInlineEnd: 0,
|
||||||
|
marginInlineStart: 0,
|
||||||
|
|
||||||
|
marginBlockStart: 26.72,
|
||||||
|
marginBlockEnd: 26.72,
|
||||||
|
|
||||||
|
fontSize: 13.28,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
|
||||||
|
h6: ({ node, ...props }) => (
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
|
||||||
|
marginInlineEnd: 0,
|
||||||
|
marginInlineStart: 0,
|
||||||
|
|
||||||
|
marginBlockStart: 37.28,
|
||||||
|
marginBlockEnd: 37.28,
|
||||||
|
|
||||||
|
fontSize: 10.72,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
p: Typography,
|
||||||
|
|
||||||
|
a: ({ node, ...props }) => (
|
||||||
|
<Link
|
||||||
|
className=" inline-flex text-sm font-bold text-blue-500"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
|
||||||
code({ node, inline, className, children, ...props }) {
|
code({ node, inline, className, children, ...props }) {
|
||||||
const match = /language-(\w+)/.exec(className || "");
|
const match = /language-(\w+)/.exec(className || "");
|
||||||
return !inline && match ? (
|
return !inline && match ? (
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
children={String(children).replace(/\n$/, "")}
|
children={String(children).replace(/\n$/, "")}
|
||||||
style={docco}
|
style={dark}
|
||||||
language={match[1]}
|
language={match[1]}
|
||||||
PreTag="div"
|
PreTag="div"
|
||||||
{...props}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<code className={className} {...props}>
|
<code className={className} {...props}>
|
||||||
|
@ -4,10 +4,12 @@ import { useSearchStoreImplementation } from "searchResults/data/searchStoreImpl
|
|||||||
import { useSearchViewModel } from "../searchResults/controller/searchResultsViewModel";
|
import { useSearchViewModel } from "../searchResults/controller/searchResultsViewModel";
|
||||||
import { ArticleSearchResult } from "./ArticleSearchResult";
|
import { ArticleSearchResult } from "./ArticleSearchResult";
|
||||||
import { Loader } from "./Loader/Loader";
|
import { Loader } from "./Loader/Loader";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export const SearchResultSection = () => {
|
export const SearchResultSection = () => {
|
||||||
const store = useSearchStoreImplementation();
|
const store = useSearchStoreImplementation();
|
||||||
const { searchResults, isLoading } = useSearchViewModel(store);
|
const { searchResults, isLoading } = useSearchViewModel(store);
|
||||||
|
const [t, i18next] = useTranslation()
|
||||||
|
|
||||||
function getResults() {
|
function getResults() {
|
||||||
if (searchResults === undefined || searchResults?.data.length === 0) {
|
if (searchResults === undefined || searchResults?.data.length === 0) {
|
||||||
@ -16,7 +18,7 @@ export const SearchResultSection = () => {
|
|||||||
fontWeightVariant="semibold"
|
fontWeightVariant="semibold"
|
||||||
className="text-xl w-full text-center items-center py-3"
|
className="text-xl w-full text-center items-center py-3"
|
||||||
>
|
>
|
||||||
Nothing found.
|
{t("searchResults.nothingFound")}.
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -31,14 +33,14 @@ export const SearchResultSection = () => {
|
|||||||
<div className="p-4 md:px-6 md:py-8">
|
<div className="p-4 md:px-6 md:py-8">
|
||||||
<div className="pb-2">
|
<div className="pb-2">
|
||||||
<Typography fontWeightVariant="semibold" className="text-3xl">
|
<Typography fontWeightVariant="semibold" className="text-3xl">
|
||||||
Search Results
|
{t("searchResults.title")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography className="text-gray-300 text-sm">
|
<Typography className="text-gray-300 text-sm">
|
||||||
Total results: {searchResults?.meta.total}
|
{t("searchResults.totalResults")}: {searchResults?.meta.total}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<hr className="w-full border-gray-100" />
|
<hr className="w-full border-gray-100" />
|
||||||
<div className="divide divide-y divide-gray-100">{ getResults()}</div>
|
<div className="divide divide-y divide-gray-100">{getResults()}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
86
src/components/fetchAnArticle/AnArticle.tsx
Normal file
86
src/components/fetchAnArticle/AnArticle.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { useArticleViewModel } from "article/controller/articleViewModel";
|
||||||
|
import { useArticleStore } from "article/data/articleStoreImplementation";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import * as ArticlePart from "../../components/Article/Article";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import AskeletonArticle from "./AskeletonArticle";
|
||||||
|
import Container from "components/Container";
|
||||||
|
import NotFound from "./NotFound";
|
||||||
|
import { SVGSearch } from "components/icons";
|
||||||
|
import BaseLayout from "components/BaseLayout";
|
||||||
|
import Typography from "components/typography/Typography";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const AnArticle = () => {
|
||||||
|
const store = useArticleStore();
|
||||||
|
const { article, hasError, shouldShowLoading } = useArticleViewModel(store);
|
||||||
|
const { i18n, t } = useTranslation();
|
||||||
|
|
||||||
|
const { id } = useParams();
|
||||||
|
const newId = `${id}`;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
store.getArticle(newId);
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
return <NotFound />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<BaseLayout>
|
||||||
|
<Container variant="straight">
|
||||||
|
{shouldShowLoading ? (
|
||||||
|
<AskeletonArticle />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ArticlePart.Article.Breadcumbs className="py-4">
|
||||||
|
{article?.topic}
|
||||||
|
</ArticlePart.Article.Breadcumbs>
|
||||||
|
<div className="flex flex-col gap-4 pb-4">
|
||||||
|
<ArticlePart.Article.Title className="text-3xl ">
|
||||||
|
{article?.title}
|
||||||
|
</ArticlePart.Article.Title>
|
||||||
|
<ArticlePart.Article.Authors>
|
||||||
|
{article?.authors !== undefined ? article?.authors : "Unknown"}
|
||||||
|
</ArticlePart.Article.Authors>
|
||||||
|
<hr></hr>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ArticlePart.Article.InteractionButtons
|
||||||
|
emphasis="high"
|
||||||
|
articleID={article?.id}
|
||||||
|
/>
|
||||||
|
{article?.tags && (
|
||||||
|
<div className="keywords my-10 flex flex-col gap-2">
|
||||||
|
<Typography className="text-2xl" fontWeightVariant="semibold">
|
||||||
|
{t("articlePage.keywords")}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<ArticlePart.Article.Keywords className="transition ease-in-out delay-50">
|
||||||
|
{article?.tags}
|
||||||
|
</ArticlePart.Article.Keywords>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="abstract my-10 flex flex-col gap-2">
|
||||||
|
<Typography className="text-2xl" fontWeightVariant="semibold">
|
||||||
|
{t("articlePage.abstract")}
|
||||||
|
</Typography>
|
||||||
|
<ArticlePart.Article.Description>
|
||||||
|
{article?.summary !== undefined ? (
|
||||||
|
article?.summary
|
||||||
|
) : (
|
||||||
|
<div className=" bg-gray-100 w-full stroke-gray-800 text-xl flex justify-center py-1">
|
||||||
|
<SVGSearch className="w-14" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ArticlePart.Article.Description>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</BaseLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// \n
|
||||||
|
export default AnArticle;
|
64
src/components/fetchAnArticle/AnArticleBody.tsx
Normal file
64
src/components/fetchAnArticle/AnArticleBody.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { useArticleViewModel } from "article/controller/articleViewModel";
|
||||||
|
import { useArticleStore } from "article/data/articleStoreImplementation";
|
||||||
|
import "react-loading-skeleton/dist/skeleton.css";
|
||||||
|
import Skeleton from "react-loading-skeleton";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Components */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
import * as ArticlePart from "../../components/Article/Article";
|
||||||
|
import BaseLayout from "components/BaseLayout";
|
||||||
|
import Container from "components/Container";
|
||||||
|
import NotFound from "./NotFound";
|
||||||
|
import Markdown from "components/Markdown";
|
||||||
|
|
||||||
|
const AnArticleBody = () => {
|
||||||
|
const store = useArticleStore();
|
||||||
|
const { article, hasError, shouldShowLoading } = useArticleViewModel(store);
|
||||||
|
const { id } = useParams();
|
||||||
|
const newId = `${id}`;
|
||||||
|
useEffect(() => {
|
||||||
|
store.getArticle(newId);
|
||||||
|
}, [id]);
|
||||||
|
if (hasError) <NotFound />;
|
||||||
|
return (
|
||||||
|
<BaseLayout>
|
||||||
|
<Container variant="straight">
|
||||||
|
{shouldShowLoading ? (
|
||||||
|
<>
|
||||||
|
<Skeleton count={1} className="my-4" />
|
||||||
|
<div className="gap-4 py-12 px-20">
|
||||||
|
<Skeleton
|
||||||
|
count={1}
|
||||||
|
containerClassName=" text-3xl"
|
||||||
|
className="mb-6"
|
||||||
|
/>
|
||||||
|
<Skeleton count={15} containerClassName="" />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ArticlePart.Article.Breadcumbs className="py-4">
|
||||||
|
{article?.topic}
|
||||||
|
</ArticlePart.Article.Breadcumbs>
|
||||||
|
<div className="gap-4 py-12 px-20">
|
||||||
|
<ArticlePart.Article.Title className="text-3xl">
|
||||||
|
{article?.title}
|
||||||
|
</ArticlePart.Article.Title>
|
||||||
|
|
||||||
|
<div className="py-6">
|
||||||
|
<Markdown
|
||||||
|
markdown={article?.content ?? ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</BaseLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnArticleBody;
|
59
src/components/fetchAnArticle/AskeletonArticle.tsx
Normal file
59
src/components/fetchAnArticle/AskeletonArticle.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import Skeleton from "react-loading-skeleton";
|
||||||
|
import "react-loading-skeleton/dist/skeleton.css";
|
||||||
|
const AskeletonArticle = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Skeleton count={1} className="my-4" />
|
||||||
|
<div className="flex flex-col gap-4 pb-4">
|
||||||
|
<Skeleton count={1} containerClassName="title w-3/4 text-2xl" />
|
||||||
|
<Skeleton count={1} containerClassName="authors w-1/4" />
|
||||||
|
<hr></hr>
|
||||||
|
</div>
|
||||||
|
<div className="interActions-buttons flex">
|
||||||
|
<Skeleton count={1} containerClassName="w-1/12 mr-2 text-xl" />
|
||||||
|
<Skeleton count={1} containerClassName="w-1/12 mr-2 text-xl" />
|
||||||
|
<Skeleton count={1} containerClassName="w-1/12 mr-2 text-xl" />
|
||||||
|
<Skeleton count={1} containerClassName="w-1/12 mr-2 text-xl" />
|
||||||
|
</div>
|
||||||
|
<div className=" my-10 flex flex-col gap-2">
|
||||||
|
<Skeleton
|
||||||
|
count={1}
|
||||||
|
className="keywords-title text-2xl"
|
||||||
|
containerClassName="w-1/4"
|
||||||
|
/>
|
||||||
|
<div className="flex">
|
||||||
|
<Skeleton
|
||||||
|
count={1}
|
||||||
|
className="border-2 border-[#ebebeb]"
|
||||||
|
containerClassName="w-1/6 mr-2"
|
||||||
|
baseColor="transparent"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
count={1}
|
||||||
|
className="border-2 border-[#ebebeb]"
|
||||||
|
containerClassName="w-1/6 mr-2"
|
||||||
|
baseColor="transparent"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
count={1}
|
||||||
|
className="border-2 border-[#ebebeb]"
|
||||||
|
containerClassName="w-1/6 mr-2"
|
||||||
|
baseColor="transparent"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
count={1}
|
||||||
|
className="border-2 border-[#ebebeb]"
|
||||||
|
containerClassName="w-1/6 mr-2"
|
||||||
|
baseColor="transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-10 flex flex-col gap-2">
|
||||||
|
<Skeleton count={1} width={200} className="text-2xl" />
|
||||||
|
<Skeleton count={25} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AskeletonArticle;
|
40
src/components/fetchAnArticle/NotFound.tsx
Normal file
40
src/components/fetchAnArticle/NotFound.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Container from "components/Container";
|
||||||
|
import { Button } from "components/Button/Button";
|
||||||
|
import Link from "components/typography/Link";
|
||||||
|
|
||||||
|
import Lottie from "react-lottie";
|
||||||
|
import animationData from "../../assets/lotties/notFoundAnimation.json";
|
||||||
|
|
||||||
|
const NotFound = () => {
|
||||||
|
const defaultOptions = {
|
||||||
|
loop: true,
|
||||||
|
autoplay: true,
|
||||||
|
animationData: animationData,
|
||||||
|
rendererSettings: {
|
||||||
|
preserveAspectRatio: "xMidYMid slice",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
variant="straight"
|
||||||
|
className="flex flex-col items-center justify-center
|
||||||
|
font-serif h-screen my-auto
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<Lottie options={defaultOptions} height={200} width={200} />
|
||||||
|
<span className="font-bold text-5xl ">404</span>
|
||||||
|
<h3 className="font-bold text-2xl text-center">Page does not exist</h3>
|
||||||
|
<h4 className="text-center">
|
||||||
|
Maybe you got a broken link, or maybe you made a misprint in the address
|
||||||
|
bar
|
||||||
|
</h4>
|
||||||
|
<Button className="my-4">
|
||||||
|
<Link to="/">Go to home</Link>
|
||||||
|
</Button>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotFound;
|
@ -6,29 +6,29 @@ import Typography from "components/typography/Typography";
|
|||||||
import { SVGFacebook, SVGInstagram, SVGCircle } from "components/icons";
|
import { SVGFacebook, SVGInstagram, SVGCircle } from "components/icons";
|
||||||
import { RouterLink } from "components/typography/RouterLink";
|
import { RouterLink } from "components/typography/RouterLink";
|
||||||
import Link from "components/typography/Link";
|
import Link from "components/typography/Link";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Define consts */
|
/* Define consts */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
const mainLinks = [
|
const mainLinks = [
|
||||||
{ label: "account settings", url: "/account/settings", disabled: false },
|
{ label: "footer.accountSettings", url: "/account/settings", disabled: false },
|
||||||
{ label: "about freeland", url: "/about", disabled: false },
|
{ label: "footer.about", url: "/about", disabled: false },
|
||||||
{ label: "help", url: "/help", disabled: false },
|
{ label: "footer.help", url: "/help", disabled: false },
|
||||||
{ label: "contact us", url: "/contact-us", disabled: false },
|
{ label: "footer.contactUs", url: "/contact-us", disabled: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
const secondaryLinks = [
|
const secondaryLinks = [
|
||||||
{ index: 1, label: "Terms of Use", url: "/terms-of-use", disabled: false },
|
{ index: 1, label: "footer.termsOfUse", url: "/terms-of-use", disabled: false },
|
||||||
{
|
{
|
||||||
index: 2,
|
index: 2,
|
||||||
label: "Privacy Policy",
|
label: "footer.privacyPolicy",
|
||||||
url: "/privacy-policy",
|
url: "/privacy-policy",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
index: 3,
|
index: 3,
|
||||||
label: "Cookies Policy",
|
label: "footer.coockiesPolicy",
|
||||||
url: "/cookies-policy",
|
url: "/cookies-policy",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
@ -48,6 +48,8 @@ const circleDivider = (
|
|||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
/* -------------------------- Part with main links -------------------------- */
|
/* -------------------------- Part with main links -------------------------- */
|
||||||
const mainLinksPart = useMemo(
|
const mainLinksPart = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -59,7 +61,7 @@ export function Footer() {
|
|||||||
to={link.url}
|
to={link.url}
|
||||||
>
|
>
|
||||||
<Typography className="" fontWeightVariant="semibold" htmlTag="p">
|
<Typography className="" fontWeightVariant="semibold" htmlTag="p">
|
||||||
{link.label.toUpperCase()}
|
{t(link.label).toUpperCase()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
)),
|
)),
|
||||||
@ -72,7 +74,7 @@ export function Footer() {
|
|||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
{link.index != 1 && circleDivider}
|
{link.index != 1 && circleDivider}
|
||||||
<Link key={link.url} disabled={link.disabled} to={link.url}>
|
<Link key={link.url} disabled={link.disabled} to={link.url}>
|
||||||
{link.label}
|
{t(link.label) }
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)),
|
)),
|
||||||
@ -88,7 +90,7 @@ export function Footer() {
|
|||||||
<div className="sm:col-span-1">
|
<div className="sm:col-span-1">
|
||||||
<Link to="*">
|
<Link to="*">
|
||||||
<Typography className="text-2xl" fontWeightVariant="semibold">
|
<Typography className="text-2xl" fontWeightVariant="semibold">
|
||||||
Freeland
|
Scipaper
|
||||||
</Typography>
|
</Typography>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@ -108,13 +110,13 @@ export function Footer() {
|
|||||||
<section className="w-full flex flex-col md:flex-row text-gray-500 text-xs justify-between">
|
<section className="w-full flex flex-col md:flex-row text-gray-500 text-xs justify-between">
|
||||||
<div className="flex flex-col md:flex-row justify-center items-center">
|
<div className="flex flex-col md:flex-row justify-center items-center">
|
||||||
<Typography>
|
<Typography>
|
||||||
@ Copyright 2022 Freeland - All rights reserved
|
@ Copyright 2022 Freeland - {t("footer.allRightsReserved")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<div className="hidden md:flex">{circleDivider}</div>
|
<div className="hidden md:flex">{circleDivider}</div>
|
||||||
<div className="flex flex-row items-center">{secondaryLinksPart}</div>
|
<div className="flex flex-row items-center">{secondaryLinksPart}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-center md:justify-end">
|
<div className="flex flex-row justify-center md:justify-end">
|
||||||
Supported by
|
{t("footer.supportedBy")}
|
||||||
<Typography className="ml-1 lg:ml-2" fontWeightVariant="bold">
|
<Typography className="ml-1 lg:ml-2" fontWeightVariant="bold">
|
||||||
Comfortel
|
Comfortel
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useState } from "react";
|
import { useState, useTransition } from "react";
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Components */
|
/* Components */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
import ContextMenuAction from "../drop-down-menu/ContextMenuAction";
|
import ContextMenuAction from "../drop-down-menu/ContextMenuAction";
|
||||||
import ContextMenu from "../drop-down-menu/ContextMenu";
|
import ContextMenu from "../drop-down-menu/ContextMenu";
|
||||||
import Logofreeland from "../Logofreeland";
|
|
||||||
import { Button } from "../Button/Button";
|
import { Button } from "../Button/Button";
|
||||||
import Avatar from "../Avatar";
|
import Avatar from "../Avatar";
|
||||||
import Navbar from "../Navbar";
|
import Navbar from "../Navbar";
|
||||||
@ -23,11 +22,18 @@ import {
|
|||||||
SVGFile,
|
SVGFile,
|
||||||
SVGEye,
|
SVGEye,
|
||||||
} from "components/icons";
|
} from "components/icons";
|
||||||
|
import i18n from "localization/i18n";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Link from "components/typography/Link";
|
||||||
|
import LocalizationButton from "components/LocalizationButton";
|
||||||
|
import LogoScipaper from "components/LogoScipaper";
|
||||||
|
import Burger from "components/Burger";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const [authenticated, setAuthenticated] = useState(false);
|
const [authenticated, setAuthenticated] = useState(false);
|
||||||
const onClick = () => setAuthenticated(true);
|
const onClick = () => setAuthenticated(true);
|
||||||
const [notification, setNotification] = useState(false);
|
const [notification, setNotification] = useState(false);
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Implement Header Component */
|
/* Implement Header Component */
|
||||||
@ -46,14 +52,14 @@ const Header = () => {
|
|||||||
{/* Logo and Menu */}
|
{/* Logo and Menu */}
|
||||||
<div className="flex gap-8 xl:gap-x-16">
|
<div className="flex gap-8 xl:gap-x-16">
|
||||||
{/* Logo - start - className="w-7 sm:w-10 " /> */}
|
{/* Logo - start - className="w-7 sm:w-10 " /> */}
|
||||||
<a className="Logo flex items-center gap-1 sm:gap-2 " href="/">
|
<Link className="Logo flex items-center gap-1 sm:gap-2 " to="/">
|
||||||
<Logo
|
<Logo
|
||||||
className={classNames(authenticated ? "w-10" : "w-7 sm:w-10")}
|
className={classNames(authenticated ? "w-10" : "w-7 sm:w-10")}
|
||||||
/>
|
/>
|
||||||
<Logofreeland
|
<LogoScipaper
|
||||||
className={classNames(authenticated ? "w-28" : "w-20 sm:w-28")}
|
className={classNames(authenticated ? "w-28" : "w-20 sm:w-28")}
|
||||||
/>
|
/>
|
||||||
</a>
|
</Link>
|
||||||
{/* Logo - end - */}
|
{/* Logo - end - */}
|
||||||
|
|
||||||
{/* Menu( Create new - My library - About ) Start */}
|
{/* Menu( Create new - My library - About ) Start */}
|
||||||
@ -66,36 +72,36 @@ const Header = () => {
|
|||||||
className="text-blue-500 px-4 font-bold uppercase"
|
className="text-blue-500 px-4 font-bold uppercase"
|
||||||
to="/create-new"
|
to="/create-new"
|
||||||
>
|
>
|
||||||
Create new
|
{t("navbar.createNew")}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
{/* Link Create now - end - */}
|
{/* Link Create now - end - */}
|
||||||
|
|
||||||
{/* Dropdown Menu My library - start - */}
|
{/* Dropdown Menu My library - start - */}
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
emphasis="high"
|
emphasis="high"
|
||||||
button="My library"
|
button={t("navbar.library.navTitle")}
|
||||||
className="border-none uppercase"
|
className="border-none uppercase"
|
||||||
>
|
>
|
||||||
<ContextMenuAction
|
<ContextMenuAction
|
||||||
caption="My Publications"
|
caption={t("navbar.library.publications")}
|
||||||
action={() => console.log("My publications")}
|
action={() => console.log("My publications")}
|
||||||
icon={<SVGFile className="stroke-black " />}
|
icon={<SVGFile className="stroke-black " />}
|
||||||
></ContextMenuAction>
|
></ContextMenuAction>
|
||||||
|
|
||||||
<ContextMenuAction
|
<ContextMenuAction
|
||||||
caption="My Favorites"
|
caption={t("navbar.library.favorites")}
|
||||||
action={() => console.log("My Favorites")}
|
action={() => console.log("My Favorites")}
|
||||||
icon={<SVGFavoriteOutlined className="stroke-black" />}
|
icon={<SVGFavoriteOutlined className="stroke-black" />}
|
||||||
></ContextMenuAction>
|
></ContextMenuAction>
|
||||||
|
|
||||||
<ContextMenuAction
|
<ContextMenuAction
|
||||||
caption="My Collections"
|
caption={t("navbar.library.collections")}
|
||||||
action={() => console.log("My Collections")}
|
action={() => console.log("My Collections")}
|
||||||
icon={<SVGFolder className="stroke-black fill-black" />}
|
icon={<SVGFolder className="stroke-black fill-black" />}
|
||||||
></ContextMenuAction>
|
></ContextMenuAction>
|
||||||
|
|
||||||
<ContextMenuAction
|
<ContextMenuAction
|
||||||
caption="Recent Viewed"
|
caption={t("navbar.library.recentViewed")}
|
||||||
action={() => console.log("Recent Viewed")}
|
action={() => console.log("Recent Viewed")}
|
||||||
icon={<SVGEye className="stroke-black " />}
|
icon={<SVGEye className="stroke-black " />}
|
||||||
></ContextMenuAction>
|
></ContextMenuAction>
|
||||||
@ -105,21 +111,21 @@ const Header = () => {
|
|||||||
{/* Dropdown Menu About - start - */}
|
{/* Dropdown Menu About - start - */}
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
emphasis="high"
|
emphasis="high"
|
||||||
button="About"
|
button={t("navbar.about.navTitle")}
|
||||||
className="border-none uppercase"
|
className="border-none uppercase"
|
||||||
>
|
>
|
||||||
<ContextMenuAction
|
<ContextMenuAction
|
||||||
caption="About Freeland"
|
caption={t("navbar.about.aboutProject")}
|
||||||
action={() => console.log("About Freeland")}
|
action={() => console.log("About Freeland")}
|
||||||
></ContextMenuAction>
|
></ContextMenuAction>
|
||||||
|
|
||||||
<ContextMenuAction
|
<ContextMenuAction
|
||||||
caption="Contact Us"
|
caption={t("navbar.about.contacts")}
|
||||||
action={() => console.log("Contact Us")}
|
action={() => console.log("Contact Us")}
|
||||||
></ContextMenuAction>
|
></ContextMenuAction>
|
||||||
|
|
||||||
<ContextMenuAction
|
<ContextMenuAction
|
||||||
caption="Help"
|
caption={t("navbar.about.help")}
|
||||||
action={() => console.log("Help")}
|
action={() => console.log("Help")}
|
||||||
></ContextMenuAction>
|
></ContextMenuAction>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
@ -132,19 +138,22 @@ const Header = () => {
|
|||||||
<div className="flex items-center font-bold text-sm gap-1 md:gap-2 ">
|
<div className="flex items-center font-bold text-sm gap-1 md:gap-2 ">
|
||||||
{!authenticated
|
{!authenticated
|
||||||
? [
|
? [
|
||||||
|
<>
|
||||||
|
<LocalizationButton className="hidden md:flex" />
|
||||||
<Button
|
<Button
|
||||||
emphasis="low"
|
emphasis="low"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="text-xs sm:px-4 sm:text-sm "
|
className="text-xs sm:px-4 sm:text-sm "
|
||||||
>
|
>
|
||||||
Sign in
|
{t("navbar.auth.signIn")}
|
||||||
</Button>,
|
</Button>
|
||||||
|
</>,
|
||||||
<Button
|
<Button
|
||||||
emphasis="medium"
|
emphasis="medium"
|
||||||
className="hidden md:flex"
|
className="hidden md:flex"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
Sign up
|
{t("navbar.auth.signUp")}
|
||||||
</Button>,
|
</Button>,
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
@ -165,7 +174,7 @@ const Header = () => {
|
|||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
{/* Burger component will be shown for the small screens */}
|
{/* Burger component will be shown for the small screens */}
|
||||||
<Navbar className="block lg:hidden" />
|
<Burger className="block lg:hidden" />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,7 @@ import SearchInput from "./SearchInput";
|
|||||||
import { Button } from "components/Button/Button";
|
import { Button } from "components/Button/Button";
|
||||||
import { SVGSearch } from "components/icons";
|
import { SVGSearch } from "components/icons";
|
||||||
import Link from "components/typography/Link";
|
import Link from "components/typography/Link";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -15,6 +16,8 @@ type Props = {
|
|||||||
|
|
||||||
export function SearchBar({ className }: Props) {
|
export function SearchBar({ className }: Props) {
|
||||||
const [onSelected, setOnSelected] = useState(""); // Selected item from response list
|
const [onSelected, setOnSelected] = useState(""); // Selected item from response list
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
|
|
||||||
const searchResolver = (item: any) => {
|
const searchResolver = (item: any) => {
|
||||||
setOnSelected(item.caption);
|
setOnSelected(item.caption);
|
||||||
@ -31,6 +34,7 @@ export function SearchBar({ className }: Props) {
|
|||||||
onSelected={searchResolver}
|
onSelected={searchResolver}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
inGroup={true}
|
inGroup={true}
|
||||||
|
placeHolder={t("mainPage.search") + "..."}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,10 +51,11 @@ export default function Link({
|
|||||||
: style
|
: style
|
||||||
}
|
}
|
||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
|
className={classNames("flex items-center", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
return <div>{link}</div>;
|
return link;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,17 @@ export const handleScrollTo = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function capitalization (str: string) {
|
export function capitalization(str: string): string {
|
||||||
return str.substring(0,1).toUpperCase() + str.substring(1);
|
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatNumber(num: number): string {
|
||||||
|
return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1 ');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function joinClassnames(first?: string, second?: string): string {
|
||||||
|
return [
|
||||||
|
first ?? '',
|
||||||
|
second ?? '',
|
||||||
|
].join('');
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
const BASE_URL = process.env.REACT_APP_INTEGRATOR_URL;
|
export const BASE_URL = process.env.REACT_APP_INTEGRATOR_URL;
|
||||||
export const GRAPHQL_URL = process.env.REACT_APP_GRAPHQL_URL ?? "";
|
export const GRAPHQL_URL = process.env.REACT_APP_GRAPHQL_URL ?? "";
|
||||||
|
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
|
@ -16,6 +16,9 @@ import AccountSettings from "pages/Information/AccountSettings";
|
|||||||
import { store } from "store/store";
|
import { store } from "store/store";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { SearchResultsPage } from "pages/SearchResultsPage";
|
import { SearchResultsPage } from "pages/SearchResultsPage";
|
||||||
|
import AnArticle from "components/fetchAnArticle/AnArticle";
|
||||||
|
import NotFound from "components/fetchAnArticle/NotFound";
|
||||||
|
import AnArticleBody from "components/fetchAnArticle/AnArticleBody";
|
||||||
|
|
||||||
const rootElement = document.getElementById("root");
|
const rootElement = document.getElementById("root");
|
||||||
if (!rootElement) throw new Error("Failed to find the root element");
|
if (!rootElement) throw new Error("Failed to find the root element");
|
||||||
@ -32,10 +35,15 @@ root.render(
|
|||||||
<Route path="/terms-of-use" element={<TermsOfUse />} />
|
<Route path="/terms-of-use" element={<TermsOfUse />} />
|
||||||
<Route path="/privacy-policy" element={<PrivacyPolicy />} />
|
<Route path="/privacy-policy" element={<PrivacyPolicy />} />
|
||||||
<Route path="/cookies-policy" element={<CookiesPolicy />} />
|
<Route path="/cookies-policy" element={<CookiesPolicy />} />
|
||||||
|
<Route path="/article">
|
||||||
|
<Route path="info/:id" element={<AnArticle />} />
|
||||||
|
<Route path="content/:id" element={<AnArticleBody />} />
|
||||||
|
</Route>
|
||||||
<Route path="/account">
|
<Route path="/account">
|
||||||
<Route path="settings" element={<AccountSettings />} />
|
<Route path="settings" element={<AccountSettings />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/search-results" element={<SearchResultsPage />}></Route>
|
<Route path="/search-results" element={<SearchResultsPage />} />
|
||||||
|
<Route path="/*" element={<NotFound />}></Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
|
@ -18,7 +18,7 @@ export const languages: Record<Langs, Lang> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
export const popularLangKeys = ["ru", "en"];
|
export const popularLangKeys = ["ru", "en"];
|
||||||
const fallbackLng: Langs = "en";
|
const fallbackLng: Langs = "ru";
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(Backend)
|
.use(Backend)
|
||||||
|
@ -20,7 +20,6 @@ export default function MainPage({ className }: Props) {
|
|||||||
<FeaturedArticlesCategories></FeaturedArticlesCategories>
|
<FeaturedArticlesCategories></FeaturedArticlesCategories>
|
||||||
<FeaturedArticlesCards></FeaturedArticlesCards>
|
<FeaturedArticlesCards></FeaturedArticlesCards>
|
||||||
<FeaturedAuthorsCards></FeaturedAuthorsCards>
|
<FeaturedAuthorsCards></FeaturedAuthorsCards>
|
||||||
<BottomBarAcceptCookies></BottomBarAcceptCookies>
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -3,9 +3,7 @@ import BaseLayout from "components/BaseLayout";
|
|||||||
import { SearchSection } from "components/SearchSection";
|
import { SearchSection } from "components/SearchSection";
|
||||||
import { ColumnLayout } from "components/layouts/ThreeColumn/ColumnLayout";
|
import { ColumnLayout } from "components/layouts/ThreeColumn/ColumnLayout";
|
||||||
import { SearchResultSection } from "components/SearchResultsSection";
|
import { SearchResultSection } from "components/SearchResultsSection";
|
||||||
import { useSearchStoreImplementation } from "searchResults/data/searchStoreImplementation";
|
import Fiter from "components/Filters/Filter";
|
||||||
import { useSearchViewModel } from "searchResults/controller/searchResultsViewModel";
|
|
||||||
import { Loader } from "components/Loader/Loader";
|
|
||||||
|
|
||||||
export const SearchResultsPage = () => {
|
export const SearchResultsPage = () => {
|
||||||
return (
|
return (
|
||||||
@ -13,13 +11,13 @@ export const SearchResultsPage = () => {
|
|||||||
<SearchSection />
|
<SearchSection />
|
||||||
<ColumnLayout>
|
<ColumnLayout>
|
||||||
<ColumnLayout.Left>
|
<ColumnLayout.Left>
|
||||||
<div className="h-98 bg-blue-200 w-[300px]">left bar</div>
|
<div className="h-98 w-[300px] border rounded my-5"><Fiter /></div>
|
||||||
</ColumnLayout.Left>
|
</ColumnLayout.Left>
|
||||||
<ColumnLayout.Main>
|
<ColumnLayout.Main>
|
||||||
<SearchResultSection />
|
<SearchResultSection />
|
||||||
</ColumnLayout.Main>
|
</ColumnLayout.Main>
|
||||||
<ColumnLayout.Right>
|
<ColumnLayout.Right>
|
||||||
<div className="h-98 bg-blue-200 w-[300px]">right bar</div>
|
<div className="w-[300px]"></div>
|
||||||
</ColumnLayout.Right>
|
</ColumnLayout.Right>
|
||||||
</ColumnLayout>
|
</ColumnLayout>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
|
import { Article } from "article/domain/articleEntity";
|
||||||
|
|
||||||
export type SearchResultsDTO = {
|
export type SearchResultsDTO = {
|
||||||
data: ArticleDTO[];
|
data: ArticleDTO[];
|
||||||
meta: SearchResultsMeta;
|
meta: SearchResultsMeta;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ArticleDTO = {
|
export type ArticleDTO = {
|
||||||
id?: string;
|
id: string;
|
||||||
title?: string;
|
title: string;
|
||||||
authors?: string[];
|
authors: string[];
|
||||||
topic?: string[];
|
topic: string[];
|
||||||
summary?: string;
|
summary: string;
|
||||||
tags?: string[];
|
tags: string[];
|
||||||
content?: string;
|
content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SearchResultsMeta = {
|
export type SearchResultsMeta = {
|
||||||
|
@ -10,11 +10,23 @@ const searchEndpoint = "/papers/search";
|
|||||||
async function search(request: string): Promise<SearchResults> {
|
async function search(request: string): Promise<SearchResults> {
|
||||||
try {
|
try {
|
||||||
const response = await integratorApiClient.get<SearchResultsDTO>(
|
const response = await integratorApiClient.get<SearchResultsDTO>(
|
||||||
// searchEndpoint + `?query=` + request + `&limit=10&offset=0`
|
searchEndpoint + `?query=` + request
|
||||||
"https://run.mocky.io/v3/ea705665-2479-4039-8b81-412e011fc145"
|
// "https://run.mocky.io/v3/ea705665-2479-4039-8b81-412e011fc145"
|
||||||
);
|
);
|
||||||
const dto = response.data;
|
const dto = response.data;
|
||||||
return create({ data: dto.data, meta: dto.meta });
|
return create({
|
||||||
|
data: dto.data.map((e) => {
|
||||||
|
return {
|
||||||
|
authors: e.authors,
|
||||||
|
content: e.content,
|
||||||
|
id: e.id,
|
||||||
|
summary: e.summary,
|
||||||
|
tags: e.tags,
|
||||||
|
title: e.title,
|
||||||
|
topic: e.topic,
|
||||||
|
}
|
||||||
|
}), meta: dto.meta
|
||||||
|
});
|
||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
if (axios.isAxiosError(reason)) {
|
if (axios.isAxiosError(reason)) {
|
||||||
throw Failure.fromReason(reason, "failures.services.load");
|
throw Failure.fromReason(reason, "failures.services.load");
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Article } from "article/domain/ArticleEntity";
|
import { Article } from "article/domain/articleEntity";
|
||||||
|
|
||||||
export interface SearchResults {
|
export interface SearchResults {
|
||||||
data: Article[];
|
data: Article[];
|
||||||
meta:SearchResultsMeta ;
|
meta: SearchResultsMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchResultsMeta {
|
export interface SearchResultsMeta {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { configureStore } from "@reduxjs/toolkit";
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
|
import { articleReducer } from "article/data/articleReducer";
|
||||||
import thunk from "redux-thunk";
|
import thunk from "redux-thunk";
|
||||||
import { searchResultReducer } from "searchResults/data/searchReducer";
|
import { searchResultReducer } from "searchResults/data/searchReducer";
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
searchResults: searchResultReducer,
|
searchResults: searchResultReducer,
|
||||||
|
article: articleReducer,
|
||||||
},
|
},
|
||||||
middleware: [thunk],
|
middleware: [thunk],
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user