Skip to content

Commit

Permalink
Merge branch 'main' into fix/2145-comment-numeric-prediction-format
Browse files Browse the repository at this point in the history
  • Loading branch information
lsabor authored Feb 28, 2025
2 parents 6447373 + 67dd3d2 commit 52dcd46
Show file tree
Hide file tree
Showing 51 changed files with 5,134 additions and 494 deletions.
11 changes: 10 additions & 1 deletion front_end/messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1089,5 +1089,14 @@
"pickQuestion": "Vybrat otázku",
"showApiToken": "Zobrazit API token",
"copyApiToken": "Kopírovat API token",
"copiedApiTokenMessage": "API token byl zkopírován do vaší schránky"
"copiedApiTokenMessage": "API token byl zkopírován do vaší schránky",
"Indexes": "Indexy",
"Index": "Index",
"forecastFlow": "Předpověď průtoků",
"viewQuestions": "Zobrazit otázky<bold>({count})</bold>",
"started": "Začátek",
"starts": "Začíná",
"lastDayForPrizeParticipation": "Poslední den pro účast v soutěži o ceny",
"questionsResolved": "Otázky vyřešeny",
"winnersAnnounced": "Vítězové vyhlášeni"
}
11 changes: 10 additions & 1 deletion front_end/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,8 @@
"tournamentsHero3": "<scores>Learn</scores> how tournaments are scored.",
"tournamentsHero4": "<email>Reach out</email> to discuss launching a tournament on what’s important to you.",
"ActiveTournaments": "Active Tournaments",
"Indexes": "Indexes",
"Index": "Index",
"Tournament": "Tournament",
"QuestionSeries": "Question Series",
"Archive": "Archive",
Expand Down Expand Up @@ -1086,5 +1088,12 @@
"noAggregationData": "No aggregation data",
"selectParentQuestion": "Select Parent Question",
"selectChildQuestion": "Select Child Question",
"pickQuestion": "Pick Question"
"pickQuestion": "Pick Question",
"forecastFlow": "Forecast Flow",
"viewQuestions": "View Questions<bold>({count})</bold>",
"started": "Started",
"starts": "Starts",
"lastDayForPrizeParticipation": "Last day for prize participation",
"questionsResolved": "Questions resolved",
"winnersAnnounced": "Winners announced"
}
11 changes: 10 additions & 1 deletion front_end/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1089,5 +1089,14 @@
"pickQuestion": "Elegir pregunta",
"showApiToken": "Mostrar token de API",
"copyApiToken": "Copiar token de API",
"copiedApiTokenMessage": "El token de API se ha copiado a su portapapeles"
"copiedApiTokenMessage": "El token de API se ha copiado a su portapapeles",
"Indexes": "Índices",
"Index": "Índice",
"forecastFlow": "Flujo de Pronóstico",
"viewQuestions": "Ver Preguntas<bold>({count})</bold>",
"started": "Comenzado",
"starts": "Empieza",
"lastDayForPrizeParticipation": "Último día para participar en el premio",
"questionsResolved": "Preguntas resueltas",
"winnersAnnounced": "Ganadores anunciados"
}
11 changes: 10 additions & 1 deletion front_end/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -1087,5 +1087,14 @@
"pickQuestion": "Escolher Pergunta",
"showApiToken": "Mostrar token da API",
"copyApiToken": "Copiar token da API",
"copiedApiTokenMessage": "Token da API foi copiado para sua área de transferência"
"copiedApiTokenMessage": "Token da API foi copiado para sua área de transferência",
"Indexes": "Índices",
"Index": "Índice",
"forecastFlow": "Fluxo de Previsão",
"viewQuestions": "Ver Perguntas<bold>({count})</bold>",
"started": "Iniciado",
"starts": "Começa",
"lastDayForPrizeParticipation": "Último dia para participação no prêmio",
"questionsResolved": "Perguntas resolvidas",
"winnersAnnounced": "Vencedores anunciados"
}
11 changes: 10 additions & 1 deletion front_end/messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1091,5 +1091,14 @@
"pickQuestion": "选择问题",
"showApiToken": "显示 API 令牌",
"copyApiToken": "复制 API 令牌",
"copiedApiTokenMessage": "API 令牌已复制到您的剪贴板"
"copiedApiTokenMessage": "API 令牌已复制到您的剪贴板",
"Indexes": "索引",
"Index": "索引",
"forecastFlow": "预测流程",
"viewQuestions": "查看问题<bold>({count})</bold>",
"started": "已开始",
"starts": "开始",
"lastDayForPrizeParticipation": "奖品参与的最后一天",
"questionsResolved": "问题已解决",
"winnersAnnounced": "公布获奖者"
}
8 changes: 8 additions & 0 deletions front_end/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ const nextConfig = {
},
];
},
async rewrites() {
return [
{
source: "/index/:slug",
destination: "/tournament/:slug",
},
];
},
eslint: {
ignoreDuringBuilds: true,
},
Expand Down
200 changes: 137 additions & 63 deletions front_end/src/app/(main)/(tournaments)/tournament/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { isNil } from "lodash";
import { Metadata } from "next";
import dynamic from "next/dynamic";
import Image from "next/image";
import Link from "next/link";
import { getLocale, getTranslations } from "next-intl/server";
import { FC } from "react";
import { FC, Suspense } from "react";
import invariant from "ts-invariant";

import ProjectContributions from "@/app/(main)/(leaderboards)/contributions/components/project_contributions";
import ProjectLeaderboard from "@/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard";
import IndexSection from "@/app/(main)/(tournaments)/tournament/components/index";
import TournamentSubscribeButton from "@/app/(main)/(tournaments)/tournament/components/tournament_subscribe_button";
import HtmlContent from "@/components/html_content";
import TournamentFilters from "@/components/tournament_filters";
Expand All @@ -24,7 +26,7 @@ import { getPublicSettings } from "@/utils/public_settings.server";

import TournamentDropdownMenu from "../components/dropdown_menu";
import TournamentFeed from "../components/tournament_feed";

import TournamentTimeline from "../components/tournament_timeline";
const LazyProjectMembers = dynamic(() => import("../components/members"), {
ssr: false,
});
Expand Down Expand Up @@ -60,38 +62,48 @@ export default async function TournamentSlug({ params }: Props) {
const tournament = await ProjectsApi.getTournament(params.slug);
invariant(tournament, `Tournament not found: ${params.slug}`);
const { PUBLIC_MINIMAL_UI } = getPublicSettings();

const currentUser = await ProfileApi.getMyProfile();

const t = await getTranslations();
const locale = await getLocale();
const isQuestionSeries = tournament.type === TournamentType.QuestionSeries;
const title = isQuestionSeries ? t("QuestionSeries") : t("Tournament");
const title = isQuestionSeries
? t("QuestionSeries")
: tournament.type === TournamentType.Index
? t("Index")
: t("Tournament");
const questionsTitle = isQuestionSeries
? t("SeriesContents")
: t("questions");

const indexWeights = tournament.index_weights ?? [];

return (
<main className="mx-auto mb-16 mt-4 min-h-min w-full max-w-[780px] flex-auto px-0">
<div className="bg-gray-0 dark:bg-gray-0-dark">
<div
className={cn(
"flex flex-wrap items-center justify-between gap-2.5 rounded-t px-3 py-1.5 text-[20px] uppercase text-gray-100 dark:text-gray-100-dark",
tournament.type === TournamentType.QuestionSeries
? "bg-gray-500 dark:bg-gray-500-dark"
: "bg-blue-600 dark:bg-blue-600-dark"
)}
>
<Link
href={"/tournaments"}
className="no-underline hover:text-gray-400 dark:hover:text-gray-400-dark"
<main className="mx-auto mb-16 min-h-min w-full max-w-[780px] flex-auto px-0 sm:mt-[52px]">
{/* header block */}
<div className="overflow-hidden rounded-b-md bg-gray-0 dark:bg-gray-0-dark sm:rounded-md">
<div className="relative h-[130px] w-full">
<div
className={cn(
"absolute z-10 flex h-6 w-full flex-wrap items-center justify-between gap-2.5 bg-transparent p-[10px] text-[20px] uppercase text-gray-100 dark:text-gray-100-dark"
)}
>
{title}
</Link>
<TournamentDropdownMenu tournament={tournament} />
</div>
{!!tournament.header_image && (
<div className="relative h-[130px] w-full">
<div className="flex items-center gap-2">
<Link
href={"/tournaments"}
className="block rounded-sm bg-black/30 px-1.5 py-1 text-xs font-bold leading-4 text-gray-0 no-underline hover:text-gray-400 dark:hover:text-gray-400-dark"
>
{title}
</Link>
{tournament.default_permission === null && (
<strong className="block rounded-sm bg-black/30 px-1.5 py-1 text-xs font-bold uppercase leading-4 text-gray-0">
{t("private")}
</strong>
)}
</div>
<TournamentDropdownMenu tournament={tournament} />
</div>
{!!tournament.header_image && (
<Image
src={tournament.header_image}
alt=""
Expand All @@ -100,20 +112,29 @@ export default async function TournamentSlug({ params }: Props) {
className="size-full object-cover object-center"
unoptimized
/>
)}
</div>
<div className="px-4 pb-5 pt-4 sm:p-8">
<div className="flex items-start justify-between gap-1 sm:gap-4">
<h1 className="m-0 text-xl text-blue-800 dark:text-blue-800-dark md:text-2xl lg:text-3xl xl:text-4xl">
{tournament.name}
</h1>

<div>
<TournamentSubscribeButton
user={currentUser}
tournament={tournament}
/>
</div>
</div>
)}
<div className="bg-gray-0 px-3 pb-4 dark:bg-gray-0-dark">
<div className="flex justify-between gap-1 pb-2">
<h1>{tournament.name}</h1>
{tournament.default_permission === null && (
<strong className="mt-4 self-start rounded-sm bg-blue-300 px-1 text-sm uppercase text-gray-900 dark:bg-blue-300-dark dark:text-gray-900-dark">
{t("private")}
</strong>
)}
</div>
<div className="flex flex-row items-center justify-between">
<div className="flex flex-wrap gap-9 py-4">
{tournament.prize_pool !== null && (

{tournament.type === TournamentType.Tournament ? (
<Suspense fallback={<Skeleton />}>
<TournamentTimeline tournament={tournament} />
</Suspense>
) : (
<div className="flex flex-wrap gap-x-9 gap-y-4 py-4">
{!isNil(tournament.prize_pool) && (
<TournamentStat
title={t("prizePool")}
text={"$" + Number(tournament.prize_pool).toLocaleString()}
Expand All @@ -123,42 +144,76 @@ export default async function TournamentSlug({ params }: Props) {
title={t("StartDate")}
text={formatDate(locale, new Date(tournament.start_date))}
/>
<TournamentStat
title={t("EndDate")}
text={formatDate(locale, new Date(tournament.close_date))}
/>
{!isNil(tournament.close_date) && (
<TournamentStat
title={t("EndDate")}
text={formatDate(locale, new Date(tournament.close_date))}
/>
)}
<TournamentStat
title={t("questions")}
text={tournament.questions_count.toString()}
/>
</div>
<div>
<TournamentSubscribeButton
user={currentUser}
tournament={tournament}
/>
</div>
</div>
)}
</div>
</div>

{/* Links block */}
<div className="mx-4 mt-4 flex flex-row justify-between gap-2 lg:mx-0">
{/* TODO: Uncomment this when the forecast flow is implemented */}
{/* <Link href="/forecastFlowLink" className="flex-1">
<Button className="w-full border-blue-400 text-sm text-blue-700 dark:border-blue-400-dark dark:text-blue-700-dark md:text-lg">
{t("forecastFlow")}
</Button>
</Link> */}
<Link href="#questions" className="flex-1">
<Button className="w-full gap-1 border-blue-400 text-sm text-blue-700 dark:border-blue-400-dark dark:text-blue-700-dark md:text-lg">
{t.rich("viewQuestions", {
count: tournament.questions_count,
bold: (chunks) => <span className="font-bold">{chunks}</span>,
})}
</Button>
</Link>
</div>

{/* Description block */}
<div className="mx-4 mt-4 rounded-md bg-gray-0 p-4 dark:bg-gray-0-dark sm:p-8 lg:mx-0">
<div>
<HtmlContent content={tournament.description} />

<div className="mt-3 flex flex-col gap-3">
<ProjectLeaderboard
projectId={tournament.id}
userId={currentUser?.id}
isQuestionSeries={isQuestionSeries}
/>
{currentUser && (
<ProjectContributions
project={tournament}
userId={currentUser.id}
{indexWeights.length > 0 && (
<IndexSection indexWeights={indexWeights} />
)}

{tournament.score_type && (
<div className="mt-3 flex flex-col gap-3">
<ProjectLeaderboard
projectId={tournament.id}
userId={currentUser?.id}
isQuestionSeries={isQuestionSeries}
/>
)}
</div>
{currentUser && (
<ProjectContributions
project={tournament}
userId={currentUser.id}
/>
)}
</div>
)}
</div>
</div>

<section className="mx-2 border-t border-t-[#e5e7eb] px-1 py-4">
{/* Questions block */}
<div
id="questions"
className="mt-4 scroll-mt-nav rounded-md bg-gray-0 p-4 dark:bg-gray-0-dark xs:mx-4 sm:p-8 lg:mx-0"
>
<section className="mx-2 px-1 py-4">
<div className="mb-5 flex flex-row justify-between">
<h2 className="m-0">{questionsTitle}</h2>
<h2 className="m-0 text-blue-800 dark:text-blue-800-dark">
{questionsTitle}
</h2>
{currentUser && (
<Button href={`/questions/create?tournament_id=${tournament.id}`}>
+ {t("question")}
Expand All @@ -169,6 +224,7 @@ export default async function TournamentSlug({ params }: Props) {
<TournamentFeed tournament={tournament} />
</section>
</div>

{!PUBLIC_MINIMAL_UI &&
[ProjectPermissions.ADMIN, ProjectPermissions.CURATOR].includes(
tournament.user_permission
Expand All @@ -181,8 +237,26 @@ const TournamentStat: FC<{ title: string; text: string }> = ({
text,
title,
}) => (
<div className="flex flex-col">
<span className="font-semibold capitalize leading-5">{title}</span>
<div className="flex flex-col text-blue-800 dark:text-blue-800-dark">
<span className="text-sm font-normal capitalize leading-5 opacity-50">
{title}
</span>
<span className="text-xl font-bold leading-6">{text}</span>
</div>
);

const Skeleton: FC = () => {
return (
<div className="mt-4 flex min-h-20 flex-col gap-x-5 gap-y-4 sm:mt-5 sm:flex-row">
<div className="flex flex-1 animate-pulse flex-col justify-between">
<div className="h-4 w-full rounded bg-gray-200 dark:bg-gray-700" />
<div className="my-3 h-1 w-full rounded bg-gray-200 dark:bg-gray-700" />
<div className="h-4 w-full rounded bg-gray-200 dark:bg-gray-700" />
</div>

<div className="flex max-h-20 animate-pulse items-center justify-center rounded bg-gray-200 py-1.5 dark:bg-gray-700 sm:w-[200px] sm:flex-col sm:py-3">
<div className="h-6 w-24 rounded bg-gray-300 dark:bg-gray-600 sm:h-8 sm:w-32" />
</div>
</div>
);
};
Loading

0 comments on commit 52dcd46

Please sign in to comment.