├── .cursorrules ├── .env.example ├── .eslintrc.json ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .storybook ├── main.ts ├── preview.ts └── vitest.setup.ts ├── .vscode ├── extensions.json └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.MD ├── components.json ├── content-collections.ts ├── next.config.js ├── package.json ├── postcss.config.mjs ├── prisma ├── migrations │ ├── 20240915171008_init │ │ └── migration.sql │ ├── 20240917210640_question_answers_migration │ │ └── migration.sql │ ├── 20240917212435_question_id_question_uid │ │ └── migration.sql │ ├── 20240919110901_questions_answer_resource │ │ └── migration.sql │ ├── 20240922134957_question_uid_change_and_correct_answer_added │ │ └── migration.sql │ ├── 20240922135159_questions_table_change_22_09_2024 │ │ └── migration.sql │ ├── 20240926200005_answers_init_migration │ │ └── migration.sql │ ├── 20240926202353_answers_correct_answer_to_boolean │ │ └── migration.sql │ ├── 20240926203943_answers_migration │ │ └── migration.sql │ ├── 20240927110748_update_answers_model │ │ └── migration.sql │ ├── 20240927112116_update_users_to_add_daily_streak │ │ └── migration.sql │ ├── 20240929184307_user_profile_pic │ │ └── migration.sql │ ├── 20241005121723_user_key_name_migration │ │ └── migration.sql │ ├── 20241005121850_user_key_name_migration_dev │ │ └── migration.sql │ ├── 20241008190017_user_show_time_taken_flag_added │ │ └── migration.sql │ ├── 20241022205215_adds_subscription_model │ │ └── migration.sql │ ├── 20241022205422_subscription_to_subscriptions_name_change │ │ └── migration.sql │ ├── 20241023202920_add_product_id_to_subscription_schema │ │ └── migration.sql │ ├── 20241024203214_adds_first_last_name_to_users │ │ └── migration.sql │ ├── 20241027221230_questions_question_date_datetime_to_string │ │ └── migration.sql │ ├── 20241102205913_adds_cascade │ │ └── migration.sql │ ├── 20241103201712_adds_code_snippet_to_question_model │ │ └── migration.sql │ ├── 20241104222534_adds_hint_to_question │ │ └── migration.sql │ ├── 20241105211551_adds_daily_question_flag_to_questions │ │ └── migration.sql │ ├── 20241106193449_adds_tags_to_question_schema │ │ └── migration.sql │ ├── 20241106194917_adds_tag_schema │ │ └── migration.sql │ ├── 20241107210935_subscription_optional_fields │ │ └── migration.sql │ ├── 20241108160452_adds_stripe_field_to_subscription │ │ └── migration.sql │ ├── 20241108204713_updates_user_level_enum │ │ └── migration.sql │ ├── 20241108211606_adds_stripe_subscription_item_id_to_subscription_schema │ │ └── migration.sql │ ├── 20241109205431_adds_difficulty_to_question_schema │ │ └── migration.sql │ ├── 20241109205633_difficulty_name_change │ │ └── migration.sql │ ├── 20241113194612_adds_streaks_schema │ │ └── migration.sql │ ├── 20241116174538_nullable_streak_start_and_streask_end │ │ └── migration.sql │ ├── 20241119222138_adds_roadmap_schema │ │ └── migration.sql │ ├── 20241119232627_roadmap_schema_changes │ │ └── migration.sql │ ├── 20241120200750_roadmap_default_question_correct_answer_flag │ │ └── migration.sql │ ├── 20241120202406_roadmap_default_question_order_key │ │ └── migration.sql │ ├── 20241120232354_roadmap_status_added_creating_key │ │ └── migration.sql │ ├── 20241121194930_roadmap_status_question_index_added │ │ └── migration.sql │ ├── 20241121202714_roadmap_remove_user_uid_unique_constraint │ │ └── migration.sql │ ├── 20241121211759_roadmap_add_has_generated_roadmap_flag │ │ └── migration.sql │ ├── 20241122154043_roadmap_default_question_adds_ai_title │ │ └── migration.sql │ ├── 20241122224413_roadmap_user_questions_adds_order_key │ │ └── migration.sql │ ├── 20241122231132_roadmap_user_question_answer_remove_order_key │ │ └── migration.sql │ ├── 20241123180614_roadmap_questions_user_add_user_correct_flag │ │ └── migration.sql │ ├── 20241124174107_roadmap_add_title_description_fields │ │ └── migration.sql │ ├── 20241125180235_adds_waitlist_model │ │ └── migration.sql │ ├── 20241209191737_adds_demo_answer_model │ │ └── migration.sql │ ├── 20241214213041_adds_code_snippet_flag_to_question_answers │ │ └── migration.sql │ ├── 20241215123538_adds_push_notifications_to_user_schema │ │ └── migration.sql │ ├── 20241217193058_adds_ai_prompts_schema │ │ └── migration.sql │ ├── 20241224183132_adds_code_editor_theme_to_user │ │ └── migration.sql │ ├── 20241228222813_adds_stats_report_schema │ │ └── migration.sql │ ├── 20241228230549_statistics_report_schema_changes │ │ └── migration.sql │ ├── 20241231223050_adds_total_time_taken_to_report_schema │ │ └── migration.sql │ ├── 20250103121019_adds_question_resource_schema │ │ └── migration.sql │ ├── 20250103121229_updates_question_resource_schema_adds_title │ │ └── migration.sql │ ├── 20250107102316_adds_stripe_emails_key │ │ └── migration.sql │ ├── 20250109121338_adds_slug_to_question_schema │ │ └── migration.sql │ ├── 20250109150134_makes_question_slug_unique │ │ └── migration.sql │ ├── 20250111161617_adds_ai_question_help_tokens_to_schema │ │ └── migration.sql │ ├── 20250113120952_adds_beginner_level_to_question_difficulty │ │ └── migration.sql │ ├── 20250113121315_adds_user_experience_level_to_user_schema │ │ └── migration.sql │ ├── 20250113123716_adds_slug_generated_flag_to_question_schema │ │ └── migration.sql │ ├── 20250113213127_adds_profile_schema │ │ └── migration.sql │ ├── 20250113213339_adds_handle_to_profile_schema │ │ └── migration.sql │ ├── 20250113233653_adds_is_custom_username_flag_to_user_model │ │ └── migration.sql │ ├── 20250114192103_adds_answer_full_snippet_to_question_answer │ │ └── migration.sql │ ├── 20250114194452_adds_title_and_desc_to_question │ │ └── migration.sql │ ├── 20250115220739_adds_referral_and_hear_about_keys │ │ └── migration.sql │ ├── 20250116214734_adds_difficulty_to_answer │ │ └── migration.sql │ ├── 20250117210627_adds_about_me_ai_help_to_user │ │ └── migration.sql │ ├── 20250118000102_init_adds_badge_schema │ │ └── migration.sql │ ├── 20250118202007_adds_question_type_enum_to_question_schema │ │ └── migration.sql │ ├── 20250118212542_adds_coding_challenge_fields │ │ └── migration.sql │ ├── 20250118215409_makes_user_answer_uid_option_in_answers_schema │ │ └── migration.sql │ ├── 20250122212032_adds_next_prev_flags_to_question_schema │ │ └── migration.sql │ ├── 20250122221549_adds_bookmark_question_schema │ │ └── migration.sql │ ├── 20250123121546_fixes_bookmark_schema │ │ └── migration.sql │ ├── 20250123211622_adds_premium_question_toggle │ │ └── migration.sql │ ├── 20250125230100_adds_study_path_schema │ │ └── migration.sql │ ├── 20250126193422_adds_category_to_study_path │ │ └── migration.sql │ ├── 20250127232301_adds_answer_uid_to_roadmap_user_answer │ │ └── migration.sql │ ├── 20250129122720_adds_bookmarks_to_roadmap_questions │ │ └── migration.sql │ ├── 20250129124520_adds_bookmarks_to_roadmap_questions_fix │ │ └── migration.sql │ ├── 20250129180933_adds_difficulty_to_roadmap_question_answer │ │ └── migration.sql │ ├── 20250131004931_adds_icon_to_study_path │ │ └── migration.sql │ ├── 20250131121003_adds_is_published_flag_to_study_paths │ │ └── migration.sql │ ├── 20250204225110_adds_mission_schema │ │ └── migration.sql │ ├── 20250205231033_makes_user_mission_user_uid_unique │ │ └── migration.sql │ ├── 20250206003251_adds_next_study_path_slug_to_study_path_schema │ │ └── migration.sql │ ├── 20250206205217_adds_icon_field_to_missions │ │ └── migration.sql │ ├── 20250206213644_removes_unique_on_user_mission_user_uid │ │ └── migration.sql │ ├── 20250209210619_adds_spent_per_day_and_7_day_challenge_email_flag │ │ └── migration.sql │ ├── 20250221195354_adds_promotional_emails_flag_to_user │ │ └── migration.sql │ ├── 20250222102545_adds_custom_coupon_flag_to_user │ │ └── migration.sql │ ├── 20250222103344_adds_has_created_custom_coupon_to_user_schema │ │ └── migration.sql │ ├── 20250222110159_adds_user_custom_coupon_expires_at_flag_to_user │ │ └── migration.sql │ ├── 20250222180413_adds_roadmap_generation_progress_model │ │ └── migration.sql │ ├── 20250222180927_adds_adds_generated_to_generation_progress │ │ └── migration.sql │ ├── 20250222181023_adds_adds_error_states_to_gen_schema │ │ └── migration.sql │ ├── 20250222184131_removes_roadmap_relation_to_generation_doc_as_not_created_at_this_point │ │ └── migration.sql │ ├── 20250225201655_adds_new_user_level │ │ └── migration.sql │ ├── 20250302175702_adds_pseo_schema │ │ └── migration.sql │ ├── 20250302180754_pseo_optional_field_changes │ │ └── migration.sql │ ├── 20250303114247_adds_is_published_flag_to_pseo_pages │ │ └── migration.sql │ ├── 20250304195916_modified_study_path_goal_schema_04_03_2025 │ │ └── migration.sql │ ├── 20250304213753_adds_user_enrol_to_study_path_goal │ │ └── migration.sql │ ├── 20250304214550_adds_user_enrol_to_study_path_goal_fixes_uid │ │ └── migration.sql │ ├── 20250304215645_fixes_study_path_goal_user_study_path_relation │ │ └── migration.sql │ ├── 20250307191502_adds_user_xp_to_user_schema │ │ └── migration.sql │ ├── 20250307194020_adds_weekly_xp │ │ └── migration.sql │ ├── 20250309152758_init_league_schema_commit │ │ └── migration.sql │ ├── 20250321214136_adds_simple_multiple_choice_question_type │ │ └── migration.sql │ ├── 20250322150258_adds_after_question_info_field_to_question_type │ │ └── migration.sql │ ├── 20250329121307_adds_new_game_mode_schema │ │ └── migration.sql │ ├── 20250329182957_adds_overview_data_key_to_study_path │ │ └── migration.sql │ ├── 20250406192418_adds_study_path_type_to_study_path │ │ └── migration.sql │ ├── 20250410194148_adds_category_tool_tip_to_study_path │ │ └── migration.sql │ └── migration_lock.toml └── schema │ ├── badge.prisma │ ├── leagues.prisma │ ├── missions.prisma │ ├── questions.prisma │ ├── roadmap.prisma │ ├── schema.prisma │ ├── statistics.prisma │ ├── study-path.prisma │ └── users.prisma ├── src ├── actions │ ├── ai │ │ ├── generate-pseo-content.ts │ │ ├── questions │ │ │ ├── answer-help.ts │ │ │ └── question-help.ts │ │ ├── reports │ │ │ ├── generate-report.ts │ │ │ └── utils │ │ │ │ ├── generate-custom-questions.ts │ │ │ │ ├── generate-report-html.ts │ │ │ │ ├── get-tags-report.ts │ │ │ │ └── stream-response.ts │ │ ├── roadmap │ │ │ ├── generate.ts │ │ │ ├── get-question-data-for-gen.ts │ │ │ ├── questions │ │ │ │ └── generate-new.ts │ │ │ └── utils │ │ │ │ ├── add-order-to-response-questions.ts │ │ │ │ ├── add-uids-to-response.ts │ │ │ │ └── generate-roadmap.ts │ │ └── utils │ │ │ ├── get-prompt.ts │ │ │ └── user-tokens.ts │ ├── answers │ │ ├── answer-onboarding.ts │ │ └── answer.ts │ ├── daily-missions │ │ └── create-user-missions-record.ts │ ├── leagues │ │ ├── create-league.ts │ │ └── update-league.ts │ ├── misc │ │ ├── create-pseo-page.ts │ │ ├── edit-pseo-page.ts │ │ ├── send-feedback.ts │ │ ├── send-invite.ts │ │ ├── send-no-challenges-email.ts │ │ ├── send-welcome-email.ts │ │ └── toggle-pseo-publish.ts │ ├── questions │ │ ├── add.ts │ │ ├── admin │ │ │ └── list.ts │ │ ├── bookmark.ts │ │ ├── execute.ts │ │ └── update-question.ts │ ├── roadmap │ │ ├── create-or-fetch-user-roadmap.ts │ │ ├── delete-roadmap.ts │ │ ├── questions │ │ │ ├── answer-roadmap-question.ts │ │ │ └── default │ │ │ │ └── answer-roadmap-question.ts │ │ └── update-roadmap-details.ts │ ├── statistics │ │ └── reports │ │ │ └── delete.ts │ ├── stripe │ │ ├── stripe-cancel-subscription.ts │ │ ├── stripe-get-subscription-details.ts │ │ └── stripe-get-user-invoices.ts │ ├── study-paths │ │ ├── enroll.ts │ │ ├── get.ts │ │ ├── send-path-complete-email.ts │ │ └── set-goal.ts │ └── user │ │ ├── account │ │ ├── create-coupon.ts │ │ ├── delete-user.ts │ │ ├── login.ts │ │ ├── logout.ts │ │ ├── oauth.ts │ │ ├── reset-password.ts │ │ └── signup.ts │ │ └── authed │ │ ├── check-username.ts │ │ ├── get-user.ts │ │ ├── handle-promotional-emails-subscription.ts │ │ ├── update-user-auth.ts │ │ └── update-user.ts ├── app │ ├── (app) │ │ ├── (default_layout) │ │ │ ├── (questions) │ │ │ │ └── coding-challenges │ │ │ │ │ ├── custom │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ ├── (roadmaps) │ │ │ │ ├── personalized-roadmaps │ │ │ │ │ └── page.tsx │ │ │ │ └── roadmaps │ │ │ │ │ ├── [slug] │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── leaderboard │ │ │ │ └── page.tsx │ │ │ └── statistics │ │ │ │ ├── page.tsx │ │ │ │ └── reports │ │ │ │ ├── [uid] │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ ├── (roadmap) │ │ │ └── roadmap │ │ │ │ └── [roadmapUid] │ │ │ │ ├── (roadmapUid) │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ │ │ └── [uid] │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ ├── admin │ │ │ ├── layout.tsx │ │ │ ├── leagues │ │ │ │ ├── [uid] │ │ │ │ │ └── page.tsx │ │ │ │ ├── achievements │ │ │ │ │ └── page.tsx │ │ │ │ ├── create │ │ │ │ │ └── page.tsx │ │ │ │ ├── list │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ ├── page.tsx │ │ │ ├── pseo │ │ │ │ ├── edit │ │ │ │ │ └── [uid] │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── list │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ ├── questions │ │ │ │ ├── [uid] │ │ │ │ │ └── page.tsx │ │ │ │ ├── list │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ └── users │ │ │ │ ├── [id] │ │ │ │ └── page.tsx │ │ │ │ ├── edit │ │ │ │ └── [id] │ │ │ │ │ └── page.tsx │ │ │ │ ├── list │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ ├── dashboard │ │ │ ├── page.client.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── providers.tsx │ │ ├── settings │ │ │ ├── account │ │ │ │ └── page.tsx │ │ │ ├── billing │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── profile │ │ │ │ └── page.tsx │ │ └── testing │ │ │ ├── code-editor │ │ │ └── page.tsx │ │ │ ├── leagues │ │ │ └── page.tsx │ │ │ └── upload-test │ │ │ └── page.tsx │ ├── (marketing) │ │ ├── (landing-pages) │ │ │ ├── (pillar-pages) │ │ │ │ ├── javascript-cheat-sheet │ │ │ │ │ ├── javascript-array-cheat-sheet │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── javascript-string-cheat-sheet │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── regular-expression-cheat-sheet │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── javascript-coding-interview-questions-and-answers │ │ │ │ │ ├── javascript-coding-test │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── javascript-interview-questions-for-senior-developers │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── javascript-fundamentals │ │ │ │ │ ├── difference-between-const-var-and-let │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── how-does-javascript-work │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── how-to-add-or-remove-css-classes-with-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── how-to-connect-html-to-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── how-to-convert-a-number-to-string-in-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── how-to-use-operators-in-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── how-to-use-typeof-operator-in-javaScript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── how-to-write-a-function-in-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── how-to-write-comments-in-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── how-to-write-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── how-to-write-loops-in-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── html-conditional-statement │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── javascript-conditionals │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── javascript-format-strings-with-variables │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── javascript-naming-conventions │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── javascript-nested-conditionals │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── loose-vs-strict-equality-in-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── primitive-types-in-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── what-is-a-string-in-javascript │ │ │ │ │ │ └── page.tsx │ │ │ │ └── javascript-projects-for-beginners │ │ │ │ │ ├── how-to-create-a-weather-app-with-javascript │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── programming-challenges-for-beginners │ │ │ │ │ └── page.tsx │ │ │ ├── [...slug] │ │ │ │ └── page.tsx │ │ │ ├── coding-roadmap │ │ │ │ └── page.tsx │ │ │ ├── daily-coding-challenges-for-beginners │ │ │ │ └── page.tsx │ │ │ ├── daily-coding-challenges │ │ │ │ └── page.tsx │ │ │ ├── free-coding-challenges │ │ │ │ └── page.tsx │ │ │ ├── free-leetcode-alternative │ │ │ │ └── page.tsx │ │ │ ├── how-to-learn-javascript │ │ │ │ └── page.tsx │ │ │ ├── how-to-learn-to-code │ │ │ │ └── page.tsx │ │ │ ├── javascript-coding-questions │ │ │ │ └── page.tsx │ │ │ ├── javascript-for-beginners │ │ │ │ └── page.tsx │ │ │ ├── javascript-roadmap │ │ │ │ └── page.tsx │ │ │ ├── learn-javascript │ │ │ │ └── page.tsx │ │ │ ├── learn-to-code │ │ │ │ └── page.tsx │ │ │ ├── personalized-coding-challenges │ │ │ │ └── page.tsx │ │ │ ├── react-coding-challenges │ │ │ │ └── page.tsx │ │ │ └── react-roadmap │ │ │ │ └── page.tsx │ │ ├── author │ │ │ └── logan │ │ │ │ └── page.tsx │ │ ├── blog │ │ │ ├── [slug] │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ ├── page │ │ │ │ └── [page] │ │ │ │ │ └── page.tsx │ │ │ └── posts │ │ │ │ ├── 250-users-on-techblitz.mdx │ │ │ │ ├── 7-free-websites-to-learn-javascript.mdx │ │ │ │ ├── difference-between-const-var-and-let.mdx │ │ │ │ ├── how-does-javascript-work.mdx │ │ │ │ ├── how-to-add-or-remove-css-classes-with-javascript.mdx │ │ │ │ ├── how-to-approach-coding-challenges.mdx │ │ │ │ ├── how-to-become-a-software-engineer-2025.mdx │ │ │ │ ├── how-to-connect-html-to-javascript.mdx │ │ │ │ ├── how-to-convert-a-number-to-string-in-javascript.mdx │ │ │ │ ├── how-to-create-a-weather-app-in-javascript.mdx │ │ │ │ ├── how-to-use-filter-in-javascript.mdx │ │ │ │ ├── how-to-use-map-in-javascript.mdx │ │ │ │ ├── how-to-use-operators-in-javascript.mdx │ │ │ │ ├── how-to-use-reduce-in-javascript.mdx │ │ │ │ ├── how-to-use-some-in-javascript.mdx │ │ │ │ ├── how-to-use-sort-in-javascript.mdx │ │ │ │ ├── how-to-use-split-in-javascript.mdx │ │ │ │ ├── how-to-use-typeof-operator-in-javaScript.mdx │ │ │ │ ├── how-to-write-a-function-in-javascript.mdx │ │ │ │ ├── how-to-write-comments-in-javascript.mdx │ │ │ │ ├── how-to-write-javascript.mdx │ │ │ │ ├── how-to-write-loops-in-javascript.mdx │ │ │ │ ├── html-conditional-statement.mdx │ │ │ │ ├── introducing-techblitz.mdx │ │ │ │ ├── javascript-array-cheat-sheet.mdx │ │ │ │ ├── javascript-coding-test.mdx │ │ │ │ ├── javascript-conditionals.mdx │ │ │ │ ├── javascript-format-strings-with-variables.mdx │ │ │ │ ├── javascript-interview-questions-for-senior-developers.mdx │ │ │ │ ├── javascript-naming-conventions.mdx │ │ │ │ ├── javascript-nested-conditionals.mdx │ │ │ │ ├── javascript-string-cheat-sheet.mdx │ │ │ │ ├── loose-vs-strict-equality-in-javascript.mdx │ │ │ │ ├── primitive-types-in-javascript.mdx │ │ │ │ ├── programming-challenges-for-beginners.mdx │ │ │ │ ├── regular-expression-cheat-sheet.mdx │ │ │ │ ├── what-are-callback-functions.mdx │ │ │ │ ├── what-is-a-string-in-javascript.mdx │ │ │ │ ├── what-is-array-at-in-javascript.mdx │ │ │ │ ├── what-is-git-branch.mdx │ │ │ │ ├── what-is-length-in-javascript.mdx │ │ │ │ └── what-is-triple-equals-in-javascript.mdx │ │ ├── changelog │ │ │ ├── [slug] │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── faqs │ │ │ └── page.tsx │ │ ├── features │ │ │ ├── coding-challenges │ │ │ │ └── page.tsx │ │ │ ├── leaderboard │ │ │ │ └── page.tsx │ │ │ ├── roadmap │ │ │ │ └── page.tsx │ │ │ └── statistics │ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── open-source │ │ │ └── page.tsx │ │ ├── page.tsx │ │ ├── pricing │ │ │ └── page.tsx │ │ ├── privacy │ │ │ └── page.tsx │ │ └── terms │ │ │ └── page.tsx │ ├── (no_nav) │ │ ├── (auth) │ │ │ ├── layout.tsx │ │ │ ├── login │ │ │ │ ├── forgot-password │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ ├── signup │ │ │ │ └── page.tsx │ │ │ ├── update-password │ │ │ │ └── page.tsx │ │ │ └── verify-email │ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── onboarding │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ └── upgrade │ │ │ └── page.tsx │ ├── (questions) │ │ ├── question │ │ │ ├── [slug] │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ │ └── custom │ │ │ │ └── [uid] │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ └── roadmap │ │ │ └── learn │ │ │ └── [slug] │ │ │ └── [subSection] │ │ │ └── lesson │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ ├── api │ │ ├── auth │ │ │ └── confirm │ │ │ │ └── route.ts │ │ ├── cron │ │ │ ├── challenges │ │ │ │ ├── 7-day-no-challenge │ │ │ │ │ └── route.ts │ │ │ │ └── send-suggested-challenge │ │ │ │ │ └── route.ts │ │ │ ├── goals │ │ │ │ └── send-daily-goal-reminder │ │ │ │ │ └── route.ts │ │ │ ├── set-daily-missions │ │ │ │ └── route.ts │ │ │ └── sync-user-streak │ │ │ │ └── route.ts │ │ ├── og │ │ │ └── route.tsx │ │ ├── stripe │ │ │ ├── checkout-session-complete.ts │ │ │ ├── invoice-payment-failed.ts │ │ │ ├── route.ts │ │ │ └── subscription-cancelled.ts │ │ ├── test │ │ │ └── route.ts │ │ ├── upload │ │ │ └── route.ts │ │ └── user │ │ │ └── [uid] │ │ │ └── route.ts │ ├── auth │ │ └── callback │ │ │ └── route.ts │ ├── favicon.ico │ ├── global-error.tsx │ ├── globals.css │ ├── layout.tsx │ ├── manifest.ts │ ├── not-found.tsx │ ├── profile │ │ └── [uid] │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ ├── providers.js │ ├── robots.ts │ ├── sitemap.ts │ └── styles │ │ └── fonts │ │ ├── font.ts │ │ ├── onest │ │ ├── Onest-Black.ttf │ │ ├── Onest-Bold.ttf │ │ ├── Onest-ExtraBold.ttf │ │ ├── Onest-ExtraLight.ttf │ │ ├── Onest-Light.ttf │ │ ├── Onest-Medium.ttf │ │ ├── Onest-Regular.ttf │ │ ├── Onest-SemiBold.ttf │ │ └── Onest-Thin.ttf │ │ └── satoshi │ │ ├── Satoshi-Variable.ttf │ │ └── Satoshi-VariableItalic.ttf ├── components │ ├── app │ │ ├── admin │ │ │ ├── admin-container.tsx │ │ │ ├── chart-component.tsx │ │ │ ├── leagues │ │ │ │ ├── create-league-form.tsx │ │ │ │ └── edit-league-form.tsx │ │ │ ├── pseo │ │ │ │ ├── pseo-ai-generator.tsx │ │ │ │ ├── pseo-form.tsx │ │ │ │ └── pseo-publish-toggle.tsx │ │ │ ├── questions │ │ │ │ ├── new-coding-challenge-modal.tsx │ │ │ │ ├── new-question-modal.tsx │ │ │ │ ├── new-simple-multiple-choice-modal.tsx │ │ │ │ ├── question-form.tsx │ │ │ │ └── question-picker.tsx │ │ │ └── user │ │ │ │ ├── active-users-map.tsx │ │ │ │ ├── recent-users.tsx │ │ │ │ ├── user-chart.tsx │ │ │ │ ├── user-edit-form.tsx │ │ │ │ ├── user-role-badge.tsx │ │ │ │ ├── user-status-badge.tsx │ │ │ │ └── users-stats-card.tsx │ │ ├── dashboard │ │ │ ├── all-questions-bento-box.tsx │ │ │ ├── dashboard-bento-grid.tsx │ │ │ ├── dashboard-header.tsx │ │ │ ├── dashboard-question-card.tsx │ │ │ ├── next-question-bento-box.tsx │ │ │ ├── next-roadmap-bento-box.tsx │ │ │ ├── next-roadmap-graphic.tsx │ │ │ └── progression-bento-box.tsx │ │ ├── filters │ │ │ ├── chips.tsx │ │ │ ├── filter-dropdown.tsx │ │ │ ├── filter.tsx │ │ │ ├── filters-loading.tsx │ │ │ ├── search │ │ │ │ └── tag-search.tsx │ │ │ ├── sort │ │ │ │ └── sort-dropdown.tsx │ │ │ └── tags-carousel.tsx │ │ ├── layout │ │ │ ├── question-single │ │ │ │ ├── code-display-wrapper.tsx │ │ │ │ ├── code-snippet.tsx │ │ │ │ ├── page-header-middle.tsx │ │ │ │ ├── page-header.stories.tsx │ │ │ │ ├── page-header.tsx │ │ │ │ ├── question-action-buttons.tsx │ │ │ │ ├── question-card-loading.tsx │ │ │ │ ├── question-card.tsx │ │ │ │ ├── question-code-display-wrapper.tsx │ │ │ │ ├── question-submitted.tsx │ │ │ │ ├── roadmap-question-action-buttons.tsx │ │ │ │ ├── roadmap-question-tabs.tsx │ │ │ │ └── upgrade-modal.tsx │ │ │ └── questions │ │ │ │ ├── clear-filters.tsx │ │ │ │ ├── question-card-client.tsx │ │ │ │ ├── question-card.tsx │ │ │ │ ├── question-page-sidebar-loading.tsx │ │ │ │ ├── question-page-sidebar.tsx │ │ │ │ └── questions-list.tsx │ │ ├── leaderboard │ │ │ ├── answer-question-modal.tsx │ │ │ ├── leaderboard-hero.tsx │ │ │ ├── leaderboard-most-answered-table.tsx │ │ │ ├── leaderboard-most-questions-answered.tsx │ │ │ ├── leagues │ │ │ │ └── leagues-showcase.tsx │ │ │ └── show-time-taken.tsx │ │ ├── navigation │ │ │ ├── continue-journey-button.tsx │ │ │ ├── question-navigation-loading.tsx │ │ │ ├── question-navigation.tsx │ │ │ ├── sidebar-dropdown.tsx │ │ │ ├── sidebar-footer-premium.tsx │ │ │ ├── sidebar-footer.tsx │ │ │ ├── sidebar-layout-trigger.tsx │ │ │ ├── sidebar-submenu-item.tsx │ │ │ └── sidebar.tsx │ │ ├── onboarding │ │ │ ├── onboarding-first-question-selection.tsx │ │ │ ├── onboarding-footer.tsx │ │ │ ├── onboarding-form.tsx │ │ │ ├── onboarding-initial-questions.tsx │ │ │ ├── onboarding-notifications.tsx │ │ │ ├── onboarding-pricing.tsx │ │ │ ├── onboarding-questions.tsx │ │ │ ├── onboarding-tags.tsx │ │ │ ├── onboarding-time-commitment.tsx │ │ │ └── onboarding-user-details.tsx │ │ ├── questions │ │ │ ├── code-editor │ │ │ │ ├── answer-submitted.tsx │ │ │ │ ├── description-tab.tsx │ │ │ │ ├── editor.tsx │ │ │ │ ├── result-card.tsx │ │ │ │ └── test-case-section.tsx │ │ │ ├── faster-than-ai │ │ │ │ ├── countdown.stories.tsx │ │ │ │ ├── countdown.tsx │ │ │ │ ├── faster-than-ai-wrapper.stories.tsx │ │ │ │ ├── faster-than-ai-wrapper.tsx │ │ │ │ ├── feedback.stories.tsx │ │ │ │ └── feedback.tsx │ │ │ ├── multiple-choice │ │ │ │ ├── card.stories.tsx │ │ │ │ ├── card.tsx │ │ │ │ ├── feedback-banner.stories.tsx │ │ │ │ ├── feedback-banner.tsx │ │ │ │ ├── footer.tsx │ │ │ │ ├── hint-drawer.tsx │ │ │ │ ├── layout.client.tsx │ │ │ │ ├── layout.stories.tsx │ │ │ │ └── layout.tsx │ │ │ ├── premium-question-denied-access.tsx │ │ │ ├── question-hint-trigger.tsx │ │ │ ├── resources │ │ │ │ ├── question-resource-tab.tsx │ │ │ │ ├── question-stats-tab.tsx │ │ │ │ └── question-tabs.tsx │ │ │ ├── single │ │ │ │ ├── ai-question-help.tsx │ │ │ │ ├── answer-question-form.tsx │ │ │ │ ├── bookmark.tsx │ │ │ │ ├── change-code-theme.tsx │ │ │ │ ├── expanded-code-modal.tsx │ │ │ │ ├── has-answered.tsx │ │ │ │ ├── question-accordion.tsx │ │ │ │ └── stopwatch.tsx │ │ │ ├── suggested-questions-table.tsx │ │ │ └── tag-display.tsx │ │ ├── roadmaps │ │ │ ├── [uid] │ │ │ │ ├── dropdown.tsx │ │ │ │ ├── edit-roadmap-modal.tsx │ │ │ │ ├── generate-more-questions.tsx │ │ │ │ ├── roadmap-card-menu.tsx │ │ │ │ ├── roadmap-stats-chart.tsx │ │ │ │ ├── roadmap-stats.tsx │ │ │ │ ├── roadmaps-card-loading.tsx │ │ │ │ └── roadmaps-card.tsx │ │ │ ├── animated-status-text.tsx │ │ │ ├── create-roadmap-button.tsx │ │ │ ├── creating-roadmap-modal.tsx │ │ │ ├── empty │ │ │ │ ├── onboarding-modal.tsx │ │ │ │ └── onboarding.tsx │ │ │ ├── lesson-transition-wrapper.tsx │ │ │ └── questions │ │ │ │ ├── [uid] │ │ │ │ ├── question-card-menu.tsx │ │ │ │ └── question-card.tsx │ │ │ │ ├── question-card.tsx │ │ │ │ └── roadmap-answer-form.tsx │ │ ├── settings │ │ │ ├── billing-history-table.tsx │ │ │ ├── cancel-subscription-button.tsx │ │ │ ├── cancel-subscription-modal.tsx │ │ │ ├── code-preview.tsx │ │ │ └── delete-account-modal.tsx │ │ ├── shared │ │ │ ├── answer-submitted.tsx │ │ │ ├── feedback │ │ │ │ ├── feedback-button.tsx │ │ │ │ └── feedback-modal.tsx │ │ │ ├── pagination.tsx │ │ │ ├── question │ │ │ │ ├── daily-goals-card.tsx │ │ │ │ ├── premium-content-wrapper.tsx │ │ │ │ ├── question-code-display.tsx │ │ │ │ ├── question-tabs.tsx │ │ │ │ ├── question-timer.tsx │ │ │ │ ├── share-question.tsx │ │ │ │ ├── tour-card.tsx │ │ │ │ └── tour-start-modal.tsx │ │ │ ├── router-back.tsx │ │ │ └── upgrade │ │ │ │ ├── upgrade-card.tsx │ │ │ │ ├── upgrade-layout.tsx │ │ │ │ └── upgrade-modal.tsx │ │ ├── statistics │ │ │ ├── generate-report-button.tsx │ │ │ ├── question-tracker.tsx │ │ │ ├── range-picker.tsx │ │ │ ├── statistics-overview-menu.tsx │ │ │ ├── statistics-report-content.tsx │ │ │ ├── statistics-report-tabs.tsx │ │ │ ├── statistics-report.tsx │ │ │ ├── stats-report-card-loading.tsx │ │ │ ├── stats-report-card-menu.tsx │ │ │ ├── stats-report-card.tsx │ │ │ ├── stats-report-cards-wrapper.tsx │ │ │ ├── suggested-questions.tsx │ │ │ └── total-question-chart.tsx │ │ ├── streaks │ │ │ └── streak-calendar.tsx │ │ └── study-paths │ │ │ ├── hero-chip.tsx │ │ │ ├── hero-heading.tsx │ │ │ ├── list.tsx │ │ │ ├── scroll-to-start-button.tsx │ │ │ ├── single-page-progress.tsx │ │ │ ├── study-path-card-popover.tsx │ │ │ ├── study-path-card.tsx │ │ │ ├── study-path-goal-modal.tsx │ │ │ ├── study-path-question-card-client.tsx │ │ │ ├── study-path-question-card.stories.tsx │ │ │ ├── study-path-question-card.tsx │ │ │ ├── study-path-sidebar.tsx │ │ │ └── subsection-card-client.tsx │ ├── auth │ │ ├── login.tsx │ │ ├── logout.tsx │ │ ├── or-separator.tsx │ │ └── signup.tsx │ ├── emails │ │ ├── 7-days-no-challenges.tsx │ │ ├── daily-challenge.tsx │ │ ├── referral.tsx │ │ ├── roadmap-complete.tsx │ │ ├── study-reminder.tsx │ │ └── welcome.tsx │ ├── marketing │ │ ├── blog │ │ │ ├── featured-post.tsx │ │ │ └── tag-filter.tsx │ │ ├── features │ │ │ ├── daily-challenge │ │ │ │ ├── feature-left-right │ │ │ │ │ ├── features-section.tsx │ │ │ │ │ ├── section-one.tsx │ │ │ │ │ └── section-three.tsx │ │ │ │ └── hero │ │ │ │ │ └── daily-challenge-hero.tsx │ │ │ ├── global │ │ │ │ └── content-header.tsx │ │ │ ├── leaderboard │ │ │ │ ├── leaderboard-features-left.tsx │ │ │ │ ├── leaderboard-features-right.tsx │ │ │ │ ├── leaderboard-features.tsx │ │ │ │ ├── leaderboard-hero.tsx │ │ │ │ └── leaderboard-podium-showcase.tsx │ │ │ ├── roadmap │ │ │ │ ├── grid │ │ │ │ │ ├── roadmap-feature-box-animation.tsx │ │ │ │ │ ├── roadmap-grid-item-one-animation.tsx │ │ │ │ │ ├── roadmap-grid-item-one.tsx │ │ │ │ │ └── roadmap-grid-item-two.tsx │ │ │ │ ├── roadmap-customisation.tsx │ │ │ │ ├── roadmap-hero.tsx │ │ │ │ └── roadmap-three-grid.tsx │ │ │ └── statistics │ │ │ │ ├── skewed-question-cards.tsx │ │ │ │ ├── stats-hero.tsx │ │ │ │ └── stats-report-section.tsx │ │ ├── global │ │ │ ├── blocks │ │ │ │ ├── call-to-action-block.tsx │ │ │ │ ├── comparison-table.tsx │ │ │ │ ├── content-grid.tsx │ │ │ │ ├── faqs.tsx │ │ │ │ ├── feature-icon-grid.stories.tsx │ │ │ │ ├── feature-icon-grid.tsx │ │ │ │ ├── features.tsx │ │ │ │ ├── goals.tsx │ │ │ │ ├── homepage-user-stats.tsx │ │ │ │ ├── large-text.tsx │ │ │ │ ├── left-right-block.tsx │ │ │ │ ├── question-marquee.tsx │ │ │ │ ├── roadmap-showcase.tsx │ │ │ │ ├── testimonials.tsx │ │ │ │ ├── three-block-showcase.tsx │ │ │ │ └── user-stats-floating-chips.tsx │ │ │ ├── code-snippet.stories.tsx │ │ │ ├── code-snippet.tsx │ │ │ ├── footer │ │ │ │ ├── footer.tsx │ │ │ │ └── socials.tsx │ │ │ ├── navigation │ │ │ │ ├── github-stars.tsx │ │ │ │ ├── mobile-menu.tsx │ │ │ │ ├── navigation-buttons.tsx │ │ │ │ ├── navigation-items.tsx │ │ │ │ └── navigation.tsx │ │ │ ├── open-source │ │ │ │ ├── commit-card.tsx │ │ │ │ ├── open-source-block.tsx │ │ │ │ └── open-source-card.tsx │ │ │ ├── social-proof.tsx │ │ │ └── waitlist-form.tsx │ │ ├── homepage │ │ │ ├── comparison │ │ │ │ ├── code-comparison.tsx │ │ │ │ └── comparison-block.tsx │ │ │ ├── features │ │ │ │ ├── daily-question-box.tsx │ │ │ │ ├── features-bento-grid.tsx │ │ │ │ ├── leaderboard-bento-box.tsx │ │ │ │ ├── progression-box.tsx │ │ │ │ ├── progression-chart.tsx │ │ │ │ └── roadmap-feature-box.tsx │ │ │ ├── hero │ │ │ │ ├── google-sign-up.tsx │ │ │ │ ├── hero-image-follow.tsx │ │ │ │ ├── hero-images.tsx │ │ │ │ ├── hero.tsx │ │ │ │ └── text-rotate.tsx │ │ │ ├── personalized │ │ │ │ ├── ai-help-demo.tsx │ │ │ │ ├── block.tsx │ │ │ │ ├── left.tsx │ │ │ │ └── right.tsx │ │ │ └── testimonials.tsx │ │ ├── misc │ │ │ └── changelog-timeline.tsx │ │ ├── pricing │ │ │ ├── compact-pricing-card.tsx │ │ │ ├── pricing-card-block.tsx │ │ │ ├── pricing-card.tsx │ │ │ ├── pricing-table.tsx │ │ │ └── student-discount.tsx │ │ ├── pseo │ │ │ └── hero.tsx │ │ └── resources │ │ │ ├── blog │ │ │ └── blog-card.tsx │ │ │ └── open-source │ │ │ └── open-source-hero.tsx │ ├── mdx │ │ ├── blog-post-layout.tsx │ │ ├── callout.tsx │ │ ├── mdx-heading.tsx │ │ ├── mdx-link.tsx │ │ ├── mdx-list.tsx │ │ ├── mdx-question-display.tsx │ │ ├── mdx-table-of-contents.tsx │ │ └── share-this-post.tsx │ ├── profile │ │ └── hero │ │ │ ├── hero.tsx │ │ │ └── profile-image.tsx │ ├── shared │ │ ├── 404.tsx │ │ ├── Card.tsx │ │ ├── cookie-banner.tsx │ │ ├── hero.tsx │ │ ├── no-daily-question.tsx │ │ ├── payment │ │ │ ├── animated-pricing-features.tsx │ │ │ ├── frequency-toggle.tsx │ │ │ └── upgrade-page.tsx │ │ ├── random-question.tsx │ │ ├── react-query-client-provider.tsx │ │ ├── referral-modal.tsx │ │ ├── referral-toast.tsx │ │ └── testimonial-modal.tsx │ └── ui │ │ ├── LogoSmall.tsx │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── animated-span.stories.tsx │ │ ├── animated-span.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── bento-grid.tsx │ │ ├── button.stories.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── chart.tsx │ │ ├── checkbox.tsx │ │ ├── chip.tsx │ │ ├── code-block.stories.tsx │ │ ├── code-block.tsx │ │ ├── code-comparison.tsx │ │ ├── collapsible.tsx │ │ ├── copy-code.tsx │ │ ├── current-streak.tsx │ │ ├── date-picker.stories.tsx │ │ ├── date-picker.tsx │ │ ├── dialog.tsx │ │ ├── difficulty-stars.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── grid-pattern.tsx │ │ ├── grid.tsx │ │ ├── hover-card.tsx │ │ ├── icons │ │ ├── Readme.txt │ │ ├── array.tsx │ │ ├── award.tsx │ │ ├── b-chart-3.stories.tsx │ │ ├── b-chart-3.tsx │ │ ├── b-chart.tsx │ │ ├── blog-3.stories.tsx │ │ ├── blog-3.tsx │ │ ├── bolt-lightning.tsx │ │ ├── book-bookmark.tsx │ │ ├── calendar.tsx │ │ ├── challenge-flag.tsx │ │ ├── challenge.tsx │ │ ├── chat-bot.tsx │ │ ├── check.tsx │ │ ├── cogwheel.stories.tsx │ │ ├── cogwheel.tsx │ │ ├── contents.tsx │ │ ├── credit-card.tsx │ │ ├── css.tsx │ │ ├── delete-key.tsx │ │ ├── document.tsx │ │ ├── e-remove.tsx │ │ ├── editor.tsx │ │ ├── enter-key.tsx │ │ ├── file-text.stories.tsx │ │ ├── file-text.tsx │ │ ├── filter.tsx │ │ ├── flag.stories.tsx │ │ ├── flag.tsx │ │ ├── git.tsx │ │ ├── github.tsx │ │ ├── globe.tsx │ │ ├── google.tsx │ │ ├── graduation-cap.tsx │ │ ├── home.stories.tsx │ │ ├── home.tsx │ │ ├── html.tsx │ │ ├── i-check-2.tsx │ │ ├── javascript.tsx │ │ ├── lightbulb.stories.tsx │ │ ├── lightbulb.tsx │ │ ├── lock-animated.stories.tsx │ │ ├── lock-animated.tsx │ │ ├── lock.tsx │ │ ├── map.stories.tsx │ │ ├── map.tsx │ │ ├── mirror-tablet-phone-3.tsx │ │ ├── msg-writing.tsx │ │ ├── react.tsx │ │ ├── roadmap.tsx │ │ ├── sort.tsx │ │ ├── spaceship.tsx │ │ ├── star.tsx │ │ ├── stats.tsx │ │ ├── target.tsx │ │ ├── treasure-chest.tsx │ │ ├── typescript.tsx │ │ ├── window-code.tsx │ │ └── xp-star.tsx │ │ ├── infinite-moving-cards.tsx │ │ ├── input-label.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── loading.tsx │ │ ├── logo.tsx │ │ ├── marquee.tsx │ │ ├── navigation-menu.tsx │ │ ├── popover.tsx │ │ ├── profile-picture.tsx │ │ ├── progress.tsx │ │ ├── resizable-layout.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── sonner.tsx │ │ ├── stars-background.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── timelime.tsx │ │ ├── tooltip.tsx │ │ ├── tour.tsx │ │ ├── tracker.tsx │ │ └── user-xp.tsx ├── contexts │ ├── filter-context.tsx │ ├── onboarding-context.tsx │ ├── question-single-context.tsx │ └── roadmap-question-context.tsx ├── hooks │ ├── use-get-query-params.ts │ ├── use-local-storage.ts │ ├── use-mobile.ts │ ├── use-onboarding-steps.ts │ ├── use-study-path.ts │ ├── use-user-server.ts │ └── use-user.ts ├── lib │ ├── blog.ts │ ├── changelog.ts │ ├── onborda.tsx │ ├── open-ai.ts │ ├── prisma.ts │ ├── resend.ts │ ├── stripe.ts │ ├── supabase.ts │ ├── utils.ts │ └── zod │ │ └── schemas │ │ ├── ai │ │ ├── answer-help.ts │ │ ├── index.ts │ │ ├── question-help.ts │ │ ├── report-html.ts │ │ └── response.ts │ │ ├── answer-question-schema.ts │ │ ├── login.ts │ │ ├── new-question-schema.ts │ │ ├── onboarding │ │ └── step-one.ts │ │ ├── signup.ts │ │ ├── update-user.ts │ │ └── user-details-schema.ts ├── mdx-components.tsx ├── mdx.tsx ├── middleware.ts ├── public │ ├── images │ │ ├── dashboard-img-2.png │ │ ├── dashboard-img-3.png │ │ ├── dashboard-img.png │ │ ├── default-profile.png │ │ ├── features-roadmap-img.png │ │ ├── logo.png │ │ ├── mobile-daily-question.png │ │ ├── roadmap-dashboard-img.png │ │ ├── roadmap.png │ │ ├── study-path-overview.png │ │ └── testimonials │ │ │ └── jake-mackie-techblitz-testimonial.jpeg │ └── language-imgs │ │ ├── golang.svg │ │ ├── javascript.svg │ │ ├── python.svg │ │ └── typescript.svg ├── scripts │ ├── add-is-custom-username-to-user.ts │ ├── add-slug-to-question.ts │ ├── add-title-to-question.ts │ ├── add-uid-as-username.ts │ ├── add-user-missions.ts │ ├── add-user-xp.ts │ ├── readme.md │ └── update-code-snippets.ts ├── stories │ ├── Configure.mdx │ └── assets │ │ ├── accessibility.png │ │ ├── accessibility.svg │ │ ├── addon-library.png │ │ ├── assets.png │ │ ├── avif-test-image.avif │ │ ├── context.png │ │ ├── discord.svg │ │ ├── docs.png │ │ ├── figma-plugin.png │ │ ├── github.svg │ │ ├── share.png │ │ ├── styling.png │ │ ├── testing.png │ │ ├── theming.png │ │ ├── tutorials.svg │ │ └── youtube.svg ├── types │ ├── Answers.ts │ ├── BaseRecord.ts │ ├── Constants.ts │ ├── Filters.ts │ ├── LanguageDropdown.ts │ ├── Profile.ts │ ├── QuestionAnswers.ts │ ├── Questions.ts │ ├── Roadmap.ts │ ├── Seo.ts │ ├── Sidebar.ts │ ├── Stats.ts │ ├── StudyPath.ts │ ├── Tags.ts │ ├── User.ts │ ├── Utils.ts │ ├── chart-component.d.ts │ └── index.ts └── utils │ ├── constants │ ├── answer-content.ts │ ├── changelog.tsx │ ├── coding-facts.ts │ ├── emails.ts │ ├── error-codes.ts │ ├── index.ts │ ├── language-options.ts │ ├── misc.ts │ ├── pricing.ts │ ├── prompts.ts │ ├── question-xp.ts │ ├── sidebar.ts │ ├── statistics-filters.ts │ └── study-paths.ts │ ├── cron │ └── index.ts │ ├── data │ ├── answers │ │ └── get-user-answer.ts │ ├── leaderboard │ │ └── get-most-questions-answered.ts │ ├── leagues │ │ └── get.ts │ ├── misc │ │ ├── get-all-pseo-pages.ts │ │ ├── get-github-stars.ts │ │ └── get-pseo-data.ts │ ├── missions │ │ ├── get-daily-missions.ts │ │ └── get-user-mission-record.ts │ ├── questions │ │ ├── get-onboarding.ts │ │ ├── get-question-stats.ts │ │ ├── get-questions-by-tag.ts │ │ ├── get-random.ts │ │ ├── get-related.ts │ │ ├── get-suggestions.ts │ │ ├── get.ts │ │ ├── list.ts │ │ ├── question-navigation.ts │ │ ├── tags │ │ │ ├── get-tags-from-question.ts │ │ │ └── get-tags.ts │ │ └── user-has-answered-any-question.ts │ ├── roadmap │ │ ├── fetch-single-roadmap.ts │ │ ├── fetch-user-roadmaps.ts │ │ └── questions │ │ │ ├── fetch-roadmap-question-via-order.ts │ │ │ ├── fetch-roadmap-question.ts │ │ │ ├── fetch-roadmap-questions.ts │ │ │ └── fetchNextPrevRoadmapQuestion.ts │ ├── statistics │ │ ├── get-stats-chart-data.ts │ │ └── reports │ │ │ ├── get-report.ts │ │ │ └── get-reports.ts │ ├── study-paths │ │ ├── get-goal.ts │ │ └── get.ts │ └── user │ │ ├── authed │ │ ├── get-daily-streak.ts │ │ └── get-user-xp.ts │ │ ├── get-user-count.ts │ │ └── profile │ │ └── get-user-profile.ts │ ├── index.ts │ ├── roadmaps.ts │ ├── search-params.ts │ ├── seo.ts │ ├── stats.ts │ ├── supabase │ ├── client.ts │ ├── middleware.ts │ └── server.ts │ ├── time.ts │ └── user.ts ├── supabase ├── .gitignore ├── config.toml ├── functions │ ├── _shared │ │ ├── cors.ts │ │ └── supabase-admin.ts │ ├── deno.json │ ├── hello-world │ │ └── index.ts │ ├── import_map.json │ ├── send-auth-emails │ │ ├── _templates │ │ │ ├── email-change.tsx │ │ │ ├── magic-link.tsx │ │ │ ├── reset-password.tsx │ │ │ └── sign-up.tsx │ │ └── index.ts │ └── sync-user-streak │ │ ├── index.ts │ │ └── readme.md └── seed.sql ├── tailwind.config.ts ├── tsconfig.json ├── vercel.json └── vitest.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript", "plugin:storybook/recommended"], 3 | "rules": { 4 | "@typescript-eslint/no-explicit-any": "off", 5 | "@typescript-eslint/ban-ts-comment": "off", 6 | "react/no-unescaped-entities": "off", 7 | "@next/next/no-img-element": "off", 8 | "react-hooks/rules-of-hooks": "off" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 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 | -------------------------------------------------------------------------------- /.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 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | #env 39 | .env 40 | 41 | # content collections 42 | .content-collections 43 | 44 | # pnpm 45 | pnpm-lock.yaml 46 | .pnpm-store/ 47 | 48 | # turbo 49 | .turbo 50 | 51 | # Million Lint 52 | .million 53 | 54 | *storybook.log 55 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true 2 | strict-peer-dependencies=false 3 | save-workspace-protocol=rolling 4 | save-prefix='' -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .expo 2 | .next 3 | node_modules 4 | package-lock.json 5 | apps/**/out 6 | 7 | # Ignore all files in the src/components/mdx directory 8 | *.mdx 9 | 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "arrowParens": "always", 10 | "endOfLine": "lf", 11 | "overrides": [ 12 | { 13 | "files": "*.json", 14 | "options": { 15 | "printWidth": 120 16 | } 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/experimental-nextjs-vite'; 2 | 3 | const config: StorybookConfig = { 4 | stories: ['../src/**/*.stories.tsx'], 5 | addons: [ 6 | '@storybook/addon-essentials', 7 | '@storybook/addon-onboarding', 8 | '@chromatic-com/storybook', 9 | '@storybook/experimental-addon-test', 10 | ], 11 | framework: { 12 | name: '@storybook/experimental-nextjs-vite', 13 | options: {}, 14 | }, 15 | }; 16 | export default config; 17 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from '@storybook/react'; 2 | import '../src/app/globals.css'; 3 | 4 | const preview: Preview = { 5 | parameters: { 6 | controls: { 7 | matchers: { 8 | color: /(background|color)$/i, 9 | date: /Date$/i, 10 | }, 11 | }, 12 | }, 13 | }; 14 | 15 | export default preview; 16 | -------------------------------------------------------------------------------- /.storybook/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import { beforeAll } from 'vitest'; 2 | import { setProjectAnnotations } from '@storybook/experimental-nextjs-vite'; 3 | import * as projectAnnotations from './preview'; 4 | 5 | // This is an important step to apply the right configuration when testing your stories. 6 | // More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations 7 | const project = setProjectAnnotations([projectAnnotations]); 8 | 9 | beforeAll(project.beforeAll); -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["denoland.vscode-deno"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "deno.enablePaths": ["supabase/functions"], 6 | "deno.importMap": "../supabase/functions/import_map.json", 7 | "deno.lint": true, 8 | "deno.unstable": true, 9 | "typescript.tsdk": "node_modules/typescript/lib", 10 | "cSpell.words": ["bento", "capitalise"] 11 | } 12 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /content-collections.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, defineConfig } from '@content-collections/core'; 2 | import { compileMDX } from '@content-collections/mdx'; 3 | 4 | const posts = defineCollection({ 5 | name: 'posts', 6 | directory: 'src/app/(marketing)/blog/posts', 7 | include: '**/*.mdx', 8 | schema: (z) => ({ 9 | title: z.string(), 10 | }), 11 | parser: 'frontmatter', 12 | transform: async (document, context) => { 13 | const mdx = await compileMDX(context, document); 14 | return { 15 | ...document, 16 | mdx, 17 | }; 18 | }, 19 | }); 20 | 21 | export default defineConfig({ 22 | collections: [posts], 23 | }); 24 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | 'postcss-preset-mantine': {}, 6 | 'postcss-simple-vars': { 7 | variables: { 8 | 'mantine-breakpoint-xs': '36em', 9 | 'mantine-breakpoint-sm': '48em', 10 | 'mantine-breakpoint-md': '62em', 11 | 'mantine-breakpoint-lg': '75em', 12 | 'mantine-breakpoint-xl': '88em', 13 | }, 14 | }, 15 | }, 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /prisma/migrations/20240917210640_question_answers_migration/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `answer` on the `Questions` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Questions" DROP COLUMN "answer"; 9 | 10 | -- CreateTable 11 | CREATE TABLE "QuestionAnswers" ( 12 | "uid" TEXT NOT NULL, 13 | "questionId" TEXT NOT NULL, 14 | "answer" TEXT NOT NULL, 15 | 16 | CONSTRAINT "QuestionAnswers_pkey" PRIMARY KEY ("uid") 17 | ); 18 | 19 | -- AddForeignKey 20 | ALTER TABLE "QuestionAnswers" ADD CONSTRAINT "QuestionAnswers_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "Questions"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 21 | -------------------------------------------------------------------------------- /prisma/migrations/20240917212435_question_id_question_uid/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `questionId` on the `QuestionAnswers` table. All the data in the column will be lost. 5 | - Added the required column `questionuId` to the `QuestionAnswers` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- DropForeignKey 9 | ALTER TABLE "QuestionAnswers" DROP CONSTRAINT "QuestionAnswers_questionId_fkey"; 10 | 11 | -- AlterTable 12 | ALTER TABLE "QuestionAnswers" DROP COLUMN "questionId", 13 | ADD COLUMN "questionuId" TEXT NOT NULL; 14 | 15 | -- AddForeignKey 16 | ALTER TABLE "QuestionAnswers" ADD CONSTRAINT "QuestionAnswers_questionuId_fkey" FOREIGN KEY ("questionuId") REFERENCES "Questions"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 17 | -------------------------------------------------------------------------------- /prisma/migrations/20240919110901_questions_answer_resource/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "answerResource" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240922135159_questions_table_change_22_09_2024/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ALTER COLUMN "correctAnswer" DROP DEFAULT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240926202353_answers_correct_answer_to_boolean/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The `correctAnswer` column on the `Answers` table would be dropped and recreated. This will lead to data loss if there is data in the column. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Answers" DROP COLUMN "correctAnswer", 9 | ADD COLUMN "correctAnswer" BOOLEAN NOT NULL DEFAULT false; 10 | -------------------------------------------------------------------------------- /prisma/migrations/20240926203943_answers_migration/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "Answers" DROP CONSTRAINT "Answers_userAnswerUid_fkey"; 3 | 4 | -- RenameForeignKey 5 | ALTER TABLE "Answers" RENAME CONSTRAINT "Answers_uid_fkey" TO "questionUid"; 6 | 7 | -- RenameForeignKey 8 | ALTER TABLE "Answers" RENAME CONSTRAINT "userAnswers" TO "Answers_uid_fkey"; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20240927110748_update_answers_model/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `questionId` to the `Answers` table without a default value. This is not possible if the table is not empty. 5 | - Added the required column `userId` to the `Answers` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- DropForeignKey 9 | ALTER TABLE "Answers" DROP CONSTRAINT "Answers_uid_fkey"; 10 | 11 | -- DropForeignKey 12 | ALTER TABLE "Answers" DROP CONSTRAINT "questionUid"; 13 | 14 | -- AlterTable 15 | ALTER TABLE "Answers" ADD COLUMN "questionId" TEXT NOT NULL, 16 | ADD COLUMN "userId" TEXT NOT NULL; 17 | 18 | -- AddForeignKey 19 | ALTER TABLE "Answers" ADD CONSTRAINT "Answers_userId_fkey" FOREIGN KEY ("userId") REFERENCES "Users"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 20 | 21 | -- AddForeignKey 22 | ALTER TABLE "Answers" ADD CONSTRAINT "Answers_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "Questions"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 23 | -------------------------------------------------------------------------------- /prisma/migrations/20240927112116_update_users_to_add_daily_streak/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "correctDailyStreak" INTEGER DEFAULT 0, 3 | ADD COLUMN "totalDailyStreak" INTEGER DEFAULT 0; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20240929184307_user_profile_pic/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "userProfilePicture" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241005121723_user_key_name_migration/migration.sql: -------------------------------------------------------------------------------- 1 | -- Step 1: Add columns with defaults 2 | ALTER TABLE "Answers" ADD COLUMN IF NOT EXISTS "userUid" TEXT DEFAULT 'legacy_user'; 3 | ALTER TABLE "Answers" ADD COLUMN IF NOT EXISTS "questionUid" TEXT DEFAULT 'legacy_question'; 4 | ALTER TABLE "Answers" ADD COLUMN IF NOT EXISTS "updatedAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP; 5 | 6 | -- Step 2: Update existing records with proper references if possible 7 | -- You might want to add custom logic here to map existing answers to proper users/questions 8 | 9 | -- Step 3: Make columns required 10 | ALTER TABLE "Answers" ALTER COLUMN "userUid" SET NOT NULL; 11 | ALTER TABLE "Answers" ALTER COLUMN "questionUid" SET NOT NULL; 12 | ALTER TABLE "Answers" ALTER COLUMN "updatedAt" SET NOT NULL; -------------------------------------------------------------------------------- /prisma/migrations/20241005121850_user_key_name_migration_dev/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `questionId` on the `Answers` table. All the data in the column will be lost. 5 | - You are about to drop the column `userId` on the `Answers` table. All the data in the column will be lost. 6 | 7 | */ 8 | -- DropForeignKey 9 | ALTER TABLE "Answers" DROP CONSTRAINT "Answers_questionId_fkey"; 10 | 11 | -- DropForeignKey 12 | ALTER TABLE "Answers" DROP CONSTRAINT "Answers_userId_fkey"; 13 | 14 | -- AlterTable 15 | ALTER TABLE "Answers" DROP COLUMN "questionId", 16 | DROP COLUMN "userId", 17 | ADD COLUMN "questionDate" TEXT NOT NULL DEFAULT ''; 18 | 19 | -- AddForeignKey 20 | ALTER TABLE "Answers" ADD CONSTRAINT "Answers_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "Users"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 21 | 22 | -- AddForeignKey 23 | ALTER TABLE "Answers" ADD CONSTRAINT "Answers_questionUid_fkey" FOREIGN KEY ("questionUid") REFERENCES "Questions"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 24 | -------------------------------------------------------------------------------- /prisma/migrations/20241008190017_user_show_time_taken_flag_added/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "showTimeTaken" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241022205215_adds_subscription_model/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Subscription" ( 3 | "uid" TEXT NOT NULL, 4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | "updatedAt" TIMESTAMP(3) NOT NULL, 6 | "userUid" TEXT NOT NULL, 7 | "startDate" TIMESTAMP(3) NOT NULL, 8 | "endDate" TIMESTAMP(3) NOT NULL, 9 | "active" BOOLEAN NOT NULL DEFAULT true, 10 | "planId" TEXT NOT NULL, 11 | "planTrial" BOOLEAN NOT NULL DEFAULT false, 12 | "planTrialDays" INTEGER, 13 | 14 | CONSTRAINT "Subscription_pkey" PRIMARY KEY ("uid") 15 | ); 16 | 17 | -- CreateIndex 18 | CREATE UNIQUE INDEX "Subscription_userUid_key" ON "Subscription"("userUid"); 19 | 20 | -- AddForeignKey 21 | ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "Users"("uid") ON DELETE RESTRICT ON UPDATE CASCADE; 22 | -------------------------------------------------------------------------------- /prisma/migrations/20241023202920_add_product_id_to_subscription_schema/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `productId` to the `Subscriptions` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Subscriptions" ADD COLUMN "productId" TEXT NOT NULL; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20241024203214_adds_first_last_name_to_users/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `name` on the `Users` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Users" DROP COLUMN "name", 9 | ADD COLUMN "firstName" TEXT, 10 | ADD COLUMN "lastName" TEXT, 11 | ADD COLUMN "username" TEXT; 12 | -------------------------------------------------------------------------------- /prisma/migrations/20241027221230_questions_question_date_datetime_to_string/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ALTER COLUMN "questionDate" SET DATA TYPE TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241103201712_adds_code_snippet_to_question_model/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "codeSnippet" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241104222534_adds_hint_to_question/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "hint" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241105211551_adds_daily_question_flag_to_questions/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "dailyQuestion" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241106193449_adds_tags_to_question_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "tags" TEXT[]; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241107210935_subscription_optional_fields/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Subscriptions" ALTER COLUMN "startDate" DROP NOT NULL, 3 | ALTER COLUMN "endDate" DROP NOT NULL; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20241108160452_adds_stripe_field_to_subscription/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Subscriptions" ADD COLUMN "stripeCustomerId" TEXT, 3 | ADD COLUMN "stripeSubscriptionId" TEXT; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20241108204713_updates_user_level_enum/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "userLevel" ADD VALUE 'PREMIUM'; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241108211606_adds_stripe_subscription_item_id_to_subscription_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Subscriptions" ADD COLUMN "stripeSubscriptionItemId" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241109205431_adds_difficulty_to_question_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "QuestionDifficulty" AS ENUM ('EASY', 'MEDIUM', 'HARD'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "Questions" ADD COLUMN "QuestionDifficulty" "QuestionDifficulty" NOT NULL DEFAULT 'EASY'; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20241109205633_difficulty_name_change/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `QuestionDifficulty` on the `Questions` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Questions" DROP COLUMN "QuestionDifficulty", 9 | ADD COLUMN "difficulty" "QuestionDifficulty" NOT NULL DEFAULT 'EASY'; 10 | -------------------------------------------------------------------------------- /prisma/migrations/20241113194612_adds_streaks_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Streaks" ( 3 | "uid" TEXT NOT NULL, 4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | "updatedAt" TIMESTAMP(3) NOT NULL, 6 | "userUid" TEXT NOT NULL, 7 | "streakStart" TIMESTAMP(3) NOT NULL, 8 | "streakEnd" TIMESTAMP(3) NOT NULL, 9 | "currentstreakCount" INTEGER NOT NULL DEFAULT 0, 10 | "longestStreak" INTEGER NOT NULL DEFAULT 0, 11 | 12 | CONSTRAINT "Streaks_pkey" PRIMARY KEY ("uid") 13 | ); 14 | 15 | -- CreateIndex 16 | CREATE UNIQUE INDEX "Streaks_userUid_key" ON "Streaks"("userUid"); 17 | 18 | -- AddForeignKey 19 | ALTER TABLE "Streaks" ADD CONSTRAINT "Streaks_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "Users"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 20 | -------------------------------------------------------------------------------- /prisma/migrations/20241116174538_nullable_streak_start_and_streask_end/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Streaks" ALTER COLUMN "streakStart" DROP NOT NULL, 3 | ALTER COLUMN "streakEnd" DROP NOT NULL; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20241119232627_roadmap_schema_changes/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "DefaultRoadmapQuestionsUsersAnswers" ( 3 | "uid" TEXT NOT NULL, 4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | "updatedAt" TIMESTAMP(3) NOT NULL, 6 | "questionUid" TEXT NOT NULL, 7 | "answer" TEXT NOT NULL, 8 | "correct" BOOLEAN NOT NULL DEFAULT false, 9 | "roadmapUid" TEXT NOT NULL, 10 | 11 | CONSTRAINT "DefaultRoadmapQuestionsUsersAnswers_pkey" PRIMARY KEY ("uid") 12 | ); 13 | 14 | -- AddForeignKey 15 | ALTER TABLE "DefaultRoadmapQuestionsUsersAnswers" ADD CONSTRAINT "DefaultRoadmapQuestionsUsersAnswers_questionUid_fkey" FOREIGN KEY ("questionUid") REFERENCES "DefaultRoadmapQuestions"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 16 | 17 | -- AddForeignKey 18 | ALTER TABLE "DefaultRoadmapQuestionsUsersAnswers" ADD CONSTRAINT "DefaultRoadmapQuestionsUsersAnswers_roadmapUid_fkey" FOREIGN KEY ("roadmapUid") REFERENCES "UserRoadmaps"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 19 | -------------------------------------------------------------------------------- /prisma/migrations/20241120200750_roadmap_default_question_correct_answer_flag/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `correctAnswer` to the `DefaultRoadmapQuestions` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "DefaultRoadmapQuestions" ADD COLUMN "correctAnswer" TEXT NOT NULL; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20241120202406_roadmap_default_question_order_key/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `order` to the `DefaultRoadmapQuestions` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "DefaultRoadmapQuestions" ADD COLUMN "order" INTEGER NOT NULL; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20241120232354_roadmap_status_added_creating_key/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "RoadmapStatus" ADD VALUE 'CREATING'; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241121194930_roadmap_status_question_index_added/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | CREATE SEQUENCE defaultroadmapquestions_order_seq; 3 | ALTER TABLE "DefaultRoadmapQuestions" ALTER COLUMN "order" SET DEFAULT nextval('defaultroadmapquestions_order_seq'); 4 | ALTER SEQUENCE defaultroadmapquestions_order_seq OWNED BY "DefaultRoadmapQuestions"."order"; 5 | 6 | -- AlterTable 7 | ALTER TABLE "UserRoadmaps" ADD COLUMN "currentQuestionIndex" INTEGER NOT NULL DEFAULT 1; 8 | -------------------------------------------------------------------------------- /prisma/migrations/20241121202714_roadmap_remove_user_uid_unique_constraint/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "UserRoadmaps_userUid_key"; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241121211759_roadmap_add_has_generated_roadmap_flag/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "UserRoadmaps" ADD COLUMN "hasGeneratedRoadmap" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241122154043_roadmap_default_question_adds_ai_title/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "DefaultRoadmapQuestions" ADD COLUMN "aiTitle" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241122224413_roadmap_user_questions_adds_order_key/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `order` to the `RoadmapUserQuestions` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "RoadmapUserQuestions" ADD COLUMN "order" INTEGER NOT NULL; 9 | 10 | -- AlterTable 11 | ALTER TABLE "RoadmapUserQuestionsAnswers" ADD COLUMN "order" INTEGER NOT NULL DEFAULT 0; 12 | -------------------------------------------------------------------------------- /prisma/migrations/20241122231132_roadmap_user_question_answer_remove_order_key/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `order` on the `RoadmapUserQuestionsAnswers` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "RoadmapUserQuestionsAnswers" DROP COLUMN "order"; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20241123180614_roadmap_questions_user_add_user_correct_flag/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "RoadmapUserQuestions" ADD COLUMN "userCorrect" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241124174107_roadmap_add_title_description_fields/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "UserRoadmaps" ADD COLUMN "description" TEXT DEFAULT 'No description provided', 3 | ADD COLUMN "title" TEXT DEFAULT 'Untitled Roadmap'; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20241125180235_adds_waitlist_model/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Waitlist" ( 3 | "uid" TEXT NOT NULL, 4 | "email" TEXT NOT NULL, 5 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | "updatedAt" TIMESTAMP(3) NOT NULL, 7 | 8 | CONSTRAINT "Waitlist_pkey" PRIMARY KEY ("uid") 9 | ); 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "Waitlist_email_key" ON "Waitlist"("email"); 13 | -------------------------------------------------------------------------------- /prisma/migrations/20241209191737_adds_demo_answer_model/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "DemoAnswers" ( 3 | "uid" TEXT NOT NULL, 4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | "questionUid" TEXT NOT NULL, 7 | "correctAnswer" BOOLEAN NOT NULL DEFAULT false, 8 | "userAnswer" TEXT NOT NULL, 9 | "timeTaken" INTEGER, 10 | 11 | CONSTRAINT "DemoAnswers_pkey" PRIMARY KEY ("uid") 12 | ); 13 | 14 | -- AddForeignKey 15 | ALTER TABLE "DemoAnswers" ADD CONSTRAINT "DemoAnswers_questionUid_fkey" FOREIGN KEY ("questionUid") REFERENCES "Questions"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 16 | -------------------------------------------------------------------------------- /prisma/migrations/20241214213041_adds_code_snippet_flag_to_question_answers/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "QuestionAnswers" ADD COLUMN "isCodeSnippet" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241215123538_adds_push_notifications_to_user_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "sendPushNotifications" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241217193058_adds_ai_prompts_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "AIPrompts" ( 3 | "uid" TEXT NOT NULL, 4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | "updatedAt" TIMESTAMP(3) NOT NULL, 6 | "prompt" TEXT NOT NULL, 7 | "name" TEXT NOT NULL, 8 | 9 | CONSTRAINT "AIPrompts_pkey" PRIMARY KEY ("uid") 10 | ); 11 | -------------------------------------------------------------------------------- /prisma/migrations/20241224183132_adds_code_editor_theme_to_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "codeEditorTheme" TEXT DEFAULT 'vs-dark'; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20241231223050_adds_total_time_taken_to_report_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "StatisticsReport" ADD COLUMN "totalTimeTaken" INTEGER DEFAULT 0; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250103121019_adds_question_resource_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "QuestionResources" ( 3 | "uid" TEXT NOT NULL, 4 | "questionUid" TEXT NOT NULL, 5 | "resource" TEXT NOT NULL, 6 | 7 | CONSTRAINT "QuestionResources_pkey" PRIMARY KEY ("uid") 8 | ); 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "QuestionResources" ADD CONSTRAINT "QuestionResources_questionUid_fkey" FOREIGN KEY ("questionUid") REFERENCES "Questions"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /prisma/migrations/20250103121229_updates_question_resource_schema_adds_title/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `title` to the `QuestionResources` table without a default value. This is not possible if the table is not empty. 5 | - Added the required column `updatedAt` to the `QuestionResources` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "QuestionResources" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | ADD COLUMN "title" TEXT NOT NULL, 11 | ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL; 12 | -------------------------------------------------------------------------------- /prisma/migrations/20250107102316_adds_stripe_emails_key/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "stripeEmails" TEXT[] DEFAULT ARRAY[]::TEXT[]; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250109121338_adds_slug_to_question_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "slug" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250109150134_makes_question_slug_unique/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[slug]` on the table `Questions` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "Questions_slug_key" ON "Questions"("slug"); 9 | -------------------------------------------------------------------------------- /prisma/migrations/20250111161617_adds_ai_question_help_tokens_to_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "aiQuestionHelpTokens" INTEGER NOT NULL DEFAULT 20; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250113120952_adds_beginner_level_to_question_difficulty/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "QuestionDifficulty" ADD VALUE 'BEGINNER'; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250113121315_adds_user_experience_level_to_user_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "UserExperienceLevel" AS ENUM ('BEGINNER', 'INTERMEDIATE', 'ADVANCED', 'MASTER'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "Users" ADD COLUMN "experienceLevel" "UserExperienceLevel" NOT NULL DEFAULT 'BEGINNER'; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20250113123716_adds_slug_generated_flag_to_question_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "slugGenerated" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250113213339_adds_handle_to_profile_schema/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `handle` to the `Profile` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "Profile" ADD COLUMN "handle" TEXT NOT NULL; 9 | -------------------------------------------------------------------------------- /prisma/migrations/20250113233653_adds_is_custom_username_flag_to_user_model/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "isCustomUsername" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250114192103_adds_answer_full_snippet_to_question_answer/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "QuestionAnswerType" AS ENUM ('PREFILL', 'STANDARD'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "QuestionAnswers" ADD COLUMN "answerFullSnippet" TEXT, 6 | ADD COLUMN "answerType" "QuestionAnswerType" NOT NULL DEFAULT 'STANDARD'; 7 | -------------------------------------------------------------------------------- /prisma/migrations/20250114194452_adds_title_and_desc_to_question/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "description" TEXT, 3 | ADD COLUMN "title" TEXT; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20250115220739_adds_referral_and_hear_about_keys/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "howDidYouHearAboutTechBlitz" TEXT, 3 | ADD COLUMN "referralCode" TEXT; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20250116214734_adds_difficulty_to_answer/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "AnswerDifficulty" AS ENUM ('EASY', 'MEDIUM', 'HARD'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "Answers" ADD COLUMN "difficulty" "AnswerDifficulty" NOT NULL DEFAULT 'EASY'; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20250117210627_adds_about_me_ai_help_to_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "aboutMeAiHelp" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250118202007_adds_question_type_enum_to_question_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "QuestionType" AS ENUM ('CODE', 'MULTIPLE_CHOICE'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "Questions" ADD COLUMN "questionType" "QuestionType" NOT NULL DEFAULT 'MULTIPLE_CHOICE'; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20250118212542_adds_coding_challenge_fields/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The values [CODE] on the enum `QuestionType` will be removed. If these variants are still used in the database, this will fail. 5 | 6 | */ 7 | -- AlterEnum 8 | BEGIN; 9 | CREATE TYPE "QuestionType_new" AS ENUM ('CODING_CHALLENGE', 'MULTIPLE_CHOICE'); 10 | ALTER TABLE "Questions" ALTER COLUMN "questionType" DROP DEFAULT; 11 | ALTER TABLE "Questions" ALTER COLUMN "questionType" TYPE "QuestionType_new" USING ("questionType"::text::"QuestionType_new"); 12 | ALTER TYPE "QuestionType" RENAME TO "QuestionType_old"; 13 | ALTER TYPE "QuestionType_new" RENAME TO "QuestionType"; 14 | DROP TYPE "QuestionType_old"; 15 | ALTER TABLE "Questions" ALTER COLUMN "questionType" SET DEFAULT 'MULTIPLE_CHOICE'; 16 | COMMIT; 17 | 18 | -- AlterTable 19 | ALTER TABLE "Questions" ADD COLUMN "expectedParams" JSONB, 20 | ADD COLUMN "functionName" TEXT, 21 | ADD COLUMN "returnType" TEXT, 22 | ADD COLUMN "testCases" JSONB; 23 | -------------------------------------------------------------------------------- /prisma/migrations/20250118215409_makes_user_answer_uid_option_in_answers_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Answers" ALTER COLUMN "userAnswerUid" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250122212032_adds_next_prev_flags_to_question_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "nextQuestionSlug" TEXT, 3 | ADD COLUMN "previousQuestionSlug" TEXT; 4 | -------------------------------------------------------------------------------- /prisma/migrations/20250122221549_adds_bookmark_question_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "_UserBookmarks" ( 3 | "A" TEXT NOT NULL, 4 | "B" TEXT NOT NULL, 5 | 6 | CONSTRAINT "_UserBookmarks_AB_pkey" PRIMARY KEY ("A","B") 7 | ); 8 | 9 | -- CreateIndex 10 | CREATE INDEX "_UserBookmarks_B_index" ON "_UserBookmarks"("B"); 11 | 12 | -- AddForeignKey 13 | ALTER TABLE "_UserBookmarks" ADD CONSTRAINT "_UserBookmarks_A_fkey" FOREIGN KEY ("A") REFERENCES "Questions"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 14 | 15 | -- AddForeignKey 16 | ALTER TABLE "_UserBookmarks" ADD CONSTRAINT "_UserBookmarks_B_fkey" FOREIGN KEY ("B") REFERENCES "Users"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 17 | -------------------------------------------------------------------------------- /prisma/migrations/20250123211622_adds_premium_question_toggle/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "isPremiumQuestion" BOOLEAN NOT NULL DEFAULT false; 3 | 4 | -- AlterTable 5 | ALTER TABLE "Users" ALTER COLUMN "aiQuestionHelpTokens" SET DEFAULT 10; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20250126193422_adds_category_to_study_path/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "StudyPath" ADD COLUMN "category" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250127232301_adds_answer_uid_to_roadmap_user_answer/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "RoadmapUserQuestionsUserAnswers" ADD COLUMN "answerUid" TEXT NOT NULL DEFAULT ''; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250129122720_adds_bookmarks_to_roadmap_questions/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "_RoadmapUserQuestionsToUserBookmarks" ( 3 | "A" TEXT NOT NULL, 4 | "B" TEXT NOT NULL, 5 | 6 | CONSTRAINT "_RoadmapUserQuestionsToUserBookmarks_AB_pkey" PRIMARY KEY ("A","B") 7 | ); 8 | 9 | -- CreateIndex 10 | CREATE INDEX "_RoadmapUserQuestionsToUserBookmarks_B_index" ON "_RoadmapUserQuestionsToUserBookmarks"("B"); 11 | 12 | -- AddForeignKey 13 | ALTER TABLE "_RoadmapUserQuestionsToUserBookmarks" ADD CONSTRAINT "_RoadmapUserQuestionsToUserBookmarks_A_fkey" FOREIGN KEY ("A") REFERENCES "RoadmapUserQuestions"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 14 | 15 | -- AddForeignKey 16 | ALTER TABLE "_RoadmapUserQuestionsToUserBookmarks" ADD CONSTRAINT "_RoadmapUserQuestionsToUserBookmarks_B_fkey" FOREIGN KEY ("B") REFERENCES "UserBookmarks"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 17 | -------------------------------------------------------------------------------- /prisma/migrations/20250129180933_adds_difficulty_to_roadmap_question_answer/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "RoadmapUserQuestionsUserAnswers" ADD COLUMN "difficulty" "AnswerDifficulty" NOT NULL DEFAULT 'EASY'; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250131004931_adds_icon_to_study_path/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "StudyPath" ADD COLUMN "icon" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250131121003_adds_is_published_flag_to_study_paths/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "StudyPath" ADD COLUMN "isPublished" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250205231033_makes_user_mission_user_uid_unique/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[userUid]` on the table `UserMission` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "UserMission_userUid_key" ON "UserMission"("userUid"); 9 | -------------------------------------------------------------------------------- /prisma/migrations/20250206003251_adds_next_study_path_slug_to_study_path_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "StudyPath" ADD COLUMN "nextStudyPathSlug" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250206205217_adds_icon_field_to_missions/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Mission" ADD COLUMN "icon" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250206213644_removes_unique_on_user_mission_user_uid/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropIndex 2 | DROP INDEX "UserMission_userUid_key"; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250209210619_adds_spent_per_day_and_7_day_challenge_email_flag/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "UserTimeSpendingPerDay" AS ENUM ('LESS_THAN_5_MINUTES', 'BETWEEN_5_AND_15_MINUTES', 'BETWEEN_15_AND_30_MINUTES', 'BETWEEN_30_AND_60_MINUTES', 'MORE_THAN_60_MINUTES'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "Users" ADD COLUMN "hasSent7DayNoChallengeEmail" BOOLEAN NOT NULL DEFAULT false, 6 | ADD COLUMN "timeSpendingPerDay" "UserTimeSpendingPerDay" DEFAULT 'LESS_THAN_5_MINUTES'; 7 | -------------------------------------------------------------------------------- /prisma/migrations/20250221195354_adds_promotional_emails_flag_to_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "sendPromotionalEmails" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250222102545_adds_custom_coupon_flag_to_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "userCustomCoupon" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250222103344_adds_has_created_custom_coupon_to_user_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "hasCreatedCustomSignupCoupon" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250222110159_adds_user_custom_coupon_expires_at_flag_to_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "userCustomCouponExpiresAt" TIMESTAMP(3); 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250222180413_adds_roadmap_generation_progress_model/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "RoadmapGenerationStatus" AS ENUM ('FETCHING_DATA', 'FIRST_PASS', 'SECOND_PASS', 'GENERATING_QUESTIONS'); 3 | 4 | -- CreateTable 5 | CREATE TABLE "RoadmapGenerationProgress" ( 6 | "uid" TEXT NOT NULL, 7 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | "updatedAt" TIMESTAMP(3) NOT NULL, 9 | "roadmapUid" TEXT NOT NULL, 10 | "status" "RoadmapGenerationStatus" NOT NULL DEFAULT 'FETCHING_DATA', 11 | 12 | CONSTRAINT "RoadmapGenerationProgress_pkey" PRIMARY KEY ("uid") 13 | ); 14 | 15 | -- AddForeignKey 16 | ALTER TABLE "RoadmapGenerationProgress" ADD CONSTRAINT "RoadmapGenerationProgress_roadmapUid_fkey" FOREIGN KEY ("roadmapUid") REFERENCES "UserRoadmaps"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 17 | -------------------------------------------------------------------------------- /prisma/migrations/20250222180927_adds_adds_generated_to_generation_progress/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "RoadmapGenerationStatus" ADD VALUE 'GENERATED'; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250222181023_adds_adds_error_states_to_gen_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | -- This migration adds more than one value to an enum. 3 | -- With PostgreSQL versions 11 and earlier, this is not possible 4 | -- in a single migration. This can be worked around by creating 5 | -- multiple migrations, each migration adding only one value to 6 | -- the enum. 7 | 8 | 9 | ALTER TYPE "RoadmapGenerationStatus" ADD VALUE 'ERROR'; 10 | ALTER TYPE "RoadmapGenerationStatus" ADD VALUE 'INVALID'; 11 | -------------------------------------------------------------------------------- /prisma/migrations/20250222184131_removes_roadmap_relation_to_generation_doc_as_not_created_at_this_point/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `roadmapUid` on the `RoadmapGenerationProgress` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "RoadmapGenerationProgress" DROP CONSTRAINT "RoadmapGenerationProgress_roadmapUid_fkey"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "RoadmapGenerationProgress" DROP COLUMN "roadmapUid"; 12 | -------------------------------------------------------------------------------- /prisma/migrations/20250225201655_adds_new_user_level/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "userLevel" ADD VALUE 'LIFETIME'; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250303114247_adds_is_published_flag_to_pseo_pages/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "PseoPages" ADD COLUMN "isPublished" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250304195916_modified_study_path_goal_schema_04_03_2025/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "StudyPathGoal" ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250304213753_adds_user_enrol_to_study_path_goal/migration.sql: -------------------------------------------------------------------------------- 1 | -- AddForeignKey 2 | ALTER TABLE "StudyPathGoal" ADD CONSTRAINT "StudyPathGoal_userUid_studyPathUid_fkey" FOREIGN KEY ("userUid", "studyPathUid") REFERENCES "UserStudyPath"("userUid", "studyPathUid") ON DELETE CASCADE ON UPDATE CASCADE; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250304214550_adds_user_enrol_to_study_path_goal_fixes_uid/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "StudyPathGoal" ADD COLUMN "userStudyPathUid" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250304215645_fixes_study_path_goal_user_study_path_relation/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "StudyPathGoal" DROP CONSTRAINT "StudyPathGoal_userUid_studyPathUid_fkey"; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "StudyPathGoal" ADD CONSTRAINT "StudyPathGoal_userStudyPathUid_fkey" FOREIGN KEY ("userStudyPathUid") REFERENCES "UserStudyPath"("uid") ON DELETE CASCADE ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20250307191502_adds_user_xp_to_user_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "userXp" INTEGER NOT NULL DEFAULT 0; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250307194020_adds_weekly_xp/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Users" ADD COLUMN "weeklyUserXp" INTEGER NOT NULL DEFAULT 0; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250321214136_adds_simple_multiple_choice_question_type/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "QuestionType" ADD VALUE 'SIMPLE_MULTIPLE_CHOICE'; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250322150258_adds_after_question_info_field_to_question_type/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "afterQuestionInfo" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250329121307_adds_new_game_mode_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Questions" ADD COLUMN "aiTimeToComplete" INTEGER; 3 | 4 | -- AlterTable 5 | ALTER TABLE "Users" ADD COLUMN "fasterThanAiGameMode" BOOLEAN NOT NULL DEFAULT false; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20250329182957_adds_overview_data_key_to_study_path/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "StudyPath" ADD COLUMN "overviewData" JSONB; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20250406192418_adds_study_path_type_to_study_path/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "StudyPathType" AS ENUM ('LEARN', 'PRACTICE', 'BUILD', 'REVIEW'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "StudyPath" ADD COLUMN "type" "StudyPathType" NOT NULL DEFAULT 'LEARN'; 6 | -------------------------------------------------------------------------------- /prisma/migrations/20250410194148_adds_category_tool_tip_to_study_path/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "StudyPath" ADD COLUMN "categoryToolTip" TEXT; 3 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (e.g., Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /prisma/schema/badge.prisma: -------------------------------------------------------------------------------- 1 | model Badge { 2 | uid String @id @default(uuid()) 3 | createdAt DateTime @default(now()) 4 | updatedAt DateTime @updatedAt 5 | name String 6 | description String 7 | imageUrl String 8 | type BadgeType 9 | requirements Json 10 | achievements Achievement[] 11 | } 12 | 13 | model Achievement { 14 | uid String @id @default(uuid()) 15 | createdAt DateTime @default(now()) 16 | updatedAt DateTime @updatedAt 17 | badgeUid String 18 | userUid String 19 | unlockedAt DateTime 20 | status CompletionStatus @default(INCOMPLETE) 21 | progress Int 22 | badge Badge @relation(fields: [badgeUid], references: [uid], onDelete: Cascade) 23 | user Users @relation(fields: [userUid], references: [uid], onDelete: Cascade) 24 | } 25 | 26 | enum BadgeType { 27 | STREAK 28 | QUESTION_ANSWERED 29 | QUESTION_CORRECT 30 | TIME_TAKEN 31 | LEADERBOARD_POSITION 32 | } 33 | 34 | enum CompletionStatus { 35 | INCOMPLETE 36 | COMPLETED 37 | } 38 | -------------------------------------------------------------------------------- /prisma/schema/statistics.prisma: -------------------------------------------------------------------------------- 1 | model StatisticsReport { 2 | uid String @id @default(uuid()) 3 | createdAt DateTime @default(now()) 4 | updatedAt DateTime @updatedAt 5 | userUid String 6 | correctTags String[] 7 | incorrectTags String[] 8 | htmlReport String? 9 | totalTimeTaken Int? @default(0) 10 | user Users @relation(fields: [userUid], references: [uid], onDelete: Cascade) 11 | questions Questions[] @relation("SharedQuestions") 12 | } 13 | -------------------------------------------------------------------------------- /src/actions/ai/reports/utils/stream-response.ts: -------------------------------------------------------------------------------- 1 | import { createStreamableValue } from 'ai/rsc'; 2 | import { LanguageModelV1, streamObject } from 'ai'; 3 | 4 | export const streamResponse = async (model: LanguageModelV1, messages: any[], schema: any) => { 5 | const stream = createStreamableValue(); 6 | 7 | (async () => { 8 | const { partialObjectStream } = streamObject({ 9 | model, 10 | messages, 11 | schema, 12 | }); 13 | 14 | try { 15 | for await (const partialObject of partialObjectStream) { 16 | stream.update(partialObject); 17 | } 18 | 19 | stream.done(); 20 | } catch (error) { 21 | console.error('Error generating AI response:', error); 22 | stream.update({ error: 'Failed to generate response. Please try again.' }); 23 | stream.done(); 24 | } 25 | })(); 26 | 27 | return { object: stream.value }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/actions/ai/roadmap/utils/add-order-to-response-questions.ts: -------------------------------------------------------------------------------- 1 | import type { Question } from '@/types'; 2 | 3 | /** 4 | * Method to add order to response questions. 5 | * If we are extending a roadmap, we need to start the 6 | * order from the last question in the roadmap. 7 | * 8 | * do not add a order to the response answers 9 | */ 10 | export const addOrderToResponseQuestions = ( 11 | questions: Question[], 12 | existingQuestionsCount: number = 0 13 | ): Question[] => { 14 | console.log('starting order from:', existingQuestionsCount); 15 | 16 | return questions.map((question, index) => ({ 17 | ...question, 18 | // Start ordering from after the last existing question 19 | order: existingQuestionsCount + index + 1, 20 | })); 21 | }; 22 | -------------------------------------------------------------------------------- /src/actions/ai/utils/get-prompt.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import type { PromptName } from '@/utils/constants/prompts'; 3 | import { prisma } from '@/lib/prisma'; 4 | 5 | export const getPrompt = async (opts: { name: PromptName | PromptName[] }) => { 6 | const { name } = opts; 7 | 8 | if (Array.isArray(name)) { 9 | const prompts = await prisma.aIPrompts.findMany({ 10 | where: { 11 | name: { 12 | in: name, 13 | }, 14 | }, 15 | }); 16 | 17 | return prompts.reduce( 18 | (acc: { [key: string]: { content: string } }, prompt: { name: string; prompt: string }) => { 19 | acc[prompt.name] = { content: prompt.prompt }; 20 | return acc; 21 | }, 22 | {} 23 | ); 24 | } 25 | 26 | const prompt = await prisma.aIPrompts.findFirst({ 27 | where: { 28 | name: name, 29 | }, 30 | }); 31 | 32 | if (!prompt) { 33 | return {}; 34 | } 35 | 36 | return { 37 | [prompt.name]: { content: prompt.prompt }, 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /src/actions/misc/send-feedback.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { resend } from '@/lib/resend'; 3 | import { getUser } from '@/actions/user/authed/get-user'; 4 | 5 | interface FeedbackData { 6 | feedback: string; 7 | emoji?: string; 8 | } 9 | 10 | export const sendFeedback = async ({ feedback, emoji }: FeedbackData) => { 11 | const user = await getUser(); 12 | 13 | if (!user) { 14 | throw new Error('User not found'); 15 | } 16 | 17 | const emailText = ` 18 | Feedback from ${user.email}: 19 | 20 | Rating: ${emoji || 'No rating provided'} 21 | Feedback: ${feedback} 22 | `; 23 | 24 | await resend.emails.send({ 25 | from: 'TechBlitz ', 26 | to: 'team@techblitz.dev', 27 | subject: `Feedback from ${user.email} ${emoji || ''}`, 28 | text: emailText, 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /src/actions/misc/send-welcome-email.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { resend } from '@/lib/resend'; 3 | import type { UserRecord } from '@/types'; 4 | import WelcomeEmail from '@/components/emails/welcome'; 5 | 6 | /** 7 | * Sends a welcome email to the user once they have completed the onboarding process 8 | * 9 | * @param user 10 | * @returns 11 | */ 12 | export const sendWelcomeEmail = async (user: Partial | null, coupon: string) => { 13 | if (!user) { 14 | console.error('User is null'); 15 | return; 16 | } 17 | 18 | const { email, username } = user; 19 | 20 | if (!email) { 21 | console.error('User has no email'); 22 | return; 23 | } 24 | 25 | const userName = username || 'there'; 26 | 27 | await resend.emails.send({ 28 | from: 'Logan from TechBlitz ', 29 | to: email, 30 | subject: 'Welcome to TechBlitz!', 31 | react: WelcomeEmail({ 32 | userName, 33 | couponCodeText: coupon, 34 | userEmail: email, 35 | }), 36 | scheduledAt: process.env.NODE_ENV === 'production' ? 'in 10 minutes' : undefined, 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /src/actions/questions/admin/list.ts: -------------------------------------------------------------------------------- 1 | import { getUser } from '@/actions/user/authed/get-user'; 2 | import { prisma } from '@/lib/prisma'; 3 | 4 | type GetQuestionsOpts = { questionSlugs: string[] }; 5 | 6 | export const getQuestions = async (opts: GetQuestionsOpts) => { 7 | const user = await getUser(); 8 | const { questionSlugs } = opts; 9 | 10 | const res = await prisma.questions.findMany({ 11 | where: { 12 | slug: { 13 | in: questionSlugs, 14 | }, 15 | }, 16 | include: { 17 | answers: true, 18 | tags: { 19 | include: { 20 | tag: true, 21 | }, 22 | }, 23 | userAnswers: { 24 | where: { 25 | userUid: user?.uid, 26 | }, 27 | }, 28 | }, 29 | }); 30 | 31 | // current date 32 | return res; 33 | }; 34 | -------------------------------------------------------------------------------- /src/actions/roadmap/delete-roadmap.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { prisma } from '@/lib/prisma'; 3 | import { revalidateTag } from 'next/cache'; 4 | import { getUser } from '@/actions/user/authed/get-user'; 5 | 6 | export const deleteRoadmap = async (roadmapUid: string) => { 7 | // get the current user 8 | const user = await getUser(); 9 | 10 | if (!user) { 11 | throw new Error('User not found'); 12 | } 13 | 14 | const roadmap = await prisma.userRoadmaps.delete({ 15 | where: { 16 | uid: roadmapUid, 17 | AND: { 18 | userUid: user.uid, 19 | }, 20 | }, 21 | }); 22 | 23 | revalidateTag('roadmaps'); 24 | 25 | return roadmap; 26 | }; 27 | -------------------------------------------------------------------------------- /src/actions/roadmap/update-roadmap-details.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { prisma } from '@/lib/prisma'; 3 | import { revalidateTag } from 'next/cache'; 4 | import { getUser } from '@/actions/user/authed/get-user'; 5 | 6 | export const updateRoadmapDetails = async ( 7 | roadmapUid: string, 8 | data: { 9 | title: string; 10 | description: string; 11 | } 12 | ) => { 13 | const user = await getUser(); 14 | if (!user) { 15 | throw new Error('User not found'); 16 | } 17 | 18 | const roadmap = await prisma.userRoadmaps.update({ 19 | where: { 20 | uid: roadmapUid, 21 | AND: { 22 | userUid: user.uid, 23 | }, 24 | }, 25 | data, 26 | }); 27 | 28 | revalidateTag('roadmap-data'); 29 | 30 | return roadmap; 31 | }; 32 | -------------------------------------------------------------------------------- /src/actions/statistics/reports/delete.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import { getUser } from '@/actions/user/authed/get-user'; 4 | import { prisma } from '@/lib/prisma'; 5 | import { revalidateTag } from 'next/cache'; 6 | 7 | export const deleteReport = async (reportUid: string) => { 8 | // ensure that the user is authed, and is the owner of the report 9 | // before they can delete it 10 | const user = await getUser(); 11 | if (!user) throw new Error('User not found'); 12 | 13 | await prisma.statisticsReport.delete({ 14 | where: { 15 | uid: reportUid, 16 | userUid: user.uid, 17 | }, 18 | }); 19 | 20 | revalidateTag('reports'); 21 | 22 | return 'ok'; 23 | }; 24 | -------------------------------------------------------------------------------- /src/actions/stripe/stripe-cancel-subscription.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { prisma } from '@/lib/prisma'; 3 | import { stripe } from '@/lib/stripe'; 4 | 5 | /** 6 | * Cancels a user's subscription at the end of the billing period 7 | * 8 | * @param opts 9 | */ 10 | export const cancelSubscription = async (opts: { userUid: string }) => { 11 | const { userUid } = opts; 12 | 13 | // get the user's subscription 14 | const userSubscription = await prisma.subscriptions.findUnique({ 15 | where: { 16 | userUid, 17 | }, 18 | }); 19 | 20 | if (!userSubscription) { 21 | throw new Error('User does not have an active subscription'); 22 | } 23 | 24 | const { stripeSubscriptionId } = userSubscription; 25 | if (!stripeSubscriptionId) { 26 | throw new Error('User does not have an active subscription'); 27 | } 28 | 29 | // cancel the subscription 30 | await stripe.subscriptions.update(stripeSubscriptionId, { 31 | cancel_at_period_end: true, 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/actions/stripe/stripe-get-subscription-details.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { stripe } from '@/lib/stripe'; 3 | import { prisma } from '@/lib/prisma'; 4 | 5 | export const getSubscriptionDetails = async (userUid: string) => { 6 | const subscriptionId = await prisma.subscriptions.findFirst({ 7 | where: { 8 | userUid, 9 | }, 10 | select: { 11 | stripeSubscriptionId: true, 12 | }, 13 | }); 14 | 15 | if (!subscriptionId?.stripeSubscriptionId) { 16 | return null; 17 | } 18 | 19 | return await stripe.subscriptions.retrieve(subscriptionId.stripeSubscriptionId, { 20 | expand: ['latest_invoice.payment_intent'], 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/actions/stripe/stripe-get-user-invoices.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { stripe } from '@/lib/stripe'; 3 | import { prisma } from '@/lib/prisma'; 4 | 5 | export const getUserInvoices = async (userUid: string) => { 6 | // get the user's subscripton object 7 | const subscription = await prisma.subscriptions.findFirst({ 8 | where: { 9 | userUid, 10 | }, 11 | }); 12 | 13 | if (!subscription) { 14 | return []; 15 | } 16 | 17 | const stripeCustomerId = subscription.stripeCustomerId; 18 | if (!stripeCustomerId) { 19 | return []; 20 | } 21 | 22 | const invoices = await stripe.invoices.list({ 23 | customer: stripeCustomerId, 24 | limit: 10, 25 | }); 26 | 27 | return invoices.data; 28 | }; 29 | -------------------------------------------------------------------------------- /src/actions/user/account/logout.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { createClient } from '@/utils/supabase/server'; 3 | 4 | /** 5 | * Method to log the user out of their account 6 | */ 7 | export const logout = async () => { 8 | const supabase = await createClient(); 9 | const { error } = await supabase.auth.signOut(); 10 | 11 | if (error) { 12 | throw new Error(error.message); 13 | } 14 | return 'ok'; 15 | }; 16 | -------------------------------------------------------------------------------- /src/actions/user/account/oauth.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { createClient } from '@/utils/supabase/server'; 3 | import { Provider } from '@supabase/supabase-js'; 4 | import { redirect } from 'next/navigation'; 5 | 6 | export async function oauth(provider: Provider, onboarding?: boolean) { 7 | const supabase = await createClient(); 8 | 9 | const callbackUrl = new URL('/auth/callback', process.env.NEXT_PUBLIC_URL); 10 | 11 | // if onboarding is true, we need the searchParams.next to be /onboarding 12 | if (onboarding) { 13 | callbackUrl.searchParams.set('next', '/onboarding'); 14 | } 15 | 16 | const { data, error } = await supabase.auth.signInWithOAuth({ 17 | provider, 18 | options: { 19 | redirectTo: callbackUrl.toString(), 20 | }, 21 | }); 22 | 23 | if (error) throw new Error(error.message); 24 | 25 | redirect(data.url); 26 | } 27 | -------------------------------------------------------------------------------- /src/actions/user/account/reset-password.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { supabase } from '@/lib/supabase'; 3 | 4 | export const resetPassword = async (opts: { email: string }) => { 5 | const { email } = opts; 6 | 7 | // we handle the redirect in the edge function 8 | const { data, error } = await supabase.auth.resetPasswordForEmail(email); 9 | 10 | if (error) { 11 | throw error; 12 | } 13 | 14 | return data; 15 | }; 16 | -------------------------------------------------------------------------------- /src/actions/user/authed/check-username.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { prisma } from '@/lib/prisma'; 3 | import { getUser } from './get-user'; 4 | 5 | export async function checkUsername(username: string): Promise { 6 | // get the current user 7 | const currentUser = await getUser(); 8 | 9 | if (!currentUser) return false; 10 | 11 | const user = await prisma.users.findFirst({ 12 | where: { 13 | username, 14 | }, 15 | }); 16 | 17 | // check if the current user is the same as the user we are checking 18 | if (currentUser.uid === user?.uid) return true; 19 | 20 | return !Boolean(user); 21 | } 22 | -------------------------------------------------------------------------------- /src/actions/user/authed/handle-promotional-emails-subscription.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | import { resend } from '@/lib/resend'; 3 | 4 | const AUDIENCE_ID = '83fcadba-be84-4d3c-bb44-d3b705162a71'; 5 | 6 | export const handleAddAndRemoveFromPromotionalEmailsList = async ({ 7 | email, 8 | firstName, 9 | lastName, 10 | status, 11 | }: { 12 | email: string; 13 | firstName: string; 14 | lastName: string; 15 | status: 'add' | 'remove'; 16 | }) => { 17 | if (status === 'add') { 18 | await resend.contacts.create({ 19 | email: email, 20 | firstName: firstName, 21 | lastName: lastName, 22 | unsubscribed: false, 23 | audienceId: AUDIENCE_ID, 24 | }); 25 | } else { 26 | await resend.contacts.remove({ 27 | email: email, 28 | audienceId: AUDIENCE_ID, 29 | }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/app/(app)/(default_layout)/layout.tsx: -------------------------------------------------------------------------------- 1 | import CurrentStreak from '@/components/ui/current-streak'; 2 | import UpgradeModal from '@/components/app/shared/upgrade/upgrade-modal'; 3 | import UserXp from '@/components/ui/user-xp'; 4 | 5 | export default function StatisticsLayout({ children }: Readonly<{ children: React.ReactNode }>) { 6 | return ( 7 |
8 |
9 |
10 | 11 | 12 | 13 |
14 |
15 |
{children}
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/(app)/providers.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useSidebar } from '@/components/ui/sidebar'; 3 | import { cn } from '@/lib/utils'; 4 | 5 | export default function RootProvider({ children }: { children: React.ReactNode }) { 6 | const { state } = useSidebar(); 7 | 8 | return ( 9 |
15 | {children} 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/(app)/settings/layout.tsx: -------------------------------------------------------------------------------- 1 | import SidebarLayoutTrigger from '@/components/app/navigation/sidebar-layout-trigger'; 2 | import FeedbackButton from '@/components/app/shared/feedback/feedback-button'; 3 | import { Separator } from '@/components/ui/separator'; 4 | 5 | export default function ProfileLayout({ children }: Readonly<{ children: React.ReactNode }>) { 6 | return ( 7 |
8 |
9 | 10 | 11 |
12 | 13 |
{children}
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/(app)/testing/code-editor/page.tsx: -------------------------------------------------------------------------------- 1 | import CodeEditor from '@/components/app/questions/code-editor/editor'; 2 | import { useUserServer } from '@/hooks/use-user-server'; 3 | import { redirect } from 'next/navigation'; 4 | 5 | export default async function CodeEditorPage() { 6 | const user = await useUserServer(); 7 | 8 | // sneaky 😉 9 | if (user?.userLevel !== 'ADMIN') { 10 | return redirect('/dashboard'); 11 | } 12 | 13 | return ( 14 |
15 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/(marketing)/blog/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function Layout({ children }: { children: React.ReactNode }) { 2 | return
{children}
; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/(marketing)/blog/page/[page]/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from 'next/navigation'; 2 | import BlogPage from '../../page'; 3 | 4 | interface PaginatedBlogPageProps { 5 | params: { 6 | page: string; 7 | }; 8 | searchParams: { [key: string]: string | string[] | undefined }; 9 | } 10 | 11 | export default function PaginatedBlogPage({ params, searchParams }: PaginatedBlogPageProps) { 12 | const page = parseInt(params.page, 10); 13 | 14 | // Redirect to the main blog page if page is 1 15 | if (page === 1) { 16 | redirect('/blog'); 17 | } 18 | 19 | // Pass the page number to the main blog page component 20 | return ; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/(no_nav)/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Logo from '@/components/ui/logo'; 2 | import StarsBackground from '@/components/ui/stars-background'; 3 | import Link from 'next/link'; 4 | 5 | export default function AuthLayout({ 6 | children, 7 | }: Readonly<{ 8 | children: React.ReactNode; 9 | }>) { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 |
17 | {children} 18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/(no_nav)/(auth)/verify-email/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Button } from '@/components/ui/button'; 3 | 4 | export default function VerifyEmailPage() { 5 | return ( 6 |
12 |

Check your email

13 |

14 | We've sent you a verification link to your email address.
15 | Please click the link to verify your account. 16 |

17 | 18 |
19 | Didn't receive the email? 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/app/(no_nav)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { InterFont, SatoshiFont, UbuntuFont } from '../styles/fonts/font'; 2 | import '../globals.css'; 3 | import { ReactQueryClientProvider } from '@/components/shared/react-query-client-provider'; 4 | import { Toaster } from '@/components/ui/sonner'; 5 | 6 | export default function Layout({ 7 | children, 8 | }: Readonly<{ 9 | children: React.ReactNode; 10 | }>) { 11 | return ( 12 | 13 | 14 | 18 | {/* Scrollable content */} 19 |
{children}
20 | 21 | 22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/app/(no_nav)/upgrade/page.tsx: -------------------------------------------------------------------------------- 1 | import RouterBack from '@/components/app/shared/router-back'; 2 | import UpgradePageComponent from '@/components/shared/payment/upgrade-page'; 3 | import StarsBackground from '@/components/ui/stars-background'; 4 | 5 | export default function UpgradePage() { 6 | return ( 7 |
8 | 9 |
10 | 11 |
12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/app/api/test/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | 3 | export async function GET(req: NextRequest) { 4 | return NextResponse.json({ message: 'Hello, world!', method: req.method }); 5 | } 6 | -------------------------------------------------------------------------------- /src/app/api/user/[uid]/route.ts: -------------------------------------------------------------------------------- 1 | import { getUserFromDb } from '@/actions/user/authed/get-user'; 2 | import { NextRequest, NextResponse } from 'next/server'; 3 | 4 | export async function GET( 5 | req: NextRequest, 6 | { 7 | params, 8 | }: { 9 | params: { 10 | uid: string; 11 | }; 12 | } 13 | ) { 14 | // get the userId from the params 15 | const userId = params.uid; 16 | 17 | if (!userId) { 18 | return NextResponse.json({ 19 | error: 'No user id provided', 20 | }); 21 | } 22 | 23 | // now go get the user from the db 24 | const user = await getUserFromDb(userId); 25 | 26 | if (!user || !user.uid) { 27 | return NextResponse.json({ 28 | error: 'No user found', 29 | }); 30 | } 31 | return NextResponse.json(user); 32 | } 33 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/global-error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Error from 'next/error'; 4 | import NextError from 'next/error'; 5 | 6 | export default function GlobalError({ error }: { error: Error }) { 7 | console.error(error); 8 | 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactQueryClientProvider } from '@/components/shared/react-query-client-provider'; 2 | import { OnestFont } from './styles/fonts/font'; 3 | import { UbuntuFont } from './styles/fonts/font'; 4 | import { SatoshiFont } from './styles/fonts/font'; 5 | import { InterFont } from './styles/fonts/font'; 6 | import './globals.css'; 7 | 8 | export default function RootLayout({ children }: { children: React.ReactNode }) { 9 | return ( 10 | 11 | 12 | 16 | {children} 17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/manifest.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from 'next'; 2 | 3 | export default function manifest(): MetadataRoute.Manifest { 4 | return { 5 | name: 'techblitz app', 6 | short_name: 'techblitz', 7 | description: '', 8 | start_url: '/', 9 | display: 'standalone', 10 | background_color: '#000000', 11 | theme_color: '#000000', 12 | lang: 'en', 13 | icons: [ 14 | { 15 | src: '/favicon.ico', 16 | sizes: '64x64', 17 | type: 'image/png', 18 | }, 19 | ], 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/profile/[uid]/layout.tsx: -------------------------------------------------------------------------------- 1 | import { AppSidebar } from '@/components/app/navigation/sidebar'; 2 | import { SidebarProvider } from '@/components/ui/sidebar'; 3 | import { useUserServer } from '@/hooks/use-user-server'; 4 | import { getSuggestions } from '@/utils/data/questions/get-suggestions'; 5 | import { getOrCreateUserProfile } from '@/utils/data/user/profile/get-user-profile'; 6 | 7 | export default async function ProfileLayout({ children }: { children: React.ReactNode }) { 8 | // check if the user accessing the profile page is logged in, if they are, show the default app sidebar 9 | const [user, profile, suggestion] = await Promise.all([ 10 | useUserServer(), 11 | getOrCreateUserProfile(), 12 | getSuggestions({ limit: 1 }), 13 | ]); 14 | 15 | return ( 16 | 17 | {user && } 18 | {children} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/profile/[uid]/page.tsx: -------------------------------------------------------------------------------- 1 | import ProfileHero from '@/components/profile/hero/hero'; 2 | import { useUserServer } from '@/hooks/use-user-server'; 3 | import { getUserProfileByUsername } from '@/utils/data/user/profile/get-user-profile'; 4 | import { notFound } from 'next/navigation'; 5 | 6 | export default async function ProfilePage({ params }: { params: { uid: string } }) { 7 | const { uid } = params; 8 | 9 | // get the profile of the user - this is different to the user viewing 10 | const { user: userProfileData } = await getUserProfileByUsername(uid); 11 | const user = await useUserServer(); 12 | 13 | if (!userProfileData) { 14 | return notFound(); 15 | } 16 | 17 | return ( 18 | <> 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/app/providers.js: -------------------------------------------------------------------------------- 1 | // app/providers.js 2 | 'use client'; 3 | import { cookieConsentGiven } from '@/components/shared/cookie-banner'; 4 | import posthog from 'posthog-js'; 5 | import { PostHogProvider } from 'posthog-js/react'; 6 | 7 | if (typeof window !== 'undefined') { 8 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { 9 | api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, 10 | person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well, 11 | persistence: 12 | cookieConsentGiven() === 'yes' ? 'localStorage+cookie' : 'memory', 13 | }); 14 | } 15 | export function CSPostHogProvider({ children }) { 16 | return {children}; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/robots.ts: -------------------------------------------------------------------------------- 1 | import type { MetadataRoute } from 'next'; 2 | import { headers } from 'next/headers'; 3 | 4 | export default function robots(): MetadataRoute.Robots { 5 | const headersList = headers(); 6 | const domain = headersList.get('host'); 7 | 8 | return { 9 | rules: [ 10 | { 11 | userAgent: '*', 12 | allow: '/', 13 | }, 14 | { 15 | userAgent: '*', 16 | disallow: '/?redirectUrl=*', 17 | }, 18 | { 19 | userAgent: '*', 20 | disallow: '/*?redirectUrl=*', 21 | }, 22 | { 23 | userAgent: '*', 24 | disallow: '/roadmaps/*/lesson*', 25 | }, 26 | { 27 | userAgent: '*', 28 | disallow: '/testing/*', 29 | }, 30 | ], 31 | sitemap: `https://${domain}/sitemap.xml`, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/app/styles/fonts/font.ts: -------------------------------------------------------------------------------- 1 | import { Inter, Ubuntu, Onest } from 'next/font/google'; 2 | import localFont from 'next/font/local'; 3 | 4 | export const InterFont = Inter({ 5 | subsets: ['latin'], 6 | display: 'swap', 7 | variable: '--font-inter', 8 | style: 'normal', 9 | weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'], 10 | }); 11 | 12 | export const SatoshiFont = localFont({ 13 | src: './satoshi/Satoshi-Variable.ttf', 14 | variable: '--font-satoshi', 15 | style: 'normal', 16 | weight: '100 200 300 400 500 600 700 800 900', 17 | }); 18 | 19 | export const UbuntuFont = Ubuntu({ 20 | subsets: ['latin'], 21 | display: 'swap', 22 | variable: '--font-ubuntu', 23 | style: 'normal', 24 | weight: ['300', '400', '500', '700'], 25 | }); 26 | 27 | export const OnestFont = Onest({ 28 | subsets: ['latin'], 29 | display: 'swap', 30 | variable: '--font-onest', 31 | style: 'normal', 32 | weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'], 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/styles/fonts/onest/Onest-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/onest/Onest-Black.ttf -------------------------------------------------------------------------------- /src/app/styles/fonts/onest/Onest-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/onest/Onest-Bold.ttf -------------------------------------------------------------------------------- /src/app/styles/fonts/onest/Onest-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/onest/Onest-ExtraBold.ttf -------------------------------------------------------------------------------- /src/app/styles/fonts/onest/Onest-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/onest/Onest-ExtraLight.ttf -------------------------------------------------------------------------------- /src/app/styles/fonts/onest/Onest-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/onest/Onest-Light.ttf -------------------------------------------------------------------------------- /src/app/styles/fonts/onest/Onest-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/onest/Onest-Medium.ttf -------------------------------------------------------------------------------- /src/app/styles/fonts/onest/Onest-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/onest/Onest-Regular.ttf -------------------------------------------------------------------------------- /src/app/styles/fonts/onest/Onest-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/onest/Onest-SemiBold.ttf -------------------------------------------------------------------------------- /src/app/styles/fonts/onest/Onest-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/onest/Onest-Thin.ttf -------------------------------------------------------------------------------- /src/app/styles/fonts/satoshi/Satoshi-Variable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/satoshi/Satoshi-Variable.ttf -------------------------------------------------------------------------------- /src/app/styles/fonts/satoshi/Satoshi-VariableItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techblitzdev/TechBlitz/1bb7b97e6e52822ed8eb890f7a2fd6454c8d2552/src/app/styles/fonts/satoshi/Satoshi-VariableItalic.ttf -------------------------------------------------------------------------------- /src/components/app/admin/admin-container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface AdminContainerProps { 4 | children: React.ReactNode; 5 | } 6 | 7 | export default function AdminContainer({ children }: AdminContainerProps) { 8 | return ( 9 |
10 |
{children}
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/app/admin/questions/question-picker.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { ChevronLeft, ChevronRight } from 'lucide-react'; 3 | import { Button } from '@/components/ui/button'; 4 | 5 | export default function AdminQuestionPicker() { 6 | return ( 7 |
8 | 12 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/app/admin/user/user-role-badge.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | interface UserRoleBadgeProps { 4 | role: string; 5 | } 6 | 7 | export default function UserRoleBadge({ role }: UserRoleBadgeProps) { 8 | let badgeClasses = 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium '; 9 | 10 | switch (role) { 11 | case 'ADMIN': 12 | badgeClasses += 'bg-blue-900 text-blue-300'; 13 | break; 14 | case 'PREMIUM': 15 | badgeClasses += 'bg-purple-900 text-purple-300'; 16 | break; 17 | case 'LIFETIME': 18 | badgeClasses += 'bg-amber-900 text-amber-300'; 19 | break; 20 | case 'TRIAL': 21 | badgeClasses += 'bg-teal-900 text-teal-300'; 22 | break; 23 | case 'FREE': 24 | badgeClasses += 'bg-gray-700 text-gray-300'; 25 | break; 26 | default: 27 | badgeClasses += 'bg-gray-700 text-gray-300'; 28 | } 29 | 30 | return {role}; 31 | } 32 | -------------------------------------------------------------------------------- /src/components/app/admin/user/user-status-badge.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | interface UserStatusBadgeProps { 4 | isActive: boolean; 5 | } 6 | 7 | export default function UserStatusBadge({ isActive }: UserStatusBadgeProps) { 8 | return ( 9 | 14 | {isActive ? 'Active' : 'Inactive'} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/app/dashboard/dashboard-header.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | import SidebarLayoutTrigger from '@/components/app/navigation/sidebar-layout-trigger'; 3 | import LoadingSpinner from '@/components/ui/loading'; 4 | import UpgradeModal from '../shared/upgrade/upgrade-modal'; 5 | import UserXp from '@/components/ui/user-xp'; 6 | 7 | const CurrentStreak = dynamic(() => import('@/components/ui/current-streak'), { 8 | loading: () => , 9 | }); 10 | 11 | export default function DashboardHeader() { 12 | return ( 13 |
14 |
15 | 16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/app/dashboard/dashboard-question-card.tsx: -------------------------------------------------------------------------------- 1 | import { Check } from 'lucide-react'; 2 | import { X } from 'lucide-react'; 3 | 4 | type DashboardQuestionCardProps = { 5 | question: { 6 | name: string; 7 | correct: boolean; 8 | label: string; 9 | }; 10 | }; 11 | 12 | export default function DashboardQuestionCard(opts: DashboardQuestionCardProps) { 13 | const { name, correct } = opts.question; 14 | 15 | return ( 16 |
17 |
18 |
19 | {correct ? ( 20 | 21 | ) : ( 22 | 23 | )} 24 |
25 | {name} 26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/app/layout/question-single/question-code-display-wrapper.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import QuestionCodeDisplay from '@/components/app/shared/question/question-code-display'; 4 | import { useRoadmapQuestion } from '@/contexts/roadmap-question-context'; 5 | 6 | export default function QuestionCodeDisplayWrapper() { 7 | const { roadmapQuestion, user, answerHelp } = useRoadmapQuestion(); 8 | 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/app/layout/question-single/roadmap-question-action-buttons.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Button } from '@/components/ui/button'; 3 | import { RefreshCcwIcon } from 'lucide-react'; 4 | import { useRoadmapQuestion } from '../../../../contexts/roadmap-question-context'; 5 | 6 | export default function RoadmapQuestionActionButtons() { 7 | const { handleAnswerRoadmapQuestion, resetQuestionState } = useRoadmapQuestion(); 8 | 9 | return ( 10 |
11 | 17 |
handleAnswerRoadmapQuestion(e)}> 18 | 21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/app/layout/questions/clear-filters.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Button } from '@/components/ui/button'; 3 | import { useRouter } from 'next/navigation'; 4 | 5 | export default function ClearFilters() { 6 | const router = useRouter(); 7 | 8 | return ( 9 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/app/layout/questions/question-card-client.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | import type { Question } from '@/types'; 5 | import { uniqueId } from 'lodash'; 6 | 7 | export default function QuestionCardClient({ 8 | children, 9 | questionData, 10 | offset, 11 | }: { 12 | children: React.ReactNode; 13 | questionData: Question | null; 14 | offset: number; 15 | }) { 16 | const key = questionData?.slug || uniqueId('study-path-question-card-skeleton-'); 17 | 18 | return ( 19 |
26 | {children} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/app/layout/questions/question-page-sidebar-loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | import { QuestionMarkCircledIcon } from '@radix-ui/react-icons'; 3 | 4 | export default function QuestionPageSidebarLoading() { 5 | return ( 6 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/app/navigation/continue-journey-button.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import { ArrowRightIcon } from 'lucide-react'; 3 | import { getSuggestions } from '@/utils/data/questions/get-suggestions'; 4 | 5 | export default async function ContinueJourney(opts: { 6 | text?: string; 7 | variant?: 'accent' | 'secondary' | 'default' | 'ghost'; 8 | }) { 9 | const suggestions = await getSuggestions({ 10 | limit: 1, 11 | }); 12 | 13 | return ( 14 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/app/navigation/sidebar-layout-trigger.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { SidebarTrigger, useSidebar } from '@/components/ui/sidebar'; 4 | import { cn } from '@/lib/utils'; 5 | 6 | /** 7 | * This component is used to trigger the sidebar layout. 8 | * It is used in the dashboard page to hide the sidebar trigger when the sidebar is expanded. 9 | * On mobile devices, the trigger is always shown regardless of sidebar state. 10 | */ 11 | export default function SidebarLayoutTrigger() { 12 | const { state } = useSidebar(); 13 | 14 | return ( 15 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/app/questions/multiple-choice/feedback-banner.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import FeedbackBanner from './feedback-banner'; 4 | 5 | const meta = { 6 | component: FeedbackBanner, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: { 15 | isCorrect: true, 16 | feedbackMessage: 'Great job!', 17 | xpIncrease: 15, 18 | }, 19 | }; 20 | 21 | export const Incorrect: Story = { 22 | args: { 23 | isCorrect: false, 24 | feedbackMessage: 'Try again!', 25 | xpIncrease: 2, 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/app/questions/question-hint-trigger.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import { TooltipContent, TooltipProvider } from '@/components/ui/tooltip'; 3 | import { Tooltip } from '@/components/ui/tooltip'; 4 | import { TooltipTrigger } from '@/components/ui/tooltip'; 5 | import { Lightbulb } from 'lucide-react'; 6 | 7 | export default function QuestionHintTrigger({ 8 | showHint, 9 | setShowHint, 10 | }: { 11 | showHint: boolean; 12 | setShowHint: (showHint: boolean) => void; 13 | }) { 14 | const toggleHint = () => { 15 | setShowHint(!showHint); 16 | }; 17 | 18 | return ( 19 | 20 | 21 | 22 | 25 | 26 | 27 |

View the hint for this question

28 |
29 |
30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/components/app/questions/single/expanded-code-modal.tsx: -------------------------------------------------------------------------------- 1 | import { Expand } from 'lucide-react'; 2 | import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'; 3 | import QuestionDisplay from '@/components/app/layout/question-single/code-snippet'; 4 | 5 | export default function ExpandedCodeModal({ code }: { code: string }) { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/app/questions/single/stopwatch.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { formatSeconds } from '@/utils/time'; 4 | import { Clock } from 'lucide-react'; 5 | 6 | export default function Stopwatch(otps: { totalSeconds: number }) { 7 | const { totalSeconds } = otps; 8 | 9 | return ( 10 |
11 | 12 |

{formatSeconds(totalSeconds)}

13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/app/roadmaps/[uid]/roadmap-stats.tsx: -------------------------------------------------------------------------------- 1 | import type { RoadmapUserQuestions, UserRoadmapsWithAnswers } from '@/types'; 2 | import RoadmapStatsChart from './roadmap-stats-chart'; 3 | 4 | export default function RoadmapStats(opts: { 5 | roadmap: UserRoadmapsWithAnswers & { 6 | questions: RoadmapUserQuestions[]; 7 | }; 8 | }) { 9 | const { roadmap } = opts; 10 | 11 | return ( 12 |
13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/app/roadmaps/empty/onboarding.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import RoadmapOnboardingModal from './onboarding-modal'; 3 | 4 | // this serves as a 'fallback' component if the modal for whatever 5 | // reason does not render 6 | export default function RoadmapOnboarding() { 7 | return ( 8 | <> 9 |
10 |
11 |

Welcome to Roadmaps!

12 |

13 | Roadmaps are personalised learning paths, curated for your needs in order to grow as a 14 | developer. 15 |
16 | Let's get started! 17 |

18 |
19 | 20 |
21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/app/shared/question/premium-content-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react'; 2 | 3 | interface PremiumContentWrapperProps { 4 | children: React.ReactNode; 5 | } 6 | 7 | export default function PremiumContentWrapper({ children }: PremiumContentWrapperProps) { 8 | return ( 9 |
10 | {children} 11 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/app/statistics/stats-report-cards-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import { getUserReports } from '@/utils/data/statistics/reports/get-reports'; 2 | import StatsReportCard from './stats-report-card'; 3 | 4 | export default async function StatsReportCardsWrapper() { 5 | const reports = await getUserReports(); 6 | 7 | return ( 8 | <> 9 | {reports.length > 0 && 10 | reports.map((report) => )} 11 | {reports.length === 0 &&

No reports found.

} 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/app/statistics/suggested-questions.tsx: -------------------------------------------------------------------------------- 1 | import QuestionSuggestedCard from '@/components/app/questions/suggested-questions-table'; 2 | 3 | export default function SuggestedQuestions() { 4 | return ( 5 |
6 |
7 |

Suggested Questions

8 |

9 | Based on your answer history, here are some questions we think will help you improve. 10 |

11 |
12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/app/study-paths/hero-chip.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; 3 | import { ChevronLeft } from 'lucide-react'; 4 | 5 | export default function HeroChip() { 6 | return ( 7 |
8 | 9 | 10 | 11 | 20 | 21 | 22 |

Back to library

23 |
24 |
25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/components/app/study-paths/hero-heading.tsx: -------------------------------------------------------------------------------- 1 | import type { StudyPath } from '@prisma/client'; 2 | 3 | export default function HeroHeading({ studyPath }: { studyPath: StudyPath }) { 4 | return ( 5 |
6 |

7 | {studyPath?.title} 8 |

9 |

{studyPath?.description}

10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/app/study-paths/single-page-progress.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Progress } from '@/components/ui/progress'; 4 | import { useSearchParams } from 'next/navigation'; 5 | 6 | export default function SinglePageProgress({ totalLessons }: { totalLessons: number }) { 7 | const searchParams = useSearchParams(); 8 | const lessonIndex = searchParams.get('lesson') as string; 9 | 10 | // if for some reason the lesson index is not found, do not render the component 11 | if (!lessonIndex) { 12 | return null; 13 | } 14 | 15 | const progressPercentage = (parseInt(lessonIndex) / totalLessons) * 100; 16 | 17 | return ( 18 |
19 | 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/components/auth/or-separator.tsx: -------------------------------------------------------------------------------- 1 | import { Separator } from '@/components/ui/separator'; 2 | 3 | export default function OrSeparator() { 4 | return ( 5 |
6 | 7 | 8 | OR 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/marketing/features/global/content-header.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import { cn } from '@/lib/utils'; 3 | 4 | export default function FeaturesContentHeader(opts: { 5 | title: string; 6 | description?: string | JSX.Element; 7 | id?: string; 8 | cta?: boolean; 9 | }) { 10 | const { title, description, id, cta } = opts; 11 | 12 | return ( 13 |
14 |

15 | {title} 16 |

17 | {description && ( 18 |

{description}

19 | )} 20 | {cta && ( 21 | 24 | )} 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/marketing/global/blocks/features.tsx: -------------------------------------------------------------------------------- 1 | export default function FeaturesBlock() { 2 | return
FeaturesBlock
; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/marketing/global/blocks/goals.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Showcases the 'goals' feature of TechBlitz. 3 | * The ability for a user to enrol in a roadmap, then receive 4 | * daily reminders to complete the next step in their roadmap. 5 | * The date is customizable, and will guide/motivate the user to 6 | * complete the next step in their roadmap. 7 | * 8 | * @returns 9 | */ 10 | export default function GoalsBlock() { 11 | return ( 12 |
13 |

Goals

14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/marketing/global/blocks/left-right-block.tsx: -------------------------------------------------------------------------------- 1 | export default function LeftRightBlock(opts: { left: React.ReactNode; right: React.ReactNode }) { 2 | const { left, right } = opts; 3 | 4 | return ( 5 |
6 |
{left}
7 |
{right}
8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/marketing/global/navigation/github-stars.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import NumberFlow from '@number-flow/react'; 4 | 5 | export default function GithubStars({ value }: { value: number }) { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/marketing/global/waitlist-form.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | 3 | export default function SignupForm() { 4 | return ( 5 |
6 |
7 | 10 |
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/marketing/homepage/features/progression-box.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | 3 | const ProgressChart = dynamic(() => import('./progression-chart'), { 4 | ssr: false, 5 | }); 6 | 7 | export default function ProgressionBentoBox() { 8 | return ( 9 | <> 10 | {/* Top Card */} 11 |
12 | 13 |
14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/marketing/homepage/features/roadmap-feature-box.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from '@/components/ui/card'; 2 | import { cn } from '@/lib/utils'; 3 | import RoadmapFeatureBoxAnimation from '../../features/roadmap/grid/roadmap-feature-box-animation'; 4 | import { Suspense } from 'react'; 5 | 6 | export default function RoadmapFeatureBox(opts: { absolute?: boolean }) { 7 | const { absolute = true } = opts; 8 | 9 | return ( 10 | 19 |
}> 20 | 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/marketing/misc/changelog-timeline.tsx: -------------------------------------------------------------------------------- 1 | import { Timeline } from '@/components/ui/timelime'; 2 | import { changelogEntries } from '@/utils/constants/changelog'; 3 | 4 | export default function ChangelogTimeline() { 5 | const data = changelogEntries.map((entry) => ({ 6 | title: entry.title, 7 | content: entry.content, 8 | date: entry.date, 9 | slug: entry.slug, 10 | })); 11 | 12 | return ( 13 |
14 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/marketing/pricing/pricing-card-block.tsx: -------------------------------------------------------------------------------- 1 | import { getPlans } from '@/utils/constants/pricing'; 2 | import PricingCard from './pricing-card'; 3 | 4 | export default function PricingCardBlock(opts: { frequency: 'month' | 'year' }) { 5 | // grab the stripe products 6 | const products = getPlans(null, false, opts.frequency); 7 | 8 | return ( 9 |
10 | {products.map( 11 | (product) => product && 12 | )} 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/marketing/pricing/student-discount.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/ui/button'; 2 | import { GraduationCap } from 'lucide-react'; 3 | 4 | export default function StudentDiscountBlock() { 5 | return ( 6 |
7 |
8 |
9 | 10 |
11 |

Students Save 30%!

12 |

13 | Unlock your full potential and claim your student discount 14 |

15 |
16 |
17 | 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/mdx/mdx-link.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { AnchorHTMLAttributes } from 'react'; 3 | 4 | interface MdxLinkProps extends AnchorHTMLAttributes { 5 | href: string; 6 | } 7 | 8 | export default function MdxLink({ href, children, ...props }: MdxLinkProps) { 9 | const isInternalLink = href && href.startsWith('/'); 10 | const isAnchorLink = href && href.startsWith('#'); 11 | 12 | if (isInternalLink) { 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | 20 | if (isAnchorLink) { 21 | return ( 22 | 23 | {children} 24 | 25 | ); 26 | } 27 | 28 | return ( 29 | 36 | {children} 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/components/mdx/mdx-list.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils'; 2 | 3 | export default function MdxList({ 4 | children, 5 | ordered = false, 6 | }: { 7 | children: React.ReactNode; 8 | ordered?: boolean; 9 | }) { 10 | const List = ordered ? 'ol' : 'ul'; 11 | 12 | return ( 13 | li]:text-base')}> 14 | {children} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/mdx/mdx-question-display.tsx: -------------------------------------------------------------------------------- 1 | import { getQuestionsByTag } from '@/utils/data/questions/get-questions-by-tag'; 2 | import QuestionCard from '../app/layout/questions/question-card'; 3 | import type { QuestionDifficulty } from '@/types'; 4 | 5 | export default async function MdxQuestionDisplay(opts: { 6 | tag: string; 7 | take: number; 8 | difficulty?: QuestionDifficulty; 9 | }) { 10 | const { tag, take, difficulty } = opts; 11 | 12 | const question = await getQuestionsByTag(tag, difficulty, take); 13 | 14 | const questions = question.flatMap((q) => q.questions.map((question) => question.question)); 15 | 16 | return ( 17 |
18 | {questions.map((question) => ( 19 | 20 | ))} 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/shared/404.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import CountUp from 'react-countup'; 4 | 5 | export default function ErrorPageCountUp() { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/shared/Card.tsx: -------------------------------------------------------------------------------- 1 | import { Separator } from '../ui/separator'; 2 | 3 | export default function Card({ 4 | header, 5 | children, 6 | footer, 7 | }: Readonly<{ 8 | header?: React.ReactNode; 9 | footer?: React.ReactNode; 10 | children: React.ReactNode; 11 | }>) { 12 | return ( 13 |
14 |
15 | {header} 16 |
17 | 18 |
{children}
19 | {footer && ( 20 | <> 21 | 22 |
{footer}
23 | 24 | )} 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/shared/react-query-client-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 3 | import { useState } from 'react'; 4 | 5 | export const ReactQueryClientProvider = ({ children }: { children: React.ReactNode }) => { 6 | const [queryClient] = useState( 7 | () => 8 | new QueryClient({ 9 | defaultOptions: { 10 | queries: { 11 | // With SSR, we usually want to set some default staleTime 12 | // above 0 to avoid refetching immediately on the client 13 | staleTime: 60 * 1000, 14 | }, 15 | }, 16 | }) 17 | ); 18 | return {children}; 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/ui/animated-span.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import AnimatedSpan from './animated-span'; 4 | 5 | const meta = { 6 | component: AnimatedSpan, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: { 15 | content: 'Animated Span', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/ui/animated-span.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | export default function AnimatedSpan(opts: { content: string | ReactNode }) { 4 | const { content } = opts; 5 | 6 | return ( 7 |
8 | 9 | 10 | {content} 11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/ui/chip.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils'; 2 | 3 | export default function Chip(opts: { 4 | text: string; 5 | color: string; 6 | textColor?: string; 7 | small?: boolean; 8 | ghost?: boolean; 9 | bold?: boolean; 10 | border?: string; 11 | className?: string; 12 | }) { 13 | const { 14 | text, 15 | color, 16 | textColor = 'white', 17 | small = false, 18 | ghost = false, 19 | bold = true, 20 | border, 21 | className, 22 | } = opts; 23 | 24 | const baseClasses = small ? 'text-[10px] px-1.5 py-0.5 font-onest' : 'text-xs px-2 py-1 h-fit'; 25 | 26 | // Build classes based on ghost prop 27 | const colorClasses = ghost ? `${color} ${textColor} bg-transparent` : `${color} ${textColor}`; 28 | 29 | const boldClasses = bold ? 'font-medium' : ''; 30 | 31 | const borderClasses = border || color; 32 | 33 | return ( 34 | 40 | {text} 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; 4 | 5 | const Collapsible = CollapsiblePrimitive.Root; 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }; 12 | -------------------------------------------------------------------------------- /src/components/ui/copy-code.tsx: -------------------------------------------------------------------------------- 1 | import { CopyIcon } from 'lucide-react'; 2 | 3 | export default function CopyCode() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/components/ui/date-picker.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { DatePicker } from './date-picker'; 4 | 5 | const meta = { 6 | component: DatePicker, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: { 15 | date: new Date(), 16 | setDate: () => {}, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/ui/difficulty-stars.tsx: -------------------------------------------------------------------------------- 1 | import type { QuestionDifficulty } from '@/types'; 2 | import Star3 from './icons/star'; 3 | 4 | /** 5 | * A component that displays a number of stars based on the difficulty of a question 6 | * - 1 star for beginner 7 | * - 2 stars for easy 8 | * - 3 stars for medium 9 | * - 4 stars for hard 10 | * - 5 stars for expert 11 | * 12 | * @param param 13 | * @returns 14 | */ 15 | export default function DifficultyStars({ difficulty }: { difficulty: QuestionDifficulty }) { 16 | // create a map of difficulty to number of stars 17 | const difficultyToStars = { 18 | BEGINNER: 1, 19 | EASY: 2, 20 | MEDIUM: 3, 21 | HARD: 4, 22 | }; 23 | 24 | return ( 25 |
26 | {Array.from({ length: difficultyToStars[difficulty] }).map((_, index) => ( 27 | 28 | ))} 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/ui/icons/Readme.txt: -------------------------------------------------------------------------------- 1 | Here's the list of props you should pass to your icon instance: 2 | - width: icon width (available if 'Replace height/width values with props' was checked while exporting); 3 | - height: icon height (available if 'Replace height/width values with props' was checked while exporting); 4 | - fill: icon color (available if 'Replace inline colors with props' was checked while exporting); 5 | - secondaryfill: icon secondary color (available if 'Replace inline colors with props' was checked while exporting); 6 | - strokewidth: icon stroke width (available if 'Replace stroke-width values with props' was checked while exporting); 7 | - title: icon title (available if 'Remove element' was not checked while exporting). -------------------------------------------------------------------------------- /src/components/ui/icons/array.tsx: -------------------------------------------------------------------------------- 1 | export default function ArrayIcon() { 2 | return ( 3 | <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"> 4 | <path fill="currentColor" d="M15 20v-2h3V6h-3V4h5v16zM4 20V4h5v2H6v12h3v2z" /> 5 | </svg> 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /src/components/ui/icons/b-chart-3.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { ChartSplineIcon } from './b-chart-3'; 4 | 5 | const meta = { 6 | component: ChartSplineIcon, 7 | } satisfies Meta<typeof ChartSplineIcon>; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj<typeof meta>; 12 | 13 | export const Default: Story = { 14 | args: {}, 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/ui/icons/b-chart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type iconProps = { 4 | fill?: string; 5 | secondaryfill?: string; 6 | strokewidth?: number; 7 | width?: string; 8 | height?: string; 9 | title?: string; 10 | }; 11 | 12 | function BChart(props: iconProps) { 13 | const fill = props.fill || 'currentColor'; 14 | const secondaryfill = props.secondaryfill || fill; 15 | const width = props.width || '1em'; 16 | const height = props.height || '1em'; 17 | const title = props.title || 'b chart'; 18 | 19 | return ( 20 | <svg height={height} width={width} viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"> 21 | <title>{title} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | 32 | export default BChart; 33 | -------------------------------------------------------------------------------- /src/components/ui/icons/blog-3.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import Blog from './blog-3'; 4 | 5 | const meta = { 6 | component: Blog, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: { 15 | width: 24, 16 | height: 24, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/ui/icons/check.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type iconProps = { 4 | fill?: string; 5 | secondaryfill?: string; 6 | strokewidth?: number; 7 | width?: string; 8 | height?: string; 9 | title?: string; 10 | }; 11 | 12 | function Check(props: iconProps) { 13 | const fill = props.fill || 'currentColor'; 14 | const strokewidth = props.strokewidth || 1; 15 | const width = props.width || '1em'; 16 | const height = props.height || '1em'; 17 | const title = props.title || 'check'; 18 | 19 | return ( 20 | 21 | {title} 22 | 23 | 31 | 32 | 33 | ); 34 | } 35 | 36 | export default Check; 37 | -------------------------------------------------------------------------------- /src/components/ui/icons/cogwheel.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { SettingsGearIcon } from './cogwheel'; 4 | 5 | const meta = { 6 | component: SettingsGearIcon, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: {}, 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/ui/icons/contents.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react'; 2 | 3 | export default function TableOfContentsIcon(props: SVGProps) { 4 | return ( 5 | 6 | {/* Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE */} 7 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/ui/icons/delete-key.tsx: -------------------------------------------------------------------------------- 1 | export default function DeleteKey({ 2 | fill = 'currentColor', 3 | width = '1em', 4 | height = '1em', 5 | }: { 6 | fill?: string; 7 | width?: string; 8 | height?: string; 9 | }) { 10 | return ( 11 | 12 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/ui/icons/editor.tsx: -------------------------------------------------------------------------------- 1 | export default function EditorIcon({ ...props }) { 2 | return ( 3 | 4 | 16 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/ui/icons/enter-key.tsx: -------------------------------------------------------------------------------- 1 | export default function EnterKey({ 2 | fill = 'currentColor', 3 | width = '1em', 4 | height = '1em', 5 | }: { 6 | fill?: string; 7 | strokeWidth?: number; 8 | width?: string; 9 | height?: string; 10 | }) { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/ui/icons/file-text.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import FileText from './file-text'; 4 | 5 | const meta = { 6 | component: FileText, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: {}, 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/ui/icons/filter.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react'; 2 | 3 | export default function MaterialSymbolsFilterListRounded(props: SVGProps) { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/ui/icons/flag.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import Flag from './flag'; 4 | 5 | const meta = { 6 | component: Flag, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: {}, 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/ui/icons/globe.tsx: -------------------------------------------------------------------------------- 1 | export default function GlobeIcon() { 2 | return ( 3 | 4 | 5 | 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/ui/icons/google.tsx: -------------------------------------------------------------------------------- 1 | export default function GoogleLogo() { 2 | return ( 3 | 4 | 8 | 12 | 16 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/ui/icons/home.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { HomeIcon } from './home'; 4 | 5 | const meta = { 6 | component: HomeIcon, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: {}, 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/ui/icons/html.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react'; 2 | 3 | export default function HtmlIcon(props: SVGProps) { 4 | return ( 5 | 6 | {/* Icon from Devicon by konpa - https://github.com/devicons/devicon/blob/master/LICENSE */} 7 | 11 | 12 | 16 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/ui/icons/lightbulb.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import Lightbulb from './lightbulb'; 4 | 5 | const meta = { 6 | component: Lightbulb, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: {}, 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/ui/icons/lock-animated.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { UilPadlock } from './lock-animated'; 4 | 5 | const meta = { 6 | component: UilPadlock, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: { 15 | size: 64, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/ui/icons/map.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import { RouteIcon } from './map'; 4 | 5 | const meta = { 6 | component: RouteIcon, 7 | } satisfies Meta; 8 | 9 | export default meta; 10 | 11 | type Story = StoryObj; 12 | 13 | export const Default: Story = { 14 | args: {}, 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/ui/icons/sort.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react'; 2 | 3 | export default function SortIcon(props: SVGProps) { 4 | return ( 5 | 6 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/ui/icons/star.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type iconProps = { 4 | fill?: string; 5 | secondaryfill?: string; 6 | strokewidth?: number; 7 | width?: string; 8 | height?: string; 9 | title?: string; 10 | }; 11 | 12 | function Star3(props: iconProps) { 13 | const fill = props.fill || 'currentColor'; 14 | const width = props.width || '1em'; 15 | const height = props.height || '1em'; 16 | const title = props.title || 'star 3'; 17 | 18 | return ( 19 | 20 | {title} 21 | 22 | 26 | 27 | 28 | ); 29 | } 30 | 31 | export default Star3; 32 | -------------------------------------------------------------------------------- /src/components/ui/icons/typescript.tsx: -------------------------------------------------------------------------------- 1 | export default function TypescriptIcon({ ...props }: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 5 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/ui/icons/xp-star.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from 'react'; 2 | 3 | export default function XpStar(props: SVGProps) { 4 | return ( 5 | 12 | 13 | {/* Star */} 14 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | interface InputProps extends React.InputHTMLAttributes {} 6 | 7 | const Input = React.forwardRef( 8 | ({ className, type, ...props }, ref) => { 9 | return ( 10 | 19 | ); 20 | } 21 | ); 22 | Input.displayName = 'Input'; 23 | 24 | export { Input }; 25 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as LabelPrimitive from '@radix-ui/react-label'; 5 | import { cva, type VariantProps } from 'class-variance-authority'; 6 | 7 | import { cn } from '@/lib/utils'; 8 | 9 | const labelVariants = cva( 10 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' 11 | ); 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & VariantProps 16 | >(({ className, ...props }, ref) => ( 17 | 18 | )); 19 | Label.displayName = LabelPrimitive.Root.displayName; 20 | 21 | export { Label }; 22 | -------------------------------------------------------------------------------- /src/components/ui/profile-picture.tsx: -------------------------------------------------------------------------------- 1 | import { User } from 'lucide-react'; 2 | import { Suspense } from 'react'; 3 | import LoadingSpinner from './loading'; 4 | import { cn } from '@/lib/utils'; 5 | import Image from 'next/image'; 6 | 7 | export default function ProfilePicture(opts: { 8 | src?: string | null; 9 | alt: string | null; 10 | className?: string; 11 | }) { 12 | const { src, alt, className } = opts; 13 | 14 | // if no src, return a placeholder 15 | if (!src) { 16 | return ( 17 |
23 | 24 |
25 | ); 26 | } 27 | 28 | return ( 29 | }> 30 | {alt 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as ProgressPrimitive from '@radix-ui/react-progress'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const Progress = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef & { indicatorColor?: string } 11 | >(({ className, value, indicatorColor, ...props }, ref) => ( 12 | 17 | 21 | 22 | )); 23 | Progress.displayName = ProgressPrimitive.Root.displayName; 24 | 25 | export { Progress }; 26 | -------------------------------------------------------------------------------- /src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as SeparatorPrimitive from '@radix-ui/react-separator'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => ( 12 | 23 | )); 24 | Separator.displayName = SeparatorPrimitive.Root.displayName; 25 | 26 | export { Separator }; 27 | -------------------------------------------------------------------------------- /src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils'; 2 | 3 | function Skeleton({ className, ...props }: React.HTMLAttributes) { 4 | return
; 5 | } 6 | 7 | export { Skeleton }; 8 | -------------------------------------------------------------------------------- /src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useTheme } from 'next-themes'; 4 | import { Toaster as Sonner } from 'sonner'; 5 | 6 | type ToasterProps = React.ComponentProps; 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = 'system' } = useTheme(); 10 | 11 | return ( 12 | 26 | ); 27 | }; 28 | 29 | export { Toaster }; 30 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | interface TextareaProps extends React.TextareaHTMLAttributes {} 6 | 7 | const Textarea = React.forwardRef( 8 | ({ className, ...props }, ref) => { 9 | return ( 10 |