├── .babelrc.json ├── .dockerignore ├── .eslintcache ├── .eslintrc.js ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.js ├── components ├── ContactUs.js ├── Footer.js ├── about │ ├── About.js │ ├── AboutComponent.js │ ├── AboutComponentRow.js │ ├── AboutZh.js │ └── MyImage.js ├── artists │ ├── DiscoverArtists.js │ ├── DiscoverAuthorList.js │ ├── DiscoverCollectionList.js │ └── ProfileDetailCard.js ├── collection-page │ ├── CollectionDetails.js │ ├── DeleteCollectionModal.js │ └── edit-collection │ │ ├── EditCollectionContainer.js │ │ ├── EditCollectionForm.js │ │ └── EditCollectionModal.js ├── create │ ├── Create.js │ ├── CreateContainer.js │ └── CreateForm.js ├── discover │ ├── Discover.js │ └── DiscoverCollectionList.js ├── home │ ├── Home.js │ ├── Latest.js │ └── TypeList.js ├── license │ ├── License.js │ └── LicenseZh.js ├── others │ ├── CategoryBar.js │ ├── LicenseButton.js │ ├── NavSearchBar.js │ ├── PhotoRelatedTagBar.js │ ├── RelatedTagBar.js │ ├── StatusButton.js │ ├── TagBar.js │ ├── TextInput.js │ ├── TextInputDescription.js │ ├── TokenExpireModal.js │ ├── TypeButton.js │ ├── UserCollectionsList.js │ ├── button │ │ ├── LoadMore.js │ │ ├── edit-collection-btn │ │ │ ├── CollectionDropdownButton.js │ │ │ └── CollectionDropdownButtonDotIcon.js │ │ └── edit-photo-btn │ │ │ └── DropdownButton.js │ ├── photo-card │ │ ├── CategoryCard.js │ │ ├── PhotoCard.js │ │ ├── PhotoMoreDetailsModal.js │ │ └── SaveToCollectionsModal.js │ ├── photo-list │ │ ├── HomePhotoList.js │ │ └── HomePhotoListContainer.js │ └── search-bar │ │ ├── DropdownButton.js │ │ └── SearchBar.js ├── photo-page │ ├── Colorbox.js │ ├── DeletePhotoModal.js │ ├── PhotoDetails.js │ ├── PhotoDetailsContainer.js │ ├── PhotoInfo.js │ └── RelatedPhotos.js ├── profile │ ├── Profile.js │ ├── Settings.js │ ├── UserArtworks.js │ ├── UserCollections.js │ ├── UserLikes.js │ ├── UserPage.js │ ├── UserPhotos.js │ ├── change-password │ │ ├── ChangePassword.js │ │ ├── ChangePasswordContainer.js │ │ └── ChangePasswordForm.js │ ├── delete-account │ │ ├── DeleteAccount.js │ │ └── DeleteAccountModal.js │ ├── edit-profile │ │ ├── EditProfile.js │ │ ├── EditProfileContainer.js │ │ └── EditProfileForm.js │ ├── update-avatar │ │ ├── AvatarEdit.js │ │ └── UpdateAvatar.js │ └── user-collections │ │ ├── CollectionCard.js │ │ ├── DeleteCollectionModal.js │ │ └── edit-collection │ │ ├── EditCollectionContainer.js │ │ ├── EditCollectionForm.js │ │ └── EditCollectionModal.js ├── search │ ├── SearchPage.js │ ├── SearchPagePhotoList.js │ ├── SearchPagePhotoListContainer.js │ └── SearchPhotoCard.js ├── sign-in │ ├── SignIn.js │ ├── SignInContainer.js │ └── SignInForm.js ├── sign-up │ ├── SignUp.js │ ├── SignUpContainer.js │ └── SignUpForm.js └── upload │ └── uploadComponent.js ├── config.js ├── graphql ├── fragment.js ├── mutations.js └── queries.js ├── hooks ├── useAuthorizedUser.js ├── useChangePassword.js ├── useCollectPhoto.js ├── useCollection.js ├── useCollections.js ├── useCreateAndLikePhoto.js ├── useCreateCollection.js ├── useCreateCollectionAndCollectPhoto.js ├── useCreatePhoto.js ├── useDeleteCollection.js ├── useDeletePhoto.js ├── useDeleteUser.js ├── useDiscoverCollections.js ├── useDownloadPhoto.js ├── useEditCollection.js ├── useEditPhotoLabels.js ├── useField.js ├── useFollowUser.js ├── useLikePhoto.js ├── usePhoto.js ├── usePhotos.js ├── useSignIn.js ├── useSignUp.js ├── useUncollectPhoto.js ├── useUnfollowUser.js ├── useUnlikeAndDeletePhoto.js ├── useUnlikePhoto.js ├── useUpdateAvatar.js ├── useUpdateProfile.js ├── useUser.js ├── useUserCollectionsPlus.js ├── useUserLikes.js ├── useUsers.js └── useVerifyMetadata.js ├── img ├── aboutImg4.jpg ├── galleryIcon.jpg ├── logo │ ├── logo.svg │ ├── logo1.svg │ └── logo2.svg ├── overlays │ ├── 01.png │ ├── 02.png │ ├── 03.png │ ├── 04.png │ ├── 05.png │ ├── 06.png │ ├── 07.png │ ├── 08.png │ └── 09.png └── svg │ ├── arrow_left.svg │ └── arrow_right.svg ├── index.css ├── index.js ├── logo.png ├── mdb.css ├── scss ├── about │ └── about.scss ├── collection-list │ └── collection-list.scss ├── create │ └── create.scss ├── home │ └── home.scss ├── license │ └── license.scss ├── navbar │ └── navbar.scss ├── photo-card │ └── photo-card.scss ├── photo-details │ └── photo-details.scss ├── photo-list │ └── photo-list.scss ├── profile │ └── profile.scss └── style.scss ├── styles.css ├── theme.js └── utils ├── formatters.js ├── photos.json └── saveToS3.js /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/preset-env"], 3 | "plugins": ["@emotion"] 4 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2020: true, 5 | jest: true, 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:react/recommended', 10 | 'airbnb', 11 | ], 12 | parser: 'babel-eslint', 13 | parserOptions: { 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | ecmaVersion: 12, 18 | sourceType: 'module', 19 | }, 20 | plugins: [ 21 | 'react', 22 | ], 23 | rules: { 24 | 'react/prop-types': 'off', 25 | semi: 'error', 26 | 'linebreak-style': 0, 27 | 'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }], 28 | 'react/jsx-props-no-spreading': 'off', 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | .eslintcache 22 | 23 | package-lock.json 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | /src/*css.map 30 | /src/css 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm ci --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | 18 | EXPOSE 3000 19 | CMD [ "npm", "run", "start" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Philo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Philo Art 2 | 3 | The relative Node.js backend is here: [PhiloArt-Backend](https://github.com/Philo-Li/philo-art-backend) 4 | 5 | ## ✔️ Website 6 | 7 | https://philoart.io/ 8 | 9 | ## 🚀 PhiloArt - Made for creators! 10 | 11 | - Personal ArtWork Gallery 12 | - Outstanding personal artwork site + high quality gallery 13 | - Help you publish and manage your work more easily 14 | - and faster access to the best free copyrighted images 15 | 16 | - What you can do with PhiloArt? 17 | - Upload, manage and publish your work with rich protocols 18 | - Discover and follow favorite artists and get instant updates 19 | - Discover, search and download high quality free copyrighted images for free 20 | - Like your favorite images and curate your own collections 21 | 22 | - Who needs PhiloArt? 23 | - Artist 24 | - Photographer 25 | - Creator 26 | - Designer 27 | - And you 28 | 29 | - Why choose PhiloArt? 30 | - Easily and quickly publish and manage your work with multiple protocols 31 | - Discover best works and free copyrighted works 32 | - Follow favorite artists 33 | - Encourage creativity, inspire and enhance creativity 34 | 35 | ## 🐛 Found a bug? 36 | 37 | Submit an issue with the bug description and a way to reproduce the bug. If you have already come up with a solution, we will gladly accept a pull request. 38 | 39 | If you have any questions or suggestions that might make the Philo Art experience even better, please let us know! You can get in touch with us at philoart42@gmail.com. 40 | 41 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | 5 | philoart: 6 | # image: nodejs:v1.0 7 | build: 8 | context: . 9 | dockerfile: Dockerfile 10 | container_name: philoart 11 | volumes: 12 | - '.:/app' 13 | ports: 14 | - 3000:3000 15 | environment: 16 | - CHOKIDAR_USEPOLLING=true -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "philoart", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ant-design/icons": "^4.7.0", 7 | "@apollo/client": "^3.2.5", 8 | "@apollo/react-hooks": "^4.0.0", 9 | "@emotion/react": "^11.1.5", 10 | "@material-ui/core": "^4.11.3", 11 | "@material-ui/icons": "^4.11.2", 12 | "@testing-library/jest-dom": "^5.11.6", 13 | "@testing-library/react": "^11.2.2", 14 | "@testing-library/user-event": "^12.3.0", 15 | "antd": "^4.18.6", 16 | "apollo-link-context": "^1.0.20", 17 | "aws-sdk": "^2.1069.0", 18 | "axios": "^0.21.1", 19 | "bootstrap": "^5.1.1", 20 | "bootstrap-icons": "^1.8.1", 21 | "cross-env": "^7.0.3", 22 | "date-fns": "^2.16.1", 23 | "dotenv": "^8.2.0", 24 | "formik": "^2.2.6", 25 | "glamor": "^2.17.9", 26 | "got": "^11.8.1", 27 | "graphql": "^15.3.0", 28 | "http2": "^3.3.7", 29 | "mdbreact": "^4.11.1", 30 | "nanoid": "^3.3.0", 31 | "path-browserify": "^1.0.1", 32 | "pexels": "^1.0.1", 33 | "query-string": "^6.14.1", 34 | "react": "^17.0.2", 35 | "react-avatar-edit": "^1.1.0", 36 | "react-blockies": "^1.4.1", 37 | "react-bootstrap": "^2.0.0-rc.0", 38 | "react-dom": "^17.0.2", 39 | "react-dropzone": "^12.0.0", 40 | "react-easy-crop": "^3.3.1", 41 | "react-icons": "^4.1.0", 42 | "react-inner-image-zoom": "^1.3.0", 43 | "react-lazyload": "^3.2.0", 44 | "react-map-interaction": "^2.1.0", 45 | "react-masonry-css": "^1.0.14", 46 | "react-router-dom": "^5.2.0", 47 | "react-scripts": "^4.0.3", 48 | "react-select": "^3.2.0", 49 | "react-spinners": "^0.10.6", 50 | "react-youtube-embed": "^1.0.3", 51 | "semantic-ui-react": "^2.0.3", 52 | "subscriptions-transport-ws": "^0.9.18", 53 | "web-vitals": "^0.2.4", 54 | "yup": "^0.32.9" 55 | }, 56 | "scripts": { 57 | "start": "react-scripts start", 58 | "build": "react-scripts build", 59 | "test": "react-scripts test", 60 | "eject": "react-scripts eject", 61 | "keep": "serve -l 3000 -s build" 62 | }, 63 | "eslintConfig": { 64 | "extends": [ 65 | "react-app", 66 | "react-app/jest" 67 | ] 68 | }, 69 | "browserslist": { 70 | "production": [ 71 | ">0.2%", 72 | "not dead", 73 | "not op_mini all" 74 | ], 75 | "development": [ 76 | "last 1 chrome version", 77 | "last 1 firefox version", 78 | "last 1 safari version" 79 | ] 80 | }, 81 | "proxy": "http://localhost:5005", 82 | "devDependencies": { 83 | "@typescript-eslint/parser": "^4.10.0", 84 | "eslint": "^7.14.0", 85 | "eslint-config-airbnb": "^18.2.1", 86 | "typescript": "^4.1.3" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Philo-Li/philoart/a88b0442a8759e7a24a4739c0540001aa6750c29/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 22 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 41 | 42 | 51 | Philo Art 52 | 53 | 54 | 55 |
56 | 57 | 58 | 59 | 60 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Philo-Li/philoart/a88b0442a8759e7a24a4739c0540001aa6750c29/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Philo-Li/philoart/a88b0442a8759e7a24a4739c0540001aa6750c29/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/components/ContactUs.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | 4 | const ContactUs = () => ( 5 |
6 |
7 |
8 |

Contact Us

9 |
10 |
11 |
12 | If you have any questions or suggestions that might make the PhiloArt experience even better, please let us know! You can get in touch with us at philoart42@gmail.com. 13 |
14 |
15 |
16 |
17 | ); 18 | 19 | export default ContactUs; 20 | -------------------------------------------------------------------------------- /src/components/about/AboutComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Col } from 'react-bootstrap'; 3 | import MyImage from './MyImage'; 4 | 5 | const AboutComponent = ({ msgToShow }) => { 6 | if (!msgToShow) return null; 7 | return ( 8 |
9 | 10 | 11 | 12 | 13 |
14 |

{msgToShow.title}

15 |
16 | {msgToShow.msgList.map((item) => ( 17 |
18 |
19 |

20 | 21 | {item.msg} 22 |

23 |
24 |
25 | ))} 26 |
27 |
28 | 29 |
30 | ); 31 | }; 32 | 33 | export default AboutComponent; 34 | -------------------------------------------------------------------------------- /src/components/about/AboutComponentRow.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Col } from 'react-bootstrap'; 3 | import MyImage from './MyImage'; 4 | 5 | const AboutComponentRow = ({ msgToShow }) => { 6 | if (!msgToShow) return null; 7 | return ( 8 |
9 | 10 | 11 | 12 | 13 |
14 |

{msgToShow.title}

15 |
16 | {msgToShow.msgList.map((item) => ( 17 |
18 |
19 |

20 | 21 |

22 |

23 | {item.msg} 24 |

25 |
26 |
27 | ))} 28 |
29 |
30 | 31 |
32 | ); 33 | }; 34 | 35 | export default AboutComponentRow; 36 | -------------------------------------------------------------------------------- /src/components/about/AboutZh.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import { Card } from 'react-bootstrap'; 4 | import aboutImg4 from '../../img/aboutImg4.jpg'; 5 | import AboutComponent from './AboutComponent'; 6 | import AboutComponentRow from './AboutComponentRow'; 7 | import MyImage from './MyImage'; 8 | 9 | const img1 = 'https://images.unsplash.com/photo-1469854523086-cc02fe5d8800?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1416&q=80'; 10 | const img2 = 'https://images.unsplash.com/photo-1497030947858-3f40f1508e84?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1500&q=80'; 11 | const img3 = 'https://images.unsplash.com/3/doctype-hi-res.jpg?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=1492&q=80'; 12 | const img4 = aboutImg4; 13 | 14 | const AboutZh = () => { 15 | const msgToShow = [ 16 | { 17 | title: '出色的个人艺术作品站+高质量图库', 18 | subtitle1: '帮助你更便捷地发布和管理自己的作品', 19 | subtitle2: '以及更快地获取最优秀的免费版权图片', 20 | intro: 'PhiloArt - 为创作者而生!', 21 | imgFirst: true, 22 | img: { srcTiny: img1, srcSmall: img1, srcLarge: img1 }, 23 | }, 24 | { 25 | title: 'PhiloArt 可以做什么?', 26 | imgFirst: false, 27 | img: { srcTiny: img2, srcSmall: img2, srcLarge: img2 }, 28 | msgList: [ 29 | { icon: 'bi bi-download icon-check', msg: '上传、管理并且以丰富的协议发布你的作品' }, 30 | { icon: 'bi bi-search icon-check', msg: '发现和关注喜欢的艺术家,获取即时动态' }, 31 | { icon: 'bi bi-heart icon-check', msg: '发现、搜索和免费下载高品质免费版权图片' }, 32 | { icon: 'bi bi-plus-square icon-check', msg: '点赞你喜欢的图片并创建专属收藏夹' }, 33 | ], 34 | }, 35 | { 36 | title: '谁需要 PhiloArt?', 37 | imgFirst: true, 38 | img: { srcTiny: img3, srcSmall: img3, srcLarge: img3 }, 39 | msgList: [ 40 | { icon: 'bi bi-palette icon-check', msg: '艺术家' }, 41 | { icon: 'bi bi-camera icon-check', msg: '摄影师' }, 42 | { icon: 'bi bi-pencil icon-check', msg: '创作者' }, 43 | { icon: 'bi bi-shop icon-check', msg: '设计师' }, 44 | { icon: 'bi bi-emoji-sunglasses icon-check', msg: '...还有你' }, 45 | ], 46 | }, 47 | { 48 | title: '为什么选择 PhiloArt?', 49 | imgFirst: false, 50 | img: { srcTiny: img4, srcSmall: img4, srcLarge: img4 }, 51 | msgList: [ 52 | { icon: 'bi bi-check2 icon-check', msg: '方便快捷地以多种协议发布和管理你的作品' }, 53 | { icon: 'bi bi-check2 icon-check', msg: '发现更多优秀的作品及免费版权作品' }, 54 | { icon: 'bi bi-check2 icon-check', msg: '关注喜欢的艺术家' }, 55 | { icon: 'bi bi-check2 icon-check', msg: '鼓励创作、激发灵感和提升创造力' }, 56 | ], 57 | }, 58 | ]; 59 | return ( 60 |
61 |
62 |
63 |

关于 PhiloArt

64 |
65 | English 66 |
67 |
68 |

{msgToShow[0].intro}

69 |
70 |
71 |

{msgToShow[0].title}

72 |

73 | {msgToShow[0].subtitle1} 74 |

75 |

76 | {msgToShow[0].subtitle2} 77 |

78 |
79 | 80 | 81 | 82 |
83 | 84 |
85 |
86 |
87 |

88 | 如果你有任何疑问或帮助 PhiloArt 实现更好的体验的改进建议,可以发送邮件到 philoart42@gmail.com. 89 |

90 |
91 |
92 | {/* 这世界战争不停,争吵不息,满目疮痍。我想画一点温暖东西,治愈自己,也治愈他人。 */} 93 |
94 | ); 95 | }; 96 | 97 | export default AboutZh; 98 | -------------------------------------------------------------------------------- /src/components/about/MyImage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const MyImage = ({ image }) => { 4 | if (!image) return null; 5 | return ( 6 | 7 | 8 | 9 | gird item 15 | 16 | ); 17 | }; 18 | 19 | export default MyImage; 20 | -------------------------------------------------------------------------------- /src/components/artists/DiscoverArtists.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { css } from '@emotion/react'; 3 | import PacmanLoader from 'react-spinners/PacmanLoader'; 4 | import useUsers from '../../hooks/useUsers'; 5 | import DiscoverAuthorList from './DiscoverAuthorList'; 6 | 7 | const override = css` 8 | display: flex; 9 | justify-content: center; 10 | align-item: center; 11 | margin: 3rem; 12 | margin-bottom: 6rem; 13 | `; 14 | 15 | const DiscoverArtists = () => { 16 | const [allUsers, setAllUsers] = useState(); 17 | const [loading, setLoading] = useState(false); 18 | const { users, fetchMore, hasNextPage } = useUsers({ 19 | first: 30, 20 | }); 21 | 22 | useEffect(() => { 23 | if (users) { 24 | const temp = users && users.edges 25 | ? users.edges.map((edge) => edge.node) 26 | : []; 27 | 28 | setAllUsers(temp); 29 | setLoading(false); 30 | } 31 | }, [users]); 32 | 33 | const clickFetchMore = () => { 34 | fetchMore(); 35 | setLoading(true); 36 | }; 37 | 38 | if (allUsers === undefined) { 39 | return ( 40 |
41 |
42 |
43 |

Discover

44 |
45 |
46 |
47 | 48 |
49 |
50 | ); 51 | } 52 | 53 | return ( 54 |
55 |
56 |
57 |

Discover Artists

58 |
59 |
60 |
61 | 67 |
68 |
69 | ); 70 | }; 71 | 72 | export default DiscoverArtists; 73 | -------------------------------------------------------------------------------- /src/components/artists/DiscoverAuthorList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card } from 'react-bootstrap'; 3 | import { nanoid } from 'nanoid'; 4 | import Masonry from 'react-masonry-css'; 5 | import LoadMore from '../others/button/LoadMore'; 6 | import ProfileDetailCard from './ProfileDetailCard'; 7 | 8 | const breakpointColumnsObj = { 9 | default: 3, 10 | 800: 2, 11 | 500: 1, 12 | }; 13 | 14 | const DiscoverAuthorList = ({ 15 | allUsers, clickFetchMore, loading, hasNextPage, 16 | }) => { 17 | if (!allUsers) { 18 | return ( 19 |
20 |

No result

21 |
22 | ); 23 | } 24 | 25 | const initProfileImage = 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png'; 26 | 27 | return ( 28 |
29 | 34 | {allUsers.map((user) => ( 35 | 36 | 37 |
38 | 42 |
43 |
44 |
45 | ))} 46 |
47 | 52 |
53 | ); 54 | }; 55 | 56 | export default DiscoverAuthorList; 57 | -------------------------------------------------------------------------------- /src/components/artists/DiscoverCollectionList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // import { Card } from 'react-bootstrap'; 3 | import { useHistory } from 'react-router-dom'; 4 | import Masonry from 'react-masonry-css'; 5 | import CollectionCard from '../profile/user-collections/CollectionCard'; 6 | // import galleryIcon from '../../img/galleryIcon.jpg'; 7 | 8 | const breakpointColumnsObj = { 9 | default: 3, 10 | 800: 2, 11 | 500: 1, 12 | }; 13 | 14 | // const INIT_COVER = galleryIcon; 15 | 16 | const DiscoverCollectionList = ({ allCollections, category }) => { 17 | const collectionsToShow = allCollections 18 | .filter((collection) => collection.description === category); 19 | 20 | const history = useHistory(); 21 | if (!collectionsToShow) { 22 | return ( 23 |
24 |

No result

25 |
26 | ); 27 | } 28 | 29 | // eslint-disable-next-line no-unused-vars 30 | const openCollection = (collection) => { 31 | history.push(`/collection/${collection.id}`); 32 | }; 33 | 34 | return ( 35 |
36 | 41 | {/* {collectionsToShow.map((collection) => ( 42 | 43 |
{ openCollection(collection); }} 46 | onKeyPress={() => openCollection(collection)} 47 | role="button" 48 | tabIndex="0" 49 | > 50 | smaple 55 |
56 | 57 |
58 |
59 | 60 |

61 | {collection.title} 62 | ( 63 | {collection.photoCount} 64 | ) 65 |

66 |
67 |
68 | ))} */} 69 | {collectionsToShow.map((collection) => ( 70 | 75 | ))} 76 |
77 |
78 | ); 79 | }; 80 | 81 | export default DiscoverCollectionList; 82 | -------------------------------------------------------------------------------- /src/components/artists/ProfileDetailCard.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable arrow-body-style */ 2 | import React from 'react'; 3 | import { Image } from 'react-bootstrap'; 4 | 5 | const ProfileDetailCard = ({ profileImage, userNow }) => { 6 | return ( 7 |
8 |
9 |
10 | 11 |
12 |
13 |

{userNow && `${userNow.firstName} ${userNow.lastName || ''}`}

14 |
15 |
16 | {userNow && userNow.description && ( 17 |
18 |
{userNow.description}
19 |
20 | )} 21 |
22 |
23 | {`${userNow ? userNow.photoCount : 0} artworks`} 24 |
25 | {/*
26 | {`${userNow ? userNow.followingCount : 0} followings`} 27 |
*/} 28 |
29 | {`${userNow ? userNow.followerCount : 0} followers`} 30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | export default ProfileDetailCard; 37 | -------------------------------------------------------------------------------- /src/components/collection-page/CollectionDetails.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React, { useEffect, useState } from 'react'; 3 | import { useParams } from 'react-router-dom'; 4 | import { css } from '@emotion/react'; 5 | import PacmanLoader from 'react-spinners/PacmanLoader'; 6 | import CollectionDropdownButton from '../others/button/edit-collection-btn/CollectionDropdownButton'; 7 | import HomePhotoList from '../others/photo-list/HomePhotoList'; 8 | import useCollection from '../../hooks/useCollection'; 9 | import EditCollectionModal from './edit-collection/EditCollectionModal'; 10 | import DeleteCollectionModal from './DeleteCollectionModal'; 11 | 12 | const override = css` 13 | display: flex; 14 | justify-content: center; 15 | align-item: center; 16 | margin: 3rem; 17 | margin-bottom: 6rem; 18 | `; 19 | 20 | const CollectionDetails = () => { 21 | const { id } = useParams(); 22 | const [allPhotos, setAllPhotos] = useState(); 23 | const [collectionNow, setCollectionNow] = useState(); 24 | const [showEditCollectionModal, setShowEditCollectionModal] = useState(false); 25 | const [showDeleteModal, setShowDeleteModal] = useState(false); 26 | const [loading, setLoading] = useState(false); 27 | const userId = localStorage.getItem('userId'); 28 | const username = localStorage.getItem('username'); 29 | 30 | const variables = { 31 | id, 32 | checkUserLike: userId, 33 | first: 20, 34 | }; 35 | 36 | const { collection, fetchMore, hasNextPage } = useCollection(variables); 37 | 38 | useEffect(() => { 39 | if (collection) { 40 | const temp = collection.photoCount > 0 41 | ? collection.photos.edges.map((edge) => edge.node.photo) 42 | : []; 43 | setCollectionNow(collection); 44 | 45 | setAllPhotos(temp); 46 | setLoading(false); 47 | } 48 | }, [collection]); 49 | 50 | const clickFetchMore = () => { 51 | if (collectionNow.photoCount > allPhotos.length) { 52 | fetchMore(); 53 | setLoading(true); 54 | } 55 | }; 56 | 57 | if (!collectionNow) { 58 | return ( 59 |
60 | 61 |
62 | ); 63 | } 64 | 65 | return ( 66 |
67 |
68 |
69 |
70 |

71 | {collectionNow.title} 72 |

73 |
74 |
75 |

76 | Collected by 77 | {' '} 78 | {collectionNow.user.username} 79 |

80 |
81 |
82 |

83 | {collectionNow.photoCount} 84 | {' '} 85 | photos 86 |

87 |
88 |
89 |
90 |
91 |
92 | {username && collectionNow.user.username === username && ( 93 | 97 | )} 98 |
99 |
100 | 106 | 111 | 119 |
120 | ); 121 | }; 122 | 123 | export default CollectionDetails; 124 | -------------------------------------------------------------------------------- /src/components/collection-page/DeleteCollectionModal.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useHistory } from 'react-router-dom'; 3 | import { Modal, Alert } from 'react-bootstrap'; 4 | import useDeleteCollection from '../../hooks/useDeleteCollection'; 5 | 6 | const DeleteCollectionModal = ({ 7 | collectionNow, 8 | showDeleteModal, 9 | setShowDeleteModal, 10 | }) => { 11 | const [errorInfo, setErrorInfo] = useState(''); 12 | const [successInfo, setSuccessInfo] = useState(''); 13 | const [deleteCollection] = useDeleteCollection(); 14 | const history = useHistory(); 15 | 16 | const deleteSingleCollection = async () => { 17 | try { 18 | await deleteCollection({ id: collectionNow.id }); 19 | setSuccessInfo('Collection is deleted'); 20 | setTimeout(() => { setSuccessInfo(''); setShowDeleteModal(false); history.goBack(); }, 3000); 21 | } catch (e) { 22 | setErrorInfo(e.message); 23 | setTimeout(() => { setErrorInfo(''); }, 3000); 24 | } 25 | }; 26 | 27 | return ( 28 |
29 | setShowDeleteModal(false)} 32 | centered 33 | dialogClassName="modal-90w" 34 | aria-labelledby="example-custom-modal-styling-title" 35 | > 36 | 37 | 38 | Delete this collection? 39 | 40 | 41 | {errorInfo && ( 42 | 43 | {errorInfo} 44 | 45 | )} 46 | {successInfo && ( 47 | 48 | {successInfo} 49 | 50 | )} 51 | 52 | 55 | 59 | 60 | 61 |
62 | ); 63 | }; 64 | 65 | export default DeleteCollectionModal; 66 | -------------------------------------------------------------------------------- /src/components/collection-page/edit-collection/EditCollectionContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Formik } from 'formik'; 3 | import { Alert } from 'react-bootstrap'; 4 | import * as Yup from 'yup'; 5 | 6 | import EditCollectionForm from './EditCollectionForm'; 7 | 8 | const validationSchema = Yup.object().shape({ 9 | title: Yup 10 | .string() 11 | .required('Title is required'), 12 | description: Yup 13 | .string() 14 | .max(50, 'Must be 50 characters or less'), 15 | }); 16 | 17 | const EditCollectionContainer = ({ 18 | initialValues, onSubmit, errorInfo, successInfo, loading, 19 | }) => ( 20 |
21 | {errorInfo && ( 22 | 23 | {errorInfo} 24 | 25 | )} 26 | {successInfo && ( 27 | 28 | {successInfo} 29 | 30 | )} 31 | 36 | {({ handleSubmit }) => } 37 | 38 |
39 | ); 40 | 41 | export default EditCollectionContainer; 42 | -------------------------------------------------------------------------------- /src/components/collection-page/edit-collection/EditCollectionForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Button, Spinner } from 'react-bootstrap'; 4 | import { Form } from 'formik'; 5 | 6 | import TextInput from '../../others/TextInput'; 7 | 8 | const EditCollectionForm = ({ loading }) => ( 9 |
10 |
11 | 17 |
18 | 19 |
20 | 26 |
27 | 28 |
29 | {!loading && ( 30 | 33 | )} 34 | {loading && ( 35 | 45 | )} 46 |
47 |
48 | ); 49 | 50 | export default EditCollectionForm; 51 | -------------------------------------------------------------------------------- /src/components/collection-page/edit-collection/EditCollectionModal.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Modal } from 'react-bootstrap'; 3 | import useEditCollection from '../../../hooks/useEditCollection'; 4 | import EditCollectionContainer from './EditCollectionContainer'; 5 | 6 | const EditCollectionModal = ({ 7 | collectionNow, 8 | setCollectionNow, 9 | showEditCollectionModal, 10 | setShowEditCollectionModal, 11 | }) => { 12 | const [editCollection] = useEditCollection(); 13 | const [loading, setLoading] = useState(false); 14 | const [errorInfo, setErrorInfo] = useState(''); 15 | const [successInfo, setSuccessInfo] = useState(''); 16 | 17 | const initialValues = { 18 | title: collectionNow.title, 19 | description: collectionNow.description || '', 20 | }; 21 | 22 | const onSubmit = async (values) => { 23 | const variables = { 24 | collectionId: collectionNow.id, 25 | newTitle: values.title, 26 | newDescription: values.description, 27 | }; 28 | 29 | const updatedCollectionNow = { 30 | ...collectionNow, 31 | title: values.title, 32 | description: values.description, 33 | }; 34 | setLoading(true); 35 | 36 | try { 37 | await editCollection(variables); 38 | setCollectionNow(updatedCollectionNow); 39 | setSuccessInfo('Collection details updated'); 40 | setTimeout(() => { setSuccessInfo(''); }, 3000); 41 | } catch (e) { 42 | setErrorInfo(e.message); 43 | setTimeout(() => { setErrorInfo(''); }, 3000); 44 | } 45 | setLoading(false); 46 | }; 47 | 48 | return ( 49 |
50 | setShowEditCollectionModal(false)} 53 | centered 54 | dialogClassName="modal-90w" 55 | aria-labelledby="example-custom-modal-styling-title" 56 | > 57 | 58 | 59 | Edit Collection 60 | {' '} 61 | {collectionNow.title} 62 | 63 | 64 | 65 | 72 | 73 | 74 |
75 | ); 76 | }; 77 | 78 | export default EditCollectionModal; 79 | -------------------------------------------------------------------------------- /src/components/create/Create.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import React, { useState } from 'react'; 3 | import axios from 'axios'; 4 | import { css } from '@emotion/react'; 5 | import { nanoid } from 'nanoid'; 6 | import { useHistory } from 'react-router-dom'; 7 | import PacmanLoader from 'react-spinners/PacmanLoader'; 8 | import CreateContainer from './CreateContainer'; 9 | import useCreatePhoto from '../../hooks/useCreatePhoto'; 10 | import saveToS3 from '../../utils/saveToS3'; 11 | import config from '../../config'; 12 | 13 | const override = css` 14 | display: flex; 15 | justify-content: center; 16 | align-item: center; 17 | margin: 3rem; 18 | margin-bottom: 6rem; 19 | `; 20 | 21 | const baseUrl = config.philoartApi; 22 | 23 | const initialValues = { 24 | title: 'Untitled', 25 | description: '', 26 | license: 'CC BY-NC', 27 | type: 'painting', 28 | }; 29 | 30 | const Create = () => { 31 | const history = useHistory(); 32 | const [successInfo, setSuccessInfo] = useState(''); 33 | const [errorInfo, setErrorInfo] = useState(''); 34 | const [loading, setLoading] = useState(false); 35 | const [createPhoto] = useCreatePhoto(); 36 | const [files, setFiles] = useState([]); 37 | const [license, setLicense] = useState('CC BY-NC'); 38 | const [type, setType] = useState('Photograph'); 39 | const [status, setStatus] = useState('None'); 40 | const [checked, setChecked] = useState(true); 41 | const userId = localStorage.getItem('userId'); 42 | const username = localStorage.getItem('username'); 43 | 44 | if (!userId) { 45 | return ( 46 |
47 | 48 |
49 | ); 50 | } 51 | 52 | const onSubmit = async (values) => { 53 | const { 54 | title, description, 55 | } = values; 56 | 57 | setLoading(true); 58 | try { 59 | const imageKey = `${userId}-${nanoid(7)}`; 60 | const imageUrl = await saveToS3(imageKey, files[0]); 61 | 62 | // store the image data to the server 63 | const variables = { 64 | photoId: imageKey, 65 | title, 66 | year: new Date().getFullYear(), 67 | description, 68 | imageUrl, 69 | license, 70 | type, 71 | status, 72 | allowDownload: checked, 73 | }; 74 | await createPhoto(variables); 75 | setLoading(false); 76 | setSuccessInfo('Photo is uploaded successfully!'); 77 | setTimeout(() => { setSuccessInfo(''); history.push(`/${username}`); }, 3000); 78 | } catch (e) { 79 | setErrorInfo(e.message); 80 | setLoading(false); 81 | setTimeout(() => { setErrorInfo(''); }, 3000); 82 | } 83 | }; 84 | 85 | const handleCheckboxChange = () => { 86 | setChecked(!checked); 87 | }; 88 | 89 | return ( 90 |
91 | 104 |
105 | ); 106 | }; 107 | 108 | export default Create; 109 | -------------------------------------------------------------------------------- /src/components/create/CreateContainer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import { Formik } from 'formik'; 4 | import { Image, Alert } from 'react-bootstrap'; 5 | import * as yup from 'yup'; 6 | 7 | import CreateForm from './CreateForm'; 8 | 9 | import logo from '../../img/logo/logo2.svg'; 10 | 11 | const validationSchema = yup.object().shape({ 12 | title: yup 13 | .string() 14 | .required(), 15 | description: yup 16 | .string(), 17 | }); 18 | 19 | const CreateContainer = ({ 20 | initialValues, onSubmit, successInfo, errorInfo, loading, files, setFiles, setLicense, setType, setStatus, handleCheckboxChange, checked, 21 | }) => ( 22 |
23 |
24 |
25 |

Create

26 |
27 |
28 | 29 |
30 |
31 | {errorInfo && ( 32 | 33 | {errorInfo} 34 | 35 | )} 36 | 41 | {({ handleSubmit }) => ( 42 | 53 | )} 54 | 55 | {successInfo && ( 56 | 57 | {successInfo} 58 | 59 | )} 60 |
61 | ); 62 | 63 | export default CreateContainer; 64 | -------------------------------------------------------------------------------- /src/components/create/CreateForm.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/label-has-associated-control */ 2 | import React from 'react'; 3 | import { Button, Spinner } from 'react-bootstrap'; 4 | import { Form } from 'formik'; 5 | import TextInput from '../others/TextInput'; 6 | import Previews from '../upload/uploadComponent'; 7 | import LicenseButton from '../others/LicenseButton'; 8 | import TypeButton from '../others/TypeButton'; 9 | import StatusButton from '../others/StatusButton'; 10 | import TextInputDescription from '../others/TextInputDescription'; 11 | 12 | const CreateForm = ({ 13 | loading, files, setFiles, setLicense, setType, setStatus, handleCheckboxChange, checked, 14 | }) => ( 15 |
16 | 17 |
18 |
19 | 25 |
26 |
27 |
28 |
29 | 35 |
36 |
37 |
38 |
39 | Type: 40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 | Status: 48 |
49 |
50 | 51 |
52 |
53 |
54 |
55 | License: 56 |
57 |
58 | 59 |
60 |
61 | 68 |
69 |
70 |
71 |
72 | 73 | 76 |
77 |
78 |
79 | {!loading && files[0] && ( 80 | 83 | )} 84 | {!loading && !files[0] && ( 85 | 88 | )} 89 | {loading && ( 90 | 100 | )} 101 |
102 | 103 | ); 104 | 105 | export default CreateForm; 106 | -------------------------------------------------------------------------------- /src/components/discover/Discover.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { css } from '@emotion/react'; 3 | import PacmanLoader from 'react-spinners/PacmanLoader'; 4 | import DiscoverCollectionList from './DiscoverCollectionList'; 5 | import config from '../../config'; 6 | import useDiscoverCollections from '../../hooks/useDiscoverCollections'; 7 | 8 | const override = css` 9 | display: flex; 10 | justify-content: center; 11 | align-item: center; 12 | margin: 3rem; 13 | margin-bottom: 6rem; 14 | `; 15 | 16 | // const CATEGORY = ['mood', 'animals', 'light', 'nature', 'human', 'road', 'food']; 17 | 18 | const Discover = () => { 19 | const [allCollections, setAllCollections] = useState(); 20 | const { collections } = useDiscoverCollections({ 21 | username: config.pickyAdmin, 22 | first: 30, 23 | }); 24 | 25 | useEffect(() => { 26 | if (collections) { 27 | const temp = collections && collections.edges 28 | ? collections.edges.map((edge) => edge.node) 29 | : []; 30 | 31 | setAllCollections(temp); 32 | } 33 | }, [collections]); 34 | 35 | if (allCollections === undefined) { 36 | return ( 37 |
38 |
39 |
40 |

Discover

41 |
42 |
43 |
44 | 45 |
46 |
47 | ); 48 | } 49 | 50 | return ( 51 |
52 |
53 |
54 |

Discover

55 |
56 |
57 |
58 |
59 |

Nature

60 |
61 | 62 |
63 |
64 |
65 |

Human

66 |
67 | 68 |
69 |
70 |
71 |

Mood

72 |
73 | 74 |
75 |
76 |
77 |

Delicious food

78 |
79 | 80 |
81 |
82 |
83 |

Animals

84 |
85 | 86 |
87 |
88 |
89 |

Light and shadow

90 |
91 | 92 |
93 |
94 |
95 |

On the Road

96 |
97 | 98 |
99 |
100 |
101 |

Other

102 |
103 | 104 |
105 |
106 | ); 107 | }; 108 | 109 | export default Discover; 110 | -------------------------------------------------------------------------------- /src/components/discover/DiscoverCollectionList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // import { Card } from 'react-bootstrap'; 3 | import { useHistory } from 'react-router-dom'; 4 | import Masonry from 'react-masonry-css'; 5 | import CollectionCard from '../profile/user-collections/CollectionCard'; 6 | // import galleryIcon from '../../img/galleryIcon.jpg'; 7 | 8 | const breakpointColumnsObj = { 9 | default: 3, 10 | 800: 2, 11 | 500: 1, 12 | }; 13 | 14 | // const INIT_COVER = galleryIcon; 15 | 16 | const DiscoverCollectionList = ({ allCollections, category }) => { 17 | const collectionsToShow = allCollections 18 | .filter((collection) => collection.description === category); 19 | 20 | const history = useHistory(); 21 | if (!collectionsToShow) { 22 | return ( 23 |
24 |

No result

25 |
26 | ); 27 | } 28 | 29 | // eslint-disable-next-line no-unused-vars 30 | const openCollection = (collection) => { 31 | history.push(`/collection/${collection.id}`); 32 | }; 33 | 34 | return ( 35 |
36 | 41 | {/* {collectionsToShow.map((collection) => ( 42 | 43 |
{ openCollection(collection); }} 46 | onKeyPress={() => openCollection(collection)} 47 | role="button" 48 | tabIndex="0" 49 | > 50 | smaple 55 |
56 | 57 |
58 |
59 | 60 |

61 | {collection.title} 62 | ( 63 | {collection.photoCount} 64 | ) 65 |

66 |
67 |
68 | ))} */} 69 | {collectionsToShow.map((collection) => ( 70 | 75 | ))} 76 |
77 |
78 | ); 79 | }; 80 | 81 | export default DiscoverCollectionList; 82 | -------------------------------------------------------------------------------- /src/components/home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Tabs, Tab, Carousel } from 'react-bootstrap'; 3 | import Latest from './Latest'; 4 | import TypeList from './TypeList'; 5 | import CategoryBar from '../others/CategoryBar'; 6 | import Discover from '../discover/Discover'; 7 | import SearchBar from '../others/search-bar/SearchBar'; 8 | 9 | const Home = () => { 10 | const [key, setKey] = useState('freetouse'); 11 | 12 | return ( 13 |
14 |
15 | 16 | 17 |
18 | 19 | 20 |

Share your artworks with the world.

21 |

Create, and Post it

22 |
23 | 24 | 25 |
26 | 27 |

Create, Mint, and Sell

28 |

Discover the best NFTs(Upcoming).

29 |
30 | 31 | 32 |
33 | 34 |

Discover the best artworks.

35 |

Free for personal use and download.

36 |
37 | 38 | 39 |
40 | 41 | setKey(k)} 45 | className="mb-3" 46 | > 47 | {/* 48 | 55 | */} 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
79 | ); 80 | }; 81 | 82 | export default Home; 83 | -------------------------------------------------------------------------------- /src/components/home/Latest.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import usePhotos from '../../hooks/usePhotos'; 3 | import HomePhotoList from '../others/photo-list/HomePhotoList'; 4 | 5 | const Latest = () => { 6 | const [allPhotos, setAllPhotos] = useState(); 7 | const [loading, setLoading] = useState(false); 8 | 9 | const userId = localStorage.getItem('userId'); 10 | 11 | const variables = { 12 | checkUserLike: userId, 13 | checkUserCollect: userId, 14 | first: 20, 15 | }; 16 | 17 | const { photos, fetchMore, hasNextPage } = usePhotos(variables); 18 | 19 | useEffect(async () => { 20 | if (photos) { 21 | const temp = photos && photos.edges 22 | ? photos.edges.map((edge) => edge.node) 23 | : []; 24 | 25 | setAllPhotos(temp); 26 | setLoading(false); 27 | } 28 | }, [photos]); 29 | 30 | const clickFetchMore = () => { 31 | fetchMore(); 32 | setLoading(true); 33 | }; 34 | 35 | return ( 36 |
37 | 44 |
45 | ); 46 | }; 47 | 48 | export default Latest; 49 | -------------------------------------------------------------------------------- /src/components/home/TypeList.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import usePhotos from '../../hooks/usePhotos'; 3 | import HomePhotoList from '../others/photo-list/HomePhotoList'; 4 | 5 | const TypeList = ({ type }) => { 6 | const [allPhotos, setAllPhotos] = useState(); 7 | const [loading, setLoading] = useState(false); 8 | 9 | const userId = localStorage.getItem('userId'); 10 | 11 | const variables = { 12 | checkUserLike: userId, 13 | checkUserCollect: userId, 14 | first: 20, 15 | searchKeyword: type, 16 | }; 17 | 18 | const { photos, fetchMore, hasNextPage } = usePhotos(variables); 19 | 20 | useEffect(async () => { 21 | if (photos) { 22 | const temp = photos && photos.edges 23 | ? photos.edges.map((edge) => edge.node) 24 | : []; 25 | 26 | setAllPhotos(temp); 27 | setLoading(false); 28 | } 29 | }, [photos]); 30 | 31 | const clickFetchMore = () => { 32 | fetchMore(); 33 | setLoading(true); 34 | }; 35 | 36 | return ( 37 |
38 | 45 |
46 | ); 47 | }; 48 | 49 | export default TypeList; 50 | -------------------------------------------------------------------------------- /src/components/others/CategoryBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CategoryCard from './photo-card/CategoryCard'; 3 | 4 | const CategoryBar = () => { 5 | const category = [{ 6 | id: 'YddPZXbgHkBRf4MVkDpXM', 7 | title: 'Photograph', 8 | cover: 'https://cdn.philoart.io/700x700/8/FS4MIHl-LbgSc25.jpg', 9 | }, 10 | { 11 | id: 'eEWmzhJf3hR7E0NiJU98l', 12 | title: 'Paintings', 13 | cover: 'https://cdn.philoart.io/c/700x700/zu3VmDeCCM55iPp2zZAVZ.jpg', 14 | }, 15 | { 16 | id: 'yibOLLFlIQtchyC6b5osL', 17 | title: 'Digital Art', 18 | cover: 'https://cdn.philoart.io/e/700x700/psYTmeZf1z6O2jukzPlyl.jpg', 19 | }]; 20 | 21 | return ( 22 |
23 |
24 |
25 | {category.map((collection) => ( 26 |
27 | 28 |
29 | ))} 30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | export default CategoryBar; 37 | -------------------------------------------------------------------------------- /src/components/others/LicenseButton.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; 3 | 4 | const options = ['CC BY', 'CC BY-SA', 'CC BY-NC', 'CC BY-NC-SA', 'CC-BY-ND', 'CC BY-NC-ND']; 5 | 6 | // eslint-disable-next-line no-unused-vars 7 | const LicenseButton = ({ setLicense }) => { 8 | const [selected, setSelected] = useState('CC BY-NC'); 9 | 10 | useEffect(() => { 11 | // setLicense(options[selectedIndex]); 12 | // setLicense(1); 13 | }, [selected]); 14 | 15 | const handleToggle = (option) => { 16 | setSelected(option); 17 | setLicense(option); 18 | }; 19 | 20 | return ( 21 |
22 |
23 | 27 |
28 | {options.map((option) => ( 29 | 32 | ))} 33 |
34 |
35 |
36 | ); 37 | }; 38 | 39 | export default LicenseButton; 40 | -------------------------------------------------------------------------------- /src/components/others/NavSearchBar.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-props-no-spreading */ 2 | import React from 'react'; 3 | import { useHistory } from 'react-router-dom'; 4 | import { Form } from 'react-bootstrap'; 5 | import useField from '../../hooks/useField'; 6 | 7 | const NavSearchBar = ({ placeholder }) => { 8 | const searchKeyword = useField('text'); 9 | const history = useHistory(); 10 | const handleSearch = async (event) => { 11 | event.preventDefault(); 12 | try { 13 | const query = searchKeyword.value.split(' '); 14 | 15 | if (query) { 16 | history.push(`/search?q=${query}`); 17 | } else history.push('/discover'); 18 | } catch (e) { 19 | // eslint-disable-next-line no-console 20 | console.log(e); 21 | } 22 | }; 23 | 24 | return ( 25 |
26 |
27 | 28 |
29 |
30 | ); 31 | }; 32 | 33 | export default NavSearchBar; 34 | -------------------------------------------------------------------------------- /src/components/others/PhotoRelatedTagBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TagBar from './TagBar'; 3 | 4 | const PhotoRelatedTagBar = ({ photo }) => { 5 | if (photo === undefined) return null; 6 | 7 | const tags1 = photo.tags; 8 | 9 | let tags = tags1.split(','); 10 | 11 | if (tags.length > 10) { 12 | tags = tags.slice(0, 10); 13 | } 14 | 15 | return ( 16 |
17 |
Related tags
18 | 19 |
20 | ); 21 | }; 22 | 23 | export default PhotoRelatedTagBar; 24 | -------------------------------------------------------------------------------- /src/components/others/RelatedTagBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TagBar from './TagBar'; 3 | 4 | const RelatedTagBar = ({ allPhotos }) => { 5 | if (allPhotos === undefined) return null; 6 | 7 | if (allPhotos.length === 0) { 8 | return ( 9 |
10 |

No result

11 |
12 | ); 13 | } 14 | 15 | const photo = allPhotos[1] || allPhotos[0]; 16 | const tags1 = photo.tags; 17 | 18 | let tags = tags1.split(','); 19 | 20 | if (tags.length > 10) { 21 | tags = tags.slice(0, 10); 22 | } 23 | 24 | return ( 25 |
26 |
Related tags
27 | 28 |
29 | ); 30 | }; 31 | 32 | export default RelatedTagBar; 33 | -------------------------------------------------------------------------------- /src/components/others/StatusButton.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; 3 | 4 | const options = ['None', 'Selling', 'Sold', 'Unavailable']; 5 | 6 | // eslint-disable-next-line no-unused-vars 7 | const StatusButton = ({ setStatus }) => { 8 | const [selected, setSelected] = useState('None'); 9 | 10 | useEffect(() => { 11 | // setLicense(options[selectedIndex]); 12 | // setLicense(1); 13 | }, [selected]); 14 | 15 | const handleToggle = (option) => { 16 | setSelected(option); 17 | setStatus(option); 18 | }; 19 | 20 | return ( 21 |
22 |
23 | 27 |
28 | {options.map((option) => ( 29 | 32 | ))} 33 |
34 |
35 |
36 | ); 37 | }; 38 | 39 | export default StatusButton; 40 | -------------------------------------------------------------------------------- /src/components/others/TagBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const TagBar = ({ tagToShow }) => { 5 | let tags = ['nature', 'animals', 'people', 'travel', 'food', 'sea', 'texture', 'interiors', 'art']; 6 | if (tagToShow) tags = tagToShow; 7 | 8 | return ( 9 |
10 |
11 |
12 | {tags.map((tag) => ( 13 |
14 | {tag} 15 |
16 | ))} 17 |
18 |
19 |
20 | ); 21 | }; 22 | 23 | export default TagBar; 24 | -------------------------------------------------------------------------------- /src/components/others/TextInput.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-props-no-spreading */ 2 | import React from 'react'; 3 | import { useField } from 'formik'; 4 | 5 | const TextInput = ({ label, info, ...props }) => { 6 | // useField() returns [formik.getFieldProps(), formik.getFieldMeta()] 7 | // which we can spread on . We can use field meta to show an error 8 | // message if the field is invalid and it has been touched (i.e. visited) 9 | const [field, meta] = useField(props); 10 | return ( 11 |
12 |
13 | 17 |
18 | 19 | {meta.error ? ( 20 |
{meta.error}
21 | ) : null} 22 | 23 |
24 | 25 |
26 | 27 |
28 | ); 29 | }; 30 | 31 | export default TextInput; 32 | -------------------------------------------------------------------------------- /src/components/others/TextInputDescription.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-props-no-spreading */ 2 | import React from 'react'; 3 | import { useField } from 'formik'; 4 | 5 | const TextInputDescription = ({ label, info, ...props }) => { 6 | // useField() returns [formik.getFieldProps(), formik.getFieldMeta()] 7 | // which we can spread on . We can use field meta to show an error 8 | // message if the field is invalid and it has been touched (i.e. visited) 9 | const [field, meta] = useField(props); 10 | return ( 11 |
12 |
13 | 17 |
18 | 19 | {meta.error ? ( 20 |
{meta.error}
21 | ) : null} 22 | 23 |
24 |