├── .env.example.sh ├── .flake8 ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── deploy_dev.yml │ ├── deploy_dev_image.yml │ ├── deploy_play.yml │ ├── deploy_prod.yml │ ├── deploy_screenshot.yml │ ├── integration_tests.yml │ ├── lint.yml │ └── unit_tests.yml ├── .gitignore ├── .python-version ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── authentication ├── __init__.py ├── admin.py ├── auth.py ├── backends.py ├── serializers.py ├── services.py ├── social_pipeline.py ├── templates │ └── emails │ │ ├── activation_email.html │ │ ├── activation_email.mjml │ │ ├── password_reset.html │ │ ├── password_reset.mjml │ │ ├── signup_invite.html │ │ └── signup_invite.mjml ├── urls.py └── views │ ├── __init__.py │ ├── common.py │ └── social.py ├── comments ├── __init__.py ├── admin.py ├── apps.py ├── constants.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_initial.py │ ├── 0003_initial.py │ ├── 0004_alter_comment_is_private.py │ ├── 0005_comment_content_last_md5_and_more.py │ ├── 0006_populate_original_fields.py │ ├── 0007_comment_text_pt.py │ ├── 0008_keyfactor_keyfactorvote.py │ ├── 0009_alter_comment_is_soft_deleted.py │ ├── 0010_comment_text_edited_at.py │ ├── 0011_comment_is_pinned_and_more.py │ ├── 0012_comment_is_automatically_translated_and_more.py │ ├── 0013_remove_keyfactorvote_votes_unique_user_key_factor_and_more.py │ ├── 0014_comment_text_zh_tw_keyfactor_text_zh_tw.py │ └── __init__.py ├── models.py ├── serializers.py ├── services │ ├── __init__.py │ ├── common.py │ ├── feed.py │ ├── key_factors.py │ ├── notifications.py │ └── spam_detection.py ├── tasks.py ├── translation.py ├── urls.py ├── utils.py └── views.py ├── docker-compose.yaml ├── docs ├── openapi.yml └── redocly.yml ├── dump.rdb ├── fab_management ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── templates │ └── fab_management.html ├── urls.py ├── utils.py └── views.py ├── front_end ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .lintstagedrc.js ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── README.md ├── global.d.ts ├── messages │ ├── cs.json │ ├── en.json │ ├── es.json │ ├── original.json │ ├── pt.json │ ├── zh-TW.json │ └── zh.json ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── fonts │ │ ├── SourceSerifPro-Bold.woff │ │ ├── SourceSerifPro-Bold.woff2 │ │ ├── SourceSerifPro-BoldItalic.woff │ │ ├── SourceSerifPro-BoldItalic.woff2 │ │ ├── SourceSerifPro-Italic.woff │ │ ├── SourceSerifPro-Italic.woff2 │ │ ├── SourceSerifPro-Regular.woff │ │ ├── SourceSerifPro-Regular.woff2 │ │ ├── inter_18pt-medium.ttf │ │ ├── inter_18pt-mediumitalic.ttf │ │ ├── inter_italic_variable.ttf │ │ ├── inter_ofl.txt │ │ ├── inter_variable.ttf │ │ ├── league_gothic_ofl.txt │ │ └── league_gothic_variable.ttf │ └── images │ │ ├── default_preview.png │ │ ├── metaculus_logo.png │ │ └── x_mark.svg ├── scripts │ └── add_missing_translations.mjs ├── sentry.edge.config.ts ├── sentry.server.config.ts ├── src │ ├── app │ │ ├── (api) │ │ │ ├── api-proxy │ │ │ │ └── [...path] │ │ │ │ │ └── route.ts │ │ │ └── app-version │ │ │ │ └── route.ts │ │ ├── (campaigns-registration) │ │ │ ├── bridgewater │ │ │ │ ├── components │ │ │ │ │ ├── header.tsx │ │ │ │ │ ├── hero-section.tsx │ │ │ │ │ ├── pixel-apis.js │ │ │ │ │ ├── pixels-tags.tsx │ │ │ │ │ ├── registration-forms.tsx │ │ │ │ │ ├── registration-status.tsx │ │ │ │ │ ├── results-announcement.tsx │ │ │ │ │ ├── results-dates.tsx │ │ │ │ │ ├── results-hero.tsx │ │ │ │ │ ├── results-leaderboard.tsx │ │ │ │ │ ├── results-prize.tsx │ │ │ │ │ └── typography.tsx │ │ │ │ ├── constants.ts │ │ │ │ ├── contest-rules │ │ │ │ │ └── page.tsx │ │ │ │ ├── how-it-works │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── leaderboards │ │ │ │ │ ├── leaderboard-tabs.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── notice-at-collection │ │ │ │ │ └── page.tsx │ │ │ │ ├── opengraph-image.jpg │ │ │ │ ├── page.tsx │ │ │ │ ├── q1 │ │ │ │ │ ├── components │ │ │ │ │ │ ├── announcement.tsx │ │ │ │ │ │ ├── dates.tsx │ │ │ │ │ │ ├── disclaimer.tsx │ │ │ │ │ │ ├── hero.tsx │ │ │ │ │ │ ├── openLeaderboard.tsx │ │ │ │ │ │ ├── prize.tsx │ │ │ │ │ │ └── undergradLeaderboard.tsx │ │ │ │ │ └── page.tsx │ │ │ │ └── twitter-image.jpg │ │ │ └── id-verification │ │ │ │ ├── actions.ts │ │ │ │ ├── components │ │ │ │ ├── login-view.tsx │ │ │ │ ├── status-view.tsx │ │ │ │ └── verify-view.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── utils.ts │ │ ├── (embed) │ │ │ ├── experiments │ │ │ │ └── embed │ │ │ │ │ └── elections │ │ │ │ │ └── page.tsx │ │ │ └── questions │ │ │ │ ├── constants │ │ │ │ └── embed_theme.ts │ │ │ │ ├── embed │ │ │ │ └── [id] │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── styles.scss │ │ │ │ └── helpers │ │ │ │ └── embed_theme.ts │ │ ├── (main) │ │ │ ├── (home) │ │ │ │ ├── components │ │ │ │ │ ├── email_confirmation.tsx │ │ │ │ │ ├── engage_block.tsx │ │ │ │ │ ├── feedback_float.tsx │ │ │ │ │ ├── focus_area_link.tsx │ │ │ │ │ ├── home_search.tsx │ │ │ │ │ ├── icons │ │ │ │ │ │ ├── focus_area_ai.tsx │ │ │ │ │ │ ├── focus_area_biosecurity.tsx │ │ │ │ │ │ ├── focus_area_climate.tsx │ │ │ │ │ │ └── focus_area_nuclear.tsx │ │ │ │ │ ├── questions_carousel.tsx │ │ │ │ │ ├── research_and_updates.tsx │ │ │ │ │ ├── topic_link.tsx │ │ │ │ │ └── tournaments_block.tsx │ │ │ │ └── page.tsx │ │ │ ├── (leaderboards) │ │ │ │ ├── components │ │ │ │ │ └── medal_icon │ │ │ │ │ │ ├── icons │ │ │ │ │ │ ├── bronze_medal.tsx │ │ │ │ │ │ ├── gold_medal.tsx │ │ │ │ │ │ └── silver_medal.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ ├── contributions │ │ │ │ │ ├── [category] │ │ │ │ │ │ └── [user] │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ ├── contributions_hero.tsx │ │ │ │ │ │ ├── contributions_table.tsx │ │ │ │ │ │ ├── global_contributions.tsx │ │ │ │ │ │ └── project_contributions.tsx │ │ │ │ │ ├── helpers │ │ │ │ │ │ └── filters.ts │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── search_params.ts │ │ │ │ ├── helpers │ │ │ │ │ └── filters.ts │ │ │ │ ├── leaderboard │ │ │ │ │ ├── components │ │ │ │ │ │ ├── categories_tab_bar.tsx │ │ │ │ │ │ ├── global_leaderboard.tsx │ │ │ │ │ │ ├── leaderboard_header.tsx │ │ │ │ │ │ ├── leaderboard_table │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ └── table_row.tsx │ │ │ │ │ │ ├── project_leaderboard.tsx │ │ │ │ │ │ └── project_leaderboard_table │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ ├── table_header.tsx │ │ │ │ │ │ │ └── table_row.tsx │ │ │ │ │ ├── filters.ts │ │ │ │ │ ├── helpers │ │ │ │ │ │ └── filter.ts │ │ │ │ │ ├── mobile_tab_bar_context.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── medals │ │ │ │ │ ├── components │ │ │ │ │ │ ├── medal_card.tsx │ │ │ │ │ │ ├── medal_categories.tsx │ │ │ │ │ │ ├── medals_page.tsx │ │ │ │ │ │ └── medals_widget.tsx │ │ │ │ │ ├── helpers │ │ │ │ │ │ ├── medal_categories.ts │ │ │ │ │ │ └── medal_title.ts │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── search_params.ts │ │ │ │ ├── ranking_categories.tsx │ │ │ │ └── search_params.ts │ │ │ ├── (tournaments) │ │ │ │ ├── tournament │ │ │ │ │ ├── [slug] │ │ │ │ │ │ ├── actions.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── components │ │ │ │ │ │ ├── active_tournament_timeline.tsx │ │ │ │ │ │ ├── closed_tournament_timeline.tsx │ │ │ │ │ │ ├── dropdown_menu.tsx │ │ │ │ │ │ ├── header_block_info.tsx │ │ │ │ │ │ ├── header_block_navigation.tsx │ │ │ │ │ │ ├── index │ │ │ │ │ │ ├── helpers.ts │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── index_community_prediction.tsx │ │ │ │ │ │ ├── index_gauge.tsx │ │ │ │ │ │ ├── index_questions_table.tsx │ │ │ │ │ │ ├── index_timeline.tsx │ │ │ │ │ │ ├── index_weight_chip.tsx │ │ │ │ │ │ └── styles.css │ │ │ │ │ │ ├── members │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── members_invite.tsx │ │ │ │ │ │ └── members_manage.tsx │ │ │ │ │ │ ├── navigation_block.tsx │ │ │ │ │ │ ├── participation_block.tsx │ │ │ │ │ │ ├── tournament_feed.tsx │ │ │ │ │ │ ├── tournament_subscribe_button.tsx │ │ │ │ │ │ └── tournament_timeline.tsx │ │ │ │ └── tournaments │ │ │ │ │ ├── components │ │ │ │ │ ├── tournament_filters.tsx │ │ │ │ │ └── tournaments_list.tsx │ │ │ │ │ ├── constants │ │ │ │ │ └── query_params.ts │ │ │ │ │ └── page.tsx │ │ │ ├── about │ │ │ │ ├── components │ │ │ │ │ ├── AboutHeader.tsx │ │ │ │ │ ├── Button.tsx │ │ │ │ │ ├── IconButton.tsx │ │ │ │ │ ├── MetacLogo.tsx │ │ │ │ │ ├── Modal.tsx │ │ │ │ │ ├── ModalWithArrows.tsx │ │ │ │ │ ├── PersonModal.tsx │ │ │ │ │ └── TeamBlock.tsx │ │ │ │ ├── img │ │ │ │ │ └── person.webp │ │ │ │ ├── page.tsx │ │ │ │ └── page_pt.tsx │ │ │ ├── accounts │ │ │ │ ├── actions.ts │ │ │ │ ├── activate │ │ │ │ │ └── route.ts │ │ │ │ ├── change-email │ │ │ │ │ └── route.ts │ │ │ │ ├── invite │ │ │ │ │ ├── components │ │ │ │ │ │ └── invite_form.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── profile │ │ │ │ │ ├── [id] │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ └── profile_page_tab.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── actions.tsx │ │ │ │ │ └── components │ │ │ │ │ │ ├── change_username.tsx │ │ │ │ │ │ ├── social_media_section.tsx │ │ │ │ │ │ ├── soft_delete_button.tsx │ │ │ │ │ │ ├── track_record.tsx │ │ │ │ │ │ └── user_info.tsx │ │ │ │ ├── reset │ │ │ │ │ ├── actions.ts │ │ │ │ │ ├── components │ │ │ │ │ │ └── password_reset.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── schemas.ts │ │ │ │ ├── settings │ │ │ │ │ ├── actions.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ ├── account_preferences.tsx │ │ │ │ │ │ ├── api_access.tsx │ │ │ │ │ │ ├── change_email.tsx │ │ │ │ │ │ ├── change_password.tsx │ │ │ │ │ │ ├── email_me_my_data.tsx │ │ │ │ │ │ ├── email_notifications.tsx │ │ │ │ │ │ └── question_notifications.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── signup │ │ │ │ │ ├── components │ │ │ │ │ │ └── signup.tsx │ │ │ │ │ └── page.tsx │ │ │ │ └── social │ │ │ │ │ └── [provider] │ │ │ │ │ ├── actions.ts │ │ │ │ │ └── page.tsx │ │ │ ├── actions.ts │ │ │ ├── aggregation-explorer │ │ │ │ ├── components │ │ │ │ │ ├── aggregation_tab.tsx │ │ │ │ │ ├── aggregation_tooltip.tsx │ │ │ │ │ ├── aggregation_wrapper.tsx │ │ │ │ │ ├── aggregations_drawer.tsx │ │ │ │ │ ├── continuous_aggregations_chart.tsx │ │ │ │ │ ├── explorer.tsx │ │ │ │ │ └── histogram_drawer.tsx │ │ │ │ ├── constants.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── page.tsx │ │ │ │ └── types.ts │ │ │ ├── aib │ │ │ │ ├── 2024 │ │ │ │ │ ├── q3 │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── q4 │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── 2025 │ │ │ │ │ └── q1 │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── components │ │ │ │ │ ├── cards-q1.tsx │ │ │ │ │ ├── cards-q3.tsx │ │ │ │ │ ├── cards-q4.tsx │ │ │ │ │ ├── cards.tsx │ │ │ │ │ ├── description.tsx │ │ │ │ │ ├── hero-q1.tsx │ │ │ │ │ ├── hero-q3.tsx │ │ │ │ │ ├── hero-q4.tsx │ │ │ │ │ ├── hero.tsx │ │ │ │ │ ├── leaderboard-q1.tsx │ │ │ │ │ ├── leaderboard-q3.tsx │ │ │ │ │ ├── leaderboard-q4.tsx │ │ │ │ │ ├── page-view-2024-q3.tsx │ │ │ │ │ ├── page-view-2024-q4.tsx │ │ │ │ │ ├── page-view-2025-q1.tsx │ │ │ │ │ └── page-view.tsx │ │ │ │ ├── contest-rules │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ ├── c │ │ │ │ ├── [slug] │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── [[...postSlug]] │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── settings │ │ │ │ │ │ ├── actions.ts │ │ │ │ │ │ ├── components │ │ │ │ │ │ ├── community_filters.tsx │ │ │ │ │ │ ├── community_management.tsx │ │ │ │ │ │ └── settings.tsx │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ └── schemas.ts │ │ │ │ └── components │ │ │ │ │ ├── community_context.tsx │ │ │ │ │ ├── community_follow.tsx │ │ │ │ │ └── community_info.tsx │ │ │ ├── charts │ │ │ │ └── page.tsx │ │ │ ├── components │ │ │ │ ├── bulletin.tsx │ │ │ │ ├── bulletins.tsx │ │ │ │ ├── comments_feed_provider.tsx │ │ │ │ ├── communities_dropdown.tsx │ │ │ │ ├── content_translated_banner │ │ │ │ │ ├── google_translate_attribution.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── cookies_banner │ │ │ │ │ ├── cookies_modal.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── footer.tsx │ │ │ │ ├── global_search.tsx │ │ │ │ ├── global_search_visibility_controller.tsx │ │ │ │ ├── headers │ │ │ │ │ ├── community_header.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ ├── community_mobile_menu.tsx │ │ │ │ │ │ ├── mobile_menu.tsx │ │ │ │ │ │ ├── mobile_menu_link.tsx │ │ │ │ │ │ ├── mobile_menu_title.tsx │ │ │ │ │ │ ├── navbar_links.tsx │ │ │ │ │ │ └── navbar_logo.tsx │ │ │ │ │ ├── global_header.tsx │ │ │ │ │ ├── header.tsx │ │ │ │ │ └── hooks │ │ │ │ │ │ └── useNavbarLinks.tsx │ │ │ │ ├── pagewrapper.tsx │ │ │ │ ├── styled_disclosure.tsx │ │ │ │ └── version_checker.tsx │ │ │ ├── cup │ │ │ │ ├── components │ │ │ │ │ └── CupVideo.tsx │ │ │ │ └── page.tsx │ │ │ ├── error.tsx │ │ │ ├── experiments │ │ │ │ ├── components │ │ │ │ │ ├── experiment_bar_graph.tsx │ │ │ │ │ ├── experiment_candle_bar_graph │ │ │ │ │ │ ├── candle_bar.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── experiment_map │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── raw_map │ │ │ │ │ │ ├── data │ │ │ │ │ │ │ └── us_states.json │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── raw_other_map.tsx │ │ │ │ │ │ └── raw_us_map.tsx │ │ │ │ │ │ └── styles.css │ │ │ │ ├── elections │ │ │ │ │ ├── components │ │ │ │ │ │ ├── card_forecast.tsx │ │ │ │ │ │ ├── elections_embed_modal.tsx │ │ │ │ │ │ ├── electoral_consequences │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ └── question_link.tsx │ │ │ │ │ │ ├── expected_electoral_votes_forecast │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── share_elections_menu.tsx │ │ │ │ │ │ └── state_by_forecast │ │ │ │ │ │ │ ├── elections_bar_graph │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ └── state_bar_hover_popup.tsx │ │ │ │ │ │ │ ├── elections_map │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ └── state_hover_card.tsx │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ ├── middle_votes_arrow.tsx │ │ │ │ │ │ │ ├── state_by_forecast_charts.tsx │ │ │ │ │ │ │ └── us_areas.ts │ │ │ │ │ ├── opengraph-image.alt.txt │ │ │ │ │ ├── opengraph-image.jpg │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── twitter-image.alt.txt │ │ │ │ │ └── twitter-image.jpg │ │ │ │ └── testing │ │ │ │ │ ├── components │ │ │ │ │ └── other_map.tsx │ │ │ │ │ └── page.tsx │ │ │ ├── faq │ │ │ │ ├── page.tsx │ │ │ │ ├── page_es.tsx │ │ │ │ └── page_pt.tsx │ │ │ ├── help │ │ │ │ ├── guidelines │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── page_pt.tsx │ │ │ │ ├── markdown │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── page_pt.tsx │ │ │ │ ├── medals-faq │ │ │ │ │ └── page.tsx │ │ │ │ ├── prediction-resources │ │ │ │ │ ├── data_sources.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── table.tsx │ │ │ │ ├── question-checklist │ │ │ │ │ └── page.tsx │ │ │ │ └── scores-faq │ │ │ │ │ ├── components │ │ │ │ │ ├── baseline_math.tsx │ │ │ │ │ ├── further_math.tsx │ │ │ │ │ ├── peer_math.tsx │ │ │ │ │ ├── points_math.tsx │ │ │ │ │ └── truncation_example.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── page_pt.tsx │ │ │ ├── home │ │ │ │ └── page.tsx │ │ │ ├── how-to-forecast │ │ │ │ └── page.tsx │ │ │ ├── intro │ │ │ ├── layout.tsx │ │ │ ├── legacy-points-rankings │ │ │ │ └── page.tsx │ │ │ ├── news │ │ │ │ ├── components │ │ │ │ │ ├── news_filters.tsx │ │ │ │ │ └── news_subscribe_button.tsx │ │ │ │ ├── helpers │ │ │ │ │ └── filters.ts │ │ │ │ ├── page.tsx │ │ │ │ └── testing │ │ │ │ │ └── page.tsx │ │ │ ├── not-found │ │ │ │ └── page.tsx │ │ │ ├── notebooks │ │ │ │ ├── [id] │ │ │ │ │ └── [[...slug]] │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ └── page_compotent.tsx │ │ │ │ ├── components │ │ │ │ │ ├── notebook_content_sections.tsx │ │ │ │ │ └── notebook_editor │ │ │ │ │ │ ├── editor.css │ │ │ │ │ │ └── index.tsx │ │ │ │ └── constants │ │ │ │ │ └── page_sections.ts │ │ │ ├── press │ │ │ │ ├── components │ │ │ │ │ ├── DisclosureItem.tsx │ │ │ │ │ ├── DisclosureSection.tsx │ │ │ │ │ └── ReferenceSection.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── page_pt.tsx │ │ │ ├── privacy-policy │ │ │ │ ├── eea-and-uk │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ ├── pro-forecasters │ │ │ │ ├── assets │ │ │ │ │ ├── Isabel Juniewicz.png │ │ │ │ │ ├── LinkedIn Pro Forecasters.svg │ │ │ │ │ ├── LinkedInIcon.tsx │ │ │ │ │ ├── Peter Wildeford.png │ │ │ │ │ ├── Philipp Schoenegger.jpeg │ │ │ │ │ └── Scott Eastman.jpeg │ │ │ │ ├── components │ │ │ │ │ ├── hero_section.tsx │ │ │ │ │ ├── info_section.tsx │ │ │ │ │ ├── link.tsx │ │ │ │ │ └── pro_forecasters_section.tsx │ │ │ │ ├── constants │ │ │ │ │ ├── expression_of_interest_form.ts │ │ │ │ │ └── pro_forecasters.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── types │ │ │ │ │ └── index.ts │ │ │ ├── question-writing │ │ │ │ └── page.tsx │ │ │ ├── questions │ │ │ │ ├── [id] │ │ │ │ │ ├── [[...slug]] │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ ├── page_component.tsx │ │ │ │ │ │ └── utils │ │ │ │ │ │ │ └── get_post.ts │ │ │ │ │ ├── components │ │ │ │ │ │ ├── download_question_data_modal │ │ │ │ │ │ │ ├── aggregation_methods_picker.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── histogram_drawer.tsx │ │ │ │ │ │ ├── key_factors │ │ │ │ │ │ │ ├── add_key_factors_modal.tsx │ │ │ │ │ │ │ ├── hooks.ts │ │ │ │ │ │ │ ├── key_factor_item │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ ├── key_factor_text.tsx │ │ │ │ │ │ │ │ ├── likert_item.tsx │ │ │ │ │ │ │ │ ├── two_step_item.tsx │ │ │ │ │ │ │ │ └── updown_item.tsx │ │ │ │ │ │ │ ├── key_factor_voter.tsx │ │ │ │ │ │ │ └── key_factors_section.tsx │ │ │ │ │ │ ├── multiple_choice_group_chart.tsx │ │ │ │ │ │ ├── multiple_choices_chart_view │ │ │ │ │ │ │ ├── choices_legend │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── post_approval_modal.tsx │ │ │ │ │ │ ├── post_destructive_action_modal.tsx │ │ │ │ │ │ ├── post_header.tsx │ │ │ │ │ │ ├── question_embed_button.tsx │ │ │ │ │ │ ├── question_embed_modal.tsx │ │ │ │ │ │ ├── question_header_info.tsx │ │ │ │ │ │ ├── question_resolution_status.tsx │ │ │ │ │ │ ├── reveal_cp_button.tsx │ │ │ │ │ │ └── sidebar │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ ├── news_match │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ ├── news_match_article.tsx │ │ │ │ │ │ │ └── news_match_drawer.tsx │ │ │ │ │ │ │ ├── question_info │ │ │ │ │ │ │ ├── include_bots_info.tsx │ │ │ │ │ │ │ └── question_weight_info.tsx │ │ │ │ │ │ │ ├── sidebar_question_info.tsx │ │ │ │ │ │ │ ├── sidebar_question_tags.tsx │ │ │ │ │ │ │ ├── sidebar_tooltip.tsx │ │ │ │ │ │ │ └── similar_questions │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ ├── similar_question_card.tsx │ │ │ │ │ │ │ ├── similar_question_prediction_chip.tsx │ │ │ │ │ │ │ └── similar_questions_drawer.tsx │ │ │ │ │ ├── image-preview │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── search_params.ts │ │ │ │ ├── actions.ts │ │ │ │ ├── components │ │ │ │ │ ├── back_to_create.tsx │ │ │ │ │ ├── category_picker.tsx │ │ │ │ │ ├── conditional_form.tsx │ │ │ │ │ ├── conditional_question_input.tsx │ │ │ │ │ ├── conditional_question_picker.tsx │ │ │ │ │ ├── feed_filters │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ ├── my_predictions.tsx │ │ │ │ │ │ └── my_questions_and_posts.tsx │ │ │ │ │ ├── forecaster_counter.tsx │ │ │ │ │ ├── group_form.tsx │ │ │ │ │ ├── group_form_bulk_modal.tsx │ │ │ │ │ ├── notebook_form.tsx │ │ │ │ │ ├── numeric_question_input.tsx │ │ │ │ │ ├── project_picker_input.tsx │ │ │ │ │ ├── question_form.tsx │ │ │ │ │ ├── question_repost.tsx │ │ │ │ │ ├── question_type_picker.tsx │ │ │ │ │ ├── repost.tsx │ │ │ │ │ ├── sidebar.tsx │ │ │ │ │ └── topic_item.tsx │ │ │ │ ├── create │ │ │ │ │ ├── [content_type] │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ ├── question_draft_cleanup.tsx │ │ │ │ │ │ └── register_message.tsx │ │ │ │ │ ├── helpers │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── discovery │ │ │ │ │ ├── components │ │ │ │ │ │ ├── categories.tsx │ │ │ │ │ │ ├── section.tsx │ │ │ │ │ │ ├── tag_filters.tsx │ │ │ │ │ │ ├── tags.tsx │ │ │ │ │ │ └── tags_discovery.tsx │ │ │ │ │ ├── constants │ │ │ │ │ │ └── tags_feed.ts │ │ │ │ │ └── page.tsx │ │ │ │ ├── helpers │ │ │ │ │ └── filters.ts │ │ │ │ ├── hooks │ │ │ │ │ └── use_feed.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── track-record │ │ │ │ │ ├── components │ │ │ │ │ ├── async_track_record.tsx │ │ │ │ │ ├── charts │ │ │ │ │ │ ├── calibration_chart.tsx │ │ │ │ │ │ ├── scatter_plot.tsx │ │ │ │ │ │ └── user_histogram.tsx │ │ │ │ │ ├── track_record_chart_hero.tsx │ │ │ │ │ └── track_record_charts.tsx │ │ │ │ │ └── page.tsx │ │ │ └── terms-of-use │ │ │ │ ├── page.tsx │ │ │ │ └── page_pt.tsx │ │ ├── (prediction-flow) │ │ │ ├── components │ │ │ │ ├── exit_flow_modal.tsx │ │ │ │ ├── header.tsx │ │ │ │ ├── post_step_button.tsx │ │ │ │ ├── prediction_flow_comments.tsx │ │ │ │ ├── prediction_flow_menu.tsx │ │ │ │ ├── prediction_flow_post.tsx │ │ │ │ ├── prediction_flow_provider.tsx │ │ │ │ ├── prediction_flow_question_card.tsx │ │ │ │ ├── progress_section.tsx │ │ │ │ └── require_attention_banner.tsx │ │ │ ├── helpers.ts │ │ │ └── tournament │ │ │ │ └── [slug] │ │ │ │ └── prediction-flow │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ ├── alpha-auth │ │ │ ├── actions.ts │ │ │ ├── page.tsx │ │ │ └── schemas.ts │ │ ├── assets │ │ │ └── images │ │ │ │ ├── logo_placeholder.png │ │ │ │ └── tournament.png │ │ ├── error.tsx │ │ ├── favicon.ico │ │ ├── global-error.tsx │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── newsmatch │ │ │ └── favicon │ │ │ │ └── route.ts │ │ └── robots.ts │ ├── components │ │ ├── auth │ │ │ ├── hooks │ │ │ │ └── usePostLoginActionHandler.ts │ │ │ ├── index.tsx │ │ │ ├── password_reset.tsx │ │ │ ├── signin.tsx │ │ │ ├── signup.tsx │ │ │ └── social_buttons.tsx │ │ ├── base_modal.tsx │ │ ├── carousel.tsx │ │ ├── charts │ │ │ ├── continuous_area_chart.tsx │ │ │ ├── cp_reveal_time_overflow.tsx │ │ │ ├── fan_chart.tsx │ │ │ ├── helpers.ts │ │ │ ├── histogram.tsx │ │ │ ├── multiple_choice_chart.tsx │ │ │ ├── new_numeric_chart.tsx │ │ │ ├── numeric_chart.tsx │ │ │ ├── numeric_timeline.tsx │ │ │ └── primitives │ │ │ │ ├── chart_container.tsx │ │ │ │ ├── chart_cursor_label.tsx │ │ │ │ ├── chart_fan_tooltip.tsx │ │ │ │ ├── fan_point.tsx │ │ │ │ ├── group_predictions_tooltip.tsx │ │ │ │ ├── line_cursor_points.tsx │ │ │ │ ├── prediction_with_range.tsx │ │ │ │ └── x_tick_label.tsx │ │ ├── choice_checkbox.tsx │ │ ├── choice_icon.tsx │ │ ├── chunk_retry_script.tsx │ │ ├── comment_feed │ │ │ ├── comment.tsx │ │ │ ├── comment_cmm.tsx │ │ │ ├── comment_date.tsx │ │ │ ├── comment_editor.tsx │ │ │ ├── comment_key_factor.tsx │ │ │ ├── comment_report_modal.tsx │ │ │ ├── comment_voter.tsx │ │ │ ├── comment_welcome_message.tsx │ │ │ ├── comment_wrapper.tsx │ │ │ ├── included_forecast.tsx │ │ │ ├── index.tsx │ │ │ └── validate_comment.tsx │ │ ├── communities_feed │ │ │ ├── community_feed_card.tsx │ │ │ ├── index.tsx │ │ │ ├── paginated_communities_feed.tsx │ │ │ └── styles.css │ │ ├── conditional_tile │ │ │ ├── conditional_card.tsx │ │ │ ├── conditional_chart.tsx │ │ │ ├── icons │ │ │ │ ├── Arrow.tsx │ │ │ │ └── DisabledArrow.tsx │ │ │ └── index.tsx │ │ ├── conditional_timeline.tsx │ │ ├── confirm_modal.tsx │ │ ├── consumer_post_card │ │ │ ├── binary_cp_bar.tsx │ │ │ ├── continuous_cp_bar.tsx │ │ │ ├── group_forecast_card │ │ │ │ ├── date_forecast_card │ │ │ │ │ ├── date_card_tooltip.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── prediction_symbol.tsx │ │ │ │ │ ├── scatter_label.tsx │ │ │ │ │ └── styles.scss │ │ │ │ ├── forecast_card_wrapper.tsx │ │ │ │ ├── forecast_choice_bar.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── numeric_forecast_card.tsx │ │ │ │ └── percentage_forecast_card.tsx │ │ │ ├── index.tsx │ │ │ ├── key_factor.tsx │ │ │ ├── prediction_info.tsx │ │ │ ├── question_forecast_chip.tsx │ │ │ ├── question_resolution_chip.tsx │ │ │ ├── time_series_chart │ │ │ │ ├── index.tsx │ │ │ │ ├── styles.scss │ │ │ │ └── time_series_label.tsx │ │ │ └── upcoming_cp.tsx │ │ ├── contact_us_modal.tsx │ │ ├── cp_movement.tsx │ │ ├── cp_reveal_time │ │ │ ├── cp_reveal_time.tsx │ │ │ └── index.tsx │ │ ├── detailed_question_card │ │ │ ├── detailed_group_card │ │ │ │ └── index.tsx │ │ │ └── detailed_question_card │ │ │ │ ├── continuous_chart_card.tsx │ │ │ │ ├── error_boundary.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── multiple_choice_chart_card.tsx │ │ │ │ └── numeric_cursor_item.tsx │ │ ├── embed_modal.tsx │ │ ├── forecast_card.tsx │ │ ├── forecast_maker │ │ │ ├── binary_slider.tsx │ │ │ ├── conditional_forecast_table.tsx │ │ │ ├── container.tsx │ │ │ ├── continuous_group_accordion │ │ │ │ ├── accordion_open_button.tsx │ │ │ │ ├── accordion_resolution_cell.tsx │ │ │ │ ├── group_forecast_accordion.tsx │ │ │ │ ├── group_forecast_accordion_item.tsx │ │ │ │ └── group_forecast_accordion_modal.tsx │ │ │ ├── continuous_input │ │ │ │ ├── continuous_input_container.tsx │ │ │ │ ├── continuous_input_mode_switcher.tsx │ │ │ │ ├── continuous_prediction_chart.tsx │ │ │ │ ├── continuous_slider.tsx │ │ │ │ ├── example_continuous_input.tsx │ │ │ │ └── index.tsx │ │ │ ├── continuous_table │ │ │ │ ├── continuous_table_input.tsx │ │ │ │ └── index.tsx │ │ │ ├── forecast_choice_option.tsx │ │ │ ├── forecast_maker_conditional │ │ │ │ ├── forecast_maker_conditional_binary.tsx │ │ │ │ ├── forecast_maker_conditional_continuous.tsx │ │ │ │ ├── forecast_maker_conditional_resolution_message.tsx │ │ │ │ └── index.tsx │ │ │ ├── forecast_maker_group │ │ │ │ ├── continuous_input_wrapper.tsx │ │ │ │ ├── forecast_maker_group_binary.tsx │ │ │ │ ├── forecast_maker_group_continuous.tsx │ │ │ │ ├── forecast_maker_group_copy_menu.tsx │ │ │ │ ├── forecast_maker_group_menu.tsx │ │ │ │ └── index.tsx │ │ │ ├── forecast_maker_question │ │ │ │ ├── forecast_maker_binary.tsx │ │ │ │ ├── forecast_maker_continuous.tsx │ │ │ │ ├── forecast_maker_multiple_choice.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── prediction_success_box.tsx │ │ │ ├── forecast_text_input.tsx │ │ │ ├── helpers.ts │ │ │ ├── index.tsx │ │ │ ├── predict_button.tsx │ │ │ ├── prediction_status_message.tsx │ │ │ └── resolution │ │ │ │ ├── index.tsx │ │ │ │ ├── resolution_modal.tsx │ │ │ │ ├── score_display.tsx │ │ │ │ └── unresolve_button.tsx │ │ ├── global_error_boundary.tsx │ │ ├── global_modals.tsx │ │ ├── group_question_resolution.tsx │ │ ├── html_content │ │ │ ├── html_content.tsx │ │ │ └── index.tsx │ │ ├── icons │ │ │ ├── admin.tsx │ │ │ ├── die.tsx │ │ │ ├── google.tsx │ │ │ ├── moderator.tsx │ │ │ └── resolution.tsx │ │ ├── katex_renderer │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── language_menu.tsx │ │ ├── markdown_editor │ │ │ ├── createJsxComponent.tsx │ │ │ ├── editor.css │ │ │ ├── editor_toolbar.tsx │ │ │ ├── embedded_question │ │ │ │ ├── embed_question_modal.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.scss │ │ │ ├── embedded_twitter │ │ │ │ ├── helpers.ts │ │ │ │ └── index.tsx │ │ │ ├── helpers.ts │ │ │ ├── hooks │ │ │ │ └── use_backspace_node_remove.ts │ │ │ ├── index.tsx │ │ │ ├── initialized_editor.tsx │ │ │ ├── plugins │ │ │ │ ├── equation │ │ │ │ │ ├── components │ │ │ │ │ │ ├── add_equation_action.tsx │ │ │ │ │ │ ├── add_equation_modal.tsx │ │ │ │ │ │ ├── equation_component.tsx │ │ │ │ │ │ └── equation_editor.tsx │ │ │ │ │ ├── equation_node.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── lexical_equation_visitor.ts │ │ │ │ │ └── mdast_equation_visitor.ts │ │ │ │ ├── link │ │ │ │ │ ├── AutoLinkPlugin.tsx │ │ │ │ │ ├── LexicalLinkVisitor.ts │ │ │ │ │ ├── MdastLinkVisitor.ts │ │ │ │ │ └── index.tsx │ │ │ │ └── mentions │ │ │ │ │ ├── LexicalBeautifulMentionVisitor.ts │ │ │ │ │ ├── components │ │ │ │ │ ├── default_mentions_context.tsx │ │ │ │ │ ├── mention.tsx │ │ │ │ │ ├── menu.tsx │ │ │ │ │ └── plugin.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ └── source_mode_title │ │ │ │ └── index.tsx │ │ ├── nav_link.tsx │ │ ├── news_card.tsx │ │ ├── onboarding │ │ │ ├── onboarding_check.tsx │ │ │ ├── onboarding_loading.tsx │ │ │ ├── onboarding_modal.tsx │ │ │ ├── steps │ │ │ │ ├── index.tsx │ │ │ │ ├── step.tsx │ │ │ │ ├── step_0.tsx │ │ │ │ ├── step_1.tsx │ │ │ │ ├── step_2.tsx │ │ │ │ ├── step_3.tsx │ │ │ │ ├── step_4.tsx │ │ │ │ └── step_5.tsx │ │ │ ├── utils.ts │ │ │ └── verbal_forecast.tsx │ │ ├── period_movement.tsx │ │ ├── popover_filter │ │ │ ├── combobox_filter.tsx │ │ │ ├── index.tsx │ │ │ ├── multi_chip_filter.tsx │ │ │ ├── toggle_chip_filter.tsx │ │ │ └── types.ts │ │ ├── post_actions │ │ │ ├── index.tsx │ │ │ ├── post_dropdown_menu.tsx │ │ │ └── share_post_menu.tsx │ │ ├── post_card │ │ │ ├── basic_post_card │ │ │ │ ├── comment_status.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── post_voter.tsx │ │ │ ├── chart_overflow.tsx │ │ │ ├── community_disclaimer.tsx │ │ │ ├── error_boundary.tsx │ │ │ ├── group_of_questions_tile │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── multiple_choice_tile │ │ │ │ ├── choice_option.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── multiple_choice_tile_legend.tsx │ │ │ ├── notebook_tile.tsx │ │ │ ├── question_tile │ │ │ │ ├── index.tsx │ │ │ │ └── question_continuous_tile.tsx │ │ │ ├── reaffirm_button.tsx │ │ │ └── reaffirm_context.tsx │ │ ├── post_default_project.tsx │ │ ├── post_status │ │ │ ├── index.tsx │ │ │ └── status_icon.tsx │ │ ├── post_subscribe │ │ │ ├── post_subscribe_customise_modal.tsx │ │ │ ├── subscribe_button │ │ │ │ ├── index.tsx │ │ │ │ ├── post_subscribe_success_modal.tsx │ │ │ │ └── utils.ts │ │ │ └── subscription_types_customisation │ │ │ │ ├── subscription_cp_change.tsx │ │ │ │ ├── subscription_milestone.tsx │ │ │ │ ├── subscription_new_comments.tsx │ │ │ │ ├── subscription_specific_time.tsx │ │ │ │ └── types.ts │ │ ├── posthog_page_view.tsx │ │ ├── posts_feed │ │ │ ├── constants.ts │ │ │ ├── empty_community_feed.tsx │ │ │ ├── feed_scroll_restoration.tsx │ │ │ ├── in_review_box.tsx │ │ │ ├── index.tsx │ │ │ └── paginated_feed.tsx │ │ ├── posts_filters.tsx │ │ ├── prediction_chip.tsx │ │ ├── public_settings_script.tsx │ │ ├── question │ │ │ ├── background_info.tsx │ │ │ └── resolution_criteria.tsx │ │ ├── random_button.tsx │ │ ├── refresh_button.tsx │ │ ├── rich_text.tsx │ │ ├── search_input.tsx │ │ ├── server_component_error_boundary.tsx │ │ ├── sliders │ │ │ ├── multi_slider.tsx │ │ │ ├── primitives │ │ │ │ └── thumb.tsx │ │ │ ├── slider.css │ │ │ └── slider.tsx │ │ ├── theme_provider.tsx │ │ ├── theme_toggle.tsx │ │ ├── tournament_card.tsx │ │ ├── tournament_filters.tsx │ │ ├── truncated_text_tooltip.tsx │ │ ├── ui │ │ │ ├── abreviated_numeric_input.tsx │ │ │ ├── button.tsx │ │ │ ├── button_group.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── chip.tsx │ │ │ ├── circle_divider.tsx │ │ │ ├── collapsible_box.tsx │ │ │ ├── datetime_utc.tsx │ │ │ ├── dropdown_menu.tsx │ │ │ ├── expandable_content.tsx │ │ │ ├── form_field.tsx │ │ │ ├── image_with_fallback.tsx │ │ │ ├── info_toggle.tsx │ │ │ ├── inline_select.tsx │ │ │ ├── input_container.tsx │ │ │ ├── listbox.tsx │ │ │ ├── loading_indicator.tsx │ │ │ ├── loading_spiner.tsx │ │ │ ├── local_daytime.tsx │ │ │ ├── markdown_text.tsx │ │ │ ├── progress_bar.tsx │ │ │ ├── radio_button.tsx │ │ │ ├── resizable_text_area.tsx │ │ │ ├── section_toggle.tsx │ │ │ ├── select.tsx │ │ │ ├── switch.tsx │ │ │ ├── tab_bar │ │ │ │ ├── index.tsx │ │ │ │ └── tab.tsx │ │ │ ├── table.tsx │ │ │ └── tooltip.tsx │ │ ├── visibility_observer.tsx │ │ └── voter.tsx │ ├── constants │ │ ├── chart_theme.ts │ │ ├── colors.ts │ │ ├── comments.ts │ │ ├── global_search_params.ts │ │ ├── metadata.ts │ │ ├── posts_feed.ts │ │ └── questions.ts │ ├── contexts │ │ ├── auth_context.tsx │ │ ├── cp_context.tsx │ │ ├── embed_modal_context.tsx │ │ ├── global_search_context.tsx │ │ ├── modal_context.tsx │ │ ├── navigation_context.tsx │ │ ├── polyfill.tsx │ │ ├── posthog_context.tsx │ │ ├── public_settings_context.tsx │ │ └── translations_banner_context.tsx │ ├── declarations │ │ ├── file-saver.d.ts │ │ └── files.d.ts │ ├── hooks │ │ ├── share.ts │ │ ├── tailwind.ts │ │ ├── use_app_theme.ts │ │ ├── use_chart_tooltip.ts │ │ ├── use_confirm_page_leave.ts │ │ ├── use_container_size.ts │ │ ├── use_debounce.ts │ │ ├── use_hash.ts │ │ ├── use_mounted.ts │ │ ├── use_previous.ts │ │ ├── use_screen_size.ts │ │ ├── use_scroll_to.ts │ │ ├── use_search_input_state.ts │ │ ├── use_search_params.ts │ │ ├── use_section_headings.ts │ │ ├── use_server_action.ts │ │ ├── use_stored_state.ts │ │ └── use_timestamp_cursor.ts │ ├── i18n │ │ └── request.ts │ ├── instrumentation-client.ts │ ├── instrumentation.ts │ ├── middleware.ts │ ├── polyfills.ts │ ├── services │ │ ├── api │ │ │ ├── aggregation_explorer │ │ │ │ ├── aggregation_explorer.client.ts │ │ │ │ ├── aggregation_explorer.server.ts │ │ │ │ └── aggregation_explorer.shared.ts │ │ │ ├── api_service.ts │ │ │ ├── auth │ │ │ │ └── auth.server.ts │ │ │ ├── comments │ │ │ │ ├── comments.client.ts │ │ │ │ ├── comments.server.ts │ │ │ │ └── comments.shared.ts │ │ │ ├── leaderboard │ │ │ │ ├── leaderboard.server.ts │ │ │ │ └── leaderboard.shared.ts │ │ │ ├── misc │ │ │ │ ├── misc.client.ts │ │ │ │ ├── misc.server.ts │ │ │ │ └── misc.shared.ts │ │ │ ├── posts │ │ │ │ ├── posts.client.ts │ │ │ │ ├── posts.server.ts │ │ │ │ └── posts.shared.ts │ │ │ ├── profile │ │ │ │ ├── profile.client.ts │ │ │ │ ├── profile.server.ts │ │ │ │ └── profile.shared.ts │ │ │ ├── projects │ │ │ │ ├── projects.client.ts │ │ │ │ ├── projects.server.ts │ │ │ │ └── projects.shared.ts │ │ │ ├── questions │ │ │ │ └── questions.server.ts │ │ │ └── track_record │ │ │ │ ├── track_record.server.ts │ │ │ │ └── track_record.shared.ts │ │ ├── session.ts │ │ └── translation.ts │ ├── types │ │ ├── auth.ts │ │ ├── charts.ts │ │ ├── choices.ts │ │ ├── comment.ts │ │ ├── experiments.ts │ │ ├── fetch.ts │ │ ├── navigation.ts │ │ ├── news.ts │ │ ├── notifications.ts │ │ ├── onboarding.ts │ │ ├── post.ts │ │ ├── preferences.ts │ │ ├── projects.ts │ │ ├── question.ts │ │ ├── reactour.d.ts │ │ ├── scoring.ts │ │ ├── sidebar.ts │ │ ├── theme.ts │ │ ├── track_record.ts │ │ ├── translations.ts │ │ ├── users.ts │ │ ├── utils.ts │ │ └── votes.ts │ └── utils │ │ ├── alpha_access.ts │ │ ├── analytics.ts │ │ ├── charts │ │ ├── axis.ts │ │ ├── cursor.ts │ │ ├── helpers.ts │ │ ├── resolution.ts │ │ └── timestamps.ts │ │ ├── comments.ts │ │ ├── core │ │ ├── cn.ts │ │ ├── colors.ts │ │ ├── errors.ts │ │ ├── fetch │ │ │ ├── fetch.client.ts │ │ │ ├── fetch.server.ts │ │ │ └── fetch.shared.ts │ │ └── storage.ts │ │ ├── drafts │ │ ├── comments.ts │ │ ├── index.ts │ │ └── questionForm.ts │ │ ├── files.ts │ │ ├── forecasts │ │ ├── dataset.ts │ │ ├── helpers.ts │ │ ├── initial_values.ts │ │ └── switch_forecast_type.ts │ │ ├── formatters │ │ ├── date.ts │ │ ├── number.ts │ │ ├── prediction.ts │ │ ├── resolution.ts │ │ ├── string.ts │ │ └── users.ts │ │ ├── markdown.ts │ │ ├── math.ts │ │ ├── navigation.ts │ │ ├── onboarding.ts │ │ ├── public_settings.server.ts │ │ ├── public_settings.ts │ │ ├── questions │ │ ├── choices.ts │ │ ├── forecastAvailability.ts │ │ ├── groupOrdering.ts │ │ ├── helpers.ts │ │ ├── predictions.ts │ │ ├── resolution.ts │ │ └── units.ts │ │ └── sidebar.ts ├── tailwind.config.ts └── tsconfig.json ├── heroku.yml ├── init-db.sql ├── licenses ├── third_party_licenses_js.csv └── third_party_licenses_python.csv ├── manage.py ├── metaculus_web ├── __init__.py ├── admin.py ├── asgi.py ├── locale │ └── original │ │ ├── LC_MESSAGES │ │ └── django.po │ │ ├── __init__.py │ │ └── formats.py ├── settings.py ├── test_settings.py ├── urls.py └── wsgi.py ├── misc ├── __init__.py ├── admin.py ├── context_processors.py ├── jobs.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── anonymize_users.py │ │ ├── cron.py │ │ ├── mjml_compose.py │ │ ├── sync_itn_articles.py │ │ ├── sync_post_itn_similarity.py │ │ └── update_translations.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_initial.py │ ├── 0003_whitelistuser.py │ ├── 0004_postarticle.py │ ├── 0005_sidebaritem.py │ └── __init__.py ├── models.py ├── serializers.py ├── services │ ├── __init__.py │ └── itn.py ├── tasks.py ├── templatetags │ ├── __init__.py │ ├── urls.py │ └── utils.py ├── urls.py └── views.py ├── notifications ├── __init__.py ├── constants.py ├── jobs.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_initial.py │ └── __init__.py ├── models.py ├── services.py ├── templates │ └── emails │ │ ├── comment_mention.html │ │ ├── comment_mention.mjml │ │ ├── comment_report.html │ │ ├── comment_report.mjml │ │ ├── post_cp_change.html │ │ ├── post_cp_change.mjml │ │ ├── post_milestone.html │ │ ├── post_milestone.mjml │ │ ├── post_new_comments.html │ │ ├── post_new_comments.mjml │ │ ├── post_specific_time.html │ │ ├── post_specific_time.mjml │ │ ├── post_status_change.html │ │ ├── post_status_change.mjml │ │ ├── predicted_question_resolved.html │ │ ├── predicted_question_resolved.mjml │ │ ├── repeated_spam_to_admins.html │ │ ├── repeated_spam_to_admins.mjml │ │ ├── suspected_spam_to_admins.html │ │ └── suspected_spam_to_admins.mjml └── utils.py ├── poetry.lock ├── posts ├── __init__.py ├── admin.py ├── jobs.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── compute_hotness.py │ │ ├── compute_movement.py │ │ └── generate_search_embeds.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_initial.py │ ├── 0003_post_comment_count_post_forecasters_count_and_more.py │ ├── 0004_post_show_on_homepage_alter_notebook_image_url.py │ ├── 0005_remove_postsubscription_postsubscription_unique_type_user_post_and_more.py │ ├── 0006_alter_post_scheduled_close_time_and_more.py │ ├── 0007_post_open_time_alter_post_actual_close_time_and_more.py │ ├── 0008_rename_published_at_triggered_post_open_time_triggered.py │ ├── 0009_notebook_content_last_md5_and_more.py │ ├── 0010_populate_original_fields.py │ ├── 0011_notebook_markdown_pt_post_title_pt_post_url_title_pt.py │ ├── 0012_alter_post_default_project.py │ ├── 0013_alter_post_hotness.py │ ├── 0014_notebook_is_automatically_translated_and_more.py │ ├── 0015_post_short_title_post_short_title_cs_and_more.py │ ├── 0016_remove_post_url_title_remove_post_url_title_cs_and_more.py │ ├── 0017_remove_post_open_time_triggered.py │ ├── 0018_notebook_markdown_zh_tw_post_short_title_zh_tw_and_more.py │ ├── 0019_post_html_metadata_json.py │ └── __init__.py ├── models.py ├── serializers.py ├── services │ ├── __init__.py │ ├── common.py │ ├── feed.py │ ├── hotness.py │ ├── search.py │ ├── spam_detection.py │ └── subscriptions.py ├── tasks.py ├── translation.py ├── urls.py ├── utils.py └── views.py ├── projects ├── __init__.py ├── admin.py ├── constants.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_initial.py │ ├── 0003_project_show_on_homepage.py │ ├── 0004_remove_project_is_active_alter_project_type.py │ ├── 0005_project_followers_count_project_unlisted_and_more.py │ ├── 0006_project_content_last_md5_and_more.py │ ├── 0007_populate_original_fields.py │ ├── 0008_project_description_pt_project_name_pt_and_more.py │ ├── 0009_project_visibility.py │ ├── 0010_remove_project_add_posts_to_main_feed_and_more.py │ ├── 0011_alter_project_type_projectindexquestion.py │ ├── 0012_project_is_automatically_translated.py │ ├── 0013_project_forecasting_end_date_and_more.py │ ├── 0014_project_description_zh_tw_project_name_zh_tw_and_more.py │ ├── 0015_project_forecasts_flow_enabled.py │ ├── 0016_remove_project_include_bots_in_leaderboard_and_more.py │ ├── 0017_remove_project_section.py │ ├── 0018_project_forecasters_count_project_forecasts_count.py │ └── __init__.py ├── models.py ├── permissions.py ├── serializers │ ├── __init__.py │ ├── common.py │ └── communities.py ├── services │ ├── __init__.py │ ├── cache.py │ ├── common.py │ └── communities.py ├── translation.py ├── urls.py └── views │ ├── __init__.py │ ├── common.py │ └── communities.py ├── pyproject.toml ├── pytest.ini ├── questions ├── __init__.py ├── admin.py ├── apps.py ├── constants.py ├── jobs.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── build_forecasts.py │ │ └── generate_question_units.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_initial.py │ ├── 0003_aggregateforecast_questions_a_questio_0d22f0_idx.py │ ├── 0004_questionpost.py │ ├── 0005_remove_aggregateforecast_questions_a_questio_0d22f0_idx_and_more.py │ ├── 0006_alter_aggregateforecast_centers_and_more.py │ ├── 0007_question_question_weight.py │ ├── 0008_alter_question_label.py │ ├── 0009_groupofquestions_content_last_md5_and_more.py │ ├── 0010_populate_original_fields.py │ ├── 0011_groupofquestions_description_pt_and_more.py │ ├── 0012_question_group_variable.py │ ├── 0013_forecast_source.py │ ├── 0014_forecast_distribution_input.py │ ├── 0015_remove_forecast_distribution_components_and_more.py │ ├── 0016_groupofquestions_is_automatically_translated_and_more.py │ ├── 0017_alter_question_actual_close_time_and_more.py │ ├── 0017_question_unit.py │ ├── 0018_groupofquestions_subquestions_order_and_more.py │ ├── 0019_merge_20250319_2033.py │ ├── 0020_question_spot_scoring_time.py │ ├── 0021_question_inbound_outcome_count_and_more.py │ ├── 0022_question_open_time_triggered.py │ ├── 0023_question_movement.py │ ├── 0024_groupofquestions_description_zh_tw_and_more.py │ └── __init__.py ├── models.py ├── serializers │ ├── __init__.py │ ├── common.py │ └── forecasting_flow.py ├── services.py ├── tasks.py ├── translation.py ├── types.py ├── urls.py ├── utils.py └── views.py ├── scoring ├── __init__.py ├── admin.py ├── apps.py ├── jobs.py ├── management │ └── commands │ │ └── update_medal_ranks.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_initial.py │ ├── 0003_alter_leaderboard_name_alter_leaderboard_project_and_more.py │ ├── 0004_alter_leaderboard_end_time_and_more.py │ ├── 0005_alter_score_aggregation_method_and_more.py │ ├── 0006_alter_leaderboardentry_excluded_and_more.py │ ├── 0007_leaderboard_finalized_leaderboard_prize_pool_and_more.py │ ├── 0008_leaderboardsranksentry.py │ ├── 0009_leaderboard_user_list.py │ ├── 0010_leaderboard_simplified_view.py │ ├── 0011_medalexclusionrecord_leaderboard_and_more.py │ ├── 0012_remove_leaderboard_simplified_view.py │ ├── 0013_leaderboard_bot_status.py │ └── __init__.py ├── models.py ├── reputation.py ├── score_math.py ├── serializers.py ├── tests.py ├── urls.py ├── utils.py └── views.py ├── screenshot ├── Dockerfile ├── Readme.md ├── app.py ├── deploy_to_heroku.sh ├── poetry.lock ├── pyproject.toml └── start.sh ├── scripts ├── create_minimal_db.sh ├── deploy_to_heroku.sh ├── k6-stress │ ├── Readme.md │ └── simple-stress.js ├── nginx │ └── etc │ │ └── nginx │ │ ├── http.d │ │ └── app_nginx.template │ │ └── proxy_params ├── prod │ ├── django_cron.sh │ ├── release.sh │ ├── run_all.sh │ ├── run_dramatiq.sh │ └── startapp.sh └── tests │ └── run_integration_tests.sh ├── templates ├── admin │ ├── posts │ │ └── assign_to_project.html │ ├── projects │ │ ├── add_posts_to_project.html │ │ └── project_change_form.html │ └── scoring │ │ └── leaderboard_action_descriptions.html ├── emails │ ├── Readme.md │ ├── email_greeting.mjml │ ├── email_similar_questions.mjml │ ├── email_styles.mjml │ ├── email_top.mjml │ ├── email_unsubscribe.mjml │ └── multiple_choice_table.mjml └── swagger-ui.html ├── tests ├── __init__.py ├── integration │ ├── __init__.py │ └── test_simple.py └── unit │ ├── __init__.py │ ├── conftest.py │ ├── test_auth │ ├── __init__.py │ ├── test_services.py │ └── test_views.py │ ├── test_comments │ ├── __init__.py │ ├── factories.py │ ├── test_serializers.py │ ├── test_services.py │ ├── test_utils.py │ └── test_views.py │ ├── test_misc │ ├── __init__.py │ └── factories.py │ ├── test_notifications │ ├── __init__.py │ ├── factories.py │ ├── test_services.py │ └── test_utils.py │ ├── test_posts │ ├── __init__.py │ ├── conftest.py │ ├── factories.py │ ├── test_models.py │ ├── test_services │ │ ├── __init__.py │ │ ├── test_common.py │ │ ├── test_feed.py │ │ ├── test_hotness.py │ │ └── test_subscriptions.py │ ├── test_tasks.py │ └── test_views.py │ ├── test_projects │ ├── __init__.py │ ├── factories.py │ ├── test_communities │ │ ├── __init__.py │ │ ├── fixtures.py │ │ └── test_views.py │ ├── test_models.py │ ├── test_services.py │ └── test_views.py │ ├── test_questions │ ├── __init__.py │ ├── conftest.py │ ├── factories.py │ ├── test_services.py │ └── test_views.py │ ├── test_users │ ├── __init__.py │ ├── factories.py │ └── test_views.py │ └── test_utils │ ├── __init__.py │ ├── test_aggregations.py │ ├── test_dtypes.py │ ├── test_measures.py │ └── test_middlewares.py ├── users ├── __init__.py ├── admin.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_user_is_onboarding_complete.py │ ├── 0003_usercampaignregistration.py │ ├── 0004_alter_usercampaignregistration_key.py │ ├── 0005_user_is_spam.py │ ├── 0006_auto_fix_spelling_campaign_registration.py │ ├── 0007_user_is_superuser_idx.py │ ├── 0008_userspamactivity.py │ ├── 0009_alter_userspamactivity_content_type.py │ ├── 0010_alter_user_is_bot.py │ └── __init__.py ├── models.py ├── serializers.py ├── services │ ├── common.py │ └── spam_detection.py ├── templates │ └── emails │ │ ├── change_email_confirm.html │ │ └── change_email_confirm.mjml ├── urls.py └── views.py └── utils ├── __init__.py ├── cache.py ├── call_llm.py ├── cloudflare.py ├── csv_utils.py ├── db.py ├── debug.py ├── dramatiq.py ├── dtypes.py ├── email.py ├── exceptions.py ├── files.py ├── formatters.py ├── frontend.py ├── management.py ├── middlewares.py ├── models.py ├── openai.py ├── paginator.py ├── requests.py ├── serializers.py ├── serper_google.py ├── tasks.py ├── the_math ├── aggregations.py ├── formulas.py └── measures.py ├── translation.py ├── types.py ├── typing.py ├── urls.py └── views.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | inline-quotes = " 3 | extend-ignore = 4 | E203,E501,E701,E712,Q003 5 | max-line-length = 80 6 | extend-select = B950 7 | max-complexity = 55 8 | exclude = 9 | .git, 10 | __pycache__, 11 | .pytest_cache, 12 | lambda, 13 | scripts, 14 | venv, 15 | .venv, 16 | cdk/cdk.out, 17 | migrations 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Help us improve 4 | title: '' 5 | labels: Bug Report 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the bug 11 | ... 12 | 13 | ### Steps to reproduce 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 18 | Observed behaviour: ... 19 | 20 | Expected behaviour: ... 21 | 22 | (Please include links and screenshots where applicable.) 23 | 24 | ### Extra context 25 | Device, OS, browser, etc. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: Feature Request 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Context 11 | 12 | _Provide relevant context if any._ 13 | 14 | ### Feature description 15 | 16 | _Describe the improvement you want._ 17 | -------------------------------------------------------------------------------- /.github/workflows/deploy_dev_image.yml: -------------------------------------------------------------------------------- 1 | name: Build and push a dev Docker image 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy-a-dev-docker-image: 8 | runs-on: ubuntu-22.04 9 | environment: dev_env 10 | env: 11 | ENV_DOCKER_TAG: dev 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | - name: Login to ghcr.io 17 | uses: docker/login-action@v3 18 | with: 19 | registry: ghcr.io 20 | username: ${{ github.actor }} 21 | password: ${{ secrets.GITHUB_TOKEN }} 22 | - name: Build and push to ghcr.io 23 | run: | 24 | docker build --platform linux/amd64 . -t ghcr.io/metaculus/metaculus:$ENV_DOCKER_TAG-$GITHUB_SHA -t ghcr.io/metaculus/metaculus:$ENV_DOCKER_TAG-latest --target all_runners --push 25 | -------------------------------------------------------------------------------- /.github/workflows/deploy_play.yml: -------------------------------------------------------------------------------- 1 | name: Deploy play.metaculus.com (Experiments) 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy-to-play: 8 | runs-on: ubuntu-22.04 9 | environment: play_env 10 | env: 11 | HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} 12 | HEROKU_APP: play-metaculus-web 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | - name: Build and deploy to Heroku 18 | run: | 19 | curl https://cli-assets.heroku.com/install-ubuntu.sh | sh 20 | heroku container:login # uses the HEROKU_API_KEY 21 | ./scripts/deploy_to_heroku.sh 22 | -------------------------------------------------------------------------------- /.github/workflows/deploy_screenshot.yml: -------------------------------------------------------------------------------- 1 | name: Deploy the screenshot service 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy-to-dev: 8 | runs-on: ubuntu-latest 9 | env: 10 | HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} 11 | HEROKU_APP: metaculus-screenshot 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | - name: Build and deploy to Heroku 17 | run: | 18 | heroku container:login # uses the HEROKU_API_KEY 19 | cd screenshot && ./deploy_to_heroku.sh 20 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.12.3 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: python manage.py migrate 2 | web: gunicorn metaculus_web.wsgi:application --workers 4 --bind 0.0.0.0:8000 & cd front_end && npm run dev 3 | dramatiq: python3 manage.py rundramatiq --processes 2 --threads 4 4 | django_cron: python3 manage.py cron 5 | # build command that doesn't store assets on release: NODE_ENV=dev npm --prefix front_end run build -------------------------------------------------------------------------------- /authentication/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/authentication/__init__.py -------------------------------------------------------------------------------- /authentication/admin.py: -------------------------------------------------------------------------------- 1 | from django.apps import apps 2 | from django.contrib import admin 3 | from rest_framework.authtoken.models import TokenProxy 4 | 5 | # Register your models here. 6 | app_models = apps.get_app_config("authentication").get_models() 7 | for model in app_models: 8 | admin.site.register(model) 9 | 10 | admin.site.unregister(TokenProxy) 11 | -------------------------------------------------------------------------------- /authentication/backends.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import user_logged_in 2 | from django.contrib.auth.backends import ModelBackend 3 | from django.db.models import Q 4 | 5 | from users.models import User 6 | 7 | 8 | class AuthLoginBackend(ModelBackend): 9 | """ 10 | Auth backend that allows to authenticate via email or username as login 11 | """ 12 | 13 | @classmethod 14 | def find_user(cls, login=None): 15 | return User.objects.filter( 16 | Q(username__iexact=login) | Q(email__iexact=login) 17 | ).first() 18 | 19 | def authenticate(self, request, login=None, password=None, **kwargs): 20 | if not login: 21 | return None 22 | 23 | user = self.find_user(login=login) 24 | 25 | if user and user.check_password(password) and self.user_can_authenticate(user): 26 | user_logged_in.send(sender=user.__class__, request=request, user=user) 27 | 28 | return user 29 | -------------------------------------------------------------------------------- /authentication/social_pipeline.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from rest_framework.exceptions import ValidationError 3 | 4 | 5 | def check_signup_allowed(strategy, details, backend, user=None, *args, **kwargs): 6 | if not settings.PUBLIC_ALLOW_SIGNUP and not user: 7 | raise ValidationError("Signup is disabled") 8 | 9 | return {"user": user, **kwargs} 10 | -------------------------------------------------------------------------------- /authentication/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import common, social 4 | 5 | urlpatterns = [ 6 | path("auth/login/token/", common.login_api_view), 7 | path("auth/verify_token/", common.verify_token_api_view), 8 | path("auth/signup/", common.signup_api_view, name="auth-signup"), 9 | path("auth/signup/resend/", common.resend_activation_link_api_view), 10 | path("auth/signup/activate/", common.signup_activate_api_view), 11 | # Social auth 12 | path("auth/social/", social.social_providers_api_view), 13 | path("auth/social//", social.SocialCodeAuth.as_view()), 14 | # Password Reset 15 | path("auth/password-reset/", common.password_reset_api_view), 16 | path("auth/password-reset/change/", common.password_reset_confirm_api_view), 17 | # Invite user 18 | path("auth/invite/", common.invite_user_api_view), 19 | ] 20 | -------------------------------------------------------------------------------- /authentication/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/authentication/views/__init__.py -------------------------------------------------------------------------------- /comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/comments/__init__.py -------------------------------------------------------------------------------- /comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "comments" 7 | -------------------------------------------------------------------------------- /comments/constants.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class CommentReportType(models.TextChoices): 5 | SPAM = "spam" 6 | VIOLATION = "violation" 7 | -------------------------------------------------------------------------------- /comments/migrations/0002_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-17 15:10 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ("comments", "0001_initial"), 13 | ("questions", "0001_initial"), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name="changedmymindentry", 19 | name="forecast", 20 | field=models.ForeignKey( 21 | blank=True, 22 | null=True, 23 | on_delete=django.db.models.deletion.CASCADE, 24 | to="questions.forecast", 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /comments/migrations/0004_alter_comment_is_private.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-10 16:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("comments", "0003_initial"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="comment", 14 | name="is_private", 15 | field=models.BooleanField(db_index=True, default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /comments/migrations/0006_populate_original_fields.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-13 15:49 2 | 3 | from django.db import migrations 4 | from utils.translation import migration_update_default_fields 5 | 6 | 7 | def migrate(apps, schema_editor): 8 | Comment = apps.get_model("comments", "Comment") 9 | migration_update_default_fields(Comment, ["text"]) 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("comments", "0005_comment_content_last_md5_and_more"), 16 | ] 17 | 18 | operations = [ 19 | migrations.RunPython(migrate, migrations.RunPython.noop), 20 | ] 21 | -------------------------------------------------------------------------------- /comments/migrations/0007_comment_text_pt.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-13 21:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("comments", "0006_populate_original_fields"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="comment", 15 | name="text_pt", 16 | field=models.TextField(null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /comments/migrations/0009_alter_comment_is_soft_deleted.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-12-10 13:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("comments", "0008_keyfactor_keyfactorvote"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="comment", 15 | name="is_soft_deleted", 16 | field=models.BooleanField(db_index=True, default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /comments/migrations/0010_comment_text_edited_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-03-03 17:57 2 | 3 | from django.db import migrations, models 4 | from django.db.models import F 5 | 6 | 7 | def migrate_text_edited_at(apps, schema_editor): 8 | Comment = apps.get_model("comments", "Comment") 9 | Comment.objects.update(text_edited_at=F("edited_at")) 10 | 11 | 12 | class Migration(migrations.Migration): 13 | dependencies = [ 14 | ("comments", "0009_alter_comment_is_soft_deleted"), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name="comment", 20 | name="text_edited_at", 21 | field=models.DateTimeField(blank=True, editable=False, null=True), 22 | ), 23 | migrations.RunPython(migrate_text_edited_at, migrations.RunPython.noop), 24 | ] 25 | -------------------------------------------------------------------------------- /comments/migrations/0012_comment_is_automatically_translated_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-03-06 10:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("comments", "0011_comment_is_pinned_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="comment", 15 | name="is_automatically_translated", 16 | field=models.BooleanField(default=True), 17 | ), 18 | migrations.AddField( 19 | model_name="keyfactor", 20 | name="is_automatically_translated", 21 | field=models.BooleanField(default=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /comments/migrations/0014_comment_text_zh_tw_keyfactor_text_zh_tw.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.7 on 2025-04-24 06:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("comments", "0013_remove_keyfactorvote_votes_unique_user_key_factor_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="comment", 15 | name="text_zh_TW", 16 | field=models.TextField(null=True), 17 | ), 18 | migrations.AddField( 19 | model_name="keyfactor", 20 | name="text_zh_TW", 21 | field=models.TextField(blank=True, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /comments/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/comments/migrations/__init__.py -------------------------------------------------------------------------------- /comments/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/comments/services/__init__.py -------------------------------------------------------------------------------- /comments/services/notifications.py: -------------------------------------------------------------------------------- 1 | from comments.models import Comment 2 | from notifications.constants import MailingTags 3 | from notifications.services import send_comment_mention_notification 4 | from ..utils import comment_extract_user_mentions, get_mention_for_user 5 | 6 | 7 | def notify_mentioned_users(comment: Comment): 8 | users, unique_mentions = comment_extract_user_mentions(comment) 9 | 10 | users = ( 11 | users.exclude(pk=comment.author_id) 12 | # Exclude users with disabled notifications 13 | .exclude(unsubscribed_mailing_tags__contains=[MailingTags.COMMENT_MENTIONS]) 14 | ) 15 | 16 | for user in users: 17 | mention = get_mention_for_user(user, unique_mentions) 18 | send_comment_mention_notification(user, comment, mention) 19 | -------------------------------------------------------------------------------- /comments/tasks.py: -------------------------------------------------------------------------------- 1 | import dramatiq 2 | 3 | from comments.models import Comment 4 | from comments.services.notifications import notify_mentioned_users 5 | from posts.services.subscriptions import notify_new_comments 6 | 7 | 8 | @dramatiq.actor 9 | def run_on_post_comment_create(comment_id: int): 10 | comment = Comment.objects.get(id=comment_id) 11 | post = comment.on_post 12 | 13 | # Notify mentioned users 14 | notify_mentioned_users(comment) 15 | 16 | # Notify new comments 17 | notify_new_comments(post) 18 | -------------------------------------------------------------------------------- /comments/translation.py: -------------------------------------------------------------------------------- 1 | from modeltranslation.translator import TranslationOptions, translator 2 | 3 | from metaculus_web.settings import TRANSLATIONS_FALLBACK_UNDEFINED 4 | 5 | from .models import Comment, KeyFactor 6 | 7 | 8 | class CommentTranslationOptions(TranslationOptions): 9 | fields = ("text",) 10 | fallback_undefined = TRANSLATIONS_FALLBACK_UNDEFINED 11 | 12 | 13 | class KeyFactorTranslationOptions(TranslationOptions): 14 | fields = ("text",) 15 | fallback_undefined = TRANSLATIONS_FALLBACK_UNDEFINED 16 | 17 | 18 | translator.register(Comment, CommentTranslationOptions) 19 | translator.register(KeyFactor, KeyFactorTranslationOptions) 20 | -------------------------------------------------------------------------------- /docs/redocly.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - recommended 3 | rules: 4 | no-path-trailing-slash: off -------------------------------------------------------------------------------- /dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/dump.rdb -------------------------------------------------------------------------------- /fab_management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/fab_management/__init__.py -------------------------------------------------------------------------------- /fab_management/admin.py: -------------------------------------------------------------------------------- 1 | # Register your models here. 2 | -------------------------------------------------------------------------------- /fab_management/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FabManagementConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "fab_management" 7 | -------------------------------------------------------------------------------- /fab_management/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/fab_management/migrations/__init__.py -------------------------------------------------------------------------------- /fab_management/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("", views.fab_management_view, name="fab-management"), 7 | ] 8 | -------------------------------------------------------------------------------- /front_end/.env.example: -------------------------------------------------------------------------------- 1 | # All these are runtime variables. The frontend needs to build without any build-time variables 2 | # The ones starting with PUBLIC_ are being propagated to the browser, so they become PUBLIC 3 | PUBLIC_APP_URL=https://metaculus.com 4 | PUBLIC_API_BASE_URL=http://127.0.0.1:8000 5 | PUBLIC_TURNSTILE_SITE_KEY=key 6 | PUBLIC_POSTHOG_KEY=key 7 | PUBLIC_POSTHOG_BASE_URL=url 8 | PUBLIC_FRONTEND_SENTRY_DSN=sentry_dsn 9 | PUBLIC_GOOGLE_MEASUREMENT_ID=google_measurement_id 10 | PUBLIC_DISALLOW_ALL_BOTS=false 11 | PUBLIC_LANDING_PAGE_URL="/" # Defaults to / 12 | PUBLIC_MINIMAL_UI=true # Defaults to false 13 | PUBLIC_ALLOW_TUTORIAL=false # Defaults to true 14 | PUBLIC_ALLOW_SIGNUP=true 15 | PUBLIC_AUTHENTICATION_REQUIRED=false # Default to false 16 | 17 | # Secret variables 18 | SCREENSHOT_SERVICE_API_URL=https:// 19 | AWS_STORAGE_BUCKET_NAME=your-bucket-name 20 | -------------------------------------------------------------------------------- /front_end/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .next/ 3 | .lintstagedrc.js 4 | commitlint.config.js 5 | next-env.d.ts 6 | -------------------------------------------------------------------------------- /front_end/.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 | # Sentry Config File 39 | .env.sentry-build-plugin 40 | -------------------------------------------------------------------------------- /front_end/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const buildEslintCommand = (filenames) => 4 | `next lint --fix --file ${filenames 5 | .map((f) => path.relative(process.cwd(), f)) 6 | .join(" --file ")}`; 7 | 8 | const buildPrettierCommand = (filenames) => 9 | `prettier --write ${filenames.join(" ")}`; 10 | 11 | module.exports = { 12 | "*.{js,jsx,ts,tsx}": [buildEslintCommand], 13 | "*.{js,jsx,ts,tsx,md,html,css}": [buildPrettierCommand], 14 | }; 15 | -------------------------------------------------------------------------------- /front_end/.nvmrc: -------------------------------------------------------------------------------- 1 | 20.18.0 2 | -------------------------------------------------------------------------------- /front_end/.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | package-lock.json 3 | public 4 | node_modules 5 | -------------------------------------------------------------------------------- /front_end/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": false, 4 | "bracketSpacing": true, 5 | "endOfLine": "lf", 6 | "jsxSingleQuote": false, 7 | "semi": true, 8 | "singleAttributePerLine": false, 9 | "singleQuote": false, 10 | "tabWidth": 2, 11 | "trailingComma": "es5", 12 | "useTabs": false, 13 | "plugins": ["prettier-plugin-tailwindcss"] 14 | } 15 | -------------------------------------------------------------------------------- /front_end/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [ 2 | `create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 3 | 4 | ## Getting Started 5 | 6 | Install packages: 7 | 8 | Ensure to add environment variables in `.env` file according to `.env.example`. 9 | 10 | ```bash 11 | npm install 12 | ``` 13 | 14 | Run the development server: 15 | 16 | ```bash 17 | npm run dev 18 | ``` 19 | 20 | ### Automatically generate missing translation messages 21 | 22 | 1. Add `OPENAI_API_KEY` to `front_end/.env` folder 23 | 24 | ```bash 25 | npm run translations:generate 26 | ``` 27 | 28 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 29 | -------------------------------------------------------------------------------- /front_end/global.d.ts: -------------------------------------------------------------------------------- 1 | import { RowData } from "@tanstack/table-core"; 2 | 3 | import en from "./messages/en.json"; 4 | 5 | import "@tanstack/react-table"; 6 | 7 | type Messages = typeof en; 8 | 9 | declare global { 10 | // Use type safe message keys with `next-intl` 11 | interface IntlMessages extends Messages {} 12 | } 13 | 14 | declare module "@tanstack/table-core" { 15 | interface ColumnMeta { 16 | className: string; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /front_end/messages/original.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /front_end/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /front_end/public/fonts/SourceSerifPro-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/SourceSerifPro-Bold.woff -------------------------------------------------------------------------------- /front_end/public/fonts/SourceSerifPro-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/SourceSerifPro-Bold.woff2 -------------------------------------------------------------------------------- /front_end/public/fonts/SourceSerifPro-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/SourceSerifPro-BoldItalic.woff -------------------------------------------------------------------------------- /front_end/public/fonts/SourceSerifPro-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/SourceSerifPro-BoldItalic.woff2 -------------------------------------------------------------------------------- /front_end/public/fonts/SourceSerifPro-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/SourceSerifPro-Italic.woff -------------------------------------------------------------------------------- /front_end/public/fonts/SourceSerifPro-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/SourceSerifPro-Italic.woff2 -------------------------------------------------------------------------------- /front_end/public/fonts/SourceSerifPro-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/SourceSerifPro-Regular.woff -------------------------------------------------------------------------------- /front_end/public/fonts/SourceSerifPro-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/SourceSerifPro-Regular.woff2 -------------------------------------------------------------------------------- /front_end/public/fonts/inter_18pt-medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/inter_18pt-medium.ttf -------------------------------------------------------------------------------- /front_end/public/fonts/inter_18pt-mediumitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/inter_18pt-mediumitalic.ttf -------------------------------------------------------------------------------- /front_end/public/fonts/inter_italic_variable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/inter_italic_variable.ttf -------------------------------------------------------------------------------- /front_end/public/fonts/inter_variable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/inter_variable.ttf -------------------------------------------------------------------------------- /front_end/public/fonts/league_gothic_variable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/fonts/league_gothic_variable.ttf -------------------------------------------------------------------------------- /front_end/public/images/default_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/images/default_preview.png -------------------------------------------------------------------------------- /front_end/public/images/metaculus_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/public/images/metaculus_logo.png -------------------------------------------------------------------------------- /front_end/public/images/x_mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /front_end/sentry.edge.config.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | import { 4 | beforeSentryAlertSend, 5 | SENTRY_IGNORE_ERRORS, 6 | } from "@/utils/core/errors"; 7 | 8 | if (!!process.env.PUBLIC_FRONTEND_SENTRY_DSN) { 9 | Sentry.init({ 10 | environment: process.env.METACULUS_ENV, 11 | dsn: process.env.PUBLIC_FRONTEND_SENTRY_DSN, 12 | tracesSampleRate: 0.1, 13 | ignoreErrors: SENTRY_IGNORE_ERRORS, 14 | beforeSend: beforeSentryAlertSend, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /front_end/sentry.server.config.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | import { 4 | beforeSentryAlertSend, 5 | SENTRY_IGNORE_ERRORS, 6 | } from "@/utils/core/errors"; 7 | 8 | if (!!process.env.PUBLIC_FRONTEND_SENTRY_DSN) { 9 | Sentry.init({ 10 | environment: process.env.METACULUS_ENV, 11 | dsn: process.env.PUBLIC_FRONTEND_SENTRY_DSN, 12 | tracesSampleRate: 0.1, 13 | ignoreErrors: SENTRY_IGNORE_ERRORS, 14 | beforeSend: beforeSentryAlertSend, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /front_end/src/app/(api)/app-version/route.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used for accessing the app version on server. 3 | */ 4 | export async function GET() { 5 | return Response.json({ 6 | buildId: process.env.BUILD_ID, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /front_end/src/app/(campaigns-registration)/bridgewater/components/results-prize.tsx: -------------------------------------------------------------------------------- 1 | function ResultsPrize() { 2 | return ( 3 |
4 |
5 |
6 | PRIZE POOL 7 |
8 |
9 | $25k 10 |
11 |
12 |
13 | ); 14 | } 15 | 16 | export default ResultsPrize; 17 | -------------------------------------------------------------------------------- /front_end/src/app/(campaigns-registration)/bridgewater/components/typography.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | 3 | export const Heading1: FC = ({ children }) => { 4 | return ( 5 |

6 | {children} 7 |

8 | ); 9 | }; 10 | 11 | export const Heading2: FC = ({ children }) => { 12 | return ( 13 |

14 | {children} 15 |

16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /front_end/src/app/(campaigns-registration)/bridgewater/constants.ts: -------------------------------------------------------------------------------- 1 | export const CAMPAIGN_KEY = "bw_q4_2024"; 2 | export const CAMPAIGN_URL_BASE_PATH = "/bridgewater"; 3 | -------------------------------------------------------------------------------- /front_end/src/app/(campaigns-registration)/bridgewater/layout.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | FacebookPixelTag, 3 | LinkedInInsightTag, 4 | RedditPixelTag, 5 | } from "./components/pixels-tags"; 6 | import CookiesBanner from "../../(main)/components/cookies_banner"; 7 | import Footer from "../../(main)/components/footer"; 8 | 9 | export default async function RootLayout({ 10 | children, 11 | }: Readonly<{ 12 | children: React.ReactNode; 13 | }>) { 14 | return ( 15 |
16 |
{children}
17 | 18 | 19 | 20 | 21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /front_end/src/app/(campaigns-registration)/bridgewater/opengraph-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/(campaigns-registration)/bridgewater/opengraph-image.jpg -------------------------------------------------------------------------------- /front_end/src/app/(campaigns-registration)/bridgewater/q1/components/disclaimer.tsx: -------------------------------------------------------------------------------- 1 | import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | 4 | function Description() { 5 | return ( 6 |
7 |
8 |
9 | 13 | Prize pool is available to US residents only 14 |
15 |
16 |
17 | ); 18 | } 19 | 20 | export default Description; 21 | -------------------------------------------------------------------------------- /front_end/src/app/(campaigns-registration)/bridgewater/q1/components/prize.tsx: -------------------------------------------------------------------------------- 1 | function Prize() { 2 | return ( 3 |
4 |
5 |
6 | PRIZE POOL 7 |
8 |
9 | $25k 10 |
11 |
12 |
13 | ); 14 | } 15 | 16 | export default Prize; 17 | -------------------------------------------------------------------------------- /front_end/src/app/(campaigns-registration)/bridgewater/twitter-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/(campaigns-registration)/bridgewater/twitter-image.jpg -------------------------------------------------------------------------------- /front_end/src/app/(campaigns-registration)/id-verification/components/login-view.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Button from "@/components/ui/button"; 4 | import { useModal } from "@/contexts/modal_context"; 5 | 6 | const LoginView = () => { 7 | const { setCurrentModal } = useModal(); 8 | 9 | return ( 10 |
11 |

12 | Log In first to verify your identity 13 |

14 | 21 |
22 | ); 23 | }; 24 | 25 | export default LoginView; 26 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(home)/components/email_confirmation.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter, useSearchParams } from "next/navigation"; 4 | import { useEffect } from "react"; 5 | 6 | import { sendAnalyticsEvent } from "@/utils/analytics"; 7 | 8 | const EmailConfirmation = () => { 9 | const router = useRouter(); 10 | const searchParams = useSearchParams(); 11 | useEffect(() => { 12 | if (searchParams.get("event") === "emailConfirmed") { 13 | sendAnalyticsEvent("emailConfirmed"); 14 | } 15 | }, [router, searchParams]); 16 | 17 | return null; 18 | }; 19 | 20 | export default EmailConfirmation; 21 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(home)/components/feedback_float.tsx: -------------------------------------------------------------------------------- 1 | import { faPaperPlane } from "@fortawesome/free-solid-svg-icons"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | 4 | const FeedbackFloat: React.FC = () => ( 5 | 12 | 13 | 14 | ); 15 | 16 | export default FeedbackFloat; 17 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(home)/components/topic_link.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { FC, ReactNode } from "react"; 3 | 4 | type Props = { 5 | text: string; 6 | emoji: string | ReactNode; 7 | href: string; 8 | }; 9 | 10 | const TopicLink: FC = ({ href, text, emoji }) => { 11 | return ( 12 | 16 | {emoji} 17 | 18 | {text} 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default TopicLink; 25 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(leaderboards)/components/medal_icon/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import { MedalType } from "@/types/scoring"; 4 | 5 | import BronzeMedal from "./icons/bronze_medal"; 6 | import GoldMedal from "./icons/gold_medal"; 7 | import SilverMedal from "./icons/silver_medal"; 8 | 9 | type Props = { 10 | type: MedalType; 11 | className?: string; 12 | }; 13 | 14 | const MedalIcon: FC = ({ type, className }) => { 15 | switch (type) { 16 | case "gold": 17 | return ; 18 | case "silver": 19 | return ; 20 | case "bronze": 21 | return ; 22 | default: 23 | return null; 24 | } 25 | }; 26 | 27 | export default MedalIcon; 28 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(leaderboards)/contributions/search_params.ts: -------------------------------------------------------------------------------- 1 | export const CONTRIBUTIONS_USER_FILTER = "user"; 2 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard_table/table_header.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | 3 | import cn from "@/utils/core/cn"; 4 | 5 | type Props = { 6 | className?: string; 7 | }; 8 | 9 | const TableHeader: FC> = ({ className, children }) => ( 10 | 16 | {children} 17 | 18 | ); 19 | 20 | export default TableHeader; 21 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(leaderboards)/medals/helpers/medal_title.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from "lodash"; 2 | 3 | import { Medal, MedalProjectType } from "@/types/scoring"; 4 | 5 | export function getMedalDisplayTitle(medal: Medal): string { 6 | if (medal.projectType === MedalProjectType.Tournament) { 7 | return medal.projectName; 8 | } 9 | 10 | const { name } = medal; 11 | const match = (name || "").match(/^(\d{4}): (\d+) year .+$/); 12 | if (!match) { 13 | return ""; 14 | } 15 | 16 | const rawStartYear = match[1]; 17 | const rawDuration = match[2]; 18 | if (isNil(rawStartYear) || isNil(rawDuration)) { 19 | return ""; 20 | } 21 | 22 | const startYear = parseInt(rawStartYear, 10); 23 | const duration = parseInt(rawDuration, 10); 24 | 25 | if (duration === 1) { 26 | return `${startYear}`; 27 | } else { 28 | const endYear = startYear + duration - 1; 29 | return `${startYear} - ${endYear}`; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(leaderboards)/medals/search_params.ts: -------------------------------------------------------------------------------- 1 | export const MEDALS_USER_FILTER = "user"; 2 | export const MEDALS_PATH_FILTER = "path"; 3 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(leaderboards)/search_params.ts: -------------------------------------------------------------------------------- 1 | export const SCORING_CATEGORY_FILTER = "category"; 2 | export const SCORING_YEAR_FILTER = "year"; 3 | export const SCORING_DURATION_FILTER = "duration"; 4 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(tournaments)/tournament/components/index/styles.css: -------------------------------------------------------------------------------- 1 | [data-editor-block-type="image"] img { 2 | @apply !max-h-none; 3 | } 4 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(tournaments)/tournament/components/members/index.tsx: -------------------------------------------------------------------------------- 1 | import ServerProjectsApi from "@/services/api/projects/projects.server"; 2 | import { Tournament } from "@/types/projects"; 3 | 4 | import InviteUsers from "./members_invite"; 5 | import UsersManage from "./members_manage"; 6 | 7 | type Props = { 8 | project: Tournament; 9 | }; 10 | 11 | export default async function Members({ project }: Props) { 12 | const members = await ServerProjectsApi.getMembers(project.id); 13 | 14 | return ( 15 | <> 16 | 17 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/(tournaments)/tournaments/constants/query_params.ts: -------------------------------------------------------------------------------- 1 | export const TOURNAMENTS_SEARCH = "search"; 2 | export const TOURNAMENTS_SORT = "sort_by"; 3 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/about/components/MetacLogo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const MetacLogo = ({ className = "" }) => { 4 | return ( 5 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | export default MetacLogo; 19 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/about/img/person.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/(main)/about/img/person.webp -------------------------------------------------------------------------------- /front_end/src/app/(main)/accounts/change-email/route.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | import ServerProfileApi from "@/services/api/profile/profile.server"; 4 | 5 | export async function GET(request: Request) { 6 | const url = new URL(request.url); 7 | const search_params = Object.fromEntries(url.searchParams.entries()); 8 | 9 | const { token } = search_params; 10 | 11 | if (token) { 12 | await ServerProfileApi.changeEmailConfirm(token); 13 | } 14 | 15 | return redirect("/accounts/settings"); 16 | } 17 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/accounts/signup/components/signup.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | 5 | import { SignupForm } from "@/components/auth/signup"; 6 | import useSearchParams from "@/hooks/use_search_params"; 7 | 8 | export const PARAM_INVITE_TOKEN = "invite_token"; 9 | export const PARAM_EMAIL = "email"; 10 | 11 | const SignUp = () => { 12 | const { params } = useSearchParams(); 13 | 14 | return ( 15 | <> 16 | 20 | 21 | ); 22 | }; 23 | 24 | export default SignUp; 25 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/accounts/social/[provider]/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import ServerAuthApi from "@/services/api/auth/auth.server"; 4 | import { setServerSession } from "@/services/session"; 5 | import { SocialProviderType } from "@/types/auth"; 6 | import { getPublicSettings } from "@/utils/public_settings.server"; 7 | 8 | export async function exchangeSocialOauthCode( 9 | provider: SocialProviderType, 10 | code: string 11 | ) { 12 | const { PUBLIC_APP_URL } = getPublicSettings(); 13 | const response = await ServerAuthApi.exchangeSocialOauthCode( 14 | provider, 15 | code, 16 | `${PUBLIC_APP_URL}/accounts/social/${provider}` 17 | ); 18 | 19 | if (response?.token) { 20 | await setServerSession(response.token); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import ServerAuthApi from "@/services/api/auth/auth.server"; 4 | import serverMiscApi from "@/services/api/misc/misc.server"; 5 | import { ContactForm } from "@/services/api/misc/misc.shared"; 6 | import { getPublicSettings } from "@/utils/public_settings.server"; 7 | 8 | export async function submitContactForm(data: ContactForm) { 9 | return await serverMiscApi.submitContactForm(data); 10 | } 11 | 12 | export async function cancelBulletin(bulletinId: number) { 13 | return await serverMiscApi.cancelBulletin(bulletinId); 14 | } 15 | 16 | export async function getSocialProviders() { 17 | const { PUBLIC_APP_URL } = getPublicSettings(); 18 | return await ServerAuthApi.getSocialProviders( 19 | `${PUBLIC_APP_URL}/accounts/social` 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/aggregation-explorer/page.tsx: -------------------------------------------------------------------------------- 1 | import { getTranslations } from "next-intl/server"; 2 | 3 | import { SearchParams } from "@/types/navigation"; 4 | 5 | import Explorer from "./components/explorer"; 6 | 7 | export default async function AggregationExplorer(props: { 8 | searchParams: Promise; 9 | }) { 10 | const searchParams = await props.searchParams; 11 | const t = await getTranslations(); 12 | return ( 13 |
14 |

15 | {t("aggregationExplorer")} 16 |

17 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/aib/2024/q3/page.tsx: -------------------------------------------------------------------------------- 1 | import { getServerSession } from "@/services/session"; 2 | 3 | import AiBenchmarkingTournamentPage from "../../components/page-view-2024-q3"; 4 | 5 | export default async function Settings() { 6 | const token = await getServerSession(); 7 | 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/aib/2024/q4/page.tsx: -------------------------------------------------------------------------------- 1 | import { getServerSession } from "@/services/session"; 2 | 3 | import AiBenchmarkingTournamentPage from "../../components/page-view-2024-q4"; 4 | 5 | export default async function Settings() { 6 | const token = await getServerSession(); 7 | 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/aib/2025/q1/page.tsx: -------------------------------------------------------------------------------- 1 | import { getServerSession } from "@/services/session"; 2 | 3 | import AiBenchmarkingTournamentPage from "../../components/page-view-2025-q1"; 4 | 5 | export default async function Q1Page() { 6 | const token = await getServerSession(); 7 | 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/aib/page.tsx: -------------------------------------------------------------------------------- 1 | import { getServerSession } from "@/services/session"; 2 | 3 | import AiBenchmarkingTournamentPage from "./components/page-view"; 4 | 5 | export default async function Settings() { 6 | const token = await getServerSession(); 7 | 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/c/[slug]/settings/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { revalidatePath } from "next/cache"; 4 | 5 | import ServerProjectsApi from "@/services/api/projects/projects.server"; 6 | import { CommunityUpdateParams } from "@/services/api/projects/projects.shared"; 7 | 8 | export async function updateCommunity(id: number, data: CommunityUpdateParams) { 9 | const response = await ServerProjectsApi.updateCommunity(id, data); 10 | revalidatePath(`/c/${data.slug}/settings`); 11 | return response; 12 | } 13 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/c/[slug]/settings/schemas.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | import { ProjectPermissions } from "@/types/post"; 4 | import { ProjectVisibility } from "@/types/projects"; 5 | 6 | export const communitySettingsSchema = z.object({ 7 | name: z.string().min(1), 8 | slug: z 9 | .string() 10 | .regex(/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/) 11 | .min(3) 12 | .max(50), 13 | description: z.string(), 14 | default_permission: z.nativeEnum(ProjectPermissions).nullable(), 15 | visibility: z.nativeEnum(ProjectVisibility), 16 | }); 17 | 18 | export type CommunitySettingsSchema = z.infer; 19 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/components/headers/components/mobile_menu_title.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | 3 | import cn from "@/utils/core/cn"; 4 | 5 | type Props = PropsWithChildren & { 6 | className?: string; 7 | }; 8 | 9 | const MobileMenuTitle: FC = ({ children, className }) => { 10 | return ( 11 |
17 | {children} 18 |
19 | ); 20 | }; 21 | 22 | export default MobileMenuTitle; 23 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/components/headers/components/navbar_links.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import NavLink from "@/components/nav_link"; 4 | import cn from "@/utils/core/cn"; 5 | 6 | type Props = { 7 | links: Array<{ label: string; href: string }>; 8 | className?: string; 9 | }; 10 | 11 | const NavbarLinks: FC = ({ links, className }) => { 12 | return ( 13 |
    19 | {links.map((link) => ( 20 |
  • 21 | 25 | {link.label} 26 | 27 |
  • 28 | ))} 29 |
30 | ); 31 | }; 32 | 33 | export default NavbarLinks; 34 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/components/headers/global_header.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { usePathname } from "next/navigation"; 4 | import { FC } from "react"; 5 | 6 | import { getWithDefaultHeader } from "@/utils/navigation"; 7 | 8 | import Header from "./header"; 9 | 10 | const GlobalHeader: FC = () => { 11 | const pathname = usePathname(); 12 | const withDefaultHeader = getWithDefaultHeader(pathname); 13 | 14 | if (withDefaultHeader) { 15 | return
; 16 | } 17 | 18 | return null; 19 | }; 20 | 21 | export default GlobalHeader; 22 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { usePathname } from "next/navigation"; 4 | 5 | import Header from "@/app/(main)/components/headers/header"; 6 | import GlobalErrorBoundary from "@/components/global_error_boundary"; 7 | import { getWithDefaultHeader } from "@/utils/navigation"; 8 | 9 | export default function RootError(props: { 10 | error: Error & { digest?: string }; 11 | reset: () => void; 12 | }) { 13 | const pathname = usePathname(); 14 | const withDefaultHeader = getWithDefaultHeader(pathname); 15 | 16 | return ( 17 | <> 18 | {/* Ensure header is always visible in error view */} 19 | {/* Even if it is dynamically defined on the route */} 20 | {!withDefaultHeader &&
} 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/experiments/components/experiment_candle_bar_graph/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import { Candle } from "@/types/experiments"; 4 | 5 | import CandleBar from "./candle_bar"; 6 | 7 | type Props = { 8 | candles: Candle[]; 9 | }; 10 | 11 | const ExperimentCandleBarGraph: FC = ({ candles }) => { 12 | return ( 13 |
14 |
15 | {candles.map((candle, index) => ( 16 | 17 | ))} 18 |
19 |
20 | ); 21 | }; 22 | 23 | export default ExperimentCandleBarGraph; 24 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/experiments/components/experiment_map/raw_map/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC, forwardRef, SVGProps } from "react"; 2 | 3 | import { MapType } from "@/types/experiments"; 4 | 5 | import RawOtherMap from "./raw_other_map"; 6 | import RawUsMap from "./raw_us_map"; 7 | 8 | type Props = { 9 | mapType: MapType; 10 | } & SVGProps; 11 | 12 | const RawMap: FC = forwardRef(({ mapType, ...props }, ref) => { 13 | switch (mapType) { 14 | case MapType.US: 15 | return ; 16 | // testing other svg path 17 | // TODO: remove this 18 | case MapType.Other: 19 | return ; 20 | default: 21 | return null; 22 | } 23 | }); 24 | RawMap.displayName = "RawMap"; 25 | 26 | export default RawMap; 27 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/experiments/components/experiment_map/styles.css: -------------------------------------------------------------------------------- 1 | .mapStroke { 2 | @apply stroke-blue-900 dark:stroke-blue-900-dark; 3 | } 4 | 5 | .mapLabelText { 6 | @apply fill-white stroke-black stroke-3 font-sans; 7 | stroke-linecap: butt; 8 | stroke-linejoin: round; 9 | font-size: 13px; 10 | paint-order: stroke; 11 | } 12 | 13 | .hoveredMapArea { 14 | @apply stroke-blue-900 stroke-3 dark:stroke-blue-900-dark; 15 | transition: stroke-width 0.2s ease; 16 | } 17 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/experiments/elections/components/card_forecast.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import ForecastCard from "@/components/forecast_card"; 4 | import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; 5 | import ServerPostsApi from "@/services/api/posts/posts.server"; 6 | import { TimelineChartZoomOption } from "@/types/charts"; 7 | 8 | type Props = { 9 | postId: number; 10 | }; 11 | 12 | const CardForecast: FC = async ({ postId }) => { 13 | const post = await ServerPostsApi.getPostAnonymous(postId, { 14 | next: { revalidate: 900 }, 15 | }); 16 | if (!post) return null; 17 | 18 | return ( 19 | 23 | ); 24 | }; 25 | 26 | export default WithServerComponentErrorBoundary(CardForecast); 27 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/experiments/elections/opengraph-image.alt.txt: -------------------------------------------------------------------------------- 1 | Elections Map 2 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/experiments/elections/opengraph-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/(main)/experiments/elections/opengraph-image.jpg -------------------------------------------------------------------------------- /front_end/src/app/(main)/experiments/elections/twitter-image.alt.txt: -------------------------------------------------------------------------------- 1 | Elections Map 2 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/experiments/elections/twitter-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/(main)/experiments/elections/twitter-image.jpg -------------------------------------------------------------------------------- /front_end/src/app/(main)/experiments/testing/page.tsx: -------------------------------------------------------------------------------- 1 | import OtherMap from "./components/other_map"; 2 | 3 | export default function OtherChartMap() { 4 | return ( 5 |
6 |
7 |

Sample of customising render output for experiment charts

8 |
9 |
10 | 11 |
12 |
13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/home/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | export default async function Home() { 4 | return redirect("/"); 5 | } 6 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/not-found/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation"; 2 | 3 | // ensure 404 UI is rendered under (main) layout 4 | export default function NotFoundRoute() { 5 | notFound(); 6 | } 7 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/notebooks/components/notebook_editor/editor.css: -------------------------------------------------------------------------------- 1 | .mdxeditor-toolbar { 2 | @apply max-md:top-28 !important; /*override sticky position to display it under mobile navbar*/ 3 | } 4 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/notebooks/constants/page_sections.ts: -------------------------------------------------------------------------------- 1 | export const NOTEBOOK_TITLE = "notebook-title"; 2 | export const NOTEBOOK_COMMENTS_TITLE = "notebook-comments-title"; 3 | export const NOTEBOOK_CONTENT_SECTION = "notebook-content-section"; 4 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/pro-forecasters/assets/Isabel Juniewicz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/(main)/pro-forecasters/assets/Isabel Juniewicz.png -------------------------------------------------------------------------------- /front_end/src/app/(main)/pro-forecasters/assets/LinkedIn Pro Forecasters.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/pro-forecasters/assets/LinkedInIcon.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps, memo } from "react"; 2 | 3 | const LinkedInIcon = (props: SVGProps) => ( 4 | 11 | 15 | 16 | ); 17 | 18 | export default memo(LinkedInIcon); 19 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/pro-forecasters/assets/Peter Wildeford.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/(main)/pro-forecasters/assets/Peter Wildeford.png -------------------------------------------------------------------------------- /front_end/src/app/(main)/pro-forecasters/assets/Philipp Schoenegger.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/(main)/pro-forecasters/assets/Philipp Schoenegger.jpeg -------------------------------------------------------------------------------- /front_end/src/app/(main)/pro-forecasters/assets/Scott Eastman.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/(main)/pro-forecasters/assets/Scott Eastman.jpeg -------------------------------------------------------------------------------- /front_end/src/app/(main)/pro-forecasters/components/info_section.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from "react"; 2 | 3 | import cn from "@/utils/core/cn"; 4 | 5 | type Props = { 6 | title: string; 7 | titleIcon?: ReactNode; 8 | info: string | ReactNode; 9 | size?: "md" | "lg"; 10 | }; 11 | 12 | const ProForecastersInfoSection: FC = ({ 13 | title, 14 | info, 15 | size = "md", 16 | titleIcon = null, 17 | }) => { 18 | return ( 19 | <> 20 |
21 | {titleIcon} 22 |

28 | {title} 29 |

30 |
31 |

{info}

32 | 33 | ); 34 | }; 35 | 36 | export default ProForecastersInfoSection; 37 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/pro-forecasters/components/link.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { FC, PropsWithChildren } from "react"; 3 | 4 | type Props = { 5 | href: string; 6 | internal?: boolean; 7 | }; 8 | 9 | const ProForecasterLink: FC> = ({ 10 | href, 11 | internal = false, 12 | children, 13 | }) => { 14 | const Container = internal ? Link : "a"; 15 | 16 | return ( 17 | 22 | {children} 23 | 24 | ); 25 | }; 26 | 27 | export default ProForecasterLink; 28 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/pro-forecasters/constants/expression_of_interest_form.ts: -------------------------------------------------------------------------------- 1 | export const EXPRESSION_OF_INTEREST_FORM_URL = 2 | "https://docs.google.com/forms/d/e/1FAIpQLSdebfiVQVoYj4WakqvwY-08k0x9Sfy8HMAUsKPE707YEChGlQ/viewform"; 3 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/pro-forecasters/types/index.ts: -------------------------------------------------------------------------------- 1 | import { StaticImageData } from "next/image"; 2 | import { ReactNode } from "react"; 3 | 4 | export type ProForecaster = { 5 | id: string; 6 | name: string; 7 | description: ReactNode; 8 | linkedInUrl: string | null; 9 | image: StaticImageData; 10 | }; 11 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/[id]/components/question_embed_button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTranslations } from "next-intl"; 4 | import { FC } from "react"; 5 | 6 | import Button from "@/components/ui/button"; 7 | import useEmbedModalContext from "@/contexts/embed_modal_context"; 8 | 9 | const QuestionEmbedButton: FC = () => { 10 | const { updateIsOpen } = useEmbedModalContext(); 11 | const t = useTranslations(); 12 | 13 | return ( 14 | 21 | ); 22 | }; 23 | 24 | export default QuestionEmbedButton; 25 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/[id]/components/question_embed_modal.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { FC } from "react"; 3 | 4 | import EmbedModal from "@/components/embed_modal"; 5 | import useEmbedModalContext from "@/contexts/embed_modal_context"; 6 | import { useEmbedUrl } from "@/hooks/share"; 7 | 8 | type Props = { 9 | postId: number; 10 | postTitle?: string; 11 | }; 12 | 13 | const QuestionEmbedModal: FC = ({ postId, postTitle }) => { 14 | const embedUrl = useEmbedUrl(`/questions/embed/${postId}`); 15 | const { isOpen, updateIsOpen } = useEmbedModalContext(); 16 | 17 | if (!embedUrl) { 18 | return null; 19 | } 20 | 21 | return ( 22 | 31 | ); 32 | }; 33 | 34 | export default QuestionEmbedModal; 35 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/[id]/components/reveal_cp_button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useTranslations } from "next-intl"; 3 | import React, { FC } from "react"; 4 | 5 | import Button from "@/app/(main)/about/components/Button"; 6 | import { useHideCP } from "@/contexts/cp_context"; 7 | import cn from "@/utils/core/cn"; 8 | 9 | type Props = { 10 | className?: string; 11 | }; 12 | 13 | const RevealCPButton: FC = ({ className }) => { 14 | const { setCurrentHideCP } = useHideCP(); 15 | const t = useTranslations(); 16 | 17 | return ( 18 |
19 |
{t("CPIsHidden")}
20 | 23 |
24 | ); 25 | }; 26 | 27 | export default RevealCPButton; 28 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/[id]/components/sidebar/news_match/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; 4 | import ServerPostsApi from "@/services/api/posts/posts.server"; 5 | 6 | import NewsMatchDrawer from "./news_match_drawer"; 7 | 8 | interface Props { 9 | questionId: number; 10 | } 11 | 12 | const NewsMatch: FC = async ({ questionId }) => { 13 | const articles = await ServerPostsApi.getRelatedNews(questionId); 14 | 15 | if (articles.length > 0) { 16 | return ; 17 | } else { 18 | return null; 19 | } 20 | }; 21 | 22 | export default WithServerComponentErrorBoundary(NewsMatch); 23 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/[id]/components/sidebar/similar_questions/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; 4 | import ServerPostsApi from "@/services/api/posts/posts.server"; 5 | 6 | import SimilarQuestionsDrawer from "./similar_questions_drawer"; 7 | 8 | type Props = { 9 | post_id: number; 10 | }; 11 | 12 | const SimilarQuestions: FC = async ({ post_id }) => { 13 | const questions = await ServerPostsApi.getSimilarPosts(post_id); 14 | 15 | if (!questions.length) return null; 16 | 17 | return ; 18 | }; 19 | 20 | export default WithServerComponentErrorBoundary(SimilarQuestions) as FC; 21 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/[id]/search_params.ts: -------------------------------------------------------------------------------- 1 | export const SLUG_POST_SUB_QUESTION_ID = "sub-question"; 2 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/components/forecaster_counter.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "next-intl"; 2 | import { FC } from "react"; 3 | 4 | import cn from "@/utils/core/cn"; 5 | 6 | type Props = { 7 | forecasters?: number; 8 | className?: string; 9 | }; 10 | 11 | const ForecastersCounter: FC = ({ forecasters, className }) => { 12 | const t = useTranslations(); 13 | if (!forecasters) { 14 | return null; 15 | } 16 | return ( 17 |
23 | {forecasters} {t("forecastersWithCount", { count: forecasters })} 24 |
25 | ); 26 | }; 27 | 28 | export default ForecastersCounter; 29 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/create/components/question_draft_cleanup.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useEffect } from "react"; 3 | 4 | import { cleanupQuestionDrafts } from "@/utils/drafts/questionForm"; 5 | 6 | const QuestionDraftCleanup = () => { 7 | useEffect(() => { 8 | cleanupQuestionDrafts(); 9 | }, []); 10 | 11 | return null; 12 | }; 13 | 14 | export default QuestionDraftCleanup; 15 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/create/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { SearchParams } from "@/types/navigation"; 2 | import { PostWithForecasts } from "@/types/post"; 3 | 4 | export const extractMode = ( 5 | searchParams: SearchParams, 6 | post?: PostWithForecasts | null 7 | ) => { 8 | let mode = post ? "edit" : "create"; 9 | const paramsMode = searchParams["mode"]; 10 | if ( 11 | typeof paramsMode === "string" && 12 | ["create", "edit"].includes(paramsMode) 13 | ) { 14 | mode = paramsMode; 15 | } 16 | return mode as "create" | "edit"; 17 | }; 18 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/discovery/components/section.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | 3 | type Props = { 4 | title: string; 5 | }; 6 | 7 | const DiscoverySection: FC> = ({ 8 | title, 9 | children, 10 | }) => ( 11 |
12 |
13 |

{title}

14 |
15 | {children} 16 |
17 | ); 18 | 19 | export default DiscoverySection; 20 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/discovery/components/tag_filters.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTranslations } from "next-intl"; 4 | import { FC } from "react"; 5 | 6 | import { TAGS_TEXT_SEARCH_FILTER } from "@/app/(main)/questions/discovery/constants/tags_feed"; 7 | import SearchInput from "@/components/search_input"; 8 | import useSearchInputState from "@/hooks/use_search_input_state"; 9 | 10 | const TagFilters: FC = () => { 11 | const t = useTranslations(); 12 | 13 | const [searchQuery, setSearchQuery] = useSearchInputState( 14 | TAGS_TEXT_SEARCH_FILTER, 15 | { modifySearchParams: true } 16 | ); 17 | 18 | return ( 19 | setSearchQuery(event.target.value)} 22 | onErase={() => setSearchQuery("")} 23 | placeholder={t("tagSearchPlaceholder")} 24 | className="max-w-lg" 25 | /> 26 | ); 27 | }; 28 | 29 | export default TagFilters; 30 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/discovery/constants/tags_feed.ts: -------------------------------------------------------------------------------- 1 | export const TAGS_TEXT_SEARCH_FILTER = "search"; 2 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/track-record/components/track_record_chart_hero.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "next-intl"; 2 | import React, { FC } from "react"; 3 | 4 | type Props = { 5 | totalQuestions: string; 6 | averageScore: string; 7 | }; 8 | 9 | const TrackRecordChartHero: FC = ({ totalQuestions, averageScore }) => { 10 | const t = useTranslations(); 11 | 12 | return ( 13 |
14 | {t("totalQuestions")} 15 | 16 | {" " + totalQuestions} 17 | 18 | {t("averageScore")} 19 | 20 | {" " + averageScore} 21 | 22 |
23 | ); 24 | }; 25 | 26 | export default TrackRecordChartHero; 27 | -------------------------------------------------------------------------------- /front_end/src/app/(main)/questions/track-record/page.tsx: -------------------------------------------------------------------------------- 1 | import { getTranslations } from "next-intl/server"; 2 | import { Suspense } from "react"; 3 | 4 | import LoadingIndicator from "@/components/ui/loading_indicator"; 5 | 6 | import AsyncTrackRecord from "./components/async_track_record"; 7 | 8 | export default async function TrackRecordPage() { 9 | const t = await getTranslations(); 10 | 11 | return ( 12 |
13 |
14 |

{t("metaculusTrackRecord")}

15 | }> 16 | 17 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /front_end/src/app/alpha-auth/schemas.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const devLoginSchema = z.object({ 4 | token: z.string(), 5 | }); 6 | 7 | export type DevLoginSchema = z.infer; 8 | -------------------------------------------------------------------------------- /front_end/src/app/assets/images/logo_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/assets/images/logo_placeholder.png -------------------------------------------------------------------------------- /front_end/src/app/assets/images/tournament.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/assets/images/tournament.png -------------------------------------------------------------------------------- /front_end/src/app/error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import GlobalErrorBoundary from "@/components/global_error_boundary"; 4 | 5 | export default function RootError(props: { 6 | error: Error & { digest?: string }; 7 | reset: () => void; 8 | }) { 9 | return ; 10 | } 11 | -------------------------------------------------------------------------------- /front_end/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/front_end/src/app/favicon.ico -------------------------------------------------------------------------------- /front_end/src/app/global-error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import NextError from "next/error"; 4 | import { useEffect } from "react"; 5 | 6 | import { logError } from "@/utils/core/errors"; 7 | 8 | export default function GlobalError({ 9 | error, 10 | }: { 11 | error: Error & { digest?: string }; 12 | }) { 13 | useEffect(() => { 14 | logError(error); 15 | }, [error]); 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /front_end/src/app/robots.ts: -------------------------------------------------------------------------------- 1 | import type { MetadataRoute } from "next"; 2 | 3 | import { getPublicSettings } from "@/utils/public_settings.server"; 4 | export const dynamic = "force-dynamic"; 5 | 6 | const { PUBLIC_DISALLOW_ALL_BOTS } = getPublicSettings(); 7 | 8 | export default function robots(): MetadataRoute.Robots { 9 | let allowRules: MetadataRoute.Robots["rules"] = { 10 | allow: "/", 11 | }; 12 | 13 | if (PUBLIC_DISALLOW_ALL_BOTS) { 14 | allowRules = { 15 | disallow: "/", 16 | }; 17 | } 18 | return { 19 | rules: { 20 | userAgent: "*", 21 | ...allowRules, 22 | }, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /front_end/src/components/auth/hooks/usePostLoginActionHandler.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/navigation"; 2 | import { useCallback } from "react"; 3 | 4 | import { PostLoginAction } from "@/app/(main)/accounts/actions"; 5 | 6 | const usePostLoginActionHandler = () => { 7 | const router = useRouter(); 8 | 9 | return useCallback( 10 | (action: PostLoginAction | undefined) => { 11 | if (!action) { 12 | return; 13 | } 14 | 15 | switch (action.type) { 16 | case "redirect": 17 | router.replace(action.payload); 18 | break; 19 | default: 20 | console.warn("Unknown post login action type", action); 21 | break; 22 | } 23 | }, 24 | [router] 25 | ); 26 | }; 27 | 28 | export default usePostLoginActionHandler; 29 | -------------------------------------------------------------------------------- /front_end/src/components/charts/cp_reveal_time_overflow.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, PropsWithChildren } from "react"; 2 | 3 | import cn from "@/utils/core/cn"; 4 | 5 | type Props = { 6 | className?: string; 7 | textClassName?: string; 8 | }; 9 | 10 | const ChartOverflowContainer: FC> = ({ 11 | className, 12 | textClassName, 13 | children, 14 | }) => { 15 | return ( 16 |
22 |

28 | {children} 29 |

30 |
31 | ); 32 | }; 33 | 34 | export default ChartOverflowContainer; 35 | -------------------------------------------------------------------------------- /front_end/src/components/charts/primitives/x_tick_label.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentProps, FC } from "react"; 2 | import { VictoryLabel } from "victory"; 3 | 4 | type Props = ComponentProps & { 5 | chartWidth: number; 6 | withCursor?: boolean; 7 | fontSize?: number; 8 | }; 9 | 10 | const XTickLabel: FC = ({ 11 | chartWidth, 12 | withCursor, 13 | fontSize = 10, 14 | ...props 15 | }) => { 16 | const estimatedTextWidth = 17 | ((props.text?.toString().length ?? 0) * fontSize) / 2; 18 | const overlapsRightEdge = withCursor 19 | ? (props.x ?? 0) > chartWidth - estimatedTextWidth 20 | : (props.x ?? 0) > chartWidth - 12; 21 | 22 | if (overlapsRightEdge) { 23 | return null; 24 | } 25 | 26 | return ( 27 | 34 | ); 35 | }; 36 | 37 | export default XTickLabel; 38 | -------------------------------------------------------------------------------- /front_end/src/components/choice_icon.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { FC } from "react"; 3 | 4 | import { METAC_COLORS } from "@/constants/colors"; 5 | import useAppTheme from "@/hooks/use_app_theme"; 6 | import useMounted from "@/hooks/use_mounted"; 7 | import { ThemeColor } from "@/types/theme"; 8 | import cn from "@/utils/core/cn"; 9 | 10 | type Props = { 11 | color?: ThemeColor; 12 | className?: string; 13 | }; 14 | 15 | const ChoiceIcon: FC = ({ 16 | color = METAC_COLORS["mc-option"]["1"], 17 | className, 18 | }) => { 19 | const { getThemeColor } = useAppTheme(); 20 | const mounted = useMounted(); 21 | 22 | return ( 23 |
27 | ); 28 | }; 29 | 30 | export default ChoiceIcon; 31 | -------------------------------------------------------------------------------- /front_end/src/components/communities_feed/styles.css: -------------------------------------------------------------------------------- 1 | .community p, 2 | .community ul, 3 | .community ol { 4 | @apply block text-blue-900/60 dark:text-blue-900-dark/60; 5 | } 6 | 7 | .community a { 8 | @apply text-blue-900/60 dark:text-blue-900-dark/60; 9 | } 10 | 11 | .community table .sticky-column { 12 | @apply sticky left-0 bg-gray-50 dark:bg-gray-100-dark; 13 | } 14 | 15 | .community table thead td, 16 | .community table thead th { 17 | @apply border-b border-gray-300 pr-2 text-blue-900/60 dark:border-gray-300-dark dark:text-blue-900-dark/60; 18 | } 19 | 20 | .community blockquote { 21 | @apply my-4 border-s-4 border-gray-300 bg-gray-100 p-4 text-blue-900/60 dark:border-gray-300-dark dark:bg-gray-200-dark dark:text-blue-900-dark/60; 22 | } 23 | -------------------------------------------------------------------------------- /front_end/src/components/conditional_tile/icons/Arrow.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGProps } from "react"; 2 | 3 | import cn from "@/utils/core/cn"; 4 | 5 | type Props = SVGProps & { 6 | didHappen?: boolean; 7 | }; 8 | 9 | const Arrow: FC = ({ didHappen, ...props }) => { 10 | return ( 11 | 20 | 26 | 27 | ); 28 | }; 29 | 30 | export default Arrow; 31 | -------------------------------------------------------------------------------- /front_end/src/components/conditional_tile/icons/DisabledArrow.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGProps } from "react"; 2 | 3 | const DisabledArrow: FC> = (props) => { 4 | return ( 5 | 14 | 20 | 21 | ); 22 | }; 23 | 24 | export default DisabledArrow; 25 | -------------------------------------------------------------------------------- /front_end/src/components/consumer_post_card/group_forecast_card/date_forecast_card/styles.scss: -------------------------------------------------------------------------------- 1 | .DateForecastCard svg { 2 | overflow: visible; 3 | } 4 | -------------------------------------------------------------------------------- /front_end/src/components/consumer_post_card/time_series_chart/styles.scss: -------------------------------------------------------------------------------- 1 | .TimeSeriesChart svg { 2 | overflow: visible; 3 | } 4 | -------------------------------------------------------------------------------- /front_end/src/components/cp_reveal_time/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import dynamic from "next/dynamic"; 3 | 4 | const CpRevealTime = dynamic(() => import("./cp_reveal_time"), { ssr: false }); 5 | 6 | export default CpRevealTime; 7 | -------------------------------------------------------------------------------- /front_end/src/components/detailed_question_card/detailed_question_card/error_boundary.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { FC, PropsWithChildren } from "react"; 3 | import { ErrorBoundary, FallbackProps } from "react-error-boundary"; 4 | 5 | const Fallback: FC = ({ error }) => { 6 | return ( 7 |
11 |

Something went wrong:

12 |
 {error.message}
13 |
14 | ); 15 | }; 16 | 17 | const DetailsQuestionCardErrorBoundary: FC = ({ 18 | children, 19 | }) => { 20 | return {children}; 21 | }; 22 | 23 | export default DetailsQuestionCardErrorBoundary; 24 | -------------------------------------------------------------------------------- /front_end/src/components/forecast_maker/container.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslations } from "next-intl"; 2 | import { FC, PropsWithChildren } from "react"; 3 | 4 | import cn from "@/utils/core/cn"; 5 | 6 | type Props = { 7 | className?: string; 8 | }; 9 | 10 | const ForecastMakerContainer: FC> = ({ 11 | className, 12 | children, 13 | }) => { 14 | const t = useTranslations(); 15 | return ( 16 |
23 |

24 | {t("makePrediction")} 25 |

26 |
{children}
27 |
28 | ); 29 | }; 30 | 31 | export default ForecastMakerContainer; 32 | -------------------------------------------------------------------------------- /front_end/src/components/html_content/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import dynamic from "next/dynamic"; 3 | 4 | const HtmlContent = dynamic(() => import("./html_content"), { ssr: false }); 5 | 6 | export default HtmlContent; 7 | -------------------------------------------------------------------------------- /front_end/src/components/icons/resolution.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SVGProps } from "react"; 3 | 4 | const ResolutionIcon = (props: SVGProps) => ( 5 | 12 | 17 | 18 | ); 19 | export default ResolutionIcon; 20 | -------------------------------------------------------------------------------- /front_end/src/components/katex_renderer/styles.css: -------------------------------------------------------------------------------- 1 | span.katex { 2 | @apply !whitespace-normal; 3 | } 4 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/embedded_question/styles.scss: -------------------------------------------------------------------------------- 1 | .ForecastCard { 2 | @apply bg-blue-200 hover:shadow-none dark:bg-blue-200-dark; 3 | } 4 | 5 | .ConditionalSummary { 6 | &-conditional-label { 7 | @apply bg-blue-200 dark:bg-blue-200-dark; 8 | } 9 | 10 | &-condition { 11 | .ConditionalSummary-card { 12 | @apply bg-blue-200 dark:bg-blue-200-dark; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/embedded_twitter/helpers.ts: -------------------------------------------------------------------------------- 1 | const TWITTER_REGEX = 2 | /https?:\/\/(?:twitter|vxtwitter|x)\.com\/(?:#!\/)?\w+\/status(?:es)?\/(\d+)/g; 3 | 4 | export const transformTwitterLinks = (markdown: string): string => { 5 | const matches = markdown.match(TWITTER_REGEX); 6 | 7 | if (!matches) { 8 | return markdown; 9 | } 10 | 11 | const uniqueTweetIds = new Set(); 12 | matches.forEach((match) => { 13 | const tweetIdMatch = match.match(/(\d+)$/); 14 | if (tweetIdMatch && tweetIdMatch[1]) { 15 | uniqueTweetIds.add(tweetIdMatch[1]); 16 | } 17 | }); 18 | 19 | const tweetComponents = Array.from(uniqueTweetIds) 20 | .map((id) => ``) 21 | .join("\n"); 22 | 23 | return `${markdown}\n\n${tweetComponents}`; 24 | }; 25 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/embedded_twitter/index.tsx: -------------------------------------------------------------------------------- 1 | import { JsxComponentDescriptor } from "@mdxeditor/editor"; 2 | import { Tweet } from "react-tweet"; 3 | 4 | import createEditorComponent from "../createJsxComponent"; 5 | 6 | export const EMBEDDED_TWITTER_COMPONENT_NAME = "Tweet"; 7 | 8 | export const tweetDescriptor: JsxComponentDescriptor = { 9 | name: EMBEDDED_TWITTER_COMPONENT_NAME, 10 | props: [{ name: "id", type: "string", required: true }], 11 | kind: "text", 12 | hasChildren: false, 13 | Editor: createEditorComponent(Tweet), 14 | }; 15 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { MDXEditorMethods } from "@mdxeditor/editor"; 4 | import dynamic from "next/dynamic"; 5 | import React, { forwardRef } from "react"; 6 | 7 | import { MarkdownEditorProps } from "./initialized_editor"; 8 | 9 | const Editor = dynamic(() => import("./initialized_editor"), { 10 | ssr: false, 11 | }); 12 | 13 | const MarkdownEditor = forwardRef( 14 | (props, ref) => 15 | ); 16 | MarkdownEditor.displayName = "MarkdownEditor"; 17 | 18 | export default MarkdownEditor; 19 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/plugins/equation/lexical_equation_visitor.ts: -------------------------------------------------------------------------------- 1 | import { LexicalExportVisitor } from "@mdxeditor/editor"; 2 | import { Math } from "mdast-util-math"; 3 | 4 | import { $isEquationNode, EquationNode } from "./equation_node"; 5 | 6 | export const LexicalEquationVisitor: LexicalExportVisitor = 7 | { 8 | testLexicalNode: $isEquationNode, 9 | visitLexicalNode({ actions, mdastParent, lexicalNode }) { 10 | actions.appendToParent(mdastParent, lexicalNode.getMdastNode()); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/plugins/equation/mdast_equation_visitor.ts: -------------------------------------------------------------------------------- 1 | import { MdastImportVisitor } from "@mdxeditor/editor"; 2 | import { InlineMath, Math } from "mdast-util-math"; 3 | 4 | import { $createEquationNode } from "./equation_node"; 5 | 6 | export const MdastEquationVisitor: MdastImportVisitor = { 7 | testNode: "math", 8 | visitNode({ mdastNode, actions }) { 9 | actions.addAndStepInto($createEquationNode(mdastNode.value, false)); 10 | }, 11 | }; 12 | 13 | export const MdastInlineEquationVisitor: MdastImportVisitor = { 14 | testNode: "inlineMath", 15 | visitNode({ mdastNode, actions }) { 16 | actions.addAndStepInto($createEquationNode(mdastNode.value, true)); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/plugins/link/AutoLinkPlugin.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AutoLinkPlugin, 3 | createLinkMatcherWithRegExp, 4 | } from "@lexical/react/LexicalAutoLinkPlugin.js"; 5 | import React, { FC } from "react"; 6 | 7 | const URL_REGEX = 8 | /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/; 9 | 10 | const EMAIL_REGEX = 11 | /(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/; 12 | 13 | const MATCHERS = [ 14 | createLinkMatcherWithRegExp(URL_REGEX, (text) => { 15 | return text.startsWith("http") ? text : `https://${text}`; 16 | }), 17 | createLinkMatcherWithRegExp(EMAIL_REGEX, (text) => { 18 | return `mailto:${text}`; 19 | }), 20 | ]; 21 | 22 | export const LexicalAutoLinkPlugin: FC = () => { 23 | return ; 24 | }; 25 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/plugins/link/LexicalLinkVisitor.ts: -------------------------------------------------------------------------------- 1 | import { $isLinkNode, LinkNode } from "@lexical/link"; 2 | import { LexicalExportVisitor } from "@mdxeditor/editor"; 3 | import * as Mdast from "mdast"; 4 | 5 | export const LexicalLinkVisitor: LexicalExportVisitor = { 6 | testLexicalNode: $isLinkNode, 7 | visitLexicalNode: ({ lexicalNode, actions }) => { 8 | actions.addAndStepInto("link", { 9 | url: lexicalNode.getURL(), 10 | title: lexicalNode.getTitle(), 11 | }); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/plugins/link/MdastLinkVisitor.ts: -------------------------------------------------------------------------------- 1 | import { $createLinkNode } from "@lexical/link"; 2 | import { MdastImportVisitor } from "@mdxeditor/editor"; 3 | import * as Mdast from "mdast"; 4 | 5 | export const createMdastLinkVisitor = ( 6 | withUgcLinks: boolean 7 | ): MdastImportVisitor => ({ 8 | testNode: "link", 9 | visitNode({ mdastNode, actions }) { 10 | actions.addAndStepInto( 11 | $createLinkNode(mdastNode.url, { 12 | title: mdastNode.title, 13 | target: "_blank", 14 | ...(withUgcLinks ? { rel: "ugc" } : {}), 15 | }) 16 | ); 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/plugins/mentions/LexicalBeautifulMentionVisitor.ts: -------------------------------------------------------------------------------- 1 | import { LexicalExportVisitor } from "@mdxeditor/editor"; 2 | import { 3 | $isBeautifulMentionNode, 4 | BeautifulMentionNode, 5 | } from "lexical-beautiful-mentions"; 6 | import * as Mdast from "mdast"; 7 | 8 | export const LexicalBeautifulMentionVisitor: LexicalExportVisitor< 9 | BeautifulMentionNode, 10 | Mdast.Link 11 | > = { 12 | testLexicalNode: $isBeautifulMentionNode, 13 | visitLexicalNode: ({ lexicalNode, actions }) => { 14 | const value = lexicalNode.getValue(); 15 | 16 | actions.addAndStepInto("text", { 17 | value: `@(${value})`, 18 | }); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/plugins/mentions/components/mention.tsx: -------------------------------------------------------------------------------- 1 | import { BeautifulMentionComponentProps } from "lexical-beautiful-mentions"; 2 | import { forwardRef } from "react"; 3 | 4 | const CustomMentionComponent = forwardRef< 5 | HTMLAnchorElement, 6 | BeautifulMentionComponentProps 7 | >(({ trigger, value, data: myData, children, className, ...other }, ref) => { 8 | // combination on inline-block and block is used as a workaround for Chrome bug on Android 9 | // when element is duplicated when typing 10 | // github.com/facebook/lexical/issues/4636 11 | // https://issues.chromium.org/issues/41254240 12 | return ( 13 | 14 | 15 | {trigger}({value}) 16 | 17 | 18 | ); 19 | }); 20 | CustomMentionComponent.displayName = "CustomMentionComponent"; 21 | 22 | export default CustomMentionComponent; 23 | -------------------------------------------------------------------------------- /front_end/src/components/markdown_editor/plugins/mentions/types.ts: -------------------------------------------------------------------------------- 1 | export type MentionItem = { 2 | value: string; 3 | }; 4 | -------------------------------------------------------------------------------- /front_end/src/components/onboarding/onboarding_loading.tsx: -------------------------------------------------------------------------------- 1 | import { faSpinner } from "@fortawesome/free-solid-svg-icons"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | import React from "react"; 4 | 5 | const OnboardingLoading: React.FC = () => { 6 | return ( 7 |
8 |
9 | 14 |
15 |
16 | ); 17 | }; 18 | 19 | export default OnboardingLoading; 20 | -------------------------------------------------------------------------------- /front_end/src/components/onboarding/verbal_forecast.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface VerbalForecastProps { 4 | forecast: number; 5 | } 6 | 7 | const getVerbalDescription = (forecast: number): string => { 8 | if (forecast < 0.4) return "Unlikely"; 9 | if (forecast < 0.6) return "About Even"; 10 | if (forecast < 0.8) return "Likely"; 11 | return "Very Likely"; 12 | }; 13 | 14 | const VerbalForecast: React.FC = ({ forecast }) => { 15 | return ( 16 | 17 | {getVerbalDescription(forecast)} 18 | 19 | ); 20 | }; 21 | 22 | export default VerbalForecast; 23 | -------------------------------------------------------------------------------- /front_end/src/components/post_actions/index.tsx: -------------------------------------------------------------------------------- 1 | export { SharePostMenu } from "./share_post_menu"; 2 | export { PostDropdownMenu } from "./post_dropdown_menu"; 3 | -------------------------------------------------------------------------------- /front_end/src/components/post_card/error_boundary.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { FC, PropsWithChildren } from "react"; 3 | import { ErrorBoundary, FallbackProps } from "react-error-boundary"; 4 | 5 | const Fallback: FC = ({ error }) => { 6 | return ( 7 |
8 |

An error has occurred when rendering the feed card.

9 |
 {error.message}
10 |
11 | ); 12 | }; 13 | 14 | const PostCardErrorBoundary: FC = ({ children }) => { 15 | return {children}; 16 | }; 17 | 18 | export default PostCardErrorBoundary; 19 | -------------------------------------------------------------------------------- /front_end/src/components/post_subscribe/subscription_types_customisation/types.ts: -------------------------------------------------------------------------------- 1 | import { Post, PostSubscriptionConfigItem } from "@/types/post"; 2 | 3 | type SubscriptionChangeHandler< 4 | T, 5 | K extends keyof T, 6 | I = never, 7 | IK extends keyof I = never, 8 | > = ( 9 | name: K | IK, 10 | value: T[K] | (I extends never ? never : I[IK]), 11 | index?: number 12 | ) => void; 13 | 14 | export type SubscriptionSectionProps< 15 | T extends PostSubscriptionConfigItem, 16 | K extends keyof T, 17 | I = never, 18 | IK extends keyof I = never, 19 | > = { 20 | subscription: T; 21 | onChange: SubscriptionChangeHandler; 22 | post: Post; 23 | }; 24 | -------------------------------------------------------------------------------- /front_end/src/components/posts_feed/constants.ts: -------------------------------------------------------------------------------- 1 | export const SCROLL_CACHE_KEY = "feed-scroll-restoration"; 2 | -------------------------------------------------------------------------------- /front_end/src/components/refresh_button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter } from "next/navigation"; 4 | import React from "react"; 5 | 6 | import Button from "./ui/button"; 7 | 8 | const RefreshButton: React.FC = () => { 9 | const router = useRouter(); 10 | 11 | return ( 12 | 15 | ); 16 | }; 17 | 18 | export default RefreshButton; 19 | -------------------------------------------------------------------------------- /front_end/src/components/rich_text.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from "react"; 2 | 3 | type Tag = "strong" | "br"; 4 | 5 | type Props = { 6 | children(tags: Record ReactNode>): ReactNode; 7 | }; 8 | 9 | const RichText: FC = ({ children }) => { 10 | return ( 11 | <> 12 | {children({ 13 | strong: (chunks: ReactNode) => {chunks}, 14 | br: () =>
, 15 | })} 16 | 17 | ); 18 | }; 19 | 20 | export default RichText; 21 | -------------------------------------------------------------------------------- /front_end/src/components/sliders/slider.css: -------------------------------------------------------------------------------- 1 | .rc-slider { 2 | @apply relative flex h-9 w-full touch-none items-center; 3 | } 4 | .rc-slider-rail { 5 | @apply absolute h-[1px] w-full bg-gray-900 dark:bg-gray-900-dark; 6 | } 7 | .rc-slider-track, 8 | .rc-slider-tracks { 9 | @apply absolute h-[1px] bg-gray-900 dark:bg-gray-900-dark; 10 | } 11 | 12 | .rc-slider-mark { 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | width: 100%; 17 | } 18 | 19 | .rc-slider-mark-text { 20 | position: absolute; 21 | display: inline-block; 22 | text-align: center; 23 | vertical-align: middle; 24 | } 25 | -------------------------------------------------------------------------------- /front_end/src/components/theme_provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useSearchParams } from "next/navigation"; 3 | import { ThemeProvider } from "next-themes"; 4 | import { FC, PropsWithChildren } from "react"; 5 | 6 | import { ENFORCED_THEME_PARAM } from "@/constants/global_search_params"; 7 | import { AppTheme } from "@/types/theme"; 8 | 9 | const AppThemeProvided: FC = ({ children }) => { 10 | const params = useSearchParams(); 11 | const themeParam = params.get(ENFORCED_THEME_PARAM) as AppTheme | null; 12 | 13 | return ( 14 | 20 | {children} 21 | 22 | ); 23 | }; 24 | 25 | export default AppThemeProvided; 26 | -------------------------------------------------------------------------------- /front_end/src/components/ui/circle_divider.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import cn from "@/utils/core/cn"; 4 | 5 | type Props = { 6 | className?: string; 7 | }; 8 | 9 | const CircleDivider: FC = ({ className }) => { 10 | return ( 11 | 12 | • 13 | 14 | ); 15 | }; 16 | 17 | export default CircleDivider; 18 | -------------------------------------------------------------------------------- /front_end/src/components/ui/image_with_fallback.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { 3 | DetailedHTMLProps, 4 | FC, 5 | ImgHTMLAttributes, 6 | PropsWithChildren, 7 | useState, 8 | } from "react"; 9 | 10 | type Props = DetailedHTMLProps< 11 | ImgHTMLAttributes, 12 | HTMLImageElement 13 | >; 14 | 15 | const ImageWithFallback: FC> = ({ 16 | children, 17 | ...props 18 | }) => { 19 | const [isFailed, setIsFailed] = useState(false); 20 | 21 | if (isFailed) { 22 | return children; 23 | } 24 | 25 | return setIsFailed(true)} />; 26 | }; 27 | 28 | export default ImageWithFallback; 29 | -------------------------------------------------------------------------------- /front_end/src/components/ui/loading_indicator.tsx: -------------------------------------------------------------------------------- 1 | import { faEllipsis } from "@fortawesome/free-solid-svg-icons"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | import { FC } from "react"; 4 | 5 | import cn from "@/utils/core/cn"; 6 | 7 | type Props = { 8 | className?: string; 9 | }; 10 | 11 | const LoadingIndicator: FC = ({ className }) => { 12 | return ( 13 | 19 | 24 | 25 | ); 26 | }; 27 | 28 | export default LoadingIndicator; 29 | -------------------------------------------------------------------------------- /front_end/src/components/ui/loading_spiner.tsx: -------------------------------------------------------------------------------- 1 | import { SizeProp } from "@fortawesome/fontawesome-svg-core"; 2 | import { faSpinner } from "@fortawesome/free-solid-svg-icons"; 3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 4 | import { FC } from "react"; 5 | 6 | import cn from "@/utils/core/cn"; 7 | 8 | type Props = { 9 | className?: string; 10 | size?: SizeProp; 11 | }; 12 | 13 | const LoadingSpinner: FC = ({ className, size = "2x" }) => { 14 | return ( 15 | 21 | 26 | 27 | ); 28 | }; 29 | 30 | export default LoadingSpinner; 31 | -------------------------------------------------------------------------------- /front_end/src/components/ui/markdown_text.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | 3 | import cn from "@/utils/core/cn"; 4 | 5 | type MarkdownProps = { 6 | className?: string; 7 | }; 8 | 9 | export const MarkdownText: FC> = ({ 10 | className, 11 | children, 12 | }) => { 13 | return ( 14 | 20 | {children} 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /front_end/src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | import { Switch as HeadlessSwitch, SwitchProps } from "@headlessui/react"; 2 | import React, { FC } from "react"; 3 | 4 | import cn from "@/utils/core/cn"; 5 | 6 | const Switch: FC = ({ className, ...props }) => { 7 | return ( 8 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default Switch; 21 | -------------------------------------------------------------------------------- /front_end/src/constants/comments.ts: -------------------------------------------------------------------------------- 1 | export const userTagPattern = /@(?!\[)(?:\(([^)]+)\)|([^\s(]+)(?!\]))/g; 2 | -------------------------------------------------------------------------------- /front_end/src/constants/global_search_params.ts: -------------------------------------------------------------------------------- 1 | export const ENFORCED_THEME_PARAM = "theme"; 2 | export const GRAPH_ZOOM_PARAM = "zoom"; 3 | export const HIDE_ZOOM_PICKER = "hideZoomPicker"; 4 | export const EMBED_QUESTION_TITLE = "embedTitle"; 5 | -------------------------------------------------------------------------------- /front_end/src/constants/metadata.ts: -------------------------------------------------------------------------------- 1 | export const defaultDescription = 2 | "Metaculus is an online forecasting platform and aggregation engine working to improve human reasoning and coordination on topics of global importance."; 3 | -------------------------------------------------------------------------------- /front_end/src/constants/questions.ts: -------------------------------------------------------------------------------- 1 | export const ANNULLED_RESOLUTION = "annulled"; 2 | export const AMBIGUOUS_RESOLUTION = "ambiguous"; 3 | -------------------------------------------------------------------------------- /front_end/src/contexts/polyfill.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import "@/polyfills"; 3 | import { FC, PropsWithChildren } from "react"; 4 | 5 | /** 6 | * Not an actual Context provider. It's a simple wrapper that enables polyfills on the client 7 | */ 8 | const PolyfillProvider: FC = ({ children }) => children; 9 | 10 | export default PolyfillProvider; 11 | -------------------------------------------------------------------------------- /front_end/src/contexts/public_settings_context.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { createContext, FC, PropsWithChildren, useContext } from "react"; 4 | 5 | import { 6 | type PublicSettings, 7 | defaultPublicSettingsValues, 8 | } from "@/utils/public_settings"; 9 | 10 | export const PublicSettingsContext = createContext( 11 | defaultPublicSettingsValues 12 | ); 13 | 14 | const PublicSettingsProvider: FC< 15 | PropsWithChildren<{ settings: PublicSettings }> 16 | > = ({ children, settings }) => { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | }; 23 | 24 | export const usePublicSettings = () => useContext(PublicSettingsContext); 25 | export default PublicSettingsProvider; 26 | -------------------------------------------------------------------------------- /front_end/src/declarations/files.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.webp"; 2 | declare module "*.png"; 3 | declare module "*.jpeg"; 4 | -------------------------------------------------------------------------------- /front_end/src/hooks/tailwind.ts: -------------------------------------------------------------------------------- 1 | import { create } from "@kodingdotninja/use-tailwind-breakpoint"; 2 | import resolveConfig from "tailwindcss/resolveConfig"; 3 | 4 | import tailwindConfig from "../../tailwind.config"; 5 | 6 | const config = resolveConfig(tailwindConfig); 7 | 8 | export const { useBreakpoint, useBreakpointValue, useBreakpointEffect } = 9 | create(config.theme.screens); 10 | -------------------------------------------------------------------------------- /front_end/src/hooks/use_app_theme.ts: -------------------------------------------------------------------------------- 1 | import { useTheme } from "next-themes"; 2 | import { Dispatch, SetStateAction, useCallback } from "react"; 3 | 4 | import { AppTheme, ThemeColor } from "@/types/theme"; 5 | 6 | const useAppTheme = () => { 7 | const { resolvedTheme, setTheme, forcedTheme } = useTheme(); 8 | 9 | const getThemeColor = useCallback( 10 | (color: ThemeColor) => { 11 | if (resolvedTheme === "dark") { 12 | return color.dark; 13 | } 14 | 15 | return color.DEFAULT; 16 | }, 17 | [resolvedTheme] 18 | ); 19 | 20 | return { 21 | theme: (forcedTheme ?? resolvedTheme) as AppTheme, 22 | setTheme: setTheme as Dispatch>, 23 | getThemeColor, 24 | }; 25 | }; 26 | 27 | export default useAppTheme; 28 | -------------------------------------------------------------------------------- /front_end/src/hooks/use_hash.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { usePathname, useSearchParams } from "next/navigation"; 3 | import { useEffect, useState } from "react"; 4 | 5 | const getHash = () => 6 | typeof window !== "undefined" 7 | ? decodeURIComponent(window.location.hash.replace("#", "")) 8 | : undefined; 9 | 10 | const useHash = () => { 11 | const [hash, setHash] = useState(getHash()); 12 | const pathname = usePathname(); 13 | const searchParams = useSearchParams(); 14 | 15 | useEffect(() => { 16 | setHash(getHash()); 17 | // for backward compatibility 18 | const handleHashChange = () => { 19 | setHash(getHash()); 20 | }; 21 | 22 | window.addEventListener("hashchange", handleHashChange); 23 | return () => { 24 | window.removeEventListener("hashchange", handleHashChange); 25 | }; 26 | }, [pathname, searchParams]); 27 | 28 | return hash ?? null; 29 | }; 30 | 31 | export default useHash; 32 | -------------------------------------------------------------------------------- /front_end/src/hooks/use_mounted.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | const useMounted = () => { 4 | const [mounted, setMounted] = useState(false); 5 | 6 | useEffect(() => { 7 | setMounted(true); 8 | }, []); 9 | 10 | return mounted; 11 | }; 12 | 13 | export default useMounted; 14 | -------------------------------------------------------------------------------- /front_end/src/hooks/use_previous.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from "react"; 2 | 3 | const usePrevious = (value: T) => { 4 | const ref = useRef(undefined); 5 | 6 | useEffect(() => { 7 | ref.current = value; 8 | }, [value]); 9 | 10 | return ref.current; 11 | }; 12 | 13 | export default usePrevious; 14 | -------------------------------------------------------------------------------- /front_end/src/hooks/use_screen_size.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | 5 | const useScreenSize = () => { 6 | const [screenSize, setScreenSize] = useState({ 7 | width: window.innerWidth, 8 | height: window.innerHeight, 9 | }); 10 | 11 | useEffect(() => { 12 | const handleResize = () => { 13 | setScreenSize({ 14 | width: window.innerWidth, 15 | height: window.innerHeight, 16 | }); 17 | }; 18 | 19 | window.addEventListener("resize", handleResize); 20 | 21 | return () => { 22 | window.removeEventListener("resize", handleResize); 23 | }; 24 | }, []); 25 | 26 | return screenSize; 27 | }; 28 | 29 | export default useScreenSize; 30 | -------------------------------------------------------------------------------- /front_end/src/hooks/use_scroll_to.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useCallback } from "react"; 4 | 5 | const headerOffset = 48; 6 | const extraOffset = 12; 7 | const SCROLL_OFFSET = headerOffset + extraOffset; 8 | 9 | const useScrollTo = () => { 10 | return useCallback((top: number, behavior: ScrollBehavior = "smooth") => { 11 | const y = top + window.scrollY - SCROLL_OFFSET; 12 | 13 | window.scrollTo({ top: y, behavior }); 14 | }, []); 15 | }; 16 | 17 | export default useScrollTo; 18 | -------------------------------------------------------------------------------- /front_end/src/hooks/use_timestamp_cursor.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | 3 | import { TickFormat } from "@/types/charts"; 4 | 5 | const useTimestampCursor = (timestamps: number[]) => { 6 | const [cursorTimestamp, setCursorTimestamp] = useState( 7 | timestamps.at(-1) ?? null 8 | ); 9 | const [tooltipDate, setTooltipDate] = useState(""); 10 | 11 | const handleCursorChange = useCallback( 12 | (value: number, format: TickFormat) => { 13 | setCursorTimestamp(value); 14 | setTooltipDate(format(value)); 15 | }, 16 | [] 17 | ); 18 | 19 | return [cursorTimestamp, tooltipDate, handleCursorChange] as const; 20 | }; 21 | 22 | export default useTimestampCursor; 23 | -------------------------------------------------------------------------------- /front_end/src/instrumentation-client.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | import { getPublicSetting } from "@/components/public_settings_script"; 4 | import { 5 | beforeSentryAlertSend, 6 | SENTRY_IGNORE_ERRORS, 7 | } from "@/utils/core/errors"; 8 | 9 | const sentryDsn = getPublicSetting("PUBLIC_FRONTEND_SENTRY_DSN"); 10 | if (!!sentryDsn) { 11 | Sentry.init({ 12 | environment: process.env.METACULUS_ENV, 13 | dsn: sentryDsn, 14 | tracesSampleRate: 0.1, 15 | integrations: [Sentry.replayIntegration()], 16 | replaysSessionSampleRate: 0.1, 17 | replaysOnErrorSampleRate: 1.0, 18 | ignoreErrors: SENTRY_IGNORE_ERRORS, 19 | beforeSend: beforeSentryAlertSend, 20 | }); 21 | } 22 | 23 | // This export will instrument router navigations, and is only relevant if you enable tracing. 24 | export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; 25 | -------------------------------------------------------------------------------- /front_end/src/instrumentation.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | export async function register() { 4 | if (process.env.NEXT_RUNTIME === "nodejs") { 5 | await import("../sentry.server.config"); 6 | } 7 | 8 | if (process.env.NEXT_RUNTIME === "edge") { 9 | await import("../sentry.edge.config"); 10 | } 11 | } 12 | 13 | export const onRequestError = Sentry.captureRequestError; 14 | -------------------------------------------------------------------------------- /front_end/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // provide any required polyfills here that will be used client-side 2 | import "core-js/features/array/find-last"; 3 | import "core-js/stable/structured-clone"; 4 | -------------------------------------------------------------------------------- /front_end/src/services/api/aggregation_explorer/aggregation_explorer.client.ts: -------------------------------------------------------------------------------- 1 | import { clientFetcher } from "@/utils/core/fetch/fetch.client"; 2 | 3 | import AggregationExplorerApi from "./aggregation_explorer.shared"; 4 | 5 | const ClientAggregationExplorerApi = new AggregationExplorerApi(clientFetcher); 6 | export default ClientAggregationExplorerApi; 7 | -------------------------------------------------------------------------------- /front_end/src/services/api/aggregation_explorer/aggregation_explorer.server.ts: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | import { serverFetcher } from "@/utils/core/fetch/fetch.server"; 3 | 4 | import AggregationExplorerApi from "./aggregation_explorer.shared"; 5 | 6 | const ServerAggregationsExplorerApi = new AggregationExplorerApi(serverFetcher); 7 | export default ServerAggregationsExplorerApi; 8 | -------------------------------------------------------------------------------- /front_end/src/services/api/comments/comments.client.ts: -------------------------------------------------------------------------------- 1 | import { clientFetcher } from "@/utils/core/fetch/fetch.client"; 2 | 3 | import CommentsApi from "./comments.shared"; 4 | 5 | const ClientCommentsApi = new CommentsApi(clientFetcher); 6 | export default ClientCommentsApi; 7 | -------------------------------------------------------------------------------- /front_end/src/services/api/leaderboard/leaderboard.server.ts: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | import { serverFetcher } from "@/utils/core/fetch/fetch.server"; 3 | 4 | import LeaderboardApi from "./leaderboard.shared"; 5 | 6 | const ServerLeaderboardApi = new LeaderboardApi(serverFetcher); 7 | export default ServerLeaderboardApi; 8 | -------------------------------------------------------------------------------- /front_end/src/services/api/misc/misc.client.ts: -------------------------------------------------------------------------------- 1 | import { clientFetcher } from "@/utils/core/fetch/fetch.client"; 2 | 3 | import MiscApi from "./misc.shared"; 4 | 5 | const ClientMiscApi = new MiscApi(clientFetcher); 6 | export default ClientMiscApi; 7 | -------------------------------------------------------------------------------- /front_end/src/services/api/misc/misc.server.ts: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | import { SidebarItem } from "@/types/sidebar"; 3 | import { serverFetcher } from "@/utils/core/fetch/fetch.server"; 4 | 5 | import MiscApi, { ContactForm } from "./misc.shared"; 6 | 7 | class ServerMiscApiClass extends MiscApi { 8 | async submitContactForm(data: ContactForm) { 9 | return this.post("/contact-form/", data); 10 | } 11 | 12 | async cancelBulletin(bulletinId: number) { 13 | return await this.post(`/cancel-bulletin/${bulletinId}/`, {}); 14 | } 15 | 16 | async getSidebarItems(): Promise { 17 | return await this.get("/sidebar/"); 18 | } 19 | } 20 | 21 | const serverMiscApi = new ServerMiscApiClass(serverFetcher); 22 | export default serverMiscApi; 23 | -------------------------------------------------------------------------------- /front_end/src/services/api/misc/misc.shared.ts: -------------------------------------------------------------------------------- 1 | import { ApiService } from "@/services/api/api_service"; 2 | 3 | export type ContactForm = { 4 | email: string; 5 | message: string; 6 | subject: string; 7 | }; 8 | 9 | export interface SiteStats { 10 | predictions: number; 11 | questions: number; 12 | resolved_questions: number; 13 | years_of_predictions: number; 14 | } 15 | 16 | class MiscApi extends ApiService { 17 | async getBulletins() { 18 | const resp = await this.get<{ 19 | bulletins: { 20 | text: string; 21 | id: number; 22 | }[]; 23 | }>("/get-bulletins/"); 24 | return resp?.bulletins; 25 | } 26 | 27 | async getSiteStats() { 28 | return await this.get("/get-site-stats/"); 29 | } 30 | } 31 | 32 | export default MiscApi; 33 | -------------------------------------------------------------------------------- /front_end/src/services/api/posts/posts.client.ts: -------------------------------------------------------------------------------- 1 | import { clientFetcher } from "@/utils/core/fetch/fetch.client"; 2 | 3 | import PostsApi from "./posts.shared"; 4 | 5 | const ClientPostsApi = new PostsApi(clientFetcher); 6 | export default ClientPostsApi; 7 | -------------------------------------------------------------------------------- /front_end/src/services/api/profile/profile.client.ts: -------------------------------------------------------------------------------- 1 | import { clientFetcher } from "@/utils/core/fetch/fetch.client"; 2 | 3 | import ProfileApi from "./profile.shared"; 4 | 5 | const ClientProfileApi = new ProfileApi(clientFetcher); 6 | export default ClientProfileApi; 7 | -------------------------------------------------------------------------------- /front_end/src/services/api/profile/profile.shared.ts: -------------------------------------------------------------------------------- 1 | import { ApiService } from "@/services/api/api_service"; 2 | import { PaginatedPayload } from "@/types/fetch"; 3 | import { CurrentUser, UserProfile } from "@/types/users"; 4 | 5 | class ProfileApi extends ApiService { 6 | async getProfileById(id: number): Promise { 7 | return await this.get(`/users/${id}/`); 8 | } 9 | 10 | async searchUsers(query: string) { 11 | return await this.get>( 12 | `/users/?search=${query}` 13 | ); 14 | } 15 | } 16 | 17 | export default ProfileApi; 18 | -------------------------------------------------------------------------------- /front_end/src/services/api/projects/projects.client.ts: -------------------------------------------------------------------------------- 1 | import { clientFetcher } from "@/utils/core/fetch/fetch.client"; 2 | 3 | import ProjectsApi from "./projects.shared"; 4 | 5 | const ClientProjectsApi = new ProjectsApi(clientFetcher); 6 | export default ClientProjectsApi; 7 | -------------------------------------------------------------------------------- /front_end/src/services/api/track_record/track_record.server.ts: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | import { serverFetcher } from "@/utils/core/fetch/fetch.server"; 3 | 4 | import TrackRecordApi from "./track_record.shared"; 5 | 6 | const ServerTrackRecordApi = new TrackRecordApi(serverFetcher); 7 | export default ServerTrackRecordApi; 8 | -------------------------------------------------------------------------------- /front_end/src/services/api/track_record/track_record.shared.ts: -------------------------------------------------------------------------------- 1 | import { ApiService } from "@/services/api/api_service"; 2 | import { GlobalTrackRecord } from "@/types/track_record"; 3 | 4 | class TrackRecordApi extends ApiService { 5 | async getGlobalTrackRecord() { 6 | return await this.get("/metaculus_track_record/"); 7 | } 8 | } 9 | 10 | export default TrackRecordApi; 11 | -------------------------------------------------------------------------------- /front_end/src/services/translation.ts: -------------------------------------------------------------------------------- 1 | import { v2 as TranslateV2 } from "@google-cloud/translate"; 2 | 3 | class TranslationApi { 4 | static async translate(text: string, targetLanguage: string) { 5 | const apiKey = process.env.GOOGLE_TRANSLATE_API; 6 | 7 | const translate = new TranslateV2.Translate({ key: apiKey }); 8 | 9 | await translate.translate(text, targetLanguage); 10 | } 11 | } 12 | 13 | export default TranslationApi; 14 | -------------------------------------------------------------------------------- /front_end/src/types/auth.ts: -------------------------------------------------------------------------------- 1 | import { CurrentUser } from "@/types/users"; 2 | 3 | export type AuthContextType = { 4 | user: CurrentUser | null; 5 | setUser: (user: CurrentUser | null) => void; 6 | }; 7 | 8 | export type SocialAuthResponse = { 9 | token: string; 10 | }; 11 | 12 | export type SocialProviderType = "facebook" | "google-oauth2"; 13 | 14 | export type SocialProvider = { 15 | name: SocialProviderType; 16 | auth_url: string; 17 | }; 18 | 19 | export type AuthResponse = { 20 | token: string; 21 | user: CurrentUser; 22 | }; 23 | 24 | export type SignUpResponse = AuthResponse & { 25 | is_active: boolean; 26 | token: string | null; 27 | }; 28 | -------------------------------------------------------------------------------- /front_end/src/types/experiments.ts: -------------------------------------------------------------------------------- 1 | import { ExtendedQuartiles } from "@/types/question"; 2 | 3 | export enum MapType { 4 | US = "us", 5 | Other = "other", 6 | } 7 | 8 | export type BaseExperimentBar = { 9 | id: string; 10 | name: string; 11 | value: number; 12 | }; 13 | 14 | export type ByStateExperimentBar = BaseExperimentBar & { 15 | abbreviation: string; 16 | democratProbability: number; 17 | hasQuestion: boolean; 18 | }; 19 | 20 | export type BaseMapArea = { 21 | name: string; 22 | abbreviation: string; 23 | x_adjust: number; 24 | y_adjust: number; 25 | }; 26 | 27 | export type StateByForecastItem = BaseMapArea & { 28 | votes: number; 29 | democratProbability: number; 30 | link?: { 31 | groupId: number; 32 | questionId: number; 33 | }; 34 | forecastersNumber?: number; 35 | forecastsNumber?: number; 36 | }; 37 | 38 | export type Candle = { 39 | quartiles: ExtendedQuartiles; 40 | color: string; 41 | }; 42 | -------------------------------------------------------------------------------- /front_end/src/types/navigation.ts: -------------------------------------------------------------------------------- 1 | import { LinkProps } from "next/link"; 2 | 3 | export type SearchParams = Record; 4 | 5 | export type Href = LinkProps["href"]; 6 | -------------------------------------------------------------------------------- /front_end/src/types/news.ts: -------------------------------------------------------------------------------- 1 | import { VoteDirection } from "@/types/votes"; 2 | 3 | export type NewsArticle = { 4 | created_at: string; 5 | id: number; 6 | title: string; 7 | url: string; 8 | favicon_url?: string; 9 | media_label: string; 10 | user_vote: VoteDirection; 11 | distance: number; 12 | }; 13 | 14 | export type NotebookIndex = Record< 15 | string, 16 | Array<{ questionId: number; weight: number }> 17 | >; 18 | -------------------------------------------------------------------------------- /front_end/src/types/notifications.ts: -------------------------------------------------------------------------------- 1 | export enum SubscriptionEmailType { 2 | comment_mentions = "comment_mentions", 3 | question_resolution = "question_resolution", 4 | cp_change = "cp_change", 5 | tournament_new_questions = "tournament_new_questions", 6 | newsletter = "newsletter", 7 | } 8 | -------------------------------------------------------------------------------- /front_end/src/types/onboarding.ts: -------------------------------------------------------------------------------- 1 | import { PostWithForecasts } from "@/types/post"; 2 | 3 | export type OnboardingStoredState = { 4 | selectedTopicId: number | null; 5 | currentStep: number; 6 | step2Prediction: number; 7 | step3Prediction?: number; 8 | }; 9 | 10 | export type OnboardingTopic = { 11 | name: string; 12 | questions: number[]; 13 | factors: string[]; 14 | emoji: string; 15 | }; 16 | 17 | export type OnboardingStep = { 18 | onNext: () => void; 19 | onPrev: () => void; 20 | onComplete: () => void; 21 | topic: OnboardingTopic | null; 22 | setTopic: (id: number) => void; 23 | handleComplete: () => void; 24 | handlePostpone: () => void; 25 | posts: PostWithForecasts[]; 26 | onboardingState: OnboardingStoredState; 27 | setOnboardingState: ( 28 | arg: 29 | | OnboardingStoredState 30 | | ((prev: OnboardingStoredState) => OnboardingStoredState) 31 | ) => void; 32 | }; 33 | -------------------------------------------------------------------------------- /front_end/src/types/preferences.ts: -------------------------------------------------------------------------------- 1 | export enum ProfilePreferencesType { 2 | hide_community_prediction = "hide_community_prediction", 3 | } 4 | -------------------------------------------------------------------------------- /front_end/src/types/theme.ts: -------------------------------------------------------------------------------- 1 | export type AppTheme = "light" | "dark"; 2 | export type ThemeColor = { DEFAULT: string; dark: string }; 3 | -------------------------------------------------------------------------------- /front_end/src/types/translations.ts: -------------------------------------------------------------------------------- 1 | import { MessageKeys } from "next-intl"; 2 | 3 | export type TranslationKey = MessageKeys; 4 | -------------------------------------------------------------------------------- /front_end/src/types/utils.ts: -------------------------------------------------------------------------------- 1 | export type Require = T & Required>; 2 | export type Optional = Pick, K> & Omit; 3 | export type DeepPartial = { 4 | [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; 5 | }; 6 | export type DataParams = { 7 | post_id?: number; 8 | question_id?: number; 9 | sub_question?: number; 10 | aggregation_methods?: string[]; 11 | minimize?: boolean; 12 | user_ids?: number[]; 13 | include_comments?: boolean; 14 | include_scores?: boolean; 15 | include_bots?: boolean; 16 | include_user_data?: boolean; 17 | anonymized?: boolean; 18 | }; 19 | -------------------------------------------------------------------------------- /front_end/src/types/votes.ts: -------------------------------------------------------------------------------- 1 | export type VoteDirection = 1 | -1 | null; 2 | 3 | export type VoteResponse = { 4 | score: number; 5 | }; 6 | -------------------------------------------------------------------------------- /front_end/src/utils/alpha_access.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | /** 4 | * Returns Access Token which is used to restrict access to dev server 5 | */ 6 | export const getAlphaAccessToken = async () => process.env.ALPHA_ACCESS_TOKEN; 7 | -------------------------------------------------------------------------------- /front_end/src/utils/core/cn.ts: -------------------------------------------------------------------------------- 1 | import clsx, { ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export default function cn(...args: ClassValue[]) { 5 | return twMerge(clsx(args)); 6 | } 7 | -------------------------------------------------------------------------------- /front_end/src/utils/files.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from "lodash"; 2 | 3 | export function base64ToBlob(base64: string): Blob { 4 | const data = base64.split(",")[1]; 5 | if (isNil(data)) { 6 | throw new Error("Invalid base64 string"); 7 | } 8 | const byteString = atob(data); 9 | 10 | const mimeString = base64.split(",")[0]?.split(":")?.[1]?.split(";")?.[0]; 11 | if (isNil(mimeString)) { 12 | throw new Error("Invalid base64 string"); 13 | } 14 | 15 | const ab = new ArrayBuffer(byteString.length); 16 | const ia = new Uint8Array(ab); 17 | for (let i = 0; i < byteString.length; i++) { 18 | ia[i] = byteString.charCodeAt(i); 19 | } 20 | return new Blob([ab], { type: mimeString }); 21 | } 22 | -------------------------------------------------------------------------------- /front_end/src/utils/formatters/string.ts: -------------------------------------------------------------------------------- 1 | export function truncateLabel(label: string, maxLength: number): string { 2 | if (label.length <= maxLength) { 3 | return label; 4 | } 5 | return label.slice(0, maxLength).trim() + "..."; 6 | } 7 | -------------------------------------------------------------------------------- /front_end/src/utils/formatters/users.ts: -------------------------------------------------------------------------------- 1 | import { UserBase } from "@/types/users"; 2 | 3 | export function formatUsername(profile: UserBase) { 4 | return profile.is_bot ? `🤖 ${profile.username}` : profile.username; 5 | } 6 | -------------------------------------------------------------------------------- /front_end/src/utils/onboarding.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { addDays, isAfter } from "date-fns"; 4 | 5 | import { safeLocalStorage } from "@/utils/core/storage"; 6 | 7 | const ONBOARDING_SUPPRESSED_KEY = "onboardingSuppressedAt"; 8 | export const ONBOARDING_STATE_KEY = "onboardingStateV2"; 9 | 10 | export function checkOnboardingAllowed() { 11 | const closedAt = safeLocalStorage.getItem(ONBOARDING_SUPPRESSED_KEY); 12 | 13 | return closedAt ? isAfter(new Date(), addDays(new Date(closedAt), 1)) : true; 14 | } 15 | 16 | export function setOnboardingSuppressed() { 17 | safeLocalStorage.setItem(ONBOARDING_SUPPRESSED_KEY, new Date().toISOString()); 18 | } 19 | -------------------------------------------------------------------------------- /front_end/src/utils/questions/units.ts: -------------------------------------------------------------------------------- 1 | export const formatValueUnit = (value: string, unit?: string) => { 2 | if (!unit) return value; 3 | 4 | return unit === "%" ? `${value}%` : `${value} ${unit}`; 5 | }; 6 | 7 | // Max length of a unit to be treated as compact 8 | const QUESTION_UNIT_COMPACT_LENGTH = 3; 9 | export const isUnitCompact = (unit?: string) => 10 | unit && unit.length <= QUESTION_UNIT_COMPACT_LENGTH; 11 | -------------------------------------------------------------------------------- /front_end/src/utils/sidebar.ts: -------------------------------------------------------------------------------- 1 | import { SidebarItem, SidebarMenuItem } from "@/types/sidebar"; 2 | import { sendAnalyticsEvent } from "@/utils/analytics"; 3 | import { getPostLink, getProjectLink } from "@/utils/navigation"; 4 | 5 | export const convertSidebarItem = ( 6 | { name, post, project, section, url, emoji }: SidebarItem, 7 | fullPathname?: string 8 | ): SidebarMenuItem => { 9 | const itemUrl: string = post 10 | ? getPostLink(post) 11 | : project 12 | ? getProjectLink(project) 13 | : url; 14 | 15 | const itemName = name || post?.title || project?.name || ""; 16 | 17 | return { 18 | name: itemName, 19 | emoji: emoji || project?.emoji, 20 | section, 21 | url: itemUrl, 22 | isActive: fullPathname ? fullPathname.startsWith(itemUrl) : undefined, 23 | onClick: () => { 24 | sendAnalyticsEvent("sidebarClick", { 25 | event_category: itemName, 26 | }); 27 | }, 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /front_end/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "noUncheckedIndexedAccess": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": [ 27 | "next-env.d.ts", 28 | "**/*.ts", 29 | "**/*.tsx", 30 | ".next/types/**/*.ts", 31 | "types/**/*.d.ts", 32 | "declarations/**/*.d.ts" 33 | ], 34 | "exclude": ["node_modules"] 35 | } 36 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | web: Dockerfile -------------------------------------------------------------------------------- /init-db.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS vector; 2 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | from dotenv import load_dotenv 7 | 8 | 9 | def main(): 10 | load_dotenv() 11 | 12 | """Run administrative tasks.""" 13 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "metaculus_web.settings") 14 | try: 15 | from django.core.management import execute_from_command_line 16 | except ImportError as exc: 17 | raise ImportError( 18 | "Couldn't import Django. Are you sure it's installed and " 19 | "available on your PYTHONPATH environment variable? Did you " 20 | "forget to activate a virtual environment?" 21 | ) from exc 22 | execute_from_command_line(sys.argv) 23 | 24 | 25 | if __name__ == "__main__": 26 | main() 27 | -------------------------------------------------------------------------------- /metaculus_web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/metaculus_web/__init__.py -------------------------------------------------------------------------------- /metaculus_web/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | admin.site.site_header = "Custom Admin" 4 | admin.site.site_title = "Custom Admin Portal" 5 | admin.site.index_title = "Welcome to Custom Admin Portal" 6 | -------------------------------------------------------------------------------- /metaculus_web/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for metaculus_web project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "metaculus_web.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /metaculus_web/locale/original/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/metaculus_web/locale/original/LC_MESSAGES/django.po -------------------------------------------------------------------------------- /metaculus_web/locale/original/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/metaculus_web/locale/original/__init__.py -------------------------------------------------------------------------------- /metaculus_web/locale/original/formats.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/metaculus_web/locale/original/formats.py -------------------------------------------------------------------------------- /metaculus_web/test_settings.py: -------------------------------------------------------------------------------- 1 | from .settings import * # noqa 2 | import dj_database_url 3 | 4 | DATABASES = { 5 | "default": { 6 | **dj_database_url.config( 7 | env="TEST_DATABASE_URL", conn_max_age=600, default="postgres:///metaculus" 8 | ), 9 | }, 10 | } 11 | 12 | TEST = True 13 | -------------------------------------------------------------------------------- /metaculus_web/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for metaculus_web project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "metaculus_web.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /misc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/misc/__init__.py -------------------------------------------------------------------------------- /misc/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def common_context(request): 5 | return { 6 | "PUBLIC_APP_URL": settings.PUBLIC_APP_URL, 7 | "IS_SCREENSHOT_SERVICE_ENABLED": bool(settings.SCREENSHOT_SERVICE_API_KEY), 8 | } 9 | -------------------------------------------------------------------------------- /misc/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/misc/management/__init__.py -------------------------------------------------------------------------------- /misc/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/misc/management/commands/__init__.py -------------------------------------------------------------------------------- /misc/management/commands/sync_itn_articles.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from misc.jobs import sync_itn_articles 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class Command(BaseCommand): 11 | help = "Sync ITN news and find similar posts" 12 | 13 | def add_arguments(self, parser): 14 | parser.add_argument( 15 | "--num_processes", 16 | type=int, 17 | default=1, 18 | help="Number of processes to use for processing (default: 10)", 19 | ) 20 | 21 | def handle(self, *args, num_processes: int = 1, **options): 22 | sync_itn_articles(num_processes=num_processes) 23 | -------------------------------------------------------------------------------- /misc/migrations/0002_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-17 15:10 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ("misc", "0001_initial"), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name="bulletinviewedby", 20 | name="user", 21 | field=models.ForeignKey( 22 | on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /misc/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/misc/migrations/__init__.py -------------------------------------------------------------------------------- /misc/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/misc/services/__init__.py -------------------------------------------------------------------------------- /misc/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/misc/templatetags/__init__.py -------------------------------------------------------------------------------- /misc/templatetags/utils.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | from django import template 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.filter 9 | def chunks(value, chunk_length): 10 | """ 11 | Breaks a list up into a list of lists of size 12 | """ 13 | 14 | clen = int(chunk_length) 15 | i = iter(value) 16 | while True: 17 | chunk = list(itertools.islice(i, clen)) 18 | if chunk: 19 | yield chunk 20 | else: 21 | break 22 | -------------------------------------------------------------------------------- /misc/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("sidebar/", views.sidebar_api_view), 7 | path("contact-form/", views.contact_api_view), 8 | path("contact-form/services/", views.contact_service_api_view), 9 | path("get-bulletins/", views.get_bulletins), 10 | path("get-site-stats/", views.get_site_stats), 11 | path("cancel-bulletin//", views.cancel_bulletin), 12 | path( 13 | "itn-articles//remove/", 14 | views.remove_article_api_view, 15 | name="itn-article-remove", 16 | ), 17 | path("select2/", include("django_select2.urls")), 18 | ] 19 | -------------------------------------------------------------------------------- /notifications/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/notifications/__init__.py -------------------------------------------------------------------------------- /notifications/constants.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class MailingTags(models.TextChoices): 5 | FORECASTED_QUESTION_RESOLUTION = "question_resolution" 6 | COMMENT_MENTIONS = "comment_mentions" 7 | # Forecasted post CP change 8 | FORECASTED_CP_CHANGE = "cp_change" 9 | TOURNAMENT_NEW_QUESTIONS = "tournament_new_questions" 10 | -------------------------------------------------------------------------------- /notifications/migrations/0002_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-17 15:10 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ("notifications", "0001_initial"), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name="notification", 20 | name="recipient", 21 | field=models.ForeignKey( 22 | on_delete=django.db.models.deletion.CASCADE, 23 | related_name="notifications", 24 | to=settings.AUTH_USER_MODEL, 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /notifications/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/notifications/migrations/__init__.py -------------------------------------------------------------------------------- /notifications/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from utils.models import TimeStampedModel 4 | 5 | 6 | class Notification(TimeStampedModel): 7 | """ 8 | Platform notifications. 9 | Will be used to display in-app notifications AND for sending batch emails once per hour 10 | (stack notifications of the same type) 11 | """ 12 | 13 | type = models.CharField(db_index=True) 14 | # Recipient of the notification 15 | recipient = models.ForeignKey( 16 | "users.User", models.CASCADE, related_name="notifications" 17 | ) 18 | 19 | params = models.JSONField() 20 | read_at = models.DateTimeField(null=True, db_index=True) 21 | 22 | email_sent = models.BooleanField(default=False, db_index=True) 23 | 24 | def mark_as_sent(self): 25 | self.email_sent = True 26 | -------------------------------------------------------------------------------- /posts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/posts/__init__.py -------------------------------------------------------------------------------- /posts/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/posts/management/__init__.py -------------------------------------------------------------------------------- /posts/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/posts/management/commands/__init__.py -------------------------------------------------------------------------------- /posts/management/commands/compute_hotness.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from ...services.hotness import compute_feed_hotness 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class Command(BaseCommand): 11 | help = """ 12 | Computes post hotness 13 | """ 14 | 15 | def handle(self, *args, **options): 16 | compute_feed_hotness() 17 | -------------------------------------------------------------------------------- /posts/management/commands/compute_movement.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from ...jobs import job_compute_movement 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class Command(BaseCommand): 11 | help = """ 12 | Computes post movement 13 | """ 14 | 15 | def handle(self, *args, **options): 16 | job_compute_movement() 17 | -------------------------------------------------------------------------------- /posts/migrations/0003_post_comment_count_post_forecasters_count_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-20 15:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("posts", "0002_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="post", 15 | name="comment_count", 16 | field=models.PositiveIntegerField(db_index=True, default=0, editable=False), 17 | ), 18 | migrations.AddField( 19 | model_name="post", 20 | name="forecasters_count", 21 | field=models.PositiveIntegerField(default=0, editable=False), 22 | ), 23 | migrations.AddField( 24 | model_name="post", 25 | name="vote_score", 26 | field=models.IntegerField(db_index=True, default=0, editable=False), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /posts/migrations/0004_post_show_on_homepage_alter_notebook_image_url.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-30 17:27 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("posts", "0003_post_comment_count_post_forecasters_count_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="post", 14 | name="show_on_homepage", 15 | field=models.BooleanField(db_index=True, default=False), 16 | ), 17 | migrations.AlterField( 18 | model_name="notebook", 19 | name="image_url", 20 | field=models.ImageField(blank=True, null=True, upload_to="user_uploaded"), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /posts/migrations/0006_alter_post_scheduled_close_time_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-14 20:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ( 9 | "posts", 10 | "0005_remove_postsubscription_postsubscription_unique_type_user_post_and_more", 11 | ), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="post", 17 | name="scheduled_close_time", 18 | field=models.DateTimeField(blank=True, db_index=True, null=True), 19 | ), 20 | migrations.AlterField( 21 | model_name="post", 22 | name="scheduled_resolve_time", 23 | field=models.DateTimeField(blank=True, db_index=True, null=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /posts/migrations/0008_rename_published_at_triggered_post_open_time_triggered.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-04 17:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("posts", "0007_post_open_time_alter_post_actual_close_time_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="post", 14 | name="published_at_triggered", 15 | field=models.BooleanField(db_index=True, default=False, editable=False), 16 | ), 17 | migrations.RenameField( 18 | model_name="post", 19 | old_name="published_at_triggered", 20 | new_name="open_time_triggered", 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /posts/migrations/0010_populate_original_fields.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-13 15:49 2 | 3 | from django.db import migrations 4 | from utils.translation import migration_update_default_fields 5 | 6 | 7 | def migrate(apps, schema_editor): 8 | Post = apps.get_model("posts", "Post") 9 | Notebook = apps.get_model("posts", "Notebook") 10 | migration_update_default_fields(Post, ["title", "url_title"]) 11 | migration_update_default_fields(Notebook, ["markdown"]) 12 | 13 | 14 | class Migration(migrations.Migration): 15 | 16 | dependencies = [ 17 | ("posts", "0009_notebook_content_last_md5_and_more"), 18 | ] 19 | 20 | operations = [ 21 | migrations.RunPython(migrate, migrations.RunPython.noop), 22 | ] 23 | -------------------------------------------------------------------------------- /posts/migrations/0011_notebook_markdown_pt_post_title_pt_post_url_title_pt.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-13 21:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("posts", "0010_populate_original_fields"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="notebook", 15 | name="markdown_pt", 16 | field=models.TextField(null=True), 17 | ), 18 | migrations.AddField( 19 | model_name="post", 20 | name="title_pt", 21 | field=models.CharField(blank=True, max_length=2000, null=True), 22 | ), 23 | migrations.AddField( 24 | model_name="post", 25 | name="url_title_pt", 26 | field=models.CharField(blank=True, default="", max_length=2000, null=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /posts/migrations/0012_alter_post_default_project.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-18 14:43 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("posts", "0011_notebook_markdown_pt_post_title_pt_post_url_title_pt"), 10 | ("projects", "0008_project_description_pt_project_name_pt_and_more"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="post", 16 | name="default_project", 17 | field=models.ForeignKey( 18 | on_delete=django.db.models.deletion.PROTECT, 19 | related_name="default_posts", 20 | to="projects.project", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /posts/migrations/0013_alter_post_hotness.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-02-11 19:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("posts", "0012_alter_post_default_project"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="post", 15 | name="hotness", 16 | field=models.IntegerField(db_index=True, default=0, editable=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /posts/migrations/0014_notebook_is_automatically_translated_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-03-06 10:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("posts", "0013_alter_post_hotness"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="notebook", 15 | name="is_automatically_translated", 16 | field=models.BooleanField(default=True), 17 | ), 18 | migrations.AddField( 19 | model_name="post", 20 | name="is_automatically_translated", 21 | field=models.BooleanField(default=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /posts/migrations/0017_remove_post_open_time_triggered.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.8 on 2025-04-10 14:49 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("posts", "0016_remove_post_url_title_remove_post_url_title_cs_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="post", 15 | name="open_time_triggered", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /posts/migrations/0018_notebook_markdown_zh_tw_post_short_title_zh_tw_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.7 on 2025-04-24 06:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("posts", "0017_remove_post_open_time_triggered"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="notebook", 15 | name="markdown_zh_TW", 16 | field=models.TextField(null=True), 17 | ), 18 | migrations.AddField( 19 | model_name="post", 20 | name="short_title_zh_TW", 21 | field=models.CharField(blank=True, default="", max_length=2000, null=True), 22 | ), 23 | migrations.AddField( 24 | model_name="post", 25 | name="title_zh_TW", 26 | field=models.CharField(blank=True, max_length=2000, null=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /posts/migrations/0019_post_html_metadata_json.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.8 on 2025-05-05 08:48 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("posts", "0018_notebook_markdown_zh_tw_post_short_title_zh_tw_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="post", 15 | name="html_metadata_json", 16 | field=models.JSONField( 17 | blank=True, 18 | default=None, 19 | help_text="Custom JSON for HTML meta tags. Supported fields are: title, description, image_url", 20 | null=True, 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /posts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/posts/migrations/__init__.py -------------------------------------------------------------------------------- /posts/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/posts/services/__init__.py -------------------------------------------------------------------------------- /posts/translation.py: -------------------------------------------------------------------------------- 1 | from modeltranslation.translator import TranslationOptions, translator 2 | from .models import Post, Notebook 3 | from metaculus_web.settings import TRANSLATIONS_FALLBACK_UNDEFINED 4 | 5 | 6 | class PostTranslationOptions(TranslationOptions): 7 | fields = ("title", "short_title") 8 | fallback_undefined = TRANSLATIONS_FALLBACK_UNDEFINED 9 | 10 | 11 | class NotebookTranslationOptions(TranslationOptions): 12 | fields = ("markdown",) 13 | fallback_undefined = TRANSLATIONS_FALLBACK_UNDEFINED 14 | 15 | 16 | translator.register(Post, PostTranslationOptions) 17 | translator.register(Notebook, NotebookTranslationOptions) 18 | -------------------------------------------------------------------------------- /posts/utils.py: -------------------------------------------------------------------------------- 1 | from django.template.defaultfilters import slugify 2 | from rest_framework.exceptions import PermissionDenied 3 | 4 | from posts.models import Post 5 | from posts.services.common import get_post_permission_for_user 6 | from projects.permissions import ObjectPermission 7 | from users.models import User 8 | 9 | 10 | def check_can_edit_post(post: Post, user: User): 11 | permission = get_post_permission_for_user(post, user=user) 12 | 13 | if ( 14 | post.curation_status == Post.CurationStatus.APPROVED 15 | and permission != ObjectPermission.ADMIN 16 | ): 17 | raise PermissionDenied("You do not have permission to edit approved post") 18 | 19 | ObjectPermission.can_edit(permission, raise_exception=True) 20 | 21 | 22 | def get_post_slug(post: Post) -> str: 23 | return slugify(post.short_title or post.title or "") 24 | -------------------------------------------------------------------------------- /projects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/projects/__init__.py -------------------------------------------------------------------------------- /projects/constants.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/projects/constants.py -------------------------------------------------------------------------------- /projects/migrations/0003_project_show_on_homepage.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-30 17:27 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("projects", "0002_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="project", 15 | name="show_on_homepage", 16 | field=models.BooleanField(db_index=True, default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /projects/migrations/0007_populate_original_fields.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-13 15:49 2 | 3 | from django.db import migrations 4 | from utils.translation import migration_update_default_fields 5 | 6 | 7 | def migrate(apps, schema_editor): 8 | Project = apps.get_model("projects", "Project") 9 | migration_update_default_fields(Project, ["description", "name", "subtitle"]) 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("projects", "0006_project_content_last_md5_and_more"), 16 | ] 17 | 18 | operations = [ 19 | migrations.RunPython(migrate, migrations.RunPython.noop), 20 | ] 21 | -------------------------------------------------------------------------------- /projects/migrations/0010_remove_project_add_posts_to_main_feed_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2024-12-16 23:06 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("projects", "0009_project_visibility"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="project", 14 | name="add_posts_to_main_feed", 15 | ), 16 | migrations.RemoveField( 17 | model_name="project", 18 | name="unlisted", 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /projects/migrations/0012_project_is_automatically_translated.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-03-06 10:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("projects", "0011_alter_project_type_projectindexquestion"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="project", 15 | name="is_automatically_translated", 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /projects/migrations/0015_project_forecasts_flow_enabled.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.8 on 2025-04-30 19:08 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("projects", "0014_project_description_zh_tw_project_name_zh_tw_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="project", 15 | name="forecasts_flow_enabled", 16 | field=models.BooleanField( 17 | default=True, help_text="Enables new forecast flow for tournaments" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /projects/migrations/0017_remove_project_section.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.8 on 2025-05-19 16:01 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("projects", "0016_remove_project_include_bots_in_leaderboard_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="project", 14 | name="section", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /projects/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/projects/migrations/__init__.py -------------------------------------------------------------------------------- /projects/serializers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/projects/serializers/__init__.py -------------------------------------------------------------------------------- /projects/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/projects/services/__init__.py -------------------------------------------------------------------------------- /projects/translation.py: -------------------------------------------------------------------------------- 1 | from modeltranslation.translator import TranslationOptions, translator 2 | from .models import Project 3 | from metaculus_web.settings import TRANSLATIONS_FALLBACK_UNDEFINED 4 | 5 | 6 | class ProjectTranslationOptions(TranslationOptions): 7 | fields = ("name", "description", "subtitle") 8 | fallback_undefined = TRANSLATIONS_FALLBACK_UNDEFINED 9 | 10 | 11 | translator.register(Project, ProjectTranslationOptions) 12 | -------------------------------------------------------------------------------- /projects/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .common import * # noqa 2 | from .communities import * # noqa 3 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = metaculus_web.test_settings 3 | addopts = --showlocals --reuse-db 4 | 5 | [pytest-watch] 6 | # clear the screen before each run 7 | clear = True 8 | 9 | # add options to each `pytest` run to make them nicer for development 10 | # runner = pytest --reuse-db 11 | 12 | 13 | [tool.pytest.ini_options] 14 | log_cli = true 15 | log_cli_level = "INFO" 16 | -------------------------------------------------------------------------------- /questions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/questions/__init__.py -------------------------------------------------------------------------------- /questions/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "questions" 7 | -------------------------------------------------------------------------------- /questions/constants.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class ResolutionType(models.TextChoices): 5 | AMBIGUOUS = "ambiguous" 6 | ANNULLED = "annulled" 7 | 8 | 9 | class QuestionStatus(models.TextChoices): 10 | UPCOMING = "upcoming" 11 | RESOLVED = "resolved" 12 | CLOSED = "closed" 13 | OPEN = "open" 14 | -------------------------------------------------------------------------------- /questions/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/questions/management/__init__.py -------------------------------------------------------------------------------- /questions/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/questions/management/commands/__init__.py -------------------------------------------------------------------------------- /questions/migrations/0003_aggregateforecast_questions_a_questio_0d22f0_idx.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-19 16:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("questions", "0002_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddIndex( 14 | model_name="aggregateforecast", 15 | index=models.Index( 16 | fields=["question", "method", "-start_time"], 17 | name="questions_a_questio_0d22f0_idx", 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /questions/migrations/0007_question_question_weight.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-25 18:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("questions", "0006_alter_aggregateforecast_centers_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="question", 15 | name="question_weight", 16 | field=models.FloatField(default=1.0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /questions/migrations/0008_alter_question_label.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | 3 | 4 | class Migration(migrations.Migration): 5 | dependencies = [ 6 | ("questions", "0007_question_question_weight"), 7 | ] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="question", 12 | name="label", 13 | field=models.TextField(blank=True, default=""), 14 | preserve_default=False, 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /questions/migrations/0010_populate_original_fields.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-13 15:49 2 | 3 | from django.db import migrations 4 | from utils.translation import migration_update_default_fields 5 | 6 | 7 | def migrate(apps, schema_editor): 8 | Question = apps.get_model("questions", "Question") 9 | GroupOfQuestions = apps.get_model("questions", "GroupOfQuestions") 10 | migration_update_default_fields( 11 | Question, ["description", "resolution_criteria", "fine_print", "label", "title"] 12 | ) 13 | migration_update_default_fields( 14 | GroupOfQuestions, ["description", "resolution_criteria", "fine_print"] 15 | ) 16 | 17 | 18 | class Migration(migrations.Migration): 19 | 20 | dependencies = [ 21 | ("questions", "0009_groupofquestions_content_last_md5_and_more"), 22 | ] 23 | 24 | operations = [ 25 | migrations.RunPython(migrate, migrations.RunPython.noop), 26 | ] 27 | -------------------------------------------------------------------------------- /questions/migrations/0012_question_group_variable.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-19 13:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("questions", "0011_groupofquestions_description_pt_and_more"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="question", 14 | name="group_variable", 15 | field=models.CharField(blank=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /questions/migrations/0013_forecast_source.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-12-06 14:48 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("questions", "0012_question_group_variable"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="forecast", 15 | name="source", 16 | field=models.CharField( 17 | blank=True, 18 | choices=[("api", "Api"), ("ui", "Ui")], 19 | default="", 20 | max_length=30, 21 | null=True, 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /questions/migrations/0015_remove_forecast_distribution_components_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-01-27 14:25 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("questions", "0014_forecast_distribution_input"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="forecast", 14 | name="distribution_components", 15 | ), 16 | migrations.RemoveField( 17 | model_name="forecast", 18 | name="slider_values", 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /questions/migrations/0016_groupofquestions_is_automatically_translated_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-03-06 10:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("questions", "0015_remove_forecast_distribution_components_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="groupofquestions", 15 | name="is_automatically_translated", 16 | field=models.BooleanField(default=True), 17 | ), 18 | migrations.AddField( 19 | model_name="question", 20 | name="is_automatically_translated", 21 | field=models.BooleanField(default=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /questions/migrations/0017_question_unit.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-03-13 16:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("questions", "0016_groupofquestions_is_automatically_translated_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="question", 15 | name="unit", 16 | field=models.CharField(blank=True, max_length=25), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /questions/migrations/0019_merge_20250319_2033.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.7 on 2025-03-19 20:33 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("questions", "0017_alter_question_actual_close_time_and_more"), 10 | ("questions", "0018_groupofquestions_subquestions_order_and_more"), 11 | ] 12 | 13 | operations = [] 14 | -------------------------------------------------------------------------------- /questions/migrations/0022_question_open_time_triggered.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.8 on 2025-04-10 14:49 2 | 3 | from django.db import migrations, models 4 | from django.utils import timezone 5 | 6 | 7 | def migrate(apps, schema_editor): 8 | Question = apps.get_model("questions", "Question") 9 | Question.objects.filter(open_time__lt=timezone.now()).update( 10 | open_time_triggered=True 11 | ) 12 | 13 | 14 | class Migration(migrations.Migration): 15 | dependencies = [ 16 | ("questions", "0021_question_inbound_outcome_count_and_more"), 17 | ] 18 | 19 | operations = [ 20 | migrations.AddField( 21 | model_name="question", 22 | name="open_time_triggered", 23 | field=models.BooleanField(db_index=True, default=False, editable=False), 24 | ), 25 | migrations.RunPython(migrate, migrations.RunPython.noop), 26 | ] 27 | -------------------------------------------------------------------------------- /questions/migrations/0023_question_movement.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.8 on 2025-04-16 11:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("questions", "0022_question_open_time_triggered"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="question", 15 | name="movement", 16 | field=models.FloatField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /questions/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/questions/migrations/__init__.py -------------------------------------------------------------------------------- /questions/serializers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/questions/serializers/__init__.py -------------------------------------------------------------------------------- /questions/translation.py: -------------------------------------------------------------------------------- 1 | from modeltranslation.translator import TranslationOptions, translator 2 | from .models import Question, GroupOfQuestions 3 | from metaculus_web.settings import TRANSLATIONS_FALLBACK_UNDEFINED 4 | 5 | 6 | class QuestionTranslationOptions(TranslationOptions): 7 | fields = ("description", "resolution_criteria", "fine_print", "label", "title") 8 | fallback_undefined = TRANSLATIONS_FALLBACK_UNDEFINED 9 | 10 | 11 | class GroupOfQuestionsTranslationOptions(TranslationOptions): 12 | fields = ("description", "resolution_criteria", "fine_print") 13 | fallback_undefined = TRANSLATIONS_FALLBACK_UNDEFINED 14 | 15 | 16 | translator.register(Question, QuestionTranslationOptions) 17 | translator.register(GroupOfQuestions, GroupOfQuestionsTranslationOptions) 18 | -------------------------------------------------------------------------------- /questions/types.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class AggregationMethod(models.TextChoices): 5 | RECENCY_WEIGHTED = "recency_weighted" 6 | UNWEIGHTED = "unweighted" 7 | SINGLE_AGGREGATION = "single_aggregation" 8 | METACULUS_PREDICTION = "metaculus_prediction" 9 | -------------------------------------------------------------------------------- /scoring/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/scoring/__init__.py -------------------------------------------------------------------------------- /scoring/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ScoringConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "scoring" 7 | -------------------------------------------------------------------------------- /scoring/migrations/0009_leaderboard_user_list.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-01-29 16:34 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("scoring", "0008_leaderboardsranksentry"), 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="leaderboard", 17 | name="user_list", 18 | field=models.ManyToManyField( 19 | blank=True, 20 | help_text="Optional. If not set, all users with scores will be included.\n
- If set, only users in this list will be included.\n
- Exclusion Records still apply independent of this list.\n ", 21 | to=settings.AUTH_USER_MODEL, 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /scoring/migrations/0010_leaderboard_simplified_view.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-02-28 21:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("scoring", "0009_leaderboard_user_list"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="leaderboard", 15 | name="simplified_view", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /scoring/migrations/0012_remove_leaderboard_simplified_view.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.7 on 2025-03-19 21:04 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("scoring", "0011_medalexclusionrecord_leaderboard_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="leaderboard", 15 | name="simplified_view", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /scoring/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/scoring/migrations/__init__.py -------------------------------------------------------------------------------- /scoring/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /scoring/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path( 7 | "leaderboards/global/", 8 | views.global_leaderboard, 9 | name="global-leaderboard", 10 | ), 11 | path( 12 | "leaderboards/project//", 13 | views.project_leaderboard, 14 | name="project-leaderboard", 15 | ), 16 | path( 17 | "medals/", 18 | views.user_medals, 19 | name="user-medals", 20 | ), 21 | path( 22 | "medal_ranks/", 23 | views.user_medal_ranks, 24 | name="user-medal-ranks", 25 | ), 26 | path( 27 | "medals/contributions/", 28 | views.medal_contributions, 29 | name="medal-contributions", 30 | ), 31 | path( 32 | "metaculus_track_record/", 33 | views.metaculus_track_record, 34 | name="metaculus-track-record", 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /screenshot/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "headless-rendering" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Vasile Popescu "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "3.12.3" 10 | fastapi = "^0.109.1" 11 | playwright = "^1.33.0" 12 | python-decouple = "^3.8" 13 | sentry-sdk = {extras = ["fastapi"], version = "^1.22.2"} 14 | uvicorn = "^0.22.0" 15 | 16 | [tool.poetry.group.dev.dependencies] 17 | poetry-plugin-sort = "^0.2.0" 18 | 19 | [tool.poetry-sort] 20 | enabled = true 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /screenshot/start.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | cd /app/ 4 | source venv/bin/activate 5 | poetry run uvicorn app:app --host 0.0.0.0 --port $PORT --log-level info -------------------------------------------------------------------------------- /scripts/create_minimal_db.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -x 3 | set -e 4 | 5 | DB_NAME=test_metaculus 6 | DB_URL=postgres://postgres:postgres@localhost:5432/$DB_NAME 7 | 8 | poetry run ./manage.py create_minimal_db_subset --new_db_name $DB_NAME --drop_table 9 | 10 | pg_dump -O -f $DB_NAME.sql $DB_URL 11 | 12 | zip $DB_NAME.sql.zip $DB_NAME.sql 13 | -------------------------------------------------------------------------------- /scripts/k6-stress/Readme.md: -------------------------------------------------------------------------------- 1 | ## Stress testing simple scripts 2 | 3 | These require [k6](https://grafana.com/docs/k6/latest/), so make sure to install that as explained in their documentation. 4 | 5 | Please don't run them against the production environment. 6 | 7 | ### How to run: 8 | 9 | ```bash 10 | MAX_DURATION=60s BASE_URL=https://dev.metaculus.com METACULUS_API_TOKEN= API_VUS=100 UI_VUS=10 ITERATIONS_PER_VU=5 K6_BROWSER_HEADLESS=true k6 run scripts/k6-stress/simple-stress.js 11 | ``` 12 | -------------------------------------------------------------------------------- /scripts/nginx/etc/nginx/proxy_params: -------------------------------------------------------------------------------- 1 | proxy_set_header Host $http_host; 2 | proxy_set_header X-Real-IP $remote_addr; 3 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 4 | proxy_set_header X-Forwarded-Proto $scheme; 5 | -------------------------------------------------------------------------------- /scripts/prod/django_cron.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | cd /app/ 4 | source venv/bin/activate 5 | 6 | python3 manage.py cron 7 | -------------------------------------------------------------------------------- /scripts/prod/release.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | cd /app/ 4 | source venv/bin/activate 5 | 6 | python manage.py migrate 7 | -------------------------------------------------------------------------------- /scripts/prod/run_dramatiq.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | cd /app/ 4 | source venv/bin/activate 5 | 6 | DRAMATIQ_PROCESSES="${DRAMATIQ_PROCESSES:-8}" 7 | DRAMATIQ_THREADS="${DRAMATIQ_THREADS:-16}" 8 | 9 | python3 manage.py rundramatiq --processes $DRAMATIQ_PROCESSES --threads $DRAMATIQ_THREADS 10 | -------------------------------------------------------------------------------- /templates/admin/posts/assign_to_project.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% extends "admin/base_site.html" %} 4 | 5 | {% block content %} 6 |

{{ title }}

7 |
{% csrf_token %} 8 | {{ form.as_p }} 9 | {% for obj in posts %} 10 | 11 | {% endfor %} 12 | 13 | 14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /templates/admin/projects/add_posts_to_project.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form.html" %} 2 | {% load admin_urls %} 3 | 4 | {% block extrahead %} 5 | {{ block.super }} 6 | {% load static %} 7 | 8 | {{ form.media }} 9 | {% endblock %} 10 | 11 | {% block content %} 12 |
13 | {% csrf_token %} 14 | {{ form.as_p }} 15 | 16 |
17 |

18 | Back to Project 19 |

20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /templates/admin/scoring/leaderboard_action_descriptions.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_list.html" %} 2 | {% load i18n %} 3 | 4 | {% block object-tools %} 5 |
6 |

Available Actions:

7 |
    8 |
  • Make Primary Leaderboard: Sets selected leaderboards as their project's primary leaderboard
  • 9 |
  • Update Leaderboards: Recalculates the leaderboard - does nothing to finalized leaderboards
  • 10 |
  • Force Update Leaderboards: Forces update on selected leaderboards even if finalized
  • 11 |
  • Force Update, Finalize, and Assign Medals/Prizes: Forces update, finalizes, and assigns medals/prizes (if eleigble) for selected leaderboards.
  • 12 |
13 | {{ block.super }} 14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /templates/emails/email_greeting.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% blocktrans %} 5 | Happy Predicting,
6 | The Metaculus Team 7 | {% endblocktrans %} 8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /templates/emails/email_top.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /templates/emails/email_unsubscribe.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | {% blocktrans %} 6 | If you no longer wish to receive this type of email, you can 7 | unsubscribe here. 8 | You can also manage your notification settings on your Metaculus 9 | {% endblocktrans %} 10 | {% trans "settings page" %}. 11 |

12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/__init__.py -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_auth/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_comments/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_misc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_misc/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_misc/factories.py: -------------------------------------------------------------------------------- 1 | from django_dynamic_fixture import G 2 | 3 | from misc.models import ITNArticle 4 | from utils.dtypes import setdefaults_not_null 5 | 6 | 7 | def factory_itn_article(*, title: str = None, **kwargs) -> ITNArticle: 8 | return G(ITNArticle, **setdefaults_not_null(kwargs, title=title)) 9 | -------------------------------------------------------------------------------- /tests/unit/test_notifications/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_notifications/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_notifications/factories.py: -------------------------------------------------------------------------------- 1 | from dataclasses import asdict 2 | 3 | from django_dynamic_fixture import G 4 | 5 | from notifications.models import Notification 6 | from users.models import User 7 | from utils.dtypes import setdefaults_not_null 8 | 9 | 10 | def factory_notification( 11 | *, notification_type: str = None, recipient: User = None, params=None, **kwargs 12 | ) -> Notification: 13 | return G( 14 | Notification, 15 | **setdefaults_not_null( 16 | kwargs, 17 | type=notification_type, 18 | recipient=recipient, 19 | params=asdict(params) if params else None, 20 | ) 21 | ) 22 | -------------------------------------------------------------------------------- /tests/unit/test_posts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_posts/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_posts/test_services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_posts/test_services/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_projects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_projects/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_projects/test_communities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_projects/test_communities/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_questions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_questions/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_users/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_users/factories.py: -------------------------------------------------------------------------------- 1 | from django_dynamic_fixture import G 2 | 3 | from users.models import User 4 | from utils.dtypes import setdefaults_not_null 5 | 6 | 7 | def factory_user(*, email: str = None, username: str = None, **kwargs) -> User: 8 | return G( 9 | User, 10 | **setdefaults_not_null( 11 | kwargs, 12 | email=email, 13 | username=username, 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /tests/unit/test_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/tests/unit/test_utils/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_utils/test_aggregations.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | 4 | from utils.the_math.aggregations import summarize_array 5 | 6 | 7 | @pytest.mark.parametrize( 8 | "array, max_size, expceted_array", 9 | [ 10 | ([], 10, []), 11 | (range(10), 10, range(10)), 12 | (range(10), 150, range(10)), 13 | (range(5), 3, [0, 2, 4]), 14 | ([1, 1.1, 1.2, 1.5, 2, 3, 4, 5], 3, [1, 3, 5]), 15 | (range(10), 5, [0, 3, 5, 7, 9]), 16 | ], 17 | ) 18 | def test_summarize_array(array, max_size, expceted_array): 19 | summarized = summarize_array(array, max_size) 20 | 21 | # Check that the summarized list has the correct length 22 | assert np.allclose(summarized, expceted_array) 23 | -------------------------------------------------------------------------------- /tests/unit/test_utils/test_dtypes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from utils.dtypes import evenly_distribute_items 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "items_per_source, n, expected_output", 8 | [ 9 | ([[1, 2, 3], [4, 5, 6, 7], [8, 9]], 5, [1, 4, 8, 2, 5]), 10 | ([["a", "b"], ["c"]], 5, ["a", "c", "b"]), 11 | ([[], [1, 2], []], 3, [1, 2]), 12 | ([[], [], []], 5, []), 13 | ([[1], [2], [3]], 3, [1, 2, 3]), 14 | ], 15 | ) 16 | def test_evenly_distribute_items(items_per_source, n, expected_output): 17 | assert evenly_distribute_items(items_per_source, n) == expected_output 18 | -------------------------------------------------------------------------------- /tests/unit/test_utils/test_middlewares.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | class TestAuthenticationRequiredMiddleware: 5 | def test_anon(self, anon_client): 6 | settings.PUBLIC_AUTHENTICATION_REQUIRED = True 7 | response = anon_client.get("/api/posts/") 8 | 9 | assert response.status_code == 404 10 | 11 | def test_user(self, user1_client): 12 | settings.PUBLIC_AUTHENTICATION_REQUIRED = True 13 | response = user1_client.get("/api/posts/") 14 | 15 | assert response.status_code == 200 16 | -------------------------------------------------------------------------------- /users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/users/__init__.py -------------------------------------------------------------------------------- /users/migrations/0002_user_is_onboarding_complete.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-21 18:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | def migrate_is_onboarding_complete(apps, schema_editor): 7 | """ 8 | We assume that all existing users with an activated email have completed onboarding. 9 | """ 10 | 11 | User = apps.get_model("users", "User") 12 | User.objects.filter(is_active=True).update(is_onboarding_complete=True) 13 | 14 | 15 | class Migration(migrations.Migration): 16 | dependencies = [ 17 | ("users", "0001_initial"), 18 | ] 19 | 20 | operations = [ 21 | migrations.AddField( 22 | model_name="user", 23 | name="is_onboarding_complete", 24 | field=models.BooleanField(default=False), 25 | ), 26 | migrations.RunPython(migrate_is_onboarding_complete, migrations.RunPython.noop), 27 | ] 28 | -------------------------------------------------------------------------------- /users/migrations/0004_alter_usercampaignregistration_key.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-11-28 12:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0003_usercampaignregistration"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="usercampaignregistration", 15 | name="key", 16 | field=models.CharField( 17 | default="-", 18 | help_text="Key to track the campaign the user registered through.", 19 | max_length=200, 20 | ), 21 | preserve_default=False, 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /users/migrations/0005_user_is_spam.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-12-02 17:34 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0004_alter_usercampaignregistration_key"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="user", 15 | name="is_spam", 16 | field=models.BooleanField(db_index=True, default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /users/migrations/0006_auto_fix_spelling_campaign_registration.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-12-30 11:18 2 | 3 | from django.db import migrations 4 | 5 | 6 | def fw_migration(apps, schema_editor): 7 | """ 8 | Fix the speling of 'undegrad' to 'undergrad' in the UserCampaignRegistration details JSON field 9 | """ 10 | 11 | UserCampaignRegistration = apps.get_model("users", "UserCampaignRegistration") 12 | 13 | for registration in UserCampaignRegistration.objects.all(): 14 | details = registration.details 15 | if "undegrad" in details: 16 | details["undergrad"] = details.pop("undegrad") 17 | registration.save() 18 | 19 | 20 | class Migration(migrations.Migration): 21 | 22 | dependencies = [ 23 | ("users", "0005_user_is_spam"), 24 | ] 25 | 26 | operations = [ 27 | migrations.RunPython(fw_migration, migrations.RunPython.noop), 28 | ] 29 | -------------------------------------------------------------------------------- /users/migrations/0007_user_is_superuser_idx.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-02-18 15:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("auth", "0012_alter_user_first_name_max_length"), 9 | ("users", "0006_auto_fix_spelling_campaign_registration"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddIndex( 14 | model_name="user", 15 | index=models.Index(models.F("is_superuser"), name="is_superuser_idx"), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /users/migrations/0010_alter_user_is_bot.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.8 on 2025-05-16 15:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0009_alter_userspamactivity_content_type"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="user", 15 | name="is_bot", 16 | field=models.BooleanField(db_index=True, default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/users/migrations/__init__.py -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metaculus/metaculus/836d16d2c28c98e0c195c75f47a6283ed64232ee/utils/__init__.py -------------------------------------------------------------------------------- /utils/cache.py: -------------------------------------------------------------------------------- 1 | from django.core.cache import cache 2 | 3 | 4 | def cache_get_or_set(key, f: callable, version: int = None, **kwargs): 5 | # Try to get the result from the cache 6 | result = cache.get(key, version=version) 7 | 8 | if not result: 9 | result = f() 10 | cache.set(key, result, version=version, **kwargs) 11 | 12 | return result 13 | -------------------------------------------------------------------------------- /utils/call_llm.py: -------------------------------------------------------------------------------- 1 | def call_llm(prompt: str, sys_prompt: str = None): 2 | return "" 3 | -------------------------------------------------------------------------------- /utils/debug.py: -------------------------------------------------------------------------------- 1 | import time 2 | import functools 3 | 4 | 5 | def print_runtime(func): 6 | @functools.wraps(func) 7 | def wrapper(*args, **kwargs): 8 | start_time = time.time() 9 | result = func(*args, **kwargs) 10 | end_time = time.time() 11 | runtime = end_time - start_time 12 | print(f"{func.__name__} took {runtime:.6f} seconds") 13 | return result 14 | 15 | return wrapper 16 | -------------------------------------------------------------------------------- /utils/management.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from multiprocessing import Pool, Manager 3 | from typing import Callable 4 | 5 | import django 6 | import numpy as np 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def parallel_command_executor( 12 | items: list, handler: Callable[[list, int], None], num_processes: int = 1 13 | ): 14 | # Split post_ids into chunks for each process 15 | item_chunks = [list(x) for x in np.array_split(items, num_processes) if x.size] 16 | 17 | with Manager(): 18 | with Pool(processes=num_processes, initializer=django.setup) as pool: 19 | pool.starmap( 20 | handler, 21 | [(chunk, worker_idx) for worker_idx, chunk in enumerate(item_chunks)], 22 | ) 23 | -------------------------------------------------------------------------------- /utils/paginator.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from rest_framework.pagination import LimitOffsetPagination as _LimitOffsetPagination 3 | from rest_framework.response import Response 4 | 5 | 6 | class LimitOffsetPagination(_LimitOffsetPagination): 7 | max_limit = settings.REST_FRAMEWORK["MAX_LIMIT"] 8 | 9 | 10 | class CountlessLimitOffsetPagination(LimitOffsetPagination): 11 | """ 12 | We don't always need to extract total records count 13 | Which also might be expensive sometimes 14 | """ 15 | 16 | def get_count(self, queryset): 17 | return float("inf") 18 | 19 | def get_paginated_response(self, data): 20 | return Response( 21 | { 22 | "next": self.get_next_link(), 23 | "previous": self.get_previous_link(), 24 | "results": data, 25 | } 26 | ) 27 | -------------------------------------------------------------------------------- /utils/requests.py: -------------------------------------------------------------------------------- 1 | from rest_framework.request import Request 2 | 3 | 4 | def get_request_ip(request: Request): 5 | return ( 6 | # Header coming from Cloudflare 7 | request.headers.get("CF-Connecting-IP") 8 | # Or coming from Nginx 9 | or request.headers.get("X-Real-IP") 10 | ) 11 | -------------------------------------------------------------------------------- /utils/types.py: -------------------------------------------------------------------------------- 1 | # Taken from https://github.com/HackSoftware/Django-Styleguide-Example 2 | from typing import TypeVar 3 | 4 | from django.db import models 5 | 6 | # Generic type for a Django model 7 | DjangoModelType = TypeVar("DjangoModelType", bound=models.Model) 8 | -------------------------------------------------------------------------------- /utils/typing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | ForecastValues = np.ndarray | list[float] # values from a single forecast 4 | ForecastsValues = np.ndarray | list[list[float]] # values from multiple forecasts 5 | Weights = np.ndarray | list[float] 6 | Percentiles = np.ndarray | list[float] 7 | -------------------------------------------------------------------------------- /utils/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from utils import views 4 | 5 | urlpatterns = [ 6 | path( 7 | "aggregation_explorer/", 8 | views.aggregation_explorer_api_view, 9 | name="aggregation_explorer", 10 | ), 11 | path( 12 | "data/email/", 13 | views.email_data_view, 14 | name="email_data", 15 | ), 16 | path( 17 | "data/download/", 18 | views.download_data_view, 19 | name="download_data", 20 | ), 21 | ] 22 | --------------------------------------------------------------------------------