Compare commits

...

96 Commits

Author SHA1 Message Date
moeidtopcoder
e9e9611f80 Merge branch 'develop' into master 2022-10-31 17:52:31 +00:00
0c054687ba Merge pull request 'the overlay in burger resolved' (#172) from fix/burger into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/172
2022-10-31 15:03:14 +00:00
3b147e50a1 Merge pull request 'resolve animation - resolve responsive' (#171) from fix/404-page into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/171
2022-10-31 15:02:49 +00:00
“Salar
e94514ed02 the overlay in burger resolved 2022-10-31 17:13:43 +03:00
“Salar
0032e51858 resolve animation - resolve responsive 2022-10-31 13:28:55 +03:00
8d6a9ab74c Dockerfile changes 2022-10-29 16:23:34 +03:00
07c4ae8cbd Fixed # on localization button 2022-10-26 16:00:45 +03:00
a782930d3d Replace mocks to real APIs 2022-10-21 18:12:15 +03:00
4c1877caa5 Merge pull request 'feature/markdown-styling' (#169) from feature/markdown-styling into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/169
2022-10-21 15:10:44 +00:00
e9324f6c14 Merge pull request 'classnames resolved' (#170) from fix/link into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/170
2022-10-21 15:10:28 +00:00
“Salar
9fcee44e0c classnames resolved 2022-10-21 18:03:17 +03:00
“Salar
9939b1c824 resolve headers, lists, link, paragraph.. 2022-10-21 17:57:55 +03:00
00deb05850 Merge branch 'develop' into feature/markdown-styling 2022-10-21 14:44:09 +03:00
b8849945b9 Merge pull request 'resolve no break space in the english localization' (#164) from fix/english-localization into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/164
2022-10-21 11:42:39 +00:00
08f5ee272b Merge pull request 'resolve no break space in the russian localization' (#165) from fix/russian-localization into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/165
2022-10-21 11:42:25 +00:00
35aa957b35 Merge pull request 'content hardcoded resolved' (#166) from fix/filter into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/166
2022-10-21 11:42:10 +00:00
472e8c2ea5 Merge pull request 'the translation in the main section has been resolved' (#167) from fix/main-section into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/167
2022-10-21 11:41:50 +00:00
f17d5f3079 Merge pull request 'resolve article translation' (#168) from fix/category-card into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/168
2022-10-21 11:41:33 +00:00
“Salar
4f9b33e5b9 resolve article translation 2022-10-21 14:21:26 +03:00
“Salar
753223c7a9 the translation of interaction buttons has been added 2022-10-21 14:07:48 +03:00
“Salar
28cc9b1a71 the translation in the main section has been resolved 2022-10-21 13:46:47 +03:00
“Salar
47472868e1 content hardcoded resolved 2022-10-21 13:29:08 +03:00
“Salar
f965eaddbc content and filter were added 2022-10-21 13:26:23 +03:00
“Salar
2075426ff7 resolve no break space in the russian localization 2022-10-21 13:08:21 +03:00
“Salar
209507833e resolve no break space in the english localization 2022-10-21 13:00:25 +03:00
eff20ae459 Mocked correct article body 2022-10-21 12:08:29 +03:00
dcfe260f0e Merge pull request 'resolve english translation' (#161) from fix/english-localization into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/161
2022-10-20 10:02:12 +00:00
1af206c360 Merge pull request 'remove additional link' (#162) from fix/featured-articles-cards into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/162
2022-10-20 10:01:39 +00:00
“Salar
932d23befa remove additional link 2022-10-19 18:12:32 +03:00
“Salar
e52026cf86 resolve english translation 2022-10-19 17:46:33 +03:00
033a1f31ad Merge pull request 'resolve localization button in the header' (#160) from fix/header into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/160
2022-10-19 14:31:03 +00:00
9c79f449c5 Manually merge 2022-10-19 17:30:50 +03:00
24652d0908 Merge branch 'develop' into fix/header 2022-10-19 17:29:26 +03:00
944baced6c Merge pull request 'localization button feature' (#159) from feature/localization-button into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/159
2022-10-19 14:24:01 +00:00
“Salar
01a873477b resolve localization button in the header 2022-10-19 16:58:19 +03:00
“Salar
d1b17592c3 localization button feature 2022-10-19 16:52:46 +03:00
1475d2db27 Merge pull request 'feature/add-filters' (#157) from feature/add-filters into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/157
2022-10-18 16:50:53 +00:00
1ba9dc28b5 Merge branch 'develop' into feature/add-filters 2022-10-18 19:50:13 +03:00
1817946fe6 Merge pull request 'changed query to our api' (#156) from fix/search-request into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/156
2022-10-18 16:49:16 +00:00
de5b6ad60c Merge pull request 'fixed logo to actual' (#158) from fix/scipaper-logo into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/158
2022-10-18 16:48:47 +00:00
maximus
31706ca4e8 fixed logo to actual 2022-10-18 19:26:29 +03:00
filantrop
969749c0c0 Merge branch 'feature/add-filters' of http://85.143.176.51:3000/free-land/front-end into feature/add-filters 2022-10-18 17:50:31 +03:00
filantrop
5667f30a92 add translate 2022-10-18 17:34:10 +03:00
maximus
d77c1c6388 fixed article endpoint 2022-10-18 17:13:46 +03:00
maximus
bd6fbbebfd changed query to our api 2022-10-18 16:51:27 +03:00
filantrop
2ffce71e74 fix styles 2022-10-18 14:59:38 +03:00
35900bec5d Merge pull request 'fix/article-interaction-buttons' (#155) from fix/article-interaction-buttons into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/155
2022-10-18 10:43:30 +00:00
maximus
c63c19e1c7 finished styling, added filters to main page 2022-10-18 12:06:21 +03:00
maximus
b5443aa033 Merge branch 'develop' into fix/article-interaction-buttons 2022-10-18 11:07:40 +03:00
maximus
18d02945f3 add functionality to share button and localize it 2022-10-18 09:40:34 +03:00
558a1fd5a7 Merge pull request 'bold style for the link resolved' (#154) from fix/featured-articles-cards into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/154
2022-10-17 15:14:59 +00:00
“Salar
bee5499816 bold style for the link resolved 2022-10-17 15:27:28 +03:00
8a2a7f0c53 Merge pull request 'added filter (beta version)' (#147) from feature/add-filters into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/147
2022-10-17 11:58:02 +00:00
15b9ed1f20 Merge branch 'develop' into feature/add-filters 2022-10-17 14:57:33 +03:00
cad2174bc5 Merge pull request 'English localization' (#152) from feature/english-localization into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/152
2022-10-17 11:56:01 +00:00
0c0a79d81d Merge pull request 'resolve padding of featured authors' (#149) from fix/featured-authors into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/149
2022-10-17 11:55:47 +00:00
df4b4d5c32 Merge pull request 'resolve not found page' (#148) from fix/404-page into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/148
2022-10-17 11:54:25 +00:00
92dddab82a Merge pull request 'the padding of featured articles cards has been resolved' (#150) from fix/featured-articles-cards into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/150
2022-10-17 11:53:58 +00:00
93cefd9ce3 Merge pull request 'resolve the margin of topic in the article info and article content pages' (#151) from fix/fetch-article into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/151
2022-10-17 11:53:40 +00:00
373d55a543 Merge pull request 'resolve the style of link' (#153) from fix/link into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/153
2022-10-17 11:52:57 +00:00
“Salar
8a075e926e resolve the style of link 2022-10-17 14:47:21 +03:00
“Salar
705ae6471f English localization 2022-10-17 14:15:13 +03:00
“Salar
64a6e6ab20 resolve the margin of topic in the article info and article content pages 2022-10-17 13:08:24 +03:00
“Salar
3cac7c7d04 the padding of featured articles cards has been resolved 2022-10-17 12:49:21 +03:00
“Salar
9154b1fb96 resolve padding of featured authors 2022-10-17 12:41:10 +03:00
“Salar
b390ae7a2e resolve not found page 2022-10-17 12:26:23 +03:00
filantrop
3c739247d2 added filter (beta version) 2022-10-17 12:17:14 +03:00
904730dfd9 Merge pull request 'Sticly footer to the bottom of the page' (#146) from fix/footer-to-bottom into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/146
2022-10-14 12:04:15 +00:00
dcda165488 Sticly footer to the bottom of the page 2022-10-14 15:03:37 +03:00
49d15e538b Merge pull request 'feature/russian-localization' (#145) from feature/russian-localization into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/145
2022-10-14 11:21:14 +00:00
maximus
da389cc337 added localiazation 2022-10-14 14:12:24 +03:00
3f9a76815e Created navbar ru localization 2022-10-14 10:29:23 +03:00
c99e9217f1 Merge pull request 'Fixed article entity bug' (#144) from fix/resolve-merge-issues into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/144
2022-10-12 16:52:58 +00:00
4479300027 Fixed article entity bug 2022-10-12 19:50:52 +03:00
e97d3abe96 Merge pull request 'new line bug has been fixed' (#143) from fix/markdown into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/143
2022-10-12 16:12:32 +00:00
“Salar
eff1a2a04f new line bug has been fixed 2022-10-12 19:04:52 +03:00
81faa7121e Manualy merge from fix/article-interaction-buttons
Resolve interaction button link issue
2022-10-12 17:09:11 +03:00
bbe6bcca51 Merge pull request 'feature/complete-develop-merge' (#142) from feature/complete-develop-merge into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/142
2022-10-12 14:03:30 +00:00
c161606e08 Merge pull request 'article slice' (#140) from feature/article-slice into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/140
2022-10-12 14:03:08 +00:00
5550b7c241 Merge pull request 'Fetch an articl by its id, Fetch the body of the articl(content)' (#139) from feature/fetch-article into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/139
2022-10-12 13:59:49 +00:00
maximus
86073d5718 set up makefile 2022-10-12 16:59:24 +03:00
85f227f7d9 Resolve entrypoint file bug 2022-10-12 16:44:41 +03:00
“Salar
779b091bd5 article slice 2022-10-12 16:34:44 +03:00
“Salar
a26faeeb02 Fetch an articl by its id, Fetch the body of the articl(content) 2022-10-12 16:23:48 +03:00
50a42955d6 Merge pull request '(feature/complete-develop-merge) Resolving multiple merges' (#138) from feature/complete-develop-merge into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/138
2022-10-12 12:15:01 +00:00
9a3e545cf0 Resolving issues 2022-10-12 15:13:08 +03:00
87ad19fa1a Merge branch 'feature/search-results-page' into feature/complete-develop-merge 2022-10-12 14:52:55 +03:00
1d5eb72826 Merge branch 'fix/article-interaction-buttons' into feature/complete-develop-merge 2022-10-12 14:46:28 +03:00
011f1564a0 Merge branch 'fix/article-keywords' into feature/complete-develop-merge 2022-10-12 14:45:52 +03:00
89146993c7 Merge branch 'fix/logo' into feature/complete-develop-merge 2022-10-12 14:45:44 +03:00
a9895cc6dd Merge branch 'fix/base-layout' into feature/complete-develop-merge 2022-10-12 14:42:46 +03:00
“Salar
5afd4ced4d Link component has been added to the icon to referense to the home page 2022-10-12 11:50:17 +03:00
“Salar
f09cddff75 The style of the children has been modified 2022-10-12 11:43:49 +03:00
“Salar
bd1771e234 The style of the logo has been modified 2022-10-10 20:02:17 +03:00
“Salar
6cdd4ed730 The border color of the keyword has been modified according to our design 2022-10-10 18:32:47 +03:00
“Salar
12043e9123 The distance between the buttons has been increased 2022-10-10 18:13:22 +03:00
67 changed files with 2458 additions and 510 deletions

View File

@ -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

View File

@ -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 .

View File

@ -1,4 +1,4 @@
PROJECT_NAME=freeland-frontend PROJECT_NAME=scipaper-frontend
setup: setup:
npm i npm i

View File

@ -1,4 +1,4 @@
#!/bin/bash #! /bin/bash
# no verbose # no verbose
set +x set +x
# config # config

259
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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>

View File

@ -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"
}
} }

View File

@ -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":"Показать все"
} }
} }

View 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 };

View 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 };

View 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";

View 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 };

View 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 };

View 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 };

View 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 };

View File

@ -0,0 +1,9 @@
export interface FetchArticleByIdDTO {
id: string;
topic: string;
title: string;
authors: string[];
tags: string[];
summary: string;
content: string;
}

View 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;
} }

View 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 };

View 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 };

View 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 };

View File

@ -0,0 +1,9 @@
export interface CreateArticleParams {
id: string;
topic: string[];
title: string;
authors: string[];
tags: string[];
summary: string;
content: string;
}

File diff suppressed because one or more lines are too long

View File

@ -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";

View File

@ -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

View File

@ -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>
); );
} }

View File

@ -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>
);
}

View File

@ -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"

View File

@ -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;

View File

@ -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
View 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;

View File

@ -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>

View File

@ -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}

View 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>
);
}

View 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>
</>
);
}

View 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>
</>
);
}

View 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;
}

View 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>
</>
);
}

View 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;
}

View 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;

View File

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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>

View File

@ -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">

View File

@ -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>
); );

View File

@ -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>

View File

@ -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}>

View File

@ -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>
); );
}; };

View 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;

View 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;

View 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;

View 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;

View File

@ -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>

View File

@ -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>
); );

View File

@ -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>

View File

@ -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;
} }

View File

@ -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('');
} }

View File

@ -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({

View File

@ -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>

View File

@ -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)

View File

@ -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>
); );

View File

@ -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>

View File

@ -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 = {

View File

@ -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");

View File

@ -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 {

View File

@ -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],
}); });