Felix Zieger
commited on
Commit
·
34f302f
1
Parent(s):
fe9a0c4
input validations
Browse files
src/components/HighScoreBoard.tsx
CHANGED
@@ -78,10 +78,11 @@ export const HighScoreBoard = ({
|
|
78 |
});
|
79 |
|
80 |
const handleSubmitScore = async () => {
|
81 |
-
|
|
|
82 |
toast({
|
83 |
title: "Error",
|
84 |
-
description: "Please enter
|
85 |
variant: "destructive",
|
86 |
});
|
87 |
return;
|
@@ -221,11 +222,16 @@ export const HighScoreBoard = ({
|
|
221 |
{!hasSubmitted && currentScore > 0 && (
|
222 |
<div className="flex gap-4 mb-6">
|
223 |
<Input
|
224 |
-
placeholder="Enter your name"
|
225 |
value={playerName}
|
226 |
-
onChange={(e) =>
|
|
|
|
|
|
|
|
|
227 |
onKeyDown={handleKeyDown}
|
228 |
className="flex-1"
|
|
|
229 |
/>
|
230 |
<Button
|
231 |
onClick={handleSubmitScore}
|
|
|
78 |
});
|
79 |
|
80 |
const handleSubmitScore = async () => {
|
81 |
+
// Validate player name (only alphanumeric characters allowed)
|
82 |
+
if (!playerName.trim() || !/^[a-zA-Z0-9]+$/.test(playerName.trim())) {
|
83 |
toast({
|
84 |
title: "Error",
|
85 |
+
description: "Please enter a valid name (only letters and numbers allowed)",
|
86 |
variant: "destructive",
|
87 |
});
|
88 |
return;
|
|
|
222 |
{!hasSubmitted && currentScore > 0 && (
|
223 |
<div className="flex gap-4 mb-6">
|
224 |
<Input
|
225 |
+
placeholder="Enter your name (letters and numbers only)"
|
226 |
value={playerName}
|
227 |
+
onChange={(e) => {
|
228 |
+
// Only allow alphanumeric input
|
229 |
+
const value = e.target.value.replace(/[^a-zA-Z0-9]/g, '');
|
230 |
+
setPlayerName(value);
|
231 |
+
}}
|
232 |
onKeyDown={handleKeyDown}
|
233 |
className="flex-1"
|
234 |
+
maxLength={20}
|
235 |
/>
|
236 |
<Button
|
237 |
onClick={handleSubmitScore}
|
src/components/game/SentenceBuilder.tsx
CHANGED
@@ -2,6 +2,7 @@ import { Button } from "@/components/ui/button";
|
|
2 |
import { Input } from "@/components/ui/input";
|
3 |
import { motion } from "framer-motion";
|
4 |
import { KeyboardEvent, useRef, useEffect, useState } from "react";
|
|
|
5 |
|
6 |
interface SentenceBuilderProps {
|
7 |
currentWord: string;
|
@@ -27,6 +28,7 @@ export const SentenceBuilder = ({
|
|
27 |
const inputRef = useRef<HTMLInputElement>(null);
|
28 |
const [imageLoaded, setImageLoaded] = useState(false);
|
29 |
const imagePath = `/think_in_sync_assets/${currentWord.toLowerCase()}.jpg`;
|
|
|
30 |
|
31 |
useEffect(() => {
|
32 |
const img = new Image();
|
@@ -55,17 +57,40 @@ export const SentenceBuilder = ({
|
|
55 |
if (e.shiftKey && e.key === 'Enter') {
|
56 |
e.preventDefault();
|
57 |
if (playerInput.trim()) {
|
58 |
-
|
59 |
-
const syntheticEvent = {
|
60 |
-
preventDefault: () => {},
|
61 |
-
} as React.FormEvent;
|
62 |
-
onSubmitWord(syntheticEvent);
|
63 |
}
|
64 |
// Make the guess immediately without waiting for AI response
|
65 |
onMakeGuess();
|
66 |
}
|
67 |
};
|
68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
return (
|
70 |
<motion.div
|
71 |
initial={{ opacity: 0 }}
|
@@ -95,14 +120,18 @@ export const SentenceBuilder = ({
|
|
95 |
{sentence.length > 0 ? sentence.join(" ") : "Start your sentence..."}
|
96 |
</p>
|
97 |
</div>
|
98 |
-
<form onSubmit={
|
99 |
<Input
|
100 |
ref={inputRef}
|
101 |
type="text"
|
102 |
value={playerInput}
|
103 |
-
onChange={(e) =>
|
|
|
|
|
|
|
|
|
104 |
onKeyDown={handleKeyDown}
|
105 |
-
placeholder="Enter your word..."
|
106 |
className="mb-4"
|
107 |
disabled={isAiThinking}
|
108 |
/>
|
|
|
2 |
import { Input } from "@/components/ui/input";
|
3 |
import { motion } from "framer-motion";
|
4 |
import { KeyboardEvent, useRef, useEffect, useState } from "react";
|
5 |
+
import { useToast } from "@/hooks/use-toast";
|
6 |
|
7 |
interface SentenceBuilderProps {
|
8 |
currentWord: string;
|
|
|
28 |
const inputRef = useRef<HTMLInputElement>(null);
|
29 |
const [imageLoaded, setImageLoaded] = useState(false);
|
30 |
const imagePath = `/think_in_sync_assets/${currentWord.toLowerCase()}.jpg`;
|
31 |
+
const { toast } = useToast();
|
32 |
|
33 |
useEffect(() => {
|
34 |
const img = new Image();
|
|
|
57 |
if (e.shiftKey && e.key === 'Enter') {
|
58 |
e.preventDefault();
|
59 |
if (playerInput.trim()) {
|
60 |
+
handleSubmit(e as any);
|
|
|
|
|
|
|
|
|
61 |
}
|
62 |
// Make the guess immediately without waiting for AI response
|
63 |
onMakeGuess();
|
64 |
}
|
65 |
};
|
66 |
|
67 |
+
const handleSubmit = (e: React.FormEvent) => {
|
68 |
+
e.preventDefault();
|
69 |
+
const input = playerInput.trim().toLowerCase();
|
70 |
+
const target = currentWord.toLowerCase();
|
71 |
+
|
72 |
+
// Check if the input contains only letters
|
73 |
+
if (!/^[a-zA-Z]+$/.test(input)) {
|
74 |
+
toast({
|
75 |
+
title: "Invalid Word",
|
76 |
+
description: "Please use only letters (no numbers or special characters)",
|
77 |
+
variant: "destructive",
|
78 |
+
});
|
79 |
+
return;
|
80 |
+
}
|
81 |
+
|
82 |
+
if (input.includes(target)) {
|
83 |
+
toast({
|
84 |
+
title: "Invalid Word",
|
85 |
+
description: `You cannot use words that contain "${currentWord}"`,
|
86 |
+
variant: "destructive",
|
87 |
+
});
|
88 |
+
return;
|
89 |
+
}
|
90 |
+
|
91 |
+
onSubmitWord(e);
|
92 |
+
};
|
93 |
+
|
94 |
return (
|
95 |
<motion.div
|
96 |
initial={{ opacity: 0 }}
|
|
|
120 |
{sentence.length > 0 ? sentence.join(" ") : "Start your sentence..."}
|
121 |
</p>
|
122 |
</div>
|
123 |
+
<form onSubmit={handleSubmit} className="mb-4">
|
124 |
<Input
|
125 |
ref={inputRef}
|
126 |
type="text"
|
127 |
value={playerInput}
|
128 |
+
onChange={(e) => {
|
129 |
+
// Only allow letters in the input
|
130 |
+
const value = e.target.value.replace(/[^a-zA-Z]/g, '');
|
131 |
+
onInputChange(value);
|
132 |
+
}}
|
133 |
onKeyDown={handleKeyDown}
|
134 |
+
placeholder="Enter your word (letters only)..."
|
135 |
className="mb-4"
|
136 |
disabled={isAiThinking}
|
137 |
/>
|
src/components/game/WordDisplay.tsx
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import { Button } from "@/components/ui/button";
|
2 |
import { motion } from "framer-motion";
|
3 |
import { useEffect, useState } from "react";
|
|
|
4 |
|
5 |
interface WordDisplayProps {
|
6 |
currentWord: string;
|
@@ -11,13 +12,16 @@ interface WordDisplayProps {
|
|
11 |
export const WordDisplay = ({ currentWord, successfulRounds, onContinue }: WordDisplayProps) => {
|
12 |
const [imageLoaded, setImageLoaded] = useState(false);
|
13 |
const imagePath = `/think_in_sync_assets/${currentWord.toLowerCase()}.jpg`;
|
|
|
14 |
|
15 |
useEffect(() => {
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
|
|
21 |
|
22 |
return (
|
23 |
<motion.div
|
@@ -27,7 +31,7 @@ export const WordDisplay = ({ currentWord, successfulRounds, onContinue }: WordD
|
|
27 |
>
|
28 |
<h2 className="mb-4 text-2xl font-semibold text-gray-900">Your Word</h2>
|
29 |
<div className="mb-4 overflow-hidden rounded-lg bg-secondary/10">
|
30 |
-
{imageLoaded && (
|
31 |
<img
|
32 |
src={imagePath}
|
33 |
alt={currentWord}
|
|
|
1 |
import { Button } from "@/components/ui/button";
|
2 |
import { motion } from "framer-motion";
|
3 |
import { useEffect, useState } from "react";
|
4 |
+
import { useIsMobile } from "@/hooks/use-mobile";
|
5 |
|
6 |
interface WordDisplayProps {
|
7 |
currentWord: string;
|
|
|
12 |
export const WordDisplay = ({ currentWord, successfulRounds, onContinue }: WordDisplayProps) => {
|
13 |
const [imageLoaded, setImageLoaded] = useState(false);
|
14 |
const imagePath = `/think_in_sync_assets/${currentWord.toLowerCase()}.jpg`;
|
15 |
+
const isMobile = useIsMobile();
|
16 |
|
17 |
useEffect(() => {
|
18 |
+
if (!isMobile) {
|
19 |
+
const img = new Image();
|
20 |
+
img.onload = () => setImageLoaded(true);
|
21 |
+
img.src = imagePath;
|
22 |
+
console.log("Attempting to load image:", imagePath);
|
23 |
+
}
|
24 |
+
}, [imagePath, isMobile]);
|
25 |
|
26 |
return (
|
27 |
<motion.div
|
|
|
31 |
>
|
32 |
<h2 className="mb-4 text-2xl font-semibold text-gray-900">Your Word</h2>
|
33 |
<div className="mb-4 overflow-hidden rounded-lg bg-secondary/10">
|
34 |
+
{!isMobile && imageLoaded && (
|
35 |
<img
|
36 |
src={imagePath}
|
37 |
alt={currentWord}
|