├── src ├── utils │ ├── index.js │ └── time.js ├── Assets │ ├── images │ │ ├── screenshot.png │ │ ├── icon-delete.svg │ │ ├── icon-minus.svg │ │ ├── icon-reply.svg │ │ ├── icon-edit.svg │ │ └── icon-plus.svg │ ├── design │ │ ├── active-states.jpg │ │ ├── desktop-modal.jpg │ │ ├── mobile-design.jpg │ │ ├── mobile-modal.jpg │ │ ├── desktop-design.jpg │ │ └── desktop-preview.jpg │ └── avatars │ │ ├── image-amyrobson.png │ │ ├── image-juliusomo.png │ │ ├── image-maxblagun.png │ │ └── image-ramsesmiron.png ├── index.js ├── Components │ ├── commentParts │ │ ├── index.js │ │ ├── CommentFooter.js │ │ ├── CommentVotes.js │ │ ├── CommentHeader.js │ │ └── CommentBtn.js │ ├── Styles │ │ ├── _mixins.scss │ │ ├── App.scss │ │ ├── _variables.scss │ │ ├── index.scss │ │ ├── DeleteModal.scss │ │ ├── AddComment.scss │ │ └── Comment.scss │ ├── ReplyContainer.js │ ├── DeleteModal.js │ ├── AddComment.js │ ├── Reply.js │ └── Comment.js └── App.js ├── public ├── robots.txt ├── favicon.ico ├── manifest.json ├── data │ └── data.json └── index.html ├── .gitignore ├── package.json └── README.md /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { commentPostedTime } from "./time"; 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/Assets/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/images/screenshot.png -------------------------------------------------------------------------------- /src/Assets/design/active-states.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/active-states.jpg -------------------------------------------------------------------------------- /src/Assets/design/desktop-modal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/desktop-modal.jpg -------------------------------------------------------------------------------- /src/Assets/design/mobile-design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/mobile-design.jpg -------------------------------------------------------------------------------- /src/Assets/design/mobile-modal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/mobile-modal.jpg -------------------------------------------------------------------------------- /src/Assets/avatars/image-amyrobson.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/avatars/image-amyrobson.png -------------------------------------------------------------------------------- /src/Assets/avatars/image-juliusomo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/avatars/image-juliusomo.png -------------------------------------------------------------------------------- /src/Assets/avatars/image-maxblagun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/avatars/image-maxblagun.png -------------------------------------------------------------------------------- /src/Assets/design/desktop-design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/desktop-design.jpg -------------------------------------------------------------------------------- /src/Assets/design/desktop-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/design/desktop-preview.jpg -------------------------------------------------------------------------------- /src/Assets/avatars/image-ramsesmiron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshGoyalDev/interactive-comments-section/HEAD/src/Assets/avatars/image-ramsesmiron.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | 3 | import './Components/Styles/index.scss'; 4 | 5 | import App from './App'; 6 | 7 | const container = document.getElementById('root'); 8 | const root = createRoot(container); 9 | root.render(); -------------------------------------------------------------------------------- /src/Assets/images/icon-delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Components/commentParts/index.js: -------------------------------------------------------------------------------- 1 | import CommentHeader from "./CommentHeader"; 2 | import CommentFooter from "./CommentFooter"; 3 | import CommentBtn from "./CommentBtn"; 4 | import CommentVotes from "./CommentVotes"; 5 | 6 | export { CommentHeader, CommentFooter, CommentBtn, CommentVotes }; 7 | -------------------------------------------------------------------------------- /src/Components/Styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin media-sm() { 2 | @media (min-width: 26.25em) { 3 | @content; 4 | } 5 | } 6 | 7 | 8 | @mixin media-md() { 9 | @media (min-width: 47em) { 10 | @content; 11 | } 12 | } 13 | 14 | @mixin support($value) { 15 | @supports (#{$value}) { 16 | @content; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Assets/images/icon-minus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Assets/images/icon-reply.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Assets/images/icon-edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Components/Styles/App.scss: -------------------------------------------------------------------------------- 1 | @use "variables" as *; 2 | @use "mixins" as *; 3 | 4 | .App { 5 | min-height: 100vh; 6 | display: flex; 7 | flex-direction: column; 8 | justify-content: center; 9 | align-items: center; 10 | gap: 15px; 11 | padding: 2rem 0; 12 | background-color: $very-light-gray; 13 | 14 | @include media-md() { 15 | gap: 20px; 16 | padding: 4rem 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.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.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # assets 26 | /Assets/design -------------------------------------------------------------------------------- /src/Assets/images/icon-plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "favicon.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "favicon.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 | -------------------------------------------------------------------------------- /src/Components/Styles/_variables.scss: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;700&display=swap"); 2 | 3 | /* primary */ 4 | $moderate-blue: hsl(238, 40%, 52%); 5 | $soft-red: hsl(358, 79%, 66%); 6 | $light-grayish-blue: hsl(239, 57%, 85%); 7 | $pale-red: hsl(357, 100%, 86%); 8 | 9 | /* neutral */ 10 | $dark-blue: hsl(212, 24%, 26%); 11 | $grayish-blue: hsl(211, 10%, 45%); 12 | $light-gray: hsl(223, 19%, 93%); 13 | $very-light-gray: hsl(228, 33%, 97%); 14 | $white: hsl(0, 0%, 100%); 15 | 16 | /* typography */ 17 | $ff-rubik: "Rubik", sans-serif; 18 | $fw-regular: 400; 19 | $fw-medium: 500; 20 | $fw-bold: 700; 21 | -------------------------------------------------------------------------------- /src/Components/ReplyContainer.js: -------------------------------------------------------------------------------- 1 | import Reply from "./Reply"; 2 | 3 | const ReplyContainer = ({ 4 | commentData, 5 | updateScore, 6 | addReply, 7 | editComment, 8 | deleteComment, 9 | setDeleteModalState, 10 | }) => { 11 | return ( 12 |
13 | {commentData.map((data) => ( 14 | 23 | ))} 24 |
25 | ); 26 | }; 27 | 28 | export default ReplyContainer; 29 | -------------------------------------------------------------------------------- /src/Components/commentParts/CommentFooter.js: -------------------------------------------------------------------------------- 1 | import { CommentVotes, CommentBtn } from "./"; 2 | 3 | const CommentFooter = ({ 4 | updateScore, 5 | commentData, 6 | setReplying, 7 | setDeleting, 8 | setDeleteModalState, 9 | setEditing, 10 | type, 11 | }) => { 12 | return ( 13 |
14 | 19 | 20 | 27 |
28 | ); 29 | }; 30 | 31 | export default CommentFooter; 32 | -------------------------------------------------------------------------------- /src/utils/time.js: -------------------------------------------------------------------------------- 1 | const commentPostedTime = (timeInMileSec) => { 2 | const sec = (timeInMileSec / 1000).toFixed(0); 3 | const min = (timeInMileSec / (1000 * 60)).toFixed(0); 4 | const hrs = (timeInMileSec / (1000 * 60 * 60)).toFixed(0); 5 | const days = (timeInMileSec / (1000 * 60 * 60 * 24)).toFixed(0); 6 | const weeks = (timeInMileSec / (1000 * 60 * 60 * 24 * 7)).toFixed(0); 7 | const months = (timeInMileSec / (1000 * 60 * 60 * 24 * 31)).toFixed(0); 8 | const years = (timeInMileSec / (1000 * 60 * 60 * 24 * 12)).toFixed(0); 9 | 10 | if (sec < 60) { 11 | return "seconds"; 12 | } else if (min < 60) { 13 | return min + " mins"; 14 | } else if (hrs < 24) { 15 | return hrs + " hrs"; 16 | } else if (days < 7) { 17 | return days + " days"; 18 | } else if (weeks < 4) { 19 | return weeks + " weeks"; 20 | } else if (months < 12) { 21 | return months + " months"; 22 | } else { 23 | return years + " year"; 24 | } 25 | }; 26 | 27 | export { commentPostedTime }; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interactive-comments-section", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.1", 7 | "@testing-library/react": "^12.1.2", 8 | "@testing-library/user-event": "^13.5.0", 9 | "node": "^17.3.0", 10 | "react": "^18.1.0", 11 | "react-dom": "^18.1.0", 12 | "react-scripts": "5.0.0", 13 | "sass": "^1.45.1", 14 | "web-vitals": "^2.1.2" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Components/DeleteModal.js: -------------------------------------------------------------------------------- 1 | import "./Styles/DeleteModal.scss"; 2 | 3 | const DeleteModal = ({ setDeleting, deleteComment, setDeleteModalState }) => { 4 | const cancelDelete = () => { 5 | setDeleting(false); 6 | setDeleteModalState(false); 7 | }; 8 | 9 | const deleteBtnClick = () => { 10 | deleteComment(); 11 | setDeleteModalState(false); 12 | }; 13 | 14 | return ( 15 |
16 |
17 |
Delete comment
18 |
19 | Are you sure you want to delete this comment? This will remove the 20 | comment and can't be undone. 21 |
22 |
23 | 26 | 29 |
30 |
31 |
32 | ); 33 | }; 34 | 35 | export default DeleteModal; 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interactive comments section 2 | 3 | ![](./src/Assets/images/screenshot.png) 4 | 5 | ## The challenge 6 | 7 | - optimal layout for the app depending on their device's screen size. 8 | - hover states for all interactive elements on the page. 9 | - create, Read, Update, and Delete comments and replies. 10 | - up-vote and down-vote comments. 11 | - tracks the time dynamically since the comment or reply was posted. 12 | 13 | ### Information 14 | - this is a solution to the [Interactive comments section challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/interactive-comments-section-iG1RugEG9). 15 | - by [Frontend Mentor](https://www.frontendmentor.io) 16 | 17 | ## Built with 18 | 19 | - CSS custom properties 20 | - Flex-box 21 | - Mobile-first workflow 22 | - [SCSS](https://sass-lang.com) - CSS Preprocessor 23 | - [React](https://reactjs.org/) - JS library 24 | 25 | ## What I learned 26 | 27 | - I learned how to calculate the time between current time and given time. 28 | 29 | ## Continued development 30 | 31 | - I still want to add the feature : 32 | - which sorts the comments the comment of the basis of their votes 33 | 34 | ## Author 35 | 36 | - Frontend Mentor - [@arshGoyalDev](https://www.frontendmentor.io/profile/arshGoyalDev) 37 | - Twitter - [@arshGoyalDDev](https://twitter.com/arshGoyalDev) 38 | -------------------------------------------------------------------------------- /src/Components/AddComment.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | import "./Styles/AddComment.scss"; 4 | 5 | const AddComment = ({ buttonValue, addComments, replyingTo }) => { 6 | const replyingToUser = replyingTo ? `@${replyingTo}, ` : ""; 7 | const [comment, setComment] = useState(replyingToUser); 8 | 9 | const clickHandler = () => { 10 | if (comment === "" || comment === " ") return; 11 | 12 | const newComment = { 13 | id: Math.floor(Math.random() * 100) + 5, 14 | content: replyingToUser + comment.replace(replyingToUser, ""), 15 | createdAt: new Date(), 16 | score: 0, 17 | username: "juliusomo", 18 | currentUser: true, 19 | replies: [], 20 | }; 21 | 22 | addComments(newComment); 23 | setComment(""); 24 | }; 25 | 26 | return ( 27 |
28 |
29 |