Skip to content

Conversation

@nee-hit476
Copy link

@nee-hit476 nee-hit476 commented May 29, 2025

  • Added the Infinite Mode feature in the Infinite section. To activate it, the user must type the phrase "infinite mode of typing." Once activated, four countdown timer options are available for selection. After the chosen timer runs out, typing stops automatically, and the results are displayed.
ekkp6tju9w6kj4i5gbph.mp4
rs5is1c93vbn0gfrjlfu.mp4

Summary by Sourcery

Implement an Infinite Mode in the typing board that is activated by the phrase "infinite mode of typing" and lets users choose from four timer durations before running continuous typing sessions with multiple quotes, automated countdown stop, and result display. Also add simple GithubLogo and TwitterLogo components.

New Features:

  • Activate Infinite Mode via trigger phrase with timer-duration selection.
  • Cycle through a predefined set of sample quotes during Infinite Mode.
  • Automatically stop the test when the countdown ends and show WPM and accuracy results.
  • Provide UI elements to indicate mode, display countdown, exit mode, and restart the test.

Enhancements:

  • Extend keydown handling to accept punctuation characters.

Chores:

  • Add standalone GithubLogo and TwitterLogo components.

@vercel
Copy link

vercel bot commented May 29, 2025

@its-nihit-0407 is attempting to deploy a commit to the deepak's projects Team on Vercel.

A member of the Team first needs to authorize it.

@sourcery-ai
Copy link

sourcery-ai bot commented May 29, 2025

Reviewer's Guide

This PR integrates an infinite typing mode into the typing board by detecting a trigger phrase, prompting the user to select a timer duration, cycling through a predefined list of quotes until time expires, and then displaying the aggregated results; it also introduces two new social logo components.

File-Level Changes

Change Details Files
Introduce infinite mode state and trigger detection
  • Define an infiniteQuotes array of sample passages
  • Add state hooks (isInfiniteMode, currentQuoteIndex, timerDuration, timeLeft, showTimerSettings, infiniteStartTime, timerActive)
  • Use a useEffect to detect the trigger phrase and initialize infinite mode with stats reset and first quote
src/components/InfiniteTypingBoard.jsx
Add timer settings modal and start logic
  • Create a startInfiniteMode function to reset state and begin the timer
  • Render a modal with four duration buttons (30s, 60s, 2min, 5min) and a cancel option
src/components/InfiniteTypingBoard.jsx
Implement countdown and final stats calculation
  • Add a useEffect that decrements timeLeft each second and stops the test on zero
  • Calculate final WPM and accuracy on timer expiry and mark completion
src/components/InfiniteTypingBoard.jsx
Extend key handling for infinite quote cycling
  • Broaden the allowed key regex to include punctuation
  • When a quote completes in infinite mode, immediately load the next passage instead of finishing
src/components/InfiniteTypingBoard.jsx
Update UI to support infinite mode
  • Conditionally render countdown indicator, quote index, exit/restart buttons, and cumulative stats
  • Show the Timer component only in regular mode and display the timer settings modal when needed
src/components/InfiniteTypingBoard.jsx
Add social logo components
  • Implement a GithubLogo component exporting an SVG icon
  • Implement a TwitterLogo component exporting an SVG icon
src/components/GithubLogo.jsx
src/components/TwitterLogo.jsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @its-nihit-0407 - I've reviewed your changes - here's some feedback:

  • The InfiniteTypingBoard component has become very large and could benefit from extracting the timer and infinite‐mode logic into separate hooks or child components for better readability and maintainability.
  • Move static data like the infiniteQuotes array and timer‐option values out of the component (e.g., into a constants file or props) to avoid reinitializing them on every render.
  • In GithubLogo.jsx, use React‐friendly SVG props (e.g., className instead of class, strokeWidth instead of stroke-width) to ensure attributes bind correctly.
Here's what I looked at during the review
  • 🟡 General issues: 5 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

const pressedKey = e.key;

if (/^[a-zA-Z0-9\s]$/.test(pressedKey)) {
if (/^[a-zA-Z0-9\s.,!?;:'"()-]$/.test(pressedKey)) {
Copy link

Choose a reason for hiding this comment

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

question: Keydown handler does not handle non-printable or special keys.

Currently, keys like Backspace, Tab, and arrow keys are ignored. If users need to edit or navigate, consider handling these keys as well.

setIsInfiniteMode(true);
setShowTimerSettings(true); // Show timer settings when entering infinite mode
// Reset stats for infinite mode
setCurrentIdx(0);
Copy link

Choose a reason for hiding this comment

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

suggestion: Quote cycling in infinite mode may repeat quotes in a predictable order.

Since the next quote is always selected in sequence, users may quickly see repeats, especially with a small quote list. Shuffling or tracking used quotes could improve variety.

Suggested implementation:

                        if (isInfiniteMode && timerActive) {
                            // In infinite mode, immediately load next quote if timer is still active
                            // Shuffle and track used quotes for better variety
                            let nextUsedQuotes = [...usedQuotes, currentQuoteIndex];
                            let nextIndex;
                            if (nextUsedQuotes.length === infiniteQuotes.length) {
                                // All quotes used, reshuffle and start over
                                const shuffled = [...infiniteQuotes].sort(() => Math.random() - 0.5);
                                setInfiniteQuotes(shuffled);
                                nextUsedQuotes = [];
                                nextIndex = 0;
                            } else {
                                // Pick a random unused quote
                                const unusedIndexes = infiniteQuotes
                                    .map((_, idx) => idx)
                                    .filter(idx => !nextUsedQuotes.includes(idx));
                                nextIndex = unusedIndexes[Math.floor(Math.random() * unusedIndexes.length)];
                            }
                            setCurrentQuoteIndex(nextIndex);
                            setQuote(infiniteQuotes[nextIndex]);
                            setCurrentIdx(0);
                            setUsedQuotes(nextUsedQuotes);

You will need to:

  1. Add usedQuotes, setUsedQuotes, infiniteQuotes, and setInfiniteQuotes to your component's state using useState.
  2. Initialize usedQuotes as an empty array and infiniteQuotes as a shuffled version of your quotes array when infinite mode starts.
  3. Reset usedQuotes and reshuffle infiniteQuotes when all quotes have been used.

Example state initialization at the top of your component:

const [usedQuotes, setUsedQuotes] = useState([]);
const [infiniteQuotes, setInfiniteQuotes] = useState(() => shuffleArray(initialQuotes));

Where shuffleArray is a helper function:

function shuffleArray(array) {
    return [...array].sort(() => Math.random() - 0.5);
}

Make sure to update these states when infinite mode is toggled or when the quote list changes.

Comment on lines +171 to 180
// Calculate WPM when typing is completed (for regular mode only)
useEffect(() => {
if (isCompeleted && finalTime > 0) {

const timeInMinutes = finalTime / 8;//number 8 is assuring that this salculation will show word per minute and not aplbet ot keysTyped per minute. i am assumned that there is 6-7 words are commmon in words.
const calculatedWPM = Math.round(rightWords / timeInMinutes);
if (isCompeleted && finalTime > 0 && !isInfiniteMode) {
// Fixed WPM calculation for regular mode
const timeInMinutes = finalTime / 60; // Convert seconds to minutes
const calculatedWPM = timeInMinutes > 0 ? Math.round((rightWords / 5) / timeInMinutes) : 0;
setWPM(calculatedWPM);

const calculatedAccuracy = ((rightWords / wordsPressed) * 100).toFixed(2);
const calculatedAccuracy = wordsPressed > 0 ? parseFloat(((rightWords / wordsPressed) * 100).toFixed(2)) : 0;
setAccuracy(calculatedAccuracy);
Copy link

Choose a reason for hiding this comment

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

suggestion: WPM calculation for infinite mode and regular mode uses different logic.

Extract the WPM calculation into a shared function to maintain consistency between modes.

Suggested implementation:

    // Shared WPM calculation function
    const calculateWPM = (wordsCount, timeInSeconds) => {
        const timeInMinutes = timeInSeconds / 60;
        return timeInMinutes > 0 ? Math.round((wordsCount / 5) / timeInMinutes) : 0;
    };

    // Calculate WPM when typing is completed (for regular mode only)
    useEffect(() => {
        if (isCompeleted && finalTime > 0 && !isInfiniteMode) {
            setWPM(calculateWPM(rightWords, finalTime));

If infinite mode calculates WPM elsewhere in this file, update that logic to use the new calculateWPM function as well for consistency.

<svg
stroke="currentColor"
fill="currentColor"
stroke-width="0"
Copy link

Choose a reason for hiding this comment

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

issue: Use camelCase for SVG attributes in JSX.

Update attributes like stroke-width to strokeWidth and class to className to comply with JSX conventions and prevent React warnings.

fill="currentColor"
stroke-width="0"
viewBox="0 0 496 512"
class="text-xl"
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Use className instead of class in JSX.

React uses 'className' for CSS classes; using 'class' may cause rendering problems.


//feature

const InfiniteTypingBoard = (props) => {
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 extracting key-handling, infinite mode logic, and the timer settings modal into separate hooks and components to simplify the main component.

Here are three small, focused extractions that will dramatically reduce the size and complexity of your main component without changing any behavior:

  1. Extract your key‐handling into a useKeyPress hook
  2. Extract infinite-mode state/timer logic into a useInfiniteMode hook
  3. Pull your timer-settings modal into its own TimerSettingsModal component

1) useKeyPress hook

// hooks/useKeyPress.js
import { useEffect } from 'react';

export default function useKeyPress(onKeyPress, deps = []) {
  useEffect(() => {
    function handle(e) {
      onKeyPress(e);
    }
    document.addEventListener('keydown', handle);
    return () => document.removeEventListener('keydown', handle);
  }, deps);
}

Usage in your component:

import useKeyPress from './hooks/useKeyPress';

function InfiniteTypingBoard(props) {
  // ... all your state ...

  const handleKeyDown = (e) => {
    if (showTimerSettings || isCompleted) return;
    // (rest of your logic here)
  };

  useKeyPress(handleKeyDown, [
    quote, currentIdx, isInfiniteMode, timerActive,
    showTimerSettings, wordsPressed, /* etc */
  ]);

  // ...
}

2) useInfiniteMode hook

// hooks/useInfiniteMode.js
import { useState, useEffect } from 'react';

export default function useInfiniteMode(infiniteQuotes, timerDuration) {
  const [isInfiniteMode, setInfinite] = useState(false);
  const [currentQuoteIndex, setQuoteIndex] = useState(0);
  const [timeLeft, setTimeLeft] = useState(timerDuration);
  const [timerActive, setTimerActive] = useState(false);
  const [showTimerSettings, setShowTimerSettings] = useState(false);

  // start & exit logic
  const start = (duration) => {
    setTimerActive(true);
    setTimeLeft(duration);
    setShowTimerSettings(false);
    setQuoteIndex(0);
  };
  const exit = () => {
    setInfinite(false);
    setTimerActive(false);
    setShowTimerSettings(false);
    setTimeLeft(timerDuration);
  };

  // timer countdown
  useEffect(() => {
    if (!isInfiniteMode || !timerActive || timeLeft <= 0) return;
    const iv = setInterval(() => {
      setTimeLeft(t => Math.max(0, t - 1));
    }, 1000);
    return () => clearInterval(iv);
  }, [isInfiniteMode, timerActive, timeLeft]);

  // detect trigger phrase
  useEffect(() => {
    // assume typedChars lives outside and is passed in via a setter
    // when trigger detected => setInfinite(true); setShowTimerSettings(true)
  }, [/* typedChars */]);

  return {
    isInfiniteMode, currentQuoteIndex, timeLeft,
    timerActive, showTimerSettings,
    setShowTimerSettings, setInfinite,
    setQuoteIndex, start, exit
  };
}

In your component you then do:

const {
  isInfiniteMode, currentQuoteIndex, timeLeft,
  timerActive, showTimerSettings,
  setShowTimerSettings, start, exit
} = useInfiniteMode(infiniteQuotes, timerDuration);

3) TimerSettingsModal component

// components/TimerSettingsModal.jsx
export default function TimerSettingsModal({onSelect, onCancel}) {
  const opts = [30, 60, 120, 300];
  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center">
      <div className="bg-white p-6 rounded-lg">
        <h2>Select duration</h2>
        <div className="flex gap-2">
          {opts.map(sec => (
            <button key={sec} onClick={() => onSelect(sec)}>
              {sec / 60 >= 1 ? sec/60 + 'm' : sec + 's'}
            </button>
          ))}
        </div>
        <button onClick={onCancel}>Cancel</button>
      </div>
    </div>
  );
}

And in your main render:

{showTimerSettings && (
  <TimerSettingsModal
    onSelect={start}
    onCancel={() => setShowTimerSettings(false)}
  />
)}

By moving those three chunks out you’ll shrink the main component by several hundred lines, eliminate nested conditionals in effects, and make each piece easy to reason about in isolation.

}

return () => {
if (timer) clearInterval(timer);
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 (timer) clearInterval(timer);
if (timer) {


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).

useEffect(() => {
const handleKeyDown = (e) => {
// Don't process keys if timer settings are shown or test is completed
if (showTimerSettings || isCompeleted) return;
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 (showTimerSettings || isCompeleted) return;
if (showTimerSettings || 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant