Showing preview only (235K chars total). Download the full file or copy to clipboard to get everything.
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 _<action>(<issue_number>): _ 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
[](https://shields.io/)
[](https://shields.io/)
[](https://shields.io/)
[](https://shields.io/)
[](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/<endpoint that you request - see next section>
_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
<img src="/demo/images/1.png" width=340px /><img src="/demo/images/2.png" width=340px />
<img src="/demo/images/3.png" width=340px /><img src="/demo/images/4.png" width=340px />
<img src="/demo/images/5.png" width=340px /><img src="/demo/images/6.png" width=340px />
<img src="/demo/images/7.png" width=340px /><img src="/demo/images/8.png" width=340px />
<img src="/demo/images/9.png" width=340px /><img src="/demo/images/10.png" width=340px />
<img src="/demo/images/11.png" width=340px /><img src="/demo/images/12.png" width=340px />
================================================
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
================================================
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<link rel='icon' href='%PUBLIC_URL%/LogoGlyphMd.svg' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<meta name='theme-color' content='#000000' />
<meta
name='description'
content='Web site created using create-react-app'
/>
<title>CLONE Stack Overflow - Where Developers Learn, Share, & Build Careers</title>
<link rel='stylesheet' type='text/css' href='https://cdn.sstatic.net/Shared/stacks.css?v=0ee8a05683e7'>
<link rel='stylesheet' type='text/css' href='https://cdn.sstatic.net/Sites/stackoverflow/primary.css?v=c5fdf309f06b'>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>
</head>
<body class='theme-dark'>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id='root'></div>
<script src='https://unpkg.com/@stackoverflow/stacks-icons'></script>
<script src='https://code.jquery.com/jquery-3.2.1.slim.min.js' integrity='sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN' crossorigin='anonymous'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js' integrity='sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q' crossorigin='anonymous'></script>
<script>
let pageX = $(document).width();
let pageY = $(document).height();
let mouseY=0;
let mouseX=0;
$(document).mousemove(function( event ) {
mouseY = event.pageY;
const yAxis = (pageY / 2 - mouseY) / pageY * 300;
mouseX = event.pageX / -pageX;
const xAxis = -mouseX * 100 - 100;
$('.box__ghost-eyes')
.css({ 'transform': 'translate('+ xAxis +'%,-'+ yAxis +'%)' });
});
</script>
</body>
</html>
================================================
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 (
<Provider store={store}>
<div className='App'>
<Header />
<Alert />
<Switch>
<LayoutRoute
exact
path='/'
title='CLONE Stack Overflow - Where Developers Learn, Share, & Build Careers'
>
<HomePage/>
</LayoutRoute>
<LayoutRoute
exact
path='/questions'
title='All Questions - CLONE Stack Overflow'
>
<QuestionsPage/>
</LayoutRoute>
<LayoutRoute
exact
path='/tags'
title='Tags - CLONE Stack Overflow'
>
<AllTagsPage/>
</LayoutRoute>
<LayoutRoute
exact
path='/users'
title='Users - CLONE Stack Overflow'
>
<AllUsersPage/>
</LayoutRoute>
<BaseRoute
exact
path='/register'
title='Sign Up - CLONE Stack Overflow'
>
<Register/>
</BaseRoute>
<BaseRoute
exact
path='/login'
title='Log In - CLONE Stack Overflow'
>
<Login/>
</BaseRoute>
<LayoutRoute
exact
path='/questions/:id'
title='Users - CLONE Stack Overflow'
>
<Post/>
</LayoutRoute>
<LayoutRoute
exact
path='/users/:id'
title='Users - CLONE Stack Overflow'
>
<ProfilePage/>
</LayoutRoute>
<LayoutRoute
exact
path='/tags/:tagname'
title='Users - CLONE Stack Overflow'
>
<TagPage/>
</LayoutRoute>
<BaseRoute
exact
path='/add/question'
title='Ask a Question - CLONE Stack Overflow'
>
<PostForm/>
</BaseRoute>
<BaseRoute
path='*'
title='Error 404'
>
<NotFound/>
</BaseRoute>
</Switch>
</div>
</Provider>
);
};
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 (
<Route {...props}>
<LayoutWrapper>
{children}
</LayoutWrapper>
</Route>
)
}
export const BaseRoute = ({ title, children, ...props }) => {
usePageTitle(title);
return (
<Route {...props}>
{children}
</Route>
)
}
================================================
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 (
<aside key={index} className="alert s-notice s-notice__success s-notice__important" role="alert">
{alert.msg}
</aside>
)
} else {
return (
<aside key={index} className="alert s-notice s-notice__danger s-notice__important" role="alert">
{alert.msg}
</aside>
)
}
}
)
}
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 (
<Helmet>
<title>{title ? title : defaultTitle}</title>
</Helmet>
);
};
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 (
<Fragment>
<button
className={`s-btn s-btn__filled ${
selected === text ? 'is-selected' : ''
}`}
style={{margin: '0'}}
onClick={onClick}
>
{text}
</button>
</Fragment>
);
};
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 (
<Fragment>
<div className='grid--cell'>
<div className=' grid s-btn-group js-filter-btn'>
{buttons.map((button, index) => (
<BaseButton
key={index}
text={button}
selected={selected}
onClick={() => setSelected(button)}
/>
))}
</div>
</div>
</Fragment>
);
};
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 (
<Fragment>
<Link onClick={handleClick} to={link}>
<button className={`s-btn ${type}`} style={{marginTop}}>
{text}
</button>
</Link>
</Fragment>
);
};
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 = (
<div className="vote answer">
<span className="vote-count">{answer_count}</span>
<div className="count-text">answers</div>
</div>
);
const answerVoteDown = (
<div className="vote">
<span className="vote-count">{answer_count}</span>
<div className="count-text">answers</div>
</div>
);
return (
<div className="posts">
<div className="stats-container fc-black-500">
<div className="stats">
<div className="vote">
<span className="vote-count">{comment_count}</span>
<div className="count-text">comments</div>
</div>
{answer_count > 0 ? answerVoteUp : answerVoteDown}
<div className="vote">
<span className="vote-count">{tags.length}</span>
<div className="count-text">tags</div>
</div>
<div className="vote">
<div className="count-text">{views} views</div>
</div>
</div>
</div>
<div className="summary">
<h3>
<Link to={`/questions/${id}`}>{censorBadWords(title)}</Link>
</h3>
<div
className="brief"
dangerouslySetInnerHTML={{
__html: injectEllipsis(censorBadWords(htmlSubstring(body, 200))),
}}
></div>
<div className="profile-tags">
{tags.map((tag, index) => (
<TagBadge key={index} tag_name={tag.tagname} size={"s-tag"} />
))}
</div>
<UserCard
created_at={created_at}
user_id={user_id}
gravatar={gravatar}
username={username}
float={"right"}
backgroundColor={"transparent"}
/>
</div>
</div>
);
};
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 (
<Fragment>
<form
id='search'
onSubmit={handleSubmit}
className={`grid--cell fl-grow1 searchbar ${pt} ${px} js-searchbar`}
autoComplete='off'
>
<div className='ps-relative search-frame' style={{width}}>
<input
className='s-input s-input__search h100 search-box'
autoComplete='off'
type='text'
name={name}
maxLength='35'
placeholder={placeholder}
onChange={handleChange}
value={value}
/>
<Search />
</div>
</form>
</Fragment>
);
};
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 (
<div className='spinner' style={{width: `${width}`, height: `${height}`}}>
{type === 'page' ? <PageSpinner /> : <ComponentSpinner />}
</div>
);
};
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 (
<Fragment>
<div className='tags-badge' style={{ display }}>
{href === true ? (
<Link className={`${size}`} to={link ? link : `/tags/${tag_name}`}>
{tag_name}
</Link>
) : (
<Link className={`${size}`} to={link ? link : `/tags/${tag_name}`}>
{tag_name}
</Link>
)}
</div>
</Fragment>
);
};
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 (
<Fragment>
<div
className='owner'
style={{float: float, backgroundColor: backgroundColor}}
>
<div className='user-block fc-black-500'>
<div className='action-time'>
{dateType ? dateType : 'asked'} {moment(created_at).fromNow(true)}{' '}
ago
</div>
<div className='user-logo'>
<Link className='user-link' to={`/users/${user_id}`}>
<div className='logo-wrapper'>
<img
alt='user_logo'
src={gravatar}
/>
</div>
</Link>
</div>
<div className='user-profile'>
<Link
className='user-profile-link fc-blue-600'
to={`/users/${user_id}`}
>
{username}
</Link>
</div>
</div>
</div>
</Fragment>
);
};
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 = (
<Fragment>
Already have an account?{' '}
<Link to='/login' name='login'>
Log in
</Link>
</Fragment>
);
const logInLink = (
<Fragment>
Don't have an account?{' '}
<Link to='/register' name='register'>
Sign up
</Link>
</Fragment>
);
return (
<Fragment>
<div>
<div className='icon-holder'>
<Logo className='icon' />
</div>
<div className='form-container'>
<form className='login-form' onSubmit={(e) => onSubmit(e)}>
<div>
<label className='form-label s-label fc-black-600'>
Username
</label>
<input
className='form-input s-input'
type='text'
name='username'
value={username}
onChange={(e) => onChange(e)}
id='username'
required
/>
</div>
<div>
<label className='form-label s-label fc-black-600'>
Password
</label>
<input
className='form-input s-input'
type='password'
name='password'
value={password}
onChange={(e) => onChange(e)}
id='password'
required
/>
</div>
<div className='grid gs4 gsy fd-column js-auth-item '>
<button
className='s-btn s-btn__primary'
id='submit-button'
name='submit-button'
>
{action}
</button>
</div>
</form>
<div className='fs-caption license fc-black-500'>
By clicking “{action}”, you agree to our{' '}
<Link
to='https://stackoverflow.com/legal/terms-of-service/public'
className='-link'
>
terms of service
</Link>
,{' '}
<Link
to='https://stackoverflow.com/legal/privacy-policy'
name='privacy'
className='-link'
>
privacy policy
</Link>{' '}
and{' '}
<Link
to='https://stackoverflow.com/legal/cookie-policy'
className='-link'
>
cookie policy
</Link>
<input type='hidden' name='legalLinksShown' value='1' />
</div>
</div>
<div className='redirects fc-black-500'>
{action === 'Sign up' ? signUpLink : logInLink}
<div>
Are you an employer?{' '}
<Link
to='https://careers.stackoverflow.com/employer/login'
name='talent'
>
Sign up on Talent{' '}
<ExternalLink/>
</Link>
</div>
</div>
</div>
</Fragment>
);
};
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 <Fragment>
<div className='footer'>
<div className="socials">
<div className="social-item">
<a
href='https://github.com/Mayank0255/Stackoverflow-Clone-Frontend'
target='_blank'
rel="noreferrer"
>
<GitHub/>
</a>
<p><strong>Frontend</strong></p>
</div>
<div className="social-item">
<a
href='https://github.com/Mayank0255/Stackoverflow-Clone-Backend'
target='_blank'
rel="noreferrer"
>
<Database/>
</a>
<p><strong>Backend</strong></p>
</div>
</div>
</div>
</Fragment>
};
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 = (
<div className='btns'>
{loading || user === null ? (
<Spinner width='50px' height='50px' />
) : (
<Link to={`/users/${user.id}`} title={user.username}>
<img
alt='user-logo'
className='logo'
src={user.gravatar}
/>
</Link>
)}
<LinkButton
text={'Log out'}
link={'/login'}
type={'s-btn__filled'}
handleClick={logout}
/>
</div>
);
const authTabs = (
<div className='s-navigation'>
<Link to='/' className='s-navigation--item is-selected'>
Products
</Link>
</div>
);
const guestTabs = (
<div className='s-navigation'>
<Link to='/' className='s-navigation--item is-selected'>
Products
</Link>
<Link to='/' className='s-navigation--item not-selected'>
Customers
</Link>
<Link to='/' className='s-navigation--item not-selected'>
Use cases
</Link>
</div>
);
const guestLinks = (
<div className='btns'>
<LinkButton text={'Log in'} link={'/login'} type={'s-btn__primary'} />
<LinkButton text={'Sign up'} link={'/register'} type={'s-btn__filled'} />
</div>
);
const SearchBar = () => {
return (
<form
onSubmit={() => history.push('/questions')}
className='small-search-form'
autoComplete='off'
>
<input
className='small-search'
autoComplete='off'
type='text'
name='search'
maxLength='35'
placeholder='Search...'
/>
<Search className="small-search-icon" />
</form>
);
}
return loading ? (
''
) : (
<Fragment>
<nav className='navbar fixed-top navbar-expand-lg navbar-light bs-md'>
<div className="hamburger">
<MobileSideBar hasOverlay />
</div>
<div className='header-brand-div'>
<Link className='navbar-brand' to='/'>
<Logo className='full-logo' />
<SmallLogo className='glyph-logo' />
</Link>
{!loading && (
<Fragment>{isAuthenticated ? authTabs : guestTabs}</Fragment>
)}
</div>
<form
id='search'
onSubmit={() => history.push('/questions')}
className={`grid--cell fl-grow1 searchbar px12 js-searchbar`}
autoComplete='off'
>
<div className='ps-relative search-frame'>
<input
className='s-input s-input__search h100 search-box'
autoComplete='off'
type='text'
name='search'
maxLength='35'
placeholder='Search...'
/>
<Search />
</div>
</form>
<div className="header-search-div">
<Search className="search-icon" onClick={() => setSearchState(!searchState)} />
{!loading && (
<Fragment>{isAuthenticated ? authLinks : guestLinks}</Fragment>
)}
</div>
</nav>
{searchState && <SearchBar />}
</Fragment>
);
};
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 (
<Fragment>
<div className='page'>
<SideBar />
<div id='content'>
{children}
<RightSideBar />
</div>
</div>
<Footer/>
</Fragment>
);
};
export default LayoutWrapper;
================================================
FILE: src/components/organisms/LayoutWrapper/RightSideBar/RightSideBar.component.jsx
================================================
import React, {Fragment} from 'react';
import SideBarWidget from './SideBarWidget/SideBarWidget.component';
import TagsWidget from './TagsWidget/TagsWidget.component';
import './RightSideBar.styles.scss';
const RightSideBar = () => {
return (
<Fragment>
<div id='sidebar' className='side-bar'>
<SideBarWidget />
<TagsWidget />
</div>
</Fragment>
);
};
export default RightSideBar;
================================================
FILE: src/components/organisms/LayoutWrapper/RightSideBar/RightSideBar.styles.scss
================================================
.side-bar {
float: right;
width: 300px;
margin: 0 0 15px;
padding-left: 5px;
height: 100%;
}
@media(max-width: 1100px) {
.side-bar {
float: none;
clear: both;
display: none;
}
}
================================================
FILE: src/components/organisms/LayoutWrapper/RightSideBar/SideBarWidget/SideBarWidget.component.jsx
================================================
import React, {Fragment} from 'react';
import { SideBarWidgetData } from "./SideBarWidgetData";
import './SideBarWidget.styles.scss';
const SideBarWidget = () => {
return (
<Fragment>
<div className="s-sidebarwidget s-sidebarwidget__yellow s-anchors s-anchors__grayscale mb16" data-tracker="cb=1">
<ul className="d-block p0 m0">
{SideBarWidgetData.map(({ type, title, icon, link }, index) => {
if (type === 'header') {
return <WidgetHeader
key={index}
title={title}
/>
} else {
return <WidgetItem
key={index}
icon={icon}
title={title}
link={link}
/>
}
})}
</ul>
</div>
</Fragment>
);
};
const WidgetHeader = ({ title }) => (
<div className="s-sidebarwidget--header s-sidebarwidget__small-bold-text fc-light d:fc-black-900 bb bbw1">
{title}
</div>
)
const WidgetItem = ({ icon, title, link }) => (
<li className="s-sidebarwidget--item d-flex px16">
<div className="flex--item1 fl-shrink0">
{icon}
</div>
<div className="flex--item wmn0 ow-break-word">
<a
href={link}
className="js-gps-track"
data-ga={`["community bulletin board","The Overflow Blog","${link}",null,null]`}
data-gps-track="communitybulletin.click({ priority: 1, position: 0 })">{title}</a>
</div>
</li>
)
export default SideBarWidget;
================================================
FILE: src/components/organisms/LayoutWrapper/RightSideBar/SideBarWidget/SideBarWidget.styles.scss
================================================
.s-sidebarwidget__yellow {
color: #f2f2f3 !important;
margin-top: 24px;
}
================================================
FILE: src/components/organisms/LayoutWrapper/RightSideBar/SideBarWidget/SideBarWidgetData.js
================================================
import React from "react";
import { ReactComponent as EditLogo } from "../../../../../assets/Edit.svg";
export const SideBarWidgetData = [
{
type: 'header',
title: 'The Overflow Blog'
},
{
type: 'item',
title: 'Celebrating the Stack Exchange sites that turned ten years old in Q1 2022',
icon: <EditLogo/>,
link: 'https://stackoverflow.blog/2022/03/16/celebrating-the-stack-exchange-site-that-turned-ten-years-old-in-q1-2022'
},
{
type: 'item',
title: 'New data: What makes developers happy at work',
icon: <EditLogo/>,
link: 'https://stackoverflow.blog/2022/03/17/new-data-what-makes-developers-happy-at-work'
},
{
type: 'header',
title: 'Featured on Meta'
},
{
type: 'item',
title: 'What goes into site sponsorships on SE?',
icon: <div className="favicon favicon-stackexchangemeta" title="Meta Stack Exchange"/>,
link: 'https://meta.stackexchange.com/questions/376530/what-goes-into-site-sponsorships-on-se'
},
{
type: 'item',
title: 'Stack Exchange Q&A access will not be restricted in Russia',
icon: <div className="favicon favicon-stackexchangemeta" title="Meta Stack Exchange"/>,
link: 'https://meta.stackexchange.com/questions/376743/stack-exchange-qa-access-will-not-be-restricted-in-russia'
},
{
type: 'item',
title: 'Announcing an A/B test for a Trending sort option',
icon: <div className="favicon favicon-stackoverflowmeta" title="Meta Stack Overflow"/>,
link: 'https://meta.stackoverflow.com/questions/416486/announcing-an-a-b-test-for-a-trending-sort-option'
},
{
type: 'item',
title: 'New User Experience: Deep Dive into our Research on the Staging Ground – How...',
icon: <div className="favicon favicon-stackoverflowmeta" title="Meta Stack Overflow"/>,
link: 'https://meta.stackoverflow.com/questions/416652/new-user-experience-deep-dive-into-our-research-on-the-staging-ground-how-do'
},
{
type: 'header',
title: 'Hot Meta Posts'
},
{
type: 'item',
title: 'Changing initializer-list tag wiki',
icon: <span className="fc-black-500" title="Question score (upvotes - downvotes)">16</span>,
link: 'https://meta.stackoverflow.com/questions/416623/changing-initializer-list-tag-wiki'
},
{
type: 'item',
title: 'What is the true intention in the "How to reference material written by...',
icon: <span className="fc-black-500" title="Question score (upvotes - downvotes)">10</span>,
link: 'https://meta.stackoverflow.com/questions/416665/what-is-the-true-intention-in-the-how-to-reference-material-written-by-others'
},
]
================================================
FILE: src/components/organisms/LayoutWrapper/RightSideBar/TagsWidget/TagsWidget.component.jsx
================================================
import React, {useEffect, Fragment} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {Link} from 'react-router-dom';
import {getTags} from '../../../../../redux/tags/tags.actions';
import TagsWidgetItem from "./TagsWidgetItem.component";
import './TagsWidget.styles.scss';
const TagsWidget = ({getTags, tag: {tags, loading}}) => {
useEffect(() => {
getTags();
}, [getTags]);
const numList = [
'One',
'Two',
'Three',
'Four',
'Five',
'Six',
'Seven',
'Eight',
'Nine',
'Ten',
];
return loading || tags.length === 0 ? (
''
) : (
<Fragment>
<div className='side-bar-tags'>
<h4 className='tag-headline'>Top {numList[tags.length - 1]} Tags</h4>
{tags.slice(0, 10).map((tag, index) => (
<TagsWidgetItem key={index} tagname={tag.tagname} posts_count={tag.posts_count}/>
))}
<Link className='show-tags' to='/tags'>
show more tags
</Link>
</div>
</Fragment>
);
};
TagsWidget.propTypes = {
getTags: PropTypes.func.isRequired,
tag: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
tag: state.tag,
});
export default connect(mapStateToProps, {getTags})(TagsWidget);
================================================
FILE: src/components/organisms/LayoutWrapper/RightSideBar/TagsWidget/TagsWidget.styles.scss
================================================
.side-bar-tags {
word-wrap: break-word;
margin-bottom: 20px;
color: #242729;
margin-top: 24px;
.tag-headline {
color: #3c4146;
font-size: 18px;
font-weight: 400;
margin-bottom: 18px;
margin-top: 0;
}
}
.show-tags {
display: block;
margin: 5px 0;
color: #0077cc;
text-decoration: none;
cursor: pointer;
font-size: 13px;
}
================================================
FILE: src/components/organisms/LayoutWrapper/RightSideBar/TagsWidget/TagsWidgetItem.component.jsx
================================================
import React, {Fragment} from "react";
import TagBadge from "../../../../molecules/TagBadge/TagBadge.component";
import './TagsWidgetItem.styles.scss';
const TagsWidgetItem = ({ tagname, posts_count }) => {
return <Fragment>
<div className='tag-content'>
<TagBadge
tag_name={tagname}
size={'s-tag s-tag__md'}
display={'inline'}
href={true}
/>
<span className='tag-mult'>
<span>×</span>
<span>{posts_count}</span>
</span>
</div>
</Fragment>
}
export default TagsWidgetItem;
================================================
FILE: src/components/organisms/LayoutWrapper/RightSideBar/TagsWidget/TagsWidgetItem.styles.scss
================================================
.tag-content {
box-sizing: inherit;
margin-bottom: 8px;
.tag-mult {
margin-right: 4px;
color: #848d95;
span {
font-size: 11px;
color: #6a737c;
}
}
}
================================================
FILE: src/components/organisms/LayoutWrapper/SideBar/SideBar.component.jsx
================================================
import React from 'react';
import SideBarItem from "./SideBarItem.component";
import { SideBarData } from "./SideBarData";
import './SideBar.styles.scss';
const SideBar = () => (
<div className='side-bar-container'>
<div className='side-bar-tabs'>
<SideBarItem isHome={true} link='/' text='Home'/>
<div className='public-tabs'>
<p className='title fc-light'>PUBLIC</p>
{SideBarData.map(({ link, icon, text}, index) => (
<SideBarItem
key={index}
link={link}
icon={icon}
text={text}
/>
))}
</div>
<div className='teams-tabs'>
<p className='title fc-light'>TEAMS</p>
</div>
</div>
</div>
);
export default SideBar;
================================================
FILE: src/components/organisms/LayoutWrapper/SideBar/SideBar.styles.scss
================================================
.side-bar-container {
width: 17%;
min-width: 206px;
flex-shrink: 0;
z-index: 1;
box-shadow: 0 0 0 rgba(12,13,14,0.05);
transition: box-shadow ease-in-out .1s,transform ease-in-out .1s;
position: relative !important;
background-color: #3d3d3d;
.side-bar-tabs {
float: right;
margin-top:25px;
.public-tabs {
margin-bottom: 12px;
}
.nav-link {
text-decoration: none;
color: #c4c8cc;
li {
font-size: 13px;
.menu-list-icon {
min-width: 21px !important;
}
.menu-list-text {
margin: 0 !important;
}
}
}
.home-link {
li {
border-right: 3px;
margin-bottom: 12px;
}
}
.icon-link {
li {
border-right: 3px solid transparent;
width: 160px;
.menu-list-btn {
padding-left: 8px;
}
}
}
.title {
font-size: 12px;
padding-left: 8px;
margin-bottom: 5px;
}
.active {
li {
font-weight: bold;
background: #2d2d2d;
color: #f2f2f3;
border-right: 3px solid #f48024;
}
.menu-list-text > span {
font-weight: 700;
}
}
}
}
@media (max-width: 768px) {
.side-bar-container {
display: none;
}
}
================================================
FILE: src/components/organisms/LayoutWrapper/SideBar/SideBarData.js
================================================
import { ReactComponent as GlobalIcon } from '../../../../assets/Globe.svg';
export const SideBarData = [
{
link: '/questions',
icon: <GlobalIcon className='icon' />,
text: 'Questions',
},
{
link: '/tags',
text: 'Tags',
},
{
link: '/users',
text: 'Users',
},
{
link: '/jobs',
text: 'Jobs',
}
]
================================================
FILE: src/components/organisms/LayoutWrapper/SideBar/SideBarItem.component.jsx
================================================
import React, { Fragment } from 'react';
import { NavLink } from 'react-router-dom';
import { ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import './SideBar.styles.scss';
const SideBarItem = ({ link, icon, text, isHome }) => {
return (
<Fragment>
{isHome ?
<HomeItem
link={link}
text={text}
/> :
<DefaultItem
link={link}
icon={icon}
text={text}
/>
}
</Fragment>
)
};
const HomeItem = ({ link, text }) => (
<NavLink
exact
activeClassName='active'
className='home-link nav-link'
to={link}
>
<ListItem disablePadding>
<ListItemButton style={{ paddingLeft: '8px' }}>
<ListItemText className='menu-list-text' primary={text} />
</ListItemButton>
</ListItem>
</NavLink>
)
const DefaultItem = ({ link, icon, text }) => (
<NavLink
activeClassName='active'
className='icon-link nav-link'
to={link}
>
<ListItem disablePadding>
<ListItemButton className='menu-list-btn'>
<ListItemIcon className='menu-list-icon'>
{icon}
</ListItemIcon>
<ListItemText className='menu-list-text' primary={text}/>
</ListItemButton>
</ListItem>
</NavLink>
)
SideBarItem.defaultProps = {
isHome: false,
};
export default SideBarItem;
================================================
FILE: src/components/organisms/MarkdownEditor/MarkdownEditor.component.jsx
================================================
import React, {useState, forwardRef, useImperativeHandle} from 'react';
import RichTextEditor from 'react-rte';
import './MarkdownEditor.styles.scss';
const MarkdownEditor = forwardRef((props, ref) => {
const [value, setValue] = useState(RichTextEditor.createEmptyValue());
useImperativeHandle(ref, () => ({
cleanEditorState() {
setValue(RichTextEditor.createEmptyValue());
},
}));
const onChange = (newValue) => {
setValue(newValue);
if (props.onChange) {
// Send the changes up to the parent component as an HTML string.
// This is here to demonstrate using `.toString()` but in a real app it
// would be better to avoid generating a string on each change.
props.onChange(newValue.toString('html'));
}
};
// The toolbarConfig object allows you to specify custom buttons, reorder buttons and to add custom css classes.
// Supported inline styles: https://github.com/facebook/draft-js/blob/master/docs/Advanced-Topics-Inline-Styles.md
// Supported block types: https://github.com/facebook/draft-js/blob/master/docs/Advanced-Topics-Custom-Block-Render.md#draft-default-block-render-map
const toolbarConfig = {
// Optionally specify the groups to display (displayed in the order listed).
display: [
'INLINE_STYLE_BUTTONS',
'BLOCK_TYPE_BUTTONS',
'LINK_BUTTONS',
// 'BLOCK_TYPE_DROPDOWN',
// 'HISTORY_BUTTONS',
],
INLINE_STYLE_BUTTONS: [
{label: 'Bold', style: 'BOLD', className: 'button-format'},
{label: 'Italic', style: 'ITALIC', className: 'button-format'},
{label: 'Underline', style: 'UNDERLINE', className: 'button-format'},
// {label: 'Monospace', style: 'CODE', className: 'button-format'},
],
// BLOCK_TYPE_DROPDOWN: [
// {label: 'Normal', style: 'unstyled'},
// {label: 'Heading Large', style: 'header-one'},
// {label: 'Heading Medium', style: 'header-two'},
// {label: 'Heading Small', style: 'header-three'},
// ],
BLOCK_TYPE_BUTTONS: [
{label: 'UL', style: 'unordered-list-item', className: 'button-format'},
{label: 'OL', style: 'ordered-list-item', className: 'button-format'},
{label: 'Blockquote', style: 'blockquote', className: 'button-format'},
{
label: 'Code Block',
style: 'code-block',
className: 'button-format code-block',
},
],
};
return (
<RichTextEditor
className='rich-text-editor-root'
toolbarClassName='rich-text-editor-toolbar'
editorClassName='rich-text-editor-editor'
toolbarConfig={toolbarConfig}
value={value}
onChange={onChange}
/>
);
});
export default MarkdownEditor;
================================================
FILE: src/components/organisms/MarkdownEditor/MarkdownEditor.styles.scss
================================================
.rich-text-editor-root {
background-color: transparent !important;
border: none !important;
font-family: inherit !important;
font-size: inherit !important;
.rich-text-editor-toolbar {
border-bottom: 1px solid var(--black-200);
button {
width: 32px;
}
button.button-format {
background: var(--white);
span {
filter: invert(100%);
-webkit-filter: invert(100%);
}
}
button.button-format.code-block {
// code-block has not default icon in react-rte, so we gotta implement it on our own
span {
display: inline-block;
width: 22px;
height: 22px;
background-position: 50%;
background-repeat: no-repeat;
background-size: 18px;
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9Ijk0LjUwNHB4IiBoZWlnaHQ9Ijk0LjUwNHB4IiB2aWV3Qm94PSIwIDAgOTQuNTA0IDk0LjUwNCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgOTQuNTA0IDk0LjUwNDsiDQoJIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPGc+DQoJCTxwYXRoIGQ9Ik05My45MTgsNDUuODMzTDY5Ljc5OSwyMS43MTRjLTAuNzUtMC43NS0yLjA3Ny0wLjc1LTIuODI3LDBsLTUuMjI5LDUuMjI5Yy0wLjc4MSwwLjc4MS0wLjc4MSwyLjA0NywwLDIuODI4DQoJCQlsMTcuNDc3LDE3LjQ3NUw2MS43NDQsNjQuNzI0Yy0wLjc4MSwwLjc4MS0wLjc4MSwyLjA0NywwLDIuODI4bDUuMjI5LDUuMjI5YzAuMzc1LDAuMzc1LDAuODg0LDAuNTg3LDEuNDE0LDAuNTg3DQoJCQljMC41MjksMCwxLjAzOS0wLjIxMiwxLjQxNC0wLjU4N2wyNC4xMTctMjQuMTE4Qzk0LjY5OSw0Ny44ODEsOTQuNjk5LDQ2LjYxNCw5My45MTgsNDUuODMzeiIvPg0KCQk8cGF0aCBkPSJNMzIuNzU5LDY0LjcyNEwxNS4yODUsNDcuMjQ4bDE3LjQ3Ny0xNy40NzVjMC4zNzUtMC4zNzUsMC41ODYtMC44ODMsMC41ODYtMS40MTRjMC0wLjUzLTAuMjEtMS4wMzktMC41ODYtMS40MTQNCgkJCWwtNS4yMjktNS4yMjljLTAuMzc1LTAuMzc1LTAuODg0LTAuNTg2LTEuNDE0LTAuNTg2Yy0wLjUzLDAtMS4wMzksMC4yMTEtMS40MTQsMC41ODZMMC41ODUsNDUuODMzDQoJCQljLTAuNzgxLDAuNzgxLTAuNzgxLDIuMDQ3LDAsMi44MjlMMjQuNzA0LDcyLjc4YzAuMzc1LDAuMzc1LDAuODg0LDAuNTg3LDEuNDE0LDAuNTg3YzAuNTMsMCwxLjAzOS0wLjIxMiwxLjQxNC0wLjU4N2w1LjIyOS01LjIyOQ0KCQkJQzMzLjU0Miw2Ni43NzEsMzMuNTQyLDY1LjUwNSwzMi43NTksNjQuNzI0eiIvPg0KCQk8cGF0aCBkPSJNNjAuOTY3LDEzLjZjLTAuMjU0LTAuNDY2LTAuNjgyLTAuODEyLTEuMTktMC45NjJsLTQuMjM5LTEuMjUxYy0xLjA1OC0wLjMxNC0yLjE3MiwwLjI5My0yLjQ4NCwxLjM1MkwzMy4zNzUsNzkuMzgyDQoJCQljLTAuMTUsMC41MDktMC4wOTIsMS4wNTYsMC4xNjEsMS41MjFjMC4yNTMsMC40NjcsMC42ODIsMC44MTIsMS4xOSwwLjk2M2w0LjIzOSwxLjI1MWMwLjE4OSwwLjA1NiwwLjM4LDAuMDgzLDAuNTY3LDAuMDgzDQoJCQljMC44NjMsMCwxLjY2LTAuNTY0LDEuOTE3LTEuNDM1bDE5LjY3OS02Ni42NDRDNjEuMjc4LDE0LjYxMiw2MS4yMjEsMTQuMDY1LDYwLjk2NywxMy42eiIvPg0KCTwvZz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjwvc3ZnPg0K');
}
}
button[title='Link'],
button[title='Remove Link'] {
span {
filter: invert(0%);
-webkit-filter: invert(0%);
}
}
button[title='Link']:disabled,
button[title='Remove Link']:disabled {
span {
filter: invert(100%);
-webkit-filter: invert(100%);
}
}
}
.rich-text-editor-editor {
height: 300px;
.DraftEditor-root {
pre {
padding: 5px;
background-color: var(--black-100);
color: var(--black-800);
}
blockquote {
color: var(--black-800);
}
}
}
}
================================================
FILE: src/components/organisms/MobileSideBar/MobileSideBar.component.jsx
================================================
import React, { useState } from "react";
import { NavLink } from "react-router-dom";
import { ReactComponent as Hamburger } from "../../../assets/LogoGlyphMd.svg";
import { ReactComponent as Stack } from "../../../assets/LogoMd.svg";
import { ReactComponent as GlobalIcon } from "../../../assets/Globe.svg";
import "./MobileSideBar.styles.scss";
const SidebarUI = ({ isOpen, ...rest }) => {
const classes = ["Sidebar", isOpen ? "is-open" : ""];
return (
<div aria-hidden={!isOpen} className={classes.join(" ")} {...rest} />
);
};
SidebarUI.Overlay = (props) => <div className="SidebarOverlay" {...props} />;
SidebarUI.Content = ({ width = "20rem", isRight = false, ...rest }) => {
const classes = ["SidebarContent", isRight ? "is-right" : ""];
const style = {
width,
height: "100%",
top: 0,
right: isRight ? `-${width}` : "auto",
left: !isRight ? `-${width}` : "auto",
};
return <div className={classes.join(" ")} style={style} {...rest} />;
};
const MobileSideBar = (props) => {
const [isOpen, setIsOpen] = useState(false);
function openSidebar(isOp = true) {
setIsOpen(isOp);
}
const { hasOverlay, isRight } = props;
return (
<SidebarUI isOpen={isOpen}>
<Hamburger onClick={openSidebar} className="ham" />
<SidebarUI.Content
isRight={isRight}
onClick={() => openSidebar(false)}
>
<div className="content-logo">
<Stack />
</div>
<div className="content-inner">
<div className="side-bar-tabs">
<NavLink
exact
activeClassName="active"
className="home-link"
to="/"
>
<p>Home</p>
</NavLink>
<div className="public-tabs">
<p className="title fc-light">PUBLIC</p>
<NavLink
activeClassName="active"
className="icon-link"
to="/questions"
>
<p>
<GlobalIcon className="icon" />
Stack Overflow
</p>
</NavLink>
<NavLink
activeClassName="active"
className="link"
to="/tags"
>
<p>Tags</p>
</NavLink>
<NavLink
activeClassName="active"
className="link"
to="/users"
>
<p>Users</p>
</NavLink>
<NavLink
activeClassName="active"
className="link"
to="/jobs"
>
<p>Jobs</p>
</NavLink>
</div>
<div className="teams-tabs">
<p className="title fc-light">TEAMS</p>
</div>
</div>
</div>
</SidebarUI.Content>
{hasOverlay ? (
<SidebarUI.Overlay onClick={() => openSidebar(false)} />
) : false}
</SidebarUI>
);
};
export default MobileSideBar;
================================================
FILE: src/components/organisms/MobileSideBar/MobileSideBar.styles.scss
================================================
.Sidebar {
z-index: 9999;
.SidebarOverlay {
position: fixed;
top: 0;
left: 0;
opacity: 0;
width: 0;
height: 0;
z-index: 99;
background-color: black;
transition: opacity 300ms ease-in-out, width 0ms 300ms, height 0ms 300ms;
}
.SidebarContent {
position: fixed;
z-index: 100;
transition: 300ms ease-in-out;
transition-property: left, right, top, bottom;
overflow-x: hidden;
overflow-y: auto;
scroll-behavior: smooth;
background-color: #333;
}
&.is-open {
.SidebarOverlay {
width: 100%;
height: 100%;
opacity: 0.25;
transition: opacity 300ms ease-in-out, width 0ms, height 0ms;
}
.SidebarContent {
left: 0 !important;
&.is-right {
left: auto !important;
right: 0 !important;
}
}
}
.ham {
margin-left: 1rem;
&:hover {
cursor: pointer;
}
}
.content-logo {
padding: 1rem 2rem;
background-color: #4d4d4d;
box-shadow: 2px -2px 8px 3px black;
}
.content-inner {
padding: 2rem 0;
filter: contrast(1.4);
.side-bar-tabs {
float: right;
margin-top: 25px;
.home-link {
text-decoration: none;
color: #c2c3c5;
p {
padding: 5px 5px 5px 5px;
border-right: 3px;
font-size: 13px;
}
&:hover {
color: #cbcbd1;
}
}
.icon-link {
text-decoration: none;
color: #c4c8cc;
p {
display: flex;
padding: 5px 30px 5px 10px;
margin-bottom: 5px;
border-right: 3px solid transparent;
width: 160px;
font-size: 13px;
}
.icon {
margin-right: 5px;
}
&:hover {
color: #f2f2f3;
.icon {
color: #f2f2f3;
}
}
}
.link {
text-decoration: none;
color: #c4c8cc;
p {
padding: 5px 45px 5px 20px;
margin-bottom: 5px;
width: 100%;
display: flex;
align-items: center;
font-size: 13px;
}
&:hover {
color: #f2f2f3;
}
}
.title {
font-size: 12px;
margin-bottom: 5px;
}
.icon {
width: 16px;
color: grey;
}
.active {
color: #f2f2f3;
p {
font-weight: bold;
background: #5a5a5a;
color: #f2f2f3;
border-right: 3px solid #f48024;
}
.icon {
color: #f2f2f3;
}
}
}
}
.side-s-navigation {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
.side-s-navigation--item {
padding: 0.8rem;
border-radius: 1.3rem;
margin: 0.5rem 0;
font-size: 1.2rem;
}
.is-selected {
background-color: #f48225;
color: #0c0c0c;
}
}
.side-s-navigation .not-selected {
color: #c4c8cc;
&:hover {
background-color: #404345;
color: #f2f2f3;
text-decoration: none;
outline: none;
}
}
}
================================================
FILE: src/components/organisms/Pagination/Pagination.component.jsx
================================================
import React, { Fragment } from "react";
import { Pagination as MuiPagination, PaginationItem } from "@mui/material";
const Pagination = ({
page,
itemList,
itemsPerPage,
handlePaginationChange,
}) => {
return (
<Fragment>
<MuiPagination
variant="outlined"
shape="rounded"
page={page}
count={Math.ceil(itemList.length/itemsPerPage)}
onChange={handlePaginationChange}
style={{ float: 'right', margin: '0 13px 16px 0' }}
renderItem={(item) => (
<PaginationItem {...item} sx={{ color: '#cfd2d6', border: '1px solid #4c4f52' }}/>
)}
/>
</Fragment>
);
}
export default Pagination;
================================================
FILE: src/config/index.js
================================================
const config = {
BASE_URL: process.env.REACT_APP_API_URL,
};
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
config.BASE_URL = process.env.REACT_APP_API_URL;
}
export default config;
================================================
FILE: src/hooks/usePageTitle.jsx
================================================
import { useEffect } from 'react';
const usePageTitle = (title, prevailOnUnmount = false) => {
useEffect(() => {
document.title = title;
}, [title])
};
export default usePageTitle;
================================================
FILE: src/index.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom';
import App from './App';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
================================================
FILE: src/modules/AllTagsPage/AllTagsPage.component.jsx
================================================
import React, {useEffect, Fragment, useState} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {getTags} from '../../redux/tags/tags.actions';
import handleSorting from '../../utils/handleSorting';
import TagPanel from './TagPanel/TagPanel.component';
import Spinner from '../../components/molecules/Spinner/Spinner.component';
import SearchBox from '../../components/molecules/SearchBox/SearchBox.component';
import ButtonGroup from '../../components/molecules/ButtonGroup/ButtonGroup.component';
import Pagination from "../../components/organisms/Pagination/Pagination.component";
import './AllTagsPage.styles.scss';
const itemsPerPage = 12;
const AllTagsPage = ({getTags, tag: {tags, loading}}) => {
useEffect(() => {
getTags();
}, [getTags]);
const [page, setPage] = useState(1);
const [fetchSearch, setSearch] = useState('');
const [sortType, setSortType] = useState('Popular');
const handleChange = (e) => {
e.preventDefault();
setSearch(e.target.value);
setPage(1)
};
const handlePaginationChange = (e, value) => setPage(value);
return loading || tags === null ? (
<Spinner type='page' width='75px' height='200px' />
) : (
<Fragment>
<div id='mainbar' className='tags-page fc-black-800'>
<h1 className='headline'>Tags</h1>
<p className='fs-body'>
A tag is a keyword or label that categorizes your question with other,
similar questions. Using the right tags makes it easier for others to
find and answer your question.
</p>
<div className='headline-count'>
<span>{new Intl.NumberFormat('en-IN').format(tags.length)} tags</span>
</div>
<div className='tags-box pl16 pr16 pb16'>
<SearchBox
placeholder={'filter by tag name'}
handleChange={handleChange}
width={'200px'}
/>
<ButtonGroup
buttons={['Popular', 'Name', 'New']}
selected={sortType}
setSelected={setSortType}
/>
</div>
<div className='user-browser'>
<div className='grid-layout'>
{tags
.filter((tag) =>
tag.tagname.toLowerCase().includes(fetchSearch.toLowerCase())
)
?.sort(handleSorting(sortType))
.slice((page - 1) * itemsPerPage, (page - 1) * itemsPerPage + itemsPerPage)
.map((tag, index) => (
<TagPanel key={index} tag={tag} />
))}
</div>
</div>
<Pagination
page={page}
itemList={tags.filter((tag) => tag.tagname.toLowerCase().includes(fetchSearch.toLowerCase()))}
itemsPerPage={itemsPerPage}
handlePaginationChange={handlePaginationChange}
/>
</div>
</Fragment>
);
};
AllTagsPage.propTypes = {
getTags: PropTypes.func.isRequired,
tag: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
tag: state.tag,
});
export default connect(mapStateToProps, {getTags})(AllTagsPage);
================================================
FILE: src/modules/AllTagsPage/AllTagsPage.styles.scss
================================================
#mainbar {
height: 100%;
}
.tags-page {
display: inline-block;
width: 60%;
margin-left: 20%;
padding: 24px 0;
.headline {
font-size: 28px;
margin-bottom: 16px;
line-height: 1.3;
font-weight: 400;
padding-left: 16px;
padding-right: 16px;
}
.fs-body {
padding-left: 16px;
padding-right: 1rem;
width: auto;
font-size: 15px;
font-weight: 400;
}
.headline-count {
margin-bottom: 12px;
display: flex;
padding-left: 16px;
padding-right: 1rem;
span {
font-size: 17px;
margin-right: 1rem;
}
}
.tags-box {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: space-between;
}
.user-browser {
float: none;
clear: both;
width: auto;
padding: 0 16px 16px 16px;
.grid-layout {
display: grid;
grid-gap: 12px;
grid-template-columns: repeat(auto-fill, minmax(225px, 1fr));
}
}
}
.Mui-selected {
color: #000 !important;
font-weight: 500 !important;
border-color: transparent !important;
background-color: #f48225 !important;
}
@media(max-width: 1100px) {
.side-bar {
display: none;
}
.tags-page {
width: 80%;
}
#content {
max-width: none;
margin-right: 10px;
}
}
@media (max-width: 500px) {
#mainbar.tags-page {
.tags-box {
display: flex;
align-items: space-between;
}
.s-input {
margin-bottom: 1rem;
}
}
}
================================================
FILE: src/modules/AllTagsPage/TagPanel/TagPanel.component.jsx
================================================
import React from 'react';
import moment from 'moment';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import TagBadge from '../../../components/molecules/TagBadge/TagBadge.component';
const TagPanel = ({tag: {tagname, description, created_at, posts_count}}) => {
return (
<div className="grid--item s-card js-tag-cell d-flex fd-column">
<div className="d-flex jc-space-between ai-center mb12">
<TagBadge tag_name={tagname} size={'s-tag'} float={'left'} />
</div>
<div className="flex--item fc-medium mb12 v-truncate4">
{description}
</div>
<div className="mt-auto d-flex jc-space-between fs-caption fc-black-400">
<div className="flex--item">{posts_count} {posts_count === 1 ? 'question' : 'questions'}</div>
<div className="flex--item s-anchors s-anchors__inherit">
added {moment(created_at).fromNow(false)}
</div>
</div>
</div>
);
};
TagPanel.propTypes = {
tag: PropTypes.object.isRequired,
};
export default connect(null)(TagPanel);
================================================
FILE: src/modules/AllUsersPage/AllUsersPage.component.jsx
================================================
import React, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { getUsers } from "../../redux/users/users.actions";
import handleSorting from "../../utils/handleSorting";
import UserPanel from "./UserPanel/UserPanel.component";
import Spinner from "../../components/molecules/Spinner/Spinner.component";
import SearchBox from "../../components/molecules/SearchBox/SearchBox.component";
import ButtonGroup from "../../components/molecules/ButtonGroup/ButtonGroup.component";
import Pagination from "../../components/organisms/Pagination/Pagination.component";
import "./AllUsersPage.styles.scss";
const itemsPerPage = 18;
const AllUsersPage = ({ getUsers, user: { users, loading } }) => {
useEffect(() => {
getUsers();
}, [getUsers]);
const [page, setPage] = useState(1);
const [fetchSearch, setSearch] = useState("");
const [sortType, setSortType] = useState("Popular");
const handlePaginationChange = (e, value) => setPage(value);
const handleChange = (e) => {
e.preventDefault();
setSearch(e.target.value);
setPage(1);
};
return loading || users === null ? (
<Spinner type="page" width="75px" height="200px" />
) : (
<Fragment>
<div id="mainbar" className="users-page fc-black-800">
<h1 className="headline">Users</h1>
<div className="headline-count">
<span>
{new Intl.NumberFormat("en-IN").format(users.length)} users
</span>
</div>
<div className="users-box pl16 pr16 pb16">
<SearchBox
placeholder={"filter by user"}
handleChange={handleChange}
width={"200px"}
/>
<ButtonGroup
buttons={["Popular", "Name", "Active", "New Users"]}
selected={sortType}
setSelected={setSortType}
/>
</div>
<div className="user-browser">
<div className="grid-layout">
{users
.filter((user) =>
user.username.toLowerCase().includes(fetchSearch.toLowerCase())
)
?.sort(handleSorting(sortType, "users"))
.slice(
(page - 1) * itemsPerPage,
(page - 1) * itemsPerPage + itemsPerPage
)
.map((user, index) => (
<UserPanel key={index} user={user} />
))}
</div>
</div>
<Pagination
page={page}
itemList={users.filter((user) =>
user.username.toLowerCase().includes(fetchSearch.toLowerCase())
)}
itemsPerPage={itemsPerPage}
handlePaginationChange={handlePaginationChange}
/>
</div>
</Fragment>
);
};
AllUsersPage.propTypes = {
getUsers: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
user: state.user,
});
export default connect(mapStateToProps, { getUsers })(AllUsersPage);
================================================
FILE: src/modules/AllUsersPage/AllUsersPage.styles.scss
================================================
.users-page {
width: calc(100% - 300px - 24px);
padding: 24px 0 24px 0;
float: left;
margin: 0;
.headline {
font-size: 28px;
margin-bottom: 24px;
line-height: 1.3;
font-weight: 400;
padding-left: 16px;
padding-right: 16px;
}
.headline-count {
margin-bottom: 12px;
display: flex;
padding-left: 16px;
padding-right: 16px;
span {
font-size: 17px;
margin-right: 12px;
}
}
.users-box {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: space-around;
}
.user-browser {
float: none;
clear: both;
width: auto;
padding: 0 16px 16px 16px;
.grid-layout {
display: grid;
grid-gap: 12px;
grid-template-columns: repeat(auto-fill, minmax(225px, 1fr));
}
}
}
@media (max-width: 520px) {
.users-box {
.s-input {
margin-bottom: 1rem;
}
}
}
================================================
FILE: src/modules/AllUsersPage/UserPanel/UserPanel.component.jsx
================================================
import React, {Fragment} from 'react';
import moment from 'moment';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
import PropTypes from 'prop-types';
import './UserPanel.styles.scss';
const UserPanel = ({
user: {id, username, created_at, posts_count, tags_count, views, gravatar},
}) => {
return (
<Fragment>
<div className='user-panel-info s-card bs-sm h:bs-md fc-black-500'>
<div className='user-gravatar'>
<Link to={`/users/${id}`}>
<div className='logo-wrapper'>
<img
alt='user-logo'
src={gravatar}
/>
</div>
</Link>
</div>
<div className='user-details'>
<Link className='fc-blue-600' to={`/users/${id}`}>
{username}
</Link>
<span className='item'>
<span className='count'>
{posts_count}{' '}
<span className='count-info'>
{posts_count === 1 ? 'QUESTION' : 'QUESTIONS'}
</span>
</span>
</span>
<span className='item'>
<span className='count'>
{tags_count}{' '}
<span className='count-info'>
{posts_count === 1 ? 'TAG' : 'TAGS'}
</span>
</span>
</span>
<span className='item user-time' style={{paddingTop: '1px'}}>
<span className='count'>
{views}{' '}
<span className='count-info'>
{views === 1 ? 'PROFILE VIEW' : 'PROFILE VIEWS'}
</span>
</span>
<span className='count' style={{fontWeight: '400'}}>
{moment(created_at).fromNow(false)}
</span>
</span>
</div>
</div>
</Fragment>
);
};
UserPanel.propTypes = {
user: PropTypes.object.isRequired,
};
export default connect(null)(UserPanel);
================================================
FILE: src/modules/AllUsersPage/UserPanel/UserPanel.styles.scss
================================================
.user-panel-info {
padding: 6px 7px 8px 8px;
border: 1px solid #4a4e51;
border-radius: 3px;
background-color: #2d2d2d;
box-sizing: border-box;
color: #6a737c;
display: flex;
align-items: center;
&:hover {
border-color: #697075;
}
.user-gravatar {
float: left;
width: 48px;
height: 48px;
a {
color: #0077cc;
text-decoration: none;
&:hover {
color: #0095ff;
text-decoration: none;
}
.logo-wrapper {
width: 48px;
height: 48px;
border-radius: 2px;
overflow: hidden;
padding: 0;
img {
width: 48px;
height: 48px;
margin: 0 auto;
}
}
}
}
.user-details {
margin-left: 9px;
width: calc(100% - 64px);
line-height: 1.3;
float: left;
a {
color: #0077cc;
text-decoration: none;
display: inline-block;
font-size: 14px;
&:hover {
color: #0095ff;
text-decoration: none;
}
}
.item {
margin-right: 2px;
display: block;
}
.user-time {
display: flex;
justify-content: space-between;
}
.count {
font-size: 11px;
font-weight: 700;
}
.count-info {
font-size: 10px;
font-weight: 500;
}
}
}
================================================
FILE: src/modules/HomePage/HomePage.component.jsx
================================================
import React, {Fragment, useEffect, useState} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {getPosts} from '../../redux/posts/posts.actions';
import LinkButton from '../../components/molecules/LinkButton/LinkButton.component';
import PostItem from '../../components/molecules/PostItem/PostItem.component';
import Spinner from '../../components/molecules/Spinner/Spinner.component';
import handleSorting from "../../utils/handleSorting";
import Pagination from "../../components/organisms/Pagination/Pagination.component";
import ButtonGroup from '../../components/molecules/ButtonGroup/ButtonGroup.component';
import handleFilter from '../../utils/handleFilter'
import './HomePage.styles.scss';
const itemsPerPage = 10;
const HomePage = ({getPosts, post: {posts, loading}}) => {
useEffect(() => {
getPosts();
}, [getPosts]);
const [page, setPage] = useState(1);
const [sortType, setSortType] = useState('Month')
const handlePaginationChange = (e, value) => setPage(value);
return loading || posts === null ? (
<Spinner type='page' width='75px' height='200px' />
) : (
<Fragment>
<div id='mainbar' className='homepage fc-black-800'>
<div className='questions-grid'>
<h3 className='questions-headline'>Top Questions</h3>
<div className='questions-btn'>
<LinkButton
text={'Ask Question'}
link={'/add/question'}
type={'s-btn__primary'}
/>
</div>
</div>
<div className='questions-tabs'>
<span>
{new Intl.NumberFormat('en-IN').format(posts.length)} questions
</span>
<div className="btns-filter">
<ButtonGroup
buttons={['Today', 'Week', 'Month', 'Year']}
selected={sortType}
setSelected={setSortType}
/>
</div>
</div>
<div className="questions">
<div className="postQues">
{posts
.sort(handleSorting(sortType))
.filter(handleFilter(sortType))
.slice((page - 1) * itemsPerPage, (page - 1) * itemsPerPage + itemsPerPage)
.map((post, index) => (
<PostItem key={index} post={post} />
))}
</div>
</div>
<Pagination
page={page}
itemList={posts
.sort(handleSorting(sortType))
.filter(handleFilter(sortType))}
itemsPerPage={itemsPerPage}
handlePaginationChange={handlePaginationChange}
/>
</div>
</Fragment>
);
};
HomePage.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
post: state.post,
});
export default connect(mapStateToProps, { getPosts })(HomePage);
================================================
FILE: src/modules/HomePage/HomePage.styles.scss
================================================
.page {
width: 100%;
background: none;
display: flex;
justify-content: space-between;
margin: 64px auto 0 auto;
}
.postQues .brief {
display: none;
}
.btns-filter {
margin: 3% 0% 3% 0%;
}
#content {
max-width: none;
width: 100vw;
padding: 0;
margin-right: 60px;
}
#mainbar {
padding-top: 14px;
}
.homepage {
width: 100;
padding: 24px 0 24px 0;
float: left;
margin: 0;
.questions-grid {
display: flex;
padding-left: 24px;
.questions-headline {
margin-bottom: 24px;
flex: 1 auto;
font-size: 28px;
font-weight: 400;
}
}
.questions-tabs {
margin-bottom: 12px;
align-items: center;
display: flex;
justify-content: space-between;
padding-left: 24px;
padding-right: 16px;
align-items: center;
span {
font-size: 17px;
margin-right: 12px;
}
}
.questions {
clear: both;
width: auto;
margin-bottom: 20px;
border-top: 1px solid #4a4e51;
}
}
@media (max-width: 1100px) {
#content {
max-width: none;
width: calc(100vw - 220px);
}
#mainbar {
width: calc(100vw - 220px);
margin: 0;
.questions-grid {
width: 100%;
}
}
.questions-tabs {
span {
font-size: 15px;
}
}
}
@media (max-width: 768px) {
#content {
max-width: none;
width: 99%;
}
#mainbar {
width: 99%;
margin: 0;
.questions-grid {
width: 100%;
}
}
.homepage .questions-tabs span {
font-size: 13px;
}
}
@media (min-width: 1350px) {
#content {
max-width: none;
margin-right: 11.5vw;
}
}
================================================
FILE: src/modules/Login/Login.component.jsx
================================================
import React, {Fragment} from 'react';
import {Redirect} from 'react-router-dom';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import AuthForm from '../../components/organisms/AuthForm/AuthForm.component';
import Footer from "../../components/organisms/Footer/Footer.component";
const Login = ({isAuthenticated}) => {
if (isAuthenticated) {
return <Redirect to='/' />;
}
return (
<Fragment>
<div className='auth-page'>
<div className='register-content'>
<div className='register-grid'>
<AuthForm action={'Log in'} />
</div>
</div>
</div>
<Footer/>
</Fragment>
);
};
Login.propTypes = {
isAuthenticated: PropTypes.bool,
};
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isAuthenticated,
});
export default connect(mapStateToProps, null)(Login);
================================================
FILE: src/modules/NotFound/NotFound.component.jsx
================================================
import React, {Fragment} from 'react';
import {Link} from 'react-router-dom';
import './NotFound.styles.scss';
const NotFound = () => {
return (
<Fragment>
<div className='page'>
<div className='box'>
<div className='box__ghost'>
<div className='symbol' />
<div className='symbol' />
<div className='symbol' />
<div className='symbol' />
<div className='symbol' />
<div className='symbol' />
<div className='box__ghost-container'>
<div className='box__ghost-eyes'>
<div className='box__eye-left' />
<div className='box__eye-right' />
</div>
<div className='box__ghost-bottom'>
<div />
<div />
<div />
<div />
<div />
</div>
</div>
<div className='box__ghost-shadow' />
</div>
<div className='box__description'>
<div className='box__description-container'>
<div className='box__description-title fc-black-800'>Whoops!</div>
<div className='box__description-text fc-black-500'>
It seems like we couldn't find the page you were looking for
</div>
</div>
<Link to='/' className='box__button'>
Back to home page
</Link>
</div>
</div>
</div>
</Fragment>
);
};
export default NotFound;
================================================
FILE: src/modules/NotFound/NotFound.styles.scss
================================================
@import url(https://fonts.googleapis.com/css?family=Ubuntu);
//variables
$purple: #28254C;
$l-purple: #332F63;
$t-purple: #8C8AA7;
$pink: #FF5E65;
$white: #fff;
html, body {
min-width: auto;
}
* {
box-sizing: border-box;
}
.box {
width: 350px;
height: 100%;
max-height: 600px;
min-height: 450px;
border-radius: 20px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 30px 50px;
.box__ghost {
padding: 15px 25px 25px;
position: absolute;
left: 50%;
top: 30%;
transform: translate(-50%, -30%);
.symbol{
&:nth-child(1) {
opacity: .2;
animation: shine 4s ease-in-out 3s infinite;
&:before, &:after {
content: '';
width: 12px;
height: 4px;
background: $white;
position: absolute;
border-radius: 5px;
bottom: 65px;
left: 0;
}
&:before {
transform: rotate(45deg);
}
&:after {
transform: rotate(-45deg);
}
}
&:nth-child(2) {
position: absolute;
left: -5px;
top: 30px;
height: 18px;
width: 18px;
border: 4px solid;
border-radius: 50%;
border-color: $white;
opacity: .2;
animation: shine 4s ease-in-out 1.3s infinite;
}
&:nth-child(3) {
opacity: .2;
animation: shine 3s ease-in-out .5s infinite;
&:before, &:after {
content: '';
width: 12px;
height: 4px;
background: $white;
position: absolute;
border-radius: 5px;
top: 5px;
left: 40px;
}
&:before {
transform: rotate(90deg);
}
&:after {
transform: rotate(180deg);
}
}
&:nth-child(4) {
opacity: .2;
animation: shine 6s ease-in-out 1.6s infinite;
&:before, &:after {
content: '';
width: 15px;
height: 4px;
background: $white;
position: absolute;
border-radius: 5px;
top: 10px;
right: 30px;
}
&:before {
transform: rotate(45deg);
}
&:after {
transform: rotate(-45deg);
}
}
&:nth-child(5) {
position: absolute;
right: 5px;
top: 40px;
height: 12px;
width: 12px;
border: 3px solid;
border-radius: 50%;
border-color: $white;
opacity: .2;
animation: shine 1.7s ease-in-out 7s infinite;
}
&:nth-child(6) {
opacity: .2;
animation: shine 2s ease-in-out 6s infinite;
&:before, &:after {
content: '';
width: 15px;
height: 4px;
background: $white;
position: absolute;
border-radius: 5px;
bottom: 65px;
right: -5px;
}
&:before {
transform: rotate(90deg);
}
&:after {
transform: rotate(180deg);
}
}
}
.box__ghost-container {
background: $white;
width: 100px;
height: 100px;
border-radius: 100px 100px 0 0;
position: relative;
margin: 0 auto;
animation: upndown 3s ease-in-out infinite;
.box__ghost-eyes {
position: absolute;
left: 50%;
top: 45%;
//transform: translate(-50%, -45%);
height: 12px;
width: 70px;
.box__eye-left {
width: 12px;
height: 12px;
background: #2d2d2d;
border-radius: 50%;
margin: 0 10px;
position: absolute;
left: 0;
}
.box__eye-right {
width: 12px;
height: 12px;
background: #2d2d2d;
border-radius: 50%;
margin: 0 10px;
position: absolute;
right: 0;
}
}
.box__ghost-bottom {
display:flex;
position: absolute;
top:100%;
left:0;
right:0;
div {
flex-grow:1;
position: relative;
top:-10px;
height:20px;
border-radius:100%;
background-color: $white;
&:nth-child(2n) {
top: -12px;
margin: 0 -0px;
border-top: 15px solid #2d2d2d;
background: transparent;
}
}
}
}
.box__ghost-shadow {
height: 20px;
box-shadow: 0 50px 15px 5px #454545aa;
border-radius: 50%;
margin: 0 auto;
animation: smallnbig 3s ease-in-out infinite;
}
}
.box__description {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
.box__description-container {
color: $white;
text-align: center;
width: 200px;
font-size: 16px;
margin: 0 auto;
.box__description-title {
font-size: 24px;
letter-spacing: .5px;
}
.box__description-text {
color: $t-purple;
line-height: 20px;
margin-top: 20px;
}
}
.box__button {
display: block;
position: relative;
background: #f48024;
border: 1px solid transparent;
border-radius: 50px;
height: 50px;
text-align: center;
text-decoration: none;
color: $white;
line-height: 50px;
font-size: 18px;
padding: 0 70px;
white-space: nowrap;
margin-top: 25px;
transition: background .5s ease;
overflow: hidden;
//-webkit-mask-image: -webkit-radial-gradient(white, black);
&:before {
content: '';
position: absolute;
width: 20px;
height: 100px;
background: $white;
bottom: -25px;
left: 0;
border: 2px solid $white;
transform: translateX(-50px) rotate(45deg);
transition: transform .5s ease;
}
&:hover {
background: #cc7525;
}
}
}
}
//keyframes
@keyframes upndown {
0% {transform: translateY(5px);}
50% {transform: translateY(15px);}
100% {transform: translateY(5px);}
}
@keyframes smallnbig {
0% {width: 90px;}
50% {width: 100px;}
100% {width: 90px;}
}
@keyframes shine {
0% {opacity: .2;}
25% {opacity: .1;}
50% {opacity: .2;}
100% {opacity: .2;}
}
================================================
FILE: src/modules/Post/AnswerSection/AnswerForm/AnswerForm.component.jsx
================================================
import React, {Fragment, useState, useRef} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {addAnswer} from '../../../../redux/answers/answers.actions';
import LinkButton from '../../../../components/molecules/LinkButton/LinkButton.component';
import MarkdownEditor from '../../../../components/organisms/MarkdownEditor/MarkdownEditor.component';
import './AnswerForm.styles.scss';
const AnswerForm = ({addAnswer, auth, post: {post}}) => {
const [formData, setFormData] = useState({
text: '',
});
const markdownEditorRef = useRef(null);
const {text} = formData;
const handleSubmit = async (e) => {
e.preventDefault();
addAnswer(post.id, {text});
setFormData({
text: '',
});
markdownEditorRef.current.cleanEditorState();
};
const updateConvertedContent = (htmlConvertedContent) => {
setFormData({...formData, text: htmlConvertedContent});
};
return (
<Fragment>
{!auth.loading && auth.isAuthenticated ? (
<Fragment>
<form className='answer-form' onSubmit={(e) => handleSubmit(e)}>
<div className='answer-grid'>
<label className=' fc-black-800'>Your Answer</label>
<div className='s-textarea rich-text-editor-container'>
<MarkdownEditor
ref={markdownEditorRef}
onChange={updateConvertedContent}
/>
</div>
<button className='s-btn s-btn__primary'>Post Your Answer</button>
</div>
</form>
</Fragment>
) : (
<Fragment>
<LinkButton
text={'You need to login to add an answer'}
link={'/login'}
type={'s-btn__outlined'}
marginTop={'12px'}
/>
</Fragment>
)}
</Fragment>
);
};
AnswerForm.propTypes = {
auth: PropTypes.object.isRequired,
addAnswer: PropTypes.func.isRequired,
post: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
auth: state.auth,
post: state.post,
});
export default connect(mapStateToProps, {addAnswer})(AnswerForm);
================================================
FILE: src/modules/Post/AnswerSection/AnswerForm/AnswerForm.styles.scss
================================================
.answer-grid {
margin-bottom: 16px;
display: flex;
flex: 1 auto !important;
flex-direction: column;
color: #242729;
label {
margin-bottom: 16px;
margin-top: 16px;
display: block;
padding: 0 2px;
color: #0c0d0e;
font-family: inherit;
font-size: 18px;
font-weight: 500;
cursor: pointer;
}
button {
margin: 12px 0 8px 0;
width: 150px;
font-size: 14px;
}
.s-textarea.rich-text-editor-container {
padding: 0;
.rich-text-editor-toolbar {
padding: 0;
}
}
}
================================================
FILE: src/modules/Post/AnswerSection/AnswerItem/AnswerItem.component.jsx
================================================
import React, { Fragment } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { deleteAnswer } from "../../../../redux/answers/answers.actions";
import { ReactComponent as UpVote } from "../../../../assets/ArrowUpLg.svg";
import { ReactComponent as DownVote } from "../../../../assets/ArrowDownLg.svg";
import UserCard from "../../../../components/molecules/UserCard/UserCard.component";
import "./AnswerItem.styles.scss";
import censorBadWords from "../../../../utils/censorBadWords";
const AnswerItem = ({
deleteAnswer,
answer: { body, user_id, gravatar, id, created_at, username },
post: { post },
auth,
}) => {
return (
<Fragment>
<div className="answer-layout">
<div className="vote-cell">
<div className="vote-container">
<button
className="vote-up"
title="This answer is useful (click again to undo)"
>
<UpVote className="icon" />
</button>
<div className="vote-count fc-black-500">0</div>
<button
className="vote-down"
title="This answer is not useful (click again to undo)"
>
<DownVote className="icon" />
</button>
</div>
</div>
<div className="answer-item">
<div
className="answer-content fc-black-800"
dangerouslySetInnerHTML={{ __html: censorBadWords(body) }}
></div>
<div className="answer-actions">
<div className="action-btns">
<div className="answer-menu">
<Link
className="answer-links"
title="short permalink to this question"
to="/"
>
share
</Link>
<Link
className="answer-links"
title="Follow this question to receive notifications"
to="/"
>
follow
</Link>
{!auth.loading &&
auth.isAuthenticated &&
user_id === auth.user.id && (
<Link
className="s-link s-link__danger"
style={{ paddingLeft: "4px" }}
title="Delete the answer"
onClick={(e) => deleteAnswer(id)}
to={`/questions/${post.id}`}
>
delete
</Link>
)}
</div>
</div>
<UserCard
created_at={created_at}
user_id={user_id}
gravatar={gravatar}
username={username}
dateType={"answered"}
backgroundColor={"transparent"}
/>
</div>
</div>
</div>
</Fragment>
);
};
AnswerItem.propTypes = {
auth: PropTypes.object.isRequired,
post: PropTypes.object.isRequired,
answer: PropTypes.object.isRequired,
deleteAnswer: PropTypes.func.isRequired,
};
const mapStateToProps = (state) => ({
auth: state.auth,
post: state.post,
});
export default connect(mapStateToProps, { deleteAnswer })(AnswerItem);
================================================
FILE: src/modules/Post/AnswerSection/AnswerItem/AnswerItem.styles.scss
================================================
.answer-layout {
display: grid;
grid-template-columns: max-content 1fr;
.vote-cell {
width: auto;
padding-right: 16px;
vertical-align: top;
grid-column: 1;
font-size: 13px;
.vote-container {
margin: -2px;
display: flex;
align-items: stretch;
flex-direction: column;
color: #bbc0c4 !important;
button {
padding: 0;
border: none;
outline: none;
font: unset;
border-radius: 0;
background: none;
box-shadow: none;
text-align: center;
text-decoration: none;
}
.vote-count {
font-size: 21px;
display: flex;
justify-content: center;
align-items: center;
color:#6a737c;
}
}
}
.answer-item {
padding-right: 16px;
grid-column: 2;
width: auto;
min-width: 0;
font-size: 13px;
.answer-content {
width: 100%;
font-size: 15px;
margin-bottom: 5px;
font-family: Arial,"Helvetica Neue",Helvetica,sans-serif;
p {
clear: both;
margin-bottom: 15px;
margin-top: 0;
display: block;
margin-block-start: 1em;
margin-block-end: 1em;
}
}
.answer-actions {
margin-bottom: 0 !important;
margin-top: -4px;
display: flex;
align-items: flex-start;
justify-content: flex-end !important;
flex-wrap: wrap !important;
.action-btns {
flex: 1 1 100px;
margin: 4px 16px 4px 0;
color: #242729;
.answer-menu {
padding-top: 2px;
.s-link .s-link__danger {
padding: 0 4px 2px;
}
.answer-links {
padding: 0 4px 2px;
color: #848d95;
text-decoration: none;
&:hover {
color: #cfd2d6;
text-decoration: none;
}
}
}
}
.answer-owner {
margin-top: 4px;
margin-bottom: 4px;
text-align: left;
vertical-align: top;
width: 200px;
flex: 0 auto !important;
.answer-user {
box-sizing: border-box;
padding: 5px 6px 7px 7px;
color: #6a737c;
font-size: 13px;
.answer-user-time {
margin-top: 1px;
margin-bottom: 4px;
font-size: 12px;
white-space: nowrap;
}
.answer-logo {
float: left;
width: 32px;
height: 32px;
border-radius: 1px;
img {
width: 32px;
height: 32px;
}
}
.answer-details {
margin-left: 8px;
width: calc(100% - 40px);
float: left;
.answer-user-profile-link {
color: #0077cc;
text-decoration: none;
cursor: pointer;
font-size: 13px;
&:hover {
color: #0095ff;
text-decoration: none;
}
}
}
}
}
}
}
}
================================================
FILE: src/modules/Post/AnswerSection/AnswerSection.component.jsx
================================================
import React, {Fragment, useState, useEffect} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {getAnswers} from '../../../redux/answers/answers.actions';
import handleSorting from '../../../utils/handleSorting';
import AnswerItem from './AnswerItem/AnswerItem.component';
import Spinner from '../../../components/molecules/Spinner/Spinner.component';
import AnswerForm from './AnswerForm/AnswerForm.component';
import ButtonGroup from '../../../components/molecules/ButtonGroup/ButtonGroup.component';
import './AnswerSection.styles.scss';
const AnswerSection = ({getAnswers, answer, post: {post}}) => {
useEffect(() => {
getAnswers(post.id);
// eslint-disable-next-line
}, [getAnswers]);
const [sortType, setSortType] = useState('Newest');
return (
<Fragment>
<div className='answer'>
<div className='answer-header fc-black-800'>
<div className='answer-sub-header'>
<div className='answer-headline'>
<h2>Answers</h2>
</div>
<ButtonGroup
buttons={['Newest', 'Oldest']}
selected={sortType}
setSelected={setSortType}
/>
</div>
</div>
{answer.loading === null ? (
<Spinner width='25px' height='25px' />
) : (
answer.answers?.sort(handleSorting(sortType)).map((answer, index) => (
<div key={index} className='answers'>
<AnswerItem answer={answer}/>
</div>
))
)}
<div className='add-answer'>
<AnswerForm/>
</div>
</div>
</Fragment>
);
};
AnswerSection.propTypes = {
getAnswers: PropTypes.func.isRequired,
answer: PropTypes.object.isRequired,
post: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
answer: state.answer,
post: state.post,
});
export default connect(mapStateToProps, {getAnswers})(AnswerSection);
================================================
FILE: src/modules/Post/AnswerSection/AnswerSection.styles.scss
================================================
.answer {
width: auto;
float: none;
padding-top: 10px;
clear: both;
color: #242729;
.answer-header {
width: 100%;
margin-top: 10px;
color: #242729;
.answer-sub-header {
margin-bottom: 8px !important;
display: flex;
align-items: center;
.answer-headline {
flex: 1 auto !important;
h2 {
font-weight: 400;
margin-bottom: 0;
font-size: 18px;
}
}
}
}
.answers {
border-bottom: 1px solid #4a4e51;
width: 100%;
padding-bottom: 16px;
padding-top: 16px;
color: #242729;
}
}
================================================
FILE: src/modules/Post/Post.component.jsx
================================================
import React, { useEffect, Fragment } from "react";
import moment from "moment";
import { useParams } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { getPost } from "../../redux/posts/posts.actions";
import LinkButton from "../../components/molecules/LinkButton/LinkButton.component";
import Spinner from "../../components/molecules/Spinner/Spinner.component";
import AnswerSection from "./AnswerSection/AnswerSection.component";
import QuestionSection from "./QuestionSection/QuestionSection.component";
import "./Post.styles.scss";
import censorBadWords from "../../utils/censorBadWords";
const Post = ({ getPost, post: { post, loading } }) => {
const { id } = useParams();
useEffect(() => {
getPost(id);
// eslint-disable-next-line
}, [getPost]);
return loading || post === null ? (
<Spinner type="page" width="75px" height="200px" />
) : (
<Fragment>
<div id="mainbar" className="post">
<div className="question-header fc-black-800 pl24">
<h1>{censorBadWords(post.title)}</h1>
<div>
<LinkButton
text={"Ask Question"}
link={"/add/question"}
type={"s-btn__primary"}
/>
</div>
</div>
<div className="question-date fc-black-800 pl24">
<div className="grid-cell">
<span className="fc-light">Asked</span>
<time dateTime={moment(post.created_at).fromNow(true)}>
{moment(post.created_at).fromNow(true)} ago
</time>
</div>
</div>
<div className="question-main pl24 pt16">
<QuestionSection />
<AnswerSection />
</div>
</div>
</Fragment>
);
};
Post.propTypes = {
getPost: PropTypes.func.isRequired,
post: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
post: state.post,
});
export default connect(mapStateToProps, { getPost })(Post);
================================================
FILE: src/modules/Post/Post.styles.scss
================================================
.page {
width: 100%;
background: none;
display: flex;
justify-content: space-between;
margin: 64px auto 0 auto;
}
#content {
max-width: none;
padding: 0;
}
#mainbar {
padding-top: 14px;
}
.post {
width: calc(100% - 300px - 24px);
padding: 24px 0 24px 0;
float: left;
margin: 0;
display: flex;
flex-direction: column;
.question-header {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
color: #242729;
h1 {
width: 80%;
margin-bottom: 8px;
font-size: 27px;
font-weight: 400;
font-family: Arial,"Helvetica Neue",Helvetica,sans-serif;
flex: 1 auto !important;
line-height: 1.3;
}
div {
margin-left: 12px;
color: #242729;
.ask-button {
color:#fff;
background-color: #0095ff;
box-shadow: inset 0 1px 0 0 rgba(255,255,255,0.4);
position: relative;
display: inline-block;
padding: .8em;
border: 1px solid transparent;
border-radius: 3px;
outline: none;
font-family: inherit;
font-size: 13px;
font-weight: normal;
line-height: 1.15384615;
text-align: center;
text-decoration: none;
cursor: pointer;
white-space: nowrap !important;
&:hover {
background-color: #0077cc;
}
}
}
}
.question-date {
padding-bottom: 8px;
margin-bottom: 16px;
display: flex;
flex-wrap: wrap;
.grid-cell {
white-space: nowrap;
margin-bottom: 8px;
margin-right: 16px;
box-sizing: inherit;
font-size: 13px;
span {
color: #6a737c;
margin-right: 4px !important;
}
}
}
.question-main {
float: none;
clear: both;
width: auto;
margin-bottom: 20px;
border-top: 1px solid #4a4e51;
}
}
.fc-blue-600:hover {
color: #378ad3 !important;
}
@media(max-width: 1100px) {
.post {
width: 80%;
}
}
================================================
FILE: src/modules/Post/QuestionSection/CommentCell/CommentCell.component.jsx
================================================
import React, { useEffect, Fragment, useState } from "react";
import moment from "moment";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import {
getComments,
deleteComment,
addComment,
} from "../../../../redux/comments/comments.actions";
import Spinner from "../../../../components/molecules/Spinner/Spinner.component";
import TagBadge from "../../../../components/molecules/TagBadge/TagBadge.component";
import LinkButton from "../../../../components/molecules/LinkButton/LinkButton.component";
import "./CommentCell.styles.scss";
import censorBadWords from "../../../../utils/censorBadWords";
const CommentCell = ({
deleteComment,
addComment,
getComments,
auth,
comment,
post: { post },
}) => {
useEffect(() => {
getComments(post.id);
// eslint-disable-next-line
}, [getComments]);
const [formData, setFormData] = useState({
body: "",
});
const { body } = formData;
const handleChange = (e) =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const handleSubmit = async (e) => {
e.preventDefault();
addComment(post.id, { body });
setFormData({
body: "",
});
};
return (
<Fragment>
<div className="comments-cell">
<div className="comments">
<ul className="comments-list">
{comment.loading === null ? (
<Spinner width="25px" height="25px" />
) : (
comment.comments.map((comment, index) => (
<li key={index} className="comments-item">
<div className="comment-text fc-black-800">
<div className="comment-body">
<span className="body">
{censorBadWords(comment.body)}
</span>
–
<TagBadge
tag_name={comment.username}
size={"s-tag"}
link={`/users/${comment.user_id}`}
display={"inline"}
/>
<span
title={moment(comment.created_at).fromNow(true)}
style={{ color: "#959ca3 !important" }}
className="date fs-body1"
>
{moment(comment.created_at).fromNow(true)} ago
</span>
</div>
{!auth.loading &&
auth.isAuthenticated &&
comment.user_id === auth.user.id && (
<Link
className="s-tag s-tag__moderator"
style={{ marginTop: "4px" }}
title="Delete the comment"
onClick={(e) => deleteComment(comment.id)}
to={`/questions/${post.id}`}
>
delete
</Link>
)}
</div>
</li>
))
)}
</ul>
</div>
<div className="add-comment">
{!auth.loading && auth.isAuthenticated ? (
<Fragment>
<form className="comment-form" onSubmit={(e) => handleSubmit(e)}>
<div>
<input
className="title-input s-input"
type="text"
name="body"
value={body}
onChange={(e) => handleChange(e)}
id="title"
placeholder="Leave a comment"
/>
</div>
</form>
</Fragment>
) : (
<Fragment>
<LinkButton
text={"You need to login to add a comment"}
link={"/login"}
/>
</Fragment>
)}
</div>
</div>
</Fragment>
);
};
CommentCell.propTypes = {
auth: PropTypes.object.isRequired,
post: PropTypes.object.isRequired,
addComment: PropTypes.func.isRequired,
deleteComment: PropTypes.func.isRequired,
getComments: PropTypes.func.isRequired,
comment: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
auth: state.auth,
post: state.post,
comment: state.comment,
});
export default connect(mapStateToProps, {
deleteComment,
getComments,
addComment,
})(CommentCell);
================================================
FILE: src/modules/Post/QuestionSection/CommentCell/CommentCell.styles.scss
================================================
.comments-cell {
padding-right: 16px;
grid-column: 2;
width: auto;
min-width: 0;
.comments {
width: 100%;
-webkit-tap-highlight-color: rgba(255,255,255,0);
padding-bottom: 10px;
margin-top: 12px !important;
border-top: 1px solid #4a4e51;
display: block;
.comments-list {
list-style-type: none;
margin: 0;
padding: 0;
display: block;
.comments-item {
display: contents;
font-size: 13px;
color: rgb(36, 39, 41);
.comment-text {
min-width: 0;
flex-basis: 0;
padding: 6px;
border-bottom: 1px solid #4a4e51;
flex-grow: 1;
font-size: 13px;
line-height: 1.3;
vertical-align: text-top;
.comment-body {
line-height: 1.7;
word-wrap: break-word;
.body {
font-family: Arial,"Helvetica Neue",Helvetica,sans-serif;
}
.date {
color: #9199a1;
margin-left: 5px;
}
}
.comment-links {
padding: 0 4px 2px;
color: #848d95;
text-decoration: none;
background-color: transparent;
&:hover {
color: #3c4146;
text-decoration: none;
}
}
}
}
}
}
}
================================================
FILE: src/modules/Post/QuestionSection/PostCell/PostCell.component.jsx
================================================
import React, { Fragment } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { deletePost } from "../../../../redux/posts/posts.actions";
import TagBadge from "../../../../components/molecules/TagBadge/TagBadge.component";
import UserCard from "../../../../components/molecules/UserCard/UserCard.component";
import "./PostCell.styles.scss";
import censorBadWords from "../../../../utils/censorBadWords";
const PostCell = ({
deletePost,
auth,
post: {
post: { id, post_body, tags, gravatar, user_id, username, created_at },
},
}) => {
return (
<Fragment>
<div className="post-cell">
<div
className="post-text fc-black-800"
dangerouslySetInnerHTML={{ __html: censorBadWords(post_body) }}
></div>
<div className="post-tags fc-black-800">
{tags.map((tag, index) => (
<TagBadge
key={index}
tag_name={tag.tagname}
size={"s-tag"}
float={"left"}
/>
))}
</div>
<div className="post-actions fc-black-800">
<div className="post-actions-extended">
<div className="post-btns">
<div className="post-menu">
<Link
className="post-links"
title="short permalink to this question"
to="/"
>
share
</Link>
<Link
className="post-links"
title="Follow this question to receive notifications"
to="/"
>
follow
</Link>
{!auth.loading &&
auth.isAuthenticated &&
user_id === auth.user.id && (
<Link
className="s-link s-link__danger"
style={{ paddingLeft: "4px" }}
title="Delete the post"
onClick={(e) => deletePost(id)}
to="/questions"
>
delete
</Link>
)}
</div>
</div>
<UserCard
created_at={created_at}
user_id={user_id}
gravatar={gravatar}
username={username}
/>
</div>
</div>
</div>
</Fragment>
);
};
PostCell.propTypes = {
post: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
deletePost: PropTypes.func.isRequired,
};
const mapStateToProps = (state) => ({
post: state.post,
auth: state.auth,
});
export default connect(mapStateToProps, { deletePost })(PostCell);
================================================
FILE: src/modules/Post/QuestionSection/PostCell/PostCell.styles.scss
================================================
.post-cell {
display: flex;
flex-direction: column;
vertical-align: top;
padding-right: 16px;
grid-column: 2;
width: auto;
min-width: 0;
pre {
color: var(--black-800);
}
.post-text {
width: 100%;
margin-bottom: 5px;
font-size: 15px;
line-height: 1.3;
font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
color: #242729;
}
.post-tags {
margin-bottom: 10px;
clear: both;
.tag-cell {
display: block;
}
}
.post-actions {
margin-bottom: 0;
.post-actions-extended {
padding-top: 4px;
margin-top: 16px;
display: flex;
align-items: flex-start;
flex-wrap: wrap;
.post-btns {
flex: 1 1 100px;
margin-top: 4px;
margin-bottom: 4px;
margin-right: 16px !important;
.post-menu {
padding-top: 2px;
.post-links {
padding: 0 4px 2px 4px;
color: #848d95;
cursor: pointer;
text-decoration: none;
font-size: 13px;
&:hover {
color: #cfd2d6;
}
}
}
}
}
}
}
================================================
FILE: src/modules/Post/QuestionSection/QuestionSection.component.jsx
================================================
import React, {Fragment} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import CommentCell from './CommentCell/CommentCell.component';
import VoteCell from './VoteCell/VoteCell.component';
import PostCell from './PostCell/PostCell.component';
import './QuestionSection.styles.scss';
const QuestionSection = ({
post: {
post: {answer_count, comment_count, tags},
},
}) => {
return (
<Fragment>
<div className='question'>
<div className='post-layout'>
<VoteCell
answerCount={answer_count}
commentCount={comment_count}
tagCount={tags ? tags.length : 0}
/>
<PostCell/>
<CommentCell/>
</div>
</div>
</Fragment>
);
};
QuestionSection.propTypes = {
post: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
post: state.post,
});
export default connect(mapStateToProps, null)(QuestionSection);
================================================
FILE: src/modules/Post/QuestionSection/QuestionSection.styles.scss
================================================
.question {
.post-layout {
display: grid;
grid-template-columns: max-content 1fr;
color: #242729;
}
}
================================================
FILE: src/modules/Post/QuestionSection/VoteCell/VoteCell.component.jsx
================================================
import React, {Fragment} from 'react';
import './VoteCell.styles.scss';
const VoteCell = ({answerCount, commentCount, tagCount}) => {
return (
<Fragment>
<div className='vote-cell fc-black-800'>
<div className='stats'>
<div className='vote'>
<span className='vote-count'>{answerCount}</span>
<div className='count-text'>answers</div>
</div>
<div className='vote'>
<span className='vote-count'>{commentCount}</span>
<div className='count-text'>comments</div>
</div>
<div className='vote'>
<span className='vote-count'>{tagCount}</span>
<div className='count-text'>tags</div>
</div>
</div>
</div>
</Fragment>
);
};
export default VoteCell;
================================================
FILE: src/modules/Post/QuestionSection/VoteCell/VoteCell.styles.scss
================================================
.vote-cell {
width: auto;
padding-right: 16px;
vertical-align: top;
grid-column: 1;
grid-column-start: 1;
grid-column-end: auto;
color:#6a737c;
.vote {
padding: 0;
margin-bottom: 8px;
text-align: center;
.vote-count {
font-size: 20px;
}
.count-text {
font-size: 12px;
}
}
}
================================================
FILE: src/modules/PostForm/AskForm/AskForm.component.jsx
================================================
import React, { Fragment, useState, useEffect, useRef } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { addPost } from "../../../redux/posts/posts.actions";
import MarkdownEditor from "../../../components/organisms/MarkdownEditor/MarkdownEditor.component";
import { badWordsFilter } from "../../../utils/censorBadWords";
import "./AskForm.styles.scss";
const AskForm = ({ addPost }) => {
const [formData, setFormData] = useState({
title: "",
body: "",
tagname: "",
});
const [formErrors, setFormErrors] = useState({});
useEffect(() => {
setFormErrors({});
}, [formData]);
const markdownEditorRef = useRef(null);
const { title, body, tagname } = formData;
const onChange = (e) =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const validateFormData = () => {
const errors = [];
const tags = formData.tagname
.split(",")
.filter(Boolean)
.map((tag) => tag.trim());
tags.forEach((tag) => {
if (tag.length > 25) {
errors.push({
tagname: `A tag name can't be longer than 25 characters.`,
});
} else if (/[^a-zA-Z]/.test(tag)) {
errors.push({
tagname: `${tag} tag must contain English alphabets only (no spaces).`,
});
}
});
if (badWordsFilter.isProfane(formData.tagname)) {
errors.push({ tagname: "Inappropriate words are not allowed." });
}
errors
.reverse()
.forEach((err) => setFormErrors((prev) => ({ ...prev, ...err })));
return errors;
};
const onSubmit = async (e) => {
e.preventDefault();
const errors = validateFormData();
// if there are errors, don't submit
if (errors.length > 0) return;
addPost({ title, body, tagname });
setFormData({
title: "",
body: "",
tagname: "",
});
markdownEditorRef.current.cleanEditorState();
};
const updateConvertedContent = (htmlConvertedContent) => {
setFormData({ ...formData, body: htmlConvertedContent });
};
return (
<Fragment>
<form onSubmit={(e) => onSubmit(e)}>
<div className="question-form p16 s-card">
<div className="question-layout">
<div className="title-grid">
<label className="form-label s-label">
Title
<p className="title-desc fw-normal fs-caption">
Be specific and imagine you’re asking a question to another
person
</p>
</label>
<input
className="title-input s-input"
type="text"
name="title"
value={title}
onChange={(e) => onChange(e)}
id="title"
placeholder="e.g. Is there an R function for finding the index of an element in a vector?"
required
/>
</div>
<div className="body-grid">
<label className="form-label s-label fc-black-800">
Body
<p className="body-desc fw-normal fs-caption fc-black-800">
Include all the information someone would need to answer your
question
</p>
</label>
<div className="s-textarea rich-text-editor-container">
<MarkdownEditor
ref={markdownEditorRef}
onChange={updateConvertedContent}
/>
</div>
</div>
<div className="tag-grid">
<label className="form-label s-label">
Tag Name
<p className="tag-desc fw-normal fs-caption">
Add up to 5 tags to describe what your question is about
</p>
</label>
<input
className="tag-input s-input"
type="text"
name="tagname"
value={tagname}
onChange={(e) => onChange(e)}
id="tagname"
placeholder="e.g. (ajax, django, string)"
required
/>
<p className="fc-error fw-bold ml8 mt4">{formErrors.tagname}</p>
</div>
</div>
</div>
<div className="post-button mt32">
<button
className="s-btn s-btn__primary"
id="submit-button"
name="submit-button"
>
Post your question
</button>
</div>
</form>
</Fragment>
);
};
AskForm.propTypes = {
addPost: PropTypes.func.isRequired,
};
export default connect(null, { addPost })(AskForm);
================================================
FILE: src/modules/PostForm/AskForm/AskForm.styles.scss
================================================
.post-form {
min-width: 0 !important;
flex: 1 auto !important;
color: #242729;
width: 100%;
padding: 0 !important;
.question-form {
color: #242729;
.post-button {
margin-top: 32px !important;
button {
margin: 0 2px 0 2px;
color: #fff;
background-color: #0095ff;
box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.4);
padding: 0.8em;
border: 1px solid transparent;
border-radius: 3px;
outline: none;
font-family: inherit;
font-size: 13px;
font-weight: normal;
line-height: 1.15384615;
text-align: center;
text-decoration: none;
cursor: pointer;
&:hover {
color: #fff;
background-color: #0077cc;
text-decoration: none;
}
}
}
.question-layout {
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: 16px !important;
background-color: #fff !important;
border-color: #d6d9dc !important;
border-radius: 3px !important;
border-style: solid !important;
border-width: 1px !important;
.title-grid {
margin-bottom: 16px;
display: flex;
flex: 1 auto !important;
flex-direction: column;
color: #242729;
label {
margin-bottom: 4px;
display: block;
padding: 0 2px;
color: #0c0d0e;
font-family: inherit;
font-size: 15px;
font-weight: 700;
cursor: pointer;
.title-desc {
font-weight: normal;
padding: 0;
margin: 0;
margin-top: 2px !important;
color: #3c4146;
font-size: 12px;
clear: both;
}
}
.title-input {
flex: 1 auto !important;
font-size: 13px;
-webkit-appearance: none;
width: 100%;
margin: 0;
padding: 0.6em 0.7em;
border: 1px solid #bbc0c4;
border-radius: 3px;
background-color: #fff;
color: #0c0d0e;
line-height: 1.15384615;
&::placeholder {
opacity: 0.6;
}
}
}
.tag-grid {
margin-bottom: 16px;
display: flex;
flex: 1 auto !important;
flex-direction: column;
color: #242729;
label {
margin-bottom: 4px;
display: block;
padding: 0 2px;
color: #0c0d0e;
font-family: inherit;
font-size: 15px;
font-weight: 700;
cursor: pointer;
.tag-desc {
font-weight: normal;
padding: 0;
margin: 0;
margin-top: 2px !important;
color: #3c4146;
font-size: 12px;
clear: both;
}
}
.tag-input {
flex: 1 auto !important;
font-size: 13px;
-webkit-appearance: none;
width: 100%;
margin: 0;
padding: 0.6em 0.7em;
border: 1px solid #bbc0c4;
border-radius: 3px;
background-color: #fff;
color: #0c0d0e;
line-height: 1.15384615;
&::placeholder {
opacity: 0.6;
}
}
}
}
}
}
.body-grid {
margin-top: 16px;
margin-bottom: 16px;
display: flex;
flex: 1 auto !important;
flex-direction: column;
color: #242729;
label {
margin-bottom: 4px;
display: block;
padding: 0 2px;
color: #0c0d0e;
font-family: inherit;
font-size: 15px;
font-weight: 700;
cursor: pointer;
.body-desc {
font-weight: normal;
padding: 0;
margin: 0;
margin-top: 2px !important;
color: #3c4146;
font-size: 12px;
clear: both;
}
}
.s-textarea.rich-text-editor-container {
padding: 0;
}
.body-input {
flex: 1 auto !important;
font-size: 13px;
-webkit-appearance: none;
width: 100%;
margin: 0;
padding: 0.6em 0.7em;
border: 1px solid #bbc0c4;
border-radius: 3px;
background-color: #fff;
color: #0c0d0e;
line-height: 1.15384615;
&::placeholder {
opacity: 0.6;
}
}
}
================================================
FILE: src/modules/PostForm/AskWidget/AskWidget.component.jsx
================================================
import React, {Fragment} from 'react';
import './AskWidget.styles.scss';
const AskWidget = () => {
return (
<Fragment>
<div className='widget'>
<div className='s-sidebarwidget--header'>
Step 1: Draft your question
</div>
<div className='widget-content fc-black-800'>
<div className='summary'>
<p className='sec1'>
The community is here to help you with specific coding, algorithm,
or language problems.
</p>
<p className='sec2'>Avoid asking opinion-based questions.</p>
</div>
<ol className='step-section'>
<li className='step'>
<button>
<div className='step-cell'>
<div>
<img
src='https://cdn.sstatic.net/Img/list-1.svg?v=e8dd475ba207'
width='16'
height='16'
alt='1.'
/>
</div>
<span>Summarize the problem</span>
</div>
</button>
<div className='inst'>
<div className='inst-content'>
<ul>
<li>
<p>Include details about your goal</p>
</li>
<li>
<p>Describe expected and actual results</p>
</li>
<li>
<p className='except'>Include any error messages</p>
</li>
</ul>
</div>
</div>
</li>
<li className='step'>
<button>
<div className='step-cell'>
<div>
<img
src='https://cdn.sstatic.net/Img/list-2.svg?v=9382fc2c3631'
width='16'
height='16'
alt='2.'
/>
</div>
<span>Summarize the problem</span>
</div>
</button>
<div className='inst'>
<div className='inst-content'>
<p className='step2'>
Show what you’ve tried and tell us what you found (on this
site or elsewhere) and why it didn’t meet your needs. You
can get better answers when you provide research.
</p>
</div>
</div>
</li>
<li
style={{
borderBottomRightRadius: '3px',
borderBottomLeftRadius: '3px',
}}
className='step except-step'
>
<button>
<div className='step-cell'>
<div>
<img
src='https://cdn.sstatic.net/Img/list-3.svg?v=323a95564232'
width='16'
height='16'
alt='3.'
/>
</div>
<span>Summarize the problem</span>
</div>
</button>
<div className='inst'>
<div className='inst-content'>
<p className='step3'>
When appropriate, share the minimum amount of code others
need to reproduce your problem (also called a minimum,
reproducible example)
</p>
</div>
</div>
</li>
</ol>
</div>
</div>
</Fragment>
);
};
export default AskWidget;
================================================
FILE: src/modules/PostForm/AskWidget/AskWidget.styles.scss
================================================
.widget {
margin-bottom: 24px;
position: relative;
border-radius: 3px;
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);
font-size: 13px;
background-color: #fff;
color: #242729;
.widget-header {
padding: 12px 15px;
background: #fafafb;
color: #6a737c;
border-top: 1px solid #e4e6e8;
font-size: 15px;
font-weight: normal;
border-top-right-radius: 3px;
border-top-left-radius: 3px;
}
.widget-content {
display: block !important;
background-color: #2d2d2d !important;
padding: 16px 15px 0 15px;
border-top: 1px solid #404345;
color: #242729;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
.summary {
margin-bottom: 16px;
.sec1 {
margin-bottom: 13px;
}
.sec2 {
margin-bottom: 13px;
}
}
.step-section {
margin-left: -16px;
margin-right: -16px;
margin-bottom: 0;
border-radius: 3px;
.step {
border-bottom: 1px solid #404345;
list-style: none;
button {
display: flex;
width: 100% !important;
cursor: pointer;
padding: 12px 16px 12px 16px;
border: none;
outline: none;
font: unset;
border-radius: 0;
color: unset;
background: none;
box-shadow: none;
.step-cell {
display: flex;
flex: 1 auto;
align-items: center;
div {
word-spacing: normal;
img {
width: 16px;
height: 16px;
}
}
span {
font-weight: 700 !important;
margin-left: 12px !important;
cursor: pointer;
}
}
}
.inst {
margin-left: 16px !important;
margin-right: 16px !important;
display: flex;
align-items: flex-start;
.inst-content {
padding-left: 4px !important;
padding-bottom: 12px !important;
margin-left: 16px;
margin-bottom: 0;
.step2, .step3 {
margin-left: 12px !important;
}
ul {
margin-left: 24px !important;
margin-bottom: 0;
list-style-type: disc;
p {
clear: both;
margin-bottom: 13px;
margin-top: 0;
}
.except {
margin-bottom: 0;
}
}
}
}
}
}
}
}
================================================
FILE: src/modules/PostForm/PostForm.component.jsx
================================================
import React, {Fragment} from 'react';
import {connect} from 'react-redux';
import {Redirect} from 'react-router-dom';
import PropTypes from 'prop-types';
import Spinner from '../../components/molecules/Spinner/Spinner.component';
import AskWidget from './AskWidget/AskWidget.component';
import AskForm from './AskForm/AskForm.component';
import Footer from "../../components/organisms/Footer/Footer.component";
import './PostForm.styles.scss';
const PostForm = ({auth: {isAuthenticated, loading}}) => {
if (!isAuthenticated) {
return <Redirect to='/login' />;
}
return loading === null ? (
<Spinner type='page' width='75px' height='200px' />
) : (
<Fragment>
<div className='post-form-container'>
<div className='post-form-content'>
<div className='post-form-header'>
<div className='post-form-headline fc-black-800'>
Ask a public question
</div>
</div>
<div className='post-form-section'>
<div className='postform' style={{width: '100%'}}>
<AskForm />
</div>
<aside>
<div className='right-panel'>
<AskWidget />
</div>
</aside>
</div>
</div>
</div>
<Footer/>
</Fragment>
);
};
PostForm.propTypes = {
auth: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
auth: state.auth,
});
export default connect(mapStateToProps, null)(PostForm);
================================================
FILE: src/modules/PostForm/PostForm.styles.scss
================================================
.post-form-container {
width: 100%;
height: calc(100vh - 64px);
max-width: 100%;
display: flex;
justify-content: center;
margin: 64px 0 0 0;
background-color: #3d3d3d;
position: relative;
flex: 1 0 auto;
color: #242729;
.post-form-content {
min-height: 750px;
overflow: visible;
width: 100%;
margin: 0;
background-color: transparent;
padding-top: 0;
padding-left: 24px;
padding-right: 24px;
.post-form-header {
height: 130px;
background-image: url("https://cdn.sstatic.net/img/ask/background.svg?v=2e9a8205b368");
background-repeat: no-repeat;
background-position: right bottom !important;
padding-top: 24px !important;
padding-bottom: 24px !important;
padding-left: 4px;
display: flex;
align-items: center;
.post-form-headline {
font-size: 27px;
color: #242729;
}
}
.post-form-section {
display: flex;
align-items: flex-start;
justify-content: space-between;
color: #242729;
aside {
flex-shrink: 0;
color: #242729;
.right-panel {
width: 300px;
margin-left: 24px;
}
}
}
}
}
================================================
FILE: src/modules/ProfilePage/ExternalUserDetails/ExternalUserDetails.component.jsx
================================================
import React from "react";
import {Link} from "react-router-dom";
import {ReactComponent as StackExchangeLogo} from "../../../assets/StackExchange.svg";
import {ReactComponent as Logo} from "../../../assets/LogoGlyphMd.svg";
import './ExternalUserDetails.styles.scss';
const ExternalUserDetails = () => (
<div className='grid-cell1'>
<div className='cell-layout'>
<div className='community'>
<h3 className='bc-black-3'>
<span className='icon'>
<StackExchangeLogo/>
</span>
<span className='text fw-bold fc-dark bc-black-3'>
Communities
</span>
</h3>
<ul>
<li className='item'>
<Link to='/'>
<span>
<Logo className='logo' />
</span>
<span className='fc-blue-600 fs-body2'>
Stack Overflow
</span>
</Link>
</li>
</ul>
</div>
<div className='user-posts'>
<h3 className='fw-bold fc-dark bc-black-3'>
Top network posts
</h3>
<p className='fc-light'>
We respect a laser-like focus on one topic.
</p>
</div>
</div>
</div>
)
export default ExternalUserDetails;
================================================
FILE: src/modules/ProfilePage/ExternalUserDetails/ExternalUserDetails.styles.scss
================================================
.grid-cell1 {
margin: 12px;
width: 100%;
max-width: 210px;
.cell-layout {
display: flex;
flex-direction: column;
.community {
margin: 16px 0 16px 0;
h3 {
padding-bottom: 6px !important;
border-bottom: 1px solid #d6d9dc;
font-size: 15px;
font-weight: 600;
display: flex;
align-items: center;
.icon {
margin: 2px;
}
.text {
margin: 2px;
}
}
ul {
list-style: none;
padding: 0;
margin-top: 8px;
display: flex;
flex-direction: column;
.item {
margin: 8px 0 8px 0;
a {
font-size: 13px;
margin-left: -2px;
margin-right: -2px;
display: flex;
align-items: center;
color: #0077cc;
text-decoration: none;
cursor: pointer;
&:hover {
color: #0095ff;
text-decoration: none;
}
span {
margin: 0 2px 0 2px;
}
.logo {
width: 18px;
height: 18px;
}
}
}
}
}
.user-posts {
margin: 16px 0 16px 0;
h3 {
font-weight: 700;
color: #0c0d0e;
padding-bottom: 8px;
border-bottom: 1px solid #d6d9dc;
font-size: 15px;
margin-bottom: 15px;
}
p {
color: #6a737c;
font-size: 12px;
padding-right: 16px;
margin-bottom: 12px;
}
}
}
}
================================================
FILE: src/modules/ProfilePage/ProfilePage.component.jsx
================================================
import React, {useEffect, Fragment} from 'react';
import { connect } from 'react-redux';
import { Link, useParams } from "react-router-dom";
import PropTypes from 'prop-types';
import { getProfile } from '../../redux/users/users.actions';
import UserSection from "./UserSection/UserSection.component";
import Spinner from '../../components/molecules/Spinner/Spinner.component';
import ExternalUserDetails from "./ExternalUserDetails/ExternalUserDetails.component";
import UserActivity from "./UserActivity/UserActivity.component";
import './ProfilePage.styles.scss';
const ProfilePage = ({getProfile, user: {user, loading}}) => {
const { id } = useParams();
useEffect(() => {
getProfile(id);
// eslint-disable-next-line
}, [getProfile]);
return loading || user === null ? (
<Spinner type='page' width='75px' height='200px' />
) : (
<Fragment>
<div id='mainbar' className='user-main-bar pl24 pt24'>
<div className='user-card'>
<div className='grid--cell s-navigation mb16'>
<Link
to='#'
className='s-navigation--item is-selected'
data-shortcut='P'
>
Profile
</Link>
<Link to='#' className='s-navigation--item' data-shortcut='A'>
Activity
</Link>
</div>
<UserSection user={user}/>
</div>
<div className='row-grid'>
<ExternalUserDetails/>
<UserActivity/>
</div>
</div>
</Fragment>
);
};
ProfilePage.propTypes = {
getProfile: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
user: state.user,
});
export default connect(mapStateToProps, {getProfile})(ProfilePage);
================================================
FILE: src/modules/ProfilePage/ProfilePage.styles.scss
================================================
.page {
display: flex;
}
.user-main-bar {
width: calc(100% - 300px - 24px);
padding: 24px 0 24px 0;
float: left;
margin: 0;
.user-card {
box-sizing: border-box;
display: block;
color: #242729;
}
.row-grid {
padding-bottom: 30px;
box-sizing: border-box;
width: 100%;
margin: -12px;
display: flex;
}
}
================================================
FILE: src/modules/ProfilePage/UserActivity/UserActivity.component.jsx
================================================
import React from "react";
import TagBadge from "../../../components/molecules/TagBadge/TagBadge.component";
import './UserActivity.styles.scss';
const UserActivity = () => (
<div className='grid-cell2'>
<div className='top-tags'>
<h3 className='fw-bold fc-dark bc-black-3'>Top Tags</h3>
<div className='top-tags-sec'>
<div className='top-tags-cells'>
<div className='top-cell'>
<div className='tag-cell bg-black-025'>
<TagBadge
tag_name={'java'}
size={'s-tag s-tag__lg'}
float={'left'}
/>
<div className='score'>
<div className='score-txt'>
<div className='score-tab'>
<span className='txt fc-light'>Posts</span>
<span className='number fc-black-800'>2</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div className='top-tags-cells'>
<div className='top-cell'>
<div className='tag-cell bg-black-025'>
<TagBadge
tag_name={'node.js'}
size={'s-tag s-tag__md'}
float={'left'}
/>
<div className='score'>
<div className='score-txt'>
<div className='score-tab'>
<span className='txt fc-light'>Posts</span>
<span className='number fc-black-800'>1</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div className='top-tags-cells'>
<div className='top-cell'>
<div className='tag-cell bg-black-025'>
<TagBadge
tag_name={'react'}
size={'s-tag s-tag__md'}
float={'left'}
/>
<div className='score'>
<div className='score-txt'>
<div className='score-tab'>
<span className='txt fc-light'>Posts</span>
<span className='number fc-black-800'>0</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
export default UserActivity;
================================================
FILE: src/modules/ProfilePage/UserActivity/UserActivity.styles.scss
================================================
.grid-cell2 {
margin: 12px;
flex: 1 auto !important;
color: #242729;
.top-tags {
margin-top: 17px;
margin-bottom: 48px !important;
h3 {
font-weight: 700;
color: #0c0d0e;
padding-bottom: 8px !important;
border-bottom: 1px solid #d6d9dc;
font-size: 15px;
margin-bottom: 15px;
}
.top-tags-sec {
margin: -4px 0 -4px 0;
display: flex;
flex-direction: column;
.top-tags-cells {
margin: 4px;
.top-cell {
margin: -4px;
display: flex;
.tag-cell {
margin: 4px;
padding: 8px;
display: flex;
flex: 1 auto;
background-color: #fafafb;
border-radius: 3px;
color: #242729;
}
.score {
display: flex;
justify-content: flex-end;
flex: 1 auto;
.score-txt {
display: flex;
.score-tab {
margin: 0 8px 0 8px;
height: 100%;
font-size: 11px;
display: flex;
align-items: center;
color: #9199a1;
.txt {
text-transform: uppercase;
font-weight: 700;
margin-right: 6px;
}
.number {
color: #3c4146;
font-size: 15px;
}
}
}
}
}
}
}
}
}
================================================
FILE: src/modules/ProfilePage/UserSection/AvatarCard/AvatarCard.component.jsx
================================================
import React from "react";
import {Link} from "react-router-dom";
import './AvatarCard.styles.scss';
const AvatarCard = ({ id, gravatar, views }) => (
<div className='img-card'>
<div className='avatar-card'>
<div className='avatar'>
<Link className='avatar-link' to={`/users/${id}`}>
<div className='logo-wrapper'>
<img
src={gravatar}
alt='user-logo'
/>
</div>
</Link>
</div>
<div className='title'>
<div className='grid fc-black-800'>
{views}
<span className='fc-light'>PROFILE VIEWS</span>
</div>
</div>
</div>
</div>
)
export default AvatarCard;
================================================
FILE: src/modules/ProfilePage/UserSection/AvatarCard/AvatarCard.styles.scss
================================================
.img-card {
box-sizing: border-box;
margin: 12px;
width: 210px;
overflow: hidden;
.avatar-card {
box-shadow: inset 0 10em 0 #3d3d3d !important;
text-align: center;
padding: 12px !important;
margin-bottom: 16px;
border: 1px solid #4a4e51 !important;
border-radius: 3px;
background-color: #2d2d2d;
.avatar {
width: 164px;
height: 164px;
overflow: hidden !important;
margin-left: auto;
margin-right: auto;
margin-top: 16px;
color: #242729;
.avatar-link {
.logo-wrapper {
border-radius: 3px;
width: 164px;
height: 164px;
img {
width: 164px;
height: 164px;
border-radius: 3px;
}
}
}
}
.title {
font-weight: 400;
margin-top: 12px !important;
margin-bottom: 12px !important;
.grid {
color: #0c0d0e;
font-size: 21px;
display: flex;
margin: -4px;
justify-content: center !important;
align-items: center !important;
span {
color: #6a737c;
font-size: 11px;
}
}
}
}
}
================================================
FILE: src/modules/ProfilePage/UserSection/ContentCard/ContentCard.component.jsx
================================================
import React from "react";
import moment from "moment";
import './ContentCard.styles.scss';
const ContentCard = ({ username, answers_count, posts_count, comments_count, tags_count, created_at }) => (
<div className='content-card'>
<div className='content-grid'>
<div className='info-cell'>
<div className='info'>
<div className='details'>
<h2>{username}</h2>
</div>
<div className='date'>
<p>
user created -
{moment(created_at).fromNow(false)}
</p>
</div>
</div>
</div>
<div className='stats-cell'>
<div className='count-sec'>
<div className='counts'>
<div className='cells'>
<div className='column-grid'>
<div className='head fc-black-700'>
{answers_count}
</div>
<div className='foot fc-black-500'>answers</div>
</div>
</div>
<div className='cells'>
<div className='column-grid'>
<div className='head fc-black-700'>
{posts_count}
</div>
<div className='foot fc-black-500'>questions</div>
</div>
</div>
<div className='cells'>
<div className='column-grid'>
<div className='head fc-black-700'>
{comments_count}
</div>
<div className='foot fc-black-500'>comments</div>
</div>
</div>
<div className='cells'>
<div className='column-grid'>
<div className='head fc-black-700'>
{tags_count}
</div>
<div className='foot fc-black-500'>tags</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
export default ContentCard;
================================================
FILE: src/modules/ProfilePage/UserSection/ContentCard/ContentCard.styles.scss
================================================
.content-card {
box-sizing: border-box;
margin: 12px;
flex: 1 auto !important;
.content-grid {
margin: -8px;
display: flex;
flex-direction: column;
.info-cell {
margin: 8px;
//height: 278px;
padding-right: 16px !important;
flex: 1 auto;
color: #242729;
.info {
display: flex;
flex-direction: column !important;
.details {
margin-top: 4px;
margin-bottom: 4px;
h2 {
word-break: break-all !important;
color: #e7e8eb !important;
font-size: 27px;
display: flex;
align-items: center;
flex-wrap: wrap;
}
}
.date {
margin-top: 16px;
margin-bottom: 4px;
color: #acb2b8 !important;
font-size: 15px;
}
}
}
.stats-cell {
margin: 8px;
padding-right: 24px !important;
.count-sec {
color: #3c4146 !important;
margin-bottom: 16px !important;
.counts {
margin: -6px;
display: flex;
.cells {
margin: 6px;
.column-grid {
display: flex;
flex-direction: column;
.head {
font-weight: 700 !important;
font-size: 17px;
text-align: center;
}
.foot {
font-size: 13px;
}
}
}
}
}
}
}
}
================================================
FILE: src/modules/ProfilePage/UserSection/UserSection.component.jsx
================================================
import React from "react";
import AvatarCard from "./AvatarCard/AvatarCard.component";
import ContentCard from "./ContentCard/ContentCard.component";
import './UserSection.styles.scss';
const UserSection = ({ user }) => (
<div className='grid'>
<AvatarCard
id={user.id}
gravatar={user.gravatar}
views={user.views}
/>
<ContentCard
username={user.username}
answers_count={user.answers_count}
posts_count={user.posts_count}
comments_count={user.comments_count}
tags_count={user.tags_count}
created_at={user.created_at}
/>
</div>
)
export default UserSection;
================================================
FILE: src/modules/ProfilePage/UserSection/UserSection.styles.scss
================================================
.grid {
box-sizing: border-box;
margin: -12px;
display: flex;
color: #242729;
}
================================================
FILE: src/modules/QuestionsPage/QuestionsPage.component.jsx
================================================
import React, {Fragment, useEffect, useState} from 'react';
import {useLocation} from 'react-router-dom';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {getPosts} from '../../redux/posts/posts.actions';
import handleSorting from '../../utils/handleSorting';
import LinkButton from '../../components/molecules/LinkButton/LinkButton.component';
import PostItem from '../../components/molecules/PostItem/PostItem.component';
import Spinner from '../../components/molecules/Spinner/Spinner.component';
import ButtonGroup from '../../components/molecules/ButtonGroup/ButtonGroup.component';
import SearchBox from '../../components/molecules/SearchBox/SearchBox.component';
import Pagination from "../../components/organisms/Pagination/Pagination.component";
import './QuestionsPage.styles.scss';
const itemsPerPage = 10;
const QuestionsPage = ({getPosts, post: {posts, loading}}) => {
useEffect(() => {
getPosts();
}, [getPosts]);
const [page, setPage] = useState(1);
const [sortType, setSortType] = useState('Newest');
let searchQuery = new URLSearchParams(useLocation().search).get('search');
const handlePaginationChange = (e, value) => setPage(value);
return loading || posts === null ? (
<Spinner type='page' width='75px' height='200px' />
) : (
<Fragment>
<div id='mainbar' className='questions-page fc-black-800'>
<div className='questions-grid'>
<h3 className='questions-headline'>
{searchQuery ? 'Search Results' : 'All Questions'}
</h3>
<div className='questions-btn'>
<LinkButton
text={'Ask Question'}
link={'/add/question'}
type={'s-btn__primary'}
/>
</div>
</div>
{searchQuery ? (
<div className='search-questions'>
<span style={{color: '#acb2b8', fontSize: '12px'}}>
Results for {searchQuery}
</span>
<SearchBox placeholder={'Search...'} name={'search'} pt={'mt8'} />
</div>
) : (
''
)}
<div className='questions-tabs'>
<span>
{new Intl.NumberFormat('en-IN').format(posts.length)} questions
</span>
<ButtonGroup
buttons={['Newest', 'Top', 'Views', 'Oldest']}
selected={sortType}
setSelected={setSortType}
/>
</div>
<div className='questions'>
{posts
.filter((post) => post.title.toLowerCase().includes(searchQuery ? searchQuery : ''))
?.sort(handleSorting(sortType))
.slice((page - 1) * itemsPerPage, (page - 1) * itemsPerPage + itemsPerPage)
.map((post, index) => (
<PostItem key={index} post={post} />
))}
</div>
<Pagination
page={page}
itemList={posts.filter((post) => post.title.toLowerCase().includes(searchQuery ? searchQuery : ''))}
itemsPerPage={itemsPerPage}
handlePaginationChange={handlePaginationChange}
/>
</div>
</Fragment>
);
};
QuestionsPage.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
post: state.post,
});
export default connect(mapStateToProps, {getPosts})(QuestionsPage);
================================================
FILE: src/modules/QuestionsPage/QuestionsPage.styles.scss
================================================
.page {
display: flex;
}
.questions-page {
padding: 24px 0 24px 0;
float: left;
margin: 0;
.questions-grid {
display: flex;
padding-left: 24px;
.questions-headline {
margin-bottom: 24px;
flex: 1 auto;
font-size: 28px;
font-weight: 400;
}
}
.search-questions {
padding-left: 24px;
padding-bottom: 16px;
padding-right: 2px;
}
.questions-tabs {
margin-bottom: 12px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding-left: 24px;
padding-right: 3px;
span {
font-size: 17px;
margin-right: 12px;
margin-bottom: 1rem;
}
}
.questions {
float: none;
clear: both;
width: auto;
margin-bottom: 20px;
border-top: 1px solid #4a4e51;
}
}
@media(max-width: 1100px) {
.questions-page {
width: 80%;
}
}
================================================
FILE: src/modules/Register/Caption/Caption.component.jsx
================================================
import React, {Fragment} from 'react';
import {Link} from 'react-router-dom';
import {ReactComponent as QuoteLogo} from '../../../assets/Quote.svg';
import {ReactComponent as VoteLogo} from '../../../assets/Vote.svg';
import {ReactComponent as TagsLogo} from '../../../assets/Tags.svg';
import {ReactComponent as TrophyLogo} from '../../../assets/Trophy.svg';
import './Caption.styles.scss';
const Caption = () => {
return (
<Fragment>
<div className='caption fc-black-600'>
<h3>Join the Stack Overflow community</h3>
<div className='caption-item'>
<div className='grid-icon'>
<QuoteLogo/>
</div>
<div className='grid-cell'>Get unstuck — ask a question</div>
</div>
<div className='caption-item'>
<div className='grid-icon'>
<VoteLogo/>
</div>
<div className='grid--cell'>
Unlock new privileges like voting and commenting
</div>
</div>
<div className='caption-item'>
<div className='grid-icon'>
<TagsLogo/>
</div>
<div className='grid-cell'>
Save your favorite tags, filters, and jobs
</div>
</div>
<div className='caption-item'>
<div className='grid-icon'>
<TrophyLogo/>
</div>
<div className='grid-cell'>Earn reputation and badges</div>
</div>
<div className='caption-item fc-black-600'>
<div>
Use the power of Stack Overflow inside your organization.
<br />
Try a{' '}
<Link to='https://stackoverflow.com/teams?utm_source=so-owned&utm_medium=product&utm_campaign=public-sign-up&utm_content=teams'>
free trial of Stack Overflow for Teams
</Link>
.
</div>
</div>
</div>
</Fragment>
);
};
export default Caption;
================================================
FILE: src/modules/Register/Caption/Caption.styles.scss
================================================
.caption {
margin-right: 48px;
margin-bottom: 0px;
width: 400px;
display: flex;
flex-direction: column;
h3 {
margin-bottom: 32px;
}
.caption-item {
display: flex;
margin-bottom: 24px;
.grid-icon {
margin-right: 8px;
.svg-icon {
color:#0095ff;
}
}
}
.fs-light {
color:#6a737c;
font-size: 13px;
box-sizing: inherit;
}
}
@media (max-width: 799px ) {
.caption {
width: 100%;
justify-content: center;
align-items: center;
overflow-x: auto;
min-width: 400px;
margin-right: 0;
}
h3 {
font-size: 1.6rem;
}
}
================================================
FILE: src/modules/Register/Register.component.jsx
================================================
import React, {Fragment} from 'react';
import {connect} from 'react-redux';
import {Redirect} from 'react-router-dom';
import PropTypes from 'prop-types';
import {setAlert} from '../../redux/alert/alert.actions';
import Caption from './Caption/Caption.component';
import AuthForm from '../../components/organisms/AuthForm/AuthForm.component';
import Footer from "../../components/organisms/Footer/Footer.component";
import './Register.styles.scss';
const Register = ({isAuthenticated}) => {
if (isAuthenticated) {
return <Redirect to='/' />;
}
return (
<Fragment>
<div className='auth-page'>
<div className='register-content'>
<div className='register-grid'>
<Caption />
<AuthForm action={'Sign up'} />
</div>
</div>
</div>
<Footer/>
</Fragment>
);
};
Register.propTypes = {
setAlert: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool,
};
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isAuthenticated,
});
export default connect(mapStateToProps, {setAlert})(Register);
================================================
FILE: src/modules/Register/Register.styles.scss
================================================
.auth-page {
height: 100vh;
width: 100%;
background-color: #3d3d3d;
}
.register-content {
background-color: transparent;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
height: 90.7vh;
}
.register-grid {
display: flex;
align-items: center;
box-sizing: inherit;
}
@media (max-width: 799px) {
.register-grid {
flex-direction: column;
padding: 7rem 0;
}
.register-content {
height: auto;
margin: auto 0;
}
}
================================================
FILE: src/modules/TagPage/TagPage.component.jsx
================================================
import React, {useEffect, Fragment, useState} from 'react';
import {connect} from 'react-redux';
import {Redirect, useParams} from 'react-router-dom';
import PropTypes from 'prop-types';
import {getTagPosts} from '../../redux/posts/posts.actions';
import {getTag} from '../../redux/tags/tags.actions';
import handleSorting from '../../utils/handleSorting';
import LinkButton from '../../components/molecules/LinkButton/LinkButton.component';
import PostItem from '../../components/molecules/PostItem/PostItem.component';
import Spinner from '../../components/molecules/Spinner/Spinner.component';
import ButtonGroup from '../../components/molecules/ButtonGroup/ButtonGroup.component';
import './TagPage.styles.scss';
const TagPage = ({getTag, getTagPosts, tag, post: {posts, loading}}) => {
const { tagname } = useParams();
useEffect(() => {
getTagPosts(tagname);
getTag(tagname);
// eslint-disable-next-line
}, [getTag, getTagPosts]);
const [sortType, setSortType] = useState('Newest');
if (tag.redirect) {
return <Redirect to='/tags' />;
}
return tag.tag === null || tag.loading || loading ? (
<Spinner type='page' width='75px' height='200px' />
) : (
<Fragment>
<div id='mainbar' className='questions-page fc-black-800'>
<div className='questions-grid'>
<h3 className='questions-headline'>
Questions tagged [{tag.tag.tagname}]
</h3>
<div className='questions-btn'>
<LinkButton
text={'Ask Question'}
link={'/add/question'}
type={'s-btn__primary'}
/>
</div>
</div>
<p
className='fs-body'
dangerouslySetInnerHTML={{__html: tag.tag.description}}
/>
<div className='questions-tabs'>
<span>
{new Intl.NumberFormat('en-IN').format(tag.tag.posts_count)}{' '}
{tag.tag.posts_count === 1 ? 'question' : 'questions'}
</span>
<ButtonGroup
buttons={['Newest', 'Top', 'Views', 'Oldest']}
selected={sortType}
setSelected={setSortType}
/>
</div>
<div className='questions'>
{tag.tag.posts_count === 0 ? (
<h4 style={{margin: '30px 30px'}}>
There are no questions from this tag
</h4>
) : (
posts
?.sort(handleSorting(sortType))
.map((post, index) => <PostItem key={index} post={post} />)
)}
</div>
</div>
</Fragment>
);
};
TagPage.propTypes = {
getTag: PropTypes.func.isRequired,
getTagPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired,
tag: PropTypes.func.isRequired,
};
const mapStateToProps = (state) => ({
post: state.post,
tag: state.tag,
});
export default connect(mapStateToProps, {getTagPosts, getTag})(TagPage);
================================================
FILE: src/modules/TagPage/TagPage.styles.scss
================================================
.fs-body {
padding-left: 24px;
padding-right: 24px;
width: 54rem;
font-size: 13px;
font-weight: 400;
}
================================================
FILE: src/redux/alert/alert.actions.js
================================================
import {v4 as uuidv4} from 'uuid';
import {SET_ALERT, REMOVE_ALERT} from './alert.types';
export const setAlert = (msg, alertType, timeout = 5000) => (dispatch) => {
const id = uuidv4();
dispatch({
type: SET_ALERT,
payload: {msg, alertType, id},
});
setTimeout(() => dispatch({type: REMOVE_ALERT, payload: id}), timeout);
};
================================================
FILE: src/redux/alert/alert.reducer.js
================================================
import {SET_ALERT, REMOVE_ALERT} from './alert.types';
const InitialState = [];
export default function alert(state = InitialState, action) {
switch (action.type) {
case SET_ALERT:
return [...state, action.payload];
case REMOVE_ALERT:
return state.filter((alert) => alert.id !== action.payload);
default:
return state;
}
}
================================================
FILE: src/redux/alert/alert.types.js
================================================
export const SET_ALERT = 'SET_ALERT';
export const REMOVE_ALERT = 'REMOVE_ALERT';
================================================
FILE: src/redux/answers/answers.actions.js
================================================
import { setAlert } from "../alert/alert.actions";
import {
GET_ANSWERS,
ANSWER_ERROR,
ADD_ANSWER,
DELETE_ANSWER,
} from "./answers.types";
import { allAnswersData, createSingleAnswer, deleteSingleAnswer } from "../../api/answersApi";
export const getAnswers = (id) => async (dispatch) => {
try {
const res = await allAnswersData(id);
dispatch({
type: GET_ANSWERS,
payload: res.data.data,
});
} catch (err) {
dispatch({
type: ANSWER_ERROR,
payload: { msg: err.response.statusText, status: err.response.status },
});
}
};
// Add Answer
export const addAnswer = (postId, formData) => async (dispatch) => {
try {
const res = await createSingleAnswer(postId, formData);
dispatch({
type: ADD_ANSWER,
payload: res.data.data,
});
dispatch(setAlert(res.data.message, "success"));
dispatch(getAnswers(postId));
} catch (err) {
dispatch(setAlert(err.response.data.mes
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
SYMBOL INDEX (40 symbols across 18 files)
FILE: src/components/organisms/MarkdownEditor/MarkdownEditor.component.jsx
method cleanEditorState (line 10) | cleanEditorState() {
FILE: src/components/organisms/MobileSideBar/MobileSideBar.component.jsx
function openSidebar (line 36) | function openSidebar(isOp = true) {
FILE: src/redux/alert/alert.reducer.js
function alert (line 5) | function alert(state = InitialState, action) {
FILE: src/redux/alert/alert.types.js
constant SET_ALERT (line 1) | const SET_ALERT = 'SET_ALERT';
constant REMOVE_ALERT (line 2) | const REMOVE_ALERT = 'REMOVE_ALERT';
FILE: src/redux/answers/answers.reducer.js
function answers (line 14) | function answers(state = initialState, action) {
FILE: src/redux/answers/answers.types.js
constant GET_ANSWERS (line 1) | const GET_ANSWERS = 'GET_ANSWERS';
constant ANSWER_ERROR (line 2) | const ANSWER_ERROR = 'ANSWER_ERROR';
constant DELETE_ANSWER (line 3) | const DELETE_ANSWER = 'DELETE_ANSWER';
constant ADD_ANSWER (line 4) | const ADD_ANSWER = 'ADD_ANSWER';
FILE: src/redux/auth/auth.reducer.js
function auth (line 18) | function auth(state = initialState, action) {
FILE: src/redux/auth/auth.types.js
constant REGISTER_SUCCESS (line 1) | const REGISTER_SUCCESS = 'REGISTER_SUCCESS';
constant REGISTER_FAIL (line 2) | const REGISTER_FAIL = 'REGISTER_FAIL';
constant USER_LOADED (line 3) | const USER_LOADED = 'USER_LOADED';
constant AUTH_ERROR (line 4) | const AUTH_ERROR = 'AUTH_ERROR';
constant LOGIN_SUCCESS (line 5) | const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
constant LOGIN_FAIL (line 6) | const LOGIN_FAIL = 'LOGIN_FAIL';
constant LOGOUT (line 7) | const LOGOUT = 'LOGOUT';
FILE: src/redux/comments/comments.reducer.js
function comments (line 14) | function comments(state = initialState, action) {
FILE: src/redux/comments/comments.types.js
constant GET_COMMENTS (line 1) | const GET_COMMENTS = 'GET_COMMENTS';
constant COMMENT_ERROR (line 2) | const COMMENT_ERROR = 'COMMENT_ERROR';
constant DELETE_COMMENT (line 3) | const DELETE_COMMENT = 'DELETE_COMMENT';
constant ADD_COMMENT (line 4) | const ADD_COMMENT = 'ADD_COMMENT';
FILE: src/redux/posts/posts.reducer.js
function posts (line 17) | function posts(state = initialState, action) {
FILE: src/redux/posts/posts.types.js
constant GET_POSTS (line 1) | const GET_POSTS = 'GET_POSTS';
constant GET_POST (line 2) | const GET_POST = 'GET_POST';
constant GET_TAG_POSTS (line 3) | const GET_TAG_POSTS = 'GET_TAG_POSTS';
constant POST_ERROR (line 4) | const POST_ERROR = 'POST_ERROR';
constant DELETE_POST (line 5) | const DELETE_POST = 'DELETE_POST';
constant ADD_POST (line 6) | const ADD_POST = 'ADD_POST';
FILE: src/redux/tags/tags.reducer.js
function tags (line 11) | function tags(state = initialState, action) {
FILE: src/redux/tags/tags.types.js
constant GET_TAG (line 1) | const GET_TAG = 'GET_TAG';
constant GET_TAGS (line 2) | const GET_TAGS = 'GET_TAGS';
constant TAG_ERROR (line 3) | const TAG_ERROR = 'TAG_ERROR';
FILE: src/redux/users/users.reducer.js
function users (line 10) | function users(state = initialState, action) {
FILE: src/redux/users/users.types.js
constant GET_USERS (line 1) | const GET_USERS = 'GET_USERS';
constant GET_USER (line 2) | const GET_USER = 'GET_USER';
constant USER_ERROR (line 3) | const USER_ERROR = 'USER_ERROR';
FILE: src/utils/handleFilter.js
function getTime (line 12) | function getTime(a) {
FILE: src/utils/handleSorting.js
function getTime (line 12) | function getTime(a) {
Condensed preview — 152 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (223K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 68,
"preview": "# These are supported funding model platforms\n\ngithub: [Mayank0255]\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 568,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG]: Write a descriptive title\"\nlabels: bug\nass"
},
{
"path": ".github/ISSUE_TEMPLATE/features-request-for-backend-or-frontend.md",
"chars": 687,
"preview": "---\nname: Features Request for Backend or Frontend\nabout: Suggest an idea for the project\ntitle: \"[Backend/Frontend]: Wr"
},
{
"path": ".github/ISSUE_TEMPLATE/proposal.md",
"chars": 210,
"preview": "---\nname: Proposal\nabout: Propose a non-trivial change\ntitle: \"[Proposal]: Write a descriptive title here\"\nlabels: ''\nas"
},
{
"path": ".github/workflows/deploy-on-release.yml",
"chars": 773,
"preview": "name: \"Deploy\"\n\non:\n release:\n types:\n - published\n push:\n branches:\n - dev\n workflow_dispatch:\n\njobs"
},
{
"path": ".gitignore",
"chars": 118,
"preview": "/node_modules\n/.idea\n/.vscode\n/client/node_modules\n.env\npackage-lock.json\n/client/package-lock.json\nyarn.lock\n.vercel\n"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3357,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "CONTRIBUTING.md",
"chars": 1467,
"preview": "# Contributing to Stackoverflow Clone\nWe love your input! We want to make contributing to this project as easy and trans"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2020 Mayank Aggarwal\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 4940,
"preview": "https://user-images.githubusercontent.com/43780137/158059050-481ffa30-e415-4156-aea7-072c817f2ae2.mp4\n\n[ => {\n let defaultTitle =\n "
},
{
"path": "src/components/atoms/box.atom.jsx",
"chars": 661,
"preview": "import styled from '@emotion/styled';\nimport {\n space,\n color,\n layout,\n flexbox,\n position,\n typography,\n border"
},
{
"path": "src/components/molecules/BaseButton/BaseButton.component.jsx",
"chars": 383,
"preview": "import React, {Fragment} from 'react';\n\nconst BaseButton = ({text, selected, onClick}) => {\n return (\n <Fragment>\n "
},
{
"path": "src/components/molecules/ButtonGroup/ButtonGroup.component.jsx",
"chars": 596,
"preview": "import React, {Fragment} from 'react';\nimport BaseButton from '../BaseButton/BaseButton.component';\n\nconst ButtonGroup ="
},
{
"path": "src/components/molecules/LinkButton/LinkButton.component.jsx",
"chars": 385,
"preview": "import React, {Fragment} from 'react';\nimport {Link} from 'react-router-dom';\n\nconst LinkButton = ({text, link, type, ha"
},
{
"path": "src/components/molecules/PostItem/PostItem.component.jsx",
"chars": 2520,
"preview": "import React from \"react\";\nimport { connect } from \"react-redux\";\nimport PropTypes from \"prop-types\";\nimport { Link } fr"
},
{
"path": "src/components/molecules/PostItem/PostItem.styles.scss",
"chars": 3062,
"preview": ".posts {\n padding: 12px 8px 12px 8px;\n width: 100%;\n box-sizing: border-box;\n display: flex;\n border-bottom: 1px so"
},
{
"path": "src/components/molecules/SearchBox/SearchBox.component.jsx",
"chars": 893,
"preview": "import React, {Fragment} from 'react';\nimport {ReactComponent as Search} from '../../../assets/Search.svg';\n\nconst Searc"
},
{
"path": "src/components/molecules/SearchBox/SearchBox.styles.scss",
"chars": 163,
"preview": ".search-frame {\n width: 220px;\n //float: right;\n}\n\n.search-box:focus {\n border-color: #2b5f8a;\n box-shadow: 0 0 0 4p"
},
{
"path": "src/components/molecules/Spinner/Spinner.component.jsx",
"chars": 468,
"preview": "import React from 'react';\n\nimport {ReactComponent as PageSpinner} from '../../../assets/PageSpinner.svg';\nimport {React"
},
{
"path": "src/components/molecules/Spinner/Spinner.styles.scss",
"chars": 95,
"preview": ".spinner {\n margin: auto;\n display: flex;\n justify-content: center;\n align-items: center;\n}"
},
{
"path": "src/components/molecules/TagBadge/TagBadge.component.jsx",
"chars": 629,
"preview": "import React, {Fragment} from 'react';\nimport {Link} from 'react-router-dom';\n\nimport './TagBadge.styles.scss';\n\nconst T"
},
{
"path": "src/components/molecules/TagBadge/TagBadge.styles.scss",
"chars": 75,
"preview": ".tags-badge {\n color: #242729;\n line-height: 18px;\n margin-right: 4px;\n}"
},
{
"path": "src/components/molecules/UserCard/UserCard.component.jsx",
"chars": 1224,
"preview": "import React, {Fragment} from 'react';\nimport moment from 'moment';\nimport {Link} from 'react-router-dom';\n\nimport './Us"
},
{
"path": "src/components/molecules/UserCard/UserCard.styles.scss",
"chars": 1231,
"preview": ".owner {\n margin-top: 4px;\n margin-bottom: 4px;\n border-radius: 3px;\n background-color: #3e4a52;\n text-align: left;"
},
{
"path": "src/components/organisms/AuthForm/AuthForm.component.jsx",
"chars": 4251,
"preview": "import React, {Fragment, useState} from 'react';\nimport {Link} from 'react-router-dom';\nimport {connect} from 'react-red"
},
{
"path": "src/components/organisms/AuthForm/AuthForm.styles.scss",
"chars": 775,
"preview": ".form-container {\n width: 320px;\n box-shadow: 0 10px 25px rgba(0,0,0,0.05), 0 20px 48px rgba(0,0,0,0.05), 0 1px 4px r"
},
{
"path": "src/components/organisms/Footer/Footer.component.jsx",
"chars": 971,
"preview": "import React, { Fragment } from \"react\";\n\nimport {ReactComponent as GitHub} from \"../../../assets/GitHub.svg\";\nimport {R"
},
{
"path": "src/components/organisms/Footer/Footer.styles.scss",
"chars": 317,
"preview": ".footer {\n height: 300px;\n display: flex;\n justify-content: center;\n padding-top: 32px;\n background-color: #232629;"
},
{
"path": "src/components/organisms/Header/Header.component.jsx",
"chars": 4296,
"preview": "import React, {Fragment, useState} from 'react';\nimport {Link, useHistory} from 'react-router-dom';\nimport {connect} fro"
},
{
"path": "src/components/organisms/Header/Header.styles.scss",
"chars": 3861,
"preview": ".navbar {\n height: 63px;\n border-top: 3px solid #f48024;\n padding: 3px 3px 0 0;\n box-shadow: 5px 2px rgba(0,0,0,0.1)"
},
{
"path": "src/components/organisms/LayoutWrapper/LayoutWrapper.component.jsx",
"chars": 498,
"preview": "import React, {Fragment} from 'react';\n\nimport SideBar from './SideBar/SideBar.component';\nimport RightSideBar from './R"
},
{
"path": "src/components/organisms/LayoutWrapper/RightSideBar/RightSideBar.component.jsx",
"chars": 425,
"preview": "import React, {Fragment} from 'react';\n\nimport SideBarWidget from './SideBarWidget/SideBarWidget.component';\nimport Tags"
},
{
"path": "src/components/organisms/LayoutWrapper/RightSideBar/RightSideBar.styles.scss",
"chars": 204,
"preview": ".side-bar {\n float: right;\n width: 300px;\n margin: 0 0 15px;\n padding-left: 5px;\n height: 100%;\n}\n\n@media(max-width"
},
{
"path": "src/components/organisms/LayoutWrapper/RightSideBar/SideBarWidget/SideBarWidget.component.jsx",
"chars": 1556,
"preview": "import React, {Fragment} from 'react';\n\nimport { SideBarWidgetData } from \"./SideBarWidgetData\";\n\nimport './SideBarWidge"
},
{
"path": "src/components/organisms/LayoutWrapper/RightSideBar/SideBarWidget/SideBarWidget.styles.scss",
"chars": 77,
"preview": ".s-sidebarwidget__yellow {\n color: #f2f2f3 !important;\n margin-top: 24px;\n}"
},
{
"path": "src/components/organisms/LayoutWrapper/RightSideBar/SideBarWidget/SideBarWidgetData.js",
"chars": 2632,
"preview": "import React from \"react\";\n\nimport { ReactComponent as EditLogo } from \"../../../../../assets/Edit.svg\";\n\nexport const S"
},
{
"path": "src/components/organisms/LayoutWrapper/RightSideBar/TagsWidget/TagsWidget.component.jsx",
"chars": 1273,
"preview": "import React, {useEffect, Fragment} from 'react';\nimport {connect} from 'react-redux';\nimport PropTypes from 'prop-types"
},
{
"path": "src/components/organisms/LayoutWrapper/RightSideBar/TagsWidget/TagsWidget.styles.scss",
"chars": 366,
"preview": ".side-bar-tags {\n word-wrap: break-word;\n margin-bottom: 20px;\n color: #242729;\n margin-top: 24px;\n\n .tag-headline "
},
{
"path": "src/components/organisms/LayoutWrapper/RightSideBar/TagsWidget/TagsWidgetItem.component.jsx",
"chars": 594,
"preview": "import React, {Fragment} from \"react\";\n\nimport TagBadge from \"../../../../molecules/TagBadge/TagBadge.component\";\n\nimpor"
},
{
"path": "src/components/organisms/LayoutWrapper/RightSideBar/TagsWidget/TagsWidgetItem.styles.scss",
"chars": 186,
"preview": ".tag-content {\n box-sizing: inherit;\n margin-bottom: 8px;\n\n .tag-mult {\n margin-right: 4px;\n color: #848d95;\n\n "
},
{
"path": "src/components/organisms/LayoutWrapper/SideBar/SideBar.component.jsx",
"chars": 759,
"preview": "import React from 'react';\n\nimport SideBarItem from \"./SideBarItem.component\";\nimport { SideBarData } from \"./SideBarDat"
},
{
"path": "src/components/organisms/LayoutWrapper/SideBar/SideBar.styles.scss",
"chars": 1313,
"preview": ".side-bar-container {\n width: 17%;\n min-width: 206px;\n flex-shrink: 0;\n z-index: 1;\n box-shadow: 0 0 0 rgba(12,13,1"
},
{
"path": "src/components/organisms/LayoutWrapper/SideBar/SideBarData.js",
"chars": 346,
"preview": "import { ReactComponent as GlobalIcon } from '../../../../assets/Globe.svg';\n\nexport const SideBarData = [\n {\n link:"
},
{
"path": "src/components/organisms/LayoutWrapper/SideBar/SideBarItem.component.jsx",
"chars": 1369,
"preview": "import React, { Fragment } from 'react';\nimport { NavLink } from 'react-router-dom';\n\nimport { ListItem, ListItemButton,"
},
{
"path": "src/components/organisms/MarkdownEditor/MarkdownEditor.component.jsx",
"chars": 2697,
"preview": "import React, {useState, forwardRef, useImperativeHandle} from 'react';\nimport RichTextEditor from 'react-rte';\n\nimport "
},
{
"path": "src/components/organisms/MarkdownEditor/MarkdownEditor.styles.scss",
"chars": 3789,
"preview": ".rich-text-editor-root {\n background-color: transparent !important;\n border: none !important;\n font-family: inherit !"
},
{
"path": "src/components/organisms/MobileSideBar/MobileSideBar.component.jsx",
"chars": 2649,
"preview": "import React, { useState } from \"react\";\nimport { NavLink } from \"react-router-dom\";\n\nimport { ReactComponent as Hamburg"
},
{
"path": "src/components/organisms/MobileSideBar/MobileSideBar.styles.scss",
"chars": 2705,
"preview": ".Sidebar {\n\tz-index: 9999;\n\t.SidebarOverlay {\n\t\tposition: fixed;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\topacity: 0;\n\t\twidth: 0;\n\t\theight"
},
{
"path": "src/components/organisms/Pagination/Pagination.component.jsx",
"chars": 683,
"preview": "import React, { Fragment } from \"react\";\nimport { Pagination as MuiPagination, PaginationItem } from \"@mui/material\";\n\nc"
},
{
"path": "src/config/index.js",
"chars": 211,
"preview": "const config = {\n BASE_URL: process.env.REACT_APP_API_URL,\n};\n\nif (!process.env.NODE_ENV || process.env.NODE_ENV === 'd"
},
{
"path": "src/hooks/usePageTitle.jsx",
"chars": 191,
"preview": "import { useEffect } from 'react';\n\nconst usePageTitle = (title, prevailOnUnmount = false) => {\n useEffect(() => {\n "
},
{
"path": "src/index.js",
"chars": 240,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport {BrowserRouter} from 'react-router-dom';\n\nimport App"
},
{
"path": "src/modules/AllTagsPage/AllTagsPage.component.jsx",
"chars": 3112,
"preview": "import React, {useEffect, Fragment, useState} from 'react';\nimport {connect} from 'react-redux';\nimport PropTypes from '"
},
{
"path": "src/modules/AllTagsPage/AllTagsPage.styles.scss",
"chars": 1466,
"preview": "#mainbar {\n height: 100%;\n}\n\n.tags-page {\n display: inline-block;\n width: 60%;\n margin-left: 20%;\n padding: 24px 0;"
},
{
"path": "src/modules/AllTagsPage/TagPanel/TagPanel.component.jsx",
"chars": 1068,
"preview": "import React from 'react';\nimport moment from 'moment';\nimport {connect} from 'react-redux';\nimport PropTypes from 'prop"
},
{
"path": "src/modules/AllUsersPage/AllUsersPage.component.jsx",
"chars": 3032,
"preview": "import React, { Fragment, useEffect, useState } from \"react\";\nimport { connect } from \"react-redux\";\nimport PropTypes fr"
},
{
"path": "src/modules/AllUsersPage/AllUsersPage.styles.scss",
"chars": 915,
"preview": ".users-page {\n width: calc(100% - 300px - 24px);\n padding: 24px 0 24px 0;\n float: left;\n margin: 0;\n\n .headline {\n "
},
{
"path": "src/modules/AllUsersPage/UserPanel/UserPanel.component.jsx",
"chars": 1965,
"preview": "import React, {Fragment} from 'react';\nimport moment from 'moment';\nimport {connect} from 'react-redux';\nimport {Link} f"
},
{
"path": "src/modules/AllUsersPage/UserPanel/UserPanel.styles.scss",
"chars": 1324,
"preview": ".user-panel-info {\n padding: 6px 7px 8px 8px;\n border: 1px solid #4a4e51;\n border-radius: 3px;\n background-color: #2"
},
{
"path": "src/modules/HomePage/HomePage.component.jsx",
"chars": 2892,
"preview": "import React, {Fragment, useEffect, useState} from 'react';\nimport {connect} from 'react-redux';\nimport PropTypes from '"
},
{
"path": "src/modules/HomePage/HomePage.styles.scss",
"chars": 1613,
"preview": ".page {\n width: 100%;\n background: none;\n display: flex;\n justify-content: space-between;\n margin: 64px auto 0 auto"
},
{
"path": "src/modules/Login/Login.component.jsx",
"chars": 887,
"preview": "import React, {Fragment} from 'react';\nimport {Redirect} from 'react-router-dom';\nimport {connect} from 'react-redux';\ni"
},
{
"path": "src/modules/NotFound/NotFound.component.jsx",
"chars": 1559,
"preview": "import React, {Fragment} from 'react';\nimport {Link} from 'react-router-dom';\n\nimport './NotFound.styles.scss';\n\nconst N"
},
{
"path": "src/modules/NotFound/NotFound.styles.scss",
"chars": 6379,
"preview": "@import url(https://fonts.googleapis.com/css?family=Ubuntu);\n//variables\n$purple: #28254C;\n$l-purple: #332F63;\n$t-purple"
},
{
"path": "src/modules/Post/AnswerSection/AnswerForm/AnswerForm.component.jsx",
"chars": 2162,
"preview": "import React, {Fragment, useState, useRef} from 'react';\nimport {connect} from 'react-redux';\nimport PropTypes from 'pro"
},
{
"path": "src/modules/Post/AnswerSection/AnswerForm/AnswerForm.styles.scss",
"chars": 541,
"preview": ".answer-grid {\n margin-bottom: 16px;\n display: flex;\n flex: 1 auto !important;\n flex-direction: column;\n color: #24"
},
{
"path": "src/modules/Post/AnswerSection/AnswerItem/AnswerItem.component.jsx",
"chars": 3339,
"preview": "import React, { Fragment } from \"react\";\nimport { connect } from \"react-redux\";\nimport PropTypes from \"prop-types\";\nimpo"
},
{
"path": "src/modules/Post/AnswerSection/AnswerItem/AnswerItem.styles.scss",
"chars": 3130,
"preview": ".answer-layout {\n display: grid;\n grid-template-columns: max-content 1fr;\n\n .vote-cell {\n width: auto;\n padding"
},
{
"path": "src/modules/Post/AnswerSection/AnswerSection.component.jsx",
"chars": 1987,
"preview": "import React, {Fragment, useState, useEffect} from 'react';\nimport {connect} from 'react-redux';\nimport PropTypes from '"
},
{
"path": "src/modules/Post/AnswerSection/AnswerSection.styles.scss",
"chars": 611,
"preview": ".answer {\n width: auto;\n float: none;\n padding-top: 10px;\n clear: both;\n color: #242729;\n\n .answer-header {\n wi"
},
{
"path": "src/modules/Post/Post.component.jsx",
"chars": 2003,
"preview": "import React, { useEffect, Fragment } from \"react\";\nimport moment from \"moment\";\nimport { useParams } from \"react-router"
},
{
"path": "src/modules/Post/Post.styles.scss",
"chars": 1989,
"preview": ".page {\n width: 100%;\n background: none;\n display: flex;\n justify-content: space-between;\n margin: 64px auto 0 auto"
},
{
"path": "src/modules/Post/QuestionSection/CommentCell/CommentCell.component.jsx",
"chars": 4558,
"preview": "import React, { useEffect, Fragment, useState } from \"react\";\nimport moment from \"moment\";\nimport { connect } from \"reac"
},
{
"path": "src/modules/Post/QuestionSection/CommentCell/CommentCell.styles.scss",
"chars": 1381,
"preview": ".comments-cell {\n padding-right: 16px;\n grid-column: 2;\n width: auto;\n min-width: 0;\n\n .comments {\n width: 100%;"
},
{
"path": "src/modules/Post/QuestionSection/PostCell/PostCell.component.jsx",
"chars": 2815,
"preview": "import React, { Fragment } from \"react\";\nimport { connect } from \"react-redux\";\nimport PropTypes from \"prop-types\";\nimpo"
},
{
"path": "src/modules/Post/QuestionSection/PostCell/PostCell.styles.scss",
"chars": 1161,
"preview": ".post-cell {\n display: flex;\n flex-direction: column;\n vertical-align: top;\n padding-right: 16px;\n grid-column: 2;\n"
},
{
"path": "src/modules/Post/QuestionSection/QuestionSection.component.jsx",
"chars": 977,
"preview": "import React, {Fragment} from 'react';\nimport {connect} from 'react-redux';\nimport PropTypes from 'prop-types';\n\nimport "
},
{
"path": "src/modules/Post/QuestionSection/QuestionSection.styles.scss",
"chars": 117,
"preview": ".question {\n .post-layout {\n display: grid;\n grid-template-columns: max-content 1fr;\n color: #242729;\n }\n}"
},
{
"path": "src/modules/Post/QuestionSection/VoteCell/VoteCell.component.jsx",
"chars": 813,
"preview": "import React, {Fragment} from 'react';\n\nimport './VoteCell.styles.scss';\n\nconst VoteCell = ({answerCount, commentCount, "
},
{
"path": "src/modules/Post/QuestionSection/VoteCell/VoteCell.styles.scss",
"chars": 335,
"preview": ".vote-cell {\n width: auto;\n padding-right: 16px;\n vertical-align: top;\n grid-column: 1;\n grid-column-start: 1;\n gr"
},
{
"path": "src/modules/PostForm/AskForm/AskForm.component.jsx",
"chars": 4739,
"preview": "import React, { Fragment, useState, useEffect, useRef } from \"react\";\nimport { connect } from \"react-redux\";\nimport Prop"
},
{
"path": "src/modules/PostForm/AskForm/AskForm.styles.scss",
"chars": 4317,
"preview": ".post-form {\n min-width: 0 !important;\n flex: 1 auto !important;\n color: #242729;\n width: 100%;\n padding: 0 !import"
},
{
"path": "src/modules/PostForm/AskWidget/AskWidget.component.jsx",
"chars": 3740,
"preview": "import React, {Fragment} from 'react';\n\nimport './AskWidget.styles.scss';\n\nconst AskWidget = () => {\n return (\n <Fra"
},
{
"path": "src/modules/PostForm/AskWidget/AskWidget.styles.scss",
"chars": 2681,
"preview": ".widget {\n margin-bottom: 24px;\n position: relative;\n border-radius: 3px;\n box-shadow: 0 10px 25px rgba(0,0,0,0.05),"
},
{
"path": "src/modules/PostForm/PostForm.component.jsx",
"chars": 1511,
"preview": "import React, {Fragment} from 'react';\nimport {connect} from 'react-redux';\nimport {Redirect} from 'react-router-dom';\ni"
},
{
"path": "src/modules/PostForm/PostForm.styles.scss",
"chars": 1221,
"preview": ".post-form-container {\n width: 100%;\n height: calc(100vh - 64px);\n max-width: 100%;\n display: flex;\n justify-conten"
},
{
"path": "src/modules/ProfilePage/ExternalUserDetails/ExternalUserDetails.component.jsx",
"chars": 1271,
"preview": "import React from \"react\";\nimport {Link} from \"react-router-dom\";\n\nimport {ReactComponent as StackExchangeLogo} from \".."
},
{
"path": "src/modules/ProfilePage/ExternalUserDetails/ExternalUserDetails.styles.scss",
"chars": 1605,
"preview": ".grid-cell1 {\n margin: 12px;\n width: 100%;\n max-width: 210px;\n\n .cell-layout {\n display: flex;\n flex-direction"
},
{
"path": "src/modules/ProfilePage/ProfilePage.component.jsx",
"chars": 1785,
"preview": "import React, {useEffect, Fragment} from 'react';\nimport { connect } from 'react-redux';\nimport { Link, useParams } from"
},
{
"path": "src/modules/ProfilePage/ProfilePage.styles.scss",
"chars": 352,
"preview": ".page {\n display: flex;\n}\n\n.user-main-bar {\n width: calc(100% - 300px - 24px);\n padding: 24px 0 24px 0;\n float: left"
},
{
"path": "src/modules/ProfilePage/UserActivity/UserActivity.component.jsx",
"chars": 2376,
"preview": "import React from \"react\";\n\nimport TagBadge from \"../../../components/molecules/TagBadge/TagBadge.component\";\n\nimport '."
},
{
"path": "src/modules/ProfilePage/UserActivity/UserActivity.styles.scss",
"chars": 1533,
"preview": ".grid-cell2 {\n margin: 12px;\n flex: 1 auto !important;\n color: #242729;\n\n .top-tags {\n margin-top: 17px;\n marg"
},
{
"path": "src/modules/ProfilePage/UserSection/AvatarCard/AvatarCard.component.jsx",
"chars": 728,
"preview": "import React from \"react\";\nimport {Link} from \"react-router-dom\";\n\nimport './AvatarCard.styles.scss';\n\nconst AvatarCard "
},
{
"path": "src/modules/ProfilePage/UserSection/AvatarCard/AvatarCard.styles.scss",
"chars": 1190,
"preview": ".img-card {\n box-sizing: border-box;\n margin: 12px;\n width: 210px;\n overflow: hidden;\n\n .avatar-card {\n box-shad"
},
{
"path": "src/modules/ProfilePage/UserSection/ContentCard/ContentCard.component.jsx",
"chars": 1995,
"preview": "import React from \"react\";\nimport moment from \"moment\";\n\nimport './ContentCard.styles.scss';\n\nconst ContentCard = ({ use"
},
{
"path": "src/modules/ProfilePage/UserSection/ContentCard/ContentCard.styles.scss",
"chars": 1529,
"preview": ".content-card {\n box-sizing: border-box;\n margin: 12px;\n flex: 1 auto !important;\n\n .content-grid {\n margin: -8px"
},
{
"path": "src/modules/ProfilePage/UserSection/UserSection.component.jsx",
"chars": 633,
"preview": "import React from \"react\";\n\nimport AvatarCard from \"./AvatarCard/AvatarCard.component\";\nimport ContentCard from \"./Conte"
},
{
"path": "src/modules/ProfilePage/UserSection/UserSection.styles.scss",
"chars": 87,
"preview": ".grid {\n box-sizing: border-box;\n margin: -12px;\n display: flex;\n color: #242729;\n}"
},
{
"path": "src/modules/QuestionsPage/QuestionsPage.component.jsx",
"chars": 3379,
"preview": "import React, {Fragment, useEffect, useState} from 'react';\nimport {useLocation} from 'react-router-dom';\nimport {connec"
},
{
"path": "src/modules/QuestionsPage/QuestionsPage.styles.scss",
"chars": 869,
"preview": ".page {\n display: flex;\n}\n\n.questions-page {\n padding: 24px 0 24px 0;\n float: left;\n margin: 0;\n\n .questions-grid {"
},
{
"path": "src/modules/Register/Caption/Caption.component.jsx",
"chars": 1947,
"preview": "import React, {Fragment} from 'react';\nimport {Link} from 'react-router-dom';\n\nimport {ReactComponent as QuoteLogo} from"
},
{
"path": "src/modules/Register/Caption/Caption.styles.scss",
"chars": 622,
"preview": ".caption {\n margin-right: 48px;\n margin-bottom: 0px;\n width: 400px;\n display: flex;\n flex-direction: column;\n\n h3"
},
{
"path": "src/modules/Register/Register.component.jsx",
"chars": 1109,
"preview": "import React, {Fragment} from 'react';\nimport {connect} from 'react-redux';\nimport {Redirect} from 'react-router-dom';\ni"
},
{
"path": "src/modules/Register/Register.styles.scss",
"chars": 493,
"preview": ".auth-page {\n height: 100vh;\n width: 100%;\n background-color: #3d3d3d;\n}\n\n.register-content {\n background-color: tra"
},
{
"path": "src/modules/TagPage/TagPage.component.jsx",
"chars": 2915,
"preview": "import React, {useEffect, Fragment, useState} from 'react';\nimport {connect} from 'react-redux';\nimport {Redirect, usePa"
},
{
"path": "src/modules/TagPage/TagPage.styles.scss",
"chars": 112,
"preview": ".fs-body {\n padding-left: 24px;\n padding-right: 24px;\n width: 54rem;\n font-size: 13px;\n font-weight: 400;\n}"
},
{
"path": "src/redux/alert/alert.actions.js",
"chars": 343,
"preview": "import {v4 as uuidv4} from 'uuid';\nimport {SET_ALERT, REMOVE_ALERT} from './alert.types';\n\nexport const setAlert = (msg,"
},
{
"path": "src/redux/alert/alert.reducer.js",
"chars": 359,
"preview": "import {SET_ALERT, REMOVE_ALERT} from './alert.types';\n\nconst InitialState = [];\n\nexport default function alert(state = "
},
{
"path": "src/redux/alert/alert.types.js",
"chars": 82,
"preview": "export const SET_ALERT = 'SET_ALERT';\nexport const REMOVE_ALERT = 'REMOVE_ALERT';\n"
},
{
"path": "src/redux/answers/answers.actions.js",
"chars": 1600,
"preview": "import { setAlert } from \"../alert/alert.actions\";\nimport {\n GET_ANSWERS,\n ANSWER_ERROR,\n ADD_ANSWER,\n DELETE_ANSWER"
},
{
"path": "src/redux/answers/answers.reducer.js",
"chars": 850,
"preview": "import {\n GET_ANSWERS,\n ANSWER_ERROR,\n ADD_ANSWER,\n DELETE_ANSWER,\n} from './answers.types';\n\nconst initialState = {"
},
{
"path": "src/redux/answers/answers.types.js",
"chars": 172,
"preview": "export const GET_ANSWERS = 'GET_ANSWERS';\nexport const ANSWER_ERROR = 'ANSWER_ERROR';\nexport const DELETE_ANSWER = 'DELE"
},
{
"path": "src/redux/auth/auth.actions.js",
"chars": 1713,
"preview": "import { loadUserData, registerUser, loginUser } from '../../api/authApi'\nimport setAuthToken from './auth.utils';\nimpor"
},
{
"path": "src/redux/auth/auth.reducer.js",
"chars": 1014,
"preview": "import {\n REGISTER_SUCCESS,\n REGISTER_FAIL,\n USER_LOADED,\n AUTH_ERROR,\n LOGIN_SUCCESS,\n LOGIN_FAIL,\n LOGOUT,\n} fr"
},
{
"path": "src/redux/auth/auth.types.js",
"chars": 297,
"preview": "export const REGISTER_SUCCESS = 'REGISTER_SUCCESS';\nexport const REGISTER_FAIL = 'REGISTER_FAIL';\nexport const USER_LOAD"
},
{
"path": "src/redux/auth/auth.utils.js",
"chars": 242,
"preview": "import axios from 'axios';\n\nconst setAuthToken = (token) => {\n if (token) {\n axios.defaults.headers.common['x-auth-t"
},
{
"path": "src/redux/comments/comments.actions.js",
"chars": 1627,
"preview": "import { setAlert } from \"../alert/alert.actions\";\nimport {\n GET_COMMENTS,\n COMMENT_ERROR,\n ADD_COMMENT,\n DELETE_COM"
},
{
"path": "src/redux/comments/comments.reducer.js",
"chars": 886,
"preview": "import {\n GET_COMMENTS,\n COMMENT_ERROR,\n ADD_COMMENT,\n DELETE_COMMENT,\n} from './comments.types';\n\nconst initialStat"
},
{
"path": "src/redux/comments/comments.types.js",
"chars": 180,
"preview": "export const GET_COMMENTS = 'GET_COMMENTS';\nexport const COMMENT_ERROR = 'COMMENT_ERROR';\nexport const DELETE_COMMENT = "
},
{
"path": "src/redux/posts/posts.actions.js",
"chars": 2488,
"preview": "import { setAlert } from \"../alert/alert.actions\";\nimport {\n GET_POSTS,\n GET_POST,\n GET_TAG_POSTS,\n POST_ERROR,\n DE"
},
{
"path": "src/redux/posts/posts.reducer.js",
"chars": 996,
"preview": "import {\n GET_POSTS,\n GET_POST,\n GET_TAG_POSTS,\n POST_ERROR,\n DELETE_POST,\n ADD_POST,\n} from './posts.types';\n\ncon"
},
{
"path": "src/redux/posts/posts.types.js",
"chars": 238,
"preview": "export const GET_POSTS = 'GET_POSTS';\nexport const GET_POST = 'GET_POST';\nexport const GET_TAG_POSTS = 'GET_TAG_POSTS';\n"
},
{
"path": "src/redux/root-reducer.js",
"chars": 443,
"preview": "import {combineReducers} from 'redux';\nimport alert from './alert/alert.reducer';\nimport auth from './auth/auth.reducer'"
},
{
"path": "src/redux/store.js",
"chars": 383,
"preview": "import {createStore, applyMiddleware} from 'redux';\nimport {composeWithDevTools} from 'redux-devtools-extension';\nimport"
},
{
"path": "src/redux/tags/tags.actions.js",
"chars": 950,
"preview": "import {setAlert} from '../alert/alert.actions';\nimport {GET_TAG, GET_TAGS, TAG_ERROR} from './tags.types';\nimport { all"
},
{
"path": "src/redux/tags/tags.reducer.js",
"chars": 705,
"preview": "import {GET_TAG, GET_TAGS, TAG_ERROR} from './tags.types';\n\nconst initialState = {\n tags: [],\n tag: null,\n loading: t"
},
{
"path": "src/redux/tags/tags.types.js",
"chars": 108,
"preview": "export const GET_TAG = 'GET_TAG';\nexport const GET_TAGS = 'GET_TAGS';\nexport const TAG_ERROR = 'TAG_ERROR';\n"
},
{
"path": "src/redux/users/users.actions.js",
"chars": 797,
"preview": "import {GET_USERS, GET_USER, USER_ERROR} from './users.types';\nimport { usersData, profileData } from '../../api/usersAp"
},
{
"path": "src/redux/users/users.reducer.js",
"chars": 624,
"preview": "import {GET_USERS, GET_USER, USER_ERROR} from './users.types';\n\nconst initialState = {\n users: [],\n user: null,\n load"
},
{
"path": "src/redux/users/users.types.js",
"chars": 114,
"preview": "export const GET_USERS = 'GET_USERS';\nexport const GET_USER = 'GET_USER';\nexport const USER_ERROR = 'USER_ERROR';\n"
},
{
"path": "src/utils/censorBadWords.js",
"chars": 519,
"preview": "import Filter from \"bad-words\";\n\nconst replaceRegex = /(?<=.).+(?=.)/;\nconst placeHolder = (str) => \"*\".repeat(str.lengt"
},
{
"path": "src/utils/handleFilter.js",
"chars": 1422,
"preview": "const handleFilter = (sortType, page = '') => {\n let temp = sortType;\n\n if (page === 'users' && temp === 'Name') {"
},
{
"path": "src/utils/handleSorting.js",
"chars": 2785,
"preview": "const handleSorting = (sortType, page = '') => {\n let temp = sortType;\n\n if (page === 'users' && temp === 'Name') {\n "
},
{
"path": "src/utils/htmlSubstring.js",
"chars": 1240,
"preview": "// http://jsfiddle.net/danmana/5mNNU/\nconst htmlSubstring = (s, n) => {\n var m, r = /<([^>\\s]*)[^>]*>/g,\n stac"
},
{
"path": "src/utils/injectEllipsis.js",
"chars": 329,
"preview": "const injectEllipsis = (html) => {\n const re = /<\\/p>/g\n const str = html;\n let lastMatchIndex;\n let match;\n"
}
]
About this extraction
This page contains the full source code of the Mayank0255/Stackoverflow-Clone-Frontend GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 152 files (197.2 KB), approximately 57.2k tokens, and a symbol index with 40 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.