Compare commits

..

No commits in common. "master" and "feature/search-results-page" have entirely different histories.

67 changed files with 509 additions and 2457 deletions

View File

@ -1,6 +1,6 @@
REACT_APP_CMS_BASE_URL=http://scipaper.ru REACT_APP_CMS_BASE_URL=EXT_CMS_BASE_URL
REACT_APP_CMS_APP_NAME=scipaper REACT_APP_CMS_APP_NAME=EXT_CMS_APP_NAME
REACT_APP_OPENID_PROVIDER_URL=http://auth.techpal.ru/auth/realms/master/protocol/openid-connect/auth?client_id=techpal&response_type=code REACT_APP_OPENID_PROVIDER_URL=EXT_OPENID_PROVIDER_URL
REACT_APP_INTEGRATOR_URL=http://scipaper.ru REACT_APP_INTEGRATOR_URL=EXT_INTEGRATOR_URL
REACT_APP_INTEGRATOR_API_VERSION=/v1 REACT_APP_INTEGRATOR_API_VERSION=EXT_INTEGRATOR_API_VERSION
REACT_APP_GRAPHQL_URL=/graphql REACT_APP_GRAPHQL_URL=EXT_GRAPHQL_URL

View File

@ -17,25 +17,22 @@ COPY . .
RUN npm run build RUN npm run build
# Bundle static assets with nginx # Bundle static assets with nginx
FROM node:16-alpine as production FROM nginx:1.21.6 as production
# Copy built assets from builder # Copy built assets from builder
WORKDIR /app COPY --from=builder /app/build /usr/share/nginx/html
COPY --from=builder /app/build . # Add nginx.config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Expose ports # Expose ports
EXPOSE 3000 EXPOSE 80
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
RUN npm i -g serve \ # Execute script
&& deluser --remove-home node \ RUN ["chmod", "+x", "./entrypoint.sh"]
&& addgroup --g ${GROUP_UID} -S ${GROUP_NAME} \ ENTRYPOINT ["./entrypoint.sh"]
&& adduser -D -S -s /sbin/nologin -u ${USER_UID} -G ${GROUP_NAME} ${USER_NAME}\
&& chown -R ${USER_NAME}:${GROUP_NAME} "/app/"
# Start serving
USER "${USER_NAME}" CMD ["nginx", "-g", "daemon off;"]
CMD serve -s .

View File

@ -1,4 +1,4 @@
PROJECT_NAME=scipaper-frontend PROJECT_NAME=freeland-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,15 +8,10 @@
"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",
@ -30,12 +25,10 @@
"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",
@ -70,7 +63,6 @@
"@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",
@ -2390,63 +2382,6 @@
"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",
@ -11544,14 +11479,6 @@
"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",
@ -11560,15 +11487,6 @@
"@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",
@ -13611,27 +13529,6 @@
"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",
@ -15124,14 +15021,6 @@
"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",
@ -25678,11 +25567,6 @@
"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",
@ -30141,18 +30025,6 @@
"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",
@ -30420,21 +30292,6 @@
"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",
@ -37717,11 +37574,6 @@
"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",
@ -41602,43 +41454,6 @@
} }
} }
}, },
"@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",
@ -48633,14 +48448,6 @@
"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",
@ -48649,15 +48456,6 @@
"@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",
@ -50266,27 +50064,6 @@
} }
} }
}, },
"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",
@ -51456,14 +51233,6 @@
"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",
@ -59412,11 +59181,6 @@
"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",
@ -62482,15 +62246,6 @@
"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",
@ -62690,15 +62445,6 @@
"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",
@ -68051,11 +67797,6 @@
"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,15 +3,10 @@
"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",
@ -25,12 +20,10 @@
"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",
@ -110,7 +103,6 @@
"@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>Scipaper</title> <title>Freeland</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 page", "goHome": "Home",
"noSuchPath": "We don't have such a page" "noSuchPath": "We don't have this page"
}, },
"sidemenu": { "sidemenu": {
"dashboard": "Dashboard", "dashboard": "Dashboard",
@ -19,130 +19,45 @@
"hellousr": "Hello, {{username}}", "hellousr": "Hello, {{username}}",
"edit": "Edit", "edit": "Edit",
"language": "Language", "language": "Language",
"selectLanguage": "Select a language", "selectLanguage": "Select 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": "Linked Account", "connectedAccounts_one": "Connected account",
"connectedAccounts_other": "Linked Accounts", "connectedAccounts_other": "Connected 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": "Protect your account by enabling 2FA via SMS or using a temporary one-time password (OTP) from the authentication app." "description": "Keep your account secure by enabling 2FA via SMS or using a temporary one-time passcode (TOTP) from an authenticator app."
}, },
"activity": { "activity": {
"caption": "Device activity" "caption": "Device activity"
} }
}, },
"search": { "search": {
"label": "We will find something.." "label": "Search for something.."
}, },
"subscriptions": { "subscriptions": {
"subscribed": "The service is attached to the account" "subscribed": "Service have been connected"
}, },
"viewHistory": "View history", "viewHistory": "View history",
"logOutEverywhere": "Log out from all devices", "logOutEverywhere": "log out from all devices",
"back": "Back", "back": "Back",
"logOut": "Exit", "logOut": "Log out",
"failures": { "failures": {
"subscriptions": { "subscriptions": {
"failure": "Failed to attach the service to your account", "failure": "Failed to connect service",
"exists": "The service was already attached to your account earlier", "exists": "Service have already been connected",
"confirmation": "Invalid password" "confirmation": "Invalid confirmation information provided"
}, },
"services": { "services": {
"fork": "Failed to perform authorization in the service" "fork": "Failed to authenticate in 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,87 +59,5 @@
"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

@ -1,24 +0,0 @@
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

@ -1,36 +0,0 @@
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

@ -1,4 +0,0 @@
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

@ -1,23 +0,0 @@
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

@ -1,39 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,35 +0,0 @@
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

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

View File

@ -1,9 +1,10 @@
export interface Article { export interface Article {
id: string; id?: string;
title: string; title?: string;
content: string;
topic?: string[];
authors?: string[]; authors?: string[];
tags?: string[]; topic?: string[];
summary?: string; summary?: string;
tags?: string[];
content?: string;
publisher?: string;
} }

View File

@ -1,14 +0,0 @@
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

@ -1,13 +0,0 @@
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

@ -1,16 +0,0 @@
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

@ -1,9 +0,0 @@
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,3 +1,4 @@
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-200": "text-base text-gray-900 px-2 border rounded hover:border-blue-600 border-gray-900":
emphasis === "high", emphasis === "high",
}, },
className className

View File

@ -12,11 +12,14 @@ 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",
@ -38,12 +41,11 @@ 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({
@ -51,14 +53,9 @@ 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"
@ -66,66 +63,32 @@ export function ArticleInteractionButtons({
onClick={openAbstract} onClick={openAbstract}
> >
<Typography fontWeightVariant="bold" className="pr-2"> <Typography fontWeightVariant="bold" className="pr-2">
{t("abstract")} Abstract
</Typography> </Typography>
<Button.Icon> <Button.Icon>
{!isAbstractOpen ? ( {!isAbstractOpen ? <SVGArrowDown className="w-4 fill-blue-700 stroke-blue-700" /> : <SVGArrowUp className="w-4 fill-blue-700 stroke-blue-700" />}
<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>
); );
/* ---------------------------- Read file button ---------------------------- */ const fileInteractionButtons = interactionButtonsStore.map((button) => {
const readFileButton = ( return (
<Link <Button
to={`/article/content/${articleID}`} emphasis={button.buttonEmphasis === "high" ? "high" : "low"}
className="rounded active:outline focus:outline focus:outline-blue-400/10 outline-8 active:outline-blue-400/10" className="h-max mx-1 px-2"
> >
<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>
<SVGDownload className="w-6 fill-gray-900 stroke-gray-900" /> {React.cloneElement(button.icon, { className: button.iconClassName })}
</Button.Icon> </Button.Icon>
{emphasis === "high" && <Typography fontWeightVariant="semibold" className="text-gray-900">{t("download")}</Typography>} {emphasis === "high" ? <Typography>{button.title}</Typography> : null}
</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 space-x-2"> <div className="flex flex-row">
{emphasis != "high" && abstractButton} {emphasis === "low" && !children ? abstractButton : null}
{readFileButton} {children ? children : fileInteractionButtons}
{downLoadButton}
{citeButton}
<ShareButton emphasis={emphasis} linktoCopy={`/article/content/${articleID}`} />
</div> </div>
); );
} }

View File

@ -1,87 +0,0 @@
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={`/article/info/${searchItem.id}`} className="text-2xl"> <Article.Title linkTo={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,11 +47,10 @@ 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,7 +5,6 @@
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 */
@ -16,8 +15,7 @@ type Props = {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
iconed?: boolean; iconed?: boolean;
onClick?: (element: any) => void; onClick?: () => void;
closeOption?: Boolean;
} & Omit<React.MouseEventHandler<HTMLSpanElement>, "">; } & Omit<React.MouseEventHandler<HTMLSpanElement>, "">;
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -29,18 +27,17 @@ 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-none p-2 rounded-md text-xs text-center", "border p-2 rounded-sm text-xs text-center",
{ {
"cursor-pointer": onClick, "cursor-pointer": onClick,
"border-transparent": emphasis == "low", "border-transparent": emphasis == "low",
"bg-blue-400 text-white rounded-xs": emphasis == "high", "bg-blue-400 text-white": emphasis == "high",
"border-gray-400 background-gray-200": emphasis == "medium", "border-gray-400 background-gray-200": emphasis == "medium",
}, },
className className
@ -48,18 +45,7 @@ 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,4 +1,3 @@
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";
@ -11,10 +10,10 @@ type Props = {
function BaseLayout({ header, footer, children, className }: Props) { function BaseLayout({ header, footer, children, className }: Props) {
return ( return (
<div className={joinClassnames(className, 'flex min-h-screen flex-col justify-between')}> <div className={className}>
<Header /> <Header />
<main className="flex-1">{children}</main> <main>{children}</main>
<Footer /> <Footer />
</div> </div>

View File

@ -1,198 +0,0 @@
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 { useTranslation } from "react-i18next"; import { JsxElement } from "typescript";
type Props = { type Props = {
count?: number; count?: number;
@ -13,7 +13,6 @@ 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";
@ -35,7 +34,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"
> >
{t(title)} {title}
</Typography> </Typography>
</div> </div>
<div className="max-w-max "> <div className="max-w-max ">
@ -43,8 +42,7 @@ 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}{" "} {count} Items
{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/arrow-down.svg"; import { ReactComponent as Checkmark } from "assets/svg/check.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-5 h-5 relative"> <div className="w-6 h-6 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,8 +33,12 @@ const Checkbox = ({ children, className, isChecked, ...props }: Props) => {
checked={isChecked} checked={isChecked}
{...props} {...props}
/> />
<div className="h-2 w-2 absolute top-0.5 left-0.5"> <div //
<Checkmark className="h-5 w-5 fill-white hover:fill-white stroke-white" /> className=" w-4 h-3 leading-[0] absolute top-1.5 left-1 opacity-0
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

@ -1,55 +0,0 @@
/* -------------------------------------------------------------------------- */
/* 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

@ -1,69 +0,0 @@
/* -------------------------------------------------------------------------- */
/* 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

@ -1,112 +0,0 @@
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

@ -1,23 +0,0 @@
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

@ -1,120 +0,0 @@
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

@ -1,11 +0,0 @@
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

@ -1,89 +0,0 @@
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,53 +7,30 @@ export type Props = {
const Logo = ({ className, fillColors = "blue" }: Props) => { const Logo = ({ className, fillColors = "blue" }: Props) => {
return ( return (
<svg <div className={className}>
className={classNames(className, "group")} <svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
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={ className={fillColors === "blue" ? "fill-blue-800" : "fill-gray-800"}
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={ className={fillColors === "blue" ? "fill-blue-700" : "fill-gray-700"}
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={ className={fillColors === "blue" ? "fill-blue-600" : "fill-gray-500"}
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={ className={fillColors === "blue" ? "fill-blue-400" : "fill-gray-200"}
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={ className={fillColors === "blue" ? "fill-blue-200" : "fill-gray-75"}
fillColors === "blue"
? "fill-blue-200"
: "transition-colors fill-gray-75 group-hover:fill-blue-200"
}
/> />
</svg> </svg>
</div>
); );
}; };

File diff suppressed because one or more lines are too long

39
src/components/Logofreeland.tsx Executable file

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,6 @@ 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 */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -98,10 +97,9 @@ 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 px-8"> <div className="slider-wrapper articles">
<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
@ -168,12 +166,11 @@ const FeaturedArticlesCards = () => {
</Card.CardContent> </Card.CardContent>
<Card.CardAction href={Articale.Link}> <Card.CardAction href={Articale.Link}>
<Typography <Link to="*">
className="text-blue-500 font-bold" <Typography className="text-blue-500 font-bold">
fontWeightVariant="bold" Read More
>
{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,25 +9,22 @@ import {
SVGTechnicsAndTechology, SVGTechnicsAndTechology,
SVGFundamental, SVGFundamental,
} from "components/icons"; } from "components/icons";
import { useTranslation} from "react-i18next";
const categories = [ const categories = [
{ id: 1, title: "mainPage.featuredArticles.categories.Medical", count: 5617813, icon: <SVGMedicine /> }, { id: 1, title: "Medical", count: 5617813, icon: <SVGMedicine /> },
{ {
id: 2, id: 2,
title: "mainPage.featuredArticles.categories.TechnicsAndTechlonogies", title: "Technics and Technology",
count: 5617813, count: 5617813,
icon: <SVGTechnicsAndTechology />, icon: <SVGTechnicsAndTechology />,
}, },
{ id: 3, title: "mainPage.featuredArticles.categories.Fundamental", count: 5617813, icon: <SVGFundamental /> }, { id: 3, title: "Fundamental", count: 5617813, icon: <SVGFundamental /> },
{ id: 4, title: "mainPage.featuredArticles.categories.Humanitarian", count: 5617813, icon: <SVGHumanitarian /> }, { id: 4, title: "Humanitarian", count: 5617813, icon: <SVGHumanitarian /> },
{ id: 5, title: "mainPage.featuredArticles.categories.Agricultural", count: 5617813, icon: <SVGAgricultural /> }, { id: 5, title: "Agricultural", count: 5617813, icon: <SVGAgricultural /> },
{ id: 6, title: "mainPage.featuredArticles.categories.Social", count: 5617813, icon: <SVGSocials /> }, { id: 6, title: "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) => (
@ -48,11 +45,11 @@ export function FeaturedArticlesCategories() {
fontWeightVariant="semibold" fontWeightVariant="semibold"
className="text-3xl mb-2" className="text-3xl mb-2"
> >
{t("mainPage.featuredArticles.title")} Featured articles
</Typography> </Typography>
<Typography htmlTag="h2" className="text-base text-gray-500"> <Typography htmlTag="h2" className="text-base text-gray-500">
{t("mainPage.featuredArticles.descriptionPart1")}<br className="visible sm:hidden" /> Select the category of science <br className="visible sm:hidden" />
{t("mainPage.featuredArticles.descriptionPart2")} you are interested in
</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,9 +17,12 @@ 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 */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -70,17 +73,13 @@ 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 text-3xl font-semibold"> <Heading className="text-center my-8">Featured Authors</Heading>
{t("mainPage.featuredAuthors")}
</Heading>
{/* Featured Authors section */} {/* Featured Authors section */}
<div className="slider-wrapper Authors px-8"> <div className="slider-wrapper Authors">
<Swiper <Swiper
slidesPerView={1.25} slidesPerView={1.25}
slidesPerGroup={1} slidesPerGroup={1}
@ -135,7 +134,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="*">
{t("mainPage.more")} See 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>
@ -189,7 +188,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">
{t("mainPage.showAll")} Show All
</Button> </Button>
</div> </div>
); );

View File

@ -2,36 +2,24 @@
/* 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 ">
{t("mainPage.title")} Scientific Library with Free Access
</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">{t("mainPage.search")}</div> <div className=" text-2xl text-gray-400">Search</div>
<div className=" text-3xl text-blue-500"> <div className=" text-3xl text-blue-500">320 455</div>
{formatNumber(amountArticles)} <div className=" text-2xl text-gray-400">Items</div>
</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">
{t("mainPage.advancedSearch")} Advanced Search
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,3 +1,5 @@
import React from "react";
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* MarkDown */ /* MarkDown */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -9,14 +11,15 @@ 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;
@ -28,134 +31,29 @@ const Markdown = ({ markdown }: Props) => {
remarkPlugins={[remarkGfm]} remarkPlugins={[remarkGfm]}
children={markdown} children={markdown}
components={{ components={{
ul: ({ node, ...props }) => ( h1: Heading,
<ul h2: Typography,
style={{ a: (props) => {
listStyleType: "disc", return (
}}
className="mx-8"
{...props}
/>
),
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 <Link
className=" inline-flex text-sm font-bold text-blue-500" href={props.href}
className="text-sky-600 font-bold text-base"
{...props} {...props}
/> >
), {props.children}
</Link>
);
},
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={dark} style={docco}
language={match[1]} language={match[1]}
PreTag="div" PreTag="div"
{...props}
/> />
) : ( ) : (
<code className={className} {...props}> <code className={className} {...props}>

View File

@ -4,12 +4,10 @@ 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) {
@ -18,7 +16,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"
> >
{t("searchResults.nothingFound")}. Nothing found.
</Typography> </Typography>
); );
} else { } else {
@ -33,14 +31,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">
{t("searchResults.title")} Search Results
</Typography> </Typography>
<Typography className="text-gray-300 text-sm"> <Typography className="text-gray-300 text-sm">
{t("searchResults.totalResults")}: {searchResults?.meta.total} Total results: {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

@ -1,86 +0,0 @@
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

@ -1,64 +0,0 @@
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

@ -1,59 +0,0 @@
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

@ -1,40 +0,0 @@
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: "footer.accountSettings", url: "/account/settings", disabled: false }, { label: "account settings", url: "/account/settings", disabled: false },
{ label: "footer.about", url: "/about", disabled: false }, { label: "about freeland", url: "/about", disabled: false },
{ label: "footer.help", url: "/help", disabled: false }, { label: "help", url: "/help", disabled: false },
{ label: "footer.contactUs", url: "/contact-us", disabled: false }, { label: "contact us", url: "/contact-us", disabled: false },
]; ];
const secondaryLinks = [ const secondaryLinks = [
{ index: 1, label: "footer.termsOfUse", url: "/terms-of-use", disabled: false }, { index: 1, label: "Terms of Use", url: "/terms-of-use", disabled: false },
{ {
index: 2, index: 2,
label: "footer.privacyPolicy", label: "Privacy Policy",
url: "/privacy-policy", url: "/privacy-policy",
disabled: false, disabled: false,
}, },
{ {
index: 3, index: 3,
label: "footer.coockiesPolicy", label: "Cookies Policy",
url: "/cookies-policy", url: "/cookies-policy",
disabled: false, disabled: false,
}, },
@ -48,8 +48,6 @@ 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(
() => () =>
@ -61,7 +59,7 @@ export function Footer() {
to={link.url} to={link.url}
> >
<Typography className="" fontWeightVariant="semibold" htmlTag="p"> <Typography className="" fontWeightVariant="semibold" htmlTag="p">
{t(link.label).toUpperCase()} {link.label.toUpperCase()}
</Typography> </Typography>
</RouterLink> </RouterLink>
)), )),
@ -74,7 +72,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}>
{t(link.label) } {link.label}
</Link> </Link>
</div> </div>
)), )),
@ -90,7 +88,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">
Scipaper Freeland
</Typography> </Typography>
</Link> </Link>
</div> </div>
@ -110,13 +108,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 - {t("footer.allRightsReserved")} @ Copyright 2022 Freeland - All rights reserved
</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">
{t("footer.supportedBy")} Supported by
<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,10 +1,11 @@
import classNames from "classnames"; import classNames from "classnames";
import { useState, useTransition } from "react"; import { useState } 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";
@ -22,18 +23,11 @@ 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 */
@ -52,14 +46,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 " /> */}
<Link className="Logo flex items-center gap-1 sm:gap-2 " to="/"> <a className="Logo flex items-center gap-1 sm:gap-2 " href="/">
<Logo <Logo
className={classNames(authenticated ? "w-10" : "w-7 sm:w-10")} className={classNames(authenticated ? "w-10" : "w-7 sm:w-10")}
/> />
<LogoScipaper <Logofreeland
className={classNames(authenticated ? "w-28" : "w-20 sm:w-28")} className={classNames(authenticated ? "w-28" : "w-20 sm:w-28")}
/> />
</Link> </a>
{/* Logo - end - */} {/* Logo - end - */}
{/* Menu( Create new - My library - About ) Start */} {/* Menu( Create new - My library - About ) Start */}
@ -72,36 +66,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"
> >
{t("navbar.createNew")} Create new
</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={t("navbar.library.navTitle")} button="My library"
className="border-none uppercase" className="border-none uppercase"
> >
<ContextMenuAction <ContextMenuAction
caption={t("navbar.library.publications")} caption="My 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={t("navbar.library.favorites")} caption="My 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={t("navbar.library.collections")} caption="My 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={t("navbar.library.recentViewed")} caption="Recent Viewed"
action={() => console.log("Recent Viewed")} action={() => console.log("Recent Viewed")}
icon={<SVGEye className="stroke-black " />} icon={<SVGEye className="stroke-black " />}
></ContextMenuAction> ></ContextMenuAction>
@ -111,21 +105,21 @@ const Header = () => {
{/* Dropdown Menu About - start - */} {/* Dropdown Menu About - start - */}
<ContextMenu <ContextMenu
emphasis="high" emphasis="high"
button={t("navbar.about.navTitle")} button="About"
className="border-none uppercase" className="border-none uppercase"
> >
<ContextMenuAction <ContextMenuAction
caption={t("navbar.about.aboutProject")} caption="About Freeland"
action={() => console.log("About Freeland")} action={() => console.log("About Freeland")}
></ContextMenuAction> ></ContextMenuAction>
<ContextMenuAction <ContextMenuAction
caption={t("navbar.about.contacts")} caption="Contact Us"
action={() => console.log("Contact Us")} action={() => console.log("Contact Us")}
></ContextMenuAction> ></ContextMenuAction>
<ContextMenuAction <ContextMenuAction
caption={t("navbar.about.help")} caption="Help"
action={() => console.log("Help")} action={() => console.log("Help")}
></ContextMenuAction> ></ContextMenuAction>
</ContextMenu> </ContextMenu>
@ -138,22 +132,19 @@ 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 "
> >
{t("navbar.auth.signIn")} Sign in
</Button> </Button>,
</>,
<Button <Button
emphasis="medium" emphasis="medium"
className="hidden md:flex" className="hidden md:flex"
onClick={onClick} onClick={onClick}
> >
{t("navbar.auth.signUp")} Sign up
</Button>, </Button>,
] ]
: [ : [
@ -174,7 +165,7 @@ const Header = () => {
</Button>, </Button>,
]} ]}
{/* Burger component will be shown for the small screens */} {/* Burger component will be shown for the small screens */}
<Burger className="block lg:hidden" /> <Navbar className="block lg:hidden" />
</div> </div>
</header> </header>
); );

View File

@ -4,7 +4,6 @@ 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 = {
@ -16,8 +15,6 @@ 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);
@ -34,7 +31,6 @@ 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,11 +51,10 @@ 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 link; return <div>{link}</div>;
} }

View File

@ -8,17 +8,6 @@ export const handleScrollTo = (e: React.MouseEvent<HTMLAnchorElement>) => {
} }
}; };
export function capitalization(str: string): string { export function capitalization (str: 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";
export const BASE_URL = process.env.REACT_APP_INTEGRATOR_URL; 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,9 +16,6 @@ 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");
@ -35,15 +32,10 @@ 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 path="/search-results" element={<SearchResultsPage />}></Route>
<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 = "ru"; const fallbackLng: Langs = "en";
i18n i18n
.use(Backend) .use(Backend)

View File

@ -20,6 +20,7 @@ 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,7 +3,9 @@ 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 Fiter from "components/Filters/Filter"; import { useSearchStoreImplementation } from "searchResults/data/searchStoreImplementation";
import { useSearchViewModel } from "searchResults/controller/searchResultsViewModel";
import { Loader } from "components/Loader/Loader";
export const SearchResultsPage = () => { export const SearchResultsPage = () => {
return ( return (
@ -11,13 +13,13 @@ export const SearchResultsPage = () => {
<SearchSection /> <SearchSection />
<ColumnLayout> <ColumnLayout>
<ColumnLayout.Left> <ColumnLayout.Left>
<div className="h-98 w-[300px] border rounded my-5"><Fiter /></div> <div className="h-98 bg-blue-200 w-[300px]">left bar</div>
</ColumnLayout.Left> </ColumnLayout.Left>
<ColumnLayout.Main> <ColumnLayout.Main>
<SearchResultSection /> <SearchResultSection />
</ColumnLayout.Main> </ColumnLayout.Main>
<ColumnLayout.Right> <ColumnLayout.Right>
<div className="w-[300px]"></div> <div className="h-98 bg-blue-200 w-[300px]">right bar</div>
</ColumnLayout.Right> </ColumnLayout.Right>
</ColumnLayout> </ColumnLayout>
</BaseLayout> </BaseLayout>

View File

@ -1,18 +1,16 @@
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,23 +10,11 @@ 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 // searchEndpoint + `?query=` + request + `&limit=10&offset=0`
// "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({ return create({ data: dto.data, meta: dto.meta });
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,12 +1,10 @@
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],
}); });