├── .env ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── баг-репорт.md │ └── предложить-фичу.md └── workflows │ ├── cd.yml │ └── ci.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .lintstagedrc ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENCE ├── README.md ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json └── robots.txt └── src ├── __tests__ └── compact-mode.test.jsx ├── app ├── App.jsx ├── GlobalProvider.jsx ├── GlobalStyles.js ├── LazyPlaceholder.jsx ├── Title │ ├── Title.jsx │ └── Title.test.jsx ├── constants.js ├── store.js └── theme.js ├── assets ├── animation-back-to-top.css ├── bootstrap-placeholder.css ├── sprite.svg └── toast-ui-iqa-theme.css ├── common ├── context │ └── Auth │ │ ├── AuthProvider.jsx │ │ ├── index.jsx │ │ └── useAuth.jsx ├── hooks │ ├── useOnScroll.js │ └── useQueryString.js └── utils │ ├── title.js │ ├── truncateLongText.js │ └── url.js ├── components ├── FavoritePopoverContent.jsx ├── icons │ ├── ArrowAvatar.jsx │ ├── CheckIcon.jsx │ ├── ChevronUpIcon.jsx │ ├── CloseIcon.jsx │ ├── CloseMenuIcon.jsx │ ├── CommentsIcon.jsx │ ├── DeleteIcon.jsx │ ├── EmptyFolderIcon.jsx │ ├── ErrorIcon.jsx │ ├── FavoritesIcon.jsx │ ├── FavoritesInIcon.jsx │ ├── GitHubIcons.jsx │ ├── HelpIcon.jsx │ ├── IconIcon.jsx │ ├── InfoIcon.jsx │ ├── LogoIcon.jsx │ ├── LogoNoColorIcon.jsx │ ├── LongLogoIcon.jsx │ ├── MenuIcon.jsx │ ├── NoIcon.jsx │ ├── PlusIcon.jsx │ ├── QuestionViewsIcon.jsx │ ├── RestoreIcon.jsx │ ├── SaveIcon.jsx │ ├── SearchIcon.jsx │ ├── SpinnerIcon.jsx │ ├── SuccessIcon.jsx │ ├── WarningIcon.jsx │ └── YesIcon.jsx └── layout │ ├── Footer.jsx │ ├── Paper │ ├── Paper.test.jsx │ └── index.jsx │ ├── ScrollToTop.jsx │ └── header │ ├── AdaptiveMenu.jsx │ ├── AnimatedSearch.jsx │ ├── Header.jsx │ ├── Logo.jsx │ ├── PopoverContent.jsx │ ├── Search.jsx │ └── header-menu │ ├── HeaderMenu.jsx │ ├── LinkToDeleted.jsx │ ├── LinkToFavorites.jsx │ └── LinkToProfilePage.jsx ├── features ├── application │ └── applicationSlice.js ├── comments │ ├── AddComment.jsx │ ├── CommentItem.jsx │ ├── CommentView.jsx │ ├── CommentsList.jsx │ ├── CommentsOfQuestion.jsx │ ├── CommentsPlaceholder.jsx │ ├── comment-actions │ │ ├── CommentsActions.jsx │ │ └── DeleteAction.jsx │ └── commentsSlice.js ├── profile │ ├── ProfileUser.jsx │ ├── UserFullnameForm.jsx │ └── profileSlice.js ├── questions │ ├── create-question │ │ └── CreateQuestionPage.jsx │ ├── question-page │ │ ├── QuestionPage.jsx │ │ ├── QuestionPageContent.jsx │ │ ├── QuestionPageHeader.jsx │ │ └── QuestionPagePlaceholder.jsx │ ├── questions-list │ │ ├── QuestionBlock.jsx │ │ ├── QuestionContent.jsx │ │ ├── QuestionEmptyFolder.jsx │ │ ├── QuestionHeader.jsx │ │ ├── QuestionsList.jsx │ │ ├── QuestionsListMapper.jsx │ │ ├── QuestionsListPlaceholder.jsx │ │ └── question-actions │ │ │ ├── CommentsAction.jsx │ │ │ ├── DeleteAction.jsx │ │ │ ├── FavoriteAction.jsx │ │ │ ├── FavoriteIconSwitcher.jsx │ │ │ ├── FavoritePopover.jsx │ │ │ ├── QuestionViews.jsx │ │ │ ├── QuestionsActions.jsx │ │ │ ├── RestoreAction.jsx │ │ │ └── TheQuestionAction.jsx │ └── questionsSlice.js └── search │ └── searchQuestionSlice.js ├── index.jsx ├── index.test.jsx └── pages └── HelpPage.jsx /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_FEATURE_SEARCH=on 2 | REACT_APP_FEATURE_ADD_QUESTION=on 3 | REACT_APP_FEATURE_FAVORITES=on 4 | REACT_APP_FEATURE_DELETE_QUESTION=on 5 | REACT_APP_FEATURE_TAGS=on 6 | REACT_APP_FEATURE_COMMENTARIES=on 7 | REACT_APP_FEATURE_RATING=on 8 | REACT_APP_FEATURE_LIKE_COMMENT=on 9 | REACT_APP_FEATURE_DELETE_COMMENT=on 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['react-app', 'airbnb', 'prettier', 'plugin:eslint-plugin/recommended'], 3 | settings: { 4 | 'import/resolver': { 5 | node: { 6 | moduleDirectory: ['node_modules', 'src/'], 7 | }, 8 | }, 9 | }, 10 | rules: { 11 | // опция для проверки на абсолютный импорт 12 | // выкидывает ошибку если испорт начинается с '../' 13 | 'no-restricted-imports': [ 14 | 'error', 15 | { 16 | patterns: ['../../*'], 17 | }, 18 | ], 19 | // 'no-unused-vars': 'off', 20 | 21 | // делаем error, чтобы зачищать консоли, если где-то консоль важна, 22 | // то правило нужно отключить на строке 23 | 'no-console': 'error', 24 | 25 | // правило не нужно т.к. CRA настроен на автоимпорт реакта 26 | 'react/react-in-jsx-scope': 'off', 27 | 28 | // todo нужно узнать что лучше off или error (по умолчанию error) 29 | 'import/prefer-default-export': 'off', 30 | 31 | // иначе компонента выглядят уродски 32 | 'arrow-body-style': 'off', 33 | 34 | // styled-components часто требует пробрасывания всех пропсов 35 | 'react/jsx-props-no-spreading': 'off', 36 | 37 | // отключаем из-за использования immer в редьюсерах 38 | // https://redux-toolkit.js.org/usage/immer-reducers#linting-state-mutations 39 | 'no-param-reassign': ['error', { props: true, ignorePropertyModificationsFor: ['state'] }], 40 | 41 | 'no-underscore-dangle': ['error', { allow: ['_id'] }], 42 | 43 | // свойство появилось в cra@5 44 | 'react/function-component-definition': 'off', 45 | }, 46 | overrides: [ 47 | { 48 | files: ['**/*.stories.*'], 49 | rules: { 50 | 'import/no-anonymous-default-export': 'off', 51 | 'react/jsx-props-no-spreading': 'off', 52 | }, 53 | }, 54 | ], 55 | }; 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/баг-репорт.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Баг-репорт 3 | about: Сообщить о найденном баге 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Описание бага 10 | 11 | Коротко, но в то же время понятно опиши в чём заключается суть бага. 12 | 13 | ## Как можно увидеть этот баг? 14 | 15 | Выполнить следующие шаги: 16 | 17 | 1. Зайти на сайт 18 | 2. Кликнуть на ... 19 | 3. Сделать ... 20 | 4. Увидеть ошибку 21 | 22 | ## Что должно было случиться? 23 | 24 | Опиши что ты ожидал увидеть вместо ошибки выполнив эти шаги 25 | 26 | ## Скриншоты 27 | 28 | Приложи скриншоты, если это возможно. 29 | 30 | ## Дополнительное описание 31 | 32 | Свободное поле для подробного описания 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/предложить-фичу.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Предложить фичу 3 | about: Предложи свою идею для улучшения 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Какую фичу ты предлагаешь? 10 | 11 | Небольшое описание фичи 12 | 13 | ## Какую пользу принесет эта фича? 14 | 15 | Дай короткое, но понятное описание о пользе этой фичи 16 | 17 | ## Дополнительное описание 18 | 19 | Опиши фичи подробнее в деталях. Если есть возможность, то приложи скриншоты или схему реализации фичи. 20 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Stage CD 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | build: 9 | # The type of runner that the job will run on 10 | runs-on: ubuntu-latest 11 | 12 | # Steps represent a sequence of tasks that will be executed as part of the job 13 | steps: 14 | - name: Deploy using ssh 15 | uses: appleboy/ssh-action@master 16 | with: 17 | host: ${{ secrets.HOST }} 18 | username: ${{ secrets.USERNAME }} 19 | password: ${{ secrets.PASSWORD }} 20 | port: 22 21 | script_stop: true 22 | script: | 23 | cd /root/stage/iqa-frontend 24 | git pull origin main 25 | git status 26 | npm install 27 | pm2 stop frontend 28 | npm run build 29 | pm2 start frontend 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Stage CI 2 | 3 | env: 4 | REACT_APP_FEATURE_SEARCH: on 5 | REACT_APP_FEATURE_ADD_QUESTION: on 6 | REACT_APP_FEATURE_FAVORITES: on 7 | REACT_APP_FEATURE_DELETE_QUESTION: on 8 | REACT_APP_FEATURE_TAGS: on 9 | REACT_APP_FEATURE_COMMENTARIES: on 10 | REACT_APP_FEATURE_RATING: on 11 | DEBUG_PRINT_LIMIT: 9999999 12 | 13 | on: 14 | push: 15 | branches: [main] 16 | pull_request: 17 | branches: [main] 18 | 19 | jobs: 20 | check: 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | 26 | - name: Use Node.js 16.x 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: '16.x' 30 | - run: npm ci 31 | - run: npm run check 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .DS_Store 4 | build 5 | storybook-static 6 | 7 | # Файл взят с https://github.com/github/gitignore/blob/master/Node.gitignore 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | lerna-debug.log* 16 | .pnpm-debug.log* 17 | 18 | # Diagnostic reports (https://nodejs.org/api/report.html) 19 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 20 | 21 | # Runtime data 22 | pids 23 | *.pid 24 | *.seed 25 | *.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | *.lcov 33 | 34 | # nyc test coverage 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 38 | .grunt 39 | 40 | # Bower dependency directory (https://bower.io/) 41 | bower_components 42 | 43 | # node-waf configuration 44 | .lock-wscript 45 | 46 | # Compiled binary addons (https://nodejs.org/api/addons.html) 47 | build/Release 48 | 49 | # Dependency directories 50 | node_modules/ 51 | jspm_packages/ 52 | 53 | # Snowpack dependency directory (https://snowpack.dev/) 54 | web_modules/ 55 | 56 | # TypeScript cache 57 | *.tsbuildinfo 58 | 59 | # Optional npm cache directory 60 | .npm 61 | 62 | # Optional eslint cache 63 | .eslintcache 64 | 65 | # Microbundle cache 66 | .rpt2_cache/ 67 | .rts2_cache_cjs/ 68 | .rts2_cache_es/ 69 | .rts2_cache_umd/ 70 | 71 | # Optional REPL history 72 | .node_repl_history 73 | 74 | # Output of 'npm pack' 75 | *.tgz 76 | 77 | # Yarn Integrity file 78 | .yarn-integrity 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | .parcel-cache 83 | 84 | # Next.js build output 85 | .next 86 | out 87 | 88 | # Nuxt.js build / generate output 89 | .nuxt 90 | dist 91 | 92 | # Gatsby files 93 | .cache/ 94 | # Comment in the public line in if your project uses Gatsby and not Next.js 95 | # https://nextjs.org/blog/next-9-1#public-directory-support 96 | # public 97 | 98 | # vuepress build output 99 | .vuepress/dist 100 | 101 | # Serverless directories 102 | .serverless/ 103 | 104 | # FuseBox cache 105 | .fusebox/ 106 | 107 | # DynamoDB Local files 108 | .dynamodb/ 109 | 110 | # TernJS port file 111 | .tern-port 112 | 113 | # Stores VSCode versions used for testing VSCode extensions 114 | .vscode-test 115 | 116 | # yarn v2 117 | .yarn/cache 118 | .yarn/unplugged 119 | .yarn/build-state.yml 120 | .yarn/install-state.gz 121 | .pnp.* 122 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*{.json,.jsx,.js,.css,.html,.yml}": "prettier --check", 3 | "*{.jsx,.js}": "eslint --report-unused-disable-directives --max-warnings 0", 4 | "*": "npm run test:ci ./src" 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "printWidth": 100, 5 | "trailingComma": "es5", 6 | "tabWidth": 2, 7 | "endOfLine": "auto", 8 | "quoteProps": "as-needed", 9 | "jsxSingleQuote": false, 10 | "bracketSameLine": false 11 | } 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Участие в проекте 2 | 3 | Любой желающий может внести свой вклад в развитие данного проекта. 4 | 5 | Помощь может заключаться не только в непосредственной разработке, но и в поиске ошибок, исправлении опечаток, предложении новых идей, которые сделают проект лучше. 6 | 7 | ## Как предложить свою идею или указать на найденный баг? 8 | 9 | Открой раздел [Issues](https://github.com/intocode/iqa-frontend/issues). Все текущие и будущие задачи обсуждаются здесь. Если в списке открытых вопросов нет твоей идеи, то смело создавай новый Issue с предложением. 10 | 11 | ## Как начать выполнять задачу? 12 | 13 | Открой раздел [Issues](https://github.com/intocode/iqa-frontend/issues). Выбери задачу, над которой хотел бы поработать. Внимательно ознакомься с описанием. Если остались вопросы, то задавай их в обсуждении Issue. 14 | 15 | Убедись, что задача тебе полностью понятна и только после этого приступай к её реализации. Если задача связана с написанием кода, то подробности выполнения такой задачи указаны в разделе ниже. 16 | 17 | ## Технические требования 18 | 19 | Ко всем выполняемым задачам есть определенные требования. Они довольно простые: 20 | 21 | **Один Pull Request должен закрывать только одну задачу.** Нельзя включать в Pull Request никакие изменения, которые напрямую не относятся к теме пуллреквеста, даже если это просто исправление опечатки в тексте. 22 | 23 | **Pull Request должен закрывать решаемую задачу полностью.** Нельзя закрывать только часть задачи, другую часть оставив на другой Pull Request. Если задача требует разбивки на более мелкие задачи, то это нужно обсудить в Issue этой задачи и в случае необходимости создать новые Issue для подзадач. 24 | 25 | **Весь код должен идти с комментариями.** Разрешается не комментировать участки кода, которые можно однозначно понять в рамках своего контекста. Комментарии не должны содержать грамматические ошибки. 26 | 27 | _Пример 1:_ 28 | 29 | ```javascript 30 | useEffect(() => { 31 | dispatch(fetchUsers()); 32 | }, [dispatch]); 33 | ``` 34 | 35 | В данном случае можно недвусмысленно понять что делает этот код, поэтому его можно не комментировать. 36 | 37 | --- 38 | 39 | _Пример 2:_ 40 | 41 | ```javascript 42 | // создаем переменную стейта, чтобы сделать поле ввода логина управляемым компонентом 43 | const [text, setText] = useState(null); 44 | ``` 45 | 46 | В этом случае код нужно было прокомментировать, потому что переменная `text` может использоваться как угодно и нужно вносить ясность в момент её создания. 47 | 48 | --- 49 | 50 | _Пример 3:_ 51 | 52 | ```javascript 53 | const [loginFieldValue, setLoginFieldValue] = useState(''); 54 | ``` 55 | 56 | Данной код выполяет ту же задачу, но за счет хорошего нейминга переменных можно избежать неоднозначности, поэтому дополнительный комментарий не требуется. 57 | 58 | ## Как приступить к разработке 59 | 60 | ### Подготовка git 61 | 62 | **1. Сделай fork текущего репозитория**. 63 | 64 | **2. Склонируй свой fork на рабочий компьютер**: 65 | 66 | ```shell 67 | git clone https://github.com/твой-логин/iqa-frontend.git 68 | ``` 69 | 70 | **3. Добавь головной сервер `upstream`**: 71 | 72 | ```shell 73 | git remote add upstream https://github.com/intocode/iqa-frontend.git 74 | ``` 75 | 76 | **4. Создай новую ветку под выполняемую задачу**: 77 | 78 | ```shell 79 | git switch -c my-feature 80 | ``` 81 | 82 | ### Запуск тестов 83 | 84 | После выполнения задачи запусти линтеры и тесты командой `npm run check`. Если какой-то тест не проходит, то внеси исправления в свой код. 85 | 86 | ### Выгрузка изменений 87 | 88 | **Если задача будет решена и все тесты проходят** выгрузи свою работу и открыть новый Pull Request: 89 | 90 | ```shell 91 | git add измененный-файл.js 92 | git commit -m "Хорошее описание коммита" 93 | git push origin my-feature 94 | ``` 95 | 96 | После этого нужно перейти в свой репозиторий на GitHub и открыть Pull Request. Подробнее об этом [в документации GitHub](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). 97 | 98 | ### Проверка на конфликты 99 | 100 | Если выполнение задачи заняло много времени, то возможно репозиторий в это время был обновлен. Необходимо сгрузить себе изменения и замёржить их. Для этого: 101 | 102 | **а) Сделай коммит своей работы:** 103 | 104 | ```shell 105 | git add file1.js file2.js 106 | git commit -m "Описание выполненной работы" 107 | ``` 108 | 109 | **б) Перейди на ветку `main`:** 110 | 111 | ```shell 112 | git switch main 113 | ``` 114 | 115 | **в) Подтяни изменения с головной ветки:** 116 | 117 | ```shell 118 | git pull upstream main 119 | ``` 120 | 121 | **г) Перейди на рабочую ветку и сделай мёрж:** 122 | 123 | ```shell 124 | git switch my-feature 125 | git merge main 126 | ``` 127 | 128 | **д) Если есть конфликты, то исправь их.** Если не знаешь как это делать, то прочитай какой-нибудь материал на эту тему. Например [этот](https://www.atlassian.com/ru/git/tutorials/using-branches/merge-conflicts), [этот](https://stackoverflow.com/questions/161813/how-do-i-resolve-merge-conflicts-in-a-git-repository) или [вот этот](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line). 129 | 130 | После этого можно делать `push` и открывать Pull Request. 131 | 132 | ## Принятие Pull Request 133 | 134 | Открытый Pull Request должен пройти код ревью как минимум двух участников проекта. Однако, в некоторых случаях мы можем принять изменения не дожидаясь двух подтверждений. 135 | 136 | Всё обсуждение пулл реквеста должно вестись на его странице в комментариях, чтобы оно было доступно всем участникам разработки. 137 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright 2021 (c) intocode 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iqa-frontend 2 | 3 | [![intocode license](https://img.shields.io/github/license/intocode/iqa-frontend)](https://github.com/intocode/iqa-frontend) 4 | 5 | Репозиторий содержит фронтенд проекта iqa-application. Backend находится в [отдельном репозитории](https://github.com/intocode/iqa-backend). 6 | 7 | Группа для обсуждения в Discord: [discord.gg/3B8pF2fYw7](https://discord.gg/3B8pF2fYw7). 8 | 9 | ## Начало работы 10 | 11 | ### Установка 12 | 13 | ```shell 14 | git clone https://github.com/intocode/iqa-frontend.git 15 | cd iqa-frontend 16 | npm install 17 | ``` 18 | 19 | ### Запуск в режиме разработки 20 | 21 | ```shell 22 | npm start 23 | ``` 24 | 25 | Установка backend-части приложения не требуется. Все запросы будут направлены на доступный в сети stage-сервер. 26 | 27 | **ВНИМАНИЕ!** Установка для участников разработки немного отличается от вышеуказанной. Подробнее читай в файле [CONTRIBUTING.md](./CONTRIBUTING.md). 28 | 29 | ## Участие в разработке 30 | 31 | Любой желающий может внести свой вклад в развитие данного проекта. 32 | 33 | Помощь может заключаться не только в непосредственной разработке, но и в поиске ошибок, исправлении опечаток, предложении новых идей, которые сделают проект лучше. 34 | 35 | Подробности участия в проекте можно прочитать в файле [CONTRIBUTING.md](./CONTRIBUTING.md). 36 | 37 | ## Стек 38 | 39 | - JavaScript, ES6, ES7 40 | - React 17 (FC), create-react-app, prop-types 41 | - Redux, Redux Toolkit, Redux Persist 42 | - antd, styled-components, bootstrap-grid.css 43 | - axios, dayjs, react-transition-group 44 | - Jest, ESLint, Prettier, lint-staged 45 | 46 | 🤘🏼 Необязательно знать все инструменты, чтобы начать работу с проектом. 47 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | }, 5 | "include": ["src"] 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "license": "MIT", 6 | "dependencies": { 7 | "@ant-design/icons": "^4.7.0", 8 | "@reduxjs/toolkit": "^1.8.2", 9 | "@toast-ui/editor": "^3.2.0", 10 | "@toast-ui/react-editor": "^3.2.0", 11 | "antd": "^4.23.0", 12 | "axios": "^0.27.2", 13 | "bootstrap": "^5.1.3", 14 | "dayjs": "^1.11.3", 15 | "prop-types": "^15.8.1", 16 | "react": "^17.0.2", 17 | "react-dom": "^17.0.2", 18 | "react-redux": "^7.2.6", 19 | "react-router-dom": "^5.3.0", 20 | "react-scripts": "5.0.1", 21 | "react-transition-group": "^4.4.2", 22 | "redux-persist": "^6.0.0", 23 | "source-map-explorer": "^2.5.3", 24 | "styled-components": "^5.3.3" 25 | }, 26 | "scripts": { 27 | "check": "npm run prettier:check && npm run lint && npm run test:ci && cross-env CI=true npm run build", 28 | "start": "react-scripts start", 29 | "build": "cross-env GENERATE_SOURCEMAP=false react-scripts build", 30 | "test": "react-scripts test", 31 | "test:ci": "cross-env CI=true react-scripts test", 32 | "eject": "react-scripts eject", 33 | "fix": "npm run lint:fix && npm run prettier", 34 | "lint": "npm run lint:js", 35 | "lint:fix": "eslint ./src --ext .js,.jsx --fix", 36 | "lint:js": "eslint ./src --ext .js,.jsx --report-unused-disable-directives --max-warnings 0", 37 | "storybook": "start-storybook -p 6006", 38 | "build-storybook": "build-storybook", 39 | "prepare": "husky install", 40 | "prettier:check": "prettier . --check", 41 | "prettier": "prettier . --write", 42 | "analyze": "npm run analyze:build && source-map-explorer 'build/static/js/*.js'", 43 | "analyze:build": "GENERATE_SOURCEMAP=true react-scripts build" 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | "> 1%, IE 10", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | }, 57 | "devDependencies": { 58 | "@testing-library/jest-dom": "^5.16.2", 59 | "@testing-library/react": "^12.1.4", 60 | "@types/jest": "^27.4.1", 61 | "@types/react-dom": "^17.0.13", 62 | "@types/react-router-dom": "^5.3.3", 63 | "@types/styled-components": "^5.1.24", 64 | "cross-env": "^7.0.3", 65 | "eslint-config-airbnb": "^19.0.4", 66 | "eslint-config-prettier": "^8.5.0", 67 | "eslint-plugin-eslint-plugin": "^5.0.6", 68 | "husky": "^8.0.3", 69 | "lint-staged": "^13.1.2", 70 | "prettier": "^2.7.1" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intocode/iqa-frontend/c15005a3631948125e09f8ec5216f5612dfe4a74/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | iqa: помощь в прохождении собеседований 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/__tests__/compact-mode.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | import { fireEvent, render, screen, waitFor } from '@testing-library/react'; 4 | import dayjs from 'dayjs'; 5 | import 'dayjs/locale/ru'; 6 | import relativeTime from 'dayjs/plugin/relativeTime'; 7 | import calendar from 'dayjs/plugin/calendar'; 8 | import { App } from 'app/App'; 9 | import { BASE_API_URL, LS_TOKEN_KEY } from 'app/constants'; 10 | import '@testing-library/jest-dom'; 11 | import { GlobalProvider } from 'app/GlobalProvider'; 12 | 13 | // import 'bootstrap/dist/css/bootstrap-grid.min.css'; 14 | 15 | axios.defaults.baseURL = BASE_API_URL; 16 | axios.defaults.headers.authorization = `Bearer ${localStorage.getItem(LS_TOKEN_KEY)}`; 17 | 18 | dayjs.extend(relativeTime); 19 | dayjs.extend(calendar); 20 | dayjs.locale('ru'); 21 | 22 | describe('Header rendering', () => { 23 | it('renders header', async () => { 24 | render( 25 | 26 | 27 | 28 | ); 29 | 30 | // дожидаемся подгрузки вопросов 31 | await waitFor(() => expect(screen.getAllByTestId('question-block')[0]).toBeInTheDocument(), { 32 | timeout: 7000, 33 | }); 34 | 35 | // кликаем на "Компактный вид" 36 | fireEvent.click(screen.getByTestId('compact-mode-label')); 37 | 38 | // проверяем остались ли ненужные элементы 39 | expect(screen.queryAllByTestId('not-for-compact')).toBeInstanceOf(Array); 40 | expect(screen.queryAllByTestId('not-for-compact').length).toBe(0); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/app/App.jsx: -------------------------------------------------------------------------------- 1 | import { useDispatch } from 'react-redux'; 2 | import { Route, Switch } from 'react-router-dom'; 3 | import { useEffect, lazy, Suspense } from 'react'; 4 | import { useAuth } from 'common/context/Auth/useAuth'; 5 | import { Header } from 'components/layout/header/Header'; 6 | import { fetchProfile } from 'features/profile/profileSlice'; 7 | import { Footer } from 'components/layout/Footer'; 8 | import UserFullnameForm from 'features/profile/UserFullnameForm'; 9 | import { LazyPlaceholder } from './LazyPlaceholder'; 10 | 11 | const QuestionPage = lazy(() => import('features/questions/question-page/QuestionPage')); 12 | const CreateQuestion = lazy(() => import('features/questions/create-question/CreateQuestionPage')); 13 | const QuestionsList = lazy(() => import('features/questions/questions-list/QuestionsList')); 14 | 15 | const ProfileUser = lazy(() => import('features/profile/ProfileUser')); 16 | const HelpPage = lazy(() => import('pages/HelpPage')); 17 | 18 | const routes = [ 19 | { 20 | key: 10, 21 | component: QuestionsList, 22 | path: '/', 23 | exact: true, 24 | }, 25 | { 26 | key: 20, 27 | component: CreateQuestion, 28 | path: '/create', 29 | }, 30 | { 31 | key: 30, 32 | component: QuestionPage, 33 | path: '/question/:id', 34 | }, 35 | { 36 | key: 40, 37 | component: ProfileUser, 38 | path: '/profile', 39 | }, 40 | { 41 | key: 50, 42 | component: HelpPage, 43 | path: '/help', 44 | }, 45 | ]; 46 | 47 | export const App = () => { 48 | const { token } = useAuth(); 49 | 50 | const dispatch = useDispatch(); 51 | 52 | useEffect(() => { 53 | if (token) { 54 | dispatch(fetchProfile()); 55 | } 56 | }, [dispatch, token]); 57 | 58 | return ( 59 | <> 60 | 61 |
62 | }> 63 | 64 | {routes.map((route) => ( 65 | 66 | ))} 67 | 68 | 69 |