Repository: Mayank0255/Stackoverflow-Clone-Frontend Branch: master Commit: 488073c643c9 Files: 152 Total size: 197.2 KB Directory structure: gitextract_h2sxkswx/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── features-request-for-backend-or-frontend.md │ │ └── proposal.md │ └── workflows/ │ └── deploy-on-release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── package.json ├── public/ │ ├── index.html │ ├── manifest.json │ └── robots.txt └── src/ ├── App.css ├── App.js ├── Router.jsx ├── api/ │ ├── answersApi.js │ ├── authApi.js │ ├── commentsApi.js │ ├── postsApis.js │ ├── tagsApi.js │ ├── urls.js │ └── usersApi.js ├── components/ │ ├── Alert/ │ │ ├── Alert.component.jsx │ │ └── Alert.styles.scss │ ├── PageTitle/ │ │ └── PageTitle.component.jsx │ ├── atoms/ │ │ └── box.atom.jsx │ ├── molecules/ │ │ ├── BaseButton/ │ │ │ └── BaseButton.component.jsx │ │ ├── ButtonGroup/ │ │ │ └── ButtonGroup.component.jsx │ │ ├── LinkButton/ │ │ │ └── LinkButton.component.jsx │ │ ├── PostItem/ │ │ │ ├── PostItem.component.jsx │ │ │ └── PostItem.styles.scss │ │ ├── SearchBox/ │ │ │ ├── SearchBox.component.jsx │ │ │ └── SearchBox.styles.scss │ │ ├── Spinner/ │ │ │ ├── Spinner.component.jsx │ │ │ └── Spinner.styles.scss │ │ ├── TagBadge/ │ │ │ ├── TagBadge.component.jsx │ │ │ └── TagBadge.styles.scss │ │ └── UserCard/ │ │ ├── UserCard.component.jsx │ │ └── UserCard.styles.scss │ └── organisms/ │ ├── AuthForm/ │ │ ├── AuthForm.component.jsx │ │ └── AuthForm.styles.scss │ ├── Footer/ │ │ ├── Footer.component.jsx │ │ └── Footer.styles.scss │ ├── Header/ │ │ ├── Header.component.jsx │ │ └── Header.styles.scss │ ├── LayoutWrapper/ │ │ ├── LayoutWrapper.component.jsx │ │ ├── RightSideBar/ │ │ │ ├── RightSideBar.component.jsx │ │ │ ├── RightSideBar.styles.scss │ │ │ ├── SideBarWidget/ │ │ │ │ ├── SideBarWidget.component.jsx │ │ │ │ ├── SideBarWidget.styles.scss │ │ │ │ └── SideBarWidgetData.js │ │ │ └── TagsWidget/ │ │ │ ├── TagsWidget.component.jsx │ │ │ ├── TagsWidget.styles.scss │ │ │ ├── TagsWidgetItem.component.jsx │ │ │ └── TagsWidgetItem.styles.scss │ │ └── SideBar/ │ │ ├── SideBar.component.jsx │ │ ├── SideBar.styles.scss │ │ ├── SideBarData.js │ │ └── SideBarItem.component.jsx │ ├── MarkdownEditor/ │ │ ├── MarkdownEditor.component.jsx │ │ └── MarkdownEditor.styles.scss │ ├── MobileSideBar/ │ │ ├── MobileSideBar.component.jsx │ │ └── MobileSideBar.styles.scss │ └── Pagination/ │ └── Pagination.component.jsx ├── config/ │ └── index.js ├── hooks/ │ └── usePageTitle.jsx ├── index.js ├── modules/ │ ├── AllTagsPage/ │ │ ├── AllTagsPage.component.jsx │ │ ├── AllTagsPage.styles.scss │ │ └── TagPanel/ │ │ └── TagPanel.component.jsx │ ├── AllUsersPage/ │ │ ├── AllUsersPage.component.jsx │ │ ├── AllUsersPage.styles.scss │ │ └── UserPanel/ │ │ ├── UserPanel.component.jsx │ │ └── UserPanel.styles.scss │ ├── HomePage/ │ │ ├── HomePage.component.jsx │ │ └── HomePage.styles.scss │ ├── Login/ │ │ └── Login.component.jsx │ ├── NotFound/ │ │ ├── NotFound.component.jsx │ │ └── NotFound.styles.scss │ ├── Post/ │ │ ├── AnswerSection/ │ │ │ ├── AnswerForm/ │ │ │ │ ├── AnswerForm.component.jsx │ │ │ │ └── AnswerForm.styles.scss │ │ │ ├── AnswerItem/ │ │ │ │ ├── AnswerItem.component.jsx │ │ │ │ └── AnswerItem.styles.scss │ │ │ ├── AnswerSection.component.jsx │ │ │ └── AnswerSection.styles.scss │ │ ├── Post.component.jsx │ │ ├── Post.styles.scss │ │ └── QuestionSection/ │ │ ├── CommentCell/ │ │ │ ├── CommentCell.component.jsx │ │ │ └── CommentCell.styles.scss │ │ ├── PostCell/ │ │ │ ├── PostCell.component.jsx │ │ │ └── PostCell.styles.scss │ │ ├── QuestionSection.component.jsx │ │ ├── QuestionSection.styles.scss │ │ └── VoteCell/ │ │ ├── VoteCell.component.jsx │ │ └── VoteCell.styles.scss │ ├── PostForm/ │ │ ├── AskForm/ │ │ │ ├── AskForm.component.jsx │ │ │ └── AskForm.styles.scss │ │ ├── AskWidget/ │ │ │ ├── AskWidget.component.jsx │ │ │ └── AskWidget.styles.scss │ │ ├── PostForm.component.jsx │ │ └── PostForm.styles.scss │ ├── ProfilePage/ │ │ ├── ExternalUserDetails/ │ │ │ ├── ExternalUserDetails.component.jsx │ │ │ └── ExternalUserDetails.styles.scss │ │ ├── ProfilePage.component.jsx │ │ ├── ProfilePage.styles.scss │ │ ├── UserActivity/ │ │ │ ├── UserActivity.component.jsx │ │ │ └── UserActivity.styles.scss │ │ └── UserSection/ │ │ ├── AvatarCard/ │ │ │ ├── AvatarCard.component.jsx │ │ │ └── AvatarCard.styles.scss │ │ ├── ContentCard/ │ │ │ ├── ContentCard.component.jsx │ │ │ └── ContentCard.styles.scss │ │ ├── UserSection.component.jsx │ │ └── UserSection.styles.scss │ ├── QuestionsPage/ │ │ ├── QuestionsPage.component.jsx │ │ └── QuestionsPage.styles.scss │ ├── Register/ │ │ ├── Caption/ │ │ │ ├── Caption.component.jsx │ │ │ └── Caption.styles.scss │ │ ├── Register.component.jsx │ │ └── Register.styles.scss │ └── TagPage/ │ ├── TagPage.component.jsx │ └── TagPage.styles.scss ├── redux/ │ ├── alert/ │ │ ├── alert.actions.js │ │ ├── alert.reducer.js │ │ └── alert.types.js │ ├── answers/ │ │ ├── answers.actions.js │ │ ├── answers.reducer.js │ │ └── answers.types.js │ ├── auth/ │ │ ├── auth.actions.js │ │ ├── auth.reducer.js │ │ ├── auth.types.js │ │ └── auth.utils.js │ ├── comments/ │ │ ├── comments.actions.js │ │ ├── comments.reducer.js │ │ └── comments.types.js │ ├── posts/ │ │ ├── posts.actions.js │ │ ├── posts.reducer.js │ │ └── posts.types.js │ ├── root-reducer.js │ ├── store.js │ ├── tags/ │ │ ├── tags.actions.js │ │ ├── tags.reducer.js │ │ └── tags.types.js │ └── users/ │ ├── users.actions.js │ ├── users.reducer.js │ └── users.types.js └── utils/ ├── censorBadWords.js ├── handleFilter.js ├── handleSorting.js ├── htmlSubstring.js └── injectEllipsis.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [Mayank0255] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: "[BUG]: Write a descriptive title" labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/features-request-for-backend-or-frontend.md ================================================ --- name: Features Request for Backend or Frontend about: Suggest an idea for the project title: "[Backend/Frontend]: Write a descriptive idea title" labels: enhancement, feature assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/proposal.md ================================================ --- name: Proposal about: Propose a non-trivial change title: "[Proposal]: Write a descriptive title here" labels: '' assignees: '' --- ## Proposal (A clear and concise description of what the proposal is.) ================================================ FILE: .github/workflows/deploy-on-release.yml ================================================ name: "Deploy" on: release: types: - published push: branches: - dev workflow_dispatch: jobs: vercel: runs-on: ubuntu-latest name: "Deploy Front-End" steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: "14" registry-url: https://registry.npmjs.org/ - name: "Deploy to Vercel" run: | prodRun="" if [[ ${GITHUB_REF} == "refs/heads/main" ]]; then prodRun="--prod" fi npx vercel --token ${VERCEL_TOKEN} $prodRun env: VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} ================================================ FILE: .gitignore ================================================ /node_modules /.idea /.vscode /client/node_modules .env package-lock.json /client/package-lock.json yarn.lock .vercel ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mayank2aggarwal@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Stackoverflow Clone We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: - Reporting a bug - Discussing the current state of the code - Submitting a fix - Proposing new features - Becoming a maintainer ## Our Development Process We use GitHub to sync code to and from our internal repository. We'll use GitHub to track issues and feature requests, as well as accept pull requests. ## Pull Requests We actively welcome your pull requests. 1. Fork the repo and create your branch from `master`. 2. If you've added code that should be tested, then state about it in the PR description. 3. If you've changed APIs, update the documentation (in the readme file at the moment). 4. The PR title should begin with _(): _ e.g. - "feat(#12): ", "chore(#12): ", "fix(#12): ", "refactor(#12):" and "test(#12):" 5. Make sure your code satisfies the coding conventions used in the rest of the project. ## Issues We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. ## License By contributing, you agree that your contributions will be licensed under its MIT License. ## References This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Mayank Aggarwal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ https://user-images.githubusercontent.com/43780137/158059050-481ffa30-e415-4156-aea7-072c817f2ae2.mp4 [![Version](https://img.shields.io/static/v1?label=version&message=2.0.0&color=blue)](https://shields.io/) [![NPM](https://img.shields.io/static/v1?label=npm&message=6.8.5&color=blue)](https://shields.io/) [![NODE](https://img.shields.io/static/v1?label=node&message=10.12.8&color=success)](https://shields.io/) [![MYSQL](https://img.shields.io/static/v1?label=mysql&message=8.0.10&color=blueviolet)](https://shields.io/) [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://shields.io/) ### [🌐 Website](https://stackoverflow-clone-client.vercel.app) | [📹 Demo Video](https://www.youtube.com/watch?v=bUAAgfGOfYg) ### API Hosted On - __[stackoverflow-clone-api.onrender.com](https://stackoverflow-clone-api.onrender.com) (Primary)__ - __[stackoverflow-clone-backend.herokuapp.com](https://stackoverflow-clone-backend.herokuapp.com)__ As the name suggests, this project is a clone of a famous Q/A website for professional and enthusiast programmers built solely by me using a completely different stack. This repo consists of the Frontend code of the project, the backend code is in __[Stackoverflow-Clone-Backend](https://github.com/Mayank0255/Stackoverflow-Clone-Backend)__ ## My Tech Stack (MERN) #### Front-end - Front-end Framework: `React.js (with Redux)` - Styling: `SASS` and `BOOTSTRAP` #### Back-end - For handling index requests: `Node.js with Express.js Framework` - As Database: `MySQL with Sequelize` - API tested using: `POSTMAN` ## Guidelines to setup There are two ways to setup the project: manually or using the Dockerfile. Read below for more details: ### Manual Setup 1. Open your local CLI - ``` mkdir Stackoverflow-Clone cd Stackoverflow-Clone ``` 2. Setup the backend code - __NOTE:__ For Frontend Developers, if they dont want to setup the Backend Code, they can skip the Step 2, and make sure they follow the optional step mentioned in Step 4 - Create a `.env` file and the format should be as given in `.env.example`. - Clone the code & install the modules- ``` git clone https://github.com/Mayank0255/Stackoverflow-Clone-Backend.git cd Stackoverflow-Clone-Backend npm install ``` - Open your MySQL Client - ``` CREATE DATABASE stack_overflow; ``` NOTE: Don't forget to keep the database name same in the `.env` and here. - Run the index `npm start`. 3. Open a new CLI terminal and goto the root `Stackoverflow-Clone` folder you created in the first step. 4. Setup the Frontend code - - Clone the code & install the modules- ``` git clone https://github.com/Mayank0255/Stackoverflow-Clone-Frontend.git cd Stackoverflow-Clone-Frontend npm install ``` - Run the client index `npm start`. __OPTIONAL (Recommended For Frontend Developers):__ Can just change the path [here](https://github.com/Mayank0255/Stackoverflow-Clone-Frontend/blob/53b64c37981c618802547cd17483525532de83f0/src/config/index.js#L6) to this `https://stackoverflow-clone-backend.herokuapp.com` Now, it will hit PROD Let me know if you are interested and would want me to assign it to you ### Docker Setup The back-end has support for Docker. So if you want to run the back-end in a container, you need do: - Setup environment variables in `.env` file. Note when you use Docker setup and run the database in localhost (host machine), you need to setup the environment variables for use correct IP of MySQL Database. Please, read [here](https://docs.docker.com/compose/environment-variables/) and [here](https://docs.docker.com/desktop/windows/networking/) for more details. - Build the Docker image: ``` docker build -t stackoverflowclone . ``` - Run the container. For example, if you want to run the container in a new terminal, you can do: ``` docker run -d -p 5000:5000 stackoverflowclone ``` The default port of api is 5000. After running the container, you can access the api by typing: http://localhost:5000/api/ _Follow the steps properly (manual or Docker) and you are good to go._ ## Contributing - Go to `Contributing.md` ## DEMO #### VIDEO - [Watch the video](https://www.youtube.com/watch?v=bUAAgfGOfYg) _Video Last Updated on 7th March, 2022_ #### IMAGES ================================================ FILE: package.json ================================================ { "name": "client", "version": "0.1.0", "private": true, "dependencies": { "@emotion/react": "^11.8.1", "@emotion/styled": "^11.8.1", "@mui/icons-material": "^5.5.0", "@mui/material": "^5.5.0", "@stackoverflow/stacks-icons": "^2.25.1", "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", "axios": "^0.24.0", "bad-words": "^3.0.4", "feather-icons": "^4.28.0", "moment": "^2.29.4", "prop-types": "^15.8.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-helmet": "^6.1.0", "react-logger": "^1.1.0", "react-redux": "^7.2.6", "react-router-dom": "^5.3.0", "react-router-redux": "^4.0.8", "react-rte": "^0.16.4", "react-scripts": "^5.0.0", "redux": "^4.1.2", "redux-devtools-extension": "^2.13.9", "redux-persist": "^6.0.0", "redux-thunk": "^2.4.1", "reselect": "^4.1.5", "sass": "^1.45.2", "styled-system": "^5.1.5", "uuid": "^8.3.2" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ================================================ FILE: public/index.html ================================================ CLONE Stack Overflow - Where Developers Learn, Share, & Build Careers
================================================ FILE: public/manifest.json ================================================ { "short_name": "React App", "name": "Create React App Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "logo192.png", "type": "image/png", "sizes": "192x192" }, { "src": "logo512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: src/App.css ================================================ :root { --yellow-200: #675c37; --yellow-050: #464236; } *{ margin: 0; padding: 0; box-sizing: border-box; font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Liberation Sans",sans-serif !important; transition: all 300ms ease-in-out; } body { background-color: #2d2d2d !important; padding-top: 0 !important; } a { text-decoration: none !important; } button { margin: 3px; } ================================================ FILE: src/App.js ================================================ import React, {useEffect} from 'react'; import {Provider} from 'react-redux'; import {Switch} from 'react-router-dom'; import store from './redux/store'; import setAuthToken from './redux/auth/auth.utils'; import {loadUser} from './redux/auth/auth.actions'; import Header from './components/organisms/Header/Header.component'; import Alert from './components/Alert/Alert.component'; import HomePage from './modules/HomePage/HomePage.component'; import QuestionsPage from './modules/QuestionsPage/QuestionsPage.component'; import AllTagsPage from './modules/AllTagsPage/AllTagsPage.component'; import AllUsersPage from './modules/AllUsersPage/AllUsersPage.component'; import Register from './modules/Register/Register.component'; import Login from './modules/Login/Login.component'; import Post from './modules/Post/Post.component'; import PostForm from './modules/PostForm/PostForm.component'; import TagPage from './modules/TagPage/TagPage.component'; import ProfilePage from './modules/ProfilePage/ProfilePage.component'; import NotFound from './modules/NotFound/NotFound.component'; import { BaseRoute, LayoutRoute } from './Router'; import './App.css'; if (localStorage.token) { setAuthToken(localStorage.token); } const App = () => { useEffect(() => { store.dispatch(loadUser()); }, []); return (
); }; export default App; ================================================ FILE: src/Router.jsx ================================================ import React from 'react'; import { Route } from 'react-router-dom'; import LayoutWrapper from './components/organisms/LayoutWrapper/LayoutWrapper.component'; import usePageTitle from './hooks/usePageTitle'; export const LayoutRoute = ({ title, children, ...props }) => { usePageTitle(title); return ( {children} ) } export const BaseRoute = ({ title, children, ...props }) => { usePageTitle(title); return ( {children} ) } ================================================ FILE: src/api/answersApi.js ================================================ import axios from 'axios'; import { allAnswersData as _allAnswersData, createSingleAnswer as _createSingleAnswer, deleteSingleAnswer as _deleteSingleAnswer } from './urls'; export const allAnswersData = (id) => { return axios.get(_allAnswersData.replace('{id}', id)); } export const createSingleAnswer = (postId, formData) => { const config_headers = { headers: { "Content-Type": "application/json", }, }; return axios.post(_createSingleAnswer.replace('{postId}', postId), formData, config_headers); } export const deleteSingleAnswer = (AnswerId) => { return axios.delete(_deleteSingleAnswer.replace('{AnswerId}', AnswerId)); } ================================================ FILE: src/api/authApi.js ================================================ import axios from 'axios'; import {loadUserData as _loadUserData, registerUser as _registerUser, loginUser as _loginUser} from './urls'; export const loadUserData = () => { return axios.get(_loadUserData); }; export const registerUser = (username, password) => { const config_headers = { headers: { 'Content-Type': 'application/json', Accept: "application/json", }, }; const body = JSON.stringify({ username, password }); return axios.post(_registerUser, body, config_headers); }; export const loginUser = (username, password) => { const config_headers = { headers: { 'Content-Type': 'application/json', Accept: "application/json", }, }; const body = JSON.stringify({username, password}); return axios.post(_loginUser, body, config_headers); }; ================================================ FILE: src/api/commentsApi.js ================================================ import axios from 'axios'; import { allCommentsData as _allCommentsData, createSingleComment as _createSingleComment, deleteSingleComment as _deleteSingleComment } from './urls'; export const allCommentsData = (id) => { return axios.get(_allCommentsData.replace('{id}', id)); } export const createSingleComment = (postId, formData) => { const config_headers = { headers: { "Content-Type": "application/json", }, }; return axios.post(_createSingleComment.replace('{postId}', postId), formData, config_headers); } export const deleteSingleComment = (CommentId) => { return axios.delete(_deleteSingleComment.replace('{CommentId}', CommentId)); } ================================================ FILE: src/api/postsApis.js ================================================ import axios from 'axios'; import { allPostsData as _allPostsData, singlePostData as _singlePostData, allTagPostsData as _allTagPostsData, createSinglePost as _createSinglePost, deleteSinglePost as _deleteSinglePost } from './urls'; export const allPostsData = () => { return axios.get(_allPostsData); } export const singlePostData = (id) => { return axios.get(_singlePostData.replace('{id}', id)); } export const allTagPostsData = (tagName) => { return axios.get(_allTagPostsData.replace('{tagName}', tagName)); } export const createSinglePost = (formData) => { const config_headers = { headers: { "Content-Type": "application/json", }, }; return axios.post(_createSinglePost, formData, config_headers); } export const deleteSinglePost = (id) => { return axios.delete(_deleteSinglePost.replace('{id}', id)); } ================================================ FILE: src/api/tagsApi.js ================================================ import axios from 'axios'; import { allTagsData as _allTagsData, singleTagData as _singleTagData } from './urls'; export const allTagsData = () => { return axios.get(_allTagsData); } export const singleTagData = (tagName) => { return axios.get(_singleTagData.replace('{tagName}', tagName)); } ================================================ FILE: src/api/urls.js ================================================ import config from "../config"; // Users export const usersData = config.BASE_URL + '/api/users'; export const profileData = config.BASE_URL + '/api/users/{id}'; // Auth export const loadUserData = config.BASE_URL + '/api/auth'; export const registerUser = config.BASE_URL + '/api/users'; export const loginUser = config.BASE_URL + '/api/auth'; // Posts export const allPostsData = config.BASE_URL + '/api/posts'; export const singlePostData = config.BASE_URL + '/api/posts/{id}'; export const allTagPostsData = config.BASE_URL + '/api/posts/tag/{tagName}'; export const createSinglePost = config.BASE_URL + '/api/posts'; export const deleteSinglePost = config.BASE_URL + '/api/posts/{id}'; // Answers export const allAnswersData = config.BASE_URL + '/api/posts/answers/{id}'; export const createSingleAnswer = config.BASE_URL + '/api/posts/answers/{postId}'; export const deleteSingleAnswer = config.BASE_URL + '/api/posts/answers/{AnswerId}'; // Comments export const allCommentsData = config.BASE_URL + '/api/posts/comments/{id}'; export const createSingleComment = config.BASE_URL + '/api/posts/comments/{postId}'; export const deleteSingleComment = config.BASE_URL + '/api/posts/comments/{CommentId}'; // Tags export const allTagsData = config.BASE_URL + '/api/tags'; export const singleTagData = config.BASE_URL + '/api/tags/{tagName}'; ================================================ FILE: src/api/usersApi.js ================================================ import axios from 'axios'; import {usersData as _usersData, profileData as _profileData} from './urls'; export const usersData = () => { return axios.get(_usersData); }; export const profileData = (id) => { return axios.get(_profileData.replace('{id}', id)); }; ================================================ FILE: src/components/Alert/Alert.component.jsx ================================================ import React from 'react'; import PropTypes from 'prop-types'; import {connect} from 'react-redux'; import './Alert.styles.scss'; const Alert = ({ alerts }) => { return alerts.length > 0 && alerts.map((alert, index) => { if (alert.alertType === 'success') { return ( ) } else { return ( ) } } ) } Alert.propTypes = { alerts: PropTypes.array.isRequired, }; const mapStateToProps = (state) => ({ alerts: state.alert, }); export default connect(mapStateToProps)(Alert); ================================================ FILE: src/components/Alert/Alert.styles.scss ================================================ .alert { top: 75px; z-index: 10; position: fixed; width: 70%; max-width: 1000px; min-width: 320px; left: 50%; transform: translateX(-50%); } ================================================ FILE: src/components/PageTitle/PageTitle.component.jsx ================================================ import React from 'react'; import Helmet from 'react-helmet'; const PageTitle = ({title}) => { let defaultTitle = 'CLONE Stack Overflow - Where Developers Learn, Share, & Build Careers'; return ( {title ? title : defaultTitle} ); }; export default PageTitle; ================================================ FILE: src/components/atoms/box.atom.jsx ================================================ import styled from '@emotion/styled'; import { space, color, layout, flexbox, position, typography, border, background, } from 'styled-system'; export const Box = styled.div` box-sizing: border-box; min-width: 0; ${space} ${color} ${layout} ${flexbox} ${position} ${typography} ${border} ${background} `; export const FlexBox = styled(Box)` display: flex; `; export const FlexBoxColumn = styled(Box)` display: flex; flex-direction: column; `; export const GapFlexBox = styled(FlexBox)` gap: ${({ gap = "0" }) => gap}; `; export const GapFlexBoxColumn = styled(FlexBoxColumn)` gap: ${({ gap = "0" }) => gap}; `; ================================================ FILE: src/components/molecules/BaseButton/BaseButton.component.jsx ================================================ import React, {Fragment} from 'react'; const BaseButton = ({text, selected, onClick}) => { return ( ); }; export default BaseButton; ================================================ FILE: src/components/molecules/ButtonGroup/ButtonGroup.component.jsx ================================================ import React, {Fragment} from 'react'; import BaseButton from '../BaseButton/BaseButton.component'; const ButtonGroup = ({buttons, selected, setSelected}) => { return (
{buttons.map((button, index) => ( setSelected(button)} /> ))}
); }; export default ButtonGroup; ================================================ FILE: src/components/molecules/LinkButton/LinkButton.component.jsx ================================================ import React, {Fragment} from 'react'; import {Link} from 'react-router-dom'; const LinkButton = ({text, link, type, handleClick, marginTop}) => { return ( ); }; export default LinkButton; ================================================ FILE: src/components/molecules/PostItem/PostItem.component.jsx ================================================ import React from "react"; import { connect } from "react-redux"; import PropTypes from "prop-types"; import { Link } from "react-router-dom"; import censorBadWords from "../../../utils/censorBadWords"; import htmlSubstring from "../../../utils/htmlSubstring"; import injectEllipsis from "../../../utils/injectEllipsis"; import UserCard from "../UserCard/UserCard.component"; import TagBadge from "../TagBadge/TagBadge.component"; import "./PostItem.styles.scss"; const PostItem = ({ post: { id, title, body, username, gravatar, user_id, answer_count, comment_count, views, created_at, tags, }, }) => { const answerVoteUp = (
{answer_count}
answers
); const answerVoteDown = (
{answer_count}
answers
); return (
{comment_count}
comments
{answer_count > 0 ? answerVoteUp : answerVoteDown}
{tags.length}
tags
{views} views

{censorBadWords(title)}

{tags.map((tag, index) => ( ))}
); }; PostItem.propTypes = { post: PropTypes.object.isRequired, }; export default connect(null)(PostItem); ================================================ FILE: src/components/molecules/PostItem/PostItem.styles.scss ================================================ .posts { padding: 12px 8px 12px 8px; width: 100%; box-sizing: border-box; display: flex; border-bottom: 1px solid #4a4e51; .profile-tags { display: flex; } .stats { display: flex; flex-direction: column; flex-shrink: 0; flex-wrap: wrap; align-items: flex-end; } .stats-container { width: 58px; color: #6a737c; margin-left: 20px; font-size: 11px; } .vote { padding: 0; margin-bottom: 8px; text-align: center; display: flex; .vote-count { font-size: 14px; margin-right: 2px; } .count-text { font-size: 12px; } } .answer { border: 2px solid #63b47c; background-color: #63b47c; color: white; border-radius: 3px; padding: 4px; .vote-count { color: white; font-size: 12px; padding: 1px; } .count-text{ color: white; font-size: 12px; padding: 1px; } } .vote { padding: 0; margin-bottom: 8px; text-align: center; display: flex; .vote-count { font-size: 12px; margin-right: 2px; } .count-text { font-size: 12px; } .views { .count-text { font-size: 12px; color: #ffa600; } } } .summary { margin-left: 30px; width: 600px; h3 { font-weight: 400; font-size: 15px; line-height: 1.4; margin-bottom: 7.5px; a { color: #0077cc; line-height: 1.3; margin-bottom: 1.2em; font-size: 16px; text-decoration: none; &:hover { color: #0095ff; } } } .brief { padding: 0 0 5px 0; margin: 0; font-family: Arial, serif; font-size: 13px; } .question-user { width: 200px; line-height: 18px; float: right; .user-info { color: #848d95; padding: 5px 6px 7px 7px; .user-action-time { margin-bottom: 2px; margin-top: 1px; font-size: 12px; } .user-gravatar { float: left; width: 32px; height: 32px; border-radius: 1px; .logo-wrapper { padding: 0; overflow: hidden; img { width: 32px; height: 32px; } } } .user-details { margin-left: 40px; float: none; line-height: 17px; width: 80%; a { color: #0077cc; font-size: 12px; text-decoration: none; &:hover { color: #0095ff; } } } } } } } @media (max-width: 420px) { .owner .user-block .user-profile .user-profile-link { font-size: 11px; } } ================================================ FILE: src/components/molecules/SearchBox/SearchBox.component.jsx ================================================ import React, {Fragment} from 'react'; import {ReactComponent as Search} from '../../../assets/Search.svg'; const SearchBox = ({ placeholder, value, name, handleSubmit, handleChange, pt, px, width, }) => { return ( ); }; export default SearchBox; ================================================ FILE: src/components/molecules/SearchBox/SearchBox.styles.scss ================================================ .search-frame { width: 220px; //float: right; } .search-box:focus { border-color: #2b5f8a; box-shadow: 0 0 0 4px #378ad326; color: #fff; outline: 0; } ================================================ FILE: src/components/molecules/Spinner/Spinner.component.jsx ================================================ import React from 'react'; import {ReactComponent as PageSpinner} from '../../../assets/PageSpinner.svg'; import {ReactComponent as ComponentSpinner} from '../../../assets/three-dots.svg'; import './Spinner.styles.scss'; const Spinner = ({type, width, height}) => { return (
{type === 'page' ? : }
); }; export default Spinner; ================================================ FILE: src/components/molecules/Spinner/Spinner.styles.scss ================================================ .spinner { margin: auto; display: flex; justify-content: center; align-items: center; } ================================================ FILE: src/components/molecules/TagBadge/TagBadge.component.jsx ================================================ import React, {Fragment} from 'react'; import {Link} from 'react-router-dom'; import './TagBadge.styles.scss'; const TagBadge = ({tag_name, size, display, link, href}) => { return (
{href === true ? ( {tag_name} ) : ( {tag_name} )}
); }; export default TagBadge; ================================================ FILE: src/components/molecules/TagBadge/TagBadge.styles.scss ================================================ .tags-badge { color: #242729; line-height: 18px; margin-right: 4px; } ================================================ FILE: src/components/molecules/UserCard/UserCard.component.jsx ================================================ import React, {Fragment} from 'react'; import moment from 'moment'; import {Link} from 'react-router-dom'; import './UserCard.styles.scss'; const UserCard = ({ created_at, user_id, gravatar, username, dateType, float, backgroundColor, }) => { return (
{dateType ? dateType : 'asked'} {moment(created_at).fromNow(true)}{' '} ago
user_logo
{username}
); }; export default UserCard; ================================================ FILE: src/components/molecules/UserCard/UserCard.styles.scss ================================================ .owner { margin-top: 4px; margin-bottom: 4px; border-radius: 3px; background-color: #3e4a52; text-align: left; vertical-align: top; width: 200px; .user-block { box-sizing: border-box; padding: 5px 6px 0 7px; color: #6a737c; .action-time { margin-top: 1px; margin-bottom: 4px; font-size: 12px; white-space: nowrap; } .user-logo { float: left; width: 32px; height: 32px; border-radius: 1px; margin-bottom: 6px; .user-link { color: #0077cc; text-decoration: none; cursor: pointer; .logo-wrapper { width: 32px; height: 32px; padding: 0; overflow: hidden; img { width: 32px; height: 32px; border-radius: 3px !important; } } } } .user-profile { margin-left: 8px; width: calc(100% - 40px); float: left; line-height: 17px; word-wrap: break-word; .user-profile-link { color: #0077cc; text-decoration: none; cursor: pointer; font-size: 14px; &:hover { color:#0095ff; } } } } } ================================================ FILE: src/components/organisms/AuthForm/AuthForm.component.jsx ================================================ import React, {Fragment, useState} from 'react'; import {Link} from 'react-router-dom'; import {connect} from 'react-redux'; import PropTypes from 'prop-types'; import {login} from '../../../redux/auth/auth.actions'; import {register} from '../../../redux/auth/auth.actions'; import {ReactComponent as Logo} from '../../../assets/LogoGlyphMd.svg'; import {ReactComponent as ExternalLink} from '../../../assets/ExternalLink.svg'; import './AuthForm.styles.scss'; const AuthForm = ({register, login, action}) => { const [formData, setFormData] = useState({ username: '', password: '', }); const {username, password} = formData; const onChange = (e) => setFormData({...formData, [e.target.name]: e.target.value}); const onSubmit = async (e) => { e.preventDefault(); if (action === 'Sign up') { register({username, password}); } else { login({username, password}); } }; const signUpLink = ( Already have an account?{' '} Log in ); const logInLink = ( Don't have an account?{' '} Sign up ); return (
onSubmit(e)}>
onChange(e)} id='username' required />
onChange(e)} id='password' required />
By clicking “{action}”, you agree to our{' '} terms of service ,{' '} privacy policy {' '} and{' '} cookie policy
{action === 'Sign up' ? signUpLink : logInLink}
Are you an employer?{' '} Sign up on Talent{' '}
); }; AuthForm.propTypes = { register: PropTypes.func.isRequired, login: PropTypes.func.isRequired, isAuthenticated: PropTypes.bool, }; const mapStateToProps = (state) => ({ isAuthenticated: state.auth.isAuthenticated, }); export default connect(mapStateToProps, {login, register})(AuthForm); ================================================ FILE: src/components/organisms/AuthForm/AuthForm.styles.scss ================================================ .form-container { width: 320px; box-shadow: 0 10px 25px rgba(0,0,0,0.05), 0 20px 48px rgba(0,0,0,0.05), 0 1px 4px rgba(0,0,0,0.1); padding: 24px; margin-left: auto; margin-right: auto; margin-bottom: 24px; background-color: #2d2d2d; border-radius: 7px; box-sizing: inherit; display: block; div { margin: 6px 0; button { margin: 5px 0 3px 0; width: 100%; } } .fs-caption { color:#6a737c; font-size: 12px; } .license { margin-top: 32px; } .form-label { font-weight: 600; } } .icon-holder { text-align: center; margin-bottom: 15px; .icon { width: 45px; height: 45px; } } .redirects { padding: 16px 16px 0 16px; text-align: center; font-size: 13px; margin-bottom: 24px; } ================================================ FILE: src/components/organisms/Footer/Footer.component.jsx ================================================ import React, { Fragment } from "react"; import {ReactComponent as GitHub} from "../../../assets/GitHub.svg"; import {ReactComponent as Database} from "../../../assets/Database.svg"; import './Footer.styles.scss'; const Footer = () => { return

Frontend

Backend

}; export default Footer; ================================================ FILE: src/components/organisms/Footer/Footer.styles.scss ================================================ .footer { height: 300px; display: flex; justify-content: center; padding-top: 32px; background-color: #232629; .socials { display: flex; justify-content: space-between; width: 120px; .social-item { display: flex; flex-direction: column; align-items: center; } } } ================================================ FILE: src/components/organisms/Header/Header.component.jsx ================================================ import React, {Fragment, useState} from 'react'; import {Link, useHistory} from 'react-router-dom'; import {connect} from 'react-redux'; import PropTypes from 'prop-types'; import { logout } from '../../../redux/auth/auth.actions'; import {ReactComponent as Search} from '../../../assets/Search.svg'; import {ReactComponent as Logo} from '../../../assets/LogoMd.svg'; import {ReactComponent as SmallLogo} from '../../../assets/LogoGlyphMd.svg'; import Spinner from '../../molecules/Spinner/Spinner.component'; import LinkButton from '../../molecules/LinkButton/LinkButton.component'; import MobileSideBar from '../../organisms/MobileSideBar/MobileSideBar.component'; import './Header.styles.scss'; const Header = ({auth: {isAuthenticated, loading, user}, logout}) => { let history = useHistory(); const [searchState, setSearchState] = useState(false); const authLinks = (
{loading || user === null ? ( ) : ( user-logo )}
); const authTabs = (
Products
); const guestTabs = (
Products Customers Use cases
); const guestLinks = (
); const SearchBar = () => { return (
history.push('/questions')} className='small-search-form' autoComplete='off' > ); } return loading ? ( '' ) : ( {searchState && } ); }; Header.propTypes = { logout: PropTypes.func.isRequired, auth: PropTypes.object.isRequired, }; const mapStateToProps = (state) => ({ auth: state.auth, }); export default connect(mapStateToProps, {logout})(Header); ================================================ FILE: src/components/organisms/Header/Header.styles.scss ================================================ .navbar { height: 63px; border-top: 3px solid #f48024; padding: 3px 3px 0 0; box-shadow: 5px 2px rgba(0,0,0,0.1); z-index: 1000; background-color: #3d3d3d; display: flex; justify-content: space-between; } .fixed-top { position: fixed; top: 0; right: 0; left: 0; } .s-navigation { padding: 2px 2px; } .s-navigation .not-selected { color: #c4c8cc; &:hover { background-color: #404345; color: #f2f2f3; text-decoration: none; outline: none; } } * { box-sizing: border-box; } .navbar-brand { margin-left: 90px; padding-left: 16px; padding-right: 16px; margin-right: 0; &:hover { background-color: #404345; } } .btns { margin-right: 140px; display: flex; justify-content: center; align-items: center; .logo { width: 32px; border-radius: 3px; margin-right: 9px; } } .btn-sm { padding: 5px 10px; } .btn-outline-primary { background-color: #e1ecf4; color: #39739d; border-color: #7aa7c7; } .btn-outline-primary:hover { color: #2c5777; background-color: #b3d3ea; border-color: #7aa7c7; } .bar-items { font-size: 14px; color: rgba(0, 0, 0, 0.6); margin: 0 10px; } .px12 { padding-left: 12px !important; padding-right: 12px !important; } .header-brand-div { display: flex; align-items: center; } .header-search-div { display: flex; justify-content: flex-end; align-items: center } .glyph-logo { display: none; } .search-icon { display: none; filter: invert(1); &:hover { cursor: pointer; } } .small-search { width: 98vw; position: fixed; top: 69px; left: 5px; border-radius: 8px; } .small-search-form { position: fixed; display: flex; z-index: 1000; padding: 2rem; justify-content: center; align-items: center; width: 100vw; background-color: #00000042; box-shadow: 2px 2px 8px 3px gray; } .small-search-icon { position: fixed; top: 80px; right: 5%; } .s-input__search{ margin-top: 10px; } .s-input-icon { margin-top: -2px; } // Side Navbar .hamburger { display: none; } /////////////// @media(max-width: 1280px) { .navbar-brand { margin-left: 60px; } } @media(max-width: 1200px) { .btns { margin-right: 30px; } } @media (max-width:986px) { .navbar-brand { margin-left: 20px ; } .btns { margin-right: 20px; } } @media (max-width: 877px){ .navbar-brand { margin-left: 0; } .header-brand-div { justify-content: flex-start; padding: 0 1.5rem; } .header-search-div { justify-content: space-around; } .searchbar { display: none; } .search-icon { display: block; margin-right: 1rem; position: absolute; left: 77%; top: 30%; } .btns { margin-right: 20px; } .s-input__search { max-width: 250px; } } @media (max-width: 715px) { .glyph-logo { display: block; } .full-logo { display: none; } .search-icon { display: block; position: relative; left: 15%; top: 30%; } } @media (max-width: 560px) { .glyph-logo { display: block; } .full-logo { display: none; } .s-navigation .s-navigation--item { display: none; &:first-of-type { display: inline; } } .search-icon { display: block; position: relative; left: 5%; top: 30%; } .hamburger { display: block; padding-top: 6px; } .header-search-div, .header-brand-div { transform: scale(0.8); } } @media (max-width: 420px) { .glyph-logo{ display: none; } .s-navigation .s-navigation--item { &:first-of-type { margin-left: -4.7rem; } } .search-icon { display: block; position: relative; left: 5%; top: 30%; } } @media (max-width: 390px) { .navbar { padding: 0; } } @media (max-width: 345px) { .glyph-logo { display: none; } } ================================================ FILE: src/components/organisms/LayoutWrapper/LayoutWrapper.component.jsx ================================================ import React, {Fragment} from 'react'; import SideBar from './SideBar/SideBar.component'; import RightSideBar from './RightSideBar/RightSideBar.component'; import Footer from "../Footer/Footer.component"; const LayoutWrapper = ({ children }) => { return (
{children}