Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions src/assets/reload.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/components/SideBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,22 @@ function SideBar() {
<NavLink
className="bg-white/10 px-4 py-1 font-[Open_Sans] font-med capitalize w-full flex items-center justify-around gap-3 rounded-sm border-b-1 border-gray-500"
to='/infinite'
reloadDocument
>
<img src={Infinite} alt="infinite logo png" className="h-3 bg-ambe0 fill-amber-50" />
infinite mode</NavLink>

<NavLink
className="bg-white/10 px-4 py-1 font-[Open_Sans] font-med capitalize w-full flex items-center justify-around gap-3 rounded-sm"
to='/'
reloadDocument
>
<img src={trial} alt="infinite logo png" className="h-4 bg-ambe0 fill-amber-50" />
trial mode
</NavLink>


<ParaLink target='/long-para' content={'long paragraph'} />
<ParaLink target='/long-para' content={'long paragraph'} reloadDocument />

</div>

Expand Down
82 changes: 78 additions & 4 deletions src/components/TypingBoard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import React, { useEffect, useState } from 'react';
import Timer from './Timer';
import { motion, time } from "motion/react"
import TypingCompleteScreen from './TypingCompleteScreen';
import reload from '../assets/reload.svg';
import { useLocation } from 'react-router-dom';


const TypingBoard = (props) => {
// console.log(props)
const [words, setWords] = useState([])
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider refactoring multiple related useState hooks into a single useReducer with a RESET action to centralize and simplify state management.

Here’s one way to collapse all of those individual setters into a single reducer with a RESET action. You’ll end up with one state object and one place that describes how it changes:

// 1) Move your “shape” into a single object
const makeInitialState = (rawQuote) => {
  const cleaned = rawQuote.trim().replace(/\s+/g, ' ')
  return {
    quote:        cleaned,
    currentIdx:   0,
    typedChars:   '',
    wordsPressed: 0,
    rightWords:   0,
    warning:      true,
    typos:        0,
    isCompleted:  false,
    finalTime:    0,
    WPM:          0,
    accuracy:     0,
  }
}

// 2) A reducer that handles RESET (and later could handle KEY_PRESS, etc)
function sessionReducer(state, action) {
  switch (action.type) {
    case 'RESET':
      return makeInitialState(action.quote)
    // you can add more actions here, e.g. KEY_PRESS
    default:
      return state
  }
}

const TypingBoard = ({ quotes }) => {
  // 3) swap all your useStates for one useReducer
  const [session, dispatch] = useReducer(
    sessionReducer,
    makeInitialState(quotes)
  )

  // 4) on props.quotes change just RESET in one line
  useEffect(() => {
    dispatch({ type: 'RESET', quote: quotes })
  }, [quotes])

  // 5) in your key handler you can dispatch KEY_PRESS instead of 10 setters
  useEffect(() => {
    const handleKeyDown = e => {
      if (session.isCompleted) return
      // …
      // dispatch({ type: 'KEY_PRESS', key: e.key })
    }

    document.addEventListener('keydown', handleKeyDown)
    return () => document.removeEventListener('keydown', handleKeyDown)
  }, [session, dispatch])

  // …render using session.currentIdx, session.typedChars, etc.
}

Benefits:

  • All reset logic lives in one place
  • No more “setX” duplication
  • Easier to add new actions (typo counting, completion, etc.) without scattering setters.

const [quote, setQuote] = useState(props.quotes)

Expand All @@ -18,13 +22,66 @@ const TypingBoard = (props) => {
const [WPM, setWPM] = useState(0)
const [accuracy, setAccuracy] = useState(0)


const resetGameStates = () => {
setCurrentIdx(0);
setTypedChars("");
setWordsPressed(0);
setRightWords(0);
setWarning(true);
setTypos(0);
setIsCompeleted(false);
setFinalTime(0);
setWPM(0);
setAccuracy(0);
};

const handleGameRestart = () => {
if (props.onRestart && typeof props.onRestart === 'function') {
props.onRestart(); // Triggers parent to update quotes prop
} else {
// fallback: reset current quote manually
const cleanedQuote = props.quotes.trim().replace(/\s+/g, ' ');
setQuote(cleanedQuote);
resetGameStates();
}
};
// Add this useEffect to update quote when props change and reset all states
useEffect(() => {
// Clean the quote string to remove any hidden characters
const cleanedQuote = props.quotes.trim().replace(/\s+/g, ' ');
setQuote(cleanedQuote);

// Reset all states when quote changes
setCurrentIdx(0);
setTypedChars("");
setWordsPressed(0);
setRightWords(0);
setWarning(true);
setTypos(0);
setIsCompeleted(false);
setFinalTime(0);
setWPM(0);
setAccuracy(0);

// console.log('Quote updated:', cleanedQuote);
// console.log('Quote length:', cleanedQuote.length);
// console.log('Quote characters:', cleanedQuote.split('').map((char, i) => `${i}: "${char}" (${char.charCodeAt(0)})`));
}, [props.quotes]);

useEffect(() => {

const handleKeyDown = (e) => {
const pressedKey = e.key;

// Don't process if typing is completed
if (isCompeleted) return;
Copy link

Choose a reason for hiding this comment

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

issue (typo): Correct spelling of isCompeleted

Use the correct spelling (isCompleted) for clarity and consistency.

Suggested implementation:

            // Don't process if typing is completed
            if (isCompleted) return;

You will also need to rename all other occurrences of isCompeleted to isCompleted in this file (and possibly elsewhere in your codebase) to maintain consistency and avoid reference errors.

Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (isCompeleted) return;
if (isCompeleted) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).


if (/^[a-zA-Z0-9\s]$/.test(pressedKey)) {
// Allow more characters including punctuation
if (/^[a-zA-Z0-9\s.,!?;:'"()-]$/.test(pressedKey)) {
setWordsPressed(prev => prev + 1)

// console.log(`Pressed: "${pressedKey}" (${pressedKey.charCodeAt(0)}), Expected: "${quote[currentIdx]}" (${quote[currentIdx]?.charCodeAt(0)}), CurrentIdx: ${currentIdx}`);

if (pressedKey === quote[currentIdx]) {
const nextIdx = currentIdx + 1;
Expand All @@ -34,10 +91,10 @@ const TypingBoard = (props) => {
setCurrentIdx(nextIdx);
setTypedChars(prev => prev + pressedKey);
setRightWords(prev => prev + 1)
setWarning(prev => prev = true)
setWarning(true) // Fixed assignment operator

} else {
setWarning(prev => prev = false)
setWarning(false) // Fixed assignment operator
setTypos(prev => prev + 1)
}
}
Expand Down Expand Up @@ -74,6 +131,23 @@ const TypingBoard = (props) => {
{/* timer */}
<Timer isCompeleted={isCompeleted} setFinalTime={setFinalTime} />

{/* handling the restart button */}
<button
tabIndex={-1}
// onClick={handleGameRestart}
onMouseDown={(e) => {
e.preventDefault(); // prevent focusing on mousedown
handleGameRestart();
}}
onKeyDown={(e) => {
e.preventDefault(); // block space/enter key presses
e.stopPropagation();
}}
className='reload inline p-2 uppercase font-med bg-white/10 rounded-md border-1 border-gray-500 hover:bg-white/20 transition-colors'
>
<img src={reload} alt="reload" className="h-5" />
</button>


</div>

Expand Down Expand Up @@ -139,4 +213,4 @@ const TypingBoard = (props) => {
);
};

export default TypingBoard;
export default TypingBoard;
36 changes: 24 additions & 12 deletions src/components/TypingCompleteScreen.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import React from 'react'
import { motion } from "motion/react"
import reload from "../assets/reload.svg"
import { useLocation, useNavigate } from 'react-router-dom'

function TypingCompleteScreen({ finalTime, WPM, accuracy, onRestart }) {

const location = useLocation()
const handleGameRestart = () => {
if (onRestart && typeof onRestart === 'function') {
onRestart(); // Call the restart function passed from parent
} else {
// Fallback to page reload if no restart function there
window.location.reload();
}
};

function TypingCompleteScreen({ finalTime, WPM, accuracy }) {
return (
<div className='w-full h-screen z-40 absolute top-0 right-0 text-center font-bold text-5xl flex justify-center items-center'>

<div className=' flex items-center justify-center w-[80%] backdrop-blur-xl px-[10vw] py-[10vh] text-gray-200'>

<motion.div
initial={{
scale: 0,
Expand All @@ -21,18 +32,19 @@ function TypingCompleteScreen({ finalTime, WPM, accuracy }) {

<p className="text-lg inline mx-2 py-1 rounded-sm px-2 text-white bg-white/10">Your time: {finalTime} sec</p>
<p className="text-lg inline mx-2 py-1 rounded-sm px-2 text-white bg-white/10">Your WPM: {WPM}</p>
<p className="text-lg inline mx-2 py-1 rounded-sm px-2 text-white bg-white/10">Your accuracy: {accuracy}
%
</p>
<p className="text-lg inline mx-2 py-1 rounded-sm px-2 text-white bg-white/10">Your accuracy: {accuracy}%</p>

{/* handling the restart button */}
<button
onClick={handleGameRestart}
className='reload inline mx-2 py-1 rounded-sm px-2 text-white bg-white/10 text-lg hover:bg-white/20 transition-colors'
>
<img src={reload} alt="reload" className="h-5" />
</button>
</motion.div>

</div>



</div>

)
}

export default TypingCompleteScreen
export default TypingCompleteScreen;