Skip to content

Commit 3119fb9

Browse files
committed
Improve group tournaments
1 parent 6b99425 commit 3119fb9

31 files changed

Lines changed: 664 additions & 243 deletions

apps/codebattle/assets/css/external.scss

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,8 @@ $dark-red: #b00020;
396396
.cb-custom-event-stage-grid {
397397
display: grid;
398398
grid-template-columns: minmax(170px, 1.05fr) minmax(110px, 0.7fr) repeat(
399-
4,
400-
minmax(90px, 0.9fr)
399+
5,
400+
minmax(80px, 0.9fr)
401401
);
402402
align-items: center;
403403
column-gap: 1rem;
@@ -681,3 +681,38 @@ $dark-red: #b00020;
681681
color: white;
682682
padding-right: 25px;
683683
}
684+
685+
.cb-bg-panel {
686+
background-color: #2a2a35;
687+
background: linear-gradient(135deg, #2a2a35 0%, #171720 100%);
688+
}
689+
690+
.cb-text {
691+
color: #ebeff5;
692+
}
693+
694+
.cb-bg-panel.modal-content,
695+
.cb-bg-panel .modal-header,
696+
.cb-bg-panel .modal-body,
697+
.cb-bg-panel .modal-footer {
698+
color: #ebeff5;
699+
border-color: rgba(255, 255, 255, 0.08);
700+
background-color: transparent;
701+
}
702+
703+
.cb-bg-panel .modal-title {
704+
color: #ffffff;
705+
}
706+
707+
.cb-bg-panel .close,
708+
.cb-bg-panel .btn-close {
709+
color: #ebeff5;
710+
text-shadow: none;
711+
opacity: 0.8;
712+
}
713+
714+
.cb-bg-panel .close:hover,
715+
.cb-bg-panel .btn-close:hover {
716+
color: #ffffff;
717+
opacity: 1;
718+
}

apps/codebattle/assets/js/widgets/config/modalCodes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const modalCodes = {
88
eventStageModal: "event_stage_modal",
99
calendarEventModal: "calendar_event_modal",
1010
tournamentModal: "tournament_modal",
11+
nextStageGroupTournamentModal: "next_stage_group_tournament_modal",
1112
};
1213

1314
export default modalCodes;

apps/codebattle/assets/js/widgets/middlewares/Room.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import NiceModal from "@ebay/nice-modal-react";
12
import Gon from "gon";
23
import { camelizeKeys } from "humps";
34
import debounce from "lodash/debounce";
45
import find from "lodash/find";
56

7+
import ModalCodes from "@/config/modalCodes";
68
import { makeGameUrl } from "@/utils/urlBuilders";
79

810
import { channelMethods, channelTopics } from "../../socket";
@@ -619,7 +621,9 @@ export const activeGameReady =
619621

620622
const handleTournamentFinished = (response) => {
621623
if (response.tournament.groupTournamentId) {
622-
window.location.href = `/group_tournaments/${response.tournament.groupTournamentId}`;
624+
NiceModal.show(ModalCodes.nextStageGroupTournamentModal, {
625+
groupTournamentId: response.tournament.groupTournamentId,
626+
});
623627
}
624628
};
625629

apps/codebattle/assets/js/widgets/pages/event/ParticipantDashboard.jsx

Lines changed: 36 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import React, { useEffect } from "react";
22

33
import NiceModal, { unregister } from "@ebay/nice-modal-react";
44
import cn from "classnames";
5-
import Gon from "gon";
65
import upperCase from "lodash/upperCase";
76
import { useSelector } from "react-redux";
87

8+
import NextStageGroupTournamentModal from "@/pages/game/NextStageGroupTournamentModal";
9+
910
import i18n from "../../../i18n";
1011
import ModalCodes from "../../config/modalCodes";
1112
import { currentUserSelector, participantDataSelector, eventSelector } from "../../selectors";
@@ -14,61 +15,14 @@ import EventStageConfirmationModal from "./EventStageConfirmationModal";
1415
import NotPassedIcon from "./NotPassedIcon";
1516
import PassedIcon from "./PassedIcon";
1617

17-
const externalPlatformLoginUrl = Gon.getAsset("external_platform_login_url");
18-
const externalPlatformName = Gon.getAsset("external_platform_name") || "External platform";
19-
const externalPlatformProfileUrlTemplate = Gon.getAsset("external_platform_profile_url_template");
20-
21-
const getExternalPlatformProfileUrl = (login) => {
22-
if (!login || !externalPlatformProfileUrlTemplate) {
23-
return null;
24-
}
25-
26-
return externalPlatformProfileUrlTemplate.replace("USER_LOGIN", login);
27-
};
28-
29-
const renderExternalPlatformLink = (user) => {
30-
if (user.externalPlatformId) {
31-
const profileUrl = getExternalPlatformProfileUrl(user.externalOauthLogin);
32-
const linkLabel = user.externalOauthLogin || user.externalPlatformId;
33-
34-
if (!profileUrl) {
35-
return <span className="cb-custom-event-profile-data ms-2">{linkLabel}</span>;
36-
}
37-
38-
return (
39-
<a
40-
className="cb-custom-event-profile-data ms-2"
41-
href={profileUrl}
42-
target="_blank"
43-
rel="noreferrer"
44-
>
45-
{linkLabel}
46-
</a>
47-
);
48-
}
49-
50-
if (!externalPlatformLoginUrl) {
51-
return <span className="cb-custom-event-profile-data ms-2">{i18n.t("Sign in")}</span>;
52-
}
53-
54-
return (
55-
<a
56-
className="cb-custom-event-profile-data ms-2"
57-
href={externalPlatformLoginUrl}
58-
target="_blank"
59-
rel="noreferrer"
60-
>
61-
{i18n.t("Sign in")}
62-
</a>
63-
);
64-
};
65-
6618
function ParticipantDashboard() {
6719
useEffect(() => {
6820
NiceModal.register(ModalCodes.eventStageModal, EventStageConfirmationModal);
21+
NiceModal.register(ModalCodes.nextStageGroupTournamentModal, NextStageGroupTournamentModal);
6922

7023
const unregisterModals = () => {
7124
unregister(ModalCodes.eventStageModal);
25+
unregister(ModalCodes.nextStageGroupTournamentModal);
7226
};
7327

7428
return unregisterModals;
@@ -79,6 +33,23 @@ function ParticipantDashboard() {
7933
const participantData = useSelector(participantDataSelector);
8034
const event = useSelector(eventSelector);
8135

36+
const pendingGroupTournamentStage = participantData?.stages?.find(
37+
(stage) =>
38+
stage.tournamentFinished && stage.groupTournamentId && !stage.groupTournamentFinished,
39+
);
40+
41+
const pendingGroupTournamentId = pendingGroupTournamentStage?.groupTournamentId;
42+
const pendingNextRoundText = pendingGroupTournamentStage?.nextRoundText;
43+
44+
useEffect(() => {
45+
if (pendingGroupTournamentId) {
46+
NiceModal.show(ModalCodes.nextStageGroupTournamentModal, {
47+
groupTournamentId: pendingGroupTournamentId,
48+
bodyText: pendingNextRoundText,
49+
});
50+
}
51+
}, [pendingGroupTournamentId, pendingNextRoundText]);
52+
8253
if (!participantData || !event) {
8354
return (
8455
<div className="container-fluid">
@@ -116,10 +87,6 @@ function ParticipantDashboard() {
11687
{i18n.t("Category")}
11788
<span className="cb-custom-event-profile-data ms-2">{user.category}</span>
11889
</div>
119-
<div className="d-flex text-white justify-content-between cb-custom-event-profile my-1 mx-1 w-100">
120-
{externalPlatformName}
121-
{renderExternalPlatformLink(user)}
122-
</div>
12390
</div>
12491
</div>
12592
</div>
@@ -138,6 +105,9 @@ function ParticipantDashboard() {
138105
<div className="d-flex justify-content-center align-items-center">
139106
{i18n.t("Score/Total")}
140107
</div>
108+
<div className="d-flex justify-content-center align-items-center">
109+
{i18n.t("AI score")}
110+
</div>
141111
<div className="d-flex justify-content-center align-items-center">
142112
{i18n.t("Time spent")}
143113
</div>
@@ -156,7 +126,7 @@ function ParticipantDashboard() {
156126
<div className="d-flex justify-content-center cb-custom-event-stage-action">
157127
{stage.isStageAvailableForUser && stage.type === "tournament" && (
158128
<div className="action-button">
159-
{stage.groupTournamentId ? (
129+
{stage.groupTournamentId && stage.tournamentFinished ? (
160130
<a
161131
type="button"
162132
className="btn btn-success rounded-pill px-4"
@@ -242,6 +212,17 @@ function ParticipantDashboard() {
242212
</div>
243213
{stage.winsCount}/{stage.gamesCount}
244214
</div>
215+
<div
216+
className={cn(
217+
"d-flex d-sm-flex cb-custom-event-stage-cell",
218+
"justify-content-center align-items-center text-center",
219+
)}
220+
>
221+
<div className="d-block d-xl-none me-2 font-weight-bold">
222+
{i18n.t("AI score")}:
223+
</div>
224+
{stage.aiScore}
225+
</div>
245226
<div
246227
className={cn(
247228
"d-flex d-sm-flex cb-custom-event-stage-cell",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React, { memo, useEffect } from "react";
2+
3+
import NiceModal, { useModal } from "@ebay/nice-modal-react";
4+
import i18n from "i18next";
5+
import Button from "react-bootstrap/Button";
6+
7+
import Modal from "@/components/BootstrapModal";
8+
9+
import ModalCodes from "../../config/modalCodes";
10+
11+
const NextStageGroupTournamentModal = NiceModal.create(({ groupTournamentId, bodyText }) => {
12+
const modal = useModal(ModalCodes.nextStageGroupTournamentModal);
13+
14+
useEffect(() => {
15+
if (modal.visible) {
16+
NiceModal.hide(ModalCodes.gameResultModal);
17+
NiceModal.hide(ModalCodes.tournamentStatisticsModal);
18+
NiceModal.hide(ModalCodes.premiumRestrictionModal);
19+
NiceModal.hide(ModalCodes.taskDescriptionModal);
20+
NiceModal.hide(ModalCodes.awardModal);
21+
}
22+
}, [modal.visible]);
23+
24+
if (!groupTournamentId) {
25+
return null;
26+
}
27+
28+
const href = `/group_tournaments/${groupTournamentId}`;
29+
const text =
30+
bodyText ||
31+
i18n.t("Your next step is the AI-round group tournament. Click the button below to continue.");
32+
33+
return (
34+
<Modal centered show={modal.visible} onHide={modal.hide} contentClassName="cb-bg-panel cb-text">
35+
<Modal.Header closeButton>
36+
<Modal.Title>{i18n.t("Tournament finished")}</Modal.Title>
37+
</Modal.Header>
38+
<Modal.Body>
39+
<p className="mb-0">{text}</p>
40+
</Modal.Body>
41+
<Modal.Footer>
42+
<Button variant="secondary" onClick={modal.hide}>
43+
{i18n.t("Close")}
44+
</Button>
45+
<Button as="a" href={href} variant="primary">
46+
{i18n.t("Go to AI-round group tournament")}
47+
</Button>
48+
</Modal.Footer>
49+
</Modal>
50+
);
51+
});
52+
53+
export default memo(NextStageGroupTournamentModal);

apps/codebattle/assets/js/widgets/pages/groupTournament/EvolutionPanel.jsx

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,28 @@ const formatInsertedAt = (insertedAt) => {
2424

2525
const isSuccess = (item) => item?.status === "success";
2626

27-
const buildRunMeta = (item) => {
28-
if (!item || typeof item !== "object") {
29-
return item ? String(item) : null;
27+
const findBestRunId = (items) => {
28+
if (!items || items.length === 0) {
29+
return null;
3030
}
3131

32-
return [i18n.t("Score %{score}", { score: item.score ?? 0 }), formatInsertedAt(item.insertedAt)]
33-
.filter(Boolean)
34-
.join(" · ");
32+
let bestId = null;
33+
let bestScore = -Infinity;
34+
35+
items.forEach((item) => {
36+
const score = item?.score ?? 0;
37+
if (score > bestScore) {
38+
bestScore = score;
39+
bestId = item?.id;
40+
}
41+
});
42+
43+
return bestId;
3544
};
3645

3746
function EvolutionPanel({ items, tournamentStatus, runId, setRunId, repoUrl }) {
47+
const bestRunId = findBestRunId(items);
48+
3849
return (
3950
<div className="card cb-card border cb-border-color rounded shadow-sm">
4051
<div className="card-header py-2 border-bottom cb-border-color">
@@ -63,9 +74,17 @@ function EvolutionPanel({ items, tournamentStatus, runId, setRunId, repoUrl }) {
6374
{items.map((item, idx) => {
6475
const isActive = runId === item?.id;
6576
const success = isSuccess(item);
66-
const borderColor = success ? "rgba(40, 167, 69, 0.95)" : "rgba(220, 53, 69, 0.95)";
67-
const meta = buildRunMeta(item);
77+
const isBest = item?.id != null && item.id === bestRunId;
78+
const statusColor = success ? "rgba(40, 167, 69, 0.95)" : "rgba(220, 53, 69, 0.95)";
79+
const goldColor = "rgba(255, 193, 7, 0.95)";
80+
const ringColor = isBest
81+
? goldColor
82+
: isActive
83+
? "rgba(96, 165, 250, 0.95)"
84+
: "rgba(99, 102, 121, 0.95)";
6885
const title = `v${items.length - idx}`;
86+
const score = item?.score ?? 0;
87+
const time = formatInsertedAt(item?.insertedAt);
6988

7089
return (
7190
<button
@@ -74,18 +93,16 @@ function EvolutionPanel({ items, tournamentStatus, runId, setRunId, repoUrl }) {
7493
onClick={() => setRunId(item?.id)}
7594
className="rounded p-2 text-left bg-transparent mb-2"
7695
style={{
77-
borderTop: isActive
78-
? "1px solid rgba(96, 165, 250, 0.95)"
79-
: "1px solid rgba(99, 102, 121, 0.95)",
80-
borderRight: isActive
81-
? "1px solid rgba(96, 165, 250, 0.95)"
82-
: "1px solid rgba(99, 102, 121, 0.95)",
83-
borderBottom: isActive
84-
? "1px solid rgba(96, 165, 250, 0.95)"
85-
: "1px solid rgba(99, 102, 121, 0.95)",
86-
borderLeft: `3px solid ${borderColor}`,
96+
borderTop: `${isBest ? 2 : 1}px solid ${ringColor}`,
97+
borderRight: `${isBest ? 2 : 1}px solid ${ringColor}`,
98+
borderBottom: `${isBest ? 2 : 1}px solid ${ringColor}`,
99+
borderLeft: `3px solid ${statusColor}`,
87100
backgroundColor: isActive ? "rgba(96, 165, 250, 0.25)" : "transparent",
88-
boxShadow: isActive ? "0 0 0 1px rgba(96, 165, 250, 0.5)" : "none",
101+
boxShadow: isBest
102+
? "0 0 0 1px rgba(255, 193, 7, 0.5)"
103+
: isActive
104+
? "0 0 0 1px rgba(96, 165, 250, 0.5)"
105+
: "none",
89106
transition: "background-color 160ms ease, box-shadow 160ms ease",
90107
width: "100%",
91108
}}
@@ -102,9 +119,22 @@ function EvolutionPanel({ items, tournamentStatus, runId, setRunId, repoUrl }) {
102119
>
103120
<div className="d-flex align-items-center text-nowrap">
104121
<span className="badge badge-secondary mr-2">{title}</span>
105-
<span className={`text-truncate ${isActive ? "text-white" : "text-muted"}`}>
106-
{meta}
122+
<span
123+
className="font-weight-bold mr-2"
124+
style={{
125+
fontSize: "1.15rem",
126+
color: isBest ? goldColor : isActive ? "#ffffff" : "#e2e8f0",
127+
}}
128+
>
129+
{i18n.t("Score %{score}", { score })}
107130
</span>
131+
{time && (
132+
<span
133+
className={`small text-truncate ${isActive ? "text-white-50" : "text-muted"}`}
134+
>
135+
{time}
136+
</span>
137+
)}
108138
</div>
109139
</button>
110140
);

0 commit comments

Comments
 (0)