├── .dockerignore ├── .env.example ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug.md │ └── feature.md ├── pull_request_template.md └── workflows │ ├── cd.yml │ ├── cd_prod.yml │ └── lint.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.dev ├── Dockerfile.prod ├── LICENSE ├── README.md ├── certificate.png ├── components.json ├── docker-compose.yml ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prisma ├── migrate.ts ├── migrations │ ├── 20231119045842_init │ │ └── migration.sql │ ├── 20231119051035_update_course │ │ └── migration.sql │ ├── 20231119063735_added_content │ │ └── migration.sql │ ├── 20231119070313_update_video_metadata │ │ └── migration.sql │ ├── 20231120052608_add_next_auth │ │ └── migration.sql │ ├── 20231120081441_init │ │ └── migration.sql │ ├── 20231121054501_init │ │ └── migration.sql │ ├── 20231125233546_appx │ │ └── migration.sql │ ├── 20231125233929_appx │ │ └── migration.sql │ ├── 20231126005118_appx │ │ └── migration.sql │ ├── 20231129013219_add_bulk_discords │ │ └── migration.sql │ ├── 20231129030824_add_bulk_discords_unique │ │ └── migration.sql │ ├── 20231129095057_add_schema_subtitles │ │ └── migration.sql │ ├── 20231130102613_add_schema_subtitles │ │ └── migration.sql │ ├── 20231204062722_open_to_everyone │ │ └── migration.sql │ ├── 20231207104413_add_slides_link │ │ └── migration.sql │ ├── 20231208122916_add_backdoor_drm │ │ └── migration.sql │ ├── 20231208123319_add_backdoor_mp4_links │ │ └── migration.sql │ ├── 20231212075742_add_segemnts_to_videos │ │ └── migration.sql │ ├── 20231214062542_add_more_info │ │ └── migration.sql │ ├── 20231214114048_add_duration │ │ └── migration.sql │ ├── 20231218161052_adds_current_timestamp │ │ └── migration.sql │ ├── 20231218162941_makes_content_user_pair_unique │ │ └── migration.sql │ ├── 20240114214841_add_notion_metadata │ │ └── migration.sql │ ├── 20240114220154_add_notion_metadata_fix │ │ └── migration.sql │ ├── 20240202163217_add_completed_field_to_video_progress │ │ └── migration.sql │ ├── 20240203231627_hidden │ │ └── migration.sql │ ├── 20240209234513_add_comments │ │ └── migration.sql │ ├── 20240226093954_update │ │ └── migration.sql │ ├── 20240317222835_add_is_pinned_comment │ │ └── migration.sql │ ├── 20240318200949_add_bookmark │ │ └── migration.sql │ ├── 20240319183729_add_password │ │ └── migration.sql │ ├── 20240319185121_add_appx_user_id │ │ └── migration.sql │ ├── 20240324133414_modify_video_progress │ │ └── migration.sql │ ├── 20240402112352_remove_course_from_bookmark │ │ └── migration.sql │ ├── 20240416130800_add_questions_answers │ │ └── migration.sql │ ├── 20240424002140_add_certificates │ │ └── migration.sql │ ├── 20240507073349_adds_certs │ │ └── migration.sql │ ├── 20240512172646_add_cert_slug │ │ └── migration.sql │ ├── 20240513051454_fix_constraints │ │ └── migration.sql │ ├── 20240601123949_added_migration_status │ │ └── migration.sql │ ├── 20240601213343_ │ │ └── migration.sql │ ├── 20240610142535_bunny_added │ │ └── migration.sql │ ├── 20240720214701_convert_to_string │ │ └── migration.sql │ ├── 20240720222420_discord │ │ └── migration.sql │ ├── 20240720230424_init │ │ └── migration.sql │ ├── 20240811175241_add_payout_methods │ │ └── migration.sql │ ├── 20240816093504_add │ │ └── migration.sql │ ├── 20240828061415_add_github_link │ │ └── migration.sql │ ├── 20240830090007_add_bounty_submission │ │ └── migration.sql │ ├── 20240830204152_ │ │ └── migration.sql │ ├── 20240830223934_add_subtitle_tries │ │ └── migration.sql │ ├── 20241121062529_add_appx_fields │ │ └── migration.sql │ ├── 20241130091016_added_appx_video_id_mapping_with_appx_video_json │ │ └── migration.sql │ └── migration_lock.toml ├── schema.prisma └── seed.ts ├── public ├── Content-Cover.png ├── Mockup.png ├── NoBookmark.svg ├── banner_placeholder.png ├── certificate.pdf ├── certificate.png ├── fonts │ ├── font.woff2 │ └── satoshi.ttf ├── footer-logos │ ├── all-logos.ts │ └── logos-svg │ │ ├── github-logo.tsx │ │ ├── insta-logo.tsx │ │ ├── x-logo.tsx │ │ └── yt-logo.tsx ├── harkirat.png ├── platform │ ├── github.svg │ ├── sol.svg │ └── upi.svg └── subs.vtt ├── setup.sh ├── src ├── actions │ ├── answer │ │ ├── index.ts │ │ ├── schema.ts │ │ └── types.ts │ ├── bookmark │ │ ├── index.ts │ │ ├── schema.ts │ │ └── types.ts │ ├── bounty │ │ ├── adminActions.ts │ │ ├── index.ts │ │ ├── schema.ts │ │ ├── types.ts │ │ └── userActions.ts │ ├── comment │ │ ├── index.ts │ │ ├── schema.ts │ │ └── types.ts │ ├── commentVote │ │ ├── index.ts │ │ ├── schema.ts │ │ └── types.ts │ ├── event-actions │ │ └── index.ts │ ├── payoutMethods │ │ ├── index.ts │ │ ├── schema.ts │ │ └── types.ts │ ├── question │ │ ├── index.ts │ │ ├── schema.ts │ │ └── types.ts │ ├── refresh-db │ │ └── index.ts │ ├── types.ts │ ├── user │ │ └── index.ts │ └── videopreview │ │ └── videoPreview.tsx ├── app │ ├── (main) │ │ ├── (pages) │ │ │ ├── bookmark │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ │ ├── home │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── question │ │ │ │ ├── [slug] │ │ │ │ │ ├── @answers │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── @question │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ └── watch-history │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── (marketing) │ │ ├── (policy) │ │ │ ├── privacy-policy │ │ │ │ ├── page.tsx │ │ │ │ └── privacy-policy.ts │ │ │ ├── refund │ │ │ │ └── page.tsx │ │ │ └── tnc │ │ │ │ ├── page.tsx │ │ │ │ └── tnc-content.ts │ │ └── layout.tsx │ ├── admin │ │ ├── add-course │ │ │ └── page.tsx │ │ ├── bounty │ │ │ ├── AdminBountyPage.tsx │ │ │ └── page.tsx │ │ ├── comment │ │ │ ├── ApproveComment.tsx │ │ │ └── page.tsx │ │ ├── content │ │ │ ├── [courseId] │ │ │ │ ├── [...moduleId] │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── discord │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── user │ │ │ ├── LogoutUser.tsx │ │ │ └── page.tsx │ │ └── userflags │ │ │ └── page.tsx │ ├── api │ │ ├── admin │ │ │ ├── content │ │ │ │ └── route.ts │ │ │ ├── contentmetadata │ │ │ │ └── route.ts │ │ │ ├── course │ │ │ │ └── route.ts │ │ │ ├── discord │ │ │ │ ├── refresh │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── discordReset │ │ │ │ ├── get │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── drm │ │ │ │ └── route.ts │ │ │ ├── segments │ │ │ │ └── route.ts │ │ │ ├── services │ │ │ │ ├── externalLogin │ │ │ │ │ └── route.ts │ │ │ │ ├── subtitle │ │ │ │ │ └── route.ts │ │ │ │ └── vizolv │ │ │ │ │ └── route.ts │ │ │ └── updatecontent │ │ │ │ └── route.ts │ │ ├── auth │ │ │ └── [...nextauth] │ │ │ │ └── route.ts │ │ ├── certificate │ │ │ ├── certiTemplate.png │ │ │ └── sign.png │ │ ├── course │ │ │ └── videoProgress │ │ │ │ ├── duration │ │ │ │ └── route.ts │ │ │ │ ├── markAsCompleted │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ ├── discord │ │ │ └── redirect │ │ │ │ ├── cohort3 │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ ├── github │ │ │ ├── callback │ │ │ │ └── route.ts │ │ │ ├── details │ │ │ │ └── route.ts │ │ │ └── link │ │ │ │ └── route.ts │ │ ├── mobile │ │ │ ├── courses │ │ │ │ ├── [courseId] │ │ │ │ │ ├── [collectionId] │ │ │ │ │ │ ├── [contentId] │ │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── search │ │ │ │ └── route.ts │ │ │ └── signin │ │ │ │ └── route.ts │ │ ├── notion │ │ │ └── route.ts │ │ ├── og │ │ │ └── home │ │ │ │ └── route.tsx │ │ ├── search │ │ │ └── route.ts │ │ └── user │ │ │ ├── exists │ │ │ └── route.ts │ │ │ └── route.ts │ ├── bounty │ │ └── page.tsx │ ├── calendar │ │ └── page.tsx │ ├── certificate │ │ ├── page.tsx │ │ └── verify │ │ │ └── [id] │ │ │ └── page.tsx │ ├── courses │ │ ├── [courseId] │ │ │ ├── [...moduleId] │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ └── page.tsx │ ├── data │ │ └── index.ts │ ├── discord │ │ └── redirect │ │ │ ├── cohort3 │ │ │ └── page.tsx │ │ │ └── page.tsx │ ├── error.tsx │ ├── favicon.ico │ ├── globals.css │ ├── invalidsession │ │ └── page.tsx │ ├── layout.tsx │ ├── not-found.tsx │ ├── page.tsx │ ├── payout-methods │ │ └── page.tsx │ ├── pdf │ │ └── [contentId] │ │ │ └── page.tsx │ ├── providers.tsx │ ├── signin │ │ └── page.tsx │ └── video │ │ └── [id] │ │ └── route.ts ├── components │ ├── 3dcard.tsx │ ├── Appbar.tsx │ ├── AppbarAuth.tsx │ ├── AppxVideoPlayer.tsx │ ├── BreadCrumbComponent.tsx │ ├── CardComponent.tsx │ ├── Certificate.tsx │ ├── CertificateVerify.tsx │ ├── CodeBlock.tsx │ ├── ContentCard.tsx │ ├── Copy-to-clipbord.tsx │ ├── CourseCard.tsx │ ├── CourseView.tsx │ ├── Courses.tsx │ ├── DonutChart.tsx │ ├── FilterContent.tsx │ ├── FolderView.tsx │ ├── FormError.tsx │ ├── GitHubLinkButton.tsx │ ├── Greeting.tsx │ ├── Loader.tsx │ ├── Loading.tsx │ ├── LoginGate.tsx │ ├── Logout.tsx │ ├── Modal.tsx │ ├── MyCourses.tsx │ ├── Navbar.tsx │ ├── NewPayoutDialog.tsx │ ├── NewPostDialog.tsx │ ├── NotionRenderer.tsx │ ├── OfflineNavigator.tsx │ ├── Pagination.tsx │ ├── PaymentDropdown.tsx │ ├── PaymentMethodCard.tsx │ ├── PercentageComplete.tsx │ ├── QualitySelectorControllBar.ts │ ├── Redirect.tsx │ ├── Sidebar.tsx │ ├── Signin.tsx │ ├── ThemeToggler.tsx │ ├── VideoContentChapters.tsx │ ├── VideoPlayer2.tsx │ ├── VideoPlayerSegment.tsx │ ├── ViewAllDialog.tsx │ ├── WatchHistoryClient.tsx │ ├── YoutubeRenderer.tsx │ ├── admin │ │ ├── AddContent.tsx │ │ ├── AddNotionMetadata.tsx │ │ ├── ContentRenderer.tsx │ │ ├── ContentRendererClient.tsx │ │ ├── CourseContent.tsx │ │ ├── SelectCourse.tsx │ │ └── UpdateVideoClient.tsx │ ├── analytics │ │ └── GoogleAnalytics.tsx │ ├── big-calendar │ │ ├── calendar.tsx │ │ ├── event-dialog.tsx │ │ ├── event-form.tsx │ │ ├── shadcn-big-calendar.css │ │ └── shadcn-big-calendar.ts │ ├── bookmark │ │ ├── BookmarkButton.tsx │ │ ├── BookmarkList.tsx │ │ ├── BookmarkView.tsx │ │ └── NoBookmark.tsx │ ├── bounty │ │ ├── BountySubmissionDialog.tsx │ │ └── admin-page │ │ │ ├── ConfirmBountyDialog.tsx │ │ │ └── ConfirmedBountiesDialog.tsx │ ├── comment │ │ ├── CommentApproveForm.tsx │ │ ├── CommentDeleteForm.tsx │ │ ├── CommentInputForm.tsx │ │ ├── CommentPinForm.tsx │ │ ├── CommentVoteForm.tsx │ │ ├── Comments.tsx │ │ └── TimeCodeComment.tsx │ ├── landing │ │ ├── footer-cta.tsx │ │ ├── footer.tsx │ │ ├── landing-page.tsx │ │ └── logo │ │ │ └── logo.tsx │ ├── posts │ │ ├── PostCard.tsx │ │ ├── form │ │ │ ├── form-delete.tsx │ │ │ ├── form-errors.tsx │ │ │ ├── form-input.tsx │ │ │ └── form-vote.tsx │ │ ├── tag.tsx │ │ └── textSnippet.tsx │ ├── print │ │ ├── Print.tsx │ │ └── PrintNotes.tsx │ ├── profile-menu │ │ ├── ExternalLinks.tsx │ │ └── ProfileDropdown.tsx │ ├── search.tsx │ ├── search │ │ ├── CommandMenu.tsx │ │ ├── SearchBar.tsx │ │ ├── SearchResults.tsx │ │ ├── VideoSearchCard.tsx │ │ ├── VideoSearchInfo.tsx │ │ └── VideoSearchLoading.tsx │ ├── theme-provider.tsx │ ├── ui │ │ ├── accordion.tsx │ │ ├── avatar.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── infinite-moving-cards.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── navigation-menu.tsx │ │ ├── popover.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── separator.tsx │ │ ├── sidebar-items.tsx │ │ ├── skeleton.tsx │ │ ├── sonner.tsx │ │ ├── textarea.tsx │ │ └── tooltip.tsx │ └── videothumbnail.tsx ├── config │ └── site-config.ts ├── db │ ├── Cache.ts │ ├── bookmark.ts │ ├── cert.ts │ ├── course.ts │ └── index.ts ├── hooks │ ├── useAction.ts │ ├── useBookmark.tsx │ ├── useCertGen.ts │ ├── useClickOutside.ts │ ├── useColorGenerator.ts │ ├── useDebounce.ts │ ├── useModal.tsx │ ├── useMountStatus.ts │ └── usePayoutMethod.ts ├── lib │ ├── auth.ts │ ├── cache │ │ ├── cache.ts │ │ ├── in-memory-cache.ts │ │ └── redis-cache.ts │ ├── create-safe-action.ts │ ├── createAndHandleSegments.ts │ ├── find-content-by-id.ts │ └── utils.ts ├── middleware.ts ├── scripts │ ├── data.ts │ └── updateVideoMetaData.ts ├── store │ └── atoms │ │ ├── courses.ts │ │ ├── filterContent.ts │ │ ├── index.ts │ │ ├── sidebar.ts │ │ └── trigger.ts ├── styles │ ├── tailwind-input.css │ └── tailwind.css ├── tests │ └── helpers │ │ ├── reset-db.ts │ │ └── setup.ts └── utiles │ ├── appx-check-mail.ts │ ├── appx.ts │ ├── certificate.ts │ ├── discord.ts │ ├── is-valid-json.ts │ └── jsx.d.ts ├── tailwind.config.js └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | .gitignore 4 | .env.example 5 | .env -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_BASE_URL_LOCAL=http://127.0.0.1:3000 2 | ADMIN_SECRET="ADMIN_SECRET" 3 | JWT_SECRET="JWT_SECRET" 4 | # DONT CHANGE FOR RUNNING WITH DOCKER 5 | DATABASE_URL="postgresql://myuser:mypassword@localhost:5432/cms?schema=public" 6 | NEXTAUTH_URL="http://localhost:3000" 7 | APPX_AUTH_KEY="AUTH_SECRET" 8 | NEXTAUTH_SECRET="NEXTAUTH_SECRET" 9 | APPX_CLIENT_SERVICE="" 10 | APPX_BASE_API = "" 11 | DISCORD_ACCESS_KEY = "123" 12 | DISCORD_ACCESS_SECRET = "123" 13 | DISCORD_REDIRECT_URL = "https://app.100xdevs.com/discord/redirect" 14 | BOT_TOKEN = "123" 15 | GUILD_ID = "123" 16 | LOCAL_CMS_PROVIDER = true 17 | CACHE_EXPIRE_S = 10 18 | SUBTITLE_SECRET=SubSecret 19 | ADMINS = "Random,example@gmail.com" 20 | NEXT_PUBLIC_DISABLE_FEATURES = "featurea,featureb,featurec" 21 | REDIS_URL= 22 | GITHUB_ID= 23 | GITHUB_SECRET= 24 | NEXT_PUBLIC_DISCORD_WEBHOOK_URL = 25 | JOB_BOARD_AUTH_SECRET= 26 | 27 | 28 | COHORT3_DISCORD_ACCESS_KEY = 29 | COHORT3_DISCORD_ACCESS_SECRET = 30 | COHORT3_DISCORD_REDIRECT_URI = 31 | COHORT3_BOT_TOKEN = 32 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .github -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: Create a report to help us improve 4 | title: "bug: " 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Do something 15 | 2. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots or GIFs** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Info (please complete the following information):** 24 | - Browser [e.g. chrome, safari] 25 | - Version [e.g. 22] 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'feature: ' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### PR Fixes: 2 | - 1 3 | - 2 4 | 5 | Resolves #[Issue Number if there] 6 | 7 | ### Checklist before requesting a review 8 | - [ ] I have performed a self-review of my code 9 | - [ ] I assure there is no similar/duplicate pull request regarding same issue 10 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Deployment 2 | on: 3 | push: 4 | branches: [ main ] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | with: 11 | fetch-depth: 0 12 | 13 | - name: Docker login 14 | uses: docker/login-action@v2 15 | with: 16 | username: ${{ secrets.DOCKERHUB_USERNAME }} 17 | password: ${{ secrets.DOCKERHUB_TOKEN }} 18 | 19 | - name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v2 21 | 22 | - name: Build and push 23 | uses: docker/build-push-action@v4 24 | with: 25 | context: . 26 | file: ./Dockerfile.prod 27 | push: true 28 | tags: 100xdevs/cms-staging:${{ github.sha }} 29 | build-args: | 30 | DATABASE_URL=${{ secrets.STAGING_DATABASE }} 31 | 32 | - name: Clone staging-ops repo, update, and push 33 | env: 34 | PAT: ${{ secrets.PAT }} 35 | run: | 36 | git clone https://github.com/code100x/staging-ops.git 37 | cd staging-ops 38 | sed -i 's|image: 100xdevs/cms-staging:.*|image: 100xdevs/cms-staging:${{ github.sha }}|' staging/cms/deployment.yml 39 | git config user.name "GitHub Actions Bot" 40 | git config user.email "actions@github.com" 41 | git add staging/cms/deployment.yml 42 | git commit -m "Update cms image to ${{ github.sha }}" 43 | git push https://${PAT}@github.com/code100x/staging-ops.git main 44 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linting and formatting on PR 2 | on: 3 | pull_request: 4 | branches: 5 | - '**' 6 | 7 | jobs: 8 | 9 | Continuous-Integration: 10 | 11 | name: Performs linting, formatting on the application 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout the Repository 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup pnpm 18 | run: npm install -g pnpm 19 | 20 | - name: Install Dependencies 21 | run: pnpm install 22 | 23 | - name: Run linting check 24 | run: pnpm run lint:check 25 | 26 | - name: Check formatting 27 | run: pnpm run format:check 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | .env 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | bun.lockb 9 | 10 | # docker volume 11 | /postgres-data/ 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # local env files 33 | .env*.local 34 | 35 | # vercel 36 | .vercel 37 | 38 | #vs-code (debug config) 39 | .vscode 40 | launch.json 41 | 42 | # typescript 43 | *.tsbuildinfo 44 | next-env.d.ts 45 | 46 | *storybook.log 47 | 48 | # ignore yarn.lock & package-lock 49 | yarn.lock 50 | package-lock.json -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps = true -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .github -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "trailingComma": "all", 6 | "plugins": [ 7 | "prettier-plugin-tailwindcss" 8 | ] 9 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to this repository. To ensure a smooth and collaborative environment, please follow these guidelines. Before contributing, set up the project locally using the steps outlined in [README.md](./README.md). 4 | 5 | ## Why these guidelines ? 6 | 7 | Our goal is to create a healthy and inclusive space for contributions. Remember that open-source contribution is a collaborative effort, not a competition. 8 | 9 | ## General guidelines 10 | 11 | - Work only on one issue at a time since it will provide an opportunity for others to contribute as well. 12 | 13 | - Note that each open-source repository generally has its own guidelines, similar to these. Always read them before starting your contributions. 14 | 15 | ## How to get an issue assigned 16 | 17 | - To get an issue assigned, provide a small description as to how you are planning to tackle this issue. 18 | 19 | > Ex - If the issue is about UI changes, you should create a design showing how you want it to look on the UI (make it using figma, paint, etc) 20 | 21 | - This will allow multiple contributors to discuss their approach to tackle the issue. The maintainer will then assign the issue. 22 | 23 | ## After getting the issue assigned 24 | 25 | - Create your own branch instead of working directly on the main branch. 26 | 27 | - Provide feedback every 24-48 hours if an issue is assigned to you. Otherwise, it may be reassigned. 28 | 29 | - When submitting a pull request, please provide a screenshot or a screen-recording showcasing your work. 30 | 31 | ## Don't while contributing 32 | 33 | - Avoid comments like "Please assign this issue to me" or "can i work on this issue ?" 34 | 35 | - Refrain from tagging the maintainer to assign issues or review pull requests. 36 | 37 | - Don't make any pull request for issues you are not assigned to. It will be closed without merging. 38 | 39 | Happy Contributing! 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine 2 | ARG DATABASE_URL 3 | 4 | WORKDIR /usr/src/app 5 | 6 | # Install pnpm globally 7 | RUN npm install -g pnpm 8 | 9 | COPY . . 10 | RUN pnpm install 11 | RUN DATABASE_URL=$DATABASE_URL npx prisma generate 12 | RUN DATABASE_URL=$DATABASE_URL pnpm run build 13 | 14 | EXPOSE 3000 15 | 16 | CMD ["pnpm", "run", "start"] 17 | 18 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package.json pnpm-lock.yaml ./ 6 | COPY prisma ./prisma 7 | 8 | RUN npm i pnpm -g 9 | 10 | RUN pnpm install 11 | 12 | COPY . . 13 | 14 | EXPOSE 3000 15 | 16 | CMD ["pnpm", "dev:docker"] -------------------------------------------------------------------------------- /Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS build 2 | 3 | WORKDIR /usr/src/app 4 | ARG DATABASE_URL 5 | ENV DATABASE_URL=${DATABASE_URL} 6 | 7 | RUN set -ex; \ 8 | apk update; \ 9 | apk add --no-cache \ 10 | openssl 11 | 12 | COPY . . 13 | 14 | RUN npm install -g pnpm && \ 15 | pnpm install && \ 16 | pnpm add sharp && \ 17 | pnpm run build 18 | 19 | RUN DATABASE_URL=${DATABASE_URL} pnpm dlx prisma@5.18.0 generate 20 | 21 | FROM node:20-alpine AS run 22 | RUN set -ex; \ 23 | apk update; \ 24 | apk add --no-cache \ 25 | openssl 26 | 27 | RUN mkdir /.npm && chown -R 1001:1001 /.npm 28 | 29 | USER 1001:1001 30 | WORKDIR /usr/src/app 31 | 32 | COPY --from=build --chown=1001:1001 usr/src/app/.next/standalone ./ 33 | COPY --from=build --chown=1001:1001 usr/src/app/.next/static ./.next/static 34 | COPY --from=build --chown=1001:1001 usr/src/app/public ./public 35 | 36 | ENV NODE_ENV production 37 | ENV PORT 3000 38 | ENV HOSTNAME "0.0.0.0" 39 | 40 | CMD [ "node", "server.js" ] 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Code100x 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /certificate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/cms/f4a9673e42b459d87abcf10b355504ca9eb5e336/certificate.png -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile.dev 6 | args: 7 | - DATABASE_URL=postgresql://postgres:postgres@db:5432/cms?schema=public 8 | container_name: cms-docker 9 | environment: 10 | - DATABASE_URL=postgresql://postgres:postgres@db:5432/cms?schema=public 11 | - NEXT_WEBPACK_USEPOLLING=1 12 | ports: 13 | - '3000:3000' 14 | - '5555:5555' 15 | volumes: 16 | # - .:/usr/src/app 17 | - /usr/src/app/.next 18 | - /usr/src/app/node_modules 19 | depends_on: 20 | db: 21 | condition: service_healthy 22 | 23 | db: 24 | image: postgres:alpine 25 | container_name: db 26 | restart: always 27 | environment: 28 | POSTGRES_USER: postgres 29 | POSTGRES_PASSWORD: postgres 30 | POSTGRES_DB: cms 31 | ports: 32 | - 5432:5432 33 | volumes: 34 | - postgres-data:/var/lib/postgresql/data 35 | healthcheck: 36 | test: [ 'CMD-SHELL', 'pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}' ] 37 | interval: 10s 38 | timeout: 5s 39 | retries: 5 40 | 41 | volumes: 42 | postgres-data: -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | images: { 5 | remotePatterns: [ 6 | { 7 | protocol: 'https', 8 | hostname: 'avatars.githubusercontent.com', 9 | port: '', 10 | pathname: '/**', 11 | }, 12 | ], 13 | }, 14 | experimental: { 15 | serverActions: { 16 | allowedOrigins: [ 17 | 'localhost:3000', 18 | 'app.100xdevs.com', 19 | 'app2.100xdevs.com', 20 | ], 21 | }, 22 | }, 23 | swcMinify: true, 24 | webpack: (config) => { 25 | // Enable polling based on env variable being set 26 | if (process.env.NEXT_WEBPACK_USEPOLLING) { 27 | config.watchOptions = { 28 | poll: 500, 29 | aggregateTimeout: 300, 30 | }; 31 | } 32 | return config; 33 | }, 34 | output: 'standalone', 35 | }; 36 | 37 | module.exports = nextConfig; 38 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /prisma/migrations/20231119045842_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" SERIAL NOT NULL, 4 | "email" TEXT NOT NULL, 5 | "name" TEXT, 6 | 7 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 8 | ); 9 | 10 | -- CreateTable 11 | CREATE TABLE "Course" ( 12 | "id" SERIAL NOT NULL, 13 | "title" TEXT NOT NULL, 14 | "imageUrl" TEXT NOT NULL, 15 | 16 | CONSTRAINT "Course_pkey" PRIMARY KEY ("id") 17 | ); 18 | 19 | -- CreateTable 20 | CREATE TABLE "UserPurchases" ( 21 | "userId" INTEGER NOT NULL, 22 | "courseId" INTEGER NOT NULL, 23 | "assignedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 24 | 25 | CONSTRAINT "UserPurchases_pkey" PRIMARY KEY ("userId","courseId") 26 | ); 27 | 28 | -- CreateIndex 29 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 30 | 31 | -- AddForeignKey 32 | ALTER TABLE "UserPurchases" ADD CONSTRAINT "UserPurchases_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 33 | 34 | -- AddForeignKey 35 | ALTER TABLE "UserPurchases" ADD CONSTRAINT "UserPurchases_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -------------------------------------------------------------------------------- /prisma/migrations/20231119051035_update_course/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `description` to the `Course` table without a default value. This is not possible if the table is not empty. 5 | - Added the required column `slug` to the `Course` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "Course" ADD COLUMN "description" TEXT NOT NULL, 10 | ADD COLUMN "slug" TEXT NOT NULL; 11 | -------------------------------------------------------------------------------- /prisma/migrations/20231119063735_added_content/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Content" ( 3 | "id" SERIAL NOT NULL, 4 | "type" TEXT NOT NULL DEFAULT 'folder', 5 | "title" TEXT NOT NULL, 6 | "description" TEXT, 7 | "thumbnail" TEXT, 8 | "parentId" INTEGER, 9 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | 11 | CONSTRAINT "Content_pkey" PRIMARY KEY ("id") 12 | ); 13 | 14 | -- CreateTable 15 | CREATE TABLE "CourseContent" ( 16 | "courseId" INTEGER NOT NULL, 17 | "contentId" INTEGER NOT NULL, 18 | 19 | CONSTRAINT "CourseContent_pkey" PRIMARY KEY ("courseId","contentId") 20 | ); 21 | 22 | -- AddForeignKey 23 | ALTER TABLE "Content" ADD CONSTRAINT "Content_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Content"("id") ON DELETE SET NULL ON UPDATE CASCADE; 24 | 25 | -- AddForeignKey 26 | ALTER TABLE "CourseContent" ADD CONSTRAINT "CourseContent_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 27 | 28 | -- AddForeignKey 29 | ALTER TABLE "CourseContent" ADD CONSTRAINT "CourseContent_contentId_fkey" FOREIGN KEY ("contentId") REFERENCES "Content"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 30 | -------------------------------------------------------------------------------- /prisma/migrations/20231119070313_update_video_metadata/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "VideoMetadata" ( 3 | "id" SERIAL NOT NULL, 4 | "contentId" INTEGER NOT NULL, 5 | "video_1080p_1" TEXT, 6 | "video_1080p_2" TEXT, 7 | "video_1080p_3" TEXT, 8 | "video_720p_1" TEXT, 9 | "video_720p_2" TEXT, 10 | "video_720p_3" TEXT, 11 | "video_360p_1" TEXT, 12 | "video_360p_2" TEXT, 13 | "video_360p_3" TEXT, 14 | 15 | CONSTRAINT "VideoMetadata_pkey" PRIMARY KEY ("id") 16 | ); 17 | 18 | -- CreateIndex 19 | CREATE UNIQUE INDEX "VideoMetadata_contentId_key" ON "VideoMetadata"("contentId"); 20 | 21 | -- AddForeignKey 22 | ALTER TABLE "VideoMetadata" ADD CONSTRAINT "VideoMetadata_contentId_fkey" FOREIGN KEY ("contentId") REFERENCES "Content"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 23 | -------------------------------------------------------------------------------- /prisma/migrations/20231120081441_init/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `emailVerified` on the `User` table. All the data in the column will be lost. 5 | - You are about to drop the column `image` on the `User` table. All the data in the column will be lost. 6 | - You are about to drop the `Account` table. If the table is not empty, all the data it contains will be lost. 7 | - You are about to drop the `VerificationToken` table. If the table is not empty, all the data it contains will be lost. 8 | 9 | */ 10 | -- DropForeignKey 11 | ALTER TABLE "Account" DROP CONSTRAINT "Account_userId_fkey"; 12 | 13 | -- AlterTable 14 | ALTER TABLE "User" DROP COLUMN "emailVerified", 15 | DROP COLUMN "image"; 16 | 17 | -- DropTable 18 | DROP TABLE "Account"; 19 | 20 | -- DropTable 21 | DROP TABLE "VerificationToken"; 22 | -------------------------------------------------------------------------------- /prisma/migrations/20231121054501_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "DiscordConnect" ( 3 | "id" TEXT NOT NULL, 4 | "discordId" TEXT NOT NULL, 5 | "userId" TEXT NOT NULL, 6 | "inviteLink" TEXT NOT NULL, 7 | 8 | CONSTRAINT "DiscordConnect_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "DiscordConnect_discordId_key" ON "DiscordConnect"("discordId"); 13 | 14 | -- CreateIndex 15 | CREATE UNIQUE INDEX "DiscordConnect_userId_key" ON "DiscordConnect"("userId"); 16 | 17 | -- AddForeignKey 18 | ALTER TABLE "DiscordConnect" ADD CONSTRAINT "DiscordConnect_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 19 | -------------------------------------------------------------------------------- /prisma/migrations/20231125233546_appx/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `appx_course_id` to the `Course` table without a default value. This is not possible if the table is not empty. 5 | - Added the required column `discord_role_id` to the `Course` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "Course" ADD COLUMN "appx_course_id" INTEGER NOT NULL, 10 | ADD COLUMN "discord_role_id" TEXT NOT NULL; 11 | -------------------------------------------------------------------------------- /prisma/migrations/20231125233929_appx/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `appx_course_id` on the `Course` table. All the data in the column will be lost. 5 | - You are about to drop the column `discord_role_id` on the `Course` table. All the data in the column will be lost. 6 | - Added the required column `appxCourseId` to the `Course` table without a default value. This is not possible if the table is not empty. 7 | - Added the required column `discordRoleId` to the `Course` table without a default value. This is not possible if the table is not empty. 8 | 9 | */ 10 | -- AlterTable 11 | ALTER TABLE "Course" DROP COLUMN "appx_course_id", 12 | DROP COLUMN "discord_role_id", 13 | ADD COLUMN "appxCourseId" INTEGER NOT NULL, 14 | ADD COLUMN "discordRoleId" TEXT NOT NULL; 15 | -------------------------------------------------------------------------------- /prisma/migrations/20231126005118_appx/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `inviteLink` on the `DiscordConnect` table. All the data in the column will be lost. 5 | - Added the required column `username` to the `DiscordConnect` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "DiscordConnect" DROP COLUMN "inviteLink", 10 | ADD COLUMN "username" TEXT NOT NULL; 11 | -------------------------------------------------------------------------------- /prisma/migrations/20231129013219_add_bulk_discords/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "DiscordConnectBulk" ( 3 | "id" TEXT NOT NULL, 4 | "username" TEXT NOT NULL, 5 | "discordId" TEXT NOT NULL, 6 | "userId" TEXT NOT NULL, 7 | 8 | CONSTRAINT "DiscordConnectBulk_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "DiscordConnectBulk_discordId_key" ON "DiscordConnectBulk"("discordId"); 13 | -------------------------------------------------------------------------------- /prisma/migrations/20231129030824_add_bulk_discords_unique/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "DiscordConnectBulk_discordId_key"; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231129095057_add_schema_subtitles/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "subtitles" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231130102613_add_schema_subtitles/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "video_1080p_4" TEXT, 3 | ADD COLUMN "video_360p_4" TEXT, 4 | ADD COLUMN "video_720p_4" TEXT; 5 | -------------------------------------------------------------------------------- /prisma/migrations/20231204062722_open_to_everyone/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Course" ADD COLUMN "openToEveryone" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231207104413_add_slides_link/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "slides" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231208122916_add_backdoor_drm/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "disableDrm" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231208123319_add_backdoor_mp4_links/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "video_1080p_mp4_1" TEXT, 3 | ADD COLUMN "video_1080p_mp4_2" TEXT, 4 | ADD COLUMN "video_1080p_mp4_3" TEXT, 5 | ADD COLUMN "video_1080p_mp4_4" TEXT, 6 | ADD COLUMN "video_360p_mp4_1" TEXT, 7 | ADD COLUMN "video_360p_mp4_2" TEXT, 8 | ADD COLUMN "video_360p_mp4_3" TEXT, 9 | ADD COLUMN "video_360p_mp4_4" TEXT, 10 | ADD COLUMN "video_720p_mp4_1" TEXT, 11 | ADD COLUMN "video_720p_mp4_2" TEXT, 12 | ADD COLUMN "video_720p_mp4_3" TEXT, 13 | ADD COLUMN "video_720p_mp4_4" TEXT; 14 | -------------------------------------------------------------------------------- /prisma/migrations/20231212075742_add_segemnts_to_videos/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "segments" JSONB; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231214062542_add_more_info/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "thumbnail_mosiac_url" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231214114048_add_duration/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "duration" INTEGER; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20231218161052_adds_current_timestamp/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "VideoProgress" ( 3 | "id" SERIAL NOT NULL, 4 | "userId" TEXT NOT NULL, 5 | "contentId" INTEGER NOT NULL, 6 | "currentTimestamp" INTEGER NOT NULL, 7 | 8 | CONSTRAINT "VideoProgress_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- AddForeignKey 12 | ALTER TABLE "VideoProgress" ADD CONSTRAINT "VideoProgress_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 13 | 14 | -- AddForeignKey 15 | ALTER TABLE "VideoProgress" ADD CONSTRAINT "VideoProgress_contentId_fkey" FOREIGN KEY ("contentId") REFERENCES "Content"("id") ON DELETE CASCADE ON UPDATE CASCADE; 16 | -------------------------------------------------------------------------------- /prisma/migrations/20231218162941_makes_content_user_pair_unique/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[contentId,userId]` on the table `VideoProgress` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "VideoProgress_contentId_userId_key" ON "VideoProgress"("contentId", "userId"); 9 | -------------------------------------------------------------------------------- /prisma/migrations/20240114214841_add_notion_metadata/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Content" ADD COLUMN "notionMetadataId" INTEGER; 3 | 4 | -- CreateTable 5 | CREATE TABLE "NotionMetadata" ( 6 | "id" SERIAL NOT NULL, 7 | "contentId" TEXT NOT NULL, 8 | 9 | CONSTRAINT "NotionMetadata_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- CreateIndex 13 | CREATE UNIQUE INDEX "NotionMetadata_contentId_key" ON "NotionMetadata"("contentId"); 14 | 15 | -- AddForeignKey 16 | ALTER TABLE "Content" ADD CONSTRAINT "Content_notionMetadataId_fkey" FOREIGN KEY ("notionMetadataId") REFERENCES "NotionMetadata"("id") ON DELETE SET NULL ON UPDATE CASCADE; 17 | -------------------------------------------------------------------------------- /prisma/migrations/20240114220154_add_notion_metadata_fix/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `notionId` to the `NotionMetadata` table without a default value. This is not possible if the table is not empty. 5 | - Changed the type of `contentId` on the `NotionMetadata` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. 6 | 7 | */ 8 | -- DropForeignKey 9 | ALTER TABLE "Content" DROP CONSTRAINT "Content_notionMetadataId_fkey"; 10 | 11 | -- AlterTable 12 | ALTER TABLE "NotionMetadata" ADD COLUMN "notionId" TEXT NOT NULL, 13 | DROP COLUMN "contentId", 14 | ADD COLUMN "contentId" INTEGER NOT NULL; 15 | 16 | -- CreateIndex 17 | CREATE UNIQUE INDEX "NotionMetadata_contentId_key" ON "NotionMetadata"("contentId"); 18 | 19 | -- AddForeignKey 20 | ALTER TABLE "NotionMetadata" ADD CONSTRAINT "NotionMetadata_contentId_fkey" FOREIGN KEY ("contentId") REFERENCES "Content"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 21 | -------------------------------------------------------------------------------- /prisma/migrations/20240202163217_add_completed_field_to_video_progress/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoProgress" ADD COLUMN "markAsCompleted" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240203231627_hidden/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Content" ADD COLUMN "hidden" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240226093954_update/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "token" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240317222835_add_is_pinned_comment/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Comment" ADD COLUMN "isPinned" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240318200949_add_bookmark/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Bookmark" ( 3 | "id" SERIAL NOT NULL, 4 | "userId" TEXT NOT NULL, 5 | "contentId" INTEGER NOT NULL, 6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | "courseId" INTEGER NOT NULL, 8 | 9 | CONSTRAINT "Bookmark_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- CreateIndex 13 | CREATE UNIQUE INDEX "Bookmark_contentId_key" ON "Bookmark"("contentId"); 14 | 15 | -- AddForeignKey 16 | ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 17 | 18 | -- AddForeignKey 19 | ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_contentId_fkey" FOREIGN KEY ("contentId") REFERENCES "Content"("id") ON DELETE CASCADE ON UPDATE CASCADE; 20 | 21 | -- AddForeignKey 22 | ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE; 23 | -------------------------------------------------------------------------------- /prisma/migrations/20240319183729_add_password/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "password" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240319185121_add_appx_user_id/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "appxUserId" TEXT, 3 | ADD COLUMN "appxUsername" TEXT; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20240324133414_modify_video_progress/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoProgress" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240402112352_remove_course_from_bookmark/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `courseId` on the `Bookmark` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "Bookmark" DROP CONSTRAINT "Bookmark_courseId_fkey"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "Bookmark" DROP COLUMN "courseId"; 12 | -------------------------------------------------------------------------------- /prisma/migrations/20240424002140_add_certificates/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Certificate" ( 3 | "id" TEXT NOT NULL, 4 | "userId" TEXT NOT NULL, 5 | "courseId" INTEGER NOT NULL, 6 | 7 | CONSTRAINT "Certificate_pkey" PRIMARY KEY ("id") 8 | ); 9 | 10 | -- CreateIndex 11 | CREATE UNIQUE INDEX "Certificate_userId_courseId_key" ON "Certificate"("userId", "courseId"); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "Certificate" ADD CONSTRAINT "Certificate_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "Certificate" ADD CONSTRAINT "Certificate_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /prisma/migrations/20240507073349_adds_certs/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Course" ADD COLUMN "certIssued" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240512172646_add_cert_slug/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Certificate" ADD COLUMN "slug" TEXT NOT NULL DEFAULT 'certId'; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240513051454_fix_constraints/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "Bookmark" DROP CONSTRAINT "Bookmark_contentId_fkey"; 3 | 4 | -- DropIndex 5 | DROP INDEX "Bookmark_contentId_key"; 6 | 7 | -- AddForeignKey 8 | ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_contentId_fkey" FOREIGN KEY ("contentId") REFERENCES "Content"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20240601123949_added_migration_status/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "MigrationStatus" AS ENUM ('NOT_MIGRATED', 'IN_PROGRESS', 'MIGRATED', 'MIGRATION_ERROR'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "VideoMetadata" ADD COLUMN "migration_pickup_time" TIMESTAMP(3), 6 | ADD COLUMN "migration_status" "MigrationStatus" NOT NULL DEFAULT 'NOT_MIGRATED'; 7 | -------------------------------------------------------------------------------- /prisma/migrations/20240601213343_/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "migrated_video_1080p_mp4_1" TEXT, 3 | ADD COLUMN "migrated_video_360p_mp4_1" TEXT, 4 | ADD COLUMN "migrated_video_720p_mp4_1" TEXT; 5 | -------------------------------------------------------------------------------- /prisma/migrations/20240610142535_bunny_added/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "bunnyProxyEnabled" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240720214701_convert_to_string/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Course" ALTER COLUMN "appxCourseId" SET DATA TYPE TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240720222420_discord/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Course" ADD COLUMN "discordOauthUrl" TEXT NOT NULL DEFAULT ''; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240720230424_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "DiscordConnectBulk" ADD COLUMN "cohortId" TEXT NOT NULL DEFAULT ''; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240811175241_add_payout_methods/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "UpiId" ( 3 | "id" SERIAL NOT NULL, 4 | "value" VARCHAR(256) NOT NULL, 5 | "userId" TEXT NOT NULL, 6 | 7 | CONSTRAINT "UpiId_pkey" PRIMARY KEY ("id") 8 | ); 9 | 10 | -- CreateTable 11 | CREATE TABLE "SolanaAddress" ( 12 | "id" SERIAL NOT NULL, 13 | "value" CHAR(44) NOT NULL, 14 | "userId" TEXT NOT NULL, 15 | 16 | CONSTRAINT "SolanaAddress_pkey" PRIMARY KEY ("id") 17 | ); 18 | 19 | -- CreateIndex 20 | CREATE UNIQUE INDEX "UpiId_userId_value_key" ON "UpiId"("userId", "value"); 21 | 22 | -- CreateIndex 23 | CREATE UNIQUE INDEX "SolanaAddress_userId_value_key" ON "SolanaAddress"("userId", "value"); 24 | 25 | -- AddForeignKey 26 | ALTER TABLE "UpiId" ADD CONSTRAINT "UpiId_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 27 | 28 | -- AddForeignKey 29 | ALTER TABLE "SolanaAddress" ADD CONSTRAINT "SolanaAddress_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 30 | -------------------------------------------------------------------------------- /prisma/migrations/20240816093504_add/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Event" ( 3 | "id" SERIAL NOT NULL, 4 | "title" TEXT NOT NULL, 5 | "start" TIMESTAMP(3) NOT NULL, 6 | "end" TIMESTAMP(3) NOT NULL, 7 | "videoLink" TEXT, 8 | "notes" TEXT, 9 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | "updatedAt" TIMESTAMP(3) NOT NULL, 11 | 12 | CONSTRAINT "Event_pkey" PRIMARY KEY ("id") 13 | ); 14 | -------------------------------------------------------------------------------- /prisma/migrations/20240828061415_add_github_link/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "GitHubLink" ( 3 | "id" TEXT NOT NULL, 4 | "userId" TEXT NOT NULL, 5 | "githubId" TEXT NOT NULL, 6 | "username" TEXT NOT NULL, 7 | "avatarUrl" TEXT, 8 | "access_token" TEXT NOT NULL, 9 | "profileUrl" TEXT, 10 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 11 | "updatedAt" TIMESTAMP(3) NOT NULL, 12 | 13 | CONSTRAINT "GitHubLink_pkey" PRIMARY KEY ("id") 14 | ); 15 | 16 | -- CreateIndex 17 | CREATE UNIQUE INDEX "GitHubLink_userId_key" ON "GitHubLink"("userId"); 18 | 19 | -- AddForeignKey 20 | ALTER TABLE "GitHubLink" ADD CONSTRAINT "GitHubLink_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 21 | -------------------------------------------------------------------------------- /prisma/migrations/20240830090007_add_bounty_submission/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "BountySubmission" ( 3 | "id" TEXT NOT NULL, 4 | "prLink" TEXT NOT NULL, 5 | "paymentMethod" TEXT NOT NULL, 6 | "status" TEXT NOT NULL DEFAULT 'pending', 7 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | "updatedAt" TIMESTAMP(3) NOT NULL, 9 | "amount" DOUBLE PRECISION NOT NULL DEFAULT 0, 10 | "userId" TEXT NOT NULL, 11 | 12 | CONSTRAINT "BountySubmission_pkey" PRIMARY KEY ("id") 13 | ); 14 | 15 | -- CreateIndex 16 | CREATE UNIQUE INDEX "BountySubmission_userId_prLink_key" ON "BountySubmission"("userId", "prLink"); 17 | 18 | -- AddForeignKey 19 | ALTER TABLE "BountySubmission" ADD CONSTRAINT "BountySubmission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 20 | -------------------------------------------------------------------------------- /prisma/migrations/20240830204152_/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "original_mp4_url" TEXT, 3 | ADD COLUMN "transcoded" BOOLEAN NOT NULL DEFAULT false; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20240830223934_add_subtitle_tries/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "subtitle_tried" INTEGER NOT NULL DEFAULT 0; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241121062529_add_appx_fields/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "appxAuthToken" TEXT; 3 | 4 | -- AlterTable 5 | ALTER TABLE "VideoMetadata" ADD COLUMN "appxVideoId" TEXT; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20241130091016_added_appx_video_id_mapping_with_appx_video_json/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "VideoMetadata" ADD COLUMN "appxVideoJSON" JSONB; 3 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /public/Content-Cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/cms/f4a9673e42b459d87abcf10b355504ca9eb5e336/public/Content-Cover.png -------------------------------------------------------------------------------- /public/Mockup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/cms/f4a9673e42b459d87abcf10b355504ca9eb5e336/public/Mockup.png -------------------------------------------------------------------------------- /public/banner_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/cms/f4a9673e42b459d87abcf10b355504ca9eb5e336/public/banner_placeholder.png -------------------------------------------------------------------------------- /public/certificate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/cms/f4a9673e42b459d87abcf10b355504ca9eb5e336/public/certificate.pdf -------------------------------------------------------------------------------- /public/certificate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/cms/f4a9673e42b459d87abcf10b355504ca9eb5e336/public/certificate.png -------------------------------------------------------------------------------- /public/fonts/font.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/cms/f4a9673e42b459d87abcf10b355504ca9eb5e336/public/fonts/font.woff2 -------------------------------------------------------------------------------- /public/fonts/satoshi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/cms/f4a9673e42b459d87abcf10b355504ca9eb5e336/public/fonts/satoshi.ttf -------------------------------------------------------------------------------- /public/footer-logos/all-logos.ts: -------------------------------------------------------------------------------- 1 | export { default as GitHubLogo } from './logos-svg/github-logo'; 2 | export { default as InstaLogo } from './logos-svg/insta-logo'; 3 | export { default as XLogo } from './logos-svg/x-logo'; 4 | export { default as YtLogo } from './logos-svg/yt-logo'; 5 | -------------------------------------------------------------------------------- /public/footer-logos/logos-svg/github-logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const GitHubLogo = () => ( 4 | 10 | 14 | 15 | ); 16 | 17 | export default GitHubLogo; 18 | -------------------------------------------------------------------------------- /public/footer-logos/logos-svg/insta-logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const InstaLogo = () => ( 4 | 10 | 17 | 22 | 29 | 30 | ); 31 | 32 | export default InstaLogo; 33 | -------------------------------------------------------------------------------- /public/footer-logos/logos-svg/x-logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const XLogo = () => ( 4 | 10 | 15 | 21 | 22 | ); 23 | 24 | export default XLogo; 25 | -------------------------------------------------------------------------------- /public/footer-logos/logos-svg/yt-logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const YtLogo = () => ( 4 | 10 | 18 | 23 | 24 | ); 25 | 26 | export default YtLogo; 27 | -------------------------------------------------------------------------------- /public/harkirat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/cms/f4a9673e42b459d87abcf10b355504ca9eb5e336/public/harkirat.png -------------------------------------------------------------------------------- /public/platform/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/platform/sol.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /public/platform/upi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /public/subs.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 1 4 | 00:00:01.000 --> 00:00:02.500 5 | Hello, welcome to the video! 6 | 7 | 2 8 | 00:00:02.500 --> 00:00:04.000 9 | This is a demonstration of subtitle use. 10 | 11 | 3 12 | 00:00:04.000 --> 00:00:06.000 13 | Subtitles appear on screen like this. 14 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | # Copies .env.example and changes it to .env so that future commands can find the env file 2 | cp .env.example .env 3 | 4 | 5 | # Start PostgreSQL container 6 | docker run -d \ 7 | --name cms-db \ 8 | -e POSTGRES_USER=myuser \ 9 | -e POSTGRES_PASSWORD=mypassword \ 10 | -e POSTGRES_DB=mydatabase \ 11 | -p 5432:5432 \ 12 | postgres 13 | 14 | # Wait for the PostgreSQL container to be ready 15 | echo "Waiting for PostgreSQL to be ready..." 16 | sleep 10 17 | 18 | # Install dependencies 19 | pnpm install 20 | 21 | # Run Prisma migrations 22 | pnpm run prisma:migrate 23 | 24 | # Generate Prisma client 25 | pnpm prisma generate 26 | 27 | # Seed the database 28 | pnpm run db:seed 29 | 30 | # Start the development server 31 | pnpm run dev 32 | -------------------------------------------------------------------------------- /src/actions/answer/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const AnswerInsertSchema = z.object({ 4 | content: z.string().min(2, 'Answer content too short'), 5 | questionId: z.number(), 6 | parentId: z.number().optional(), // Optional, used if the answer is a response to another answer 7 | }); 8 | export const AnswerUpdateSchema = z.object({ 9 | answerId: z.number(), 10 | content: z.string().min(2, 'Answer content too short'), 11 | }); 12 | export const AnswerDeleteSchema = z.object({ 13 | answerId: z.number(), 14 | }); 15 | -------------------------------------------------------------------------------- /src/actions/answer/types.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { Answer } from '@prisma/client'; 3 | import { ActionState } from '@/lib/create-safe-action'; 4 | import { 5 | AnswerDeleteSchema, 6 | AnswerInsertSchema, 7 | AnswerUpdateSchema, 8 | } from './schema'; 9 | import { Delete } from '@/lib/utils'; 10 | 11 | // Import or define your Answer Zod schemas here 12 | // For example: 13 | // import { AnswerInsertSchema, AnswerUpdateSchema, AnswerDeleteSchema } from './schema'; 14 | 15 | export type InputTypeCreateAnswer = z.infer; 16 | export type ReturnTypeCreateAnswer = ActionState; 17 | 18 | export type InputTypeUpdateAnswer = z.infer; 19 | export type ReturnTypeUpdateAnswer = ActionState; 20 | 21 | export type DeleteTypeAnswer = z.infer; 22 | export type ReturnTypeDeleteAnswer = ActionState; 23 | -------------------------------------------------------------------------------- /src/actions/bookmark/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const BookmarkCreateSchema = z.object({ 4 | contentId: z.number(), 5 | }); 6 | export const BookmarkDeleteSchema = z.object({ 7 | id: z.number(), 8 | }); 9 | -------------------------------------------------------------------------------- /src/actions/bookmark/types.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { BookmarkCreateSchema, BookmarkDeleteSchema } from './schema'; 3 | import { ActionState } from '@/lib/create-safe-action'; 4 | import { Bookmark, Content, CourseContent } from '@prisma/client'; 5 | 6 | export type InputTypeCreateBookmark = z.infer; 7 | export type ReturnTypeCreateBookmark = ActionState< 8 | InputTypeCreateBookmark, 9 | Bookmark 10 | >; 11 | export type InputTypeDeleteBookmark = z.infer; 12 | export type ReturnTypeDeleteBookmark = ActionState< 13 | InputTypeDeleteBookmark, 14 | Bookmark 15 | >; 16 | 17 | export type TBookmarkWithContent = Bookmark & { 18 | content: Content & { 19 | parent: { id: number; courses: CourseContent[] } | null; 20 | courses: CourseContent[]; 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/actions/bounty/index.ts: -------------------------------------------------------------------------------- 1 | export * from './userActions'; 2 | export * from './adminActions'; 3 | -------------------------------------------------------------------------------- /src/actions/bounty/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const bountySubmissionSchema = z.object({ 4 | prLink: z.string().url({ message: 'Invalid GitHub PR link' }), 5 | paymentMethod: z.string(), 6 | }); 7 | 8 | export const adminApprovalSchema = z.object({ 9 | bountyId: z.string(), 10 | status: z.enum(['approved', 'rejected']), 11 | }); 12 | -------------------------------------------------------------------------------- /src/actions/bounty/types.ts: -------------------------------------------------------------------------------- 1 | export interface BountySubmissionData { 2 | prLink: string; 3 | paymentMethod: string; 4 | } 5 | 6 | export interface AdminApprovalData { 7 | bountyId: string; 8 | status: 'approved' | 'rejected'; 9 | } 10 | -------------------------------------------------------------------------------- /src/actions/bounty/userActions.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import prisma from '@/db'; 3 | import { bountySubmissionSchema } from './schema'; 4 | import { BountySubmissionData } from './types'; 5 | import { getServerSession } from 'next-auth'; 6 | import { authOptions } from '@/lib/auth'; 7 | import { createSafeAction } from '@/lib/create-safe-action'; 8 | import { Prisma } from '@prisma/client'; 9 | 10 | async function submitBountyHandler(data: BountySubmissionData) { 11 | try { 12 | const session = await getServerSession(authOptions); 13 | 14 | if (!session || !session.user) { 15 | return { error: 'User not authenticated' }; 16 | } 17 | 18 | const bountySubmission = await prisma.bountySubmission.create({ 19 | data: { 20 | prLink: data.prLink, 21 | paymentMethod: data.paymentMethod, 22 | userId: session.user.id, 23 | }, 24 | }); 25 | return { data: bountySubmission }; 26 | } catch (error: any) { 27 | if (error instanceof Prisma.PrismaClientKnownRequestError) { 28 | if (error.code === 'P2002') { 29 | return { 30 | error: 'PR already submitted. Try a different one.', 31 | }; 32 | } 33 | } 34 | return { error: 'Failed to submit bounty!' }; 35 | } 36 | } 37 | 38 | export async function getUserBounties() { 39 | try { 40 | const session = await getServerSession(authOptions); 41 | 42 | if (!session || !session.user) { 43 | throw new Error('User not authenticated'); 44 | } 45 | 46 | const bounties = await prisma.bountySubmission.findMany({ 47 | where: { userId: session.user.id }, 48 | include: { user: true }, 49 | }); 50 | 51 | return bounties; 52 | } catch (error) { 53 | console.error('Error retrieving user bounties:', error); 54 | throw error; 55 | } 56 | } 57 | 58 | export const submitBounty = createSafeAction( 59 | bountySubmissionSchema, 60 | submitBountyHandler, 61 | ); 62 | -------------------------------------------------------------------------------- /src/actions/comment/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const CommentInsertSchema = z.object({ 4 | content: z.string().min(1, 'Comment content is required'), 5 | contentId: z.number(), 6 | parentId: z.number().optional(), 7 | currentPath: z.string().optional(), 8 | }); 9 | 10 | export const CommentUpdateSchema = z.object({ 11 | commentId: z.number(), 12 | content: z.string().optional(), 13 | // upVotes: z.number().optional(), 14 | // downVotes: z.number().optional(), 15 | approved: z.boolean().optional(), 16 | adminPassword: z.string().optional(), 17 | currentPath: z.string().optional(), 18 | }); 19 | export const CommentApproveIntroSchema = z.object({ 20 | content_comment_ids: z.string(), 21 | approved: z.boolean().optional(), 22 | adminPassword: z.string().optional(), 23 | currentPath: z.string().optional(), 24 | }); 25 | export const CommentDeleteSchema = z.object({ 26 | adminPassword: z.string().optional(), 27 | commentId: z.number(), 28 | currentPath: z.string().optional(), 29 | }); 30 | export const CommentPinSchema = z.object({ 31 | commentId: z.number(), 32 | contentId: z.number(), 33 | currentPath: z.string().optional(), 34 | }); 35 | -------------------------------------------------------------------------------- /src/actions/comment/types.ts: -------------------------------------------------------------------------------- 1 | import { ActionState } from '@/lib/create-safe-action'; 2 | import { z } from 'zod'; 3 | import { 4 | CommentInsertSchema, 5 | CommentUpdateSchema, 6 | CommentDeleteSchema, 7 | CommentApproveIntroSchema, 8 | CommentPinSchema, 9 | } from './schema'; 10 | import { Delete } from '../types'; 11 | import { User, Comment, Vote } from '@prisma/client'; 12 | 13 | export type InputTypeCreateComment = z.infer; 14 | export type ReturnTypeCreateComment = ActionState< 15 | InputTypeCreateComment, 16 | Comment 17 | >; 18 | 19 | export type InputTypeUpdateComment = z.infer; 20 | export type ReturnTypeUpdateComment = ActionState< 21 | InputTypeUpdateComment, 22 | Comment 23 | >; 24 | export type InputTypeApproveIntroComment = z.infer< 25 | typeof CommentApproveIntroSchema 26 | >; 27 | export type ReturnTypeApproveIntroComment = ActionState< 28 | InputTypeApproveIntroComment, 29 | Comment 30 | >; 31 | 32 | export type InputTypeDeleteComment = z.infer; 33 | export type ReturnTypeDeleteComment = ActionState< 34 | InputTypeDeleteComment, 35 | Delete 36 | >; 37 | export type InputTypePinComment = z.infer; 38 | export type ReturnTypePinComment = ActionState; 39 | 40 | export interface ExtendedComment extends Comment { 41 | user?: User; 42 | votes?: Vote[]; 43 | } 44 | -------------------------------------------------------------------------------- /src/actions/commentVote/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { VoteType } from '@prisma/client'; // Assuming VoteType is an enum in Prisma 3 | 4 | export const VoteHandleSchema = z.object({ 5 | commentId: z.number().optional(), 6 | questionId: z.number().optional(), 7 | answerId: z.number().optional(), 8 | voteType: z.nativeEnum(VoteType), 9 | currentPath: z.string(), 10 | slug: z.string(), 11 | }); 12 | -------------------------------------------------------------------------------- /src/actions/commentVote/types.ts: -------------------------------------------------------------------------------- 1 | import { Answer, Comment, Question } from '@prisma/client'; 2 | import { ActionState } from '@/lib/create-safe-action'; 3 | import { z } from 'zod'; 4 | import { VoteHandleSchema } from './schema'; 5 | 6 | export type InputTypeHandleVote = z.infer; 7 | export type ReturnTypeHandleVote = ActionState< 8 | InputTypeHandleVote, 9 | Question | Answer | Comment | null 10 | >; 11 | 12 | export enum VoteTypeModel { 13 | COMMENT, 14 | QUESTION, 15 | ANSWER, 16 | } 17 | -------------------------------------------------------------------------------- /src/actions/event-actions/index.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import prisma from '@/db'; 4 | import { authOptions } from '@/lib/auth'; 5 | import { Event } from '@prisma/client'; 6 | import { getServerSession } from 'next-auth'; 7 | 8 | import { revalidatePath } from 'next/cache'; 9 | 10 | async function isAdmin() { 11 | const session = await getServerSession(authOptions); 12 | const adminEmails = process.env.ADMINS?.split(',') || []; 13 | return session?.user?.email && adminEmails.includes(session.user.email); 14 | } 15 | 16 | export async function fetchEvents() { 17 | return await prisma.event.findMany(); 18 | } 19 | 20 | export async function addEvent( 21 | event: Omit, 22 | ) { 23 | if (!(await isAdmin())) { 24 | return { error: 'Unauthorized' }; 25 | } 26 | 27 | await prisma.event.create({ data: event }); 28 | revalidatePath('/'); 29 | } 30 | 31 | export async function updateEvent(event: Event) { 32 | if (!(await isAdmin())) { 33 | return { error: 'Unauthorized' }; 34 | } 35 | 36 | await prisma.event.update({ 37 | where: { id: event.id }, 38 | data: event, 39 | }); 40 | revalidatePath('/'); 41 | } 42 | 43 | export async function deleteEvent(id: number) { 44 | if (!(await isAdmin())) { 45 | return { error: 'Unauthorized' }; 46 | } 47 | 48 | await prisma.event.delete({ where: { id } }); 49 | revalidatePath('/'); 50 | } 51 | -------------------------------------------------------------------------------- /src/actions/payoutMethods/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const payoutMethodSchema = z.object({ 4 | upiId: z 5 | .string() 6 | .refine((value) => (/^[0-9A-Za-z._-]{2,256}@[A-Za-z]{2,64}$/).test(value), { 7 | message: 'Enter a valid UPI address', 8 | }) 9 | .optional(), 10 | solanaAddress: z 11 | .string() 12 | .refine((value) => (/^[A-Za-z0-9]{44}$/).test(value), { 13 | message: 'Enter a valid Solana address', 14 | }) 15 | .optional(), 16 | }); 17 | 18 | export const upiIdInsertSchema = z.object({ 19 | upiId: z 20 | .string() 21 | .refine((value) => (/^[0-9A_Za-z._-]{2,256}@[A_Za-z]{2,64}$/).test(value), { 22 | message: 'Invalid UPI address', 23 | }), 24 | }); 25 | 26 | export const solanaAddressInsertSchema = z.object({ 27 | solanaAddress: z.string().refine((value) => (/^[A-Za-z0-9]{44}$/).test(value), { 28 | message: 'Invalid Solana address', 29 | }), 30 | }); 31 | 32 | export const payoutMethodDeleteSchema = z.object({ 33 | id: z.number(), 34 | }); 35 | -------------------------------------------------------------------------------- /src/actions/payoutMethods/types.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { 3 | payoutMethodDeleteSchema, 4 | solanaAddressInsertSchema, 5 | upiIdInsertSchema, 6 | } from './schema'; 7 | import { ActionState } from '@/lib/create-safe-action'; 8 | import { SolanaAddress, UpiId } from '@prisma/client'; 9 | import { Delete } from '@/lib/utils'; 10 | 11 | export type InputTypeCreateUpi = z.infer; 12 | export type ReturnTypeCreateUpi = ActionState; 13 | 14 | export type InputTypeCreateSolana = z.infer; 15 | export type ReturnTypeCreateSolana = ActionState< 16 | InputTypeCreateSolana, 17 | SolanaAddress 18 | >; 19 | 20 | export type DeleteTypePayoutMethod = z.infer; 21 | export type ReturnTypePayoutMethodDelete = ActionState< 22 | DeleteTypePayoutMethod, 23 | Delete 24 | >; 25 | -------------------------------------------------------------------------------- /src/actions/question/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const QuestionInsertSchema = z.object({ 4 | title: z.string().min(5, 'Question title too short'), 5 | content: z.string().min(0, 'Question content too short'), 6 | tags: z.array(z.string()).optional(), 7 | }); 8 | 9 | export const QuestionUpdateSchema = z.object({ 10 | title: z.string().min(5, 'Question title too short'), 11 | content: z.string().min(0, 'Question content too short'), 12 | tags: z.array(z.string()).optional(), 13 | questionId: z.number(), 14 | }); 15 | export const QuestionDeleteSchema = z.object({ 16 | questionId: z.number(), 17 | }); 18 | -------------------------------------------------------------------------------- /src/actions/types.ts: -------------------------------------------------------------------------------- 1 | import { CommentType } from '@prisma/client'; 2 | 3 | export interface QueryParams { 4 | limit?: number; 5 | page?: number; 6 | tabtype?: TabType; 7 | search?: string; 8 | date?: string; 9 | type?: CommentType; 10 | parentId?: number; 11 | userId?: number; 12 | commentId?: number; 13 | timestamp?: number; 14 | editCommentId?: number; 15 | newPost?: 'open' | 'close'; 16 | } 17 | export enum TabType { 18 | md = 'Most downvotes', 19 | mu = 'Most upvotes', 20 | mr = 'Most Recent', 21 | mq = 'My question', 22 | } 23 | export type Delete = { 24 | message: string; 25 | }; 26 | 27 | export enum ROLES { 28 | ADMIN = 'admin', 29 | USER = 'user', 30 | } 31 | -------------------------------------------------------------------------------- /src/actions/videopreview/videoPreview.tsx: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | // import db from '@/db'; 3 | 4 | export default async function VideoPreview({ 5 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 6 | contentId, 7 | }: { 8 | contentId: number; 9 | }) { 10 | // const videoMetadata = await db.videoMetadata.findFirst({ 11 | // where: { contentId }, 12 | // select: { video_360p_1: true }, 13 | // }); 14 | 15 | // if (videoMetadata) { 16 | // return videoMetadata.video_360p_1; 17 | // } 18 | return null; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/(main)/(pages)/bookmark/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 |
7 | 8 | 9 |
10 |
11 | {[1, 2, 3, 4, 5, 6].map((v) => ( 12 |
13 | 14 | 15 | 16 |
17 | ))} 18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/(main)/(pages)/bookmark/page.tsx: -------------------------------------------------------------------------------- 1 | import { Content, CourseContent, VideoProgress } from '@prisma/client'; 2 | import BookmarkView from '@/components/bookmark/BookmarkView'; 3 | import { getBookmarkDataWithContent } from '@/db/bookmark'; 4 | 5 | export type TWatchHistory = VideoProgress & { 6 | content: Content & { 7 | parent: { id: number; courses: CourseContent[] } | null; 8 | VideoMetadata: { duration: number | null } | null; 9 | }; 10 | }; 11 | 12 | export default async function BookmarksPage() { 13 | const bookmarkData = await getBookmarkDataWithContent(); 14 | 15 | return ( 16 |
17 |
18 |

19 | Bookmarks 20 |

21 |
22 |
23 | 24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/app/(main)/(pages)/home/page.tsx: -------------------------------------------------------------------------------- 1 | import { Greeting } from '@/components/Greeting'; 2 | import { MyCourses } from '@/components/MyCourses'; 3 | import { Redirect } from '@/components/Redirect'; 4 | import { getServerSession } from 'next-auth'; 5 | 6 | export default async function MyCoursesPage() { 7 | const session = await getServerSession(); 8 | 9 | if (!session?.user) { 10 | return ; 11 | } 12 | 13 | return ( 14 |
15 |
16 |

17 | {session.user.name} 18 |

19 |
20 | 21 |
22 | 23 |
24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/app/(main)/(pages)/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { getServerSession } from 'next-auth'; 3 | import { Redirect } from '@/components/Redirect'; 4 | 5 | interface Props { 6 | children: React.ReactNode; 7 | } 8 | 9 | export default async function MainLayout(props: Props) { 10 | const session = await getServerSession(); 11 | 12 | if (!session?.user) { 13 | return ; 14 | } 15 | return
{props.children}
; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/(main)/(pages)/question/[slug]/@answers/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | 3 | const LoadingAnswers = () => { 4 | return ( 5 |
6 |
7 | 8 | 9 |
10 |
11 | {[1, 2, 3].map((i) => ( 12 |
13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 |
21 | ))} 22 |
23 |
24 | ); 25 | }; 26 | 27 | export default LoadingAnswers; 28 | -------------------------------------------------------------------------------- /src/app/(main)/(pages)/question/[slug]/@question/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | 3 | const LoadingQuestion = () => { 4 | return ( 5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 |
17 | ); 18 | }; 19 | 20 | export default LoadingQuestion; 21 | -------------------------------------------------------------------------------- /src/app/(main)/(pages)/question/[slug]/@question/page.tsx: -------------------------------------------------------------------------------- 1 | import { QueryParams } from '@/actions/types'; 2 | import React from 'react'; 3 | import db from '@/db'; 4 | import { getServerSession } from 'next-auth'; 5 | import { authOptions } from '@/lib/auth'; 6 | import PostCard from '@/components/posts/PostCard'; 7 | 8 | const SingleQuestionPage = async ({ 9 | params, 10 | }: { 11 | params: { slug: string }; 12 | searchParams: QueryParams; 13 | }) => { 14 | const session = await getServerSession(authOptions); 15 | const sessionId = session?.user.id; 16 | const question: any = await db.question.findUnique({ 17 | where: { 18 | slug: params.slug, 19 | }, 20 | select: { 21 | id: true, 22 | title: true, 23 | upvotes: true, 24 | downvotes: true, 25 | totalanswers: true, 26 | tags: true, 27 | slug: true, 28 | authorId: true, 29 | content: true, 30 | createdAt: true, 31 | updatedAt: true, 32 | author: { 33 | select: { 34 | id: true, 35 | name: true, 36 | }, 37 | }, 38 | votes: { 39 | where: { 40 | userId: sessionId, 41 | }, 42 | select: { 43 | userId: true, 44 | voteType: true, 45 | }, 46 | }, 47 | }, 48 | }); 49 | 50 | return ( 51 |
52 |
53 | {question && ( 54 | 62 | )} 63 |
64 |
65 | ); 66 | }; 67 | 68 | export default SingleQuestionPage; 69 | -------------------------------------------------------------------------------- /src/app/(main)/(pages)/question/[slug]/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function QuestionLayout(props: { 4 | children: React.ReactNode; 5 | question: React.ReactNode; 6 | answers: React.ReactNode; 7 | }) { 8 | return ( 9 |
10 | {props.children} 11 | {props.question} 12 | {props.answers} 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/app/(main)/(pages)/question/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { getDisabledFeature } from '@/lib/utils'; 2 | import { redirect } from 'next/navigation'; 3 | import { Metadata } from 'next'; 4 | import { siteConfig } from '@/config/site-config'; 5 | 6 | const page = () => { 7 | const disabled = getDisabledFeature('qa'); 8 | if (disabled) { 9 | redirect('/question'); 10 | } 11 | return null; 12 | }; 13 | 14 | export async function generateMetadata({ 15 | params, 16 | }: { 17 | params: { slug: string }; 18 | }): Promise { 19 | return { 20 | title: `${params.slug} | ${siteConfig.title}`, 21 | }; 22 | } 23 | 24 | export default page; 25 | -------------------------------------------------------------------------------- /src/app/(main)/(pages)/watch-history/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 |
7 | 8 | 9 |
10 |
11 | {[1, 2].map((_, index) => ( 12 |
13 | 14 |
15 | {[1, 2, 3, 4].map((_, idx) => ( 16 |
17 | 18 | 19 |
20 | ))} 21 |
22 |
23 | ))} 24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/app/(main)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Appbar } from '@/components/Appbar'; 2 | import React from 'react'; 3 | 4 | interface Props { 5 | children: React.ReactNode; 6 | } 7 | 8 | export default (props: Props) => { 9 | return ( 10 |
11 | 12 |
{props.children}
13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/app/(marketing)/(policy)/refund/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import Footer from '@/components/landing/footer'; 3 | import FooterCTA from '@/components/landing/footer-cta'; 4 | import { motion } from 'framer-motion'; 5 | 6 | const RefundAndCancellationPage = () => { 7 | return ( 8 | <> 9 | 21 |

22 | Refund/Cancellation Policy 23 |

24 | 36 |

37 | You are entitled to a refund in the case of the purchased course not 38 | being assigned to you within the expiration date from your date of 39 | purchase or if you have paid twice for the same course. Under any 40 | other circumstance, we will not consider any requests for refund as 41 | this is a digital course purchase. 42 |

43 |
44 |
45 | 46 |