├── .husky ├── pre-push └── pre-commit ├── .npmrc ├── .gitattributes ├── .editorconfig ├── src ├── exceptions │ ├── index.ts │ └── response-error.ts ├── types │ ├── snackbar │ │ ├── snackbar.index.ts │ │ └── types │ │ │ └── snackbar.types.ts │ ├── mui │ │ ├── customTypes.index.ts │ │ └── customTypes │ │ │ └── customTypes.d.ts │ ├── notes │ │ ├── notes.index.ts │ │ └── interfaces │ │ │ └── notes.interfaces.ts │ ├── categories │ │ ├── categories.index.ts │ │ └── interfaces │ │ │ └── categories.interfaces.ts │ ├── chat │ │ ├── file │ │ │ ├── file.index.ts │ │ │ └── interfaces │ │ │ │ └── file.interface.ts │ │ ├── link │ │ │ ├── link.index.ts │ │ │ └── interfaces │ │ │ │ └── link.interface.ts │ │ ├── media │ │ │ ├── media.index.ts │ │ │ └── interface │ │ │ │ └── media.interface.ts │ │ ├── message │ │ │ └── message.index.ts │ │ ├── chat.index.ts │ │ ├── types │ │ │ └── chat.types.ts │ │ └── enum │ │ │ └── chat.enums.ts │ ├── lesson │ │ ├── lesson.index.ts │ │ └── interfaces │ │ │ └── lesson.interfaces.ts │ ├── services │ │ └── services.index.ts │ ├── utils │ │ ├── utils.index.ts │ │ └── utils-interfaces │ │ │ └── utils.interface.ts │ ├── finished-quizzes │ │ ├── finishedQuizzes.index.ts │ │ └── types │ │ │ └── finishedQuizzes.types.ts │ ├── quizzes │ │ └── quizzes.index.ts │ ├── subject │ │ ├── subject.index.ts │ │ └── interfaces │ │ │ └── subject.interface.ts │ ├── my-offers │ │ ├── myOffers.index.ts │ │ └── interfaces │ │ │ └── myOffers.interfaces.ts │ ├── questions │ │ └── questions.index.ts │ ├── components │ │ ├── tab │ │ │ └── tab.types.ts │ │ ├── appSelect │ │ │ └── appSelect.types.ts │ │ ├── enum-filter │ │ │ └── enumFilter.interface.ts │ │ ├── enhanced-table │ │ │ └── enhancedTable.types.ts │ │ ├── app-breadcrumbs │ │ │ ├── appBreadcrumbs.types.ts │ │ │ └── appBreadcrumbs.interfaces.ts │ │ ├── icon-with-text-list │ │ │ └── profileDoneItemsList.interfaces.ts │ │ ├── radioButtonInputs │ │ │ └── RadioButtonInputs.types.ts │ │ ├── accordion-with-image │ │ │ └── accordionWithImage.interface.ts │ │ ├── subject-level-chips │ │ │ └── subjectLevelChips.interface.ts │ │ ├── appContentSwitcher │ │ │ └── appContentSwitcher.types.ts │ │ ├── multi-accordion-with-title │ │ │ └── multiAccordionWithTitle.interface.ts │ │ ├── user-profile-info │ │ │ └── userProfileInfo.interface.ts │ │ ├── price-filter │ │ │ └── price-filter.types.ts │ │ └── accordions │ │ │ └── accordions.interface.ts │ ├── bookmarked-offers │ │ ├── bookmarkedOffers.index.ts │ │ └── interfaces │ │ │ └── bookmarkedOffers.interfaces.ts │ ├── offer-details │ │ ├── offerDetails.index.ts │ │ └── interfaces │ │ │ └── offerDetails.interfaces.ts │ ├── my-attachments │ │ ├── myAttachments.index.ts │ │ └── interfaces │ │ │ └── myAttachments.interfaces.ts │ ├── user-profile │ │ ├── types │ │ │ └── userProfile.types.ts │ │ └── userProfile.index.ts │ ├── findOffers │ │ ├── enums │ │ │ └── findOffers.enums.ts │ │ ├── findOffers.index.ts │ │ └── types │ │ │ └── findOffers.types.ts │ ├── offer │ │ ├── enums │ │ │ └── offer.enums.ts │ │ └── offer.index.ts │ ├── video-box │ │ ├── videoBox.index.ts │ │ └── types │ │ │ └── videoBox.types.ts │ ├── course │ │ ├── enums │ │ │ └── course.enum.ts │ │ ├── course.index.ts │ │ └── types │ │ │ └── course.types.ts │ ├── attachment │ │ ├── attachment.index.ts │ │ └── types │ │ │ └── attachment.types.ts │ ├── edit-profile │ │ ├── editProfile.index.ts │ │ └── types │ │ │ └── editProfile.types.ts │ ├── notification │ │ ├── notification.index.ts │ │ ├── interfaces │ │ │ └── notification.interfaces.ts │ │ └── enums │ │ │ └── notification.enums.ts │ ├── edit-user-profile │ │ ├── interfaces │ │ │ └── securityBlockForm.interfaces.ts │ │ ├── editUserProfile.index.ts │ │ └── enums │ │ │ └── editUserProfile.enums.ts │ ├── user │ │ ├── user.index.ts │ │ ├── user-enums │ │ │ └── user.enums.ts │ │ └── user-types │ │ │ └── user.types.ts │ ├── common │ │ ├── common.index.ts │ │ └── events │ │ │ ├── events.index.ts │ │ │ └── enums │ │ │ └── events.enums.ts │ ├── cooperation │ │ ├── cooperation.index.ts │ │ └── enums │ │ │ └── cooperation.enums.ts │ ├── my-resources │ │ ├── myResources.index.ts │ │ └── types │ │ │ └── myResources.types.ts │ └── my-cooperations │ │ ├── myCooperations.index.ts │ │ └── enums │ │ ├── myCooperations.enums.ts │ │ └── materialsAccess.enums.ts ├── components │ ├── sidebar-content-box │ │ └── SidebarContentBox.constants.ts │ ├── find-block │ │ ├── find-student-constants.ts │ │ ├── find-tutor-constants.ts │ │ └── find-block.styles.ts │ ├── search-input │ │ └── SearchInput.styles.js │ ├── download-button │ │ ├── DownloadButton.constants.ts │ │ └── DownloadButton.styles.ts │ ├── dropdown-add-btn │ │ └── DropdownButton.styles.ts │ ├── app-menu │ │ └── AppMenu.styles.ts │ ├── drag-handle │ │ └── DragHandle.styles.ts │ ├── popular-categories │ │ ├── PopularCategories.constants.ts │ │ └── PopularCategories.styles.ts │ ├── app-progress-bar-line │ │ └── AppProgressBarLine.constans.tsx │ ├── page-wrapper │ │ └── PageWrapper.styles.ts │ ├── app-select-button │ │ ├── AppSelectButton.styles.tsx │ │ └── AppSelectButton.tsx │ ├── tutor-schedule │ │ └── types.ts │ ├── file-component │ │ └── FileComponent.constants.ts │ ├── avatar-icon │ │ └── AvatarIcon.styles.ts │ ├── app-text-field │ │ └── AppTextField.styles.ts │ ├── enhanced-table │ │ ├── enhanced-table-head │ │ │ └── EnhancedTableHead.styles.ts │ │ ├── enhanced-table-header-cell │ │ │ └── EnhancedTableHeaderCell.styles.ts │ │ ├── enhanced-table-toolbar │ │ │ └── EnhancedTableToolbar.styles.ts │ │ ├── date-filter │ │ │ └── DateFilter.styles.ts │ │ ├── enhanced-table-row │ │ │ └── EnhancedTableRow.styles.ts │ │ └── EnhancedTable.styles.ts │ ├── radio-button-inputs │ │ └── RadioButtonInputs.styles.ts │ ├── video-player │ │ ├── VideoPlayer.styles.ts │ │ └── VideoPlayer.tsx │ ├── accordion-with-image │ │ └── AccordionWithImage.styles.ts │ ├── sidebar-image-grid │ │ └── SidebarImageGrid.styles.ts │ ├── user-table │ │ └── UserTable.styles.js │ ├── loader │ │ └── Loader.styles.ts │ ├── app-rating-large │ │ └── AppRatingLarge.styles.ts │ ├── title-with-description │ │ └── TitleWithDescription.styles.ts │ ├── search-by-message │ │ └── SearchByMessage.styles.ts │ ├── app-text-area │ │ └── AppTextArea.styles.ts │ ├── icons-with-counter │ │ └── IconsWithCounter.style.ts │ ├── direction-link │ │ └── DirectionLink.styles.ts │ ├── input-with-icon │ │ └── InputWithIcon.styles.ts │ ├── app-toolbar │ │ ├── AppToolbar.styles.ts │ │ └── AppToolbar.tsx │ ├── app-drawer │ │ └── AppDrawer.styles.ts │ ├── icon-title-description │ │ └── IconTitleDescription.styles.ts │ ├── languages-list-with-icon │ │ └── LanguagesListWithIcon.styles.ts │ ├── app-carousel │ │ └── AppCarousel.styles.ts │ ├── app-pagination │ │ └── AppPagination.styles.ts │ ├── status-chip │ │ ├── StatusChip.styles.ts │ │ └── StatusChip.tsx │ ├── typing-block │ │ └── TypingBlock.styles.ts │ ├── img-title-description │ │ └── ImgTitleDescription.styles.ts │ ├── offer-card │ │ ├── offer-actions │ │ │ └── OfferActions.styles.ts │ │ ├── OfferCard.styles.ts │ │ └── offer-details │ │ │ └── OfferDetails.styles.js │ ├── tab-navigation │ │ └── TabNavigation.styles.ts │ ├── all-content-modal │ │ └── AllContentModal.styles.ts │ ├── checkbox-with-tooltip │ │ └── CheckboxWithTooltip.styles.ts │ ├── search-filter-input │ │ └── SearchFilterInput.styles.tsx │ ├── view-switcher │ │ └── ViewSwitcher.styles.ts │ ├── scroll-to-top │ │ └── ScrollToTop.tsx │ ├── setting-item │ │ └── SettingItem.styles.ts │ ├── app-rating │ │ └── AppRating.styles.ts │ ├── popup-dialog │ │ └── PopupDialog.styles.ts │ ├── subject-level-with-labels │ │ └── SubjectLevelWithLabels.styles.ts │ ├── app-card │ │ └── AppCard.styles.ts │ ├── tab │ │ ├── Tab.styles.ts │ │ └── Tab.tsx │ ├── checkbox-list │ │ └── CheckboxList.styles.ts │ ├── video-box │ │ └── VideoBox.styles.ts │ ├── app-rating-mobile │ │ └── AppRatingMobile.styles.ts │ ├── app-menu-button │ │ └── AppMenuButton.styles.ts │ ├── search-autocomplete │ │ └── SearchAutocomplete.styles.ts │ ├── info-card │ │ └── InfoCard.styles.ts │ ├── not-found-results │ │ └── NotFoundResults.styles.ts │ ├── subject-level-chips │ │ └── SubjectLevelChips.styles.ts │ ├── app-select │ │ └── AppSelect.styles.ts │ └── scroll-to-top-button │ │ └── ScrollToTopButton.styles.ts ├── utils │ ├── normalize-string.ts │ ├── map-array-by-field.tsx │ ├── are-all-values-empty-strings.ts │ ├── get-error-key.ts │ ├── is-submit-disabled.tsx │ ├── hash-scroll.ts │ ├── is-updated-photo.ts │ ├── get-first-answer.ts │ ├── title-to-camel-case.ts │ ├── to-lower-array.ts │ ├── isEqual.tsx │ ├── debounce.ts │ ├── error-with-message.tsx │ ├── get-validated-hex-color.tsx │ ├── validations │ │ └── validations.constants.ts │ ├── download-file.ts │ ├── translate-data.tsx │ ├── replace-empty-strings-with-null.ts │ ├── check-icons.tsx │ ├── get-changed-fields.ts │ ├── calculate-total-points.ts │ ├── cn.ts │ └── toggle-bookmark.ts ├── pages │ ├── new-quiz │ │ └── NewQuiz.styles.ts │ ├── categories │ │ ├── Categories.constants.tsx │ │ └── Categories.styles.ts │ ├── admin-home │ │ └── AdminHome.jsx │ ├── quiz-review │ │ ├── QuizReview.styles.ts │ │ └── QuizReview.tsx │ ├── student-profile │ │ └── StudentProfile.jsx │ ├── my-courses │ │ ├── MyCourses.constants.tsx │ │ └── MyCourses.styles.ts │ ├── guest-home-page │ │ └── GuestHome.styles.ts │ ├── create-course │ │ └── CreateCourse.styles.ts │ ├── bookmarked-offers │ │ ├── BookmarkedOffers.constants.ts │ │ └── BookmarkedOffers.styles.ts │ ├── tutor-home │ │ └── TutorHome.styles.ts │ ├── find-offers │ │ └── FindOffers.constants.ts │ ├── lesson-details │ │ └── LessonDetails.constants.tsx │ ├── create-or-edit-question │ │ └── CreateOrEditQuestion.constants.tsx │ ├── admin-table │ │ └── AdminTable.jsx │ ├── tutor-table │ │ └── TutorTable.jsx │ ├── my-cooperations │ │ └── MyCooperations.styles.ts │ ├── student-table │ │ └── StudentTable.jsx │ ├── my-resources │ │ └── MyResources.styles.ts │ └── edit-profile │ │ └── EditProfile.styles.ts ├── plugins │ ├── queryClient.ts │ ├── axiosClient.ts │ └── report-web-vitals.ts ├── design-system │ ├── scss │ │ ├── utilities.scss │ │ ├── utilities │ │ │ ├── _index.scss │ │ │ └── shadow.scss │ │ ├── _reset.scss │ │ └── styles.scss │ └── components │ │ ├── input-field │ │ └── InputField.constants.ts │ │ ├── icon-button │ │ └── IconButton.constants.ts │ │ ├── menu │ │ └── Menu.scss │ │ ├── menu-item │ │ ├── MenuItem.constants.ts │ │ └── MenuItem.types.ts │ │ └── badge │ │ └── Badge.scss ├── assets │ └── img │ │ ├── student-home │ │ └── bag.png │ │ ├── find-offer │ │ └── subject_icon.png │ │ ├── guest-home-page │ │ ├── learnImg.png │ │ ├── teachImg.png │ │ ├── title-bar.png │ │ ├── videoImg.png │ │ └── dots.svg │ │ ├── user-profile-page │ │ ├── avatar.png │ │ └── presentationVideoImg.png │ │ ├── offer-details │ │ └── top-block-icon.png │ │ ├── student-home-page │ │ └── service_icon.png │ │ ├── tutor-my-courses │ │ └── banner-pattern.png │ │ └── download-attachments │ │ └── download-symbol.svg ├── containers │ ├── user-profile │ │ ├── profile-info │ │ │ └── ProfileInfo.constants.ts │ │ └── comments-block │ │ │ └── CommentsBlock.styles.ts │ ├── layout │ │ ├── language-menu │ │ │ ├── LanguageMenu.styles.tsx │ │ │ └── LanguageMenu.constants.ts │ │ ├── app-snackbar │ │ │ └── AppSnackbar.styles.ts │ │ ├── app-header │ │ │ ├── AppHeader.styles.ts │ │ │ └── AppHeader.tsx │ │ ├── chat-menu │ │ │ └── ChatMenu.styles.ts │ │ ├── sidebar │ │ │ └── Sidebar.styles.ts │ │ ├── account-menu │ │ │ └── AccountMenu.styles.ts │ │ ├── notifications-menu │ │ │ └── NotificationsMenu.constants.ts │ │ └── admin-portal │ │ │ ├── admin-nav-bar-item │ │ │ └── AdminNavBarItem.styles.js │ │ │ └── admin-nav-bar │ │ │ └── AdminNavBar.styles.js │ ├── quiz │ │ ├── scroll-question-quiz-view │ │ │ └── ScrollQuestionsQuizView.styles.ts │ │ ├── time-is-up │ │ │ └── TimeIsUp.styles.ts │ │ ├── question-answer │ │ │ └── Answer.types.ts │ │ ├── quiz-header │ │ │ └── QuizHeader.styles.ts │ │ └── points │ │ │ └── Points.tsx │ ├── add-resources │ │ └── AddResources.constants.tsx │ ├── offer-details │ │ └── offer-carousel │ │ │ └── OfferCarousel.constants.ts │ ├── find-offer │ │ ├── constants.ts │ │ ├── offer-filter-block │ │ │ ├── offer-filter-list │ │ │ │ ├── OfferFilterList.styles.ts │ │ │ │ └── OfferFilterList.constants.ts │ │ │ ├── OfferFilterBlock.constants.ts │ │ │ └── OfferFilterBlock.styles.ts │ │ ├── filter-bar-menu │ │ │ └── FilterBarMenu.styles.ts │ │ ├── create-subject │ │ │ └── CreateSubject.constants.ts │ │ ├── offer-search-toolbar │ │ │ └── OfferSearchToolbar.styles.ts │ │ └── offer-container │ │ │ └── OfferContainer.styles.ts │ ├── my-cooperations │ │ ├── cooperation-closure-declined-banner │ │ │ └── CooperationClosureDeclinedBanner.styles.ts │ │ ├── accept-cooperation-close │ │ │ └── AcceptCooperationClosing.styles.ts │ │ ├── cooperation-notes │ │ │ ├── CooperationNotes.constants.ts │ │ │ └── CooperationNotes.styles.ts │ │ ├── cooperation-completion │ │ │ └── CooperationCompletion.styles.ts │ │ ├── cooperation-offer-toolbar │ │ │ └── CooperationOfferToolbar.styles.ts │ │ └── cooperation-action-input │ │ │ └── CooperationActionInput.styles.ts │ ├── find-course │ │ └── courses-filter-bar │ │ │ ├── CoursesFilterBar.styles.ts │ │ │ └── CorseFilterBar.constants.ts │ ├── tutor-home-page │ │ ├── language-step │ │ │ └── constants.ts │ │ └── add-photo-step │ │ │ └── constants.js │ ├── chat │ │ ├── sidebar-grouped-content │ │ │ └── SidebarGroupedContent.styles.ts │ │ └── chat-date │ │ │ └── ChatDate.styles.ts │ ├── edit-profile │ │ ├── professional-info-tab │ │ │ ├── professional-category-list │ │ │ │ └── ProfessionalCategoryList.styles.ts │ │ │ ├── add-professional-category-modal │ │ │ │ └── AddProfessionalCategoryModal.constants.ts │ │ │ └── about-tutor-accordion │ │ │ │ └── AboutTutorAccordion.styles.ts │ │ ├── profile-tab │ │ │ └── ProfileTab.styles.ts │ │ └── password-security-tab │ │ │ └── password-security-item │ │ │ └── PasswordSecurityItem.styles.ts │ ├── guest-home-page │ │ ├── google-button │ │ │ └── GoogleButton.styles.ts │ │ ├── how-it-works │ │ │ └── HowItWorks.styles.ts │ │ ├── who-we-are │ │ │ └── WhoWeAre.styles.ts │ │ ├── notification-modal │ │ │ └── NotificationModal.styles.ts │ │ ├── signup-form │ │ │ └── SignupForm.styles.js │ │ └── forgot-password │ │ │ └── ForgotPassword.styles.ts │ ├── my-resources │ │ ├── add-attachment-category-modal │ │ │ ├── AddAttachmentCategoryModal.constants.tsx │ │ │ └── AddAttachmentCategoryModal.styles.ts │ │ ├── add-categories-modal │ │ │ ├── AddCategories.styles.ts │ │ │ └── AddCategoriesModal.constants.ts │ │ ├── add-resource-with-input │ │ │ └── AddResourceWithInput.styles.ts │ │ ├── edit-attachment-modal │ │ │ └── EditAttachmentModal.styles.ts │ │ ├── create-or-edit-question-modal │ │ │ └── CreateOrEditQuestionModal.styles.ts │ │ └── rename-input │ │ │ └── RenameInput.styles.ts │ ├── cooperation-details │ │ ├── add-course-modal-modal │ │ │ └── AddCourseTemplateModal.constants.tsx │ │ ├── cooperation-activities-view │ │ │ └── CooperationActivitiesView.style.ts │ │ └── cooperation-activities │ │ │ └── CooperationActivities.constants.tsx │ ├── category-dropdown │ │ ├── CategoryDropdown.constants.ts │ │ └── CategoryDropdown.styles.tsx │ ├── my-courses │ │ └── my-courses-container │ │ │ └── MyCorsesCardsList.styles.ts │ ├── proficiency-level-select │ │ └── ProficiencyLevelSelect.styles.ts │ ├── questions-list │ │ └── QuestionsList.styles.ts │ ├── my-quizzes │ │ ├── view-quiz-container │ │ │ └── ViewQuizContainer.styles.ts │ │ └── create-or-edit-quiz-question │ │ │ └── CreateOrEditQuizQuestion.constants.ts │ ├── student-home-page │ │ ├── faq │ │ │ ├── Faq.styles.ts │ │ │ └── accordionItems.ts │ │ └── popular-categories │ │ │ └── PopularCategories.styles.js │ ├── app-content │ │ ├── AppContent.styles.ts │ │ └── AppContent.tsx │ ├── add-documents │ │ ├── AddDocuments.styles.ts │ │ └── AddDocuments.constants.ts │ ├── email-confirm-modal │ │ └── EmailConfirmModal.styles.ts │ ├── offer-page │ │ └── chat-dialog-window │ │ │ └── ChatDialogWindow.constants.ts │ ├── logo │ │ └── Logo.tsx │ └── navigation-icons │ │ └── NavigationIcons.tsx ├── styles │ └── app-theme │ │ ├── app.select.ts │ │ ├── app.menu-item.ts │ │ ├── app.menu-list.ts │ │ ├── app.checkbox.ts │ │ ├── app.svgicon.ts │ │ ├── app.table.ts │ │ ├── app.tooltip.ts │ │ ├── custom-shadows.ts │ │ └── app.button.ts ├── constants │ └── translations │ │ ├── index.ts │ │ ├── en │ │ ├── footer.json │ │ ├── active-students.json │ │ ├── step.json │ │ ├── admin.json │ │ ├── bookmarked-offers-page.json │ │ ├── table.json │ │ ├── icons-tooltip.json │ │ ├── error-messages.json │ │ ├── questions.json │ │ ├── titles.json │ │ ├── subjects-page.json │ │ ├── filters.json │ │ ├── cookie-consent-banner.json │ │ ├── change-confirm.json │ │ ├── question-page.json │ │ ├── lesson.json │ │ ├── email-modals.json │ │ ├── user-table.json │ │ ├── categories.json │ │ ├── button.json │ │ ├── my-offers-page.json │ │ └── signup.json │ │ └── uk │ │ ├── footer.json │ │ ├── active-students.json │ │ ├── step.json │ │ ├── admin.json │ │ ├── table.json │ │ ├── bookmarked-offers-page.json │ │ ├── icons-tooltip.json │ │ ├── error-messages.json │ │ ├── questions.json │ │ ├── change-confirm.json │ │ ├── titles.json │ │ ├── lesson.json │ │ ├── filters.json │ │ ├── subjects-page.json │ │ ├── question-page.json │ │ ├── cookie-consent-banner.json │ │ ├── email-modals.json │ │ ├── categories.json │ │ ├── user-table.json │ │ ├── button.json │ │ └── my-offers-page.json ├── services │ ├── example-service.ts │ ├── course-cooperation-service.ts │ └── location-service.ts ├── router │ ├── constants │ │ ├── adminRoutes.ts │ │ ├── errorRoutes.ts │ │ ├── studentRoutes.ts │ │ └── loaders.ts │ └── routes │ │ └── guestRouter.tsx ├── redux │ ├── apiSlice.ts │ ├── redux.constants.ts │ ├── selectors │ │ └── socket-selectors.ts │ └── socket-factory.ts ├── vite-env.d.ts ├── hooks │ ├── use-redux.tsx │ ├── use-droppable.tsx │ └── use-translate.tsx ├── PopupsProvider.tsx ├── QueryProvider.tsx └── index.tsx ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png └── manifest.json ├── spacetostudy-thumbnail.jpg ├── .vscode └── settings.json ├── docker └── files │ ├── ssh_setup.sh │ └── sshd_config ├── .prettierrc ├── tests ├── unit │ ├── components │ │ ├── step-wrapper │ │ │ └── TempComponent.jsx │ │ ├── loader │ │ │ └── Loader.spec.jsx │ │ ├── file-editor │ │ │ └── FileEditor.spec.jsx │ │ ├── layout │ │ │ └── AppHeader.spec.jsx │ │ ├── page-wrapper │ │ │ └── PageWrapper.spec.jsx │ │ ├── scroll-to-top │ │ │ └── ScrollToTop.spec.jsx │ │ ├── app-icon-button │ │ │ └── AppIconButton.spec.jsx │ │ └── app-rating-mobile │ │ │ └── AppRatingMobile.spec.jsx │ ├── pages │ │ ├── new-quiz │ │ │ └── NewQuiz.spec.jsx │ │ ├── admin-home │ │ │ └── AdminHome.spec.jsx │ │ ├── student-profile │ │ │ └── StudentProfile.spec.jsx │ │ ├── my-resources │ │ │ └── MyResources.spec.jsx │ │ └── my-courses │ │ │ └── MyCourses.spec.constans.js │ ├── hooks │ │ ├── use-input-visibility.spec.jsx │ │ ├── use-axios.spec.js │ │ ├── use-query.spec.js │ │ └── mock-categories.js │ ├── utils │ │ ├── are-all-values-empty-strings.spec.js │ │ ├── course-custom-options.spec.jsx │ │ └── error-with-message.spec.jsx │ ├── design-system │ │ └── components │ │ │ └── Button │ │ │ └── Button.spec.jsx │ └── containers │ │ ├── edit-profile │ │ └── profile-tab │ │ │ └── profile-tab-form │ │ │ └── ProfileTabForm.spec.constants.js │ │ └── tutor-profile │ │ └── video-presentation │ │ └── VideoPresentation.spec.jsx └── setup-tests.js ├── tsconfig.node.json ├── tsconfig.eslint.json ├── .github └── dependabot.yml ├── .dockerignore ├── .gitignore ├── .storybook └── main.ts ├── .stylelintrc.json └── compose.yml /.husky/pre-push: -------------------------------------------------------------------------------- 1 | npm run test 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run pre-commit 2 | -------------------------------------------------------------------------------- /src/exceptions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './response-error' 2 | -------------------------------------------------------------------------------- /src/types/snackbar/snackbar.index.ts: -------------------------------------------------------------------------------- 1 | export * from './types/snackbar.types' 2 | -------------------------------------------------------------------------------- /src/types/mui/customTypes.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/mui/customTypes/customTypes' 2 | -------------------------------------------------------------------------------- /src/types/notes/notes.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/notes/interfaces/notes.interfaces' 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/types/categories/categories.index.ts: -------------------------------------------------------------------------------- 1 | export * from './interfaces/categories.interfaces' 2 | -------------------------------------------------------------------------------- /src/types/chat/file/file.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/chat/file/interfaces/file.interface' 2 | -------------------------------------------------------------------------------- /src/types/chat/link/link.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/chat/link/interfaces/link.interface' 2 | -------------------------------------------------------------------------------- /src/types/lesson/lesson.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/lesson/interfaces/lesson.interfaces' 2 | -------------------------------------------------------------------------------- /src/types/services/services.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/services/types/services.types' 2 | -------------------------------------------------------------------------------- /src/types/utils/utils.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/utils/utils-interfaces/utils.interface' 2 | -------------------------------------------------------------------------------- /src/components/sidebar-content-box/SidebarContentBox.constants.ts: -------------------------------------------------------------------------------- 1 | export const maxElemToShow = 3 2 | -------------------------------------------------------------------------------- /src/types/chat/media/media.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/chat/media/interface/media.interface' 2 | -------------------------------------------------------------------------------- /src/types/finished-quizzes/finishedQuizzes.index.ts: -------------------------------------------------------------------------------- 1 | export * from './types/finishedQuizzes.types' 2 | -------------------------------------------------------------------------------- /src/types/quizzes/quizzes.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/quizzes/interfaces/quizzes.interface' 2 | -------------------------------------------------------------------------------- /src/types/subject/subject.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/subject/interfaces/subject.interface' 2 | -------------------------------------------------------------------------------- /src/types/my-offers/myOffers.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/my-offers/interfaces/myOffers.interfaces' 2 | -------------------------------------------------------------------------------- /src/types/questions/questions.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/questions/interfaces/questions.interface' 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/types/chat/message/message.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/chat/message/interfaces/message.interface' 2 | -------------------------------------------------------------------------------- /src/types/components/tab/tab.types.ts: -------------------------------------------------------------------------------- 1 | export interface TabType { 2 | label: string 3 | value: T 4 | } 5 | -------------------------------------------------------------------------------- /src/components/find-block/find-student-constants.ts: -------------------------------------------------------------------------------- 1 | export const translationKey = 'tutorHomePage.findStudentBlock' 2 | -------------------------------------------------------------------------------- /src/components/find-block/find-tutor-constants.ts: -------------------------------------------------------------------------------- 1 | export const translationKey = 'studentHomePage.findTutorBlock' 2 | -------------------------------------------------------------------------------- /src/types/bookmarked-offers/bookmarkedOffers.index.ts: -------------------------------------------------------------------------------- 1 | export * from './interfaces/bookmarkedOffers.interfaces' 2 | -------------------------------------------------------------------------------- /src/utils/normalize-string.ts: -------------------------------------------------------------------------------- 1 | export const normalizeString = (str: string): string => str.trim().toLowerCase() 2 | -------------------------------------------------------------------------------- /src/types/offer-details/offerDetails.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/offer-details/interfaces/offerDetails.interfaces' 2 | -------------------------------------------------------------------------------- /spacetostudy-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/spacetostudy-thumbnail.jpg -------------------------------------------------------------------------------- /src/components/search-input/SearchInput.styles.js: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | input: { 3 | width: '70%' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/new-quiz/NewQuiz.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | px: { sm: '70px' } 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/types/my-attachments/myAttachments.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/my-attachments/interfaces/myAttachments.interfaces' 2 | -------------------------------------------------------------------------------- /src/types/user-profile/types/userProfile.types.ts: -------------------------------------------------------------------------------- 1 | export type RatingType = { 2 | rating: number 3 | count: number 4 | } 5 | -------------------------------------------------------------------------------- /src/types/components/appSelect/appSelect.types.ts: -------------------------------------------------------------------------------- 1 | export type SelectFieldType = { 2 | value: T 3 | title: string 4 | } 5 | -------------------------------------------------------------------------------- /src/types/findOffers/enums/findOffers.enums.ts: -------------------------------------------------------------------------------- 1 | export enum CardsViewEnum { 2 | Grid = 'grid', 3 | Inline = 'inline' 4 | } 5 | -------------------------------------------------------------------------------- /src/types/offer/enums/offer.enums.ts: -------------------------------------------------------------------------------- 1 | export enum OfferActionsEnum { 2 | Create = 'createOffer', 3 | Edit = 'editOffer' 4 | } 5 | -------------------------------------------------------------------------------- /src/types/video-box/videoBox.index.ts: -------------------------------------------------------------------------------- 1 | import type { VideoBoxType } from './types/videoBox.types' 2 | 3 | export { VideoBoxType } 4 | -------------------------------------------------------------------------------- /src/pages/categories/Categories.constants.tsx: -------------------------------------------------------------------------------- 1 | export const itemsLoadLimit = { 2 | default: 9, 3 | tablet: 8, 4 | mobile: 6 5 | } 6 | -------------------------------------------------------------------------------- /src/plugins/queryClient.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from '@tanstack/react-query' 2 | 3 | export const queryClient = new QueryClient() 4 | -------------------------------------------------------------------------------- /src/types/components/enum-filter/enumFilter.interface.ts: -------------------------------------------------------------------------------- 1 | export interface FilterEnum { 2 | value: string 3 | label: string 4 | } 5 | -------------------------------------------------------------------------------- /src/types/my-attachments/interfaces/myAttachments.interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface MyAttachmentsFilters { 2 | search: string 3 | } 4 | -------------------------------------------------------------------------------- /src/design-system/scss/utilities.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | 3 | @forward 'mixins'; 4 | @forward 'functions'; 5 | @forward 'variables'; 6 | -------------------------------------------------------------------------------- /src/pages/admin-home/AdminHome.jsx: -------------------------------------------------------------------------------- 1 | const AdminHome = () => { 2 | return
Hello Admin!
3 | } 4 | 5 | export default AdminHome 6 | -------------------------------------------------------------------------------- /src/types/components/enhanced-table/enhancedTable.types.ts: -------------------------------------------------------------------------------- 1 | export type TableActionFunc = (id: string | string[]) => Promise | void 2 | -------------------------------------------------------------------------------- /src/assets/img/student-home/bag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/student-home/bag.png -------------------------------------------------------------------------------- /src/components/download-button/DownloadButton.constants.ts: -------------------------------------------------------------------------------- 1 | export const status = { 2 | action: 'downloading', 3 | inaction: 'download' 4 | } 5 | -------------------------------------------------------------------------------- /src/types/offer/offer.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/offer/interfaces/offer.interfaces' 2 | export * from '~/types/offer/enums/offer.enums' 3 | -------------------------------------------------------------------------------- /src/utils/map-array-by-field.tsx: -------------------------------------------------------------------------------- 1 | export const mapArrayByField = (data: T[], transform: keyof T) => 2 | data.map((item) => item[transform]) 3 | -------------------------------------------------------------------------------- /src/containers/user-profile/profile-info/ProfileInfo.constants.ts: -------------------------------------------------------------------------------- 1 | export interface DoneItem { 2 | title: string 3 | description: string 4 | } 5 | -------------------------------------------------------------------------------- /src/types/course/enums/course.enum.ts: -------------------------------------------------------------------------------- 1 | export enum CourseAutocompleteOptionsEnum { 2 | Categories = 'Categories', 3 | Subjects = 'Subjects' 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": "explicit" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/components/dropdown-add-btn/DropdownButton.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | optionsButton: { 3 | justifyContent: 'flex-start' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/containers/layout/language-menu/LanguageMenu.styles.tsx: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | active: { 3 | backgroundColor: 'primary.100' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/containers/quiz/scroll-question-quiz-view/ScrollQuestionsQuizView.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | question: { 3 | px: '0' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/types/components/app-breadcrumbs/appBreadcrumbs.types.ts: -------------------------------------------------------------------------------- 1 | import { Crumb } from '~/types' 2 | 3 | export type Crumbfunc = (data: unknown) => Crumb 4 | -------------------------------------------------------------------------------- /src/types/user-profile/userProfile.index.ts: -------------------------------------------------------------------------------- 1 | import type { RatingType } from '~/types/user-profile/types/userProfile.types' 2 | 3 | export { RatingType } 4 | -------------------------------------------------------------------------------- /src/types/video-box/types/videoBox.types.ts: -------------------------------------------------------------------------------- 1 | export interface VideoBoxType { 2 | video: string 3 | videoPreview: boolean 4 | videoMock: string 5 | } 6 | -------------------------------------------------------------------------------- /docker/files/ssh_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ssh-keygen -A 4 | 5 | #prepare run dir 6 | if [ ! -d "/var/run/sshd" ]; then 7 | mkdir -p /var/run/sshd 8 | fi -------------------------------------------------------------------------------- /src/assets/img/find-offer/subject_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/find-offer/subject_icon.png -------------------------------------------------------------------------------- /src/assets/img/guest-home-page/learnImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/guest-home-page/learnImg.png -------------------------------------------------------------------------------- /src/assets/img/guest-home-page/teachImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/guest-home-page/teachImg.png -------------------------------------------------------------------------------- /src/assets/img/guest-home-page/title-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/guest-home-page/title-bar.png -------------------------------------------------------------------------------- /src/assets/img/guest-home-page/videoImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/guest-home-page/videoImg.png -------------------------------------------------------------------------------- /src/assets/img/user-profile-page/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/user-profile-page/avatar.png -------------------------------------------------------------------------------- /src/components/app-menu/AppMenu.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | menu: { 3 | '& .MuiPaper-root': { 4 | borderRadius: 0 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/drag-handle/DragHandle.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | dragIcon: { 3 | touchAction: 'none', 4 | cursor: 'grab' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/styles/app-theme/app.select.ts: -------------------------------------------------------------------------------- 1 | export const select = { 2 | styleOverrides: { 3 | select: { 4 | padding: '12.5px 14px' 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/containers/quiz/time-is-up/TimeIsUp.styles.ts: -------------------------------------------------------------------------------- 1 | const styles = { 2 | icon: { 3 | color: 'var(--s2s-red-600)' 4 | } 5 | } 6 | 7 | export default styles 8 | -------------------------------------------------------------------------------- /src/types/components/icon-with-text-list/profileDoneItemsList.interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface ProfileDoneItem { 2 | title: string 3 | description: string 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/img/offer-details/top-block-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/offer-details/top-block-icon.png -------------------------------------------------------------------------------- /src/assets/img/student-home-page/service_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/student-home-page/service_icon.png -------------------------------------------------------------------------------- /src/containers/layout/app-snackbar/AppSnackbar.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | action: { 3 | p: '4px 8px 0 30px', 4 | cursor: 'pointer' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/types/attachment/attachment.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/attachment/interfaces/attachment.interface' 2 | export * from '~/types/attachment/types/attachment.types' 3 | -------------------------------------------------------------------------------- /src/assets/img/tutor-my-courses/banner-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/tutor-my-courses/banner-pattern.png -------------------------------------------------------------------------------- /src/components/popular-categories/PopularCategories.constants.ts: -------------------------------------------------------------------------------- 1 | export const itemsLoadLimit = { 2 | desktop: 12, 3 | tablet: 6, 4 | mobile: 4, 5 | default: 9 6 | } 7 | -------------------------------------------------------------------------------- /src/containers/add-resources/AddResources.constants.tsx: -------------------------------------------------------------------------------- 1 | import { SortEnum } from '~/types' 2 | 3 | export const initialSort = { order: SortEnum.Desc, orderBy: 'updatedAt' } 4 | -------------------------------------------------------------------------------- /src/pages/quiz-review/QuizReview.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | quizzesWrapper: { 3 | maxWidth: '936px', 4 | width: '100%', 5 | mx: 'auto' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/types/chat/file/interfaces/file.interface.ts: -------------------------------------------------------------------------------- 1 | import { Link } from '~/types/chat/link/link.index' 2 | 3 | export interface File extends Link { 4 | size: number 5 | } 6 | -------------------------------------------------------------------------------- /src/containers/offer-details/offer-carousel/OfferCarousel.constants.ts: -------------------------------------------------------------------------------- 1 | export const itemsLoadLimit = { 2 | desktop: 3, 3 | tablet: 2, 4 | mobile: 1, 5 | default: 3 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/student-profile/StudentProfile.jsx: -------------------------------------------------------------------------------- 1 | const StudentProfile = () => { 2 | return
StudentProfile Page Placeholder
3 | } 4 | 5 | export default StudentProfile 6 | -------------------------------------------------------------------------------- /src/types/edit-profile/editProfile.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/edit-profile/interfaces/editProfile.interfaces' 2 | export * from '~/types/edit-profile/types/editProfile.types' 3 | -------------------------------------------------------------------------------- /src/constants/translations/index.ts: -------------------------------------------------------------------------------- 1 | import en from './en' 2 | import uk from './uk' 3 | 4 | const resources = { 5 | en, 6 | uk 7 | } 8 | 9 | export default resources 10 | -------------------------------------------------------------------------------- /src/design-system/components/input-field/InputField.constants.ts: -------------------------------------------------------------------------------- 1 | export enum InputFieldVariantEnum { 2 | Large = 'large', 3 | Small = 'small', 4 | Outlined = 'outlined' 5 | } 6 | -------------------------------------------------------------------------------- /src/types/bookmarked-offers/interfaces/bookmarkedOffers.interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface FindBookmarksFilters { 2 | title: string 3 | sort: string 4 | page: string | number 5 | } 6 | -------------------------------------------------------------------------------- /src/types/chat/chat.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/chat/interfaces/chat.interfaces' 2 | export * from '~/types/chat/types/chat.types' 3 | export * from '~/types/chat/enum/chat.enums' 4 | -------------------------------------------------------------------------------- /src/types/findOffers/findOffers.index.ts: -------------------------------------------------------------------------------- 1 | export * from './interfaces/findOffers.interfaces' 2 | export * from './types/findOffers.types' 3 | export * from './enums/findOffers.enums' 4 | -------------------------------------------------------------------------------- /src/types/notification/notification.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/notification/interfaces/notification.interfaces' 2 | export * from '~/types/notification/enums/notification.enums' 3 | -------------------------------------------------------------------------------- /src/assets/img/user-profile-page/presentationVideoImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ita-social-projects/SpaceToStudy-Client/HEAD/src/assets/img/user-profile-page/presentationVideoImg.png -------------------------------------------------------------------------------- /src/containers/find-offer/constants.ts: -------------------------------------------------------------------------------- 1 | export const translationKey = 'findOffers.offerRequestBlock' 2 | 3 | export const initialSubjectValue = { 4 | name: '', 5 | category: '' 6 | } 7 | -------------------------------------------------------------------------------- /src/containers/my-cooperations/cooperation-closure-declined-banner/CooperationClosureDeclinedBanner.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | boldText: { 3 | fontWeight: 500 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/types/components/radioButtonInputs/RadioButtonInputs.types.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export type RadioButtonType = { 4 | title: React.ReactNode 5 | value: T 6 | } 7 | -------------------------------------------------------------------------------- /src/types/edit-user-profile/interfaces/securityBlockForm.interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface FormValues { 2 | currentPassword: string 3 | password: string 4 | confirmPassword: string 5 | } 6 | -------------------------------------------------------------------------------- /src/components/app-progress-bar-line/AppProgressBarLine.constans.tsx: -------------------------------------------------------------------------------- 1 | export const studentLabelsPercentage = [0, 25, 50, 75, 100] 2 | export const tutorLabelsPercentage = [0, 20, 40, 60, 80, 100] 3 | -------------------------------------------------------------------------------- /src/constants/translations/en/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "allRightsReserved": "© All rights reserved Space to Study", 3 | "privacyPolicy": "Privacy Policy", 4 | "termsOfUse": "Term of Use" 5 | } 6 | -------------------------------------------------------------------------------- /src/containers/find-course/courses-filter-bar/CoursesFilterBar.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | display: 'flex', 4 | justifyContent: 'space-between' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/containers/find-offer/offer-filter-block/offer-filter-list/OfferFilterList.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | title: { 3 | typography: 'midTitle' 4 | }, 5 | checkbox: { mt: 1 } 6 | } 7 | -------------------------------------------------------------------------------- /src/types/course/course.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/course/interfaces/course.interface' 2 | export * from '~/types/course/types/course.types' 3 | export * from '~/types/course/enums/course.enum' 4 | -------------------------------------------------------------------------------- /src/types/user/user.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/user/user-interfaces/user.interfaces' 2 | export * from '~/types/user/user-types/user.types' 3 | export * from '~/types/user/user-enums/user.enums' 4 | -------------------------------------------------------------------------------- /src/components/page-wrapper/PageWrapper.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | flex: 1, 4 | display: 'flex', 5 | flexDirection: 'column', 6 | mb: '100px' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/pages/my-courses/MyCourses.constants.tsx: -------------------------------------------------------------------------------- 1 | export const defaultResponse = { items: [], count: 0 } 2 | 3 | export const courseItemsLoadLimit = { 4 | tablet: 6, 5 | mobile: 6, 6 | default: 6 7 | } 8 | -------------------------------------------------------------------------------- /src/types/chat/types/chat.types.ts: -------------------------------------------------------------------------------- 1 | import { Offer } from '~/types/offer/offer.index' 2 | 3 | export type ChatInfo = Pick & { 4 | updateInfo: () => void 5 | } 6 | -------------------------------------------------------------------------------- /src/types/common/common.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/common/enums/common.enums' 2 | export * from '~/types/common/types/common.types' 3 | export * from '~/types/common/interfaces/common.interfaces' 4 | -------------------------------------------------------------------------------- /src/components/app-select-button/AppSelectButton.styles.tsx: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | text: { typography: TypographyVariantEnum.Subtitle1 } 5 | } 6 | -------------------------------------------------------------------------------- /src/constants/translations/uk/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "allRightsReserved": "© Всі права захищені Space to Study", 3 | "privacyPolicy": "Політика конфіденційності", 4 | "termsOfUse": "Умови використання" 5 | } 6 | -------------------------------------------------------------------------------- /src/design-system/components/icon-button/IconButton.constants.ts: -------------------------------------------------------------------------------- 1 | export enum IconButtonVariant { 2 | Primary = 'primary', 3 | Secondary = 'secondary', 4 | Success = 'success', 5 | Error = 'error' 6 | } 7 | -------------------------------------------------------------------------------- /src/types/attachment/types/attachment.types.ts: -------------------------------------------------------------------------------- 1 | export type UploadFileEmitterArgs = { 2 | files: File[] 3 | error: string 4 | } 5 | 6 | export type UploadFileEmitter = (args: UploadFileEmitterArgs) => void 7 | -------------------------------------------------------------------------------- /src/types/edit-user-profile/editUserProfile.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/edit-user-profile/enums/editUserProfile.enums' 2 | export * from '~/types/edit-user-profile/interfaces/securityBlockForm.interfaces' 3 | -------------------------------------------------------------------------------- /src/components/tutor-schedule/types.ts: -------------------------------------------------------------------------------- 1 | export interface ITutorScheduleItem { 2 | time: string 3 | firstName: string 4 | lastName: string 5 | subject: string 6 | chapter: string 7 | price: number 8 | } 9 | -------------------------------------------------------------------------------- /src/constants/translations/en/active-students.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Your Students", 3 | "showMore": "show more", 4 | "addStudent": "add student", 5 | "noStudentsYet": "You don't have any students yet" 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/guest-home-page/GuestHome.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { flex: 1 }, 3 | sectionsWrapper: { 4 | gap: { sm: '80px', xs: '48px' }, 5 | mt: { sm: '80px', xs: '48px' } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/types/components/accordion-with-image/accordionWithImage.interface.ts: -------------------------------------------------------------------------------- 1 | import { AccordionItem } from '~/types' 2 | 3 | export interface AccordionWithImageItem extends AccordionItem { 4 | image: string 5 | } 6 | -------------------------------------------------------------------------------- /src/types/components/subject-level-chips/subjectLevelChips.interface.ts: -------------------------------------------------------------------------------- 1 | import { SxProps } from '@mui/material' 2 | 3 | export interface SubjectLevelChipsSx { 4 | container?: SxProps 5 | label?: SxProps 6 | } 7 | -------------------------------------------------------------------------------- /src/constants/translations/uk/active-students.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Ваші студенти", 3 | "showMore": "показати більше", 4 | "addStudent": "додати студента", 5 | "noStudentsYet": "У вас ще немає студентів" 6 | } 7 | -------------------------------------------------------------------------------- /src/containers/tutor-home-page/language-step/constants.ts: -------------------------------------------------------------------------------- 1 | export const languages = [ 2 | 'English', 3 | 'Ukrainian', 4 | 'Polish', 5 | 'German', 6 | 'French', 7 | 'Spanish', 8 | 'Arabic' 9 | ] 10 | -------------------------------------------------------------------------------- /src/types/chat/enum/chat.enums.ts: -------------------------------------------------------------------------------- 1 | export enum SidebarContentEnum { 2 | About = 'about', 3 | Links = 'links' 4 | } 5 | 6 | export enum AdornmentPosition { 7 | Start = 'start', 8 | End = 'end' 9 | } 10 | -------------------------------------------------------------------------------- /src/types/chat/link/interfaces/link.interface.ts: -------------------------------------------------------------------------------- 1 | import { CommonEntityFields } from '~/types/common/common.index' 2 | 3 | export interface Link extends CommonEntityFields { 4 | name: string 5 | url: string 6 | } 7 | -------------------------------------------------------------------------------- /src/types/chat/media/interface/media.interface.ts: -------------------------------------------------------------------------------- 1 | import { CommonEntityFields } from '~/types/common/common.index' 2 | 3 | export interface Media extends CommonEntityFields { 4 | path: string 5 | name: string 6 | } 7 | -------------------------------------------------------------------------------- /src/containers/chat/sidebar-grouped-content/SidebarGroupedContent.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | groupedContent: { 3 | container: { p: '24px 0 0 0' }, 4 | textWithIconWrapper: { ml: '15px' } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/types/common/events/events.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/common/events/enums/events.enums' 2 | export * from '~/types/common/events/types/events.types' 3 | export * from '~/types/common/events/interfaces/events.interfaces' 4 | -------------------------------------------------------------------------------- /src/components/file-component/FileComponent.constants.ts: -------------------------------------------------------------------------------- 1 | import { File, Link } from '~/types' 2 | 3 | export const openInNewTab = (component: File | Link) => { 4 | window.open(component.url, '_blank', 'noopener noreferrer') 5 | } 6 | -------------------------------------------------------------------------------- /src/design-system/components/menu/Menu.scss: -------------------------------------------------------------------------------- 1 | @use '~scss/utilities' as *; 2 | 3 | .#{$prefix}menu { 4 | border-radius: 6px; 5 | box-shadow: 6 | 0 0 8px 0 rgba(0, 0, 0, 0.08), 7 | 0 10px 12px 0 rgba(0, 0, 0, 0.1); 8 | } 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "jsxSingleQuote": true, 6 | "arrowParens": "always", 7 | "endOfLine": "lf", 8 | "useTabs": false, 9 | "tabWidth": 2 10 | } 11 | -------------------------------------------------------------------------------- /src/components/avatar-icon/AvatarIcon.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | avatar: { 5 | color: 'primary.900', 6 | typography: TypographyVariantEnum.Button 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/constants/translations/uk/step.json: -------------------------------------------------------------------------------- 1 | { 2 | "stepLabels": { 3 | "generalInfo": "Загальні", 4 | "interests": "Інтереси", 5 | "language": "Мова", 6 | "subjects": "Предмети", 7 | "photo": "Фото" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/edit-profile/professional-info-tab/professional-category-list/ProfessionalCategoryList.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | cards: { 3 | display: 'flex', 4 | flexDirection: 'column', 5 | gap: 3 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/types/components/appContentSwitcher/appContentSwitcher.types.ts: -------------------------------------------------------------------------------- 1 | export type SwitchContent = { 2 | text: string 3 | tooltip?: string 4 | } 5 | 6 | export type SwitchOptions = { 7 | [key in 'left' | 'right']?: SwitchContent 8 | } 9 | -------------------------------------------------------------------------------- /src/types/cooperation/cooperation.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/cooperation/interfaces/cooperation.interface' 2 | export * from '~/types/cooperation/types/cooperation.types' 3 | export * from '~/types/cooperation/enums/cooperation.enums' 4 | -------------------------------------------------------------------------------- /src/constants/translations/en/step.json: -------------------------------------------------------------------------------- 1 | { 2 | "stepLabels": { 3 | "generalInfo": "General", 4 | "interests": "Interests", 5 | "language": "Language", 6 | "subjects": "Subjects", 7 | "photo": "Photo" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/styles/app-theme/app.menu-item.ts: -------------------------------------------------------------------------------- 1 | import palette from './app.pallete' 2 | 3 | export const menuItem = { 4 | styleOverrides: { 5 | root: { 6 | '&:hover': { backgroundColor: palette.primary[50] } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/types/my-resources/myResources.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/my-resources/interfaces/myResources.interface' 2 | export * from '~/types/my-resources/types/myResources.types' 3 | export * from '~/types/my-resources/enum/myResources.enum' 4 | -------------------------------------------------------------------------------- /src/containers/layout/language-menu/LanguageMenu.constants.ts: -------------------------------------------------------------------------------- 1 | export const languageMenuConstants = [ 2 | { 3 | value: 'en', 4 | label: 'English' 5 | }, 6 | { 7 | value: 'uk', 8 | label: 'Українська' 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /src/design-system/scss/utilities/_index.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | 3 | @forward 'background'; 4 | @forward 'borders'; 5 | @forward 'flexbox'; 6 | @forward 'shadow'; 7 | @forward 'sizing'; 8 | @forward 'spacing'; 9 | @forward 'colors'; 10 | -------------------------------------------------------------------------------- /tests/unit/components/step-wrapper/TempComponent.jsx: -------------------------------------------------------------------------------- 1 | const TempComponent = ({ btnsBox, children }) => { 2 | return ( 3 | <> 4 | {children} 5 | {btnsBox} 6 | 7 | ) 8 | } 9 | 10 | export default TempComponent 11 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.mts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/are-all-values-empty-strings.ts: -------------------------------------------------------------------------------- 1 | export const areAllValuesEmptyStrings = (obj: { 2 | [key: string]: string 3 | }): boolean => { 4 | return Object.values(obj).every( 5 | (value) => typeof value === 'string' && value === '' 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/services/example-service.ts: -------------------------------------------------------------------------------- 1 | import { axiosClient } from '~/plugins/axiosClient' 2 | import { URLs } from '~/constants/request' 3 | 4 | export const exampleService = { 5 | getAll: () => { 6 | return axiosClient.get(URLs.example.get) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/types/edit-user-profile/enums/editUserProfile.enums.ts: -------------------------------------------------------------------------------- 1 | export enum UserProfileTabsEnum { 2 | Profile = 'profile', 3 | ProfessionalInfo = 'professionalInfo', 4 | Notifications = 'notifications', 5 | PasswordAndSecurity = 'passwordAndSecurity' 6 | } 7 | -------------------------------------------------------------------------------- /src/containers/guest-home-page/google-button/GoogleButton.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | google: { 3 | marginBottom: '16px', 4 | height: '44px', 5 | width: '100%', 6 | overflow: 'hidden', 7 | minWidth: '315px' 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/my-cooperations/accept-cooperation-close/AcceptCooperationClosing.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | boldText: { 3 | fontWeight: 500 4 | }, 5 | response: { 6 | paddingTop: '10px', 7 | display: 'block' 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/router/constants/adminRoutes.ts: -------------------------------------------------------------------------------- 1 | export const adminRoutes = { 2 | students: { route: 'students' }, 3 | admins: { route: 'admins' }, 4 | tutors: { route: 'tutors' }, 5 | categories: { route: 'categories' }, 6 | complains: { route: 'complains' } 7 | } 8 | -------------------------------------------------------------------------------- /src/router/constants/errorRoutes.ts: -------------------------------------------------------------------------------- 1 | export const errorRoutes = { 2 | badRequest: { route: '400' }, 3 | internalServerError: { route: '500' }, 4 | authPolicy: { route: '401', path: '/error/401' }, 5 | notFound: { route: '404', path: '/error/404' } 6 | } 7 | -------------------------------------------------------------------------------- /src/types/notification/interfaces/notification.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { CommonEntityFields, NotificationTypeEnums } from '~/types' 2 | 3 | export interface Notification extends CommonEntityFields { 4 | type: NotificationTypeEnums 5 | reference?: string 6 | } 7 | -------------------------------------------------------------------------------- /src/types/subject/interfaces/subject.interface.ts: -------------------------------------------------------------------------------- 1 | export interface CreateSubjectParams { 2 | name: string 3 | category: string 4 | } 5 | 6 | export interface CreateSubjectInterface { 7 | id: string 8 | name: string 9 | category: string 10 | } 11 | -------------------------------------------------------------------------------- /src/constants/translations/en/admin.json: -------------------------------------------------------------------------------- 1 | { 2 | "navBar": { 3 | "roles": "Roles", 4 | "categories": "Categories", 5 | "complains": "Complains", 6 | "admins": "Admins", 7 | "tutors": "Tutors", 8 | "students": "Students" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/types/my-cooperations/myCooperations.index.ts: -------------------------------------------------------------------------------- 1 | export * from '~/types/my-cooperations/interfaces/myCooperations.interfaces' 2 | export * from '~/types/my-cooperations/enums/materialsAccess.enums' 3 | export * from '~/types/my-cooperations/enums/myCooperations.enums' 4 | -------------------------------------------------------------------------------- /src/components/app-text-field/AppTextField.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | helperText: (multiline?: boolean) => ({ 3 | overflow: 'hidden', 4 | textOverflow: 'ellipsis', 5 | whiteSpace: 'pre-wrap', 6 | mr: multiline ? '48px' : '14px' 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /src/components/enhanced-table/enhanced-table-head/EnhancedTableHead.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | tableHead: { 3 | backgroundColor: 'primary.100', 4 | '& th': { 5 | color: 'primary', 6 | typography: 'subtitle2' 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/radio-button-inputs/RadioButtonInputs.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | title: { 3 | color: 'primary.700', 4 | mb: '15px' 5 | }, 6 | radioItems: { 7 | color: 'basic.black', 8 | mb: '8px', 9 | ml: '10px' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/video-player/VideoPlayer.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | playerWrapper: { 3 | position: 'relative', 4 | aspectRatio: '16/9' 5 | }, 6 | player: { 7 | position: 'absolute', 8 | top: '0', 9 | left: '0' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/constants/translations/uk/admin.json: -------------------------------------------------------------------------------- 1 | { 2 | "navBar": { 3 | "roles": "Ролі", 4 | "categories": "Категорії", 5 | "complains": "Скарги", 6 | "admins": "Адміністратори", 7 | "tutors": "Викладачі", 8 | "students": "Студенти" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/containers/my-cooperations/cooperation-notes/CooperationNotes.constants.ts: -------------------------------------------------------------------------------- 1 | import { ResponseError } from '~/exceptions' 2 | 3 | export const noteNotFoundError = new ResponseError({ 4 | message: 'Note not found', 5 | status: 404, 6 | code: 'NOTE_NOT_FOUND' 7 | }) 8 | -------------------------------------------------------------------------------- /src/types/mui/customTypes/customTypes.d.ts: -------------------------------------------------------------------------------- 1 | import { TextFieldProps } from '@mui/material/TextField' 2 | 3 | declare module '@mui/x-date-pickers/DesktopDatePicker' { 4 | interface DesktopDatePickerProps { 5 | inputProps?: TextFieldProps['inputProps'] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/get-error-key.ts: -------------------------------------------------------------------------------- 1 | import { type ResponseError } from '~/exceptions' 2 | interface ErrorResponse { 3 | code: string 4 | } 5 | 6 | export const getErrorKey = (error?: ErrorResponse | ResponseError) => 7 | `errors.${error?.code ? error.code : 'UNKNOWN_ERROR'}` 8 | -------------------------------------------------------------------------------- /src/utils/is-submit-disabled.tsx: -------------------------------------------------------------------------------- 1 | export const isSubmitDisabled = (items: { name: string }[]): boolean => { 2 | const areItemsPresent = items.length > 0 3 | const areAllItemsValid = items.every((item) => item.name) 4 | return areAllItemsValid && areItemsPresent 5 | } 6 | -------------------------------------------------------------------------------- /src/constants/translations/en/bookmarked-offers-page.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Bookmarks", 3 | "search": "Search for offer or request", 4 | "notFound": { 5 | "description": "We couldn’t find any bookmarks." 6 | }, 7 | "loadingError": "Couldn’t load offers data" 8 | } 9 | -------------------------------------------------------------------------------- /src/constants/translations/en/table.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": "Actions", 3 | "of": "of", 4 | "numberOfRows": "Number of rows:", 5 | "goToPage": "Go to page", 6 | "go": "GO", 7 | "selected": "selected", 8 | "noExactMatches": "No exact matches found!" 9 | } 10 | -------------------------------------------------------------------------------- /src/constants/translations/uk/table.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": "Дії", 3 | "of": "з", 4 | "numberOfRows": "Кількість рядків:", 5 | "goToPage": "Перейти на сторінку", 6 | "go": "ПЕРЕЙТИ", 7 | "selected": "вибрано", 8 | "noExactMatches": "Точних збігів не знайдено!" 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/my-resources/add-attachment-category-modal/AddAttachmentCategoryModal.constants.tsx: -------------------------------------------------------------------------------- 1 | import { Attachment } from '~/types' 2 | 3 | export const getInitialValues = (attachment: Attachment) => { 4 | return { 5 | id: attachment._id, 6 | category: null 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/types/offer-details/interfaces/offerDetails.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { Offer, ProficiencyLevelEnum } from '~/types' 2 | 3 | export interface EnrollOfferForm extends Pick { 4 | proficiencyLevel: ProficiencyLevelEnum 5 | additionalInfo?: string 6 | title: string 7 | } 8 | -------------------------------------------------------------------------------- /src/types/user/user-enums/user.enums.ts: -------------------------------------------------------------------------------- 1 | export enum UserRoleEnum { 2 | Student = 'student', 3 | Tutor = 'tutor', 4 | Admin = 'admin' 5 | } 6 | 7 | export enum UserStatusEnum { 8 | Active = 'active', 9 | Blocked = 'blocked', 10 | Deactivated = 'deactivated' 11 | } 12 | -------------------------------------------------------------------------------- /tests/setup-tests.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' 2 | import { vi } from 'vitest' 3 | 4 | vi.mock('react-i18next', () => ({ 5 | Trans: ({ i18nKey }) => i18nKey, 6 | useTranslation: () => ({ 7 | i18n: { language: 'en' }, 8 | t: (str) => str 9 | }) 10 | })) 11 | -------------------------------------------------------------------------------- /src/components/enhanced-table/enhanced-table-header-cell/EnhancedTableHeaderCell.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | py: '25px' 4 | }, 5 | sortLabel: { 6 | '&.Mui-active .MuiTableSortLabel-icon': { 7 | color: 'primary.900' 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/containers/layout/app-header/AppHeader.styles.ts: -------------------------------------------------------------------------------- 1 | import { mainShadow } from '~/styles/app-theme/custom-shadows' 2 | 3 | export const styles = { 4 | appBar: { boxShadow: mainShadow, backgroundColor: 'basic.white' }, 5 | toolBar: { height: { xs: '56px', sm: '72px', md: '80px' } } 6 | } 7 | -------------------------------------------------------------------------------- /src/design-system/components/menu-item/MenuItem.constants.ts: -------------------------------------------------------------------------------- 1 | export enum MenuItemColorVariant { 2 | Default = 'default', 3 | Danger = 'danger', 4 | Secondary = 'secondary' 5 | } 6 | 7 | export enum MenuItemVariant { 8 | Default = 'default', 9 | Nested = 'nested' 10 | } 11 | -------------------------------------------------------------------------------- /src/types/cooperation/enums/cooperation.enums.ts: -------------------------------------------------------------------------------- 1 | export enum CooperationTabsEnum { 2 | Calendar = 'calendar', 3 | Activities = 'activities', 4 | Details = 'details' 5 | } 6 | 7 | export enum CompletionStatusEnum { 8 | Active = 'active', 9 | Completed = 'completed' 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "esModuleInterop": true 5 | }, 6 | "include": ["src", "vite.config.ts", "vitest.config.ts", "eslint.config.mjs"], 7 | "exclude": ["node_modules", "dist", "coverage", "tests", ".storybook"] 8 | } 9 | -------------------------------------------------------------------------------- /src/components/accordion-with-image/AccordionWithImage.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | feature: { 3 | overflow: 'auto' 4 | }, 5 | image: { 6 | width: '100%', 7 | maxWidth: '860px', 8 | overflow: 'auto', 9 | mr: { lg: '60px', xs: '24px' } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/constants/translations/en/icons-tooltip.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Language", 3 | "messages": "Messages", 4 | "bookmarks": "Offers", 5 | "notifications": "Notifications", 6 | "account": "Account", 7 | "menu": "Menu", 8 | "login": "Login", 9 | "logout": "Logout" 10 | } 11 | -------------------------------------------------------------------------------- /src/constants/translations/uk/bookmarked-offers-page.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Закладки", 3 | "search": "Пошук пропозиції або запиту", 4 | "notFound": { 5 | "description": "Не знайдено збережених пропозицій." 6 | }, 7 | "loadingError": "Не вдалося завантажити дані про пропозиції" 8 | } 9 | -------------------------------------------------------------------------------- /src/constants/translations/uk/icons-tooltip.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Мова", 3 | "messages": "Повідомлення", 4 | "bookmarks": "Пропозиції", 5 | "notifications": "Сповіщення", 6 | "account": "Профіль", 7 | "menu": "Меню", 8 | "login": "Увійти", 9 | "logout": "Вийти" 10 | } 11 | -------------------------------------------------------------------------------- /src/types/lesson/interfaces/lesson.interfaces.ts: -------------------------------------------------------------------------------- 1 | import type { Attachment } from '~/types' 2 | 3 | export interface LessonData { 4 | title: string 5 | description: string 6 | content: string 7 | attachments: Attachment[] 8 | category: string | null 9 | isDuplicate?: boolean 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/hash-scroll.ts: -------------------------------------------------------------------------------- 1 | export const scrollToHash = (path: string) => { 2 | setTimeout(() => { 3 | const elementWithId = document.getElementById( 4 | path.split('#').slice(1).join() 5 | ) 6 | elementWithId && elementWithId.scrollIntoView({ behavior: 'smooth' }) 7 | }, 1000) 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/is-updated-photo.ts: -------------------------------------------------------------------------------- 1 | import { EditProfilePhoto } from '~/types' 2 | 3 | export const isUpdatedPhoto = (photo: EditProfilePhoto): boolean => { 4 | return ( 5 | photo !== null && 6 | typeof photo === 'object' && 7 | 'name' in photo && 8 | 'src' in photo 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /src/types/my-cooperations/enums/myCooperations.enums.ts: -------------------------------------------------------------------------------- 1 | export enum ResourcesAvailabilityEnum { 2 | OpenAll = 'openAll', 3 | OpenManually = 'openManually' 4 | } 5 | 6 | export enum ResourceAvailabilityStatusEnum { 7 | Open = 'open', 8 | Closed = 'closed', 9 | OpenFrom = 'openFrom' 10 | } 11 | -------------------------------------------------------------------------------- /src/components/sidebar-image-grid/SidebarImageGrid.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | imageGrid: { 3 | display: 'grid', 4 | justifyContent: 'center', 5 | gridTemplateColumns: 'repeat(3, 88px)', 6 | gap: '8px' 7 | }, 8 | modalImage: { width: '100%', borderRadius: '5px' } 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/cooperation-details/add-course-modal-modal/AddCourseTemplateModal.constants.tsx: -------------------------------------------------------------------------------- 1 | import { CourseFilters } from '~/types' 2 | 3 | export const coursesDefaultFilters: CourseFilters = { 4 | title: '', 5 | category: '', 6 | subject: '', 7 | proficiencyLevel: [], 8 | page: 1 9 | } 10 | -------------------------------------------------------------------------------- /src/types/my-resources/types/myResources.types.ts: -------------------------------------------------------------------------------- 1 | import { ItemsWithCount, GetResourcesParams } from '~/types' 2 | 3 | export type ResourcesTableData = { 4 | response: ItemsWithCount 5 | getData: ( 6 | params?: GetResourcesParams 7 | ) => Promise | void | Promise> 8 | } 9 | -------------------------------------------------------------------------------- /src/types/notification/enums/notification.enums.ts: -------------------------------------------------------------------------------- 1 | export enum NotificationTypeEnums { 2 | NewCooperation = 'NEW_COOPERATION', 3 | NewComment = 'NEW_COMMENT', 4 | UpdateCooperation = 'UPDATE_COOPERATION', 5 | AcceptCooperation = 'ACCEPT_COOPERATION', 6 | CancelCooperation = 'CANCEL_COOPERATION' 7 | } 8 | -------------------------------------------------------------------------------- /src/types/my-offers/interfaces/myOffers.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { RequestParams } from '~/types/services/services.index' 2 | import { MyCooperationsFilters, Offer } from '~/types' 3 | 4 | export type GetMyOffersParams = Partial & 5 | RequestParams & { 6 | id: Offer['author']['_id'] 7 | } 8 | -------------------------------------------------------------------------------- /src/components/user-table/UserTable.styles.js: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | width: '90%', 4 | m: '0 auto', 5 | py: '25px' 6 | }, 7 | tabs: { 8 | display: 'flex', 9 | mt: '20px' 10 | }, 11 | header: { 12 | color: 'primary.900', 13 | typography: 'h4' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/types/snackbar/types/snackbar.types.ts: -------------------------------------------------------------------------------- 1 | import { Draft } from '@reduxjs/toolkit' 2 | import { TOptions } from 'i18next/typescript/options' 3 | 4 | export type ExtendedSnackbarMessage = { 5 | text: string 6 | options: Draft 7 | } 8 | 9 | export type SnackbarMessage = string | ExtendedSnackbarMessage 10 | -------------------------------------------------------------------------------- /src/utils/get-first-answer.ts: -------------------------------------------------------------------------------- 1 | import type { Result, Answer } from '~/types' 2 | 3 | export function getFirstAnswer( 4 | results: Result[] | undefined, 5 | questionText: string 6 | ): Answer | null { 7 | return ( 8 | results?.find((res) => res.question === questionText)?.answers?.[0] ?? null 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /src/pages/create-course/CreateCourse.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | buttons: { 3 | display: 'flex', 4 | justifyContent: 'flex-end', 5 | gap: '24px', 6 | mt: '32px' 7 | }, 8 | functionalButton: { 9 | display: 'flex', 10 | '& button': { 11 | width: '100%' 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/loader/Loader.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: (pageLoad: boolean) => ({ 3 | alignSelf: 'center', 4 | display: 'flex', 5 | justifyContent: 'center', 6 | alignItems: 'center', 7 | flex: pageLoad ? 1 : 0 8 | }), 9 | loader: { 10 | color: 'basic.black' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/title-to-camel-case.ts: -------------------------------------------------------------------------------- 1 | export function titleToCamel(title: string): string { 2 | return title 3 | .replace(/[^a-zA-Z0-9\s]/g, '') 4 | .toLowerCase() 5 | .split(' ') 6 | .map((word, index) => 7 | index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1) 8 | ) 9 | .join('') 10 | } 11 | -------------------------------------------------------------------------------- /src/redux/apiSlice.ts: -------------------------------------------------------------------------------- 1 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' 2 | 3 | export const appApi = createApi({ 4 | baseQuery: fetchBaseQuery({ 5 | baseUrl: import.meta.env.VITE_API_BASE_PATH, 6 | credentials: 'include' 7 | }), 8 | reducerPath: 'appApi', 9 | endpoints: () => ({}) 10 | }) 11 | -------------------------------------------------------------------------------- /src/components/app-rating-large/AppRatingLarge.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | display: 'flex', 4 | alignItems: 'center', 5 | flexDirection: 'column', 6 | columnGap: '4px', 7 | padding: '3px 6px', 8 | borderRadius: '5px' 9 | }, 10 | number: { display: 'flex', alignItems: 'center' } 11 | } 12 | -------------------------------------------------------------------------------- /src/constants/translations/en/error-messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileSize": "Maximum file size - {{size}}", 3 | "resultsNotFound": "Sorry, no results found", 4 | "tryAgainText": "We couldn’t find what you were searching for. Please try again or suggest a new {{name}} that you were looking for.", 5 | "buttonRequest": "Request a new {{name}}" 6 | } 7 | -------------------------------------------------------------------------------- /src/containers/category-dropdown/CategoryDropdown.constants.ts: -------------------------------------------------------------------------------- 1 | import { CategoryNameInterface } from '~/types' 2 | 3 | export const getOptionLabel = (item: CategoryNameInterface) => item.name 4 | 5 | export const isOptionEqualToValue = ( 6 | option: CategoryNameInterface, 7 | value: CategoryNameInterface 8 | ) => option?._id === value?._id 9 | -------------------------------------------------------------------------------- /src/pages/bookmarked-offers/BookmarkedOffers.constants.ts: -------------------------------------------------------------------------------- 1 | import { FindBookmarksFilters } from '~/types' 2 | 3 | export const defaultFilters: FindBookmarksFilters = { 4 | title: '', 5 | sort: 'createdAt', 6 | page: '1' 7 | } 8 | 9 | export const defaultResponse = { items: [], count: 0 } 10 | 11 | export const itemsPerPage = 6 12 | -------------------------------------------------------------------------------- /src/types/components/multi-accordion-with-title/multiAccordionWithTitle.interface.ts: -------------------------------------------------------------------------------- 1 | import { SxProps } from '@mui/material' 2 | import { AccordionSx } from '~/types' 3 | 4 | export interface MultiAccordionWithTitleSx extends AccordionSx { 5 | root?: SxProps 6 | title?: SxProps 7 | icon?: SxProps 8 | container?: SxProps 9 | } 10 | -------------------------------------------------------------------------------- /src/types/my-cooperations/enums/materialsAccess.enums.ts: -------------------------------------------------------------------------------- 1 | export enum CooperationMaterialsAccessEnum { 2 | NoAccess = 'noAccess', 3 | OneMonthAccess = 'oneMonthAccess', 4 | ThreeMonthsAccess = 'threeMonthsAccess', 5 | SixMonthsAccess = 'sixMonthsAccess', 6 | OneYearAccess = 'oneYearAccess', 7 | PermanentAccess = 'permanentAccess' 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/to-lower-array.ts: -------------------------------------------------------------------------------- 1 | export function toLowerArray( 2 | value: string | string[] | undefined | null 3 | ): string[] { 4 | if (Array.isArray(value)) { 5 | return value.map((v) => v.toLowerCase()) 6 | } 7 | 8 | if (typeof value === 'string') { 9 | return [value.toLowerCase()] 10 | } 11 | 12 | return [] 13 | } 14 | -------------------------------------------------------------------------------- /tests/unit/pages/new-quiz/NewQuiz.spec.jsx: -------------------------------------------------------------------------------- 1 | import { renderWithProviders } from '~tests/test-utils' 2 | 3 | import NewQuiz from '~/pages/new-quiz/NewQuiz' 4 | 5 | describe('NewQuiz', () => { 6 | it('should render NewQuiz page', () => { 7 | renderWithProviders() 8 | 9 | expect('New Quiz').toBeTruthy() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: 'npm' # See documentation for possible values 6 | directory: '/' # Location of package manifests 7 | schedule: 8 | interval: 'weekly' 9 | -------------------------------------------------------------------------------- /src/styles/app-theme/app.menu-list.ts: -------------------------------------------------------------------------------- 1 | import { mainShadow } from '~/styles/app-theme/custom-shadows' 2 | 3 | export const menuList = { 4 | styleOverrides: { 5 | root: { 6 | '& .MuiPaper-root': { 7 | boxShadow: mainShadow 8 | }, 9 | '& .MuiMenu-list': { 10 | padding: 0 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/isEqual.tsx: -------------------------------------------------------------------------------- 1 | export const isEqual = (x: T, y: T): boolean => { 2 | const ok = Object.keys, 3 | tx = typeof x, 4 | ty = typeof y 5 | return x && y && tx === 'object' && tx === ty 6 | ? ok(x).length === ok(y).length && 7 | ok(x).every((key) => isEqual(x[key as keyof T], y[key as keyof T])) 8 | : x === y 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/find-offer/filter-bar-menu/FilterBarMenu.styles.ts: -------------------------------------------------------------------------------- 1 | const container = { 2 | display: 'flex', 3 | alignItems: 'center', 4 | justifyContent: 'space-between' 5 | } 6 | 7 | export const styles = { 8 | container, 9 | selectContainer: { marginRight: '50px' }, 10 | mobileContainer: { ...container, justifyContent: 'center' } 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | export function debounce void>( 2 | func: T, 3 | wait: number 4 | ) { 5 | let timeout: ReturnType 6 | return function (...args: Parameters) { 7 | clearTimeout(timeout) 8 | timeout = setTimeout(() => { 9 | func(...args) 10 | }, wait) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/containers/my-courses/my-courses-container/MyCorsesCardsList.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | wrapper: { 3 | display: 'grid', 4 | gridTemplateColumns: { 5 | xs: 'minmax(0, 1fr)', 6 | md: 'repeat(2, minmax(0, 1fr))', 7 | lg: 'repeat(3, minmax(0, 1fr))' 8 | }, 9 | gap: '24px', 10 | mb: '24px' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/containers/proficiency-level-select/ProficiencyLevelSelect.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | menuProps: { 3 | PaperProps: { 4 | style: { 5 | maxHeight: '224px', 6 | width: '250px' 7 | } 8 | } 9 | }, 10 | inputColor: (hasError: boolean) => ({ 11 | color: hasError ? 'red' : 'basic.bismark' 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /src/design-system/scss/_reset.scss: -------------------------------------------------------------------------------- 1 | @use 'variables' as *; 2 | 3 | @use 'normalize.css' as *; 4 | 5 | body { 6 | font-family: $font-family-base; 7 | font-size: $font-size-base; 8 | 9 | // font-weight: $font-weight-base; 10 | // line-height: $line-height-base; //breaks current styles in the app 11 | // letter-spacing: $letter-spacing-base; 12 | } 13 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Git configs 2 | .git 3 | .gitignore 4 | README.md 5 | LICENSE 6 | CONTRIBUTING* 7 | 8 | # Node JS 9 | **/node_modules 10 | **/dist 11 | .storybook 12 | 13 | # Docker configs 14 | docker 15 | docker-compose.yaml 16 | 17 | # Other 18 | .vscode 19 | .husky 20 | sonar-project.properties 21 | azure-pipelines.yaml 22 | 23 | # Excludes 24 | !./docker/files/* -------------------------------------------------------------------------------- /src/constants/translations/uk/error-messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileSize": "Максимальний розмір файлу - {{size}}", 3 | "resultsNotFound": "Вибачте, результатів не знайдено", 4 | "tryAgainText": "Ми не змогли знайти те, що ви шукали. Спробуйте знову, або запропонуйте нов{{suffix}} {{name}}, як{{suffix}} ви шукаєте.", 5 | "buttonRequest": "Запитати нов{{suffix}} {{name}}" 6 | } 7 | -------------------------------------------------------------------------------- /src/containers/my-cooperations/cooperation-completion/CooperationCompletion.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | title: { 5 | typography: TypographyVariantEnum.H6, 6 | color: 'primary.500', 7 | mt: '32px' 8 | }, 9 | dropdown: { 10 | maxWidth: '216px', 11 | ml: '15px' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/title-with-description/TitleWithDescription.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | wrapper: { 3 | maxWidth: '1128px', 4 | margin: '0 auto', 5 | marginBottom: '32px', 6 | textAlign: 'center', 7 | zIndex: '1' 8 | }, 9 | title: { 10 | marginBottom: '16px' 11 | }, 12 | description: { 13 | marginBottom: '0px' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/containers/layout/chat-menu/ChatMenu.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | menuItem: (isDangerous: boolean) => ({ 5 | minWidth: '300px', 6 | p: '15px 24px', 7 | gap: '16px', 8 | color: isDangerous ? 'error.900' : 'primary.900', 9 | typography: TypographyVariantEnum.MidTitle 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/styles/app-theme/app.checkbox.ts: -------------------------------------------------------------------------------- 1 | import { checkboxClasses } from '@mui/material/Checkbox' 2 | import palette from './app.pallete' 3 | 4 | export const checkbox = { 5 | styleOverrides: { 6 | root: { 7 | color: palette.primary[300], 8 | [`&.${checkboxClasses.checked}`]: { 9 | color: palette.primary[700] 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/styles/app-theme/app.svgicon.ts: -------------------------------------------------------------------------------- 1 | import palette from './app.pallete' 2 | 3 | export const svgIcon = { 4 | styleOverrides: { 5 | colorPrimary: { 6 | color: palette.primary[900] 7 | }, 8 | colorSecondary: { 9 | color: palette.primary[700] 10 | }, 11 | colorDisabled: { 12 | color: palette.primary[100] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/types/categories/interfaces/categories.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { Categories } from '~/types/my-resources/myResources.index' 2 | import { RequestParams } from '~/types/services/types/services.types' 3 | 4 | export type CategoriesParams = RequestParams & { 5 | name: string 6 | } 7 | 8 | export interface CreateCategoriesParams { 9 | name: Categories['name'] 10 | } 11 | -------------------------------------------------------------------------------- /src/types/components/user-profile-info/userProfileInfo.interface.ts: -------------------------------------------------------------------------------- 1 | import { SxProps } from '@mui/material' 2 | 3 | export interface UserProfileInfoSx { 4 | root?: SxProps 5 | name?: SxProps 6 | avatar?: SxProps 7 | rating?: SxProps 8 | reviews?: SxProps 9 | date?: SxProps 10 | info?: SxProps 11 | interlocutorInfo?: SxProps 12 | myInfo?: SxProps 13 | } 14 | -------------------------------------------------------------------------------- /src/components/search-by-message/SearchByMessage.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | width: '100%', 4 | maxHeight: '56px', 5 | display: 'flex', 6 | justifyContent: 'space-between', 7 | py: '8px', 8 | backgroundColor: 'basic.white', 9 | borderRadius: ' 0 0 12px 12px' 10 | }, 11 | input: { 12 | width: '90%' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/exceptions/response-error.ts: -------------------------------------------------------------------------------- 1 | import { type ErrorResponse } from '~/types' 2 | 3 | class ResponseError extends Error { 4 | code?: string 5 | status?: number 6 | 7 | constructor({ code, message, status }: Partial) { 8 | super(message) 9 | 10 | this.code = code 11 | this.status = status 12 | } 13 | } 14 | 15 | export { ResponseError } 16 | -------------------------------------------------------------------------------- /src/styles/app-theme/app.table.ts: -------------------------------------------------------------------------------- 1 | import palette from './app.pallete' 2 | 3 | const table = { 4 | styleOverrides: { 5 | root: { 6 | '&.MuiTableRow-hover:hover': { 7 | backgroundColor: palette.basic.grey 8 | }, 9 | '& .MuiTableCell-root': { 10 | borderBottom: 'none' 11 | } 12 | } 13 | } 14 | } 15 | 16 | export default table 17 | -------------------------------------------------------------------------------- /src/styles/app-theme/app.tooltip.ts: -------------------------------------------------------------------------------- 1 | import palette from './app.pallete' 2 | 3 | const tooltip = { 4 | styleOverrides: { 5 | tooltip: { 6 | backgroundColor: palette.primary[900], 7 | fontSize: '11px', 8 | padding: '4px 8px' 9 | }, 10 | arrow: { 11 | color: palette.primary[900] 12 | } 13 | } 14 | } 15 | 16 | export default tooltip 17 | -------------------------------------------------------------------------------- /src/assets/img/guest-home-page/dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/app-text-area/AppTextArea.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { position: 'relative' }, 3 | textLengthRight: { position: 'absolute', right: 0, bottom: 0 }, 4 | textLength: { position: 'absolute', left: 0, bottom: 0 }, 5 | title: { typography: 'body2', color: 'primary.500', mr: '8px' }, 6 | textarea: { '& .MuiInputBase-root': { p: '12.5px 14px' } } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/enhanced-table/enhanced-table-toolbar/EnhancedTableToolbar.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | display: 'flex', 4 | justifyContent: 'space-between', 5 | width: '300px', 6 | alignItems: 'center', 7 | ml: '20px', 8 | py: '5px' 9 | }, 10 | selected: { 11 | flex: '1 1 100%', 12 | typography: 'subtitle2' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/icons-with-counter/IconsWithCounter.style.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | iconBox: { 3 | width: 'max-content', 4 | display: 'flex', 5 | alignItems: 'center' 6 | }, 7 | typography: { 8 | cursor: 'default', 9 | minWidth: 'max-content', 10 | my: '10px', 11 | width: '40px', 12 | height: '24px', 13 | padding: '3px' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/constants/translations/en/questions.json: -------------------------------------------------------------------------------- 1 | { 2 | "confirmation": "Do you want to exit this page?", 3 | "unsavedChanges": "Are you certain you want to close? Any unsaved changes will be lost", 4 | "discardChanges": "Are you certain you want to discard changes of your offer?", 5 | "goBackToProfile": "If you go back to your personal profile you will discard your recent changes." 6 | } 7 | -------------------------------------------------------------------------------- /src/constants/translations/en/titles.json: -------------------------------------------------------------------------------- 1 | { 2 | "confirmTitle": "Please Confirm", 3 | "discardOffer": "Discard offer changes?", 4 | "discardChanges": "Discard recent changes?", 5 | "confirmCooperationClosing": "Confirmation of cooperation closing", 6 | "acceptCooperationClosing": "Cooperation closing process", 7 | "cooperationClosureDeclined": "Cooperation closure declined" 8 | } 9 | -------------------------------------------------------------------------------- /src/constants/translations/uk/questions.json: -------------------------------------------------------------------------------- 1 | { 2 | "confirmation": "Ви впевнені, що хочете вийти?", 3 | "unsavedChanges": "Ви впевнені, що хочете закрити? Усі незбережені зміни буде втрачено", 4 | "discardChanges": "Ви впевнені, що бажаєте відхилити зміни вашої пропозиції?", 5 | "goBackToProfile": "Якщо ви повернетеся до свого особистого профілю, ви відхилите останні зміни." 6 | } 7 | -------------------------------------------------------------------------------- /src/containers/find-offer/create-subject/CreateSubject.constants.ts: -------------------------------------------------------------------------------- 1 | import { emptyField } from '~/utils/validations/common' 2 | 3 | export const validations = { 4 | category: (value: string | null) => 5 | emptyField({ value, emptyMessage: 'common.errorMessages.category' }), 6 | name: (value: string) => 7 | emptyField({ value, emptyMessage: 'common.errorMessages.subject' }) 8 | } 9 | -------------------------------------------------------------------------------- /src/containers/find-offer/offer-filter-block/OfferFilterBlock.constants.ts: -------------------------------------------------------------------------------- 1 | export const sortTranslationKeys = [ 2 | { title: 'findOffers.sortTitles.newest', value: 'createdAt' }, 3 | { title: 'findOffers.sortTitles.rating', value: 'rating' }, 4 | { title: 'findOffers.sortTitles.priceAsc', value: 'priceAsc' }, 5 | { title: 'findOffers.sortTitles.priceDesc', value: 'priceDesc' } 6 | ] 7 | -------------------------------------------------------------------------------- /src/design-system/scss/styles.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | 3 | @forward 'mixins'; 4 | @forward 'functions'; 5 | @forward 'variables'; 6 | @forward 'root'; 7 | @forward 'reset'; 8 | 9 | @forward 'typography'; 10 | 11 | @forward 'utilities/index'; 12 | 13 | @import 'https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,100..900&family=Rubik:wght@300..900&display=swap'; 14 | -------------------------------------------------------------------------------- /src/utils/error-with-message.tsx: -------------------------------------------------------------------------------- 1 | export const getErrorMessage = (message: string) => { 2 | const errorsList = message 3 | .slice(message.indexOf(':') + 1) 4 | .trim() 5 | .split(',') 6 | const errorListWithoutDuplications = new Set( 7 | errorsList.map((error) => error.split(':')[1]) 8 | ) 9 | return Array.from(errorListWithoutDuplications).join(',') 10 | } 11 | -------------------------------------------------------------------------------- /docker/files/sshd_config: -------------------------------------------------------------------------------- 1 | Port 2222 2 | ListenAddress 0.0.0.0 3 | LoginGraceTime 180 4 | X11Forwarding yes 5 | Ciphers aes128-cbc,3des-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr 6 | MACs hmac-sha1,hmac-sha1-96 7 | StrictModes yes 8 | SyslogFacility DAEMON 9 | PasswordAuthentication yes 10 | PermitEmptyPasswords no 11 | PermitRootLogin yes 12 | Subsystem sftp internal-sftp -------------------------------------------------------------------------------- /src/components/direction-link/DirectionLink.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | showAllOffers: { 3 | display: 'flex', 4 | justifyContent: 'end', 5 | alignItems: 'center', 6 | columnGap: { sm: '10px' }, 7 | color: 'primary.500', 8 | fontWeight: '500', 9 | textDecoration: 'none', 10 | m: { xs: '8px 0', sm: '12px 24px', md: '20px 40px' } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/input-with-icon/InputWithIcon.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | display: 'flex', 4 | alignItems: 'center', 5 | px: { xs: '8px', sm: '14px' }, 6 | py: { xs: '4px', sm: 0 }, 7 | gap: '12px', 8 | minHeight: { xs: '40px' } 9 | }, 10 | input: { 11 | flex: 1 12 | }, 13 | clearIcon: { 14 | color: 'primary.700' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/constants/translations/uk/change-confirm.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Зміни ресурсу можуть вплинути на курси та співпрацю", 3 | "descriptionResource": "Ви збираєтеся внести зміни до ресурсу", 4 | "description": "Ці зміни вплинуть на такі курси та співпраці:", 5 | "backButton": "Назад", 6 | "confirmButton": "Підтвердити", 7 | "course": "Курс", 8 | "cooperation": "Співпраця" 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/category-dropdown/CategoryDropdown.styles.tsx: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | addButtonNoOptions: { 3 | pl: '0' 4 | }, 5 | labelCategory: { 6 | color: 'primary.600', 7 | maxWidth: '464px', 8 | width: '100%', 9 | display: 'flex', 10 | flexDirection: 'column', 11 | gap: '4px' 12 | }, 13 | divider: { 14 | color: 'primary.300' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/plugins/axiosClient.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from 'axios' 2 | import qs from 'qs' 3 | 4 | export const axiosClient: AxiosInstance = axios.create({ 5 | withCredentials: true, 6 | baseURL: import.meta.env.VITE_API_BASE_PATH, 7 | allowAbsoluteUrls: false, 8 | paramsSerializer: (params) => { 9 | return encodeURI(qs.stringify(params, { encode: false })) 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly VITE_API_BASE_PATH: string 5 | readonly VITE_GMAIL_CLIENT_ID: string 6 | readonly VITE_APP_IMG_URL: string 7 | readonly VITE_APP_IMG_USER_URL: string 8 | readonly VITE_APP_TINY_MCE_API_KEY: string 9 | } 10 | 11 | interface ImportMeta { 12 | readonly env: ImportMetaEnv 13 | } 14 | -------------------------------------------------------------------------------- /src/components/app-toolbar/AppToolbar.styles.ts: -------------------------------------------------------------------------------- 1 | import { commonShadow } from '~/styles/app-theme/custom-shadows' 2 | 3 | export const styles = { 4 | container: { 5 | backgroundColor: 'basic.white', 6 | display: 'flex', 7 | alignItems: 'center', 8 | boxShadow: commonShadow, 9 | mb: { xs: '20px', sm: '50px' }, 10 | p: { xs: '10px 24px', sm: '25px 45px' } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/download-button/DownloadButton.styles.ts: -------------------------------------------------------------------------------- 1 | import palette from '~/styles/app-theme/app.pallete' 2 | 3 | export const styles = { 4 | downloadIcon: (loading: boolean) => ({ 5 | color: loading ? palette.basic.blueGray : palette.basic.black, 6 | fontWeight: loading ? 500 : 600, 7 | fontSize: '15px' 8 | }), 9 | iconImage: { 10 | marginTop: '-3px' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/constants/translations/en/subjects-page.json: -------------------------------------------------------------------------------- 1 | { 2 | "subject": "subject", 3 | "subjects": { 4 | "title": "{{category}} Subjects", 5 | "description": "Explore subjects you're passionate about.", 6 | "showAllOffers": "Show offers", 7 | "backToAllCategories": "Categories", 8 | "searchLabel": "What would you like to learn?", 9 | "viewMore": "View more" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/constants/translations/uk/titles.json: -------------------------------------------------------------------------------- 1 | { 2 | "confirmTitle": "Підтвердіть вихід", 3 | "discardOffer": "Відхилити зміни пропозиції?", 4 | "discardChanges": "Зберегти останні зміни?", 5 | "confirmCooperationClosing": "Підтвердження припинення cпівпраці", 6 | "acceptCooperationClosing": "Процес припинення співпраці", 7 | "cooperationClosureDeclined": "Відмова від припинення співпраці" 8 | } 9 | -------------------------------------------------------------------------------- /src/containers/chat/chat-date/ChatDate.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | container: { px: '45px' }, 5 | divider: { '&::before, &::after': { borderColor: 'primary.200' } }, 6 | date: { 7 | mx: '10px', 8 | userSelect: 'none', 9 | typography: TypographyVariantEnum.Caption, 10 | color: 'primary.500' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/pages/my-courses/MyCourses.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | title: { 5 | typography: TypographyVariantEnum.H4, 6 | mb: '40px' 7 | }, 8 | divider: { 9 | display: 'flex', 10 | borderBottom: '1px solid', 11 | borderColor: 'primary.100', 12 | mb: '32px' 13 | }, 14 | pagination: { mt: 'auto' } 15 | } 16 | -------------------------------------------------------------------------------- /src/constants/translations/en/filters.json: -------------------------------------------------------------------------------- 1 | { 2 | "sortBy": { 3 | "sortByTitle": "Sort by:", 4 | "sortByFields": { 5 | "tutorRating": "Tutor rating", 6 | "ascendingPrice": "Ascending price", 7 | "descendingPrice": "Descending price", 8 | "creationTime": "Creation time" 9 | } 10 | }, 11 | "filtersListTitle": "Filters", 12 | "filterLevelsLabel": "Levels" 13 | } 14 | -------------------------------------------------------------------------------- /src/components/app-drawer/AppDrawer.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | width: { xs: '100%', sm: 'auto' }, 4 | p: '24px 36px', 5 | boxSizing: 'border-box' 6 | }, 7 | closeButton: { 8 | position: 'absolute', 9 | right: '16px', 10 | top: '22px', 11 | color: 'primary.700' 12 | }, 13 | closeIcon: { 14 | typography: { xs: 'h6', sm: 'h5' } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/constants/translations/uk/lesson.json: -------------------------------------------------------------------------------- 1 | { 2 | "labels": { 3 | "title": "Без заголовка", 4 | "description": "Опис...", 5 | "attachments": "Вкладення" 6 | }, 7 | "successAddedLesson": "Урок успішно створено", 8 | "successEditedLesson": "Урок успішно змінено", 9 | "content": "Зміст", 10 | "attachments": "Вкладення", 11 | "fileEditorPlaceholder": "Створіть свій файл тут..." 12 | } 13 | -------------------------------------------------------------------------------- /src/hooks/use-redux.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-restricted-imports */ 2 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' 3 | import { RootState, AppDispatch } from '~/redux/store' 4 | 5 | type DispatchFunc = () => AppDispatch 6 | 7 | export const useAppDispatch: DispatchFunc = useDispatch 8 | export const useAppSelector: TypedUseSelectorHook = useSelector 9 | -------------------------------------------------------------------------------- /src/constants/translations/en/cookie-consent-banner.json: -------------------------------------------------------------------------------- 1 | { 2 | "notice": "We would like to inform you that we place cookies on your device that remember your choices on SpaceToStudy, such as your country and SpaceToStudy account. We also place cookies that help us to customize the website. For more information, please read our <0>Cookie Policy and our <1>Privacy Policy.", 3 | "acceptButton": "I accept" 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/get-validated-hex-color.tsx: -------------------------------------------------------------------------------- 1 | import palette from '~/styles/app-theme/app.pallete' 2 | 3 | const HEX_COLOR_PATTERN = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i 4 | 5 | export const getValidatedHexColor = ( 6 | color: string, 7 | fallbackColor: string = palette.success[500] 8 | ): string => { 9 | const isValidHex = HEX_COLOR_PATTERN.test(color) 10 | return isValidHex ? color : fallbackColor 11 | } 12 | -------------------------------------------------------------------------------- /src/components/icon-title-description/IconTitleDescription.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | textAlign: 'center', 4 | color: 'primary.900' 5 | }, 6 | icon: { 7 | svg: { width: '35px', height: '35px' } 8 | }, 9 | titleWithDescription: { 10 | title: { 11 | typography: 'h6' 12 | }, 13 | description: { 14 | typography: 'body1' 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/languages-list-with-icon/LanguagesListWithIcon.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | languagesContainer: { 3 | display: 'flex', 4 | alignItems: 'center', 5 | color: 'primary.400', 6 | gap: '8px', 7 | my: '4px' 8 | }, 9 | languageIcon: { 10 | typography: 'midTitle' 11 | }, 12 | languages: { 13 | typography: 'body2', 14 | lineHeight: 'inherit' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/constants/translations/en/change-confirm.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Changes to resource may affect courses and cooperation", 3 | "descriptionResource": "You are about to make changes to the resource", 4 | "description": "These changes will affect the following courses & cooperation:", 5 | "backButton": "Back", 6 | "confirmButton": "Confirm", 7 | "course": "Course", 8 | "cooperation": "Cooperation" 9 | } 10 | -------------------------------------------------------------------------------- /src/constants/translations/uk/filters.json: -------------------------------------------------------------------------------- 1 | { 2 | "sortBy": { 3 | "sortByTitle": "Сортувати за:", 4 | "sortByFields": { 5 | "tutorRating": "Рейтингом викладача", 6 | "ascendingPrice": "Зростанням ціни", 7 | "descendingPrice": "Спаданням ціни", 8 | "creationTime": "Часом створення" 9 | } 10 | }, 11 | "filtersListTitle": "Фільтри", 12 | "filterLevelsLabel": "Рівні" 13 | } 14 | -------------------------------------------------------------------------------- /src/constants/translations/uk/subjects-page.json: -------------------------------------------------------------------------------- 1 | { 2 | "subject": "предмет", 3 | "subjects": { 4 | "title": "{{category}} Предмети", 5 | "description": "Досліджуйте предмети, якими ви захоплюєтеся.", 6 | "showAllOffers": "Показати пропозиції", 7 | "backToAllCategories": "До категорій", 8 | "searchLabel": "Чого б ви хотіли навчитися?", 9 | "viewMore": "Переглянути більше" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/containers/guest-home-page/how-it-works/HowItWorks.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | display: 'flex', 4 | alignItems: 'center', 5 | justifyContent: 'center', 6 | flexDirection: 'column' 7 | }, 8 | switch: { 9 | display: 'flex', 10 | mb: '45px', 11 | flexDirection: { sm: 'row', xs: 'column' } 12 | }, 13 | title: { mb: '32px', typography: 'h3' } 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/validations/validations.constants.ts: -------------------------------------------------------------------------------- 1 | export const validationPatterns = { 2 | name: /^[a-zа-яєії' -]+$/i, 3 | number: /^-?(?:\d+|\d*\.\d+)(?:[eE][+-]?\d+)?$/, 4 | email: 5 | /^[a-zA-Z0-9]+([._%+-][a-zA-Z0-9]+)*@[a-zA-Z0-9]+([.-][a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/, 6 | passwordComplex: 7 | /^(?=.*[a-zа-яєії])(?=.*\d)(?=.*[!@#$%^&*()_+[\]{};':"\\|,.<>/?-]).+$/i, 8 | passwordValid: /^\S+$/i 9 | } 10 | -------------------------------------------------------------------------------- /src/constants/translations/en/question-page.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Question", 3 | "answer": "Answer", 4 | "questionType": { 5 | "multipleChoice": "Multiple Choice", 6 | "openAnswer": "Open Answer", 7 | "oneAnswer": "One Answer" 8 | }, 9 | "addNewOne": "Add new one", 10 | "writeYourAnswer": "Write your answer", 11 | "chooseCategory": "Choose the category:", 12 | "untitled": "Untitled" 13 | } 14 | -------------------------------------------------------------------------------- /src/containers/layout/sidebar/Sidebar.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | list: { 5 | mt: '16px', 6 | p: 0, 7 | width: '248px' 8 | }, 9 | listTitle: { 10 | py: '10px', 11 | textDecoration: 'none', 12 | color: 'primary.900', 13 | typography: TypographyVariantEnum.MidTitle 14 | }, 15 | listItem: { 16 | px: 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/constants/translations/en/lesson.json: -------------------------------------------------------------------------------- 1 | { 2 | "labels": { 3 | "title": "Untitled", 4 | "description": "Description...", 5 | "attachments": "Attachments" 6 | }, 7 | "successAddedLesson": "Lesson was successfully created", 8 | "successEditedLesson": "Lesson was successfully edited", 9 | "content": "Content", 10 | "attachments": "Attachments", 11 | "fileEditorPlaceholder": "Create your file here..." 12 | } 13 | -------------------------------------------------------------------------------- /src/types/utils/utils-interfaces/utils.interface.ts: -------------------------------------------------------------------------------- 1 | export interface FormatedDate { 2 | date: Date | string 3 | locales?: string 4 | options?: Intl.DateTimeFormatOptions 5 | isCurrentDayHours?: boolean 6 | includeOrdinal?: boolean 7 | } 8 | 9 | export interface ConvertedSize { 10 | size: string 11 | unit: string 12 | } 13 | 14 | export interface GroupedByDateItems { 15 | date: string 16 | items: T[] 17 | } 18 | -------------------------------------------------------------------------------- /src/types/components/app-breadcrumbs/appBreadcrumbs.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { Params } from 'react-router-dom' 2 | import { Crumbfunc } from './appBreadcrumbs.types' 3 | 4 | export interface Crumb { 5 | name: string 6 | path?: string 7 | } 8 | 9 | export interface Matches { 10 | id: string 11 | pathname: string 12 | params: Params 13 | data: unknown 14 | handle: { 15 | crumb: Crumb | Crumbfunc 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/app-carousel/AppCarousel.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | arrowsWrapper: { 3 | width: '100%', 4 | position: 'absolute', 5 | top: '50%', 6 | transform: '-translateY(50%)', 7 | display: 'flex', 8 | justifyContent: 'space-between', 9 | alignItems: 'center' 10 | }, 11 | carouselContainer: { 12 | position: 'relative' 13 | }, 14 | childrenWrapper: { display: 'flex', gap: '1.5rem' } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/app-pagination/AppPagination.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | wrapper: { 3 | display: 'flex', 4 | justifyContent: 'center', 5 | alignItems: 'center', 6 | '& .MuiPaginationItem-root.Mui-selected': { 7 | backgroundColor: 'primary.900', 8 | color: 'primary.50', 9 | '&:hover': { 10 | backgroundColor: 'primary.900', 11 | color: 'primary.50' 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/containers/guest-home-page/who-we-are/WhoWeAre.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | flexDirection: 'column' 4 | }, 5 | titleWithDescription: { 6 | wrapper: { 7 | mb: '32px', 8 | textAlign: 'center' 9 | }, 10 | title: { 11 | typography: { md: 'h3', xs: 'h4' }, 12 | mb: '16px' 13 | }, 14 | description: { 15 | typography: { xs: 'subtitle1' } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/containers/questions-list/QuestionsList.styles.ts: -------------------------------------------------------------------------------- 1 | import palette from '~/styles/app-theme/app.pallete' 2 | 3 | export const styles = { 4 | question: (isDragging: boolean) => ({ 5 | mb: '32px', 6 | backgroundColor: 'basic.white', 7 | borderRadius: '6px', 8 | ...(isDragging && { 9 | boxShadow: `0px 3px 16px 2px ${palette.primary[300]}`, 10 | border: `2px solid ${palette.primary[300]}` 11 | }) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/tutor-home/TutorHome.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | modal: { 3 | maxHeight: { sm: '652px' }, 4 | height: '100%', 5 | maxWidth: '1130px', 6 | width: '100%' 7 | }, 8 | 9 | scheduleAndCalendarContainer: { 10 | display: 'flex', 11 | flexDirection: { xs: 'column', lg: 'row' }, 12 | gap: { xs: '0px', sm: '30px' }, 13 | justifyContent: 'space-between', 14 | alignItems: 'center' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/status-chip/StatusChip.styles.ts: -------------------------------------------------------------------------------- 1 | import { StatusEnum } from '~/types' 2 | import { type ChipColor } from '~scss-components/chip/Chip' 3 | 4 | export const statusColors: Record = { 5 | [StatusEnum.Pending]: 'blue', 6 | [StatusEnum.Active]: 'green', 7 | [StatusEnum.Closed]: 'blue-gray', 8 | [StatusEnum.Draft]: 'blue', 9 | [StatusEnum.NeedAction]: 'red', 10 | [StatusEnum.RequestToClose]: 'yellow' 11 | } 12 | -------------------------------------------------------------------------------- /src/components/typing-block/TypingBlock.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | wrapper: { 3 | display: 'flex', 4 | flexDirection: 'row', 5 | overflow: 'hidden' 6 | }, 7 | avatar: { 8 | width: '44px', 9 | height: '44px', 10 | '&:hover': { transform: 'scale(1.1)' } 11 | }, 12 | typingAnimation: { 13 | width: '178px', 14 | height: '116px', 15 | marginTop: '-40px', 16 | marginLeft: '-42px' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/containers/guest-home-page/notification-modal/NotificationModal.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | display: 'flex', 4 | flexDirection: 'column', 5 | alignItems: 'center', 6 | maxWidth: '620px', 7 | m: { xs: '24px', sm: '48px', md: '80px' } 8 | }, 9 | imgTitleDesc: { 10 | root: { textAlign: 'center', mb: '18px' }, 11 | titleWithDescription: { title: { typography: 'h5', mb: '12px' } } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/unit/components/loader/Loader.spec.jsx: -------------------------------------------------------------------------------- 1 | import { screen } from '@testing-library/react' 2 | 3 | import Loader from '~/components/loader/Loader' 4 | import { renderWithProviders } from '~tests/test-utils' 5 | 6 | describe('Loader test', () => { 7 | it('should render loader', () => { 8 | renderWithProviders() 9 | const loader = screen.getByTestId('loader') 10 | 11 | expect(loader).toBeInTheDocument() 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/constants/translations/uk/question-page.json: -------------------------------------------------------------------------------- 1 | { 2 | "question": "Питання", 3 | "answer": "Відповідь", 4 | "questionType": { 5 | "multipleChoice": "Множинний вибір", 6 | "openAnswer": "Відкрита відповідь", 7 | "oneAnswer": "Одна відповідь" 8 | }, 9 | "addNewOne": "Добавити ще одне питання", 10 | "writeYourAnswer": "Напишіть свою відповідь", 11 | "chooseCategory": "Виберіть категорію:", 12 | "untitled": "Без заголовку" 13 | } 14 | -------------------------------------------------------------------------------- /src/containers/my-quizzes/view-quiz-container/ViewQuizContainer.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | titleWithDescription: { 5 | title: { typography: TypographyVariantEnum.H5 }, 6 | description: { 7 | typography: TypographyVariantEnum.Body1, 8 | color: 'primary.600' 9 | } 10 | }, 11 | questionWrapper: { 12 | question: { 13 | px: '0' 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/containers/student-home-page/faq/Faq.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | flexDirection: 'column', 4 | mb: 0, 5 | pb: 10, 6 | pt: 6 7 | }, 8 | titleWithDescription: { 9 | wrapper: { 10 | textAlign: 'center', 11 | mb: '32px' 12 | }, 13 | title: { 14 | typography: { xs: 'h4' } 15 | }, 16 | description: { 17 | typography: { xs: 'subtitle1' } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/enhanced-table/date-filter/DateFilter.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | datePickers: { 3 | display: 'flex', 4 | transform: 'translateY(-30px)', 5 | visibility: 'hidden', 6 | '& .MuiFormControl-root.MuiTextField-root:nth-of-type(2)': { 7 | transform: 'translateX(5px)' 8 | } 9 | }, 10 | datePicker: { 11 | width: '0px', 12 | height: '0px' 13 | }, 14 | input: { 15 | width: '70%' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/containers/layout/account-menu/AccountMenu.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | import palette from '~/styles/app-theme/app.pallete' 3 | 4 | export const styles = { 5 | menuItem: { 6 | width: '200px', 7 | p: '16px', 8 | typography: TypographyVariantEnum.MidTitle 9 | }, 10 | logoutIcon: { 11 | pr: '12px' 12 | }, 13 | logoutItem: { 14 | borderTop: `1px solid ${palette.basic.lightGray}` 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/styles/app-theme/custom-shadows.ts: -------------------------------------------------------------------------------- 1 | export const mainShadow = 2 | '0px 5px 6px -3px rgba(144, 164, 174, 0.2), 0px 9px 12px 1px rgba(144, 164, 174,' + 3 | ' 0.14), 0px 3px 16px 2px rgba(144, 164, 174, 0.12)' 4 | 5 | export const commonShadow = '0px 3px 16px 2px rgba(144, 164, 174, 0.12)' 6 | 7 | export const commonHoverShadow = '0px 3px 16px 2px rgba(144, 164, 174, 0.56)' 8 | 9 | export const smallHoverShadow = '0 3px 1px rgba(144, 164, 174, 0.56)' 10 | -------------------------------------------------------------------------------- /src/types/notes/interfaces/notes.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { CommonEntityFields, Cooperation, UserResponse } from '~/types' 2 | 3 | export interface CreateOrUpdateNoteParams { 4 | isPrivate: boolean 5 | text: string 6 | } 7 | 8 | export interface NoteResponse extends CommonEntityFields { 9 | author: Pick 10 | text: string 11 | isPrivate: boolean 12 | cooperation: Pick 13 | } 14 | -------------------------------------------------------------------------------- /src/components/img-title-description/ImgTitleDescription.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { m: { xs: '100px 10px', sm: '56px', md: '80px' } }, 3 | img: { display: 'block', margin: '0 auto' }, 4 | titleWithDescription: { 5 | wrapper: { 6 | maxWidth: '630px', 7 | textAlign: 'center' 8 | }, 9 | title: { 10 | typography: 'h5' 11 | }, 12 | description: { 13 | typography: 'subtitile' 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/constants/translations/uk/cookie-consent-banner.json: -------------------------------------------------------------------------------- 1 | { 2 | "notice": "Ми хочемо повідомити, що ми розміщуємо на Вашому пристрої файли cookie, які запам'ятовують Ваш вибір на SpaceToStudy, наприклад Вашу країну та обліковий запис SpaceToStudy. Ми також розміщуємо файли cookie, які допомогають нам запускати веб сайт. Для більшої інформації ознайомтеся з нашою <0>Політикою Cookie та нашою <1>Політикою конфіденційності.", 3 | "acceptButton": "Я погоджуюся" 4 | } 5 | -------------------------------------------------------------------------------- /src/containers/edit-profile/professional-info-tab/add-professional-category-modal/AddProfessionalCategoryModal.constants.ts: -------------------------------------------------------------------------------- 1 | import { ProfessionalCategory, SubjectNameInterface } from '~/types' 2 | 3 | export const professionalSubjectTemplate: SubjectNameInterface = { 4 | _id: '', 5 | name: '' 6 | } 7 | 8 | export const userMainSubjectTemplate: ProfessionalCategory = { 9 | category: { _id: '', name: '' }, 10 | subjects: [professionalSubjectTemplate] 11 | } 12 | -------------------------------------------------------------------------------- /src/containers/find-course/courses-filter-bar/CorseFilterBar.constants.ts: -------------------------------------------------------------------------------- 1 | import { SortEnum } from '~/types' 2 | 3 | export const sortTranslationKeys = [ 4 | { 5 | title: 'myCoursesPage.sortTitles.newest', 6 | value: `updatedAt ${SortEnum.Desc}` 7 | }, 8 | { 9 | title: 'myCoursesPage.sortTitles.oldest', 10 | value: `updatedAt ${SortEnum.Asc}` 11 | } 12 | ] 13 | 14 | export const initialSort = { order: SortEnum.Desc, orderBy: 'updatedAt' } 15 | -------------------------------------------------------------------------------- /src/containers/layout/notifications-menu/NotificationsMenu.constants.ts: -------------------------------------------------------------------------------- 1 | import { authRoutes } from '~/router/constants/authRoutes' 2 | 3 | export const liksByType = { 4 | NEW_COOPERATION: authRoutes.cooperationDetails.path, 5 | NEW_COMMENT: authRoutes.userProfile.path, 6 | UPDATE_COOPERATION: authRoutes.cooperationDetails.path, 7 | ACCEPT_COOPERATION: authRoutes.cooperationDetails.path, 8 | CANCEL_COOPERATION: authRoutes.cooperationDetails.path 9 | } 10 | -------------------------------------------------------------------------------- /src/hooks/use-droppable.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | 3 | const useDroppable = () => { 4 | const [enabled, setEnabled] = useState(false) 5 | 6 | useEffect(() => { 7 | const animation = requestAnimationFrame(() => setEnabled(true)) 8 | 9 | return () => { 10 | cancelAnimationFrame(animation) 11 | setEnabled(false) 12 | } 13 | }, []) 14 | 15 | return { enabled } 16 | } 17 | 18 | export default useDroppable 19 | -------------------------------------------------------------------------------- /src/constants/translations/uk/email-modals.json: -------------------------------------------------------------------------------- 1 | { 2 | "emailConfirm": "Ваш email успішно перевірено!", 3 | "emailNotConfirm": "Ваш email не перевірено!", 4 | "emailAlreadyConfirm": "Ваш email вже перевірено!", 5 | "emailReject": { 6 | "badToken": "Підтвердження або недійсне, або термін його дії минув. Будь ласка, зареєструйтеся ще раз і підтвердьте свою електронну адресу.", 7 | "alreadyConfirmed": "Електронну адресу користувача вже підтверджено." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/app-content/AppContent.styles.ts: -------------------------------------------------------------------------------- 1 | const layout = { 2 | display: 'flex', 3 | flexDirection: 'column' 4 | } 5 | 6 | export const styles = { 7 | root: { 8 | height: '100vh', 9 | color: 'primary.900', 10 | backgroundColor: 'backgroundColor', 11 | ...layout 12 | }, 13 | content: { 14 | overflowY: 'auto', 15 | flex: 1, 16 | '&::-webkit-scrollbar-track': { 17 | marginBottom: '-4px' 18 | }, 19 | ...layout 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/hooks/use-translate.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | import { useTranslation } from 'react-i18next' 3 | import { translateData } from '~/utils/translate-data' 4 | 5 | const useTranslate = (entity: string) => { 6 | const { t } = useTranslation() 7 | 8 | return useCallback( 9 | (data: T[]) => { 10 | return translateData(data, entity, t) 11 | }, 12 | [entity, t] 13 | ) 14 | } 15 | 16 | export default useTranslate 17 | -------------------------------------------------------------------------------- /src/containers/add-documents/AddDocuments.styles.ts: -------------------------------------------------------------------------------- 1 | import { fadeAnimation } from '~/styles/app-theme/custom-animations' 2 | 3 | export const styles = { 4 | root: { 5 | display: 'flex', 6 | justifyContent: 'space-between', 7 | ...fadeAnimation 8 | }, 9 | fileUpload: { 10 | root: { 11 | border: 'none', 12 | Label: { 13 | py: '12px' 14 | } 15 | }, 16 | button: { 17 | backgroundColor: 'primary.50' 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/unit/pages/admin-home/AdminHome.spec.jsx: -------------------------------------------------------------------------------- 1 | import { screen } from '@testing-library/react' 2 | import { renderWithProviders } from '~tests/test-utils' 3 | import AdminHome from '~/pages/admin-home/AdminHome' 4 | 5 | describe('AdminHome component', () => { 6 | beforeEach(() => { 7 | renderWithProviders() 8 | }) 9 | 10 | it('Should render AdminHome placeholder', () => { 11 | expect(screen.getByText('Hello Admin!')).toBeInTheDocument() 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/components/offer-card/offer-actions/OfferActions.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | containerTop: { 3 | minWidth: '150px', 4 | position: 'relative', 5 | m: '16px 0' 6 | }, 7 | bookmarkButton: { 8 | position: 'absolute', 9 | top: '-42px', 10 | right: '-12px', 11 | color: 'basic.black' 12 | }, 13 | buttons: { 14 | display: 'flex', 15 | flexDirection: 'column', 16 | gap: '16px' 17 | }, 18 | button: { whiteSpace: 'nowrap' } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/tab-navigation/TabNavigation.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | tabs: { 3 | display: 'flex', 4 | borderBottom: '1px solid', 5 | borderColor: 'primary.100', 6 | mb: '24px', 7 | overflowX: 'auto', 8 | '&::-webkit-scrollbar': { display: 'none' } 9 | }, 10 | titleBox: { 11 | display: 'flex', 12 | alignItems: 'center', 13 | gap: '16px', 14 | '& > svg': { 15 | width: '16px', 16 | height: '16px' 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/download-file.ts: -------------------------------------------------------------------------------- 1 | export async function downloadFile(response: Promise, fileName: string) { 2 | const blobResponse = await response 3 | const url = window.URL.createObjectURL(blobResponse) 4 | const link = document.createElement('a') 5 | link.href = url 6 | link.setAttribute('download', fileName) 7 | document.body.appendChild(link) 8 | link.click() 9 | document.body.removeChild(link) 10 | 11 | setTimeout(() => window.URL.revokeObjectURL(url), 100) 12 | } 13 | -------------------------------------------------------------------------------- /tests/unit/components/file-editor/FileEditor.spec.jsx: -------------------------------------------------------------------------------- 1 | import { queryByAttribute, render } from '@testing-library/react' 2 | import FileEditor from '~/components/file-editor/FileEditor.tsx' 3 | 4 | describe('Test file editor', () => { 5 | it('should render file editor', () => { 6 | const { container } = render() 7 | 8 | const editorElement = queryByAttribute('id', container, /tiny-react/) 9 | 10 | expect(editorElement).toBeInTheDocument() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/unit/components/layout/AppHeader.spec.jsx: -------------------------------------------------------------------------------- 1 | import { screen } from '@testing-library/react' 2 | import { renderWithProviders } from '~tests/test-utils' 3 | import AppHeader from '~/containers/layout/app-header/AppHeader' 4 | 5 | describe('AppHeader layout component test', () => { 6 | renderWithProviders() 7 | it('should render toolbar', () => { 8 | const toolbar = screen.getByTestId('toolbar') 9 | 10 | expect(toolbar).toBeInTheDocument() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/constants/translations/en/email-modals.json: -------------------------------------------------------------------------------- 1 | { 2 | "emailConfirm": "Your email has been successfully verified!", 3 | "emailNotConfirm": "Your email address has not been verified!", 4 | "emailAlreadyConfirm": "Your email address has already been verified!", 5 | "emailReject": { 6 | "badToken": "The confirmation is either invalid or has expired. Please sign up again and confirm your email address.", 7 | "alreadyConfirmed": "User email has been already confirmed." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/containers/edit-profile/professional-info-tab/about-tutor-accordion/AboutTutorAccordion.styles.ts: -------------------------------------------------------------------------------- 1 | import palette from '~/styles/app-theme/app.pallete' 2 | 3 | export const styles = { 4 | accordion: { 5 | withIcon: { 6 | root: { 7 | borderRadius: '4px' 8 | }, 9 | accordion: { 10 | boxShadow: 'none', 11 | border: `1px solid ${palette.primary[100]}` 12 | }, 13 | active: { 14 | mb: 1 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/unit/hooks/use-input-visibility.spec.jsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react' 2 | import useInputVisibility from '~/hooks/use-input-visibility' 3 | 4 | describe('Use input visibility custom hook', () => { 5 | it('should use input visibility', () => { 6 | const { result } = renderHook(() => useInputVisibility('error')) 7 | 8 | expect(result.current.showInputText).toBe(false) 9 | expect(typeof result.current.inputVisibility).toBe('object') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/components/all-content-modal/AllContentModal.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | container: { p: '34px' }, 5 | textWithIconWrapper: { 6 | display: 'flex', 7 | alignItems: 'center', 8 | gap: '8px', 9 | mb: '15px', 10 | '& svg': { 11 | width: '20px', 12 | height: '20px' 13 | } 14 | }, 15 | text: { 16 | color: 'primary.800', 17 | typography: TypographyVariantEnum.Subtitle2 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/checkbox-with-tooltip/CheckboxWithTooltip.styles.ts: -------------------------------------------------------------------------------- 1 | import palette from '~/styles/app-theme/app.pallete' 2 | 3 | export const styles = { 4 | root: { 5 | display: 'flex', 6 | alignItems: 'center' 7 | }, 8 | label: { 9 | mr: '8px' 10 | }, 11 | typography: { 12 | fontSize: '14px' 13 | }, 14 | icon: { 15 | color: palette.primary[500] 16 | }, 17 | tooltip: { 18 | fontSize: '12px', 19 | lineHeight: 1.3, 20 | fontWeight: 400 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/containers/email-confirm-modal/EmailConfirmModal.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | box: { 3 | margin: { xs: '0 auto', sm: 0 }, 4 | padding: { xs: 4, md: 11 }, 5 | textAlign: 'center', 6 | boxShadow: 'none', 7 | borderRadius: '8px' 8 | }, 9 | button: { 10 | margin: '0 auto', 11 | mt: '32px' 12 | }, 13 | titleWithDescription: { 14 | title: { 15 | typography: 'h6', 16 | color: 'primary.900', 17 | my: '14px' 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/translate-data.tsx: -------------------------------------------------------------------------------- 1 | import { titleToCamel } from './title-to-camel-case' 2 | 3 | export function translateData( 4 | data: T[], 5 | translationKey: string, 6 | t: (key: string, options?: { defaultValue: string }) => string 7 | ): Array { 8 | return data.map((item) => ({ 9 | ...item, 10 | displayName: t(`${translationKey}.${titleToCamel(item.name)}`, { 11 | defaultValue: item.name 12 | }) 13 | })) 14 | } 15 | -------------------------------------------------------------------------------- /tests/unit/components/page-wrapper/PageWrapper.spec.jsx: -------------------------------------------------------------------------------- 1 | import { renderWithProviders } from '~tests/test-utils' 2 | import PageWrapper from '~/components/page-wrapper/PageWrapper' 3 | 4 | describe('PageWrapper', () => { 5 | it('should render children', () => { 6 | const children =
Test children
7 | const { getByText } = renderWithProviders( 8 | {children} 9 | ) 10 | expect(getByText('Test children')).toBeInTheDocument() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/assets/img/download-attachments/download-symbol.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/search-filter-input/SearchFilterInput.styles.tsx: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | width: '100%', 4 | maxHeight: '48px', 5 | backgroundColor: 'basic.white', 6 | borderRadius: '70px', 7 | display: 'flex', 8 | alignItems: 'center' 9 | }, 10 | searchIcon: { 11 | color: 'primary.700' 12 | }, 13 | input: { 14 | flex: 1 15 | }, 16 | inputLabel: { 17 | color: 'primary.300', 18 | top: '12px', 19 | left: '15px' 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/view-switcher/ViewSwitcher.styles.ts: -------------------------------------------------------------------------------- 1 | const toggleButtonStyle = { 2 | borderColor: 'primary.200', 3 | '&.Mui-selected': { 4 | borderColor: 'primary.900', 5 | transition: '.16s ease', 6 | backgroundColor: 'transparent' 7 | } 8 | } 9 | export const styles = { 10 | root: { minWidth: '110px' }, 11 | icon: { 12 | fontSize: '24px', 13 | color: 'primary.900' 14 | }, 15 | gridButton: toggleButtonStyle, 16 | inlineButton: { ...toggleButtonStyle, marginRight: '8px' } 17 | } 18 | -------------------------------------------------------------------------------- /src/containers/my-resources/add-categories-modal/AddCategories.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | root: { p: '32px', width: '481px' }, 5 | title: { typography: TypographyVariantEnum.H6 }, 6 | form: { pt: '24px', mb: '24px' }, 7 | textField: { 8 | '.MuiInputLabel-asterisk': { 9 | display: 'none' 10 | } 11 | }, 12 | buttons: { display: 'flex', justifyContent: 'flex-end', gap: '10px' }, 13 | saveBtn: { minWidth: '103px' } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/app-toolbar/AppToolbar.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, FC } from 'react' 2 | 3 | import { SxProps } from '@mui/system' 4 | import Box from '@mui/material/Box' 5 | 6 | import { styles } from '~/components/app-toolbar/AppToolbar.styles' 7 | 8 | interface AppToolbarProps { 9 | sx: SxProps 10 | children: ReactNode 11 | } 12 | 13 | const AppToolbar: FC = ({ sx, children }) => { 14 | return {children} 15 | } 16 | 17 | export default AppToolbar 18 | -------------------------------------------------------------------------------- /src/components/scroll-to-top/ScrollToTop.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useLayoutEffect } from 'react' 2 | import { useLocation } from 'react-router-dom' 3 | 4 | interface ScrollToTopProps { 5 | element: React.RefObject 6 | } 7 | 8 | const ScrollToTop: FC = ({ element }) => { 9 | const { pathname } = useLocation() 10 | 11 | useLayoutEffect(() => { 12 | element.current?.scrollTo(0, 0) 13 | }, [element, pathname]) 14 | 15 | return null 16 | } 17 | 18 | export default ScrollToTop 19 | -------------------------------------------------------------------------------- /src/containers/my-resources/add-attachment-category-modal/AddAttachmentCategoryModal.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | root: { p: '32px', width: '481px' }, 5 | title: { typography: TypographyVariantEnum.H6 }, 6 | form: { py: '24px' }, 7 | inputTitle: { 8 | typography: TypographyVariantEnum.Body2, 9 | my: '3px' 10 | }, 11 | buttons: { display: 'flex', justifyContent: 'flex-end', gap: '10px' }, 12 | saveBtn: { minWidth: '87px' } 13 | } 14 | -------------------------------------------------------------------------------- /src/containers/offer-page/chat-dialog-window/ChatDialogWindow.constants.ts: -------------------------------------------------------------------------------- 1 | export const questionsForTutor = [ 2 | { question: 'chatPage.firstQuestion.teachMethod' }, 3 | { question: 'chatPage.firstQuestion.trialLesson' }, 4 | { question: 'chatPage.firstQuestion.tutoringSession' } 5 | ] 6 | 7 | export const questionsForStudent = [ 8 | { question: 'chatPage.firstQuestion.motivation' }, 9 | { question: 'chatPage.firstQuestion.availability' }, 10 | { question: 'chatPage.firstQuestion.studyCommitment' } 11 | ] 12 | -------------------------------------------------------------------------------- /src/containers/quiz/question-answer/Answer.types.ts: -------------------------------------------------------------------------------- 1 | export enum AnswerStatusEnum { 2 | Correct = 'correct', 3 | Incorrect = 'incorrect', 4 | Answered = 'answered', 5 | Unanswered = 'unanswered' 6 | } 7 | 8 | export type AnswerCorrectnessStatus = 9 | | AnswerStatusEnum.Correct 10 | | AnswerStatusEnum.Incorrect 11 | export type AnswerCompletionStatus = 12 | | AnswerStatusEnum.Answered 13 | | AnswerStatusEnum.Unanswered 14 | 15 | export type AnswerStatus = AnswerCorrectnessStatus | AnswerCompletionStatus 16 | -------------------------------------------------------------------------------- /src/components/setting-item/SettingItem.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | export const styles = { 3 | title: { 4 | typography: TypographyVariantEnum.Subtitle1, 5 | fontWeight: 500 6 | }, 7 | subtitle: { 8 | typography: TypographyVariantEnum.Subtitle1, 9 | fontWeight: 400, 10 | color: 'basic.blueGray' 11 | }, 12 | settingContainer: { 13 | mt: '24px', 14 | display: 'flex', 15 | justifyContent: 'space-between', 16 | alignItems: 'center' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/plugins/report-web-vitals.ts: -------------------------------------------------------------------------------- 1 | import { MetricType } from 'web-vitals' 2 | 3 | const reportWebVitals = (onPerfEntry?: (metric: MetricType) => void): void => { 4 | if (onPerfEntry && typeof onPerfEntry === 'function') { 5 | void import('web-vitals').then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => { 6 | onCLS(onPerfEntry) 7 | onINP(onPerfEntry) 8 | onFCP(onPerfEntry) 9 | onLCP(onPerfEntry) 10 | onTTFB(onPerfEntry) 11 | }) 12 | } 13 | } 14 | 15 | export default reportWebVitals 16 | -------------------------------------------------------------------------------- /src/types/common/events/enums/events.enums.ts: -------------------------------------------------------------------------------- 1 | export enum CourseResourceEventType { 2 | ResourceUpdated = 'resourceUpdated', 3 | ResourceRemoved = 'resourceRemoved', 4 | ResourcesOrderChange = 'resourcesOrderChange', 5 | AddSectionResources = 'addSectionResources', 6 | ResourceUpdateAvailability = 'resourceUpdateAvailability' 7 | } 8 | 9 | export enum CourseSectionEventType { 10 | SectionAdded = 'sectionAdded', 11 | SectionRemoved = 'sectionRemoved', 12 | SectionsOrderChange = 'sectionsOrderChange' 13 | } 14 | -------------------------------------------------------------------------------- /src/types/edit-profile/types/editProfile.types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CategoryNameInterface, 3 | SubjectNameInterface, 4 | UserMainSubject, 5 | UpdatedPhoto 6 | } from '~/types' 7 | 8 | export type OpenProfessionalCategoryModalHandler = ( 9 | initialValues?: UserMainSubject, 10 | isEdit?: boolean 11 | ) => void 12 | 13 | export type UserMainSubjectFieldValues = string & 14 | boolean & 15 | CategoryNameInterface & 16 | SubjectNameInterface[] 17 | 18 | export type EditProfilePhoto = UpdatedPhoto | string | null 19 | -------------------------------------------------------------------------------- /src/utils/replace-empty-strings-with-null.ts: -------------------------------------------------------------------------------- 1 | type ConvertStringsToNullable = { 2 | [K in keyof T]: T[K] extends string ? string | null : T[K] 3 | } 4 | 5 | export const replaceEmptyStringsWithNull = >( 6 | obj: T 7 | ) => { 8 | return Object.fromEntries( 9 | Object.entries(obj).map((entries) => { 10 | const [key, value] = entries as [keyof T, T[keyof T]] 11 | 12 | return [key, value === '' ? null : value] 13 | }) 14 | ) as ConvertStringsToNullable 15 | } 16 | -------------------------------------------------------------------------------- /src/components/app-rating/AppRating.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | display: 'flex', 4 | alignItems: 'center', 5 | columnGap: '4px', 6 | padding: '3px 6px', 7 | borderRadius: '5px', 8 | width: 'fit-content', 9 | height: 'fit-content' 10 | }, 11 | number: { 12 | lineHeight: 'normal' 13 | }, 14 | emptyIcon: { 15 | color: 'primary.200' 16 | }, 17 | stars: { 18 | typography: 'body2', 19 | '& .MuiRating-icon': { 20 | mx: '1px' 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/status-chip/StatusChip.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import Chip from '~scss-components/chip/Chip' 3 | 4 | import { StatusEnum } from '~/types' 5 | import { statusColors } from '~/components/status-chip/StatusChip.styles' 6 | 7 | interface StatusChipProps { 8 | status: StatusEnum 9 | } 10 | 11 | const StatusChip: FC = ({ status }) => { 12 | return ( 13 | 14 | ) 15 | } 16 | 17 | export default StatusChip 18 | -------------------------------------------------------------------------------- /src/router/constants/studentRoutes.ts: -------------------------------------------------------------------------------- 1 | import { authRoutes } from '~/router/constants/authRoutes' 2 | import { guestRoutes } from '~/router/constants/guestRoutes' 3 | 4 | export const studentRoutes = { 5 | navBar: { 6 | homePage: { route: 'homePage', path: guestRoutes.student.path }, 7 | findOffers: { 8 | route: 'findTutor', 9 | path: authRoutes.findOffers.path 10 | }, 11 | cooperations: { 12 | route: 'cooperations', 13 | path: authRoutes.myCooperations.path 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/popup-dialog/PopupDialog.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | box: { 3 | margin: { xs: '0 auto', sm: 0 }, 4 | display: { xl: 'flex' }, 5 | flexDirection: 'column', 6 | justifyContent: 'center', 7 | width: { xs: '100%', sm: 'auto' }, 8 | height: { xl: '100%' } 9 | }, 10 | contentWraper: { overflowY: { lg: 'auto' } }, 11 | icon: { 12 | color: 'primary.900', 13 | position: 'absolute', 14 | right: { xs: '8px', sm: '20px' }, 15 | top: { xs: '8px', sm: '20px' } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/subject-level-with-labels/SubjectLevelWithLabels.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | container: { 5 | display: 'flex', 6 | gap: '10px' 7 | }, 8 | labels: { 9 | display: 'flex', 10 | flexDirection: 'column', 11 | gap: '8px' 12 | }, 13 | label: { 14 | typography: TypographyVariantEnum.Overline, 15 | color: 'primary.500', 16 | lineHeight: '26px' 17 | }, 18 | chips: { 19 | flexDirection: 'column' 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | /tests/coverage 11 | test-report.xml 12 | .scannerwork 13 | 14 | # production 15 | /build 16 | /build-story 17 | /dist 18 | 19 | # misc 20 | /.idea 21 | .idea 22 | .DS_Store 23 | .env* 24 | !.env.example 25 | 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | 31 | # scss builds 32 | /src/scss/lib 33 | *storybook.log 34 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/react-vite' 2 | 3 | const config: StorybookConfig = { 4 | stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], 5 | addons: [ 6 | '@storybook/addon-onboarding', 7 | '@storybook/addon-links', 8 | '@storybook/addon-essentials', 9 | '@chromatic-com/storybook', 10 | '@storybook/addon-interactions' 11 | ], 12 | framework: { 13 | name: '@storybook/react-vite', 14 | options: {} 15 | } 16 | } 17 | export default config 18 | -------------------------------------------------------------------------------- /src/containers/my-resources/add-resource-with-input/AddResourceWithInput.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | display: 'flex', 4 | justifyContent: 'space-between', 5 | mb: '24px' 6 | }, 7 | addIcon: { width: { xs: '18px', sm: '22px' }, ml: '5px' }, 8 | searchIcon: { color: 'primary.700' }, 9 | input: { 10 | flex: 1, 11 | height: '48px', 12 | maxWidth: '500px', 13 | width: '285px' 14 | }, 15 | filterWithInput: { 16 | display: 'flex', 17 | columnGap: '25px' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/design-system/components/menu-item/MenuItem.types.ts: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from 'react' 2 | import { type MenuItemColorVariant } from './MenuItem.constants' 3 | 4 | export type OnItemClickArgs = Record 5 | 6 | export interface MenuItemProps { 7 | title: string 8 | additionalInfo?: string 9 | alignVariant?: 'left' | 'center' | 'right' 10 | colorVariant?: MenuItemColorVariant 11 | isDisabled?: boolean 12 | graphics?: ReactNode 13 | isBottomBorder?: boolean 14 | onClick?: () => void 15 | } 16 | -------------------------------------------------------------------------------- /src/redux/redux.constants.ts: -------------------------------------------------------------------------------- 1 | export const sliceNames = { 2 | cooperations: 'cooperationsSlice', 3 | snackbar: 'snackbarSlice', 4 | editProfile: 'editProfileSlice', 5 | socket: 'socketSlice' 6 | } 7 | 8 | export enum LoadingStatusEnum { 9 | Idle = 'idle', 10 | Pending = 'pending', 11 | Fulfilled = 'fulfilled', 12 | Rejected = 'rejected' 13 | } 14 | 15 | export type LoadingStatus = 16 | | LoadingStatusEnum.Idle 17 | | LoadingStatusEnum.Pending 18 | | LoadingStatusEnum.Fulfilled 19 | | LoadingStatusEnum.Rejected 20 | -------------------------------------------------------------------------------- /src/router/routes/guestRouter.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react' 2 | import { Route } from 'react-router-dom' 3 | 4 | import { guestRoutes } from '~/router/constants/guestRoutes' 5 | import { privacyPolicy } from '~/router/constants/crumbs' 6 | 7 | const CookiePolicy = lazy(() => import('~/pages/cookie-policy/CookiePolicy')) 8 | 9 | export const guestRouter = ( 10 | } 12 | handle={{ 13 | crumb: privacyPolicy 14 | }} 15 | path={guestRoutes.privacyPolicy.route} 16 | /> 17 | ) 18 | -------------------------------------------------------------------------------- /src/components/app-card/AppCard.styles.ts: -------------------------------------------------------------------------------- 1 | import { 2 | commonHoverShadow, 3 | commonShadow 4 | } from '~/styles/app-theme/custom-shadows' 5 | 6 | export const styles = { 7 | container: (isClickable: boolean) => ({ 8 | display: 'flex', 9 | padding: '20px 30px', 10 | textDecoration: 'none', 11 | backgroundColor: 'basic.white', 12 | boxShadow: commonShadow, 13 | borderRadius: '6px', 14 | '&:hover': isClickable && { 15 | cursor: 'pointer', 16 | boxShadow: commonHoverShadow 17 | } 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /src/containers/layout/app-header/AppHeader.tsx: -------------------------------------------------------------------------------- 1 | import AppBar from '@mui/material/AppBar' 2 | import Toolbar from '@mui/material/Toolbar' 3 | import NavBar from '~/containers/layout/navbar/NavBar' 4 | import { styles } from '~/containers/layout/app-header/AppHeader.styles' 5 | 6 | const AppHeader = () => { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export default AppHeader 18 | -------------------------------------------------------------------------------- /src/containers/student-home-page/popular-categories/PopularCategories.styles.js: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | flexDirection: 'column', 4 | alignItems: 'stretch' 5 | }, 6 | titleWithDescription: { 7 | wrapper: { 8 | margin: { xs: '40px 0', sm: '75px 0', md: '100px 0' } 9 | }, 10 | title: { 11 | typography: { sm: 'h4', xs: 'h5' }, 12 | mb: 1, 13 | color: 'primary.500' 14 | }, 15 | description: { 16 | typography: { sm: 'body1', xs: 'body2' } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/design-system/scss/utilities/shadow.scss: -------------------------------------------------------------------------------- 1 | @use '../variables' as *; 2 | @use '../functions' as *; 3 | @use '../mixins' as *; 4 | @use 'sass:map'; 5 | 6 | $shadow-utilities: ( 7 | 'box-shadow': ( 8 | class: 'shadow', 9 | properties: box-shadow, 10 | values: 11 | map.merge( 12 | map-to-var-map($box-shadows, 'box-shadow'), 13 | ( 14 | 'none': none 15 | ) 16 | ) 17 | ) 18 | ); 19 | 20 | @each $key, $utility in $shadow-utilities { 21 | @include generate-utilities($utility); 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/find-offers/FindOffers.constants.ts: -------------------------------------------------------------------------------- 1 | import { FindOffersFilters, UserRoleEnum } from '~/types' 2 | 3 | export const defaultFilters = (role: UserRoleEnum): FindOffersFilters => ({ 4 | categoryId: '', 5 | subjectId: '', 6 | sort: 'createdAt', 7 | language: '', 8 | native: 'false', 9 | rating: '0', 10 | authorRole: role, 11 | search: '', 12 | proficiencyLevel: [], 13 | price: undefined, 14 | page: '1' 15 | }) 16 | 17 | export const defaultResponse = { items: [], count: 0 } 18 | 19 | export const itemsPerPage = 8 20 | -------------------------------------------------------------------------------- /src/PopupsProvider.tsx: -------------------------------------------------------------------------------- 1 | import { ModalProvider } from '~/context/modal-context' 2 | import { ConfirmationDialogProvider } from '~/context/confirm-context' 3 | 4 | import { FC, ReactElement } from 'react' 5 | 6 | interface PopupsProvider { 7 | children: ReactElement 8 | } 9 | 10 | const PopupsProvider: FC = ({ children }) => { 11 | return ( 12 | 13 | {children} 14 | 15 | ) 16 | } 17 | 18 | export default PopupsProvider 19 | -------------------------------------------------------------------------------- /src/components/enhanced-table/enhanced-table-row/EnhancedTableRow.styles.ts: -------------------------------------------------------------------------------- 1 | import palette from '~/styles/app-theme/app.pallete' 2 | 3 | export const styles = { 4 | row: (isSelected: boolean, isRowOnClick = false, isDisableRow = false) => ({ 5 | ...(isRowOnClick && { cursor: 'pointer' }), 6 | ...(isSelected && { background: palette.basic.grey }), 7 | '&:hover .addCategory': { 8 | visibility: 'visible' 9 | }, 10 | ...(isDisableRow && { 11 | pointerEvents: 'none', 12 | opacity: 0.5 13 | }) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/constants/translations/en/user-table.json: -------------------------------------------------------------------------------- 1 | { 2 | "adminsTab": "Admins Tab", 3 | "tutorsTab": "Tutors Tab", 4 | "studentsTab": "Students Tab", 5 | "name": "Name", 6 | "email": "Email", 7 | "status": "Status", 8 | "rating": "Rating", 9 | "lastLogin": "Last login", 10 | "signedUp": "Signed up", 11 | "all": "All", 12 | "active": "Active", 13 | "blocked": "Blocked", 14 | "invited": "Invited", 15 | "deleteUserSuccess": "User successfully deleted", 16 | "deleteUsersSuccess": "Selected users successfully deleted" 17 | } 18 | -------------------------------------------------------------------------------- /src/containers/quiz/quiz-header/QuizHeader.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | import { theme } from '~/styles/app-theme/custom-mui.styles' 3 | 4 | const styles = { 5 | wrapper: { 6 | position: 'sticky' 7 | }, 8 | titleWithDescription: { 9 | title: { 10 | typography: TypographyVariantEnum.H5, 11 | mb: theme.spacing(2) 12 | }, 13 | description: { 14 | typography: TypographyVariantEnum.Body1, 15 | color: 'primary.600' 16 | } 17 | } 18 | } 19 | 20 | export default styles 21 | -------------------------------------------------------------------------------- /src/types/findOffers/types/findOffers.types.ts: -------------------------------------------------------------------------------- 1 | import { CardsViewEnum, FindOffersFilters, LanguagesEnum } from '~/types' 2 | 3 | export type CardsView = CardsViewEnum.Grid | CardsViewEnum.Inline 4 | export type LanguageFilter = LanguagesEnum | null 5 | export type UpdateFiltersInQuery = (filtersToUpdate: Partial) => void 6 | export type FilterFromQuery = { 7 | [key: string]: string | string[] 8 | } 9 | export type UpdateOfferFilterByKey = ( 10 | key: K 11 | ) => (value: FindOffersFilters[K]) => void 12 | -------------------------------------------------------------------------------- /src/containers/my-cooperations/cooperation-offer-toolbar/CooperationOfferToolbar.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | display: 'flex', 4 | justifyContent: 'space-between', 5 | gap: { xs: '14px', sm: '40px' }, 6 | flexDirection: { xs: 'column', sm: 'row' }, 7 | mb: '20px' 8 | }, 9 | searchIcon: { color: 'primary.700' }, 10 | input: { 11 | flex: 1, 12 | height: '48px', 13 | maxWidth: '580px' 14 | }, 15 | actionBlock: { display: 'flex', gap: '24px' }, 16 | select: { flex: { xs: 1, sm: 0 } } 17 | } 18 | -------------------------------------------------------------------------------- /src/containers/tutor-home-page/add-photo-step/constants.js: -------------------------------------------------------------------------------- 1 | export const MB = 1_048_576 2 | 3 | export const validationData = { 4 | maxFileSize: 5 * MB, 5 | maxAllFilesSize: 5 * MB, 6 | filesTypes: ['image/jpeg', 'image/png'], 7 | fileSizeError: 'becomeTutor.photo.fileSizeError', 8 | allFilesSizeError: 'common.allFilesSizeError', 9 | typeError: 'becomeTutor.photo.typeError', 10 | maxQuantityFiles: 1, 11 | quantityError: 'becomeTutor.photo.quantityError', 12 | maxFileNameLength: 55, 13 | maxFileNameError: 'common.fileNameError' 14 | } 15 | -------------------------------------------------------------------------------- /src/constants/translations/en/categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "computerScience": "Computer science", 3 | "music": "Music", 4 | "psychology": "Psychology", 5 | "finances": "Finances", 6 | "audit": "Audit", 7 | "astronomy": "Astronomy", 8 | "biology": "Biology", 9 | "cooking": "Cooking", 10 | "languages": "Languages", 11 | "painting": "Painting", 12 | "design": "Design", 13 | "marketing": "Marketing", 14 | "it": "IT", 15 | "mathematics": "Mathematics", 16 | "chemistry": "Chemistry", 17 | "physics": "Physics", 18 | "history": "History" 19 | } 20 | -------------------------------------------------------------------------------- /src/design-system/components/badge/Badge.scss: -------------------------------------------------------------------------------- 1 | @use '~scss/utilities' as *; 2 | 3 | @mixin badge-style($bg-color, $color) { 4 | & > .MuiBadge-dot { 5 | background-color: $bg-color; 6 | } 7 | 8 | & > .MuiBadge-standard { 9 | background-color: $bg-color; 10 | color: $color; 11 | } 12 | } 13 | 14 | .#{$prefix}badge-error { 15 | @include badge-style(var(--#{$prefix}red-500), var(--#{$prefix}neutral-50)); 16 | } 17 | 18 | .#{$prefix}badge-success { 19 | @include badge-style(var(--#{$prefix}green-600), var(--#{$prefix}neutral-50)); 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/lesson-details/LessonDetails.constants.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Lesson, 3 | ResourceAvailabilityStatusEnum, 4 | ResourcesTypesEnum as ResourceType 5 | } from '~/types' 6 | 7 | export const defaultResponse: Lesson = { 8 | attachments: [], 9 | category: null, 10 | author: '', 11 | createdAt: '', 12 | description: '', 13 | content: '', 14 | title: '', 15 | updatedAt: '', 16 | _id: '', 17 | availability: { 18 | status: ResourceAvailabilityStatusEnum.Open, 19 | date: null 20 | }, 21 | resourceType: ResourceType.Lesson 22 | } 23 | -------------------------------------------------------------------------------- /src/redux/selectors/socket-selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from '@reduxjs/toolkit' 2 | import { RootState } from '~/redux/store' 3 | 4 | export const selectIsUserOnline = (userId: string) => 5 | createSelector( 6 | (state: RootState) => state.socket.usersOnline, 7 | (usersOnline: string[]) => usersOnline.includes(userId) 8 | ) 9 | 10 | export const selectIsTyping = (chatId: string) => 11 | createSelector( 12 | (state: RootState) => state.socket.isTypingChats, 13 | (isTypingChats: string[]) => isTypingChats.includes(chatId) 14 | ) 15 | -------------------------------------------------------------------------------- /tests/unit/hooks/use-axios.spec.js: -------------------------------------------------------------------------------- 1 | import { act, renderHook } from '@testing-library/react' 2 | import useAxios from '~/hooks/use-axios' 3 | import { vi } from 'vitest' 4 | 5 | describe('Use axios custom hook', () => { 6 | const serviceMock = vi.fn(() => 'test') 7 | 8 | it('should call serviceMock', async () => { 9 | const { result } = renderHook(() => 10 | useAxios({ service: serviceMock, fetchOnMount: false }) 11 | ) 12 | 13 | await act(() => result.current.fetchData()) 14 | 15 | expect(serviceMock).toBeCalled() 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /tests/unit/pages/student-profile/StudentProfile.spec.jsx: -------------------------------------------------------------------------------- 1 | import { screen } from '@testing-library/react' 2 | import { renderWithProviders } from '~tests/test-utils' 3 | import StudentProfile from '~/pages/student-profile/StudentProfile' 4 | 5 | describe('StudentProfile component', () => { 6 | beforeEach(() => { 7 | renderWithProviders() 8 | }) 9 | 10 | it('Should render StudentProfile placeholder', () => { 11 | expect( 12 | screen.getByText('StudentProfile Page Placeholder') 13 | ).toBeInTheDocument() 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /src/components/tab/Tab.styles.ts: -------------------------------------------------------------------------------- 1 | import { fadeAnimation } from '~/styles/app-theme/custom-animations' 2 | 3 | const activeTab = { 4 | color: 'primary.600', 5 | borderBottom: '2px solid', 6 | ...fadeAnimation 7 | } 8 | 9 | export const styles = { 10 | tab: (isActive: boolean) => ({ 11 | px: { xs: '32px', sm: '44px' }, 12 | pb: '14px', 13 | cursor: 'pointer', 14 | color: 'primary.300', 15 | typography: 'subtitle2', 16 | borderBottom: '2px solid transparent', 17 | borderRadius: 0, 18 | ...(isActive && activeTab) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/constants/translations/en/button.json: -------------------------------------------------------------------------------- 1 | { 2 | "toMain": "Home Page", 3 | "goToLogin": "Go to login", 4 | "applyFilters": "Apply filters", 5 | "clearFilters": "Clear filters", 6 | "addToDrafts": "Add to drafts", 7 | "createCooperation": "Send cooperation request", 8 | "sendRequest": "Send request", 9 | "addQuestion": "Add one more question", 10 | "view": { 11 | "tutor": "View my offers", 12 | "student": "View my requests" 13 | }, 14 | "newLesson": "New lesson", 15 | "download": "Download", 16 | "downloading": "Downloading..." 17 | } 18 | -------------------------------------------------------------------------------- /src/containers/add-documents/AddDocuments.constants.ts: -------------------------------------------------------------------------------- 1 | import { AddDocuments } from '~/types' 2 | 3 | export const validationData: AddDocuments = { 4 | maxFileSize: 5_000_000, 5 | maxAllFilesSize: 20_000_000, 6 | filesTypes: ['application/pdf', 'image/jpeg', 'image/png'], 7 | fileSizeError: 'common.fileSizeError', 8 | allFilesSizeError: 'common.allFilesSizeError', 9 | typeError: 'common.typeError', 10 | maxQuantityFiles: 7, 11 | quantityError: 'common.quantityError', 12 | maxFileNameLength: 55, 13 | maxFileNameError: 'common.fileNameError' 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/quiz-review/QuizReview.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from 'react-router-dom' 2 | import PageWrapper from '~/components/page-wrapper/PageWrapper' 3 | import { styles } from '~/pages/quiz-review/QuizReview.styles' 4 | import { FinishedQuiz } from '~/pages/quiz/QuizVariants' 5 | 6 | const QuizReview: React.FC = () => { 7 | const { attemptId = '' } = useParams() 8 | 9 | return ( 10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default QuizReview 17 | -------------------------------------------------------------------------------- /src/utils/check-icons.tsx: -------------------------------------------------------------------------------- 1 | import ListIcon from '@mui/icons-material/CheckCircleOutline' 2 | import DriveFileRenameOutlineOutlinedIcon from '@mui/icons-material/DriveFileRenameOutlineOutlined' 3 | import FormatListBulletedOutlinedIcon from '@mui/icons-material/FormatListBulletedOutlined' 4 | 5 | export const CheckIcons = (type: string) => { 6 | return ( 7 | (type === 'oneAnswer' && ) || 8 | (type === 'openAnswer' && ) || 9 | (type === 'multipleChoice' && ) 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/components/find-block/find-block.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | mt: { xs: '64px', md: '84px' }, 4 | 5 | img: { 6 | xs: { display: 'none', width: '168px', height: '168px' }, 7 | md: { display: 'block' } 8 | } 9 | }, 10 | 11 | button: { 12 | whiteSpace: 'nowrap', 13 | height: '48px' 14 | }, 15 | 16 | input: { 17 | display: 'flex', 18 | flexGrow: '1', 19 | mr: { sm: 3, xs: 0 }, 20 | mb: { sm: 0, xs: 2 }, 21 | backgroundColor: 'basic.white', 22 | maxHeight: '48px' 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/constants/translations/uk/categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "computerScience": "Комп'ютерні науки", 3 | "music": "Музика", 4 | "psychology": "Психологія", 5 | "finances": "Фінанси", 6 | "audit": "Аудит", 7 | "astronomy": "Астрономія", 8 | "biology": "Біологія", 9 | "cooking": "Готування", 10 | "languages": "Мови", 11 | "painting": "Малювання", 12 | "design": "Дизайн", 13 | "marketing": "Маркетинг", 14 | "it": "Програмування", 15 | "mathematics": "Математика", 16 | "chemistry": "Хімія", 17 | "physics": "Фізика", 18 | "history": "Історія" 19 | } 20 | -------------------------------------------------------------------------------- /src/containers/logo/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import logo from '~/assets/logo.svg' 3 | import logoLight from '~/assets/logo-light.svg' 4 | import Box, { BoxProps } from '@mui/material/Box' 5 | import { ComponentEnum } from '~/types' 6 | 7 | interface LogoProps extends BoxProps { 8 | light?: boolean 9 | } 10 | 11 | const Logo: FC = ({ light = false, ...props }) => ( 12 | 18 | ) 19 | 20 | export default Logo 21 | -------------------------------------------------------------------------------- /src/pages/create-or-edit-question/CreateOrEditQuestion.constants.tsx: -------------------------------------------------------------------------------- 1 | import { QuestionTypesEnum } from '~/types' 2 | 3 | export const initialValues = { 4 | category: null, 5 | answers: [], 6 | openAnswer: '', 7 | title: '', 8 | text: '', 9 | type: QuestionTypesEnum.MultipleChoice 10 | } 11 | 12 | export const defaultResponse = { 13 | category: null, 14 | answers: [], 15 | openAnswer: '', 16 | title: '', 17 | text: '', 18 | type: QuestionTypesEnum.MultipleChoice, 19 | author: '', 20 | _id: '', 21 | createdAt: '', 22 | updatedAt: '' 23 | } 24 | -------------------------------------------------------------------------------- /src/styles/app-theme/app.button.ts: -------------------------------------------------------------------------------- 1 | declare module '@mui/material/styles' { 2 | interface ButtonVariants { 3 | tonal: React.CSSProperties 4 | containedLight: React.CSSProperties 5 | danger: React.CSSProperties 6 | base: React.CSSProperties 7 | } 8 | } 9 | 10 | declare module '@mui/material/Button' { 11 | interface ButtonPropsSizeOverrides { 12 | extraLarge: true 13 | } 14 | interface ButtonPropsVariantOverrides { 15 | tonal: true 16 | containedLight: true 17 | danger: true 18 | base: true 19 | } 20 | } 21 | 22 | export {} 23 | -------------------------------------------------------------------------------- /src/containers/my-resources/edit-attachment-modal/EditAttachmentModal.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | root: { p: '32px', width: '481px' }, 5 | title: { typography: TypographyVariantEnum.H6 }, 6 | form: { py: '24px' }, 7 | inputTitle: (error?: boolean) => ({ 8 | color: error ? 'error.500' : 'primary.700', 9 | typography: TypographyVariantEnum.Body2, 10 | my: '3px' 11 | }), 12 | buttons: { display: 'flex', justifyContent: 'flex-end', gap: '10px' }, 13 | saveBtn: { minWidth: '87px' } 14 | } 15 | -------------------------------------------------------------------------------- /src/containers/cooperation-details/cooperation-activities-view/CooperationActivitiesView.style.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | p: { sm: '24px', xs: '12px' } 4 | }, 5 | input: { 6 | style: { padding: 0, margin: 0 }, 7 | disabled: true 8 | }, 9 | descriptionInput: { 10 | style: { 11 | marginTop: 0 12 | }, 13 | disableUnderline: true 14 | }, 15 | editContainer: { 16 | display: 'flex', 17 | justifyContent: 'flex-end' 18 | }, 19 | editButton: { 20 | p: '16px', 21 | backgroundColor: 'basic.grey' 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/admin-table/AdminTable.jsx: -------------------------------------------------------------------------------- 1 | import UserTable from '~/components/user-table/UserTable' 2 | 3 | import { 4 | columns, 5 | initialFilters, 6 | initialSort, 7 | tabsInfo 8 | } from '~/pages/admin-table/constants' 9 | import { UserRoleEnum } from '~/types' 10 | 11 | const AdminTable = () => { 12 | return ( 13 | 20 | ) 21 | } 22 | 23 | export default AdminTable 24 | -------------------------------------------------------------------------------- /src/pages/tutor-table/TutorTable.jsx: -------------------------------------------------------------------------------- 1 | import UserTable from '~/components/user-table/UserTable' 2 | 3 | import { 4 | columns, 5 | initialFilters, 6 | initialSort, 7 | tabsInfo 8 | } from '~/pages/tutor-table/constants' 9 | import { UserRoleEnum } from '~/types' 10 | 11 | const TutorTable = () => { 12 | return ( 13 | 20 | ) 21 | } 22 | 23 | export default TutorTable 24 | -------------------------------------------------------------------------------- /src/utils/get-changed-fields.ts: -------------------------------------------------------------------------------- 1 | export function getChangedFields>( 2 | initialState: T, 3 | currentState: T 4 | ): Partial { 5 | const changes: Partial = {} 6 | 7 | Object.keys(currentState).forEach((key) => { 8 | const typedKey = key as keyof T 9 | const initialValue = initialState[typedKey] 10 | const currentValue = currentState[typedKey] 11 | 12 | if (JSON.stringify(initialValue) !== JSON.stringify(currentValue)) { 13 | changes[typedKey] = currentValue 14 | } 15 | }) 16 | 17 | return changes 18 | } 19 | -------------------------------------------------------------------------------- /src/components/checkbox-list/CheckboxList.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | display: 'flex', 4 | flexDirection: 'column', 5 | alignItems: 'start' 6 | }, 7 | title: { 8 | color: 'primary.700', 9 | mb: '15px' 10 | }, 11 | checkbox: { 12 | '&.MuiCheckbox-root': { 13 | py: '6px' 14 | } 15 | }, 16 | error: { 17 | color: 'error.500', 18 | whiteSpace: 'nowrap', 19 | overflow: 'hidden', 20 | textOverflow: 'ellipsis', 21 | width: { xs: '230px' }, 22 | m: '3px 14px', 23 | minHeight: '20px' 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/video-box/VideoBox.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | videoBox: { 3 | display: 'flex', 4 | flexDirection: 'column', 5 | width: '100%' 6 | }, 7 | videoBg: { 8 | padding: { 9 | md: '32px 60px', 10 | sm: '20px 40px', 11 | xs: '20px 12px' 12 | }, 13 | backgroundColor: 'basic.grey', 14 | borderRadius: { 15 | md: '0 0 20px 20px', 16 | sm: '0 0 12px 12px', 17 | xs: '0 0 8px 8px' 18 | } 19 | }, 20 | video: { 21 | display: 'block', 22 | width: '100%', 23 | margin: '0 auto' 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/constants/translations/uk/user-table.json: -------------------------------------------------------------------------------- 1 | { 2 | "adminsTab": "Адміністратори", 3 | "tutorsTab": "Викладачі", 4 | "studentsTab": "Студенти", 5 | "name": "Ім'я", 6 | "email": "Електронна пошта", 7 | "status": "Статус", 8 | "rating": "Рейтинг", 9 | "lastLogin": "Останній вхід у систему", 10 | "signedUp": "Дата реєстрації", 11 | "all": "Усі", 12 | "active": "Активні", 13 | "blocked": "Заблоковані", 14 | "invited": "Запрошені", 15 | "deleteUserSuccess": "Користувача успішно видалено", 16 | "deleteUsersSuccess": "Вибраних користувачів успішно видалено" 17 | } 18 | -------------------------------------------------------------------------------- /src/containers/find-offer/offer-filter-block/offer-filter-list/OfferFilterList.constants.ts: -------------------------------------------------------------------------------- 1 | import { LanguagesEnum } from '~/types' 2 | 3 | export const defaultResponse = { 4 | minPrice: 0, 5 | maxPrice: 1000 6 | } 7 | 8 | export const radioButtonsTranslationKeys = [ 9 | { title: 'findOffers.radioFilter.any', value: 0 }, 10 | { title: 'findOffers.radioFilter.5stars', value: 5 }, 11 | { title: 'findOffers.radioFilter.4stars', value: 4 }, 12 | { title: 'findOffers.radioFilter.3stars', value: 3 } 13 | ] 14 | export const languageValues = ['', ...Object.values(LanguagesEnum)] 15 | -------------------------------------------------------------------------------- /src/pages/my-cooperations/MyCooperations.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | titleBlock: { 3 | display: 'flex', 4 | flexDirection: { xs: 'column', sm: 'row' }, 5 | gap: '10px', 6 | justifyContent: 'space-between', 7 | alignItems: 'center', 8 | mb: '24px' 9 | }, 10 | title: { typography: { xs: 'h5', sm: 'h4' } }, 11 | tabs: { 12 | display: 'flex', 13 | mb: '20px', 14 | borderBottom: '1px solid', 15 | borderColor: 'primary.100' 16 | }, 17 | pagination: { marginTop: 'auto' }, 18 | drawer: { width: { xs: '100%', md: 'auto' } } 19 | } 20 | -------------------------------------------------------------------------------- /src/types/user/user-types/user.types.ts: -------------------------------------------------------------------------------- 1 | import { UserRoleEnum } from '~/types' 2 | 3 | export type UserRole = 4 | | UserRoleEnum.Admin 5 | | UserRoleEnum.Tutor 6 | | UserRoleEnum.Student 7 | 8 | export type VideoUserRole = UserRoleEnum.Tutor | UserRoleEnum.Student 9 | 10 | export type UpdateFields = 11 | | 'firstName' 12 | | 'lastName' 13 | | 'address' 14 | | 'professionalSummary' 15 | | 'nativeLanguage' 16 | | 'professionalBlock' 17 | | 'aboutStudent' 18 | | 'notificationSettings' 19 | 20 | export type MainUserRole = UserRoleEnum.Tutor | UserRoleEnum.Student 21 | -------------------------------------------------------------------------------- /tests/unit/hooks/use-query.spec.js: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | import { vi } from 'vitest' 3 | 4 | import QueryProvider from '~/QueryProvider' 5 | import useQuery from '~/hooks/use-query' 6 | 7 | describe('useQuery', () => { 8 | const queryKey = ['testKey'] 9 | const queryFn = vi.fn() 10 | 11 | it('should return loading state initially', () => { 12 | const { result } = renderHook(() => useQuery({ queryKey, queryFn }), { 13 | wrapper: QueryProvider 14 | }) 15 | 16 | expect(result.current.isLoading).toBe(true) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/containers/my-resources/create-or-edit-question-modal/CreateOrEditQuestionModal.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | root: { p: '32px', width: '481px' }, 5 | title: { typography: TypographyVariantEnum.H6 }, 6 | form: { py: '24px' }, 7 | inputTitle: (error?: boolean) => ({ 8 | color: error ? 'error.500' : 'primary.700', 9 | typography: TypographyVariantEnum.Body2, 10 | my: '3px' 11 | }), 12 | buttons: { display: 'flex', justifyContent: 'flex-end', gap: '10px' }, 13 | saveBtn: { minWidth: '87px' } 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/student-table/StudentTable.jsx: -------------------------------------------------------------------------------- 1 | import UserTable from '~/components/user-table/UserTable' 2 | 3 | import { 4 | columns, 5 | initialFilters, 6 | initialSort, 7 | tabsInfo 8 | } from '~/pages/student-table/constants' 9 | import { UserRoleEnum } from '~/types' 10 | 11 | const StudentTable = () => { 12 | return ( 13 | 20 | ) 21 | } 22 | 23 | export default StudentTable 24 | -------------------------------------------------------------------------------- /src/components/popular-categories/PopularCategories.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | wrapper: { 3 | textAlign: 'center', 4 | margin: { xs: '64px 0', sm: '80px 0', md: '104px 0' }, 5 | marginBottom: '100px' 6 | }, 7 | titleWithDescription: { 8 | title: { 9 | typography: { md: 'h4', xs: 'h5' }, 10 | marginBottom: '8px' 11 | }, 12 | description: { 13 | typography: { sm: 'body1', xs: 'body2' }, 14 | color: 'primary.500' 15 | }, 16 | wrapper: { 17 | marginBottom: { lg: '51px', md: '47px', xs: '36px' } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/containers/user-profile/comments-block/CommentsBlock.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | width: '100%', 4 | display: 'flex', 5 | flexDirection: 'column', 6 | gap: { xs: '20px', sm: '15px', md: '32px' }, 7 | alignItems: 'center' 8 | }, 9 | title: { 10 | typography: { xs: 'h6', sm: 'h5' }, 11 | color: 'primary.700', 12 | marginRight: 'auto', 13 | lineHeight: '28px' 14 | }, 15 | commentList: { 16 | width: '100%', 17 | borderTop: '1px solid', 18 | borderColor: 'primary.100' 19 | }, 20 | button: { minWidth: '195px' } 21 | } 22 | -------------------------------------------------------------------------------- /tests/unit/components/scroll-to-top/ScrollToTop.spec.jsx: -------------------------------------------------------------------------------- 1 | import { renderWithProviders } from '~tests/test-utils' 2 | import ScrollToTop from '~/components/scroll-to-top/ScrollToTop' 3 | import { vi } from 'vitest' 4 | 5 | const mockScrollTo = vi.fn() 6 | const element = { current: { scrollTo: mockScrollTo } } 7 | 8 | describe('ScrollToTop dialog test', () => { 9 | beforeEach(() => { 10 | renderWithProviders() 11 | }) 12 | 13 | it('should show ArrowUpwardRoundedIcon', () => { 14 | expect(mockScrollTo).toHaveBeenCalledWith(0, 0) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /src/components/app-rating-mobile/AppRatingMobile.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | root: { 5 | display: 'flex', 6 | alignItems: 'center', 7 | flexDirection: 'column', 8 | columnGap: '4px', 9 | borderRadius: '5px' 10 | }, 11 | starMobile: { 12 | color: 'basic.yellow', 13 | height: '18px' 14 | }, 15 | number: { display: 'flex', alignItems: 'center' }, 16 | rating: { 17 | typography: TypographyVariantEnum.H6 18 | }, 19 | reviews: { 20 | typography: TypographyVariantEnum.Caption 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/constants/translations/uk/button.json: -------------------------------------------------------------------------------- 1 | { 2 | "toMain": "На Головну", 3 | "goToLogin": "Перейти до Логіну", 4 | "applyFilters": "Застосувати фільтри", 5 | "clearFilters": "Очистити фільтри", 6 | "addToDrafts": "Додати в чернетки", 7 | "createCooperation": "Відправити запит на співпрацю", 8 | "sendRequest": "Надіслати запит", 9 | "addQuestion": "Додати ще одне питання", 10 | "view": { 11 | "tutor": "До моїх пропозицій", 12 | "student": "До моїх запитів" 13 | }, 14 | "newLesson": "Новий урок", 15 | "download": "Завантажити", 16 | "downloading": "Завантаження..." 17 | } 18 | -------------------------------------------------------------------------------- /src/containers/find-offer/offer-search-toolbar/OfferSearchToolbar.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | minHeight: '110px', 4 | display: 'flex', 5 | flexDirection: 'column', 6 | justifyContent: 'space-between' 7 | }, 8 | autocomplete: { 9 | width: '100%', 10 | maxWidth: { md: '180px', lg: '220px' }, 11 | mr: { sm: '20px', lg: '30px' }, 12 | '& .MuiOutlinedInput-root': { 13 | padding: '5px 9px' 14 | }, 15 | label: { 16 | lineHeight: '20px' 17 | } 18 | }, 19 | searchToolbar: { 20 | borderRadius: '70px' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/unit/components/app-icon-button/AppIconButton.spec.jsx: -------------------------------------------------------------------------------- 1 | import { screen } from '@testing-library/react' 2 | import { renderWithProviders } from '~tests/test-utils' 3 | import AppIconButton from '~/components/app-icon-button/AppIconButton' 4 | 5 | describe('test AppIconButton component', () => { 6 | it('Should render a link when url is passed', () => { 7 | renderWithProviders( 8 | I 9 | ) 10 | 11 | const iconButtonElement = screen.getByRole('link') 12 | 13 | expect(iconButtonElement).toBeInTheDocument() 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["stylelint-prettier"], 3 | "extends": "stylelint-config-standard-scss", 4 | "rules": { 5 | "prettier/prettier": true, 6 | "selector-class-pattern": null, 7 | "length-zero-no-unit": null, 8 | "at-rule-empty-line-before": null, 9 | "comment-whitespace-inside": null, 10 | "alpha-value-notation": "number", 11 | "color-function-notation": null, 12 | "declaration-block-no-redundant-longhand-properties": null, 13 | "scss/at-mixin-argumentless-call-parentheses": "always", 14 | "scss/dollar-variable-empty-line-before": null 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/containers/edit-profile/profile-tab/ProfileTab.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | import { 3 | rootContainer, 4 | updateProfileBtn, 5 | innerContainer 6 | } from '~/containers/edit-profile/common.styles' 7 | 8 | const { Body2, H6 } = TypographyVariantEnum 9 | 10 | export const styles = { 11 | root: rootContainer, 12 | profileInnerContainer: innerContainer, 13 | headerTitleWithDesc: { 14 | wrapper: { textAlign: 'left' }, 15 | title: { typography: H6 }, 16 | description: { typography: Body2, color: 'primary.500' } 17 | }, 18 | updateProfileBtn 19 | } 20 | -------------------------------------------------------------------------------- /src/containers/student-home-page/faq/accordionItems.ts: -------------------------------------------------------------------------------- 1 | export const accordionItems = [ 2 | { 3 | title: 'studentHomePage.faq.findTutor', 4 | description: 'studentHomePage.faq.findTutorDescription' 5 | }, 6 | { 7 | title: 'studentHomePage.faq.bookLesson', 8 | description: 'studentHomePage.faq.bookLessonDescription' 9 | }, 10 | { 11 | title: 'studentHomePage.faq.rules', 12 | description: 'studentHomePage.faq.rulesDescription' 13 | }, 14 | { 15 | title: 'studentHomePage.faq.howPayLessons', 16 | description: 'studentHomePage.faq.howPayLessonsDescription' 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /tests/unit/utils/are-all-values-empty-strings.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'vitest' 2 | 3 | import { areAllValuesEmptyStrings } from '~/utils/are-all-values-empty-strings' 4 | 5 | describe('areAllValuesEmptyStrings', () => { 6 | it('should return true if all values are empty strings', () => { 7 | const obj = { a: '', b: '', c: '' } 8 | expect(areAllValuesEmptyStrings(obj)).toBe(true) 9 | }) 10 | 11 | it('should return false if any value is not an empty string', () => { 12 | const obj = { a: '', b: 'hello', c: '' } 13 | expect(areAllValuesEmptyStrings(obj)).toBe(false) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.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/QueryProvider.tsx: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from 'react' 2 | import { QueryClientProvider } from '@tanstack/react-query' 3 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools' 4 | 5 | import { queryClient } from '~/plugins/queryClient' 6 | 7 | type QueryProviderProps = { 8 | children: ReactNode 9 | } 10 | 11 | const QueryProvider: React.FC = ({ children }) => { 12 | return ( 13 | 14 | 15 | {children} 16 | 17 | ) 18 | } 19 | 20 | export default QueryProvider 21 | -------------------------------------------------------------------------------- /src/types/components/price-filter/price-filter.types.ts: -------------------------------------------------------------------------------- 1 | export type InputRange = number | null 2 | export type RangeArray = [number, number] 3 | export type InputRangeArray = [InputRange, InputRange] 4 | 5 | export interface CheckInputChange { 6 | inputValue: InputRange 7 | range: InputRangeArray 8 | constrainedNumber: number 9 | } 10 | 11 | export interface CheckRangeIsInRange { 12 | inputValue: InputRange 13 | min: number 14 | max: number 15 | } 16 | 17 | export interface CreateNewState 18 | extends Omit { 19 | inputIndex: number 20 | sort?: boolean 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/calculate-total-points.ts: -------------------------------------------------------------------------------- 1 | interface Answer { 2 | isCorrect: boolean 3 | isChosen: boolean 4 | points?: number 5 | } 6 | 7 | interface Result { 8 | answers?: Answer[] 9 | } 10 | 11 | export const calculateTotalPoints = (results: Result[] = []) => { 12 | return results.reduce((total, { answers = [] }) => { 13 | const chosen = answers.find((answer) => answer.isChosen) 14 | 15 | if (!chosen) return total 16 | 17 | if (typeof chosen.points === 'number') { 18 | return total + chosen.points 19 | } 20 | 21 | return chosen.isCorrect ? total + 1 : total 22 | }, 0) 23 | } 24 | -------------------------------------------------------------------------------- /src/containers/my-quizzes/create-or-edit-quiz-question/CreateOrEditQuizQuestion.constants.ts: -------------------------------------------------------------------------------- 1 | import { Question, QuestionTypesEnum } from '~/types' 2 | 3 | export const initialValues = (question?: Question) => { 4 | const initialAnswers = (question?.answers ?? []).map((answer, index) => ({ 5 | ...answer, 6 | id: index 7 | })) 8 | 9 | return { 10 | type: question?.type ?? QuestionTypesEnum.MultipleChoice, 11 | title: question?.title ?? '', 12 | text: question?.text ?? '', 13 | answers: initialAnswers, 14 | openAnswer: '', 15 | category: question?.category?._id ?? null 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/types/course/types/course.types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Lesson, 3 | Quiz, 4 | Attachment, 5 | ResourceAvailabilityStatusEnum, 6 | Resource 7 | } from '~/types' 8 | 9 | export interface ResourceAvailability { 10 | status: ResourceAvailabilityStatusEnum 11 | date: string | null 12 | } 13 | 14 | export type CourseResource = (Lesson | Quiz | Attachment) & { 15 | availability?: ResourceAvailability 16 | } 17 | 18 | export type SetResourceAvailability = ( 19 | sectionId: string, 20 | availability: ResourceAvailability 21 | ) => void 22 | 23 | export type CourseFieldValues = string & string[] & Resource[] 24 | -------------------------------------------------------------------------------- /src/components/offer-card/OfferCard.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | wrapper: { 3 | width: '100%', 4 | display: 'flex', 5 | justifyContent: 'space-between', 6 | gap: { sm: '24px', md: '40px' }, 7 | flexWrap: 'wrap', 8 | wordBreak: 'break-word' 9 | }, 10 | userInfo: { 11 | root: { 12 | display: 'flex', 13 | justifyContent: 'center', 14 | alignItems: 'center', 15 | gap: '4px', 16 | maxWidth: '112px' 17 | }, 18 | avatar: { 19 | width: '80px', 20 | height: '80px', 21 | alignSelf: 'center', 22 | mb: '12px' 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/app-menu-button/AppMenuButton.styles.ts: -------------------------------------------------------------------------------- 1 | import palette from '~/styles/app-theme/app.pallete' 2 | import { TypographyVariantEnum } from '~/types' 3 | 4 | export const styles = { 5 | inputWrapper: { p: '15px 20px 0px 20px' }, 6 | input: { 7 | m: '0 auto', 8 | width: '100%', 9 | height: '40px', 10 | p: { xs: 0, sm: 0 } 11 | }, 12 | clearAll: { 13 | typography: TypographyVariantEnum.Subtitle2, 14 | m: '15px 20px 0 auto' 15 | }, 16 | clearIcon: { height: '18px', width: '18px' }, 17 | divider: { 18 | border: `1px solid ${palette.primary[200]}`, 19 | mt: '8px' 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/services/course-cooperation-service.ts: -------------------------------------------------------------------------------- 1 | import { CourseCooperationResponse } from '~/types' 2 | import { URLs } from '~/constants/request' 3 | import { baseService } from '~/services/base-service' 4 | import { getFullUrl } from '~/utils/get-full-url' 5 | 6 | export const CoursesAndCooperationsService = { 7 | getByResourceId: (resourceId: string) => { 8 | const url = getFullUrl({ 9 | pathname: URLs.coursesAndCooperations.getByResourceId, 10 | parameters: { resourceId } 11 | }) 12 | 13 | return baseService.request({ 14 | url, 15 | method: 'GET' 16 | }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/types/components/accordions/accordions.interface.ts: -------------------------------------------------------------------------------- 1 | import { SxProps } from '@mui/material' 2 | import { ReactNode } from 'react' 3 | 4 | export interface AccordionItem { 5 | title: string 6 | description?: string 7 | content?: ReactNode 8 | } 9 | export interface AccordionStyles { 10 | root?: SxProps 11 | accordion?: SxProps 12 | active?: SxProps 13 | inactive?: SxProps 14 | summary?: SxProps 15 | titleActive?: SxProps 16 | titleInactive?: SxProps 17 | details?: SxProps 18 | description?: SxProps 19 | } 20 | export interface AccordionSx { 21 | withIcon?: AccordionStyles 22 | noIcon?: AccordionStyles 23 | } 24 | -------------------------------------------------------------------------------- /src/types/finished-quizzes/types/finishedQuizzes.types.ts: -------------------------------------------------------------------------------- 1 | import type { CommonEntityFields } from '~/types' 2 | 3 | type Answer = { 4 | text: string 5 | isCorrect: boolean 6 | isChosen: boolean 7 | } 8 | 9 | export type Result = { 10 | question: string 11 | answers: Answer[] 12 | } 13 | 14 | export type CreateFinishedQuizParams = { 15 | quiz: string 16 | cooperation: string 17 | grade: number 18 | results: Result[] 19 | } 20 | 21 | export type UpdateFinishedQuizParams = { 22 | questionText: string 23 | newIsCorrect: boolean 24 | } 25 | 26 | export type FinishedQuiz = CreateFinishedQuizParams & CommonEntityFields 27 | -------------------------------------------------------------------------------- /src/components/enhanced-table/EnhancedTable.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | mb: '40px', 4 | '& .MuiPaper-root': { 5 | mt: '0' 6 | } 7 | }, 8 | paper: { 9 | mt: '10px', 10 | boxShadow: 'none', 11 | backgroundColor: 'inherit' 12 | }, 13 | noMatches: { 14 | backgroundColor: 'basic.grey', 15 | color: 'primary.700', 16 | typography: 'subtitle1', 17 | fontSize: '20px', 18 | py: '26px', 19 | display: 'flex', 20 | justifyContent: 'center', 21 | alignItems: 'center', 22 | columnGap: 1, 23 | borderRadius: '10px' 24 | }, 25 | loader: { py: '170px' } 26 | } 27 | -------------------------------------------------------------------------------- /src/containers/guest-home-page/signup-form/SignupForm.styles.js: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | input: { 3 | maxWidth: '343px' 4 | }, 5 | checkboxContainer: { 6 | mb: '20px' 7 | }, 8 | checkboxLabel: { 9 | mr: 0, 10 | '& .MuiFormControlLabel-label': { 11 | typography: 'subtitle2' 12 | } 13 | }, 14 | box: { 15 | display: 'flex', 16 | flexWrap: 'wrap', 17 | color: 'primary.700', 18 | whiteSpace: 'nowrap' 19 | }, 20 | signupButton: { 21 | width: '100%' 22 | }, 23 | underlineText: { 24 | color: 'primary.900', 25 | marginLeft: '5px', 26 | textDecoration: 'underline' 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/unit/pages/my-resources/MyResources.spec.jsx: -------------------------------------------------------------------------------- 1 | import { screen } from '@testing-library/react' 2 | import { renderWithProviders } from '~tests/test-utils' 3 | 4 | import MyResources from '~/pages/my-resources/MyResources' 5 | 6 | describe('MyResources', () => { 7 | beforeEach(() => { 8 | renderWithProviders() 9 | }) 10 | 11 | it('renders the component with tabs', () => { 12 | const tab1 = screen.getByText('myResourcesPage.tabs.quizzes') 13 | const tab2 = screen.getByText('myResourcesPage.tabs.attachments') 14 | 15 | expect(tab1).toBeInTheDocument() 16 | expect(tab2).toBeInTheDocument() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/components/search-autocomplete/SearchAutocomplete.styles.ts: -------------------------------------------------------------------------------- 1 | import palette from '~/styles/app-theme/app.pallete' 2 | 3 | export const styles = { 4 | container: { 5 | width: '100%', 6 | maxHeight: '48px', 7 | backgroundColor: 'basic.white', 8 | borderRadius: '70px', 9 | display: 'flex', 10 | alignItems: 'center' 11 | }, 12 | autocomplete: { 13 | flex: 1 14 | }, 15 | listBox: { 16 | maxHeight: 250 17 | }, 18 | searchIcon: { 19 | color: 'primary.700', 20 | mr: '15px' 21 | }, 22 | input: { 23 | bottom: '8px' 24 | }, 25 | inputLabel: { 26 | color: palette.primary[300] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/bookmarked-offers/BookmarkedOffers.styles.ts: -------------------------------------------------------------------------------- 1 | import { VisibilityEnum } from '~/types' 2 | 3 | export const styles = { 4 | title: { 5 | typography: { xs: 'h5', sm: 'h4' }, 6 | fontSize: { sm: '35px' }, 7 | lineHeight: { sm: '40px' }, 8 | mb: { xs: '20px', sm: '32px' } 9 | }, 10 | divider: { 11 | mb: '4px', 12 | backgroundColor: 'primary.200', 13 | width: '100%' 14 | }, 15 | notFound: { 16 | container: { 17 | my: '0', 18 | height: '100%' 19 | } 20 | }, 21 | 22 | pagination: (hide: boolean) => ({ 23 | visibility: hide ? VisibilityEnum.Hidden : VisibilityEnum.Visible 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /src/redux/socket-factory.ts: -------------------------------------------------------------------------------- 1 | import { io, Socket } from 'socket.io-client' 2 | 3 | export interface SocketInterface { 4 | socket: Socket 5 | } 6 | 7 | class SocketConnection implements SocketInterface { 8 | public socket: Socket 9 | 10 | constructor() { 11 | this.socket = io({ withCredentials: true }) 12 | } 13 | } 14 | 15 | let socketConnection: SocketConnection | undefined 16 | 17 | class SocketFactory { 18 | public static create(): SocketConnection { 19 | if (!socketConnection) { 20 | socketConnection = new SocketConnection() 21 | } 22 | return socketConnection 23 | } 24 | } 25 | 26 | export default SocketFactory 27 | -------------------------------------------------------------------------------- /tests/unit/design-system/components/Button/Button.spec.jsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react' 2 | 3 | import Button from '~scss-components/button/Button' 4 | 5 | const buttonText = 'Button' 6 | describe('Button test', () => { 7 | it('should render button text', () => { 8 | render() 9 | const text = screen.getByText(buttonText) 10 | expect(text).toBeInTheDocument() 11 | }) 12 | 13 | it('should render loader', () => { 14 | render() 15 | const loader = screen.getByTestId('loader') 16 | expect(loader).toBeInTheDocument() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | container_name: nginx-proxy 4 | # Build the nginx service using Dockerfile.stage 5 | build: 6 | context: . 7 | dockerfile: ./docker/Dockerfile.stage 8 | args: 9 | NGINX_VERSION: 1.19.6 10 | NODE_VERSION: 20.15.1 11 | ALPINE_VERSION: 3.14 12 | PROXY_API_URL: ${PROXY_API_URL:-http://localhost:8080} 13 | password: ${password} 14 | # Expose ports 80 and 2222 from the container 15 | ports: 16 | - '80:80' 17 | - '2222:2222' 18 | - '22:22' 19 | # Enable tty and stdin for the container 20 | stdin_open: true 21 | tty: true 22 | -------------------------------------------------------------------------------- /src/containers/find-offer/offer-filter-block/OfferFilterBlock.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: (open: boolean) => ({ 3 | maxWidth: '240px', 4 | color: 'primary.700', 5 | whiteSpace: 'nowrap', 6 | py: { xs: '2px', sm: 0 }, 7 | pr: { xs: 0, sm: open ? '40px' : 0 }, 8 | width: open ? '100%' : 0, 9 | overflow: open ? 'visible' : 'hidden', 10 | opacity: open ? 1 : 0, 11 | transition: 'all 0.3s', 12 | transformOrigin: 'left' 13 | }), 14 | selectWrapper: { 15 | display: 'flex', 16 | flexDirection: 'column', 17 | alignItems: 'start' 18 | }, 19 | content: { 20 | variant: 'body2' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/categories/Categories.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | titleWithDescription: { 3 | wrapper: { 4 | my: '30px', 5 | textAlign: 'center' 6 | }, 7 | title: { 8 | typography: { sm: 'h4', xs: 'h5' } 9 | }, 10 | description: { 11 | typography: { sm: 'body1', xs: 'body2' }, 12 | color: 'primary.500' 13 | } 14 | }, 15 | showAllOffers: { 16 | display: 'inline-flex', 17 | alignItems: 'center', 18 | columnGap: '10px', 19 | color: 'primary.500', 20 | textDecoration: 'none', 21 | m: '0 45px 20px 0' 22 | }, 23 | searchToolbar: { 24 | borderRadius: '70px' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/constants/translations/en/my-offers-page.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "student": "My Requests", 4 | "tutor": "My Offers" 5 | }, 6 | "tabs": { 7 | "all": "All", 8 | "active": "Active", 9 | "draft": "Draft", 10 | "closed": "Closed" 11 | }, 12 | "tableHeaders": { 13 | "title": "Title", 14 | "subject": "Subject", 15 | "price": "Price", 16 | "updated": "Last update", 17 | "status": "Status" 18 | }, 19 | "buttonLabel": { 20 | "student": "Create new request", 21 | "tutor": "Create new offer" 22 | }, 23 | "editButton": { 24 | "tutor": "Edit offer", 25 | "student": "Edit request" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/containers/my-resources/rename-input/RenameInput.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | inputWithIcons: { 5 | display: 'flex', 6 | columnGap: '10px', 7 | flex: 1 8 | }, 9 | input: { 10 | flex: 1, 11 | typography: TypographyVariantEnum.Subtitle2 12 | }, 13 | actions: { maxHeight: '36px' }, 14 | actionIcon: (color: string, disabled?: boolean) => ({ 15 | width: '16px', 16 | height: '16px', 17 | p: '4px', 18 | borderRadius: '6px', 19 | ...(!disabled && { color: `${color}.700` }), 20 | ...(!disabled && { backgroundColor: `${color}.100` }) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /src/components/offer-card/offer-details/OfferDetails.styles.js: -------------------------------------------------------------------------------- 1 | const ellipsisTextStyle = (linesCount) => ({ 2 | display: '-webkit-box', 3 | WebkitLineClamp: linesCount, 4 | lineClamp: linesCount, 5 | WebkitBoxOrient: 'vertical', 6 | boxOrient: 'vertical', 7 | overflow: 'hidden' 8 | }) 9 | 10 | export const styles = { 11 | bio: { 12 | ...ellipsisTextStyle(2), 13 | mb: '10px' 14 | }, 15 | title: { 16 | color: 'primary.700', 17 | ...ellipsisTextStyle(3) 18 | }, 19 | chipContainer: { 20 | my: '10px' 21 | }, 22 | description: { 23 | ...ellipsisTextStyle(4), 24 | color: 'primary.600', 25 | mb: '10px' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/constants/translations/en/signup.json: -------------------------------------------------------------------------------- 1 | { 2 | "head": { 3 | "tutor": "Sign up as a tutor", 4 | "student": "Sign up as a student" 5 | }, 6 | "iAgree": "I agree to the", 7 | "and": "and", 8 | "googleButton": "Sign up with Google", 9 | "continue": "or continue", 10 | "haveAccount": "Already have a Space2Study account?", 11 | "joinUs": "Login!", 12 | "confirmEmailTitle": "Your email address needs to be verified", 13 | "confirmEmailMessage": "We sent a confirmation email to: ", 14 | "confirmEmailDesc": " Check your email and click on the confirmation button to continue.", 15 | "terms": "Terms", 16 | "privacyPolicy": "Privacy policy" 17 | } 18 | -------------------------------------------------------------------------------- /src/containers/find-offer/offer-container/OfferContainer.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | offerContainer: (isGrid: boolean) => ({ 3 | flexGrow: 1, 4 | my: '20px', 5 | display: 'grid', 6 | gap: '24px', 7 | gridTemplateColumns: isGrid 8 | ? { 9 | xs: '1fr', 10 | md: 'repeat(3, 1fr)' 11 | } 12 | : '1fr' 13 | }), 14 | gridItem: { width: '100%' }, 15 | appCard: { 16 | p: { sm: '30px 20px', md: '30px 20px' }, 17 | alignSelf: 'flex-start' 18 | }, 19 | appCardSquare: { 20 | minHeight: '460px', 21 | maxHeight: '491px', 22 | p: '24px 20px', 23 | boxSizing: 'border-box' 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/containers/quiz/points/Points.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box' 2 | import Chip from '@mui/material/Chip' 3 | import Typography from '@mui/material/Typography' 4 | 5 | import styles from '~/containers/quiz/points/Points.styles' 6 | 7 | type PointsProps = { 8 | points: number 9 | totalPoints: number 10 | title: string 11 | } 12 | 13 | const Points = ({ points, totalPoints, title }: PointsProps) => { 14 | return ( 15 | 16 | {title} 17 | 18 | 19 | ) 20 | } 21 | 22 | export default Points 23 | -------------------------------------------------------------------------------- /src/utils/cn.ts: -------------------------------------------------------------------------------- 1 | export function cn(...args: unknown[]): string { 2 | return args 3 | .flat(5) 4 | .reduce((previousValue, currentValue) => { 5 | if (!currentValue) return previousValue 6 | 7 | if (typeof currentValue === 'object') { 8 | Object.entries(currentValue).forEach(([k, v]) => { 9 | if (v) previousValue.push(k) 10 | }) 11 | return previousValue 12 | } 13 | 14 | if (typeof currentValue === 'string') { 15 | previousValue.push(currentValue) 16 | return previousValue 17 | } 18 | 19 | return previousValue 20 | }, []) 21 | .join(' ') 22 | .trim() 23 | } 24 | -------------------------------------------------------------------------------- /tests/unit/pages/my-courses/MyCourses.spec.constans.js: -------------------------------------------------------------------------------- 1 | export const mockCourse = { 2 | proficiencyLevel: ['Beginner'], 3 | title: 'Advanced Lineal Math: Theoretical Concepts', 4 | description: 'The mathematical language of quantum mechanics', 5 | languages: ['English'], 6 | subject: { 7 | _id: '648850c4fdc2d1a130c24aea', 8 | name: 'Quantum Mechanics' 9 | }, 10 | category: { 11 | _id: '64884f21fdc2d1a130c24ac0', 12 | appearance: { 13 | icon: 'mocked-path-to-icon', 14 | color: '#66C42C' 15 | } 16 | }, 17 | sections: [{}, {}, {}], 18 | createdAt: '2023-09-19T12:12:25.098Z', 19 | updatedAt: '2023-09-19T12:17:10.447Z' 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/my-resources/MyResources.styles.ts: -------------------------------------------------------------------------------- 1 | import palette from '~/styles/app-theme/app.pallete' 2 | import { TypographyVariantEnum } from '~/types' 3 | 4 | export const styles = { 5 | title: { 6 | typography: TypographyVariantEnum.H4, 7 | mb: '40px' 8 | }, 9 | tabs: { 10 | root: { 11 | '& > button:last-child': { 12 | position: 'relative' 13 | }, 14 | '& > button:last-child:before': { 15 | content: '" "', 16 | position: 'absolute', 17 | height: '32px', 18 | left: '0', 19 | borderLeft: `1px solid ${palette.primary[100]}` 20 | } 21 | }, 22 | tab: { minWidth: '150px' } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/containers/edit-profile/password-security-tab/password-security-item/PasswordSecurityItem.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | container: { 5 | display: 'flex', 6 | justifyContent: 'space-between', 7 | alignItems: 'flex-start', 8 | m: '20px 0' 9 | }, 10 | title: { 11 | typography: TypographyVariantEnum.Button 12 | }, 13 | description: { 14 | typography: TypographyVariantEnum.Body2, 15 | color: 'primary.500' 16 | }, 17 | titlesAndButtonContainer: { 18 | p: '20px 0' 19 | }, 20 | appButton: { 21 | mt: '20px', 22 | width: '192px', 23 | height: '40px' 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/info-card/InfoCard.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | card: { 3 | flexDirection: 'column', 4 | justifyContent: 'space-between', 5 | padding: { xs: '24px 32px', lg: '40px 70px' } 6 | }, 7 | imgTitleDescription: { 8 | titleWithDescription: { 9 | title: { 10 | typography: 'h5', 11 | marginBottom: '16px' 12 | }, 13 | description: { 14 | typography: { md: 'body1', xs: 'body2' } 15 | } 16 | }, 17 | img: { 18 | marginBottom: '24px' 19 | }, 20 | root: { 21 | textAlign: 'center', 22 | mb: '24px' 23 | } 24 | }, 25 | button: { 26 | alignSelf: 'center' 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/containers/guest-home-page/forgot-password/ForgotPassword.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | root: { 3 | maxWidth: '350px', 4 | padding: { xs: '50px 16px', sm: '70px', md: '96px' } 5 | }, 6 | 7 | sentPassword: { 8 | mb: 1, 9 | width: '100%' 10 | }, 11 | backButton: { 12 | textDecoration: 'underline', 13 | width: '100%', 14 | p: '16px 0' 15 | }, 16 | titleWithDescription: { 17 | wrapper: { 18 | textAlign: 'start', 19 | m: 0 20 | }, 21 | title: { 22 | typography: 'h5', 23 | fontWeight: 500, 24 | mb: '16px' 25 | }, 26 | description: { 27 | typography: 'body2' 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/unit/containers/edit-profile/profile-tab/profile-tab-form/ProfileTabForm.spec.constants.js: -------------------------------------------------------------------------------- 1 | import { UserRoleEnum } from '~/types' 2 | 3 | export const userDataMock = { 4 | _id: 1, 5 | role: [UserRoleEnum.Tutor], 6 | firstName: '', 7 | lastName: '', 8 | mainSubjects: { student: [], tutor: [] }, 9 | nativeLanguage: null, 10 | address: { country: null, city: null }, 11 | photo: 'photo.png', 12 | videoLink: { student: '', tutor: '' } 13 | } 14 | 15 | export const formDataMock = { 16 | firstName: '', 17 | lastName: '', 18 | mainSubjects: [], 19 | nativeLanguage: null, 20 | country: null, 21 | city: null, 22 | photo: 'photo.png', 23 | videoLink: '' 24 | } 25 | -------------------------------------------------------------------------------- /src/containers/layout/admin-portal/admin-nav-bar-item/AdminNavBarItem.styles.js: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | stableWidth: { 3 | width: '250px' 4 | }, 5 | wrapper: { 6 | display: 'flex', 7 | marginTop: 2 8 | }, 9 | icon: { 10 | justifyContent: 'center' 11 | }, 12 | label: { 13 | margin: 0 14 | }, 15 | subItem: { 16 | pl: 7, 17 | py: 0, 18 | mt: 1, 19 | '&::before': { 20 | content: '"•"', 21 | pr: 5 22 | } 23 | }, 24 | active: { 25 | backgroundColor: 'primary.500', 26 | width: '2px', 27 | float: 'right', 28 | display: 'inline-block' 29 | }, 30 | activeSubItem: { 31 | fontWeight: 600 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/containers/layout/admin-portal/admin-nav-bar/AdminNavBar.styles.js: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | navBar: { 3 | boxSizing: 'border-box', 4 | width: '90px', 5 | backgroundColor: 'primary.50', 6 | paddingTop: 5, 7 | transition: '.5s', 8 | overflow: 'hidden', 9 | display: 'flex', 10 | flexDirection: 'column' 11 | }, 12 | expanded: { 13 | width: '250px' 14 | }, 15 | divider: { 16 | width: '80%', 17 | margin: '0 auto' 18 | }, 19 | listItem: { 20 | marginBottom: 2, 21 | flexGrow: 0 22 | }, 23 | openButton: { 24 | justifyContent: 'flex-end', 25 | '& > div': { 26 | justifyContent: 'center' 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/containers/my-cooperations/cooperation-notes/CooperationNotes.styles.ts: -------------------------------------------------------------------------------- 1 | import palette from '~/styles/app-theme/app.pallete' 2 | 3 | export const styles = { 4 | divider: { 5 | mx: '16px', 6 | border: `1px solid ${palette.primary[200]}` 7 | }, 8 | notesWrapper: { 9 | display: 'flex', 10 | justifyContent: 'space-between' 11 | }, 12 | notesIcon: { 13 | display: 'flex', 14 | justifyContent: 'space-between', 15 | width: '400px', 16 | '& > p': { 17 | color: palette.basic.bismark, 18 | fontWeight: 500 19 | }, 20 | '& > svg': { 21 | width: '25px', 22 | height: '25px', 23 | cursor: 'pointer' 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/tab/Tab.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from 'react' 2 | import Button, { ButtonProps } from '@mui/material/Button' 3 | 4 | import { spliceSx } from '~/utils/helper-functions' 5 | import { styles } from '~/components/tab/Tab.styles' 6 | 7 | interface TabProps extends ButtonProps { 8 | activeTab: boolean 9 | onClick: () => void 10 | children: ReactNode 11 | } 12 | 13 | const Tab: FC = ({ activeTab, onClick, children, sx, ...props }) => { 14 | return ( 15 | 22 | ) 23 | } 24 | 25 | export default Tab 26 | -------------------------------------------------------------------------------- /src/components/video-player/VideoPlayer.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import { Box } from '@mui/material' 3 | import ReactPlayer from 'react-player/youtube' 4 | import { styles } from '~/components/video-player/VideoPlayer.styles' 5 | 6 | interface VideoPlayerProps { 7 | video: string 8 | } 9 | 10 | const VideoPlayer: FC = ({ video }) => { 11 | return ( 12 | 13 | 21 | 22 | ) 23 | } 24 | 25 | export default VideoPlayer 26 | -------------------------------------------------------------------------------- /src/utils/toggle-bookmark.ts: -------------------------------------------------------------------------------- 1 | import { userService } from '~/services/user-service' 2 | import useMutation from '~/hooks/use-mutation' 3 | import { UseAxiosProps } from '~/hooks/use-axios' 4 | 5 | export const useToggleBookmark = ( 6 | userId: string, 7 | onResponse: UseAxiosProps['onResponse'], 8 | onResponseError: UseAxiosProps['onResponseError'] 9 | ) => { 10 | const { mutate: toggleBookmark } = useMutation({ 11 | queryKey: ['bookmarks'], 12 | mutationFn: (offerID: string) => 13 | userService.toggleBookmark(userId, offerID), 14 | onSuccess: onResponse, 15 | onError: onResponseError 16 | }) 17 | 18 | return toggleBookmark 19 | } 20 | -------------------------------------------------------------------------------- /src/components/not-found-results/NotFoundResults.styles.ts: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | container: { 3 | display: 'flex', 4 | flexDirection: 'column', 5 | alignItems: 'center', 6 | justifyContent: 'center', 7 | maxWidth: '488px', 8 | mx: 'auto', 9 | my: { xs: '50px', sm: '75px', md: '100px' } 10 | }, 11 | imgTitleDescription: { 12 | root: { textAlign: 'center' }, 13 | titleWithDescription: { 14 | title: { 15 | typography: 'h5', 16 | m: { xs: '10px 0', sm: '25px 0 10px' } 17 | }, 18 | text: { 19 | typography: { xs: 'body2', sm: 'body1' } 20 | } 21 | } 22 | }, 23 | button: { 24 | mt: '33px' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/subject-level-chips/SubjectLevelChips.styles.ts: -------------------------------------------------------------------------------- 1 | import { alpha } from '@mui/material/styles' 2 | 3 | export const styles = { 4 | chips: { 5 | display: 'flex', 6 | flexWrap: 'wrap', 7 | alignItems: 'start', 8 | gap: '4px' 9 | }, 10 | subjectChipLabel: { 11 | typography: 'overline', 12 | fontWeight: '500', 13 | color: 'primary.900' 14 | }, 15 | levelChipLabel: { 16 | typography: 'overline' 17 | }, 18 | subjectChip: (color: string) => ({ 19 | backgroundColor: alpha(color, 0.6), 20 | cursor: 'inherit' 21 | }), 22 | levelChip: (color: string) => ({ 23 | backgroundColor: alpha(color, 0.2), 24 | cursor: 'inherit' 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/containers/my-cooperations/cooperation-action-input/CooperationActionInput.styles.ts: -------------------------------------------------------------------------------- 1 | import { type SxProps } from '@mui/material' 2 | 3 | import palette from '~/styles/app-theme/app.pallete' 4 | 5 | export const styles = { 6 | inputBox: { 7 | mb: '8px' 8 | }, 9 | inputContainer: { 10 | display: 'flex', 11 | gap: '16px', 12 | height: '50px', 13 | mt: '10px' 14 | }, 15 | divider: { 16 | mb: '16px' 17 | }, 18 | inputField: { 19 | flex: 1 20 | }, 21 | textGray: { 22 | color: palette.basic.lightBlue 23 | }, 24 | textMediumGray: { 25 | color: palette.basic.lightBlue, 26 | fontWeight: 'medium' 27 | } 28 | } satisfies Record 29 | -------------------------------------------------------------------------------- /tests/unit/utils/course-custom-options.spec.jsx: -------------------------------------------------------------------------------- 1 | import { expect } from 'vitest' 2 | 3 | import { customOptions } from '~/utils/course-custom-options' 4 | 5 | const mockCategories = [ 6 | { _id: '6255bc080a75adf9223df444', name: 'Category1' }, 7 | { _id: '6255bc080a75adf9223df445', name: 'Category2' } 8 | ] 9 | 10 | const userOptionsMock = ['Category1', 'Category3'] 11 | 12 | describe('CourseCustomOptions', () => { 13 | it('Should return array with title property', () => { 14 | const result = customOptions(mockCategories, userOptionsMock) 15 | 16 | expect(result[0].title).toEqual('course.yourCategories') 17 | expect(result[1].title).toEqual('course.otherCategories') 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/constants/translations/uk/my-offers-page.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "student": "Мої запити", 4 | "tutor": "Мої пропозиції" 5 | }, 6 | "tabs": { 7 | "all": "Всі", 8 | "active": "Активні", 9 | "draft": "Чернетки", 10 | "closed": "Закриті" 11 | }, 12 | "tableHeaders": { 13 | "title": "Заголовок", 14 | "subject": "Предмет", 15 | "price": "Ціна", 16 | "updated": "Останнє оновлення", 17 | "status": "Статус" 18 | }, 19 | "buttonLabel": { 20 | "student": "Створити новий запит", 21 | "tutor": "Створити нову пропозицію" 22 | }, 23 | "editButton": { 24 | "tutor": "Редагувати пропозицію", 25 | "student": "Редагувати запит" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/app-select-button/AppSelectButton.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from 'react' 2 | import { Checkbox, MenuItem } from '@mui/material' 3 | 4 | import { styles } from '~/components/app-select-button/AppSelectButton.styles' 5 | 6 | interface AppSelectButtonProps { 7 | onMenuItemClick: () => void 8 | checked: boolean 9 | children: ReactNode 10 | } 11 | 12 | const AppSelectButton: FC = ({ 13 | checked, 14 | children, 15 | onMenuItemClick 16 | }) => { 17 | return ( 18 | 19 | 20 | {children} 21 | 22 | ) 23 | } 24 | 25 | export default AppSelectButton 26 | -------------------------------------------------------------------------------- /src/pages/edit-profile/EditProfile.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | headerContainer: { 5 | display: 'flex', 6 | justifyContent: 'space-between', 7 | alignItems: 'center' 8 | }, 9 | title: { 10 | typography: TypographyVariantEnum.H4 11 | }, 12 | description: { 13 | typography: TypographyVariantEnum.Subtitle1, 14 | color: 'basic.gray' 15 | }, 16 | line: { 17 | m: '16px 0' 18 | }, 19 | updateBtn: { 20 | padding: '10px' 21 | }, 22 | mainContainer: { 23 | display: 'flex', 24 | justifyContent: 'space-between', 25 | gap: '24px' 26 | }, 27 | mainContent: { 28 | width: '65%' 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/unit/hooks/mock-categories.js: -------------------------------------------------------------------------------- 1 | export const mockCategories = [ 2 | { 3 | id: '1', 4 | img: 'img.pmg', 5 | count: 234, 6 | title: 'Languages', 7 | link: '#' 8 | }, 9 | { 10 | id: '2', 11 | img: 'img.pmg', 12 | count: 234, 13 | title: 'Mathematics', 14 | link: '#' 15 | }, 16 | { 17 | id: '3', 18 | img: 'img.pmg', 19 | count: 234, 20 | title: 'Computer science', 21 | link: '#' 22 | }, 23 | { 24 | id: '4', 25 | img: 'img.pmg', 26 | count: 234, 27 | title: 'Music', 28 | link: '#' 29 | }, 30 | { 31 | id: '5', 32 | img: 'img.pmg', 33 | count: 234, 34 | title: 'Design', 35 | link: '#' 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /src/containers/cooperation-details/cooperation-activities/CooperationActivities.constants.tsx: -------------------------------------------------------------------------------- 1 | import { ResponseError } from '~/exceptions' 2 | import { ResourcesAvailabilityEnum } from '~/types' 3 | 4 | export const cooperationTranslationKeys = [ 5 | { 6 | title: 'cooperationDetailsPage.select.openAll', 7 | value: ResourcesAvailabilityEnum.OpenAll 8 | }, 9 | { 10 | title: 'cooperationDetailsPage.select.openManually', 11 | value: ResourcesAvailabilityEnum.OpenManually 12 | } 13 | ] 14 | 15 | export const OpenFromError = new ResponseError({ 16 | status: 409, 17 | code: 'VALIDATION_ERROR', 18 | message: 19 | 'Cooperation validation failed: OpenFrom:OpenFrom should be with date.' 20 | }) 21 | -------------------------------------------------------------------------------- /tests/unit/components/app-rating-mobile/AppRatingMobile.spec.jsx: -------------------------------------------------------------------------------- 1 | import { screen } from '@testing-library/react' 2 | import AppRatingMobile from '~/components/app-rating-mobile/AppRatingMobile' 3 | import { renderWithProviders } from '~tests/test-utils' 4 | 5 | const mockState = { 6 | appMain: { userRole: 'tutor' } 7 | } 8 | 9 | describe('AppRatingMobile component', () => { 10 | it('should display a star icon', () => { 11 | const props = { value: 4.5, reviewsCount: 5 } 12 | renderWithProviders(, { 13 | preloadedState: mockState 14 | }) 15 | 16 | const starIcon = screen.queryByTestId('star-icon') 17 | expect(starIcon).toBeInTheDocument() 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/containers/navigation-icons/NavigationIcons.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import { useAppSelector } from '~/hooks/use-redux' 3 | 4 | import GuestIcons from '~/containers/navigation-icons/guest-icons/GuestIcons' 5 | import UserIcons from '~/containers/navigation-icons/user-icons/UserIcons' 6 | 7 | interface NavigationIconsProps { 8 | setSidebarOpen: () => void 9 | } 10 | 11 | const NavigationIcons: FC = ({ setSidebarOpen }) => { 12 | const { userRole } = useAppSelector((state) => state.appMain) 13 | 14 | if (!userRole) return 15 | 16 | return 17 | } 18 | 19 | export default NavigationIcons 20 | -------------------------------------------------------------------------------- /src/services/location-service.ts: -------------------------------------------------------------------------------- 1 | import { URLs } from '~/constants/request' 2 | import { type Country } from '~/types' 3 | import { getFullUrl } from '~/utils/get-full-url' 4 | import { baseService } from './base-service' 5 | 6 | export const locationService = { 7 | getCountries: () => { 8 | return baseService.request({ 9 | method: 'GET', 10 | url: URLs.location.getCountries 11 | }) 12 | }, 13 | getCitiesByCountryName: (countryName: string) => { 14 | return baseService.request({ 15 | method: 'GET', 16 | url: getFullUrl({ 17 | pathname: URLs.location.getCitiesByCountryName, 18 | parameters: { countryName } 19 | }) 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/app-select/AppSelect.styles.ts: -------------------------------------------------------------------------------- 1 | import { TypographyVariantEnum } from '~/types' 2 | 3 | export const styles = { 4 | selectField: { 5 | height: '46px', 6 | minWidth: '115px' 7 | }, 8 | selectContainer: { 9 | display: 'flex', 10 | alignItems: 'center', 11 | flex: 1, 12 | '& .MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline': { 13 | borderColor: 'primary.200' 14 | } 15 | }, 16 | selectTitle: { 17 | typography: TypographyVariantEnum.Body1, 18 | color: 'primary.500', 19 | mr: '8px', 20 | minWidth: 'fit-content' 21 | }, 22 | formControl: { 23 | '& label': { 24 | lineHeight: 'inherit', 25 | color: 'primary.500' 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/containers/my-resources/add-categories-modal/AddCategoriesModal.constants.ts: -------------------------------------------------------------------------------- 1 | import { emptyField, textField } from '~/utils/validations/common' 2 | 3 | export const initialValues = { 4 | name: '' 5 | } 6 | 7 | const valueNotIn = (value: string, disallowedValues: string[]) => 8 | disallowedValues.includes(value) 9 | ? 'myResourcesPage.categories.categoryAlreadyExistsError' 10 | : undefined 11 | 12 | export const validations = (disallowedValues: string[]) => ({ 13 | name: (value: string) => { 14 | const trimmedValue = value.trim() 15 | return ( 16 | emptyField({ value, helperText: textField(2, 35)(trimmedValue) }) ?? 17 | valueNotIn(trimmedValue, disallowedValues) 18 | ) 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /src/router/constants/loaders.ts: -------------------------------------------------------------------------------- 1 | import { LoaderFunctionArgs } from 'react-router-dom' 2 | import { userService } from '~/services/user-service' 3 | import { UserRoleEnum } from '~/types' 4 | import { queryClient } from '~/plugins/queryClient' 5 | 6 | export const userProfileLoader = async ({ 7 | request, 8 | params 9 | }: LoaderFunctionArgs) => { 10 | const role = new URL(request.url).searchParams.get('role') as UserRoleEnum 11 | await queryClient.prefetchQuery({ 12 | queryKey: ['user', params.id, role], 13 | queryFn: () => 14 | userService.getUserByIdWithBaseService(params.id ?? '', role), 15 | staleTime: Infinity 16 | }) 17 | 18 | return { 19 | _id: params.id, 20 | role 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/scroll-to-top-button/ScrollToTopButton.styles.ts: -------------------------------------------------------------------------------- 1 | import { commonHoverShadow } from '~/styles/app-theme/custom-shadows' 2 | 3 | export const styles = { 4 | root: { 5 | display: 'flex', 6 | justifyContent: 'flex-end', 7 | position: 'sticky', 8 | bottom: '0' 9 | }, 10 | button: { 11 | position: 'absolute', 12 | bottom: '0', 13 | m: { xs: '0 8px 8px 0', md: '0 12px 12px 0', lg: '0 20px 20px 0' }, 14 | cursor: 'pointer', 15 | backgroundColor: 'primary.50', 16 | opacity: 0.7, 17 | boxShadow: commonHoverShadow, 18 | '&:hover': { opacity: 1, backgroundColor: 'primary.100' } 19 | }, 20 | icon: { 21 | fontSize: '24px', 22 | color: 'primary.900' 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/containers/app-content/AppContent.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box' 2 | 3 | import AppHeader from '~/containers/layout/app-header/AppHeader' 4 | import AppMain from '~/containers/layout/app-main/AppMain' 5 | import AppSnackbar from '~/containers/layout/app-snackbar/AppSnackbar' 6 | import CookieConsentBanner from '~/containers/cookie-consent-banner/CookieConsentBanner' 7 | import { styles } from '~/containers/app-content/AppContent.styles' 8 | 9 | const AppContent = () => { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | export default AppContent 21 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import { RouterProvider } from 'react-router-dom' 4 | import { Provider } from 'react-redux' 5 | 6 | import { store } from '~/redux/store' 7 | import { setupInterceptors } from '~/services/setup-interceptors' 8 | import { router } from '~/router/router' 9 | import '~/styles/index.css' 10 | import '~scss/styles.scss' 11 | import '~/plugins/i18n' 12 | 13 | const root = createRoot(document.getElementById('root') as HTMLElement) 14 | 15 | root.render( 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | 23 | setupInterceptors() 24 | -------------------------------------------------------------------------------- /tests/unit/containers/tutor-profile/video-presentation/VideoPresentation.spec.jsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react' 2 | import VideoPresentation from '~/containers/user-profile/video-presentation/VideoPresentation' 3 | 4 | describe('VideoPresentation component', () => { 5 | beforeEach(() => { 6 | render() 7 | }) 8 | 9 | it('should render title text', () => { 10 | const title = screen.getByText('userProfilePage.videoPresentation.title') 11 | 12 | expect(title).toBeInTheDocument() 13 | }) 14 | 15 | it('should render VideoBox component', () => { 16 | const videoBox = screen.getByTestId('videoBox') 17 | 18 | expect(videoBox).toBeInTheDocument() 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /tests/unit/utils/error-with-message.spec.jsx: -------------------------------------------------------------------------------- 1 | import { expect } from 'vitest' 2 | import { getErrorMessage } from '~/utils/error-with-message' 3 | 4 | const mockedError = { 5 | code: 'VALIDATION_ERROR', 6 | message: 7 | 'Course validation failed: title: The title field cannot be empty., description: The description field cannot be empty.', 8 | status: 409 9 | } 10 | 11 | const expectedMessage = 12 | ' The title field cannot be empty., The description field cannot be empty.' 13 | 14 | describe('getErrorMessage', () => { 15 | it('Should return data about non-valid fields', () => { 16 | const resultMessage = getErrorMessage(mockedError.message) 17 | expect(resultMessage).toBe(expectedMessage) 18 | }) 19 | }) 20 | --------------------------------------------------------------------------------