Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/bracket viewer #224

Merged
merged 2 commits into from
Jan 6, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 89 additions & 133 deletions app/createBracket/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ const bracketSchema = z.object({
grandFinalType: z.enum(["simple", "double"]),
});

const teamSchema = z.object({
teams: z.array(z.string().min(1)),
});

const storage = new InMemoryDatabase();
const manager = new BracketsManager(storage);

Expand All @@ -50,6 +46,24 @@ export default function Page() {
const [stageData, setStageData] = useState(null);
const [info, setInfo] = useState(null);
const [isBracketsViewerReady, setIsBracketsViewerReady] = useState(false);
const [teams, setTeams] = useState("");
const [error, setError] = useState("");

const handleInputChange = (e) => {
setTeams(e.target.value);
setError("");
};

const handleSubmit = (e) => {
e.preventDefault();

if (isNaN(teams) || teams === "") {
setError("Please enter a valid number.");
} else {
onTeamSubmit(teams);
setTeams("");
}
};
Comment on lines +52 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve input validation and safety

The current validation has several issues:

  1. Using isNaN is unsafe due to type coercion
  2. Missing validation for negative numbers and decimals
  3. No maximum limit for number of teams

Apply these improvements:

 const handleInputChange = (e) => {
-  setTeams(e.target.value);
+  const value = e.target.value.trim();
+  setTeams(value);
   setError("");
 };

 const handleSubmit = (e) => {
   e.preventDefault();
+  const numTeams = Number(teams);
 
-  if (isNaN(teams) || teams === "") {
+  if (Number.isNaN(numTeams) || teams === "" || !Number.isInteger(numTeams) || numTeams <= 0) {
     setError("Please enter a valid number.");
+  } else if (numTeams > 64) {
+    setError("Maximum 64 teams allowed.");
   } else {
-    onTeamSubmit(teams);
+    onTeamSubmit(numTeams);
     setTeams("");
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleInputChange = (e) => {
setTeams(e.target.value);
setError("");
};
const handleSubmit = (e) => {
e.preventDefault();
if (isNaN(teams) || teams === "") {
setError("Please enter a valid number.");
} else {
onTeamSubmit(teams);
setTeams("");
}
};
const handleInputChange = (e) => {
const value = e.target.value.trim();
setTeams(value);
setError("");
};
const handleSubmit = (e) => {
e.preventDefault();
const numTeams = Number(teams);
if (Number.isNaN(numTeams) || teams === "" || !Number.isInteger(numTeams) || numTeams <= 0) {
setError("Please enter a valid number.");
} else if (numTeams > 64) {
setError("Maximum 64 teams allowed.");
} else {
onTeamSubmit(numTeams);
setTeams("");
}
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 60-60: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


const bracketForm = useForm({
resolver: zodResolver(bracketSchema),
Expand All @@ -61,35 +75,20 @@ export default function Page() {
},
});

const teamForm = useForm({
resolver: zodResolver(teamSchema),
defaultValues: {
teams: ["", "", "", ""],
},
});

function onBracketSubmit(values) {
setBracketInfo(values);
setBracketCreated(true);
}

function onTeamSubmit(values) {
if (values.teams.length < 4) {
toast.error("Please enter at least 4 team names.");
return;
}
if ((values.teams.length & (values.teams.length - 1)) !== 0) {
toast.error("Number of teams must be a power of 2 (e.g. 4, 8, 16, etc.)");
if (values < 4) {
toast.error("Number of teams must be greater than 4");
return;
}

const uniqueTeams = new Set(values.teams);
if (uniqueTeams.size !== values.teams.length) {
toast.error("Team names must be unique.");
return;
}
const teams = Array.from({ length: values }, (_, i) => `Team${i + 1}`);

const res = { ...bracketInfo, ...values };
const res = { ...bracketInfo, teams };
Comment on lines +84 to +91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance team generation and validation

The team submission logic could be improved in several ways:

  1. Add maximum team validation
  2. Improve team name generation
  3. Move success toast after successful bracket creation
 function onTeamSubmit(values) {
-  if (values < 4) {
+  if (values < 4 || values > 64) {
-    toast.error("Number of teams must be greater than 4");
+    toast.error("Number of teams must be between 4 and 64");
     return;
   }

-  const teams = Array.from({ length: values }, (_, i) => `Team${i + 1}`);
+  const teams = Array.from(
+    { length: values },
+    (_, i) => `Team ${String(i + 1).padStart(2, '0')}`
+  );

   const res = { ...bracketInfo, teams };
   setInfo(res);

   console.log("Bracket Info", JSON.stringify(info, null, 2));

   if (!showBrackets) {
     setShowBrackets(true);
   }

-  toast.success("Bracket created successfully");
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (values < 4) {
toast.error("Number of teams must be greater than 4");
return;
}
const uniqueTeams = new Set(values.teams);
if (uniqueTeams.size !== values.teams.length) {
toast.error("Team names must be unique.");
return;
}
const teams = Array.from({ length: values }, (_, i) => `Team${i + 1}`);
const res = { ...bracketInfo, ...values };
const res = { ...bracketInfo, teams };
if (values < 4 || values > 64) {
toast.error("Number of teams must be between 4 and 64");
return;
}
const teams = Array.from(
{ length: values },
(_, i) => `Team ${String(i + 1).padStart(2, '0')}`
);
const res = { ...bracketInfo, teams };

setInfo(res);

console.log("Bracket Info", JSON.stringify(info, null, 2));
Expand All @@ -104,12 +103,22 @@ export default function Page() {
async function rendering() {
if (!info) return;

const teamArray = info.teams;
const len = teamArray.length;
const nearestPowerOf2 = Math.pow(2, Math.ceil(Math.log2(len)));
const byesCount = nearestPowerOf2 - len;

const paddedTeams = [
...teamArray,
...Array.from({ length: byesCount }, (_, i) => `Bye ${i + 1}`),
];

try {
await manager.create.stage({
name: info.tournament_name,
tournamentId: 0,
type: info.format,
seeding: info.teams,
seeding: paddedTeams,
settings: {
consolationFinal: info.consolationFinal,
grandFinal: info.grandFinalType,
Expand All @@ -133,56 +142,51 @@ export default function Page() {
async function rerendering() {
if (!isBracketsViewerReady || !stageData) return;

try {
const bracketsViewerNode = document.querySelector(".brackets-viewer");
bracketsViewerNode?.replaceChildren();
// const bracketsViewerNode = document.querySelector(".brackets-viewer");
// bracketsViewerNode?.replaceChildren();

window.bracketsViewer.onMatchClicked = async (match) => {
try {
await manager.update.match({
id: match.id,
opponent1: { score: 5 },
opponent2: { score: 7, result: "win" },
});
} catch (error) {}
const tourneyData2 = await manager.get.currentMatches(0);
const tourneyData = await manager.get.stageData(0);
setStageData(tourneyData);
};
// window.bracketsViewer.onMatchClicked = async (match) => {
// console.log("Match clicked", match);
// try {
// await manager.update.match({
// id: match.id,
// opponent1: { score: 5 },
// opponent2: { score: 7, result: "win" },
// });
// } catch (error) {
// console.error("Error during match update:", error);
// }
// const tourneyData2 = await manager.get.currentMatches(0);
// const tourneyData = await manager.get.stageData(0);
// setStageData(tourneyData);
// };

if (stageData.participant) {
window.bracketsViewer.setParticipantImages(
stageData.participant.map((participant) => ({
participantId: participant.id,
imageUrl: "https://github.githubassets.com/pinned-octocat.svg",
})),
);
}
window.bracketsViewer.setParticipantImages(
stageData.participant.map((participant) => ({
participantId: participant.id,
imageUrl: "https://github.githubassets.com/pinned-octocat.svg",
})),
);

window.bracketsViewer.render(
{
stages: stageData.stage,
matches: stageData.match,
matchGames: stageData.match_game,
participants: stageData.participant,
},
{
customRoundName: (info, t) => {
if (info.fractionOfFinal === 1 / 2) {
await window.bracketsViewer.render(
{
stages: stageData.stage,
matches: stageData.match,
matchGames: stageData.match_game,
participants: stageData.participant,
},
{
customRoundName: (info, t) => {
if (info.fractionOfFinal === 1 / 2) {
if (info.groupType === "single-bracket") {
return "Semi Finals";
} else {
return `${t(`abbreviations.${info.groupType}`)} Semi Finals`;
}
if (info.fractionOfFinal === 1 / 4) {
return "Quarter Finals";
}
if (info.finalType === "grand-final") {
return `Grand Final`;
}
},
}
},
);
} catch (error) {
console.error("Error during rerendering:", error);
}
},
);
}

useEffect(() => {
Expand All @@ -203,13 +207,6 @@ export default function Page() {
}
}, [isBracketsViewerReady, stageData]);

const removeParticipant = (index) => {
const updatedTeams = teamForm
.getValues("teams")
.filter((_, i) => i !== index);
teamForm.setValue("teams", updatedTeams);
};

return (
<div>
<div className="bg-card border p-6 rounded-md max-w-[80%] mx-auto">
Expand Down Expand Up @@ -323,67 +320,26 @@ export default function Page() {
</form>
</Form>
) : (
<Form {...teamForm}>
<form
onSubmit={teamForm.handleSubmit(onTeamSubmit)}
className="space-y-6"
>
<h2 className="text-2xl font-bold mb-4">Enter Team Names</h2>
<p className="text-sm text-muted-foreground mb-4">
Please enter at least 4 team names.
</p>
{teamForm.watch("teams").map((_, index) => (
<FormField
key={index}
control={teamForm.control}
name={`teams.${index}`}
render={({ field }) => (
<FormItem>
<div className="flex items-center space-x-4">
<div>
<FormLabel className="text-base">
Team {index + 1}
</FormLabel>
</div>
<FormControl>
<Input
{...field}
placeholder={`Team ${index + 1} Name`}
className="w-[80%]"
/>
</FormControl>
<Button
type="button"
onClick={() => removeParticipant(index)}
>
Remove
</Button>
</div>
</FormItem>
)}
/>
))}
<form onSubmit={handleSubmit} className="lg:w-1/2 mx-auto">
<h2 className="text-2xl font-bold mb-2">Enter Number of Teams</h2>
<Input
type="text"
id="number"
value={teams}
onChange={handleInputChange}
placeholder="Enter Number of Teams"
/>
{error && <p style={{ color: "red" }}>{error}</p>}
<div className="flex justify-center mt-4">
<Button
type="button"
variant="outline"
onClick={() =>
teamForm.setValue("teams", [...teamForm.watch("teams"), ""])
}
arial-label="add-another-team-btn"
type="submit"
disabled={showBrackets}
arial-label="submit-team-btn"
>
Add Another Team
Submit
</Button>
<div className="flex justify-center">
<Button
type="submit"
disabled={teamForm.formState.isSubmitting || showBrackets}
arial-label="submit-team-btn"
>
Submit Teams
</Button>
</div>
</form>
</Form>
</div>
</form>
Comment on lines +323 to +342
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix input semantics and accessibility issues

Several UI improvements are needed:

  1. Use number input type for semantic HTML
  2. Fix misspelled aria-label attribute
  3. Move inline styles to CSS
 <Input
-  type="text"
+  type="number"
+  min="4"
+  max="64"
   id="number"
   value={teams}
   onChange={handleInputChange}
   placeholder="Enter Number of Teams"
 />
- {error && <p style={{ color: "red" }}>{error}</p>}
+ {error && <p className="text-red-500 mt-2">{error}</p>}
 <div className="flex justify-center mt-4">
   <Button
     type="submit"
     disabled={showBrackets}
-    arial-label="submit-team-btn"
+    aria-label="submit-team-btn"
   >
     Submit
   </Button>
 </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<form onSubmit={handleSubmit} className="lg:w-1/2 mx-auto">
<h2 className="text-2xl font-bold mb-2">Enter Number of Teams</h2>
<Input
type="text"
id="number"
value={teams}
onChange={handleInputChange}
placeholder="Enter Number of Teams"
/>
{error && <p style={{ color: "red" }}>{error}</p>}
<div className="flex justify-center mt-4">
<Button
type="button"
variant="outline"
onClick={() =>
teamForm.setValue("teams", [...teamForm.watch("teams"), ""])
}
arial-label="add-another-team-btn"
type="submit"
disabled={showBrackets}
arial-label="submit-team-btn"
>
Add Another Team
Submit
</Button>
<div className="flex justify-center">
<Button
type="submit"
disabled={teamForm.formState.isSubmitting || showBrackets}
arial-label="submit-team-btn"
>
Submit Teams
</Button>
</div>
</form>
</Form>
</div>
</form>
<form onSubmit={handleSubmit} className="lg:w-1/2 mx-auto">
<h2 className="text-2xl font-bold mb-2">Enter Number of Teams</h2>
<Input
type="number"
min="4"
max="64"
id="number"
value={teams}
onChange={handleInputChange}
placeholder="Enter Number of Teams"
/>
{error && <p className="text-red-500 mt-2">{error}</p>}
<div className="flex justify-center mt-4">
<Button
type="submit"
disabled={showBrackets}
aria-label="submit-team-btn"
>
Submit
</Button>
</div>
</form>

)}
</div>
{showBrackets ? (
Expand Down
Loading