├── .firebaserc ├── public ├── robots.txt ├── favicon.ico ├── favicon.png ├── manifest.json └── index.html ├── src ├── components │ ├── Footer │ │ ├── components │ │ │ ├── index.ts │ │ │ └── FooterContent │ │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── styled.ts │ ├── Header │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── MenuWrapper │ │ │ │ ├── styled.ts │ │ │ │ ├── components │ │ │ │ │ ├── CoursesSelect │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── LangSelect │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── Nav │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── styled.ts │ │ │ │ └── index.tsx │ │ │ └── BurgerMenu │ │ │ │ ├── styled.ts │ │ │ │ └── index.tsx │ │ ├── styled.ts │ │ └── index.tsx │ ├── App │ │ └── styled.ts │ ├── SelectField │ │ ├── styled.ts │ │ └── index.tsx │ ├── ErrorBoundary │ │ ├── styled.ts │ │ └── index.tsx │ ├── Loader │ │ ├── index.tsx │ │ └── styled.ts │ ├── TablePopup │ │ ├── index.tsx │ │ └── styled.ts │ ├── InputField │ │ ├── styled.ts │ │ └── index.tsx │ ├── TourGuide │ │ ├── styled.ts │ │ ├── index.tsx │ │ └── tourConfig.tsx │ ├── index.ts │ ├── PrivateRoute │ │ └── index.tsx │ ├── Pagination │ │ └── index.tsx │ ├── ErrorModal │ │ └── index.tsx │ ├── Modal │ │ └── index.module.css │ ├── FilterSelect │ │ └── index.tsx │ ├── ModalExpel │ │ └── index.tsx │ ├── CourseField │ │ ├── styled.ts │ │ └── index.tsx │ ├── ModalCreated │ │ └── index.tsx │ ├── ModalJoin │ │ └── index.tsx │ ├── FilterForm │ │ ├── filterFormFields.ts │ │ └── styled.ts │ └── ModalCreateEditTeam │ │ └── index.tsx ├── modules │ ├── AdminPage │ │ ├── components │ │ │ ├── index.ts │ │ │ └── ContentWrapper │ │ │ │ ├── components │ │ │ │ ├── CoursesList │ │ │ │ │ ├── styled.ts │ │ │ │ │ ├── components │ │ │ │ │ │ └── Course │ │ │ │ │ │ │ ├── styled.ts │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── AddCourseBlock │ │ │ │ │ ├── styled.ts │ │ │ │ │ └── components │ │ │ │ │ │ └── InputsBlock │ │ │ │ │ │ ├── styled.ts │ │ │ │ │ │ └── index.tsx │ │ │ │ └── ShowCourseSelect │ │ │ │ │ └── index.tsx │ │ │ │ └── styled.ts │ │ └── index.tsx │ ├── LoginPage │ │ ├── components │ │ │ ├── index.ts │ │ │ └── LoginInfoBlock │ │ │ │ ├── index.tsx │ │ │ │ └── styled.ts │ │ ├── index.tsx │ │ ├── selectors.ts │ │ ├── loginPageMiddleware.ts │ │ ├── styled.ts │ │ └── loginPageReducer.ts │ ├── TeamsList │ │ ├── components │ │ │ ├── index.ts │ │ │ └── Teams │ │ │ │ ├── components │ │ │ │ ├── index.ts │ │ │ │ ├── TeamUserTable │ │ │ │ │ ├── components │ │ │ │ │ │ └── TableRow │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── TableCell │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ └── ExpelButton │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ └── styled.ts │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styled.tsx │ │ │ │ ├── MyTeam │ │ │ │ │ ├── components │ │ │ │ │ │ └── MyTeamInfoBlock │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ ├── NotificationPopup │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ └── styled.ts │ │ │ │ │ │ │ └── MyTeamInfoLine │ │ │ │ │ │ │ │ ├── styled.tsx │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ ├── styled.ts │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── MemberListToggle │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styled.ts │ │ │ │ ├── TeamItem │ │ │ │ │ ├── styled.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── TeamsHeader │ │ │ │ │ ├── styled.ts │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ ├── styled.ts │ │ ├── selectors.ts │ │ └── index.tsx │ ├── TutorialPage │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── StepBlock │ │ │ │ ├── styled.ts │ │ │ │ └── index.tsx │ │ │ └── NoteBlock │ │ │ │ ├── index.tsx │ │ │ │ └── styled.ts │ │ ├── tutorialPageInfo.tsx │ │ ├── styled.ts │ │ └── index.tsx │ ├── StudentsTable │ │ ├── components │ │ │ └── Dashboard │ │ │ │ ├── components │ │ │ │ ├── index.ts │ │ │ │ ├── TableBody │ │ │ │ │ ├── styled.ts │ │ │ │ │ ├── components │ │ │ │ │ │ └── TableRow │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ └── TableItem │ │ │ │ │ │ │ │ ├── styled.ts │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ ├── styled.ts │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── TableHead │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styled.ts │ │ │ │ ├── index.tsx │ │ │ │ └── styled.ts │ │ ├── selectors.ts │ │ ├── studentsTableReducer.ts │ │ └── styled.ts │ ├── NotFoundPage │ │ ├── styled.ts │ │ └── index.tsx │ ├── index.ts │ ├── EditProfile │ │ ├── components │ │ │ └── UserCourseListItem │ │ │ │ ├── styled.ts │ │ │ │ └── index.tsx │ │ └── styled.ts │ └── TokenPage │ │ └── index.tsx ├── assets │ ├── fonts │ │ ├── Poppins-Bold-700.eot │ │ ├── Poppins-Bold-700.otf │ │ ├── Poppins-Bold-700.ttf │ │ ├── Poppins-Bold-700.woff │ │ ├── Poppins-Bold-700.woff2 │ │ ├── Poppins-Medium-500.eot │ │ ├── Poppins-Medium-500.otf │ │ ├── Poppins-Medium-500.ttf │ │ ├── Poppins-Medium-500.woff │ │ ├── Poppins-Medium-500.woff2 │ │ ├── Poppins-Regular-400.eot │ │ ├── Poppins-Regular-400.otf │ │ ├── Poppins-Regular-400.ttf │ │ ├── Poppins-Regular-400.woff │ │ ├── Poppins-SemiBold-600.eot │ │ ├── Poppins-SemiBold-600.otf │ │ ├── Poppins-SemiBold-600.ttf │ │ ├── Poppins-Regular-400.woff2 │ │ ├── Poppins-SemiBold-600.woff │ │ └── Poppins-SemiBold-600.woff2 │ ├── images │ │ ├── myTeamExampleEN.png │ │ ├── myTeamExampleRU.png │ │ ├── editProfileExampleEN.png │ │ ├── editProfileExampleRU.png │ │ ├── teamActionsExampleEN.png │ │ └── teamActionsExampleRU.png │ └── svg │ │ ├── check.svg │ │ ├── chevron-arrow.svg │ │ ├── coursesSelectArrow.svg │ │ ├── paginateArrowLeft.svg │ │ ├── paginateArrowRight.svg │ │ ├── info.svg │ │ ├── cross-small.svg │ │ ├── plus.svg │ │ ├── cross.svg │ │ ├── menuToggle.svg │ │ ├── headerActiveElement.svg │ │ ├── edit.svg │ │ ├── filterIcon.svg │ │ ├── copy.svg │ │ ├── copy2clip.svg │ │ ├── team-header-decorations.svg │ │ └── team-header-background-pattern.svg ├── appConstants │ ├── api.ts │ └── colors.ts ├── graphql │ ├── mutations │ │ ├── sortStudentsMutation.ts │ │ ├── createCourseMutation.ts │ │ ├── updateCourseMutation.ts │ │ ├── createTeamMutation.ts │ │ ├── updateTeamMutation.ts │ │ ├── index.ts │ │ ├── updUserMutation.ts │ │ ├── addUserToTeamMutation.ts │ │ ├── removeUserFromCourseMutation.ts │ │ └── removeUserFromTeamMutation.ts │ └── queries │ │ ├── coursesQuery.ts │ │ ├── index.ts │ │ ├── teamsQuery.ts │ │ ├── usersQuery.ts │ │ └── whoAmIQuery.ts ├── setupTests.ts ├── hooks │ └── graphql │ │ ├── queries │ │ ├── useCoursesQuery.ts │ │ ├── useWhoAmIQuery.ts │ │ ├── useTeamsQuery.ts │ │ └── useUsersQuery.ts │ │ ├── mutations │ │ ├── useSortStudentsMutation.ts │ │ ├── useAddUserToTeamMutation.ts │ │ ├── useUpdUserMutation.ts │ │ ├── useCreateCourseMutation.ts │ │ ├── useUpdateCourseMutation.ts │ │ ├── useUpdateTeamMutation.ts │ │ ├── useCreateTeamMutation.ts │ │ ├── useRemoveUserFromTeamMutation.ts │ │ └── useExpelUserFromTeamMutation.ts │ │ └── index.ts ├── react-app-env.d.ts ├── reportWebVitals.ts ├── typography │ ├── common.css │ └── fonts.css ├── translation │ └── resources.ts ├── store │ └── index.tsx └── utils │ └── isFieldValid.ts ├── .prettierrc ├── firebase.json ├── pre-build.js ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ └── deploy-client.yml ├── .eslintrc.js └── package.json /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "rss-teams" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/components/Footer/components/index.ts: -------------------------------------------------------------------------------- 1 | export { FooterContent } from './FooterContent'; 2 | -------------------------------------------------------------------------------- /src/modules/AdminPage/components/index.ts: -------------------------------------------------------------------------------- 1 | export { AdminPageWrapper } from './ContentWrapper'; 2 | -------------------------------------------------------------------------------- /src/modules/LoginPage/components/index.ts: -------------------------------------------------------------------------------- 1 | export { LoginInfoBlock } from './LoginInfoBlock'; 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/public/favicon.png -------------------------------------------------------------------------------- /src/modules/TeamsList/components/index.ts: -------------------------------------------------------------------------------- 1 | export { Teams } from './Teams'; 2 | export { TeamListModals } from './TeamListModals'; 3 | -------------------------------------------------------------------------------- /src/components/Header/components/index.ts: -------------------------------------------------------------------------------- 1 | export { MenuWrapper } from './MenuWrapper'; 2 | export { BurgerMenu } from './BurgerMenu'; 3 | -------------------------------------------------------------------------------- /src/modules/TutorialPage/components/index.ts: -------------------------------------------------------------------------------- 1 | export { StepBlock } from './StepBlock'; 2 | export { NoteBlock } from './NoteBlock'; 3 | -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Bold-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Bold-700.eot -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Bold-700.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Bold-700.otf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Bold-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Bold-700.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Bold-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Bold-700.woff -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Bold-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Bold-700.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Medium-500.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Medium-500.eot -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Medium-500.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Medium-500.otf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Medium-500.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Medium-500.ttf -------------------------------------------------------------------------------- /src/assets/images/myTeamExampleEN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/images/myTeamExampleEN.png -------------------------------------------------------------------------------- /src/assets/images/myTeamExampleRU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/images/myTeamExampleRU.png -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Medium-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Medium-500.woff -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Medium-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Medium-500.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Regular-400.eot -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Regular-400.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Regular-400.otf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Regular-400.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Regular-400.woff -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-SemiBold-600.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-SemiBold-600.eot -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-SemiBold-600.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-SemiBold-600.otf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-SemiBold-600.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-SemiBold-600.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-Regular-400.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-SemiBold-600.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-SemiBold-600.woff -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-SemiBold-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/fonts/Poppins-SemiBold-600.woff2 -------------------------------------------------------------------------------- /src/assets/images/editProfileExampleEN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/images/editProfileExampleEN.png -------------------------------------------------------------------------------- /src/assets/images/editProfileExampleRU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/images/editProfileExampleRU.png -------------------------------------------------------------------------------- /src/assets/images/teamActionsExampleEN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/images/teamActionsExampleEN.png -------------------------------------------------------------------------------- /src/assets/images/teamActionsExampleRU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rolling-scopes-school/RSS-Teams-FE/HEAD/src/assets/images/teamActionsExampleRU.png -------------------------------------------------------------------------------- /src/modules/StudentsTable/components/Dashboard/components/index.ts: -------------------------------------------------------------------------------- 1 | export { TableBody } from './TableBody'; 2 | export { TableHead } from './TableHead'; 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "endOfLine": "auto", 4 | "semi": true, 5 | "singleQuote": true, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "printWidth": 100 9 | } -------------------------------------------------------------------------------- /src/appConstants/api.ts: -------------------------------------------------------------------------------- 1 | export const BACKEND_LINK = 'https://rss-teams-dev.herokuapp.com/graphql'; 2 | export const AUTH_BACKEND_LINK = 'https://rss-teams-dev.herokuapp.com/auth/github/'; 3 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/index.ts: -------------------------------------------------------------------------------- 1 | export { TeamsHeader } from './TeamsHeader'; 2 | export { MyTeam } from './MyTeam'; 3 | export { TeamItem } from './TeamItem'; 4 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/TeamUserTable/components/TableRow/components/index.ts: -------------------------------------------------------------------------------- 1 | export { TableCell } from './TableCell'; 2 | export { ExpelButton } from './ExpelButton'; 3 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/components/Dashboard/components/TableBody/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledTableBody = styled.div` 4 | display: flex; 5 | width: 100%; 6 | height: 100%; 7 | `; 8 | -------------------------------------------------------------------------------- /src/assets/svg/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/graphql/mutations/sortStudentsMutation.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const SORT_STUDENTS_MUTATION = gql` 4 | mutation sortStudents($courseId: String!) { 5 | sortStudents(courseId: $courseId) 6 | } 7 | `; 8 | -------------------------------------------------------------------------------- /src/assets/svg/chevron-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/coursesSelectArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/paginateArrowLeft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/paginateArrowRight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/graphql/queries/coursesQuery.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const COURSES_QUERY = gql` 4 | query getCourses { 5 | courses { 6 | id 7 | name 8 | teamSize 9 | isActive 10 | } 11 | } 12 | `; 13 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/selectors.ts: -------------------------------------------------------------------------------- 1 | import { State } from 'types'; 2 | 3 | export const selectUserData = (state: State) => state.studentsTableReducer.userData; 4 | 5 | export const selectFilterData = (state: State) => state.studentsTableReducer.filterData; 6 | -------------------------------------------------------------------------------- /src/modules/NotFoundPage/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const NotFoundPageWrapper = styled.div` 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | width: 100%; 8 | height: 100%; 9 | `; 10 | -------------------------------------------------------------------------------- /src/graphql/queries/index.ts: -------------------------------------------------------------------------------- 1 | export { TEAMS_QUERY } from './teamsQuery'; 2 | export { USERS_QUERY } from './usersQuery'; 3 | // export { USER_QUERY } from './userQuery'; 4 | export { WHOAMI_QUERY } from './whoAmIQuery'; 5 | export { COURSES_QUERY } from './coursesQuery'; 6 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/components/App/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const AppStyled = styled.div` 4 | position: relative; 5 | height: 100vh; 6 | min-height: 100vh; 7 | display: flex; 8 | flex-direction: column; 9 | align-items: center; 10 | `; 11 | -------------------------------------------------------------------------------- /src/assets/svg/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/cross-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/SelectField/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const FieldWrapper = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | margin-bottom: 20px; 7 | `; 8 | 9 | export const PlaceholderOption = styled.option` 10 | display: none; 11 | `; 12 | -------------------------------------------------------------------------------- /src/assets/svg/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/graphql/mutations/createCourseMutation.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const CREATE_COURSE_MUTATION = gql` 4 | mutation createCourse($course: CreateCourseInput!) { 5 | createCourse(course: $course) { 6 | id 7 | name 8 | teamSize 9 | isActive 10 | } 11 | } 12 | `; 13 | -------------------------------------------------------------------------------- /src/graphql/mutations/updateCourseMutation.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const UPDATE_COURSE_MUTATION = gql` 4 | mutation updateCourse($course: UpdateCourseInput!) { 5 | updateCourse(course: $course) { 6 | id 7 | name 8 | teamSize 9 | isActive 10 | } 11 | } 12 | `; 13 | -------------------------------------------------------------------------------- /src/assets/svg/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { PageTitle } from 'typography'; 3 | 4 | export const BoundryContainer = styled(PageTitle)` 5 | width: 100vw; 6 | height: 100vh; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | font-size: 30px; 11 | text-align: center; 12 | `; 13 | -------------------------------------------------------------------------------- /src/assets/svg/menuToggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/MyTeam/components/MyTeamInfoBlock/components/NotificationPopup/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { NotificationPopupStyled } from './styled'; 3 | 4 | const NotificationPopup: FC = ({ children }) => { 5 | return {children}; 6 | }; 7 | 8 | export default NotificationPopup; 9 | -------------------------------------------------------------------------------- /src/modules/index.ts: -------------------------------------------------------------------------------- 1 | export { LoginPage } from './LoginPage'; 2 | export { StudentsTable } from './StudentsTable'; 3 | export { TeamsList } from './TeamsList'; 4 | export { TokenPage } from './TokenPage'; 5 | export { NotFoundPage } from './NotFoundPage'; 6 | export { EditProfile } from './EditProfile'; 7 | export { TutorialPage } from './TutorialPage'; 8 | export { AdminPage } from './AdminPage'; 9 | -------------------------------------------------------------------------------- /src/modules/TeamsList/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledTeams = styled.div` 4 | max-width: 1320px; 5 | width: 92%; 6 | `; 7 | 8 | export const TeamsTitleWrapper = styled.div` 9 | position: relative; 10 | display: flex; 11 | align-items: center; 12 | justify-content: space-between; 13 | width: 100%; 14 | 15 | h1 { 16 | width: auto; 17 | } 18 | `; 19 | -------------------------------------------------------------------------------- /src/hooks/graphql/queries/useCoursesQuery.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@apollo/client'; 2 | 3 | import { COURSES_QUERY } from 'graphql/queries'; 4 | 5 | export const useCoursesQuery = () => { 6 | const { data, loading, error } = useQuery(COURSES_QUERY); 7 | const isLoaded = !loading && data?.courses; 8 | 9 | return { 10 | loading: !isLoaded, 11 | error, 12 | courses: data?.courses, 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/modules/TutorialPage/components/StepBlock/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { TextRegular } from 'typography'; 3 | 4 | export const StepBlockWrapper = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | width: 100%; 8 | height: 100%; 9 | 10 | & > * { 11 | margin-bottom: 20px; 12 | } 13 | `; 14 | 15 | export const StepBlockText = styled.p` 16 | ${TextRegular}; 17 | margin-top: 0; 18 | `; 19 | -------------------------------------------------------------------------------- /pre-build.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const fs = require('fs'); 3 | require('dotenv').config(); 4 | 5 | const BACKEND_LINK = `export const BACKEND_LINK = 'https://rss-teams.herokuapp.com/graphql'; 6 | `; 7 | 8 | const AUTH_BACKEND_LINK = `export const AUTH_BACKEND_LINK = 'https://rss-teams.herokuapp.com/auth/github/'; 9 | `; 10 | 11 | fs.writeFileSync('./src/appConstants/api.ts', BACKEND_LINK + AUTH_BACKEND_LINK); 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | /.eslintcache* 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | /.firebase 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .eslintcache* 27 | -------------------------------------------------------------------------------- /src/assets/svg/headerActiveElement.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/modules/AdminPage/components/ContentWrapper/components/CoursesList/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const CoursesListWrapper = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | width: 100%; 9 | gap: 20px; 10 | 11 | @media (max-width: 1100px) and (min-width: 768px) { 12 | flex-wrap: wrap; 13 | flex-direction: row; 14 | align-items: stretch; 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '*.woff2'; 3 | declare module '*.svg' { 4 | import React = require('react'); 5 | export const ReactComponent: React.SFC>; 6 | const src: string; 7 | export default src; 8 | } 9 | 10 | declare module '*.jpg' { 11 | const content: string; 12 | export default content; 13 | } 14 | 15 | declare module '*.png' { 16 | const content: string; 17 | export default content; 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/graphql/queries/useWhoAmIQuery.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@apollo/client'; 2 | import { WHOAMI_QUERY } from 'graphql/queries'; 3 | 4 | type Props = { 5 | skip: boolean; 6 | }; 7 | 8 | export const useWhoAmIQuery = ({ skip = false }: Props) => { 9 | const { 10 | data, 11 | loading: loadingW, 12 | error, 13 | } = useQuery(WHOAMI_QUERY, { 14 | skip, 15 | }); 16 | 17 | return { 18 | loadingW, 19 | errorW: error, 20 | whoAmI: data?.whoAmI, 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/TeamUserTable/components/TableRow/components/TableCell/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | type TTableCell = { 4 | value: any; 5 | isSocialLink?: boolean; 6 | }; 7 | 8 | const formatSocialLinks = (link: string | null): string => 9 | link ? '@' + link.replace('@', '') : ''; 10 | 11 | export const TableCell: FC = ({ value, isSocialLink = false }) => { 12 | return {isSocialLink ? formatSocialLinks(value) : value}; 13 | }; 14 | -------------------------------------------------------------------------------- /src/modules/NotFoundPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { ContentPageWrapper, PageTitle } from 'typography'; 4 | import { NotFoundPageWrapper } from './styled'; 5 | 6 | export const NotFoundPage: FC = () => { 7 | const { t } = useTranslation(); 8 | return ( 9 | 10 | 11 | {t('Not found!')} 12 | 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/components/Dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { TableBody, TableHead } from './components'; 3 | import { StyledTable } from './styled'; 4 | import { User } from 'types'; 5 | 6 | type DashboardProps = { 7 | users: User[]; 8 | page: number; 9 | }; 10 | 11 | export const Dashboard: FC = ({ users, page }) => { 12 | return ( 13 | 14 | 15 | 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/TeamUserTable/components/TableRow/components/ExpelButton/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { SmallCrossIcon, StyledExpelButton } from './styled'; 3 | 4 | type ExpelButtonProps = { 5 | onClickHandler?: () => void; 6 | }; 7 | 8 | export const ExpelButton: FC = ({ onClickHandler }) => { 9 | return ( 10 | 11 | 12 | Expel 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/hooks/graphql/mutations/useSortStudentsMutation.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@apollo/client'; 2 | import { SORT_STUDENTS_MUTATION } from 'graphql/mutations'; 3 | 4 | type Props = { 5 | courseId: string; 6 | }; 7 | 8 | export const useSortStudentsMutation = ({ courseId }: Props) => { 9 | const [sortStudents, { loading, error }] = useMutation(SORT_STUDENTS_MUTATION, { 10 | variables: { 11 | courseId, 12 | }, 13 | }); 14 | return { 15 | sortStudents, 16 | loadingM: loading, 17 | errorM: error, 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/Loader/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { LoaderStyled, LoaderWrapper } from './styled'; 3 | 4 | export const Loader: FC = () => { 5 | return ( 6 | 7 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/typography/common.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | --BG_COLOR: #f2f8fd; 9 | --OVERLAY_COLOR: rgba(54, 61, 72, 0.3); 10 | --SCROLL_THUMB_COLOR: #1e33570d; 11 | --WHITE_COLOR: #ffffff; 12 | } 13 | 14 | body { 15 | margin: 0; 16 | font: normal 16px/24px "Poppins", sans-serif; 17 | background-color: var(--BG_COLOR); 18 | 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | 23 | input { 24 | background-color: var(--BG_COLOR); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/TablePopup/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { StyledPopup, StyledPopupItem } from './styled'; 3 | 4 | type TablePopupProps = { 5 | dataLength: number; 6 | popupElements: string[]; 7 | }; 8 | 9 | export const TablePopup: FC = ({ popupElements, dataLength }) => { 10 | return ( 11 | 12 | {popupElements?.map((element: string) => ( 13 | {element} 14 | ))} 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/InputField/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { ALERT_COLOR } from 'appConstants/colors'; 3 | import { GeneralAdaptiveFont, Label } from 'typography'; 4 | 5 | export const FieldWrapper = styled.div` 6 | display: flex; 7 | flex-direction: column; 8 | margin-bottom: 0; 9 | `; 10 | 11 | export const ValidationAlert = styled.div` 12 | ${GeneralAdaptiveFont} 13 | color: ${ALERT_COLOR}; 14 | font-size: 14px; 15 | height: 22px; 16 | `; 17 | 18 | export const FLabel = styled(Label)` 19 | margin-bottom: 8px; 20 | `; 21 | -------------------------------------------------------------------------------- /src/graphql/mutations/createTeamMutation.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const CREATE_TEAM_MUTATION = gql` 4 | mutation createTeam($team: CreateTeamInput!) { 5 | createTeam(team: $team) { 6 | id 7 | password 8 | number 9 | courseId 10 | socialLink 11 | memberIds 12 | members { 13 | id 14 | firstName 15 | lastName 16 | github 17 | telegram 18 | discord 19 | score 20 | country 21 | city 22 | } 23 | } 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /src/graphql/mutations/updateTeamMutation.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const UPDATE_TEAM_MUTATION = gql` 4 | mutation updateTeam($team: UpdateTeamInput!) { 5 | updateTeam(team: $team) { 6 | id 7 | password 8 | number 9 | courseId 10 | socialLink 11 | memberIds 12 | members { 13 | id 14 | firstName 15 | lastName 16 | github 17 | telegram 18 | discord 19 | score 20 | country 21 | city 22 | } 23 | } 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/components/Dashboard/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { WHITE_COLOR } from 'appConstants/colors'; 3 | import { GeneralAdaptiveFont } from 'typography'; 4 | 5 | export const StyledTable = styled.div` 6 | display: flex; 7 | flex-direction: column; 8 | width: 100%; 9 | max-width: 1320px; 10 | height: 75vh; 11 | margin: 0 auto; 12 | padding: 10px; 13 | padding-right: 0; 14 | font-size: 1rem; 15 | background-color: ${WHITE_COLOR}; 16 | border-radius: 20px; 17 | ${GeneralAdaptiveFont}; 18 | `; 19 | -------------------------------------------------------------------------------- /src/components/TourGuide/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { MAIN1_COLOR, WHITE_COLOR } from 'appConstants/colors'; 3 | import { TextSemiBold, GeneralAdaptiveFont, GeneralButtonPadding } from 'typography'; 4 | 5 | export const LinkButton = styled.a` 6 | ${TextSemiBold}; 7 | margin-right: 0; 8 | border-radius: 20px; 9 | border: none; 10 | text-decoration: none; 11 | outline: none; 12 | cursor: pointer; 13 | background-color: ${MAIN1_COLOR}; 14 | color: ${WHITE_COLOR}; 15 | ${GeneralAdaptiveFont}; 16 | ${GeneralButtonPadding} 17 | `; 18 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/components/Dashboard/components/TableBody/components/TableRow/components/TableItem/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | type TStyledTableItem = { 4 | tableItemCursor: boolean; 5 | }; 6 | 7 | export const StyledTableItem = styled.div` 8 | position: relative; 9 | max-width: 140px; 10 | cursor: ${({ tableItemCursor }) => (tableItemCursor ? 'pointer' : 'unset')}; 11 | .TableItem__first-element { 12 | width: 100%; 13 | margin: 0; 14 | overflow: hidden; 15 | white-space: nowrap; 16 | text-overflow: ellipsis; 17 | } 18 | `; 19 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "RSS Teams", 3 | "name": "The Rolling Scopes School Teams", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "favicon.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "favicon.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/graphql/queries/teamsQuery.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const TEAMS_QUERY = gql` 4 | query getTeams($courseId: String!, $pagination: PaginationInput!) { 5 | teams(courseId: $courseId, pagination: $pagination) { 6 | count 7 | results { 8 | id 9 | number 10 | courseId 11 | socialLink 12 | members { 13 | id 14 | firstName 15 | lastName 16 | github 17 | telegram 18 | discord 19 | score 20 | country 21 | city 22 | } 23 | } 24 | } 25 | } 26 | `; 27 | -------------------------------------------------------------------------------- /src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { RSLogo } from 'typography'; 3 | import { StyledFooter, FooterWrapper, FooterContentBlockLogo } from './styled'; 4 | import { Link } from 'react-router-dom'; 5 | import { FooterContent } from './components'; 6 | 7 | export const Footer: FC = () => { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/graphql/mutations/index.ts: -------------------------------------------------------------------------------- 1 | export { UPD_USER_MUTATION } from './updUserMutation'; 2 | export { ADD_USER_TO_TEAM_MUTATION } from './addUserToTeamMutation'; 3 | export { REMOVE_USER_FROM_TEAM_MUTATION } from './removeUserFromTeamMutation'; 4 | export { CREATE_TEAM_MUTATION } from './createTeamMutation'; 5 | export { UPDATE_TEAM_MUTATION } from './updateTeamMutation'; 6 | export { SORT_STUDENTS_MUTATION } from './sortStudentsMutation'; 7 | export { REMOVE_USER_FROM_COURSE_MUTATION } from './removeUserFromCourseMutation'; 8 | export { CREATE_COURSE_MUTATION } from './createCourseMutation'; 9 | export { UPDATE_COURSE_MUTATION } from './updateCourseMutation'; 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx", 22 | "baseUrl": "./src" 23 | }, 24 | "include": [ 25 | "src" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/components/Dashboard/components/TableBody/components/TableRow/styled.ts: -------------------------------------------------------------------------------- 1 | import { BG_COLOR, DARK_TEXT_COLOR } from 'appConstants/colors'; 2 | import styled from 'styled-components'; 3 | 4 | export const StyledTableRow = styled.div` 5 | display: flex; 6 | justify-content: space-between; 7 | align-items: center; 8 | padding: 20px; 9 | font-weight: 400; 10 | line-height: 150%; 11 | color: ${DARK_TEXT_COLOR}; 12 | border-radius: 10px; 13 | &:nth-child(2n) { 14 | background-color: ${BG_COLOR}; 15 | } 16 | 17 | @media (max-width: 768px) { 18 | padding: 10px; 19 | } 20 | @media (max-width: 440px) { 21 | padding: 5px; 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /src/translation/resources.ts: -------------------------------------------------------------------------------- 1 | import { CURRENT_LANG, DEFAULT_LANGUAGE } from 'appConstants'; 2 | import i18n from 'i18next'; 3 | import { initReactI18next } from 'react-i18next'; 4 | 5 | import translationEN from './en/en.json'; 6 | import translationRU from './ru/ru.json'; 7 | 8 | const resources = { 9 | en: { 10 | translation: translationEN, 11 | }, 12 | ru: { 13 | translation: translationRU, 14 | }, 15 | }; 16 | 17 | const language = localStorage.getItem(CURRENT_LANG) ?? DEFAULT_LANGUAGE; 18 | 19 | i18n.use(initReactI18next).init({ 20 | resources, 21 | lng: language, 22 | keySeparator: false, 23 | interpolation: { 24 | escapeValue: false, 25 | }, 26 | }); 27 | 28 | export default i18n; 29 | -------------------------------------------------------------------------------- /src/modules/AdminPage/components/ContentWrapper/components/AddCourseBlock/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const AddCourseBlockWrapper = styled.div` 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | width: 100%; 8 | height: 250px; 9 | gap: 4%; 10 | 11 | @media (max-width: 850px) { 12 | flex-direction: column; 13 | } 14 | `; 15 | 16 | export const InputsBlockWrapper = styled.div` 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | justify-content: center; 21 | width: fit-content; 22 | height: fit-content; 23 | gap: 20px; 24 | 25 | @media (max-width: 850px) { 26 | margin-bottom: 30px; 27 | } 28 | `; 29 | -------------------------------------------------------------------------------- /src/graphql/queries/usersQuery.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const USERS_QUERY = gql` 4 | query getUsers($courseId: String!, $pagination: PaginationInput!, $filter: UserFilterInput) { 5 | users(courseId: $courseId, pagination: $pagination, filter: $filter) { 6 | count 7 | results { 8 | id 9 | firstName 10 | lastName 11 | github 12 | telegram 13 | discord 14 | score 15 | country 16 | city 17 | isAdmin 18 | courses { 19 | id 20 | name 21 | } 22 | teams { 23 | id 24 | number 25 | courseId 26 | } 27 | } 28 | } 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /src/modules/LoginPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { Redirect } from 'react-router-dom'; 4 | import { selectToken } from './selectors'; 5 | import { StyledLoginImage, StyledLoginPage, StyledLoginPageItemsWrapper } from './styled'; 6 | import { LoginInfoBlock } from './components'; 7 | 8 | export const LoginPage: FC = () => { 9 | const loginToken = useSelector(selectToken); 10 | 11 | if (loginToken) return ; 12 | 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/modules/AdminPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { TeamsTitleWrapper } from 'modules/TeamsList/styled'; 3 | import { useTranslation } from 'react-i18next'; 4 | import { ContentPageWrapper } from 'typography'; 5 | import { StudentTableWrapper, TableTitle } from 'modules/StudentsTable/styled'; 6 | import { AdminPageWrapper } from './components'; 7 | 8 | export const AdminPage: FC = () => { 9 | const { t } = useTranslation(); 10 | 11 | return ( 12 | 13 | 14 | 15 | {t('Admin page')} 16 | 17 | 18 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/components/Dashboard/components/TableHead/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { TABLE_HEADERS } from 'appConstants'; 3 | import { StyledTableHead, StyledTableHeadRow, StyledTableHeader } from './styled'; 4 | import { useTranslation } from 'react-i18next'; 5 | 6 | export const TableHead: FC = () => { 7 | const { t } = useTranslation(); 8 | return ( 9 | 10 | 11 | {TABLE_HEADERS.map((tableHeader: string, index: number) => ( 12 | 13 | {t(tableHeader)} 14 | 15 | ))} 16 | 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/modules/TutorialPage/components/StepBlock/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { StepBlockWrapper, StepBlockText } from './styled'; 4 | import { PageTitle } from 'typography'; 5 | 6 | type StepBlockProps = { 7 | title: string; 8 | subtitle: string; 9 | imageSrc: string; 10 | altText: string; 11 | }; 12 | 13 | export const StepBlock: FC = ({ title, subtitle, imageSrc, altText }) => { 14 | const { t } = useTranslation(); 15 | return ( 16 | 17 | {t(title)} 18 | {t(subtitle)} 19 | {altText} 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/appConstants/colors.ts: -------------------------------------------------------------------------------- 1 | export const WHITE_COLOR = '#FFFFFF'; 2 | export const BG_COLOR = '#F2F8FD'; 3 | export const MAIN1_COLOR = '#6550F6'; // violet 4 | export const MAIN1_DARK_COLOR = '#5039EF'; // dark violet 5 | export const MAIN2_COLOR = '#FA6678'; // red 6 | export const MAIN2_LIGHT_COLOR = '#FE7888'; // light red 7 | export const LIGHT_TEXT_COLOR = '#7E96C2'; 8 | export const DARK_TEXT_COLOR = '#363D48'; 9 | export const OVERLAY_COLOR = '#363D4866'; 10 | export const DASHBOARD_HEADER_BG_COLOR = '#E1EEFA'; 11 | export const TABLE_SCROLLBAR_BG_COLOR = '#F9F9FD'; 12 | export const TABLE_SCROLLBAR_THUMB_COLOR = '#1E33570D'; 13 | export const TABLE_POPUP_BORDER_COLOR = '#363D4833'; 14 | export const FOOTER_NAMES_COLOR = '#9CA5B5'; 15 | 16 | export const ALERT_COLOR = '#FA6678'; // red 17 | -------------------------------------------------------------------------------- /src/graphql/queries/whoAmIQuery.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const WHOAMI_QUERY = gql` 4 | query getWhoAMi { 5 | whoAmI { 6 | id 7 | firstName 8 | lastName 9 | github 10 | telegram 11 | discord 12 | score 13 | country 14 | city 15 | isAdmin 16 | courses { 17 | id 18 | name 19 | } 20 | teams { 21 | id 22 | password 23 | number 24 | courseId 25 | socialLink 26 | members { 27 | id 28 | firstName 29 | lastName 30 | github 31 | telegram 32 | discord 33 | score 34 | country 35 | city 36 | } 37 | } 38 | } 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /src/modules/EditProfile/components/UserCourseListItem/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { BG_COLOR, DARK_TEXT_COLOR } from 'appConstants/colors'; 3 | import { PlusButton } from 'components/CourseField/styled'; 4 | import { GeneralAdaptiveFont } from 'typography'; 5 | 6 | export const UserCourseListItemStyled = styled.div` 7 | display: flex; 8 | 9 | div { 10 | flex-grow: 1; 11 | padding: 8px 15px; 12 | margin-bottom: 20px; 13 | border-radius: 10px; 14 | background-color: ${BG_COLOR}; 15 | color: ${DARK_TEXT_COLOR}; 16 | ${GeneralAdaptiveFont} 17 | } 18 | `; 19 | 20 | export const MinusButton = styled(PlusButton)` 21 | background-image: none; 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | `; 26 | -------------------------------------------------------------------------------- /.github/workflows/deploy-client.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | admin: 9 | name: Deploy PROD 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Repo 13 | uses: actions/checkout@master 14 | - name: Install Dependencies 15 | run: npm install 16 | - name: Build 17 | run: npm run build 18 | - name: Archive Production Artifact 19 | uses: actions/upload-artifact@master 20 | with: 21 | name: build 22 | path: build 23 | - name: Deploy to Firebase 24 | uses: w9jds/firebase-action@master 25 | with: 26 | args: deploy --only hosting 27 | env: 28 | FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} 29 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/MyTeam/components/MyTeamInfoBlock/components/NotificationPopup/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { LIGHT_TEXT_COLOR, WHITE_COLOR } from 'appConstants/colors'; 3 | 4 | export const NotificationPopupStyled = styled.div` 5 | position: absolute; 6 | top: 153%; 7 | right: 0; 8 | width: 240px; 9 | background-color: ${WHITE_COLOR}; 10 | color: ${LIGHT_TEXT_COLOR}; 11 | border-radius: 10px; 12 | padding: 20px 20px; 13 | z-index: 1; 14 | 15 | &::after { 16 | content: ''; 17 | position: absolute; 18 | top: 0; 19 | right: 8px; 20 | transform: rotateZ(59deg) skew(33deg); 21 | height: 22px; 22 | width: 22px; 23 | border-radius: 4px; 24 | background-color: ${WHITE_COLOR}; 25 | } 26 | `; 27 | -------------------------------------------------------------------------------- /src/hooks/graphql/queries/useTeamsQuery.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@apollo/client'; 2 | import { TEAMS_PER_PAGE } from 'appConstants'; 3 | 4 | import { TEAMS_QUERY } from 'graphql/queries'; 5 | type Props = { 6 | reactCourseId: string; 7 | skip?: boolean; 8 | page?: number; 9 | }; 10 | 11 | export const useTeamsQuery = ({ reactCourseId, skip = false, page = 0 }: Props) => { 12 | const { data, loading, error } = useQuery(TEAMS_QUERY, { 13 | skip, 14 | variables: { 15 | courseId: reactCourseId, 16 | pagination: { 17 | skip: page * TEAMS_PER_PAGE, 18 | take: TEAMS_PER_PAGE, 19 | }, 20 | }, 21 | }); 22 | const isLoaded = !loading && !!data; 23 | return { 24 | loadingT: !isLoaded, 25 | errorT: error, 26 | teams: data?.teams, 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/components/Dashboard/components/TableBody/components/TableRow/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { ListChildComponentProps } from 'react-window'; 3 | import { TableItem } from './components/TableItem'; 4 | import { StyledTableRow } from './styled'; 5 | 6 | export const TableRow: FC = ({ data, index, style }) => { 7 | const optimalItemIndexCount = 2; 8 | return ( 9 | 10 | {data[index].map((item: string, ind: number) => ( 11 | optimalItemIndexCount ? index / (data.length - 1) : 0} 15 | key={`TableItemKey-${ind}`} 16 | /> 17 | ))} 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/graphql/mutations/updUserMutation.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const UPD_USER_MUTATION = gql` 4 | mutation updateUser($user: UpdateUserInput!) { 5 | updateUser(user: $user) { 6 | id 7 | firstName 8 | lastName 9 | github 10 | telegram 11 | discord 12 | score 13 | country 14 | city 15 | isAdmin 16 | courses { 17 | id 18 | name 19 | } 20 | teams { 21 | id 22 | password 23 | number 24 | courseId 25 | socialLink 26 | members { 27 | id 28 | firstName 29 | lastName 30 | github 31 | telegram 32 | discord 33 | score 34 | country 35 | city 36 | } 37 | } 38 | } 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /src/graphql/mutations/addUserToTeamMutation.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const ADD_USER_TO_TEAM_MUTATION = gql` 4 | mutation addUserToTeam($data: AddUserToTeamInput!) { 5 | addUserToTeam(data: $data) { 6 | id 7 | firstName 8 | lastName 9 | github 10 | telegram 11 | discord 12 | score 13 | country 14 | city 15 | isAdmin 16 | courses { 17 | id 18 | name 19 | } 20 | teams { 21 | id 22 | password 23 | number 24 | courseId 25 | socialLink 26 | members { 27 | id 28 | firstName 29 | lastName 30 | github 31 | telegram 32 | discord 33 | score 34 | country 35 | city 36 | } 37 | } 38 | } 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /src/modules/LoginPage/selectors.ts: -------------------------------------------------------------------------------- 1 | import { State } from 'types'; 2 | 3 | export const selectToken = (state: State) => state.loginPageReducer.loginToken; 4 | 5 | export const selectCurrCourse = (state: State) => state.loginPageReducer.currCourse; 6 | 7 | export const selectCurrLanguage = (state: State) => state.loginPageReducer.currLanguage; 8 | 9 | export const selectIsCommonError = (state: State) => state.loginPageReducer.isCommonError; 10 | 11 | export const selectIsBurgerMenuOpen = (state: State) => state.loginPageReducer.isBurgerMenuOpen; 12 | 13 | export const selectIsEditProfileDataChange = (state: State) => 14 | state.loginPageReducer.isEditProfileDataChange; 15 | 16 | export const selectPathToThePage = (state: State) => state.loginPageReducer.pathToThePage; 17 | 18 | export const selectIsTourOpen = (state: State) => state.loginPageReducer.isTourOpen; 19 | -------------------------------------------------------------------------------- /src/assets/svg/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/hooks/graphql/mutations/useAddUserToTeamMutation.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@apollo/client'; 2 | import { ADD_USER_TO_TEAM_MUTATION } from 'graphql/mutations'; 3 | import { WHOAMI_QUERY } from 'graphql/queries'; 4 | import { AddUserToTeamInput } from 'types'; 5 | 6 | type Props = { 7 | data: AddUserToTeamInput; 8 | }; 9 | 10 | export const useAddUserToTeamMutation = ({ data }: Props) => { 11 | const [addUserToTeam, { loading, error }] = useMutation(ADD_USER_TO_TEAM_MUTATION, { 12 | variables: { 13 | data, 14 | }, 15 | 16 | update(cache, { data: { addUserToTeam } }) { 17 | cache.writeQuery({ 18 | query: WHOAMI_QUERY, 19 | data: { 20 | addUserToTeam, 21 | }, 22 | }); 23 | }, 24 | }); 25 | return { 26 | addUserToTeam, 27 | loadingM: loading, 28 | errorM: error, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /src/modules/LoginPage/loginPageMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { CURRENT_LANG, CURRENT_COURSE, TOUR_OPENING } from 'appConstants'; 2 | import { Course } from 'types'; 3 | import { setCurrCourse, setCurrLang } from './loginPageReducer'; 4 | 5 | export const setLanguage = (currentLanguage: string) => { 6 | return (dispatch: (actionCreator: any) => void) => { 7 | localStorage.setItem(CURRENT_LANG, currentLanguage); 8 | dispatch(setCurrLang(currentLanguage)); 9 | }; 10 | }; 11 | 12 | export const setCourse = (currentCourse: Course) => { 13 | return (dispatch: (actionCreator: any) => void) => { 14 | localStorage.setItem(CURRENT_COURSE, JSON.stringify(currentCourse)); 15 | dispatch(setCurrCourse(currentCourse)); 16 | }; 17 | }; 18 | 19 | export const setTourOpening = (tourOpening: string) => { 20 | return () => { 21 | localStorage.setItem(TOUR_OPENING, tourOpening); 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/graphql/mutations/removeUserFromCourseMutation.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const REMOVE_USER_FROM_COURSE_MUTATION = gql` 4 | mutation removeUserFromCourse($data: RemoveUserFromCourseInput!) { 5 | removeUserFromCourse(data: $data) { 6 | id 7 | firstName 8 | lastName 9 | github 10 | telegram 11 | discord 12 | score 13 | country 14 | city 15 | isAdmin 16 | courses { 17 | id 18 | name 19 | } 20 | teams { 21 | id 22 | password 23 | number 24 | courseId 25 | socialLink 26 | members { 27 | id 28 | firstName 29 | lastName 30 | github 31 | telegram 32 | discord 33 | score 34 | country 35 | city 36 | } 37 | } 38 | } 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /src/hooks/graphql/mutations/useUpdUserMutation.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@apollo/client'; 2 | import { UPD_USER_MUTATION } from 'graphql/mutations'; 3 | import { WHOAMI_QUERY } from 'graphql/queries'; 4 | import { UpdateUserInput } from 'types'; 5 | 6 | type Props = { 7 | user: UpdateUserInput; 8 | }; 9 | 10 | export const useUpdUserMutation = ({ user }: Props) => { 11 | const formattedUser = { ...user, score: Number(user.score) }; 12 | const [updateUser, { loading, error }] = useMutation(UPD_USER_MUTATION, { 13 | variables: { 14 | user: formattedUser, 15 | }, 16 | update(cache, { data: { updateUser } }) { 17 | cache.writeQuery({ 18 | query: WHOAMI_QUERY, 19 | data: { 20 | updateUser, 21 | }, 22 | }); 23 | }, 24 | }); 25 | return { 26 | updateUser, 27 | loadingM: loading, 28 | errorM: error, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/Header/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { MAIN1_COLOR } from 'appConstants/colors'; 3 | import { GeneralAdaptiveFont } from 'typography'; 4 | 5 | type TStyledHeaderProps = { 6 | login: string | null; 7 | }; 8 | 9 | export const StyledHeader = styled.header` 10 | position: sticky; 11 | display: flex; 12 | justify-content: center; 13 | align-items: flex-end; 14 | height: 80px; 15 | padding: ${({ login }) => (login ? '1.4% 4.2% 0' : '2.8% 4.2% 0 2.8%')}; 16 | background-color: ${({ login }) => (login ? MAIN1_COLOR : 'transparent')}; 17 | width: 100%; 18 | z-index: 1; 19 | 20 | @media (max-width: 1260px) { 21 | align-items: center; 22 | } 23 | `; 24 | 25 | export const Container = styled.div` 26 | display: flex; 27 | justify-content: space-between; 28 | width: 100%; 29 | max-width: 1320px; 30 | ${GeneralAdaptiveFont}; 31 | `; 32 | -------------------------------------------------------------------------------- /src/assets/svg/filterIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/hooks/graphql/queries/useUsersQuery.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@apollo/client'; 2 | import { USERS_PER_PAGE } from 'appConstants'; 3 | 4 | import { USERS_QUERY } from 'graphql/queries'; 5 | import { UserFilterInput } from 'types'; 6 | 7 | type Props = { 8 | reactCourseId: string; 9 | skip?: boolean; 10 | page?: number; 11 | filter?: UserFilterInput; 12 | }; 13 | 14 | export const useUsersQuery = ({ reactCourseId, skip = false, page = 0, filter }: Props) => { 15 | const { data, loading, error } = useQuery(USERS_QUERY, { 16 | skip, 17 | variables: { 18 | filter, 19 | courseId: reactCourseId, 20 | pagination: { 21 | skip: page * USERS_PER_PAGE, 22 | take: USERS_PER_PAGE, 23 | }, 24 | }, 25 | }); 26 | const isLoaded = !loading && !!data; 27 | 28 | return { 29 | loadingU: !isLoaded, 30 | errorU: error, 31 | users: data?.users, 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/modules/LoginPage/components/LoginInfoBlock/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { 3 | StyledLoginInfoBlock, 4 | StyledLoginTitle, 5 | StyledLoginRegistrationLink, 6 | StyledLoginTextWrapper, 7 | } from './styled'; 8 | import { AUTH_BACKEND_LINK } from 'appConstants'; 9 | import { useTranslation } from 'react-i18next'; 10 | 11 | export const LoginInfoBlock: FC = () => { 12 | const { t } = useTranslation(); 13 | return ( 14 | 15 | {t('Sign in')} 16 | 17 | {t('Sign in with Github')} 18 | 19 | 20 |

{t('Don’t have github account?')}

21 | {t('Sign up')} 22 |
23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/modules/TutorialPage/components/NoteBlock/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { NoteWrapper, NoteList } from './styled'; 3 | import { useTranslation } from 'react-i18next'; 4 | 5 | type NoteBlockProps = { 6 | title?: string; 7 | subtitle?: string; 8 | listItems?: string[]; 9 | isNote?: string; 10 | }; 11 | 12 | export const NoteBlock: FC = ({ 13 | title = 'Note', 14 | subtitle, 15 | listItems, 16 | children, 17 | isNote, 18 | }) => { 19 | const { t } = useTranslation(); 20 | return ( 21 | 22 |

{t(title)}

23 | {subtitle &&

{t(subtitle)}

} 24 | {listItems && ( 25 | 26 | {listItems.map((item: string) => ( 27 |
  • {t(item)}
  • 28 | ))} 29 |
    30 | )} 31 | {children} 32 |
    33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { App } from './App'; 2 | export { Loader } from './Loader'; 3 | export { PrivateRoute } from './PrivateRoute'; 4 | export { Header } from './Header'; 5 | export { InputField } from './InputField'; 6 | export { CourseField } from './CourseField'; 7 | export { Pagination } from './Pagination'; 8 | export { Modal } from './Modal'; 9 | export { ModalExpel } from './ModalExpel'; 10 | export { ModalJoin } from './ModalJoin'; 11 | export { ModalCreateEditTeam } from './ModalCreateEditTeam'; 12 | export { ModalCreated } from './ModalCreated'; 13 | export { TablePopup } from './TablePopup'; 14 | export { FilterForm } from './FilterForm'; 15 | export { FilterSelect } from './FilterSelect'; 16 | export { Footer } from './Footer'; 17 | export { ErrorModal } from './ErrorModal'; 18 | export { CommonSelectList } from './CommonSelectList'; 19 | export { TourGuide } from './TourGuide'; 20 | export { ModalEditCourse } from './ModalEditCourse'; 21 | -------------------------------------------------------------------------------- /src/components/PrivateRoute/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { Redirect, Route } from 'react-router-dom'; 3 | 4 | type Props = { 5 | isLoggedIn: boolean; 6 | path: string; 7 | component: FC; 8 | exact?: boolean; 9 | newUserCheck?: boolean; 10 | }; 11 | 12 | export const PrivateRoute: FC = ({ 13 | component: Component, 14 | isLoggedIn, 15 | newUserCheck, 16 | path, 17 | exact, 18 | }) => { 19 | return ( 20 | { 24 | if (isLoggedIn && newUserCheck) { 25 | return ; 26 | } 27 | if (isLoggedIn && !newUserCheck) { 28 | return ; 29 | } 30 | if (!isLoggedIn) { 31 | return ; 32 | } 33 | }} 34 | /> 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/modules/AdminPage/components/ContentWrapper/components/AddCourseBlock/components/InputsBlock/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const InputWrapper = styled.div<{ isModal?: boolean }>` 4 | display: flex; 5 | ${({ isModal }) => isModal && 'flex-direction: column;'} 6 | justify-content: space-between; 7 | align-items: center; 8 | width: 100%; 9 | height: fit-content; 10 | ${({ isModal }) => isModal && 'margin-bottom: 40px;'} 11 | gap: ${({ isModal }) => (isModal ? '5px' : '20px')}; 12 | 13 | @media (max-width: 580px) { 14 | ${({ isModal }) => isModal && 'margin-bottom: 30px;'} 15 | label { 16 | ${({ isModal }) => !isModal && 'display: none;'} 17 | } 18 | } 19 | `; 20 | 21 | export const FieldWrapper = styled.div` 22 | position: relative; 23 | display: flex; 24 | flex-direction: column; 25 | 26 | & > div:nth-child(2) { 27 | position: absolute; 28 | top: 100%; 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /src/store/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { studentsTableReducer } from 'modules/StudentsTable/studentsTableReducer'; 4 | import { teamsListReducer } from 'modules/TeamsList/teamsListReducer'; 5 | import { loginPageReducer } from 'modules/LoginPage/loginPageReducer'; 6 | import { createStore, combineReducers, applyMiddleware } from 'redux'; 7 | import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly'; 8 | import thunkMiddleware from 'redux-thunk'; 9 | import { State } from 'types'; 10 | 11 | const appReducer = combineReducers({ 12 | studentsTableReducer, 13 | teamsListReducer, 14 | loginPageReducer, 15 | }); 16 | 17 | const store = createStore(appReducer, composeWithDevTools(applyMiddleware(thunkMiddleware))); 18 | 19 | export const AppState: FC = ({ children }) => { 20 | return ( 21 | 22 | <>{children} 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/graphql/mutations/removeUserFromTeamMutation.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | export const REMOVE_USER_FROM_TEAM_MUTATION = gql` 4 | mutation removeUserFromTeam($data: RemoveUserFromTeamInput!) { 5 | removeUserFromTeam(data: $data) { 6 | id 7 | firstName 8 | lastName 9 | github 10 | telegram 11 | discord 12 | score 13 | country 14 | city 15 | avatar 16 | isAdmin 17 | courses { 18 | id 19 | name 20 | } 21 | email 22 | courseIds 23 | teamIds 24 | teams { 25 | id 26 | password 27 | number 28 | courseId 29 | socialLink 30 | memberIds 31 | members { 32 | id 33 | firstName 34 | lastName 35 | github 36 | telegram 37 | discord 38 | score 39 | country 40 | city 41 | } 42 | } 43 | } 44 | } 45 | `; 46 | -------------------------------------------------------------------------------- /src/modules/TokenPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useEffect } from 'react'; 2 | import { AUTH_TOKEN } from 'appConstants'; 3 | import { useDispatch } from 'react-redux'; 4 | import { useParams, useHistory } from 'react-router-dom'; 5 | import { useTranslation } from 'react-i18next'; 6 | import { setToken } from 'modules/LoginPage/loginPageReducer'; 7 | 8 | type ParamsType = { 9 | id: string; 10 | }; 11 | 12 | export const TokenPage: FC = () => { 13 | const dispatch = useDispatch(); 14 | const { id } = useParams(); 15 | const history = useHistory(); 16 | const { t } = useTranslation(); 17 | 18 | useEffect(() => { 19 | const loginToken = sessionStorage.getItem(AUTH_TOKEN); 20 | if (!loginToken) { 21 | sessionStorage.setItem(AUTH_TOKEN, id); 22 | dispatch(setToken(id)); 23 | } 24 | history.push('/'); 25 | }, [id, history, dispatch]); 26 | 27 | return ( 28 |
    29 |

    {t('Redirecting...')}

    30 |
    31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /src/components/Header/components/MenuWrapper/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { ReactComponent as MenuToggle } from 'assets/svg/menuToggle.svg'; 3 | 4 | export const StyledMenuWrapper = styled.div` 5 | display: flex; 6 | height: 60px; 7 | 8 | @media (max-width: 1260px) { 9 | height: 40px; 10 | } 11 | @media (max-width: 550px) { 12 | margin-left: -30px; 13 | } 14 | @media (max-width: 440px) { 15 | margin-left: -50px; 16 | } 17 | `; 18 | 19 | export const MenuButton = styled(MenuToggle)` 20 | width: 0; 21 | height: 0; 22 | cursor: pointer; 23 | 24 | @media (max-width: 1430px) { 25 | width: 24px; 26 | height: 24px; 27 | margin: 8px 0 0 40px; 28 | } 29 | @media (max-width: 700px) { 30 | width: 20px; 31 | height: 20px; 32 | margin: 10px 0 0 30px; 33 | } 34 | @media (max-width: 600px) { 35 | margin-left: 0; 36 | } 37 | @media (max-width: 440px) { 38 | width: 16px; 39 | height: 16px; 40 | margin-top: 12px; 41 | } 42 | `; 43 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/components/Dashboard/components/TableHead/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { LIGHT_TEXT_COLOR, DASHBOARD_HEADER_BG_COLOR } from 'appConstants/colors'; 3 | 4 | export const StyledTableHead = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: center; 8 | align-items: center; 9 | margin-right: 10px; 10 | padding: 10px 20px; 11 | color: ${LIGHT_TEXT_COLOR}; 12 | background-color: ${DASHBOARD_HEADER_BG_COLOR}; 13 | border-radius: 10px; 14 | @media (max-width: 768px) { 15 | padding: 10px; 16 | } 17 | @media (max-width: 440px) { 18 | padding: 5px; 19 | } 20 | `; 21 | 22 | export const StyledTableHeadRow = styled.div` 23 | display: flex; 24 | justify-content: space-between; 25 | align-items: center; 26 | width: 100%; 27 | `; 28 | 29 | export const StyledTableHeader = styled.div` 30 | max-width: 140px; 31 | height: auto; 32 | margin: 0; 33 | font-weight: 600; 34 | line-height: 150%; 35 | text-align: start; 36 | `; 37 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/TeamUserTable/components/TableRow/components/ExpelButton/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { ReactComponent as Cross } from 'assets/svg/cross-small.svg'; 3 | import { MAIN1_COLOR } from 'appConstants/colors'; 4 | import { SVGArrowAdaptive } from 'typography'; 5 | 6 | type StyledExpelButtonProps = { 7 | color?: string; 8 | }; 9 | 10 | export const StyledExpelButton = styled.div` 11 | width: 63px; 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | cursor: pointer; 16 | color: ${({ color }) => (color ? color : MAIN1_COLOR)}; 17 | 18 | @media (max-width: 550px) { 19 | width: 55px; 20 | } 21 | `; 22 | 23 | export const SmallCrossIcon = styled(Cross)` 24 | width: 12px; 25 | height: 12px; 26 | ${SVGArrowAdaptive}; 27 | 28 | @media (max-width: 992px) and (min-width: 768px) { 29 | width: 11px; 30 | height: 11px; 31 | } 32 | 33 | path { 34 | stroke: currentColor; 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /src/modules/AdminPage/components/ContentWrapper/components/CoursesList/components/Course/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { BG_COLOR } from 'appConstants/colors'; 3 | import { GeneralAdaptiveFont } from 'typography'; 4 | 5 | export const CourseWrapper = styled.div` 6 | display: flex; 7 | justify-content: space-between; 8 | align-items: center; 9 | width: 90%; 10 | background-color: ${BG_COLOR}; 11 | padding: 20px; 12 | border-radius: 20px; 13 | 14 | & > div:nth-child(1) { 15 | font-weight: 600; 16 | } 17 | 18 | & > div { 19 | ${GeneralAdaptiveFont}; 20 | } 21 | 22 | @media (max-width: 1100px) { 23 | flex-direction: column; 24 | width: 40%; 25 | gap: 10px; 26 | 27 | & > div:nth-child(1) { 28 | text-align: left; 29 | } 30 | 31 | button, 32 | div { 33 | width: 90%; 34 | text-align: center; 35 | } 36 | } 37 | 38 | @media (max-width: 768px) { 39 | width: 80%; 40 | } 41 | 42 | @media (max-width: 450px) { 43 | gap: 5px; 44 | } 45 | `; 46 | -------------------------------------------------------------------------------- /src/modules/AdminPage/components/ContentWrapper/styled.ts: -------------------------------------------------------------------------------- 1 | import { DARK_TEXT_COLOR, WHITE_COLOR } from 'appConstants/colors'; 2 | import styled from 'styled-components'; 3 | import { H2AdaptiveFont } from 'typography'; 4 | 5 | export const AdminPageContentWrapper = styled.div` 6 | position: relative; 7 | z-index: 0; 8 | display: flex; 9 | flex-direction: column; 10 | width: 100%; 11 | max-width: 1320px; 12 | min-height: 75vh; 13 | padding: 20px 0; 14 | background-color: ${WHITE_COLOR}; 15 | border-radius: 20px; 16 | `; 17 | 18 | export const ListTitle = styled.h4` 19 | ${H2AdaptiveFont} 20 | font-size: 25px; 21 | color: ${DARK_TEXT_COLOR}; 22 | text-align: center; 23 | `; 24 | 25 | export const CourseListSettings = styled.div` 26 | position: relative; 27 | display: flex; 28 | justify-content: flex-end; 29 | padding: 10px 5% 30px; 30 | 31 | @media (max-width: 1100px) { 32 | justify-content: flex-start; 33 | padding: 10px 9% 80px; 34 | } 35 | @media (max-width: 768px) { 36 | padding: 10px 10% 80px; 37 | } 38 | `; 39 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/MemberListToggle/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { MembersListToggleStyled, Chevron } from './styled'; 3 | import { ReactComponent as ChevronArrow } from 'assets/svg/chevron-arrow.svg'; 4 | import { useTranslation } from 'react-i18next'; 5 | 6 | type MembersListToggle = { 7 | countMembers: number; 8 | isOpen: boolean; 9 | onToggleList: () => void; 10 | color?: string; 11 | }; 12 | 13 | export const MembersListToggle: FC = ({ 14 | countMembers, 15 | isOpen, 16 | onToggleList, 17 | color, 18 | }) => { 19 | const { t } = useTranslation(); 20 | return ( 21 | { 23 | !!countMembers && onToggleList(); 24 | }} 25 | color={color} 26 | > 27 |
    28 | {countMembers || 0} {countMembers === 1 ? t('member') : t('members')} 29 |
    30 | 31 | 32 | 33 |
    34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/TablePopup/styled.ts: -------------------------------------------------------------------------------- 1 | import { WHITE_COLOR, DARK_TEXT_COLOR, TABLE_POPUP_BORDER_COLOR } from 'appConstants/colors'; 2 | import styled from 'styled-components'; 3 | import { GeneralAdaptiveFont } from 'typography'; 4 | 5 | type TStyledPopup = { 6 | dataLength: number; 7 | }; 8 | 9 | export const StyledPopup = styled.div` 10 | position: absolute; 11 | top: ${({ dataLength }) => dataLength < 0.5 && '95%'}; 12 | bottom: ${({ dataLength }) => dataLength > 0.5 && '95%'}; 13 | right: 0; 14 | z-index: 2; 15 | display: flex; 16 | flex-direction: column; 17 | justify-content: center; 18 | padding: 10px; 19 | font-size: 1rem; 20 | color: ${DARK_TEXT_COLOR}; 21 | background-color: ${WHITE_COLOR}; 22 | border: 1px solid ${TABLE_POPUP_BORDER_COLOR}; 23 | border-radius: 10px; 24 | ${GeneralAdaptiveFont}; 25 | @media (max-width: 440px) { 26 | left: -5px; 27 | padding: 5px; 28 | } 29 | `; 30 | 31 | export const StyledPopupItem = styled.p` 32 | margin: 0; 33 | line-height: 17px; 34 | text-align: justify; 35 | `; 36 | -------------------------------------------------------------------------------- /src/components/SelectField/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, SelectHTMLAttributes } from 'react'; 2 | import { Label, Select, SelectInner } from 'typography'; 3 | import { FieldWrapper } from './styled'; 4 | // 5 | // for future 6 | // TODO: make separate SelectField component for other screens 7 | // now is not used 8 | // 9 | interface SelectFieldProps extends SelectHTMLAttributes { 10 | labelText: string; 11 | placeholder: string; 12 | multi?: boolean; 13 | register: any; 14 | } 15 | 16 | const SelectField: FC = ({ labelText, placeholder, register, ...rest }) => { 17 | return ( 18 | 19 | 20 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/hooks/graphql/index.ts: -------------------------------------------------------------------------------- 1 | export { useTeamsQuery } from './queries/useTeamsQuery'; 2 | export { useUsersQuery } from './queries/useUsersQuery'; 3 | export { useWhoAmIQuery } from './queries/useWhoAmIQuery'; 4 | export { useCoursesQuery } from './queries/useCoursesQuery'; 5 | export { useUpdUserMutation } from './mutations/useUpdUserMutation'; 6 | export { useRemoveUserFromTeamMutation } from './mutations/useRemoveUserFromTeamMutation'; 7 | export { useExpelUserFromTeamMutation } from './mutations/useExpelUserFromTeamMutation'; 8 | export { useCreateTeamMutation } from './mutations/useCreateTeamMutation'; 9 | export { useAddUserToTeamMutation } from './mutations/useAddUserToTeamMutation'; 10 | export { useUpdateTeamMutation } from './mutations/useUpdateTeamMutation'; 11 | export { useSortStudentsMutation } from './mutations/useSortStudentsMutation'; 12 | export { useRemoveUserFromCourseMutation } from './mutations/useRemoveUserFromCourseMutation'; 13 | export { useCreateCourseMutation } from './mutations/useCreateCourseMutation'; 14 | export { useUpdateCourseMutation } from './mutations/useUpdateCourseMutation'; 15 | -------------------------------------------------------------------------------- /src/modules/LoginPage/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { MAIN1_COLOR, WHITE_COLOR } from 'appConstants/colors'; 3 | import { ReactComponent as LoginImage } from 'assets/svg/loginImage.svg'; 4 | 5 | export const StyledLoginPage = styled.div` 6 | position: fixed; 7 | top: 0; 8 | right: 0; 9 | bottom: 0; 10 | left: 0; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | width: 100%; 15 | height: 100vh; 16 | margin: 0 auto; 17 | background: linear-gradient(90deg, ${WHITE_COLOR} 75%, ${MAIN1_COLOR} 75%); 18 | `; 19 | 20 | export const StyledLoginPageItemsWrapper = styled.div` 21 | position: relative; 22 | display: flex; 23 | align-items: center; 24 | width: 100%; 25 | max-width: 1440px; 26 | height: 100%; 27 | overflow: hidden; 28 | `; 29 | 30 | export const StyledLoginImage = styled(LoginImage)` 31 | position: absolute; 32 | top: 0; 33 | right: 0; 34 | z-index: -1; 35 | width: auto; 36 | height: 100%; 37 | 38 | @media (max-width: 991px) { 39 | left: 50%; 40 | transform: translate(-50%, 0%); 41 | } 42 | `; 43 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/MemberListToggle/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { WHITE_COLOR } from 'appConstants/colors'; 3 | import { SVGArrowAdaptive } from 'typography'; 4 | 5 | type ChevronProps = { 6 | open: boolean; 7 | }; 8 | 9 | export const MembersListToggleStyled = styled.div` 10 | display: flex; 11 | justify-content: space-between; 12 | align-items: center; 13 | width: 134px; 14 | cursor: pointer; 15 | color: ${({ color }) => color || WHITE_COLOR}; 16 | 17 | path { 18 | stroke: currentColor; 19 | } 20 | 21 | @media (max-width: 992px) { 22 | width: 115px; 23 | } 24 | @media (max-width: 768px) { 25 | width: 105px; 26 | } 27 | @media (max-width: 440px) { 28 | width: 85px; 29 | } 30 | `; 31 | 32 | export const Chevron = styled.div` 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | transform: rotateX(${({ open }) => (open ? '180deg' : '0deg')}); 37 | margin-left: 10px; 38 | transition: all 0.3s; 39 | 40 | svg { 41 | ${SVGArrowAdaptive}; 42 | } 43 | `; 44 | -------------------------------------------------------------------------------- /src/components/Pagination/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import ReactPaginate from 'react-paginate'; 3 | import './style.css'; 4 | import leftArrow from 'assets/svg/paginateArrowLeft.svg'; 5 | import rightArrow from 'assets/svg/paginateArrowRight.svg'; 6 | 7 | type PaginationProps = { 8 | pageCount: number; 9 | changePage: (page: number) => void; 10 | page: number; 11 | }; 12 | 13 | export const Pagination: FC = ({ pageCount, changePage, page }) => { 14 | return ( 15 | } 17 | nextLabel={Next} 18 | breakLabel={'...'} 19 | pageCount={pageCount} 20 | initialPage={page} 21 | marginPagesDisplayed={1} 22 | pageRangeDisplayed={2} 23 | containerClassName={'pagination'} 24 | pageClassName={'pageContainer'} 25 | pageLinkClassName={'pageLink'} 26 | activeClassName={'activePageContainer'} 27 | activeLinkClassName={'activePageLink'} 28 | onPageChange={(page) => changePage(page.selected)} 29 | /> 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/TeamItem/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { TextRegular, TextSemiBold, GeneralAdaptiveFont } from 'typography'; 3 | import { WHITE_COLOR } from 'appConstants/colors'; 4 | import { TableWrapper } from '../MyTeam/styled'; 5 | 6 | export const TeamItemStyled = styled.div` 7 | background: ${WHITE_COLOR}; 8 | border-radius: 20px; 9 | margin-bottom: 20px; 10 | 11 | .teamItem__header { 12 | padding: 20px 30px; 13 | display: flex; 14 | align-items: center; 15 | justify-content: space-between; 16 | ${GeneralAdaptiveFont}; 17 | } 18 | 19 | .teamItem__name { 20 | ${TextSemiBold}; 21 | ${GeneralAdaptiveFont}; 22 | } 23 | .teamItem__description { 24 | ${TextRegular}; 25 | flex-grow: 1; 26 | } 27 | `; 28 | 29 | export const TeamItemTableWrapper = styled(TableWrapper)` 30 | margin-top: ${({ open }) => (open ? '-20px' : '0')}; 31 | ${({ open }) => (open ? 'padding: 20px 30px 20px' : null)}; 32 | 33 | @media (max-width: 440px) { 34 | ${({ open }) => (open ? 'padding: 20px 15px 20px' : null)}; 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /src/hooks/graphql/mutations/useCreateCourseMutation.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@apollo/client'; 2 | import { Course, CreateCourseInput } from 'types'; 3 | import { COURSES_QUERY } from 'graphql/queries'; 4 | import { CREATE_COURSE_MUTATION } from 'graphql/mutations'; 5 | 6 | type Props = { 7 | course: CreateCourseInput; 8 | }; 9 | 10 | export const useCreateCourseMutation = ({ course }: Props) => { 11 | const [createCourse, { loading, error }] = useMutation(CREATE_COURSE_MUTATION, { 12 | variables: { 13 | course, 14 | }, 15 | 16 | update(cache, { data: { createCourse } }) { 17 | const data: { courses: Course[] } | null = cache.readQuery({ 18 | query: COURSES_QUERY, 19 | }); 20 | 21 | const updatedResults = data?.courses?.length 22 | ? [createCourse, ...data?.courses] 23 | : [createCourse]; 24 | 25 | cache.writeQuery({ 26 | query: COURSES_QUERY, 27 | data: { 28 | courses: updatedResults, 29 | }, 30 | }); 31 | }, 32 | }); 33 | return { 34 | createCourse, 35 | loadingM: loading, 36 | errorM: error, 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { TeamsHeader, MyTeam, TeamItem } from './components'; 4 | import { Team, TeamList } from 'types'; 5 | import { TableTitle } from 'modules/StudentsTable/styled'; 6 | import { TeamsTitleWrapper } from 'modules/TeamsList/styled'; 7 | import { useTranslation } from 'react-i18next'; 8 | 9 | type TeamsProps = { 10 | teams: TeamList; 11 | myTeam?: Team; 12 | userId: string; 13 | }; 14 | 15 | export const Teams: FC = ({ teams, myTeam, userId }) => { 16 | const { t } = useTranslation(); 17 | return ( 18 | <> 19 | 20 | {t('Teams')} 21 | 22 | {myTeam ? : } 23 | {!!teams.count && 24 | teams.results.map((team) => ( 25 | 31 | ))} 32 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/modules/AdminPage/components/ContentWrapper/components/ShowCourseSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState } from 'react'; 2 | import { SHOW_COURSES_OPTIONS } from 'appConstants'; 3 | import { CommonSelectList } from 'components'; 4 | 5 | interface ShowCourseSelectProps { 6 | currentOption: string; 7 | setCurrentOption: (newOption: string) => void; 8 | } 9 | 10 | export const ShowCourseSelect: FC = ({ 11 | currentOption, 12 | setCurrentOption, 13 | }) => { 14 | const [isSelectOpen, setIsSelectOpen] = useState(false); 15 | 16 | const onOptionChange = (item: { id: string; name: string }) => { 17 | setCurrentOption(item.name); 18 | }; 19 | 20 | const options: { id: string; name: string }[] = SHOW_COURSES_OPTIONS.filter( 21 | (option) => option.name !== currentOption 22 | ); 23 | 24 | return ( 25 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/components/ErrorModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { Modal } from 'components'; 3 | import { ApolloError } from '@apollo/client'; 4 | import { UNAUTHORIZED_ERROR_MESSAGE } from 'appConstants'; 5 | 6 | type Props = { 7 | title?: string; 8 | text?: string; 9 | text2?: string; 10 | open?: boolean; 11 | cancelText?: string; 12 | isCrossIconVisible?: boolean; 13 | error?: ApolloError; 14 | }; 15 | 16 | export const ErrorModal: FC = ({ 17 | title = 'Something went wrong!', 18 | text = 'Please, try again later.', 19 | text2 = '@besovadevka or @MadaShindeInai', 20 | open = true, 21 | isCrossIconVisible = false, 22 | cancelText = 'Ok', 23 | error, 24 | }) => { 25 | const isUserUnauthorized = !!error?.graphQLErrors.find( 26 | ({ message }) => message === UNAUTHORIZED_ERROR_MESSAGE 27 | ); 28 | 29 | if (isUserUnauthorized) { 30 | return null; 31 | } 32 | 33 | const onClose = () => { 34 | location.reload(); 35 | }; 36 | 37 | return ( 38 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /src/components/InputField/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, InputHTMLAttributes } from 'react'; 2 | import { Input } from 'typography'; 3 | import { FieldWrapper, ValidationAlert, FLabel } from './styled'; 4 | import { useTranslation } from 'react-i18next'; 5 | export interface InputFieldProps extends InputHTMLAttributes { 6 | labelText?: string; 7 | placeholder?: string; 8 | register?: any; 9 | name: string; 10 | message?: string | undefined; 11 | onChange?: (e: React.ChangeEvent) => void; 12 | } 13 | 14 | export const InputField: FC = ({ 15 | labelText, 16 | placeholder, 17 | register, 18 | name, 19 | message, 20 | onChange, 21 | }) => { 22 | const { t } = useTranslation(); 23 | return ( 24 | 25 | {labelText ? t(labelText) : ''} 26 | 34 | {message ? t(message) : ''} 35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/hooks/graphql/mutations/useUpdateCourseMutation.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@apollo/client'; 2 | import { UpdateCourseInput, Course } from 'types'; 3 | import { COURSES_QUERY } from 'graphql/queries'; 4 | import { UPDATE_COURSE_MUTATION } from 'graphql/mutations'; 5 | 6 | type Props = { 7 | course: UpdateCourseInput; 8 | }; 9 | 10 | export const useUpdateCourseMutation = ({ course }: Props) => { 11 | const { id } = course; 12 | const [updateCourse, { loading, error }] = useMutation(UPDATE_COURSE_MUTATION, { 13 | variables: { 14 | course, 15 | }, 16 | 17 | update(cache, { data: { updateCourse } }) { 18 | const data: { courses: Course[] } | null = cache.readQuery({ 19 | query: COURSES_QUERY, 20 | }); 21 | 22 | const updatedResults = data?.courses?.map((course: Course) => { 23 | if (course.id === id) { 24 | return updateCourse; 25 | } 26 | return course; 27 | }); 28 | 29 | cache.writeQuery({ 30 | query: COURSES_QUERY, 31 | data: { 32 | courses: updatedResults, 33 | }, 34 | }); 35 | }, 36 | }); 37 | return { 38 | updateCourse, 39 | loadingM: loading, 40 | errorM: error, 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/MyTeam/components/MyTeamInfoBlock/components/MyTeamInfoLine/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { WHITE_COLOR } from 'appConstants/colors'; 3 | import { TextBold, GeneralAdaptiveFont, SVGParamsAdaptive } from 'typography'; 4 | import { ReactComponent as CopyIcon } from 'assets/svg/copy.svg'; 5 | 6 | type TInfoLineStyled = { 7 | blink: boolean; 8 | }; 9 | 10 | export const InfoLineStyled = styled.div` 11 | ${TextBold}; 12 | color: ${WHITE_COLOR}; 13 | display: flex; 14 | align-items: center; 15 | border-radius: 5px; 16 | 17 | .info__text { 18 | overflow: hidden; 19 | white-space: nowrap; 20 | text-overflow: ellipsis; 21 | animation: ${({ blink }) => (blink ? 'blink 1s' : 'none')}; 22 | border-radius: 3px; 23 | padding: 0 7px; 24 | margin-left: -7px; 25 | ${GeneralAdaptiveFont}; 26 | } 27 | 28 | @keyframes blink { 29 | from { 30 | background: rgb(101, 80, 246, 0.5); 31 | } 32 | to { 33 | background: rgba(101, 80, 246, 0); 34 | } 35 | } 36 | `; 37 | 38 | export const CopyClipboardButton = styled(CopyIcon)` 39 | cursor: pointer; 40 | margin-left: 3px; 41 | ${SVGParamsAdaptive}; 42 | `; 43 | -------------------------------------------------------------------------------- /src/components/Modal/index.module.css: -------------------------------------------------------------------------------- 1 | /* setting absolute can break other modals */ 2 | .overlay { 3 | position: fixed; 4 | top: 0; 5 | right: 0; 6 | bottom: 0; 7 | left: 0; 8 | z-index: 999; 9 | text-align: center; 10 | background: var(--OVERLAY_COLOR); 11 | 12 | overscroll-behavior: contain; 13 | } 14 | 15 | /* setting max width to container can break other modals */ 16 | .container { 17 | position: relative; 18 | display: flex; 19 | flex-direction: column; 20 | width: 440px; 21 | margin: 11vh auto; 22 | padding: 30px 40px; 23 | text-align: center; 24 | background: var(--WHITE_COLOR); 25 | border: none; 26 | border-radius: 20px; 27 | } 28 | 29 | .icon { 30 | position: absolute; 31 | top: 20px; 32 | right: 20px; 33 | z-index: 1; 34 | width: 25px; 35 | height: 25px; 36 | cursor: pointer; 37 | } 38 | 39 | @media (max-width: 550px) { 40 | .container { 41 | width: 350px; 42 | } 43 | 44 | .icon { 45 | top: 15px; 46 | right: 20px; 47 | width: 22px; 48 | height: 22px; 49 | } 50 | } 51 | 52 | @media (max-width: 440px) { 53 | .container { 54 | width: 300px; 55 | } 56 | 57 | .icon { 58 | top: 15px; 59 | right: 20px; 60 | width: 20px; 61 | height: 20px; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BoundryContainer } from './styled'; 3 | 4 | interface ErrorBoundaryState { 5 | error: any; 6 | errorInfo: any; 7 | } 8 | 9 | class ErrorBoundary extends Component { 10 | state = { error: null, errorInfo: null }; 11 | 12 | static getDerivedStateFromError(error: any, errorInfo: any) { 13 | return { error: error, errorInfo: errorInfo }; 14 | } 15 | 16 | componentDidCatch(error: any, errorInfo: any) { 17 | // Catch errors in any components below and re-render with error message 18 | this.setState({ 19 | error: error, 20 | errorInfo: errorInfo, 21 | }); 22 | // You can also log error messages to an error reporting service here 23 | } 24 | 25 | handleReload() { 26 | location.reload(); 27 | } 28 | 29 | render() { 30 | const { children } = this.props; 31 | 32 | if (this.state.errorInfo) { 33 | return ( 34 | 35 | Something went wrong. 36 |
    Click on the page to reload it. 37 |
    38 | ); 39 | } 40 | 41 | return <>{children}; 42 | } 43 | } 44 | 45 | export default ErrorBoundary; 46 | -------------------------------------------------------------------------------- /src/components/Header/components/MenuWrapper/components/CoursesSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { selectCurrCourse } from 'modules/LoginPage/selectors'; 4 | import { selectUserData } from 'modules/StudentsTable/selectors'; 5 | import { Course } from 'types'; 6 | import { CommonSelectList } from 'components'; 7 | import { setCourse } from 'modules/LoginPage/loginPageMiddleware'; 8 | 9 | export const CoursesSelect: FC = () => { 10 | const [isCourseSelectOpen, setCourseSelectOpen] = useState(false); 11 | const dispatch = useDispatch(); 12 | const currCourse = useSelector(selectCurrCourse); 13 | const userData = useSelector(selectUserData); 14 | 15 | const userCourses = userData.courses.filter((item) => item.id !== currCourse.id) ?? null; 16 | 17 | const onCourseChange = (course: Course) => { 18 | setCourseSelectOpen(false); 19 | dispatch(setCourse(course)); 20 | }; 21 | 22 | return ( 23 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /src/hooks/graphql/mutations/useUpdateTeamMutation.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from '@apollo/client'; 2 | import { UpdateTeamInput, User, Team } from 'types'; 3 | import { WHOAMI_QUERY } from 'graphql/queries'; 4 | import { UPDATE_TEAM_MUTATION } from 'graphql/mutations'; 5 | 6 | type Props = { 7 | team: UpdateTeamInput; 8 | }; 9 | 10 | export const useUpdateTeamMutation = ({ team }: Props) => { 11 | const { id } = team; 12 | const [updateTeam, { loading, error }] = useMutation(UPDATE_TEAM_MUTATION, { 13 | variables: { 14 | team, 15 | }, 16 | 17 | update(cache, { data: { updateTeam } }) { 18 | const userData: { whoAmI: User } | null = cache.readQuery({ 19 | query: WHOAMI_QUERY, 20 | }); 21 | 22 | const updatedUserTeam = (userData?.whoAmI.teams as Team[]).map((team: Team) => { 23 | if (team.id === id) { 24 | return updateTeam; 25 | } 26 | return team; 27 | }); 28 | 29 | cache.writeQuery({ 30 | query: WHOAMI_QUERY, 31 | data: { 32 | whoAmI: { 33 | ...userData?.whoAmI, 34 | teams: updatedUserTeam, 35 | }, 36 | }, 37 | }); 38 | }, 39 | }); 40 | return { 41 | updateTeam, 42 | loadingM: loading, 43 | errorM: error, 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/studentsTableReducer.ts: -------------------------------------------------------------------------------- 1 | import { SET_USER_DATA, SET_FILTER_DATA } from 'appConstants'; 2 | import { StateStudentsTable } from 'types'; 3 | import { defaultFilterData } from 'components/FilterForm/filterFormFields'; 4 | import { createActions, handleActions } from 'redux-actions'; 5 | 6 | export const studentsTableState = { 7 | userData: { 8 | id: '', 9 | firstName: '', 10 | lastName: '', 11 | github: '', 12 | telegram: null, 13 | discord: '', 14 | score: 1000, 15 | country: '', 16 | city: '', 17 | avatar: '', 18 | isAdmin: false, 19 | courses: [], 20 | email: '', 21 | courseIds: [''], 22 | teamIds: [''], 23 | teams: [], 24 | }, 25 | filterData: defaultFilterData, 26 | }; 27 | 28 | export const { setUserData, setFilterData } = createActions({ 29 | SET_USER_DATA: (userData) => ({ userData }), 30 | SET_FILTER_DATA: (filterData) => ({ filterData }), 31 | }); 32 | 33 | export const studentsTableReducer = handleActions( 34 | { 35 | [SET_USER_DATA]: (state, { payload: { userData } }) => ({ 36 | ...state, 37 | userData, 38 | }), 39 | [SET_FILTER_DATA]: (state, { payload: { filterData } }) => ({ 40 | ...state, 41 | filterData, 42 | }), 43 | }, 44 | studentsTableState 45 | ); 46 | -------------------------------------------------------------------------------- /src/modules/StudentsTable/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { DARK_TEXT_COLOR } from 'appConstants/colors'; 3 | import { ScrollBar } from 'typography'; 4 | 5 | export const StudentTableWrapper = styled.div` 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | width: 100%; 10 | height: fit-content; 11 | max-width: 1440px; 12 | padding: 0 4% 60px; 13 | overflow-y: scroll; 14 | ${ScrollBar}; 15 | @media screen and (max-width: 768px) { 16 | padding-bottom: 50px; 17 | } 18 | `; 19 | 20 | export const TableTitle = styled.h1` 21 | align-self: flex-start; 22 | width: 80%; 23 | margin: 40px 0; 24 | font: 700 30px/45px 'Poppins', sans-serif; 25 | color: ${DARK_TEXT_COLOR}; 26 | 27 | @media (max-width: 1200px) { 28 | font-size: 26px; 29 | line-height: 35px; 30 | } 31 | @media (max-width: 992px) { 32 | margin: 35px 0; 33 | font-size: 24px; 34 | line-height: 30px; 35 | } 36 | @media (max-width: 768px) { 37 | margin: 30px 0; 38 | font-size: 22px; 39 | line-height: 25px; 40 | } 41 | @media (max-width: 550px) { 42 | margin: 30px 0; 43 | font-size: 20px; 44 | line-height: 25px; 45 | } 46 | @media (max-width: 440px) { 47 | margin: 20px 0; 48 | font-size: 17px; 49 | line-height: 20px; 50 | } 51 | `; 52 | -------------------------------------------------------------------------------- /src/components/FilterSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, SelectHTMLAttributes } from 'react'; 2 | import { Label, Select, SelectInner } from 'typography'; 3 | import { FieldWrapper, SelectCourse } from 'components/CourseField/styled'; 4 | import { useTranslation } from 'react-i18next'; 5 | interface SelectFieldProps extends SelectHTMLAttributes { 6 | labelText?: string; 7 | placeholder: string; 8 | register: any; 9 | options: string[]; 10 | currentOption: string; 11 | } 12 | 13 | export const FilterSelect: FC = ({ 14 | labelText, 15 | placeholder, 16 | register, 17 | options, 18 | currentOption, 19 | ...rest 20 | }) => { 21 | const { t } = useTranslation(); 22 | const filterFieldOptions = 23 | options.map((option: string) => { 24 | return ( 25 | 28 | ); 29 | }) ?? null; 30 | return ( 31 | 32 | {labelText && } 33 | 34 | 39 | 40 | 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/MyTeam/components/MyTeamInfoBlock/styled.ts: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import { WHITE_COLOR, MAIN2_LIGHT_COLOR, MAIN2_COLOR } from 'appConstants/colors'; 3 | import { TextRegular, GeneralAdaptiveFont, SVGParamsAdaptive } from 'typography'; 4 | 5 | export const StyledMyTeamInfoBlock = styled.div` 6 | position: relative; 7 | max-width: 300px; 8 | width: 100%; 9 | border-radius: 10px; 10 | padding: 20px; 11 | color: ${WHITE_COLOR}; 12 | background-color: ${MAIN2_LIGHT_COLOR}; 13 | 14 | .infoBlock__title { 15 | ${TextRegular}; 16 | color: ${WHITE_COLOR}; 17 | margin-bottom: 10px; 18 | ${GeneralAdaptiveFont}; 19 | } 20 | 21 | @media (max-width: 992px) { 22 | max-width: 50%; 23 | } 24 | @media (max-width: 580px) { 25 | max-width: 100%; 26 | } 27 | `; 28 | 29 | const InfoBlockButton = css` 30 | position: absolute; 31 | top: 10px; 32 | right: 10px; 33 | display: flex; 34 | align-items: center; 35 | justify-content: center; 36 | width: 32px; 37 | height: 32px; 38 | padding: 8px; 39 | background: ${MAIN2_COLOR}; 40 | fill: ${WHITE_COLOR}; 41 | cursor: pointer; 42 | border-radius: 5px; 43 | `; 44 | 45 | export const InfoButton = styled.div` 46 | ${InfoBlockButton}; 47 | 48 | svg { 49 | ${SVGParamsAdaptive}; 50 | } 51 | `; 52 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'plugin:react/recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'prettier/@typescript-eslint', 7 | 'plugin:react-hooks/recommended', 8 | 'plugin:prettier/recommended', 9 | ], 10 | plugins: ['@typescript-eslint', 'react', 'prettier', 'react-hooks'], 11 | parserOptions: { 12 | ecmaVersion: 2018, 13 | sourceType: 'module', 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | }, 18 | 19 | rules: { 20 | 'react-hooks/rules-of-hooks': 'error', 21 | 'react-hooks/exhaustive-deps': 'warn', 22 | 'comma-dangle': ['error', 'only-multiline'], 23 | 'react/prop-types': 'off', 24 | 'react/display-name': 'off', 25 | '@typescript-eslint/explicit-function-return-type': 'off', 26 | 'prettier/prettier': ['error', { endOfLine: 'auto' }], 27 | '@typescript-eslint/interface-name-prefix': 'off', 28 | '@typescript-eslint/ban-ts-ignore': 'off', 29 | '@typescript-eslint/explicit-module-boundary-types': 'off', 30 | '@typescript-eslint/no-empty-function': 'off', 31 | '@typescript-eslint/no-explicit-any': 'off', 32 | '@typescript-eslint/no-var-reqiures': 'off', 33 | }, 34 | 35 | settings: { 36 | react: { 37 | version: 'detect', 38 | }, 39 | }, 40 | globals: { React: 'writable' }, 41 | }; 42 | -------------------------------------------------------------------------------- /src/components/Footer/components/FooterContent/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { FooterContentBlock, FooterContentWrapper, FooterTitle } from 'components/Footer/styled'; 3 | import { FOOTER_INFO, LINK_TO_DESIGN_BLOCK } from 'appConstants'; 4 | import { useTranslation } from 'react-i18next'; 5 | 6 | export const FooterContent: FC = () => { 7 | const { t } = useTranslation(); 8 | return ( 9 | 10 | {FOOTER_INFO.map((item, index) => { 11 | return ( 12 | 13 | {t(item.title)} 14 |
    15 | {item.members.map((item: string) => { 16 | return ( 17 | 24 | {item} 25 | 26 | ); 27 | })} 28 |
    29 |
    30 | ); 31 | })} 32 |
    33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/modules/TeamsList/components/Teams/components/TeamItem/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState } from 'react'; 2 | import { TeamItemStyled, TeamItemTableWrapper } from './styled'; 3 | import { User } from 'types'; 4 | import { MembersListToggle } from '../MemberListToggle'; 5 | import { LIGHT_TEXT_COLOR } from 'appConstants/colors'; 6 | import { TeamUserTable } from '../TeamUserTable'; 7 | 8 | type TeamItemProps = { 9 | name: string; 10 | description?: string; 11 | countMember: number; 12 | members: User[]; 13 | }; 14 | 15 | export const TeamItem: FC = ({ name, countMember, description, members }) => { 16 | const [isOpen, setToggle] = useState(false); 17 | const toggleListHandler = () => { 18 | setToggle(!isOpen); 19 | }; 20 | return ( 21 | 22 |
    23 |
    {name}
    24 | {description &&
    {description}
    } 25 | 31 |
    32 | 33 | {isOpen && } 34 | 35 |
    36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/ModalExpel/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useEffect, useCallback } from 'react'; 2 | import { Modal } from 'components'; 3 | 4 | type Props = { 5 | title: string; 6 | text: string; 7 | open: boolean; 8 | onSubmit?: () => void; 9 | onClose: () => void; 10 | okText?: string; 11 | isCrossIconVisible?: boolean; 12 | cancelText?: string; 13 | }; 14 | 15 | export const ModalExpel: FC = ({ 16 | title, 17 | text, 18 | open, 19 | okText, 20 | cancelText, 21 | isCrossIconVisible = true, 22 | onClose, 23 | onSubmit, 24 | }) => { 25 | const onSubmitModal = useCallback(() => { 26 | onClose(); 27 | if (onSubmit) { 28 | onSubmit(); 29 | } 30 | }, [onClose, onSubmit]); 31 | 32 | useEffect(() => { 33 | const listener = (e: KeyboardEvent) => { 34 | if (e.key === 'Enter') { 35 | onSubmitModal(); 36 | } 37 | }; 38 | if (open) { 39 | document.addEventListener('keydown', listener); 40 | } 41 | return () => document.removeEventListener('keydown', listener); 42 | }, [onSubmitModal, open]); 43 | 44 | return ( 45 | 59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /src/components/Header/components/MenuWrapper/components/LangSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { LANGUAGES } from 'appConstants'; 4 | import { selectCurrLanguage } from 'modules/LoginPage/selectors'; 5 | import i18n from 'translation/resources'; 6 | import { CommonSelectList } from 'components'; 7 | import { setLanguage } from 'modules/LoginPage/loginPageMiddleware'; 8 | 9 | type LangSelectProps = { 10 | menuToggle?: boolean; 11 | customStyle?: boolean; 12 | }; 13 | 14 | export const LangSelect: FC = ({ menuToggle, customStyle }) => { 15 | const [displayLangList, setDisplayLangList] = useState(false); 16 | const dispatch = useDispatch(); 17 | const currentLanguage = useSelector(selectCurrLanguage); 18 | 19 | const onLangChange = (item: string) => { 20 | setDisplayLangList(false); 21 | dispatch(setLanguage(item)); 22 | i18n.changeLanguage(item); 23 | }; 24 | 25 | const languages: string[] = LANGUAGES.filter((lang) => lang !== currentLanguage); 26 | 27 | return ( 28 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/modules/AdminPage/components/ContentWrapper/components/AddCourseBlock/components/InputsBlock/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { ModalInput, Label } from 'typography'; 4 | import { ValidationAlert } from 'components/InputField/styled'; 5 | import { InputWrapper, FieldWrapper } from './styled'; 6 | 7 | interface InputBlockProps { 8 | inputLabel: string; 9 | value: string; 10 | placeholder: string; 11 | onChangeHandler: (e: React.ChangeEvent) => void; 12 | isFieldValid: boolean; 13 | errorMessage: string; 14 | isModal?: boolean; 15 | } 16 | 17 | export const InputBlock: FC = ({ 18 | inputLabel, 19 | value, 20 | placeholder, 21 | onChangeHandler, 22 | isFieldValid, 23 | errorMessage, 24 | isModal, 25 | }) => { 26 | const { t } = useTranslation(); 27 | 28 | return ( 29 | 30 | 31 | 32 | 41 | {!isFieldValid && {t(errorMessage)}} 42 | 43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/components/Header/components/MenuWrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { selectUserData } from 'modules/StudentsTable/selectors'; 4 | import { CoursesSelect } from './components/CoursesSelect'; 5 | import { Nav } from './components/Nav'; 6 | import { MenuButton, StyledMenuWrapper } from './styled'; 7 | import { LangSelect } from './components/LangSelect'; 8 | import { setBurgerMenuOpen } from 'modules/LoginPage/loginPageReducer'; 9 | import { selectIsBurgerMenuOpen } from 'modules/LoginPage/selectors'; 10 | 11 | type MenuWrapperProps = { 12 | navOnClickHandler: (e: React.MouseEvent, path: string) => void; 13 | }; 14 | 15 | export const MenuWrapper: FC = ({ navOnClickHandler }) => { 16 | const dispatch = useDispatch(); 17 | const userData = useSelector(selectUserData); 18 | const isBurgerMenuOpen = useSelector(selectIsBurgerMenuOpen); 19 | 20 | const newUserCheck = !!userData?.courses.length; 21 | const isUserAdmin = !!userData?.isAdmin; 22 | 23 | const onClickMenuToggle = () => { 24 | dispatch(setBurgerMenuOpen(!isBurgerMenuOpen)); 25 | }; 26 | 27 | return ( 28 | 29 |