Compare commits

..

No commits in common. "master" and "feature/complete-develop-merge" have entirely different histories.

61 changed files with 240 additions and 2169 deletions

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 .

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

@ -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,27 +41,21 @@ 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({
isAbstractOpen = false, isAbstractOpen = false,
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 px-2 mr-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;

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 */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -13,10 +15,9 @@ import Heading from "./typography/Heading";
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* Code */ /* Code */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
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 { dark } from 'react-syntax-highlighter/dist/esm/styles/prism';
import Link from "./typography/Link"; 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,125 +29,19 @@ 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 || "");

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