├── .env.example ├── .eslintrc.json ├── .gitignore ├── .husky └── pre-push ├── .prettierrc.json ├── .vscode ├── launch.json └── settings.json ├── README.md ├── actions ├── actions.ts ├── auth │ └── authActions.ts ├── contact.ts └── dashboard │ ├── AI │ ├── ExamPreparationActions.tsx │ ├── KnowMeActions.tsx │ └── TaskSandboxActions.tsx │ ├── chatActions.ts │ ├── courseActions.ts │ ├── exercisesActions.ts │ ├── lessonsAction.ts │ ├── linkProductToCourse.ts │ ├── notificationsActions.ts │ ├── studentActions.ts │ ├── testActions.ts │ └── userActions.ts ├── app ├── [locale] │ ├── (front) │ │ ├── about │ │ │ └── page.tsx │ │ ├── checkout │ │ │ ├── cancel │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── success │ │ │ │ └── page.tsx │ │ ├── contact │ │ │ ├── page.tsx │ │ │ └── support │ │ │ │ ├── error.tsx │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── plans │ │ │ ├── [planId] │ │ │ │ ├── error.tsx │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── store │ │ │ ├── [productId] │ │ │ │ ├── error.tsx │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── stripe │ │ │ ├── checkout │ │ │ │ └── route.ts │ │ │ └── webhook │ │ │ │ └── route.ts │ │ ├── student │ │ │ └── [studentId] │ │ │ │ └── exercises │ │ │ │ └── [exerciseId] │ │ │ │ └── page.tsx │ │ └── waiting-room │ │ │ └── page.tsx │ ├── auth │ │ ├── callback │ │ │ └── route.ts │ │ ├── confirm │ │ │ └── route.ts │ │ ├── error.tsx │ │ ├── error │ │ │ └── page.tsx │ │ ├── forgot-password │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── login │ │ │ └── page.tsx │ │ ├── reset-password │ │ │ └── page.tsx │ │ ├── signout │ │ │ └── route.ts │ │ └── signup │ │ │ └── page.tsx │ ├── dashboard │ │ ├── account │ │ │ ├── orders │ │ │ │ └── columns.tsx │ │ │ └── page.tsx │ │ ├── admin │ │ │ ├── courses │ │ │ │ ├── [courseId] │ │ │ │ │ ├── lessons │ │ │ │ │ │ ├── [lessonId] │ │ │ │ │ │ │ ├── edit │ │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── lessonsCols.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── tests │ │ │ │ │ │ ├── [testId] │ │ │ │ │ │ │ ├── edit │ │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ │ ├── review │ │ │ │ │ │ │ │ └── [submissionId] │ │ │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ │ └── testSubmissionsCols.tsx │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── testsCols.tsx │ │ │ │ ├── courseCols.tsx │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── error.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── notifications │ │ │ ├── error.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── page.tsx │ │ ├── student │ │ │ ├── account │ │ │ │ ├── edit │ │ │ │ │ └── page.tsx │ │ │ │ ├── know-me │ │ │ │ │ └── page.tsx │ │ │ │ ├── loading.tsx │ │ │ │ ├── orders │ │ │ │ │ ├── columns.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── subscriptions │ │ │ │ │ └── page.tsx │ │ │ ├── chat │ │ │ │ ├── [chatId] │ │ │ │ │ ├── exam-prep │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ ├── layout.tsx │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── free-chat │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── loading.tsx │ │ │ │ ├── exam-prep │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── loading.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ │ ├── courses │ │ │ │ ├── [courseId] │ │ │ │ │ ├── error.tsx │ │ │ │ │ ├── exams │ │ │ │ │ │ ├── [examId] │ │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ │ └── review │ │ │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── exercises │ │ │ │ │ │ ├── [exerciseId] │ │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── lessons │ │ │ │ │ │ ├── [lessonsId] │ │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── error.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ │ ├── error.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ └── teacher │ │ │ ├── account │ │ │ ├── edit │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ │ ├── chat │ │ │ ├── [chatId] │ │ │ │ ├── exam-prep │ │ │ │ │ ├── error.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── free-chat │ │ │ │ │ ├── error.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── loading.tsx │ │ │ ├── exam-prep │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ │ ├── courses │ │ │ ├── [courseId] │ │ │ │ ├── edit │ │ │ │ │ └── page.tsx │ │ │ │ ├── error.tsx │ │ │ │ ├── exercises │ │ │ │ │ ├── [exerciseId] │ │ │ │ │ │ ├── edit │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── exercisesCols.tsx │ │ │ │ ├── lessons │ │ │ │ │ ├── [lessonId] │ │ │ │ │ │ ├── edit │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── lessonsCols.tsx │ │ │ │ ├── loading.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── tests │ │ │ │ │ ├── [testId] │ │ │ │ │ │ ├── edit │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ ├── review │ │ │ │ │ │ │ └── [submissionId] │ │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ └── testSubmissionsCols.tsx │ │ │ │ │ ├── error.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ └── testsCols.tsx │ │ │ ├── courseCols.tsx │ │ │ ├── error.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ │ ├── error.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ ├── layout.tsx │ ├── not-found.tsx │ └── page.tsx ├── api │ ├── binance │ │ └── route.ts │ ├── chat │ │ ├── exercises │ │ │ ├── route.ts │ │ │ └── student │ │ │ │ └── route.ts │ │ ├── home │ │ │ └── route.ts │ │ └── route.ts │ ├── chatbox-ai │ │ └── route.ts │ ├── exams │ │ ├── [examId] │ │ │ └── ai-data │ │ │ │ └── route.ts │ │ ├── review │ │ │ └── route.ts │ │ └── submit │ │ │ └── route.ts │ ├── exercises │ │ ├── code │ │ │ └── route.ts │ │ └── route.ts │ ├── lessons │ │ └── ai-task │ │ │ └── route.ts │ ├── plans │ │ └── checkout │ │ │ └── route.ts │ ├── sentry-example-api │ │ └── route.ts │ ├── stripe │ │ └── create-payment-intent │ │ │ └── route.ts │ └── test │ │ └── route.ts ├── favicon.ico ├── global-error.tsx ├── globals.css ├── layout.tsx ├── locales │ ├── client.ts │ ├── en │ │ ├── components.ts │ │ ├── en.ts │ │ └── views.ts │ ├── es │ │ ├── components.ts │ │ ├── es.ts │ │ └── views.ts │ └── server.ts ├── opengraph-image.png └── twitter-image.png ├── components.json ├── components ├── AuthButton.tsx ├── Code.tsx ├── ConnectSupabaseSteps.tsx ├── DarkThemeToggle.tsx ├── DeployButton.tsx ├── Footer.tsx ├── GenericError.tsx ├── Header.tsx ├── NextLogo.tsx ├── ScrollToTopButton.tsx ├── SignUpUserSteps.tsx ├── Step.tsx ├── SupabaseLogo.tsx ├── auth │ ├── PasswordComponent.tsx │ ├── ResetPasswordForm.tsx │ ├── UserLoginForm.tsx │ ├── UserSignupForm.tsx │ └── auth-form.tsx ├── chatbox │ ├── ChatBox.tsx │ └── WebSearchResult.tsx ├── checkout │ ├── CheckoutCard.tsx │ ├── CheckoutForm.tsx │ ├── CheckoutImages.tsx │ └── CheckoutStripeWrapper.tsx ├── contact │ └── TicketReview.tsx ├── courses │ ├── SubmitedAnswers.tsx │ └── exams │ │ ├── ExamHeader.tsx │ │ ├── Question.tsx │ │ └── QuestionSection.tsx ├── dashboards │ ├── ButtonSubmitDashbaord.tsx │ ├── Common │ │ ├── BigSidebar.tsx │ │ ├── CommandDialogComponent.tsx │ │ ├── CommentsSections.tsx │ │ ├── CopyToClipboardButton.tsx │ │ ├── DashboardHeaderSheet.tsx │ │ ├── DashboardLayout.tsx │ │ ├── Editor │ │ │ ├── CompletedMessage.tsx │ │ │ ├── MonacoEditor.tsx │ │ │ ├── MySandpackEditor.tsx │ │ │ └── TestCompletionHandler.tsx │ │ ├── ExpandableText.tsx │ │ ├── ProfileDropdown.tsx │ │ ├── account │ │ │ └── AccountEditPage.tsx │ │ ├── chat │ │ │ ├── ApprovalButton.tsx │ │ │ ├── ChatTabs.tsx │ │ │ ├── ChatWidget.tsx │ │ │ ├── EditMessage.tsx │ │ │ ├── MarkdownEditor.tsx │ │ │ ├── Message.tsx │ │ │ ├── MessageContentWrapper.tsx │ │ │ ├── MessageFeaturesSection.tsx │ │ │ ├── MesssageItem.tsx │ │ │ ├── NotApprovedMessage.tsx │ │ │ ├── SuggestionsContainer.tsx │ │ │ ├── chat.tsx │ │ │ └── hooks │ │ │ │ └── useApprovalHandler.ts │ │ ├── comments │ │ │ └── CommentsCard.tsx │ │ ├── lessons │ │ │ └── SidebarLessons.tsx │ │ ├── reviews │ │ │ ├── AiReview.tsx │ │ │ ├── ListOfReviews.tsx │ │ │ └── ReviewForm.tsx │ │ ├── table │ │ │ └── ScopedHeader.tsx │ │ └── tour │ │ │ └── MarkdownEditorTour.tsx │ ├── DashboardHeader.tsx │ ├── SelectStatus.tsx │ ├── Sidebar.tsx │ ├── SidebarLink.tsx │ ├── StateMessages.tsx │ ├── admin │ │ └── course │ │ │ └── ConvertCourseToProduct.tsx │ ├── cards │ │ ├── CourseCards.tsx │ │ ├── NewsCards.tsx │ │ ├── ReviewCard.tsx │ │ └── TestsCards.tsx │ ├── chat │ │ ├── ChatCreationButton.tsx │ │ ├── ChatLoadingSkeleton.tsx │ │ ├── ChatSidebarItem.tsx │ │ ├── ChatSidebarMobile.tsx │ │ ├── Chatsv2 │ │ │ ├── ChatContent.tsx │ │ │ ├── ChatInput.tsx │ │ │ ├── ChatMessages.tsx │ │ │ ├── MarkdowEditorInput.tsx │ │ │ └── MessageItem.tsx │ │ ├── EmptyChatState.tsx │ │ ├── ExamFeedbackCard.tsx │ │ ├── ExamLink.tsx │ │ ├── ExamPrepAiComponent.tsx │ │ ├── ExamPrepChat.tsx │ │ ├── ExamnSuggestions.tsx │ │ ├── SearchChats.tsx │ │ ├── ShowCaseChat.tsx │ │ ├── StudentChatSidebar.tsx │ │ ├── StudentCreateNewChat.tsx │ │ ├── exam-prep │ │ │ ├── ConfettiMessage.tsx │ │ │ ├── FreeTextQuestionComponent.tsx │ │ │ ├── MatchingTextQuestionComponent.tsx │ │ │ ├── MultipleChoiceQuestionComponent.tsx │ │ │ └── TrueFalseQuestion.tsx │ │ └── tour │ │ │ ├── ExamPrepSetup.tsx │ │ │ └── FreeChatSetup.tsx │ ├── exercises │ │ ├── CourseExercisesPage.tsx │ │ ├── ExerciseCard.tsx │ │ ├── SaveCode.tsx │ │ ├── StudentExerciseCodePage.tsx │ │ ├── StudentExerciseCodeWrapper.tsx │ │ ├── StudentExercisePage.tsx │ │ └── hooks │ │ │ └── useSaveCode.ts │ ├── notifications │ │ ├── NotificationSidebarFilter.tsx │ │ ├── Notifications.tsx │ │ ├── NotificationsReadButton.tsx │ │ └── notifications-page.tsx │ ├── student │ │ ├── ExamSubmissionForm.tsx │ │ ├── NoCoruseOrSubAlert.tsx │ │ ├── account │ │ │ ├── AIResponseDisplay.tsx │ │ │ ├── CancelSunscriptionForm.tsx │ │ │ ├── DynamicQuestionForm.tsx │ │ │ ├── EditCardProfile.tsx │ │ │ ├── EditProfile.tsx │ │ │ ├── EditProfileDialog.tsx │ │ │ ├── EditProfileForm.tsx │ │ │ ├── EditUserPassword.tsx │ │ │ ├── KnowMeChat.tsx │ │ │ ├── LearningPreferenceForm.tsx │ │ │ └── PasswordEdit.tsx │ │ ├── course │ │ │ ├── ActionButton.tsx │ │ │ ├── AllCoursesCard.tsx │ │ │ ├── BreadcrumbComponent.tsx │ │ │ ├── CarouselCourse.tsx │ │ │ ├── CourseCard.tsx │ │ │ ├── CourseDashboard.tsx │ │ │ ├── CourseHeader.tsx │ │ │ ├── CourseSectionComponent.tsx │ │ │ ├── EnhancedCourseStudentPage.tsx │ │ │ ├── EnrollButton.tsx │ │ │ ├── EnrollCard.tsx │ │ │ ├── ExamCard.tsx │ │ │ ├── ExerciseCard.tsx │ │ │ ├── ItemCard.tsx │ │ │ ├── LessonCard.tsx │ │ │ ├── LinksCourseCards.tsx │ │ │ ├── NoDataPlaceholder.tsx │ │ │ ├── ProgressCard.tsx │ │ │ ├── StatusBadge.tsx │ │ │ ├── enhanced-student-dashboard.tsx │ │ │ ├── exams │ │ │ │ ├── AIReview.tsx │ │ │ │ ├── ExamHeader.tsx │ │ │ │ ├── QuestionCard.tsx │ │ │ │ └── enhanced-exam-review.tsx │ │ │ ├── exercises │ │ │ │ ├── CodeEditor.tsx │ │ │ │ ├── ExerciseCode.tsx │ │ │ │ ├── ExercisesTextEditors.tsx │ │ │ │ └── exerciseChat.tsx │ │ │ └── lessons │ │ │ │ ├── CommentEditor.tsx │ │ │ │ ├── EmbedCodeSection.tsx │ │ │ │ ├── ExercisesSuggestions.tsx │ │ │ │ ├── LessonComplete.tsx │ │ │ │ ├── LessonContent.tsx │ │ │ │ ├── LessonLoaderView.tsx │ │ │ │ ├── LessonNavigationButtons.tsx │ │ │ │ ├── LessonPage.tsx │ │ │ │ ├── LessonTableOfContent.tsx │ │ │ │ ├── LessonsTimeLine.tsx │ │ │ │ ├── MarkLessonsAsCompleted.tsx │ │ │ │ ├── RecentluViewed.tsx │ │ │ │ ├── ResetTaskAIConversation.tsx │ │ │ │ ├── TaskMessageTour.tsx │ │ │ │ ├── ToggleableSection.tsx │ │ │ │ └── excersice │ │ │ │ ├── ChatLessonTask.tsx │ │ │ │ └── LessonTaskMessageWrapper.tsx │ │ └── tour │ │ │ └── StudentOnBoarding.tsx │ └── teacher │ │ ├── DeleteAlert.tsx │ │ ├── account │ │ └── TeacherAccountPage.tsx │ │ ├── course │ │ ├── CreateCourse.tsx │ │ ├── DeleteCourseAlert.tsx │ │ ├── EditCourse.tsx │ │ └── TaskSandboxMessage.tsx │ │ ├── enhanced-teacher-dashboard.tsx │ │ ├── exercises │ │ ├── DeleteExerciseAlert.tsx │ │ └── ExerciseForm.tsx │ │ ├── lessons │ │ ├── DeleteLessonAlert.tsx │ │ ├── LessonForm.tsx │ │ └── TaskMessageSandbox.tsx │ │ └── test │ │ ├── CorrectnessRadio.tsx │ │ ├── DeleteTestAlert.tsx │ │ ├── FreeTextQuestion.tsx │ │ ├── MultipleChoiceQuestion.tsx │ │ ├── SingleSelectQuestion.tsx │ │ ├── TestSubmissionReview.tsx │ │ └── utils │ │ └── categorizeQuestions.ts ├── errors │ ├── CustomErrorBoundary.tsx │ └── RetryError.tsx ├── example │ ├── RetroGridDemo.tsx │ └── hero-video-dialog-demo-top-in-bottom-out.tsx ├── form │ ├── Form.tsx │ ├── FormBuilder.tsx │ ├── InputField.tsx │ ├── SupportTicket.tsx │ └── TeacherTestForm.tsx ├── front │ └── StudentPublicExercisePage.tsx ├── home │ ├── ChatOptionsShowCase.tsx │ ├── FeaturesSection.tsx │ ├── GeminiCompetition.tsx │ ├── Header.tsx │ ├── HeroScroll.tsx │ ├── HeroSection.tsx │ ├── OptionsSection.tsx │ ├── ParticlesSection.tsx │ ├── SubscribeNow.tsx │ ├── VoiceAIChat.tsx │ └── WaitingList.tsx ├── magicui │ ├── HeroVideoDialog.tsx │ ├── confetti.tsx │ ├── dot-pattern.tsx │ ├── particles.tsx │ └── retro-grid.tsx ├── plans │ ├── CheckoutPlan.tsx │ ├── PlanCards.tsx │ ├── PlansTabs.tsx │ ├── PriceCard.tsx │ └── TierCard.tsx ├── provider │ ├── JotaiProvider.tsx │ └── ProgressBarProvider.tsx ├── store │ ├── CheckoutProduct.tsx │ ├── StoreFooter.tsx │ └── product │ │ ├── CourseCard.tsx │ │ ├── ImageContainer.tsx │ │ └── ReviewCard.tsx ├── theme-provider.tsx └── ui │ ├── Acernity │ ├── DotBackground.tsx │ ├── GridBackground.tsx │ ├── TimeLine.tsx │ ├── container-scroll-animation.tsx │ └── tracing-beam.tsx │ ├── LocaleButtons.tsx │ ├── Table │ ├── DataTableColumnHeader.tsx │ ├── DataTablePagination.tsx │ ├── SkeletonTable.tsx │ └── data-table.tsx │ ├── accordion.tsx │ ├── alert-dialog.tsx │ ├── alert.tsx │ ├── aspect-ratio.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── breadcrumb.tsx │ ├── button.tsx │ ├── card.tsx │ ├── carousel.tsx │ ├── chart.tsx │ ├── checkbox.tsx │ ├── collapsible.tsx │ ├── command.tsx │ ├── comments │ ├── Comment.tsx │ └── CommentsList.tsx │ ├── dialog.tsx │ ├── drawer.tsx │ ├── dropdown-menu.tsx │ ├── form.tsx │ ├── hover-card.tsx │ ├── input.tsx │ ├── label.tsx │ ├── markdown │ ├── ForwardRefEditor.tsx │ ├── InitializedMDXEditor.tsx │ ├── ViewMarkdown.module.css │ └── ViewMarkdown.tsx │ ├── menubar.tsx │ ├── pagination.tsx │ ├── popover.tsx │ ├── progress.tsx │ ├── radio-group.tsx │ ├── resizable.tsx │ ├── responsive-dialog.tsx │ ├── scroll-area.tsx │ ├── select.tsx │ ├── separator.tsx │ ├── sheet.tsx │ ├── skeleton.tsx │ ├── sonner.tsx │ ├── table.tsx │ ├── tabs.tsx │ ├── textarea.tsx │ ├── timeline.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── tooltip.tsx │ └── use-toast.ts ├── db ├── readme.md ├── seed.sql ├── sql-functions.sql ├── tables.sql └── views.sql ├── e2e ├── comments.spec.ts ├── components │ ├── admin.ts │ ├── login-admin.ts │ ├── login-student.ts │ ├── login-teacher.ts │ ├── logout.ts │ ├── set-system-theme.ts │ ├── student.ts │ └── teacher.ts ├── crud-product.spec.ts ├── example.spec.ts └── utils │ ├── creds.ts │ └── url.ts ├── flow-chart ├── instrumentation.ts ├── middleware.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── playwright.config.ts ├── postcss.config.js ├── public ├── icons │ └── placeholder.svg └── img │ ├── 404(2).jpeg │ ├── 404.png │ ├── about │ ├── 2024-09-15 18.40.03.jpg │ ├── 2024-09-15 18.40.17.jpg │ ├── 2024-09-15 18.40.22.jpg │ ├── 2024-09-15 18.40.28.jpg │ ├── 2024-09-15 18.40.34.jpg │ ├── 2024-09-15 18.40.41.jpg │ ├── google-competition.png │ └── lms-landing.png │ ├── asd.png │ ├── dashboard.png │ ├── desktop-light.png │ ├── feature(1).png │ ├── feature(2).png │ ├── feature(3).png │ ├── feature(4).png │ ├── feature(5).png │ ├── hero.png │ ├── logo.png │ ├── noise.webp │ ├── product-page-01-related-product-01.jpg │ ├── robot.jpeg │ └── tengo-fe.jpg ├── sentry.client.config.ts ├── sentry.edge.config.ts ├── sentry.server.config.ts ├── tailwind.config.ts ├── tests-examples └── demo-todo-app.spec.ts ├── tsconfig.json ├── utils.ts └── utils ├── const.ts ├── functions.ts ├── hooks ├── useNoCopy.ts └── useScrollAnchor.tsx ├── prompts └── teacherPrompts.md ├── rateLimit.ts ├── stores └── userStore.ts ├── supabase ├── client.ts ├── getClientUserRole.ts ├── getUserRole.ts ├── middleware.ts ├── server.ts └── supabase.ts └── types.ts /.env.example: -------------------------------------------------------------------------------- 1 | # THIS IS FOR CHOOSING THE ENVIRONMENT 2 | NEXT_PUBLIC_DOMAIN_URL="" 3 | 4 | # API KEYS 5 | OPENAI_API_KEY="" 6 | GOOGLE_GENERATIVE_AI_API_KEY="" 7 | 8 | # LMS 9 | NEXT_PUBLIC_SUPABASE_URL='' 10 | NEXT_PUBLIC_SUPABASE_ANON_KEY="" 11 | DATABASE_PSS="" 12 | 13 | # STRIPE 14 | STRIPE_SECRET_KEY="" 15 | STRIPE_WEBHOOKS_ENDPOINT_SECRET="" 16 | 17 | # Binance 18 | BINANCE_API_KEY="" 19 | BINANCE_API_KEY_SECRET="" 20 | 21 | # SMTP 22 | MAILGUN_API_KEY="" 23 | 24 | SMTP_USER="" 25 | SMTP_PSS="" 26 | 27 | # SENTRY 28 | SENTRY_AUTH_TOKEN 29 | 30 | # WP 31 | NEXT_PUBLIC_CONTACT_FORM_ID="" 32 | 33 | # Upstash 34 | UPSTASH_REDIS_REST_URL="" 35 | UPSTASH_REDIS_REST_TOKEN="" -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # Next.js 39 | .next 40 | /test-results/ 41 | /playwright-report/ 42 | /blob-report/ 43 | /playwright/.cache/ 44 | 45 | # Sentry Config File 46 | .env.sentry-build-plugin 47 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | npm run lint 2 | npm run build 3 | git add . -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": false, 5 | "singleQuote": true, 6 | "useTabs": false 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Next.js: debug full stack", 6 | "type": "node-terminal", 7 | "request": "launch", 8 | "command": "npm run dev", 9 | "serverReadyAction": { 10 | "pattern": "started server on .+, url: (https?://.+)", 11 | "uriFormat": "%s", 12 | "action": "debugWithChrome" 13 | } 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.format.enable": true, 3 | "editor.tabSize": 4, 4 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "always" 7 | }, 8 | "files.eol": "\n", 9 | "cSpell.language": "es,en", 10 | } -------------------------------------------------------------------------------- /actions/actions.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | import { revalidatePath } from 'next/cache' 3 | 4 | import { createResponse } from '@/utils/functions' 5 | import { createClient } from '@/utils/supabase/server' 6 | 7 | export interface ApiResponse { 8 | status: 'success' | 'error' | 'idle' 9 | message: string 10 | data?: T 11 | error?: any 12 | } 13 | 14 | export const updateProfile = async (formData: FormData) => { 15 | const supabase = createClient() 16 | 17 | const { 18 | data: { user } 19 | } = await supabase.auth.getUser() 20 | 21 | if (!user) { 22 | return createResponse('error', 'You must be logged in to submit a comment', null) 23 | } 24 | 25 | if (!formData) { 26 | return createResponse('error', 'No form data was submitted', null) 27 | } 28 | 29 | if (!formData.get('name')) { 30 | return createResponse('error', 'No name was submitted', null) 31 | } 32 | 33 | if (!formData.get('bio')) { 34 | return createResponse('error', 'No bio was submitted', null) 35 | } 36 | 37 | if (!formData.get('photo')) { 38 | return createResponse('error', 'No photo was submitted', null) 39 | } 40 | 41 | const { data, error } = await supabase 42 | .from('profiles') 43 | .update({ 44 | bio: formData.get('bio') as string, 45 | full_name: formData.get('name') as string, 46 | avatar_url: formData.get('photo') as string 47 | }) 48 | .eq('id', user?.id) 49 | 50 | if (error) { 51 | console.log(error) 52 | } 53 | 54 | if (data) { 55 | console.log(data) 56 | revalidatePath('/dashboard/account') 57 | } 58 | return data 59 | } 60 | -------------------------------------------------------------------------------- /actions/contact.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | import { revalidatePath } from 'next/cache' 3 | 4 | import { createResponse } from '@/utils/functions' 5 | import { createClient } from '@/utils/supabase/server' 6 | 7 | export const createSupportTicket = async ({ 8 | title, 9 | description, 10 | issues, 11 | }: { 12 | title: string 13 | description: string 14 | issues: string[] 15 | }) => { 16 | const supabase = createClient() 17 | 18 | const { data, error } = await supabase.auth.getUser() 19 | 20 | if (error) { 21 | return createResponse('error', 'You must be logged in to submit a support ticket', null) 22 | } 23 | 24 | if (!title) { 25 | return createResponse('error', 'No title was submitted', null) 26 | } 27 | 28 | if (!description) { 29 | return createResponse('error', 'No description was submitted', null) 30 | } 31 | 32 | if (!issues) { 33 | return createResponse('error', 'No issues were submitted', null) 34 | } 35 | 36 | const { data: ticketData, error: ticketError } = await supabase 37 | .from('tickets') 38 | .insert({ 39 | title, 40 | description: `${description} \n\nIssues: ${issues.join(', ')}`, 41 | user_id: data?.user.id, 42 | }) 43 | 44 | if (ticketError) { 45 | console.log(ticketError) 46 | } 47 | 48 | if (ticketData) { 49 | revalidatePath('/contact/support') 50 | } 51 | 52 | return createResponse('success', 'Ticket submitted successfully', ticketData) 53 | } 54 | -------------------------------------------------------------------------------- /actions/dashboard/linkProductToCourse.ts: -------------------------------------------------------------------------------- 1 | import { courseSchemaType } from '@/components/dashboards/teacher/course/CreateCourse' 2 | import { createResponse } from '@/utils/functions' 3 | 4 | export async function linkProductAction (data: courseSchemaType) { 5 | console.log(data) 6 | 7 | // if (!data.price || !data.status || !data.course_id) { 8 | // return createResponse('error', 'Please fill in all fields', null, null) 9 | // } 10 | 11 | return createResponse('success', 'Product linked successfully', null, null) 12 | } 13 | -------------------------------------------------------------------------------- /actions/dashboard/notificationsActions.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | import { revalidatePath } from 'next/cache' 3 | 4 | import { createResponse } from '@/utils/functions' 5 | import { createClient } from '@/utils/supabase/server' 6 | import { Tables } from '@/utils/supabase/supabase' 7 | 8 | export async function notificationUpdate (notification: Tables<'notifications'>) { 9 | const supabase = createClient() 10 | const commentUpdate = await supabase 11 | .from('notifications') 12 | .update({ 13 | ...notification 14 | }) 15 | .eq('notification_id', notification.notification_id) 16 | 17 | if (commentUpdate.error) { 18 | console.log(commentUpdate.error) 19 | return createResponse('error', 'Error updating notification', null, 'Error updating notification') 20 | } 21 | 22 | revalidatePath('/dashboard/student/', 'layout') 23 | return createResponse('success', 'Notification updated successfully', null, null) 24 | } 25 | -------------------------------------------------------------------------------- /actions/dashboard/testActions.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | import { revalidatePath } from 'next/cache' 3 | 4 | import { createResponse } from '@/utils/functions' 5 | import { createClient } from '@/utils/supabase/server' 6 | 7 | export async function deleteTestAction(data: { 8 | testId: string; 9 | }) { 10 | const testId = data.testId 11 | 12 | if (!testId) { 13 | return createResponse('error', 'Lesson id is required', null, 'Lesson id is required') 14 | } 15 | 16 | const supabase = createClient() 17 | const lessonData = await supabase 18 | .from('exams') 19 | .delete() 20 | .eq('exam_id', testId) 21 | 22 | if (lessonData.error) { 23 | console.log(lessonData.error) 24 | return createResponse('error', 'Error deleting lesson', null, 'Error deleting lesson') 25 | } 26 | 27 | revalidatePath('/dashboard/teacher/courses/[courseId]', 'layout') 28 | return createResponse('success', 'Lesson deleted successfully', null, null) 29 | } 30 | -------------------------------------------------------------------------------- /actions/dashboard/userActions.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | 3 | import { revalidatePath } from 'next/cache' 4 | import { z } from 'zod' 5 | 6 | import { updateUserProfileSchema } from '@/components/dashboards/student/account/EditProfile' 7 | import { createResponse } from '@/utils/functions' 8 | import { createClient } from '@/utils/supabase/server' 9 | import { Tables } from '@/utils/supabase/supabase' 10 | 11 | export async function updatePassword(state: { 12 | newPassword: string 13 | }) { 14 | const { newPassword } = state 15 | const client = createClient() 16 | 17 | const { error } = await client.auth.updateUser({ 18 | password: newPassword, 19 | }) 20 | 21 | if (error) { 22 | return createResponse('error', 'Error updating password', null, 'Error updating password') 23 | } 24 | 25 | revalidatePath('/dashboard') 26 | return createResponse('success', 'Password updated', null, 'Password updated') 27 | } -------------------------------------------------------------------------------- /app/[locale]/(front)/checkout/layout.tsx: -------------------------------------------------------------------------------- 1 | 2 | export default function Layout ({ children }: { children: React.ReactNode }) { 3 | return ( 4 | <> 5 |
6 | {children} 7 |
8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /app/[locale]/(front)/contact/support/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client' // Error components must be Client Components 2 | 3 | import { useEffect } from 'react' 4 | 5 | import GenericError from '@/components/GenericError' 6 | 7 | export default function Error ({ 8 | error, 9 | reset 10 | }: { 11 | error: Error & { digest?: string } 12 | reset: () => void 13 | }) { 14 | useEffect(() => { 15 | // Log the error to an error reporting service 16 | console.error(error) 17 | }, [error]) 18 | 19 | return ( 20 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /app/[locale]/(front)/contact/support/page.tsx: -------------------------------------------------------------------------------- 1 | import TicketReview from '@/components/contact/TicketReview' 2 | import SupportTicket from '@/components/form/SupportTicket' 3 | import { Separator } from '@/components/ui/separator' 4 | import { createClient } from '@/utils/supabase/server' 5 | 6 | export default async function SupportTicketPage() { 7 | const supabase = createClient() 8 | 9 | const { data, error } = await supabase.auth.getUser() 10 | 11 | if (error) { 12 | throw new Error('Not authenticated') 13 | } 14 | 15 | const ticketData = await supabase 16 | .from('tickets') 17 | .select('*') 18 | .eq('user_id', data?.user.id) 19 | 20 | return ( 21 |
22 | 23 | 24 |
25 |

Review Your Ticket

26 | {ticketData.data?.length > 0 27 | ? ticketData.data?.map((ticket) => ( 28 | 33 | )) : ( 34 |

No tickets found, create a new ticket to get started

37 | )} 38 |
39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /app/[locale]/(front)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react' 2 | 3 | import Footer from '@/components/Footer' 4 | import Header from '@/components/Header' 5 | import { Skeleton } from '@/components/ui/skeleton' 6 | 7 | export default function Layout ({ children }: { children: React.ReactNode }) { 8 | return ( 9 | <> 10 |
11 | <>{children} 12 | 13 |
14 | 15 |
16 | } 17 | > 18 |