Skip to content

Commit 4beb556

Browse files
committed
Improve
1 parent e43fa0f commit 4beb556

15 files changed

Lines changed: 749 additions & 381 deletions

File tree

apps/codebattle/assets/js/widgets/App.jsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,18 @@ export function GroupTournamentPage() {
225225
document.getElementById("group-tournament-root") ||
226226
document.getElementById("group-tournament-admin-root");
227227
const tournamentId = container?.dataset?.groupTournamentId;
228+
const tournamentName = container?.dataset?.groupTournamentName;
229+
const tournamentDescription = container?.dataset?.groupTournamentDescription;
228230

229231
return (
230232
<Provider store={store}>
231233
<PersistGate loading={null} persistor={persistor}>
232234
<Suspense>
233-
<GroupTournament tournamentId={tournamentId} />
235+
<GroupTournament
236+
tournamentId={tournamentId}
237+
tournamentName={tournamentName}
238+
tournamentDescription={tournamentDescription}
239+
/>
234240
</Suspense>
235241
</PersistGate>
236242
</Provider>

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ import languages from "../../config/languages";
44
import useEditor from "../../utils/useEditor";
55

66
function EditorPanel({ text, lang }) {
7-
// Map language slug to Monaco language
87
const mappedLanguage = lang ? languages[lang] || lang : "javascript";
98

10-
// Props for useEditor in readOnly mode with empty handlers
119
const editorProps = {
1210
wordWrap: "on",
1311
lineNumbers: "on",
@@ -31,12 +29,12 @@ function EditorPanel({ text, lang }) {
3129
const { options, handleEditorWillMount, handleEditorDidMount } = useEditor(editorProps);
3230

3331
return (
34-
<div className="card border rounded max-vh-66 h-100">
35-
<div className="card-header py-2">
32+
<div className="cb-bg-panel shadow-sm cb-rounded max-vh-66 h-100">
33+
<div className="p-3 border-bottom cb-border-color d-flex align-items-center justify-content-between">
3634
<h6 className="mb-0">Editor</h6>
37-
<small>{lang ? `Language: ${lang}` : ""}</small>
35+
{lang && <small className="text-muted">{lang}</small>}
3836
</div>
39-
<div className="card-body p-3 border-top">
37+
<div className="p-3">
4038
<MonacoEditor
4139
theme="vs-dark"
4240
language={mappedLanguage}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import React from "react";
22

33
function EvolutionPanel({ items, tournamentStatus, setRunId }) {
44
return (
5-
<div className="card border rounded">
6-
<div className="card-header py-2">
5+
<div className="cb-bg-panel shadow-sm cb-rounded">
6+
<div className="p-3 border-bottom cb-border-color">
77
<h6 className="mb-0">Executions History</h6>
88
</div>
9-
<div className="card-body p-3 border-top">
9+
<div className="p-3">
1010
<div className="cb-overflow-y-auto max-vh-50">
1111
{tournamentStatus !== "finished" && <a href="_blank">+ Add Solution</a>}
1212
{items && items.length > 0 && (

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

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as selectors from "../../selectors";
1111
import Loading from "@/components/Loading";
1212
import { requestInviteUpdate } from "@/middlewares/GroupTournament";
1313

14-
function GroupTournamentPage({ tournamentId }) {
14+
function GroupTournamentPage({ tournamentId, tournamentName, tournamentDescription }) {
1515
const dispatch = useDispatch();
1616

1717
const [showInviteWindow, setShowInviteWindow] = useState(false);
@@ -57,64 +57,61 @@ function GroupTournamentPage({ tournamentId }) {
5757

5858
if (invite.state === "creating" || invite.state === "pending") {
5959
return (
60-
<>
61-
<div className="container-fluid h-100">
62-
<div className="row justify-content-center h-100">
63-
<div className="col-lg-5 col-md-5 col-sm-5 px-md-4 align-content-center">
64-
<div className="card cb-card border cb-border-color cb-rounded shadow-sm p-5">
65-
<div className="d-flex">
66-
<button
67-
type="button"
68-
className="btn btn-success text-white cb-rounded w-100"
69-
onClick={openExternalRegistrationWindow}
70-
>
71-
Registration
72-
</button>
73-
<button
74-
type="button"
75-
className="btn btn-secondary cb-rounded w-100 ml-2"
76-
onClick={requestInviteUpdates}
77-
>
78-
Next Step
79-
</button>
80-
</div>
81-
<small className="text-center mt-3">
82-
For this stage you need registrated on a new platform
83-
</small>
60+
<div className="container-fluid h-100">
61+
<div className="row justify-content-center h-100">
62+
<div className="col-lg-5 col-md-6 col-sm-8 px-md-4 align-content-center">
63+
<div className="cb-bg-panel shadow-sm cb-rounded p-5">
64+
{tournamentName && <h5 className="text-center mb-4">{tournamentName}</h5>}
65+
<div className="d-flex">
66+
<button
67+
type="button"
68+
className="btn btn-success text-white cb-rounded w-100"
69+
onClick={openExternalRegistrationWindow}
70+
>
71+
Registration
72+
</button>
73+
<button
74+
type="button"
75+
className="btn btn-secondary cb-rounded w-100 ml-2"
76+
onClick={requestInviteUpdates}
77+
>
78+
Next Step
79+
</button>
8480
</div>
81+
<small className="text-center d-block mt-3">
82+
For this stage you need to register on the external platform
83+
</small>
8584
</div>
8685
</div>
8786
</div>
88-
</>
87+
</div>
8988
);
9089
}
9190

9291
return (
93-
<>
94-
<div className="container-fluid">
95-
<div className="row">
96-
<div className="col-12">
97-
<Header />
98-
</div>
92+
<div className="container-fluid py-3">
93+
<div className="row">
94+
<div className="col-12">
95+
<Header name={tournamentName} status={status} />
9996
</div>
100-
<div className="row mt-1">
101-
<div className="col-lg-3 col-md-3 col-12">
102-
<EvolutionPanel
103-
items={solutionEvolution}
104-
tournamentStatus={status}
105-
setRunId={setRunId}
106-
/>
107-
</div>
108-
<div className="col-lg-6 col-md-6 col-12">
109-
<MainPanel status={status} externalSetup={externalSetup} />
110-
</div>
111-
<div className="col-lg-3 col-md-3 col-12">
112-
<EditorPanel text={code} lang={langSlug} />
113-
<LogPanel logs={logs} className="mt-1" />
114-
</div>
97+
</div>
98+
<div className="row mt-3">
99+
<div className="col-lg-3 col-md-4 col-12 mb-3 mb-md-0">
100+
<EvolutionPanel items={solutionEvolution} tournamentStatus={status} setRunId={setRunId} />
101+
</div>
102+
<div className="col-lg-6 col-md-4 col-12 mb-3 mb-md-0">
103+
<MainPanel
104+
status={status}
105+
externalSetup={externalSetup}
106+
description={tournamentDescription}
107+
/>
108+
</div>
109+
<div className="col-lg-3 col-md-4 col-12">
110+
<EditorPanel text={code} lang={langSlug} />
111+
<LogPanel logs={logs} className="mt-3" />
115112
</div>
116113
</div>
117-
</>
114+
</div>
118115
);
119116
}
120117

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
import React from "react";
22

3-
function Header({ status }) {
4-
return <div className="border rounded p-3">{/* Header component placeholder */}</div>;
3+
function Header({ name, status }) {
4+
const statusBadge = {
5+
active: { className: "badge-success", label: "Active" },
6+
finished: { className: "badge-secondary", label: "Finished" },
7+
loading: { className: "badge-warning", label: "Loading" },
8+
};
9+
10+
const badge = statusBadge[status] || statusBadge.loading;
11+
12+
return (
13+
<div className="cb-bg-panel shadow-sm cb-rounded p-3 d-flex align-items-center justify-content-between">
14+
<h4 className="mb-0">{name || "Group Tournament"}</h4>
15+
<span className={`badge ${badge.className} px-3 py-2`}>{badge.label}</span>
16+
</div>
17+
);
518
}
619

720
export default Header;

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,24 @@ import React from "react";
22

33
function LogPanel({ logs, className }) {
44
return (
5-
<div className={`card border rounded max-vh-33 h-100 ${className || ""}`}>
6-
<div className="card-header py-2">
5+
<div className={`cb-bg-panel shadow-sm cb-rounded max-vh-33 h-100 ${className || ""}`}>
6+
<div className="p-3 border-bottom cb-border-color">
77
<h6 className="mb-0">Execution Logs</h6>
88
</div>
9-
<div className="card-body p-3 border-top overflow-auto">
10-
{/* LogPanel component placeholder */}
11-
{logs && logs.length > 0 && (
12-
<div className="mt-2 small">
9+
<div className="p-3 overflow-auto">
10+
{logs && logs.length > 0 ? (
11+
<div className="small">
1312
<ul className="list-unstyled mb-0">
1413
{logs.slice(0, 3).map((log, idx) => (
1514
<li key={idx} className="text-truncate">
16-
{log}
15+
{log}
1716
</li>
1817
))}
19-
{logs.length > 3 && <li>... and {logs.length - 3} more</li>}
18+
{logs.length > 3 && <li className="text-muted">... and {logs.length - 3} more</li>}
2019
</ul>
2120
</div>
21+
) : (
22+
<div className="small text-muted">No logs yet</div>
2223
)}
2324
</div>
2425
</div>

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

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,37 @@
11
import React from "react";
22

3-
function MainPanel({ status, externalSetup }) {
3+
function MainPanel({ status, externalSetup, description }) {
44
return (
5-
<div className="card border rounded">
6-
<div className="card-header py-2">
5+
<div className="cb-bg-panel shadow-sm cb-rounded">
6+
<div className="p-3 border-bottom cb-border-color">
77
<h6 className="mb-0">Tournament Overview</h6>
8-
<small className="text-muted">Current status and controls</small>
98
</div>
10-
<div className="card-body p-3 border-top max-vh-50 overflow-auto">
11-
<p className="text-muted mb-3">Main content area for tournament information and actions.</p>
9+
<div className="p-3 cb-overflow-y-auto max-vh-50">
10+
{description && (
11+
<div className="mb-3">
12+
<p className="mb-0" style={{ whiteSpace: "pre-wrap" }}>
13+
{description}
14+
</p>
15+
</div>
16+
)}
1217
{externalSetup ? (
1318
<div className="small">
14-
<div>
19+
<div className="mb-1">
1520
<strong>External setup:</strong> {externalSetup.state}
1621
</div>
17-
<div>
22+
<div className="mb-1">
1823
<strong>Repo:</strong> {externalSetup.repoState}
1924
</div>
20-
<div>
25+
<div className="mb-1">
2126
<strong>Role:</strong> {externalSetup.roleState}
2227
</div>
23-
<div>
28+
<div className="mb-1">
2429
<strong>Secret:</strong> {externalSetup.secretState}
2530
</div>
26-
<div>
31+
<div className="mb-1">
2732
<strong>Repo slug:</strong> {externalSetup.repoSlug || "n/a"}
2833
</div>
29-
<div>
34+
<div className="mb-1">
3035
<strong>Repo URL:</strong>{" "}
3136
{externalSetup.repoUrl ? (
3237
<a href={externalSetup.repoUrl} target="_blank" rel="noreferrer">
@@ -43,9 +48,11 @@ function MainPanel({ status, externalSetup }) {
4348
) : null}
4449
</div>
4550
) : (
46-
<div className="text-muted small">
47-
External setup is not required for this tournament.
48-
</div>
51+
!description && (
52+
<div className="small text-muted">
53+
No additional setup is required for this tournament.
54+
</div>
55+
)
4956
)}
5057
</div>
5158
</div>

apps/codebattle/lib/codebattle/user_group_tournament/context.ex

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ defmodule Codebattle.UserGroupTournament.Context do
2020
|> Repo.one()
2121
end
2222

23+
@spec list_users(pos_integer()) :: list(UserGroupTournament.t())
24+
def list_users(group_tournament_id) do
25+
UserGroupTournament
26+
|> where([record], record.group_tournament_id == ^group_tournament_id)
27+
|> preload(:user)
28+
|> order_by([record], desc: record.inserted_at)
29+
|> Repo.all()
30+
end
31+
2332
@spec get_or_create(User.t(), GroupTournament.t()) :: UserGroupTournament.t()
2433
def get_or_create(%User{id: user_id} = user, %GroupTournament{id: group_tournament_id} = group_tournament) do
2534
case get(user_id, group_tournament_id) do
@@ -277,7 +286,8 @@ defmodule Codebattle.UserGroupTournament.Context do
277286
repo_url: nil,
278287
role: @default_repo_role,
279288
secret_key: @default_secret_key,
280-
secret_group: @default_secret_group
289+
secret_group: @default_secret_group,
290+
token: generate_token()
281291
}
282292
end
283293

apps/codebattle/lib/codebattle_web/channels/group_tournament_channel.ex

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,36 @@ defmodule CodebattleWeb.GroupTournamentChannel do
1212
case parse_tournament_id(tournament_id) do
1313
{:ok, parsed_tournament_id} ->
1414
current_user = socket.assigns.current_user
15-
alias_name = current_user.external_oauth_login || current_user.name
1615
group_tournament = GroupTournamentContext.get_group_tournament!(parsed_tournament_id)
1716

18-
invite =
19-
current_user.id
20-
|> InviteContext.get_or_create_invite(parsed_tournament_id)
21-
|> maybe_send_invite_on_join(alias_name)
22-
23-
external_setup = maybe_ensure_external_setup(current_user, group_tournament, invite)
24-
25-
{:ok,
26-
%{
27-
invite: serialize_invite(invite),
28-
external_setup: serialize_external_setup(external_setup, current_user, group_tournament)
29-
}, socket}
17+
if has_access?(current_user, group_tournament) do
18+
join_tournament(socket, current_user, group_tournament, parsed_tournament_id)
19+
else
20+
{:error, %{reason: "not_authorized"}}
21+
end
3022

3123
:error ->
3224
{:error, %{reason: "invalid_tournament_id"}}
3325
end
3426
end
3527

28+
defp join_tournament(socket, current_user, group_tournament, tournament_id) do
29+
alias_name = current_user.external_oauth_login || current_user.name
30+
31+
invite =
32+
current_user.id
33+
|> InviteContext.get_or_create_invite(tournament_id)
34+
|> maybe_send_invite_on_join(alias_name)
35+
36+
external_setup = maybe_ensure_external_setup(current_user, group_tournament, invite)
37+
38+
{:ok,
39+
%{
40+
invite: serialize_invite(invite),
41+
external_setup: serialize_external_setup(external_setup, current_user, group_tournament)
42+
}, socket}
43+
end
44+
3645
def handle_in("request_invite_update", _, socket) do
3746
current_user = socket.assigns.current_user
3847

@@ -151,6 +160,12 @@ defmodule CodebattleWeb.GroupTournamentChannel do
151160
UserGroupTournamentContext.can_lookup_platform_identity?(user)
152161
end
153162

163+
defp has_access?(user, group_tournament) do
164+
group_tournament.creator_id == user.id ||
165+
Codebattle.User.admin_or_moderator?(user) ||
166+
UserGroupTournamentContext.get(user.id, group_tournament.id) != nil
167+
end
168+
154169
defp parse_tournament_id(tournament_id) do
155170
case Integer.parse(tournament_id) do
156171
{parsed_tournament_id, ""} -> {:ok, parsed_tournament_id}

0 commit comments

Comments
 (0)