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}
|